Fork of Tangled for Furgit integration

knotserver/git: Use furgit for refs too

This commit was primarily produced by a large language model. While it
has been tested and reviewed and no major flaws were discovered, a full
review has not been performed. Do not merge.

runxiyu.tngl.sh d1a9cbda f0d2860e

verified
+356 -163
+15 -63
knotserver/git/branch.go
··· 3 3 import ( 4 4 "fmt" 5 5 "slices" 6 - "strconv" 7 6 "strings" 8 - "time" 9 7 10 8 "github.com/go-git/go-git/v5/plumbing" 11 - "github.com/go-git/go-git/v5/plumbing/object" 12 9 "tangled.org/core/types" 13 10 ) 14 11 15 12 func (g *GitRepo) Branches() ([]types.Branch, error) { 16 - fields := []string{ 17 - "refname:short", 18 - "objectname", 19 - "authorname", 20 - "authoremail", 21 - "authordate:unix", 22 - "committername", 23 - "committeremail", 24 - "committerdate:unix", 25 - "tree", 26 - "parent", 27 - "contents", 28 - } 29 - 30 - var outFormat strings.Builder 31 - outFormat.WriteString("--format=") 32 - for i, f := range fields { 33 - if i != 0 { 34 - outFormat.WriteString(fieldSeparator) 35 - } 36 - outFormat.WriteString(fmt.Sprintf("%%(%s)", f)) 37 - } 38 - outFormat.WriteString("") 39 - outFormat.WriteString(recordSeparator) 40 - 41 - output, err := g.forEachRef(outFormat.String(), "refs/heads") 13 + output, err := g.forEachRef( 14 + fmt.Sprintf("--format=%%(refname:short)%s%%(objectname)%s", fieldSeparator, recordSeparator), 15 + "refs/heads", 16 + ) 42 17 if err != nil { 43 18 return nil, fmt.Errorf("failed to get branches: %w", err) 44 19 } ··· 54 29 defaultBranch, _ := g.FindMainBranch() 55 30 56 31 for _, line := range records { 57 - parts := strings.SplitN(strings.TrimSpace(line), fieldSeparator, len(fields)) 58 - if len(parts) < 6 { 32 + parts := strings.SplitN(strings.TrimSpace(line), fieldSeparator, 2) 33 + if len(parts) < 2 { 59 34 continue 60 35 } 61 36 62 37 branchName := parts[0] 63 - commitHash := plumbing.NewHash(parts[1]) 64 - authorName := parts[2] 65 - authorEmail := strings.TrimSuffix(strings.TrimPrefix(parts[3], "<"), ">") 66 - authorDate := parts[4] 67 - committerName := parts[5] 68 - committerEmail := strings.TrimSuffix(strings.TrimPrefix(parts[6], "<"), ">") 69 - committerDate := parts[7] 70 - treeHash := plumbing.NewHash(parts[8]) 71 - parentHash := plumbing.NewHash(parts[9]) 72 - message := parts[10] 38 + commitHash := parts[1] 73 39 74 - // parse creation time 75 - var authoredAt, committedAt time.Time 76 - if unix, err := strconv.ParseInt(authorDate, 10, 64); err == nil { 77 - authoredAt = time.Unix(unix, 0) 40 + hash, err := g.furgitRepo.ParseHash(commitHash) 41 + if err != nil { 42 + continue 78 43 } 79 - if unix, err := strconv.ParseInt(committerDate, 10, 64); err == nil { 80 - committedAt = time.Unix(unix, 0) 44 + 45 + commit, err := g.commitFromHash(hash) 46 + if err != nil { 47 + continue 81 48 } 82 49 83 50 branch := types.Branch{ 84 51 IsDefault: branchName == defaultBranch, 85 52 Reference: types.Reference{ 86 53 Name: branchName, 87 - Hash: commitHash.String(), 88 - }, 89 - Commit: &object.Commit{ 90 54 Hash: commitHash, 91 - Author: object.Signature{ 92 - Name: authorName, 93 - Email: authorEmail, 94 - When: authoredAt, 95 - }, 96 - Committer: object.Signature{ 97 - Name: committerName, 98 - Email: committerEmail, 99 - When: committedAt, 100 - }, 101 - TreeHash: treeHash, 102 - ParentHashes: []plumbing.Hash{parentHash}, 103 - Message: message, 104 55 }, 56 + Commit: commit, 105 57 } 106 58 107 59 branches = append(branches, branch)
+276 -55
knotserver/git/diff.go
··· 3 3 import ( 4 4 "bytes" 5 5 "fmt" 6 - "log" 7 6 "os" 8 7 "os/exec" 8 + "path" 9 9 "slices" 10 10 "strings" 11 11 12 + "git.sr.ht/~runxiyu/furgit" 12 13 "github.com/bluekeyes/go-gitdiff/gitdiff" 14 + "github.com/go-enry/go-enry/v2" 13 15 "github.com/go-git/go-git/v5/plumbing" 14 16 "github.com/go-git/go-git/v5/plumbing/object" 15 17 "tangled.org/core/patchutil" ··· 17 19 ) 18 20 19 21 func (g *GitRepo) Diff() (*types.NiceDiff, error) { 20 - repo, err := g.goGitRepo() 22 + commit, err := g.storedCommit(g.h) 21 23 if err != nil { 22 24 return nil, fmt.Errorf("commit object: %w", err) 23 25 } 24 26 25 - c, err := repo.CommitObject(plumbingHashFromFurgit(g.h)) 27 + commitTree, err := g.readTree(commit.Tree) 26 28 if err != nil { 27 - return nil, fmt.Errorf("commit object: %w", err) 29 + return nil, fmt.Errorf("commit tree: %w", err) 28 30 } 29 31 30 - patch := &object.Patch{} 31 - commitTree, err := c.Tree() 32 - parent := &object.Commit{} 33 - if err == nil { 34 - parentTree := &object.Tree{} 35 - if c.NumParents() != 0 { 36 - parent, err = c.Parents().Next() 37 - if err == nil { 38 - parentTree, err = parent.Tree() 39 - if err == nil { 40 - patch, err = parentTree.Patch(commitTree) 41 - if err != nil { 42 - return nil, fmt.Errorf("patch: %w", err) 43 - } 44 - } 45 - } 46 - } else { 47 - patch, err = parentTree.Patch(commitTree) 48 - if err != nil { 49 - return nil, fmt.Errorf("patch: %w", err) 50 - } 32 + var parent *furgit.StoredCommit 33 + var parentTree *furgit.StoredTree 34 + if len(commit.Parents) > 0 { 35 + parent, err = g.storedCommit(commit.Parents[0]) 36 + if err != nil { 37 + return nil, fmt.Errorf("parent commit: %w", err) 38 + } 39 + parentTree, err = g.readTree(parent.Tree) 40 + if err != nil { 41 + return nil, fmt.Errorf("parent tree: %w", err) 51 42 } 52 43 } 53 44 54 - diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String())) 45 + patchStr, parsedDiffs, err := g.buildPatchFromTrees(parentTree, commitTree) 55 46 if err != nil { 56 - log.Println(err) 47 + return nil, fmt.Errorf("building diff: %w", err) 57 48 } 58 49 59 50 nd := types.NiceDiff{} 60 - for _, d := range diffs { 51 + for _, d := range parsedDiffs { 61 52 ndiff := types.Diff{} 62 53 ndiff.Name.New = d.NewName 63 54 ndiff.Name.Old = d.OldName ··· 82 73 nd.Diff = append(nd.Diff, ndiff) 83 74 } 84 75 85 - nd.Stat.FilesChanged = len(diffs) 86 - nd.Commit.This = c.Hash.String() 87 - nd.Commit.PGPSignature = c.PGPSignature 88 - nd.Commit.Committer = c.Committer 89 - nd.Commit.Tree = c.TreeHash.String() 76 + nd.Stat.FilesChanged = len(parsedDiffs) 77 + nd.Commit.This = commit.Hash().String() 78 + if parent != nil { 79 + nd.Commit.Parent = parent.Hash().String() 80 + } 81 + nd.Commit.Author = convertIdent(commit.Author) 82 + nd.Commit.Committer = convertIdent(commit.Committer) 83 + nd.Commit.Tree = commit.Tree.String() 84 + nd.Commit.Message = string(commit.Message) 85 + nd.Commit.PGPSignature = "" 90 86 91 - if parent.Hash.IsZero() { 92 - nd.Commit.Parent = "" 93 - } else { 94 - nd.Commit.Parent = parent.Hash.String() 87 + if commit.ChangeID != "" { 88 + nd.Commit.ChangedId = commit.ChangeID 95 89 } 96 - nd.Commit.Author = c.Author 97 - nd.Commit.Message = c.Message 98 90 99 - if v, ok := c.ExtraHeaders["change-id"]; ok { 100 - nd.Commit.ChangedId = string(v) 91 + // keep raw patch lines for downstream consumers that may expect it 92 + if patchStr == "" { 93 + nd.Stat.FilesChanged = 0 101 94 } 102 95 103 96 return &nd, nil 104 97 } 105 98 106 99 func (g *GitRepo) DiffTree(commit1, commit2 *object.Commit) (*types.DiffTree, error) { 107 - tree1, err := commit1.Tree() 100 + oldHash, err := g.furgitRepo.ParseHash(commit1.Hash.String()) 108 101 if err != nil { 109 102 return nil, err 110 103 } 111 - 112 - tree2, err := commit2.Tree() 104 + newHash, err := g.furgitRepo.ParseHash(commit2.Hash.String()) 113 105 if err != nil { 114 106 return nil, err 115 107 } 116 108 117 - diff, err := object.DiffTree(tree1, tree2) 109 + oldCommit, err := g.storedCommit(oldHash) 110 + if err != nil { 111 + return nil, err 112 + } 113 + newCommit, err := g.storedCommit(newHash) 118 114 if err != nil { 119 115 return nil, err 120 116 } 121 117 122 - patch, err := diff.Patch() 118 + oldTree, err := g.readTree(oldCommit.Tree) 119 + if err != nil { 120 + return nil, err 121 + } 122 + newTree, err := g.readTree(newCommit.Tree) 123 123 if err != nil { 124 124 return nil, err 125 125 } 126 126 127 - diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String())) 127 + patchStr, parsedDiffs, err := g.buildPatchFromTrees(oldTree, newTree) 128 128 if err != nil { 129 129 return nil, err 130 130 } ··· 132 132 return &types.DiffTree{ 133 133 Rev1: commit1.Hash.String(), 134 134 Rev2: commit2.Hash.String(), 135 - Patch: patch.String(), 136 - Diff: diffs, 135 + Patch: patchStr, 136 + Diff: parsedDiffs, 137 137 }, nil 138 138 } 139 139 ··· 173 173 } 174 174 175 175 func (g *GitRepo) ResolveRevision(revStr string) (*object.Commit, error) { 176 - repo, err := g.goGitRepo() 176 + hash, err := g.resolveHash(revStr) 177 177 if err != nil { 178 178 return nil, fmt.Errorf("resolving revision %s: %w", revStr, err) 179 179 } 180 180 181 - rev, err := repo.ResolveRevision(plumbing.Revision(revStr)) 181 + commit, err := g.commitFromHash(hash) 182 + if err != nil { 183 + return nil, fmt.Errorf("getting commit for %s: %w", revStr, err) 184 + } 185 + 186 + return commit, nil 187 + } 188 + 189 + func (g *GitRepo) buildPatchFromTrees(oldTree, newTree *furgit.StoredTree) (string, []*gitdiff.File, error) { 190 + entries, err := g.treeDiffEntries(oldTree, newTree) 191 + if err != nil { 192 + return "", nil, err 193 + } 194 + 195 + if len(entries) == 0 { 196 + return "", nil, nil 197 + } 198 + 199 + var patch strings.Builder 200 + for _, entry := range entries { 201 + filePatch, _, err := g.patchFromEntry(entry) 202 + if err != nil { 203 + return "", nil, err 204 + } 205 + if filePatch == "" { 206 + continue 207 + } 208 + patch.WriteString(filePatch) 209 + if !strings.HasSuffix(filePatch, "\n") { 210 + patch.WriteString("\n") 211 + } 212 + } 213 + 214 + patchStr := patch.String() 215 + if strings.TrimSpace(patchStr) == "" { 216 + return "", nil, nil 217 + } 218 + 219 + diffs, _, err := gitdiff.Parse(strings.NewReader(patchStr)) 220 + if err != nil { 221 + return patchStr, nil, err 222 + } 223 + 224 + return patchStr, diffs, nil 225 + } 226 + 227 + func (g *GitRepo) treeDiffEntries(oldTree, newTree *furgit.StoredTree) ([]furgit.TreeDiffEntry, error) { 228 + if newTree == nil { 229 + return nil, nil 230 + } 231 + 232 + if oldTree == nil { 233 + var entries []furgit.TreeDiffEntry 234 + if err := g.walkAddedEntries(newTree, "", &entries); err != nil { 235 + return nil, err 236 + } 237 + return entries, nil 238 + } 239 + 240 + return g.furgitRepo.DiffTrees(oldTree, newTree) 241 + } 242 + 243 + func (g *GitRepo) walkAddedEntries(tree *furgit.StoredTree, prefix string, out *[]furgit.TreeDiffEntry) error { 244 + for _, e := range tree.Entries { 245 + name := path.Join(prefix, string(e.Name)) 246 + if e.Mode == furgit.FileModeDir { 247 + subtree, err := g.readTree(e.ID) 248 + if err != nil { 249 + return err 250 + } 251 + if err := g.walkAddedEntries(subtree, name, out); err != nil { 252 + return err 253 + } 254 + continue 255 + } 256 + 257 + entry := e 258 + entry.Name = []byte(name) 259 + *out = append(*out, furgit.TreeDiffEntry{ 260 + Path: []byte(name), 261 + Kind: furgit.TreeDiffEntryKindAdded, 262 + New: &entry, 263 + }) 264 + } 265 + return nil 266 + } 267 + 268 + func (g *GitRepo) patchFromEntry(entry furgit.TreeDiffEntry) (string, bool, error) { 269 + pathStr := string(entry.Path) 270 + if pathStr == "" { 271 + if entry.New != nil { 272 + pathStr = string(entry.New.Name) 273 + } else if entry.Old != nil { 274 + pathStr = string(entry.Old.Name) 275 + } 276 + } 277 + if pathStr == "" { 278 + return "", false, nil 279 + } 280 + 281 + // skip directory-only diffs; only render blobs 282 + if (entry.New != nil && entry.New.Mode == furgit.FileModeDir) || (entry.Old != nil && entry.Old.Mode == furgit.FileModeDir) { 283 + return "", false, nil 284 + } 285 + 286 + oldData, err := g.entryData(entry.Old) 287 + if err != nil { 288 + return "", false, err 289 + } 290 + newData, err := g.entryData(entry.New) 291 + if err != nil { 292 + return "", false, err 293 + } 294 + 295 + isBinary := enry.IsBinary(oldData) || enry.IsBinary(newData) 296 + 297 + var b strings.Builder 298 + b.WriteString(fmt.Sprintf("diff --git a/%s b/%s\n", pathStr, pathStr)) 299 + 300 + switch entry.Kind { 301 + case furgit.TreeDiffEntryKindAdded: 302 + b.WriteString(fmt.Sprintf("new file mode %s\n", formatFileMode(entry.New.Mode))) 303 + b.WriteString("--- /dev/null\n") 304 + b.WriteString(fmt.Sprintf("+++ b/%s\n", pathStr)) 305 + case furgit.TreeDiffEntryKindDeleted: 306 + b.WriteString(fmt.Sprintf("deleted file mode %s\n", formatFileMode(entry.Old.Mode))) 307 + b.WriteString(fmt.Sprintf("--- a/%s\n", pathStr)) 308 + b.WriteString("+++ /dev/null\n") 309 + default: 310 + b.WriteString(fmt.Sprintf("index %s..%s %s\n", shortHash(entry.Old.ID), shortHash(entry.New.ID), formatFileMode(entry.New.Mode))) 311 + b.WriteString(fmt.Sprintf("--- a/%s\n", pathStr)) 312 + b.WriteString(fmt.Sprintf("+++ b/%s\n", pathStr)) 313 + } 314 + 315 + if isBinary { 316 + b.WriteString("Binary files differ\n\n") 317 + return b.String(), true, nil 318 + } 319 + 320 + chunks, err := furgit.DiffBytes(oldData, newData) 182 321 if err != nil { 183 - return nil, fmt.Errorf("resolving revision %s: %w", revStr, err) 322 + return "", false, err 323 + } 324 + 325 + if len(chunks) == 0 { 326 + b.WriteString("\n") 327 + return b.String(), false, nil 328 + } 329 + 330 + oldLines := countLines(oldData) 331 + newLines := countLines(newData) 332 + 333 + b.WriteString(fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", hunkStart(oldLines), oldLines, hunkStart(newLines), newLines)) 334 + 335 + oldLineNum := 1 336 + newLineNum := 1 337 + for _, chunk := range chunks { 338 + lines := splitLines(chunk.Data) 339 + for _, line := range lines { 340 + prefix := ' ' 341 + switch chunk.Kind { 342 + case furgit.BytesDiffChunkKindAdded: 343 + prefix = '+' 344 + newLineNum++ 345 + case furgit.BytesDiffChunkKindDeleted: 346 + prefix = '-' 347 + oldLineNum++ 348 + default: 349 + oldLineNum++ 350 + newLineNum++ 351 + } 352 + b.WriteByte(byte(prefix)) 353 + b.WriteString(line) 354 + if !strings.HasSuffix(line, "\n") { 355 + b.WriteString("\n") 356 + } 357 + } 184 358 } 185 359 186 - commit, err := repo.CommitObject(*rev) 360 + if b.Len() > 0 && !strings.HasSuffix(b.String(), "\n") { 361 + b.WriteString("\n") 362 + } 363 + 364 + return b.String(), false, nil 365 + } 366 + 367 + func (g *GitRepo) entryData(entry *furgit.TreeEntry) ([]byte, error) { 368 + if entry == nil { 369 + return nil, nil 370 + } 371 + blob, err := g.readBlob(entry.ID) 187 372 if err != nil { 373 + return nil, err 374 + } 375 + return blob.Data, nil 376 + } 188 377 189 - return nil, fmt.Errorf("getting commit for %s: %w", revStr, err) 378 + func hunkStart(lines int) int { 379 + if lines == 0 { 380 + return 0 381 + } 382 + return 1 383 + } 384 + 385 + func shortHash(h furgit.Hash) string { 386 + s := h.String() 387 + if len(s) < 7 { 388 + return s 389 + } 390 + return s[:7] 391 + } 392 + 393 + func countLines(data []byte) int { 394 + if len(data) == 0 { 395 + return 0 190 396 } 397 + count := strings.Count(string(data), "\n") 398 + if data[len(data)-1] != '\n' { 399 + count++ 400 + } 401 + return count 402 + } 191 403 192 - return commit, nil 404 + func splitLines(data []byte) []string { 405 + if len(data) == 0 { 406 + return nil 407 + } 408 + text := string(data) 409 + parts := strings.SplitAfter(text, "\n") 410 + if len(parts) > 0 && parts[len(parts)-1] == "" { 411 + parts = parts[:len(parts)-1] 412 + } 413 + return parts 193 414 } 194 415 195 416 func (g *GitRepo) commitsBetween(newCommit, oldCommit *object.Commit) ([]*object.Commit, error) {
+30 -16
knotserver/git/git.go
··· 56 56 return hash, nil 57 57 } 58 58 59 - if hash, err := g.furgitRepo.ResolveRefFully(ref); err == nil { 60 - return hash, nil 59 + if strings.HasPrefix(ref, "refs/") || ref == "HEAD" { 60 + if hash, err := g.furgitRepo.ResolveRefFully(ref); err == nil { 61 + return hash, nil 62 + } 61 63 } 62 64 63 - goRepo, err := g.goGitRepo() 64 - if err != nil { 65 - return furgit.Hash{}, fmt.Errorf("opening go-git repo: %w", err) 65 + // try common shorthand refs 66 + candidates := []string{ 67 + path.Join("refs", "heads", ref), 68 + path.Join("refs", "tags", ref), 69 + ref, 66 70 } 67 - 68 - resolved, err := goRepo.ResolveRevision(plumbing.Revision(ref)) 69 - if err != nil { 70 - return furgit.Hash{}, err 71 + for _, candidate := range candidates { 72 + if hash, err := g.furgitRepo.ResolveRefFully(candidate); err == nil { 73 + return hash, nil 74 + } 71 75 } 72 76 73 - return g.furgitRepo.ParseHash(resolved.String()) 77 + return furgit.Hash{}, fmt.Errorf("resolving ref %s: %w", ref, furgit.ErrNotFound) 74 78 } 75 79 76 80 func plumbingHashFromFurgit(hash furgit.Hash) plumbing.Hash { ··· 278 282 } 279 283 280 284 func (g *GitRepo) Branch(name string) (*plumbing.Reference, error) { 281 - repo, err := g.goGitRepo() 282 - if err != nil { 283 - return nil, fmt.Errorf("branch: %w", err) 285 + branchName := name 286 + if !strings.HasPrefix(branchName, "refs/") { 287 + branchName = path.Join("refs", "heads", branchName) 284 288 } 285 289 286 - ref, err := repo.Reference(plumbing.NewBranchReferenceName(name), false) 290 + hash, err := g.furgitRepo.ResolveRefFully(branchName) 287 291 if err != nil { 288 292 return nil, fmt.Errorf("branch: %w", err) 289 293 } 290 294 295 + ref := plumbing.NewHashReference(plumbing.ReferenceName(branchName), plumbingHashFromFurgit(hash)) 291 296 if !ref.Name().IsBranch() { 292 297 return nil, fmt.Errorf("branch: %s is not a branch", ref.Name()) 293 298 } ··· 306 311 } 307 312 308 313 func (g *GitRepo) FindMainBranch() (string, error) { 309 - output, err := g.revParse("--abbrev-ref", "HEAD") 314 + ref, err := g.furgitRepo.ResolveRef("") 310 315 if err != nil { 311 316 return "", fmt.Errorf("failed to find main branch: %w", err) 312 317 } 313 318 314 - return strings.TrimSpace(string(output)), nil 319 + target := ref.Ref 320 + if target == "" { 321 + target = ref.Name 322 + } 323 + 324 + if strings.HasPrefix(target, "refs/heads/") { 325 + return strings.TrimPrefix(target, "refs/heads/"), nil 326 + } 327 + 328 + return target, nil 315 329 } 316 330 317 331 // WriteTar writes itself from a tree into a binary tar file format.
+35 -29
knotserver/git/tag.go
··· 6 6 "strings" 7 7 "time" 8 8 9 + "git.sr.ht/~runxiyu/furgit" 9 10 "github.com/go-git/go-git/v5/plumbing" 10 11 "github.com/go-git/go-git/v5/plumbing/object" 11 12 ) ··· 15 16 "refname:short", 16 17 "objectname", 17 18 "objecttype", 18 - "*objectname", 19 - "*objecttype", 20 19 "taggername", 21 20 "taggeremail", 22 21 "taggerdate:unix", 23 - "contents", 24 22 } 25 23 26 24 var outFormat strings.Builder ··· 55 53 tagName := parts[0] 56 54 objectHash := parts[1] 57 55 objectType := parts[2] 58 - targetHash := parts[3] // dereferenced object hash (empty for lightweight tags) 59 - // targetType := parts[4] // dereferenced object type (empty for lightweight tags) 60 - taggerName := parts[5] 61 - taggerEmail := parts[6] 62 - taggerDate := parts[7] 63 - message := parts[8] 56 + taggerName := parts[3] 57 + taggerEmail := strings.TrimSuffix(strings.TrimPrefix(parts[4], "<"), ">") 58 + taggerDate := parts[5] 64 59 65 - // parse creation time 66 - var createdAt time.Time 67 - if unix, err := strconv.ParseInt(taggerDate, 10, 64); err == nil { 68 - createdAt = time.Unix(unix, 0) 60 + hash, err := g.furgitRepo.ParseHash(objectHash) 61 + if err != nil { 62 + continue 69 63 } 70 64 71 - // parse object type 72 - typ, err := plumbing.ParseObjectType(objectType) 65 + obj, err := g.furgitRepo.ReadObject(hash) 73 66 if err != nil { 74 - return nil, err 67 + continue 75 68 } 76 69 77 - // strip email separators 78 - taggerEmail = strings.TrimSuffix(strings.TrimPrefix(taggerEmail, "<"), ">") 70 + var tag object.Tag 71 + tag.Hash = plumbingHashFromFurgit(hash) 72 + tag.Name = tagName 79 73 80 - tag := object.Tag{ 81 - Hash: plumbing.NewHash(objectHash), 82 - Name: tagName, 83 - Tagger: object.Signature{ 84 - Name: taggerName, 85 - Email: taggerEmail, 86 - When: createdAt, 87 - }, 88 - Message: message, 89 - TargetType: typ, 90 - Target: plumbing.NewHash(targetHash), 74 + switch typed := obj.(type) { 75 + case *furgit.StoredTag: 76 + tag.Target = plumbingHashFromFurgit(typed.Target) 77 + tag.TargetType = plumbing.ObjectType(typed.TargetType) 78 + if typed.Tagger != nil { 79 + tag.Tagger = convertIdent(*typed.Tagger) 80 + } 81 + tag.Message = string(typed.Message) 82 + default: 83 + // lightweight tag 84 + targetType, err := plumbing.ParseObjectType(objectType) 85 + if err != nil { 86 + return nil, err 87 + } 88 + tag.TargetType = targetType 89 + // When lightweight, leave Target zero and Tagger empty. 90 + if taggerDate != "" { 91 + if unix, err := strconv.ParseInt(taggerDate, 10, 64); err == nil { 92 + tag.Tagger.When = time.Unix(unix, 0) 93 + } 94 + } 95 + tag.Tagger.Name = taggerName 96 + tag.Tagger.Email = taggerEmail 91 97 } 92 98 93 99 tags = append(tags, tag)