larc r19

232 lines ยท 5.7 KB Raw
1 package convert
2
3 import (
4 "fmt"
5 "sort"
6 "strings"
7
8 "larc.wejust.rest/larc/internal/core"
9 )
10
11 /* Mapping between larc and git data structures.
12 * Handles conversion of revision numbers <-> git SHA,
13 * flat trees <-> hierarchical trees, etc. */
14
15 // RevisionMapping stores bidirectional mapping between larc revision and git SHA
16 type RevisionMapping struct {
17 LarcRev int64
18 GitSHA string
19 }
20
21 // MappingStore tracks mappings during conversion
22 type MappingStore struct {
23 revToSHA map[int64]string // larc revision -> git SHA
24 shaToRev map[string]int64 // git SHA -> larc revision
25 BlobMap map[string]string // larc blob hash -> git blob SHA (or vice versa) - exported for git handlers
26 }
27
28 // NewMappingStore creates a new mapping store
29 func NewMappingStore() *MappingStore {
30 return &MappingStore{
31 revToSHA: make(map[int64]string),
32 shaToRev: make(map[string]int64),
33 BlobMap: make(map[string]string),
34 }
35 }
36
37 // AddRevisionMapping adds a revision <-> SHA mapping
38 func (m *MappingStore) AddRevisionMapping(rev int64, sha string) {
39 m.revToSHA[rev] = sha
40 m.shaToRev[sha] = rev
41 }
42
43 // GetGitSHA returns git SHA for larc revision
44 func (m *MappingStore) GetGitSHA(rev int64) (string, bool) {
45 sha, ok := m.revToSHA[rev]
46 return sha, ok
47 }
48
49 // GetLarcRev returns larc revision for git SHA
50 func (m *MappingStore) GetLarcRev(sha string) (int64, bool) {
51 rev, ok := m.shaToRev[sha]
52 return rev, ok
53 }
54
55 // AddBlobMapping adds a blob hash mapping
56 func (m *MappingStore) AddBlobMapping(larcHash, gitSHA string) {
57 m.BlobMap[larcHash] = gitSHA
58 }
59
60 // GetGitBlobSHA returns git blob SHA for larc blob hash
61 func (m *MappingStore) GetGitBlobSHA(larcHash string) (string, bool) {
62 sha, ok := m.BlobMap[larcHash]
63 return sha, ok
64 }
65
66 // TreeNode represents a hierarchical tree node for git
67 type TreeNode struct {
68 Name string
69 Mode uint32
70 IsDir bool
71 BlobSHA string // for files
72 Children map[string]*TreeNode // for directories
73 }
74
75 // NewTreeNode creates a new tree node
76 func NewTreeNode(name string, isDir bool) *TreeNode {
77 node := &TreeNode{
78 Name: name,
79 IsDir: isDir,
80 }
81 if isDir {
82 node.Children = make(map[string]*TreeNode)
83 node.Mode = 0040000 // git tree mode
84 }
85 return node
86 }
87
88 // FlatTreeToHierarchical converts larc flat tree to hierarchical structure for git
89 func FlatTreeToHierarchical(entries []core.TreeEntry) *TreeNode {
90 root := NewTreeNode("", true)
91
92 for _, entry := range entries {
93 if entry.Kind == core.EntryKindDir {
94 continue // skip directories, we'll create them as needed
95 }
96
97 parts := strings.Split(entry.Path, "/")
98 current := root
99
100 /* traverse/create directory path */
101 for i := 0; i < len(parts)-1; i++ {
102 dirName := parts[i]
103 if child, ok := current.Children[dirName]; ok {
104 current = child
105 } else {
106 newDir := NewTreeNode(dirName, true)
107 current.Children[dirName] = newDir
108 current = newDir
109 }
110 }
111
112 /* add file entry */
113 fileName := parts[len(parts)-1]
114 fileNode := NewTreeNode(fileName, false)
115 fileNode.Mode = entry.Mode
116 fileNode.BlobSHA = entry.BlobHash // will be remapped later
117 current.Children[fileName] = fileNode
118 }
119
120 return root
121 }
122
123 // HierarchicalToFlatTree converts git hierarchical tree to larc flat tree
124 func HierarchicalToFlatTree(root *TreeNode, prefix string) []core.TreeEntry {
125 var entries []core.TreeEntry
126
127 /* sort children for consistent ordering */
128 names := make([]string, 0, len(root.Children))
129 for name := range root.Children {
130 names = append(names, name)
131 }
132 sort.Strings(names)
133
134 for _, name := range names {
135 child := root.Children[name]
136 path := name
137 if prefix != "" {
138 path = prefix + "/" + name
139 }
140
141 if child.IsDir {
142 /* recurse into directory */
143 subEntries := HierarchicalToFlatTree(child, path)
144 entries = append(entries, subEntries...)
145 } else {
146 /* add file entry */
147 entries = append(entries, core.TreeEntry{
148 Path: path,
149 Mode: child.Mode,
150 BlobHash: child.BlobSHA,
151 Kind: core.EntryKindFile,
152 })
153 }
154 }
155
156 return entries
157 }
158
159 // ConvertBranchName converts larc branch name to git ref name
160 func ConvertBranchName(larcBranch string) string {
161 /* "port" -> "main" (default branch mapping) */
162 if larcBranch == core.DefaultBranchName {
163 return "main"
164 }
165 return larcBranch
166 }
167
168 // ConvertRefToBranch converts git ref name to larc branch name
169 func ConvertRefToBranch(gitRef string) string {
170 /* strip "refs/heads/" prefix if present */
171 ref := strings.TrimPrefix(gitRef, "refs/heads/")
172
173 /* "main" or "master" -> "port" */
174 if ref == "main" || ref == "master" {
175 return core.DefaultBranchName
176 }
177 return ref
178 }
179
180 // FormatRevisionMessage adds larc revision info to git commit message
181 func FormatRevisionMessage(message string, rev int64) string {
182 return fmt.Sprintf("%s\n\n[larc:r%d]", message, rev)
183 }
184
185 // ParseRevisionFromMessage extracts larc revision from git commit message
186 func ParseRevisionFromMessage(message string) (int64, bool) {
187 /* look for [larc:rN] pattern */
188 idx := strings.Index(message, "[larc:r")
189 if idx == -1 {
190 return 0, false
191 }
192
193 var rev int64
194 _, err := fmt.Sscanf(message[idx:], "[larc:r%d]", &rev)
195 if err != nil {
196 return 0, false
197 }
198
199 return rev, true
200 }
201
202 // ConvertMode converts larc mode to git mode
203 func ConvertMode(mode uint32, kind core.EntryKind) uint32 {
204 switch kind {
205 case core.EntryKindSymlink:
206 return 0120000 // git symlink
207 case core.EntryKindFile:
208 if mode&0111 != 0 {
209 return 0100755 // executable
210 }
211 return 0100644 // regular file
212 default:
213 return 0100644
214 }
215 }
216
217 // ConvertGitMode converts git mode to larc mode
218 func ConvertGitMode(gitMode uint32) (uint32, core.EntryKind) {
219 switch gitMode {
220 case 0120000:
221 return 0777, core.EntryKindSymlink
222 case 0100755:
223 return 0755, core.EntryKindFile
224 case 0100644:
225 return 0644, core.EntryKindFile
226 case 0040000:
227 return 0755, core.EntryKindDir
228 default:
229 return 0644, core.EntryKindFile
230 }
231 }
232