github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/sha1.go (about) 1 package git 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "sort" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/driusan/dgit/zlib" 15 ) 16 17 type Sha1 [20]byte 18 type CommitID Sha1 19 type TreeID Sha1 20 type BlobID Sha1 21 22 type Treeish interface { 23 TreeID(c *Client) (TreeID, error) 24 } 25 26 type Commitish interface { 27 CommitID(c *Client) (CommitID, error) 28 } 29 30 func CommitIDFromString(s string) (CommitID, error) { 31 s1, err := Sha1FromString(s) 32 return CommitID(s1), err 33 } 34 35 func Sha1FromString(s string) (Sha1, error) { 36 b, err := hex.DecodeString(strings.TrimSpace(s)) 37 if err != nil { 38 return Sha1{}, err 39 } 40 return Sha1FromSlice(b) 41 } 42 43 func Sha1FromSlice(s []byte) (Sha1, error) { 44 if len(s) != 20 { 45 return Sha1{}, fmt.Errorf("Invalid Sha1 %x (Size: %d)", s, len(s)) 46 } 47 var val Sha1 48 for i, b := range s { 49 val[i] = b 50 } 51 return val, nil 52 } 53 54 func (s Sha1) String() string { 55 return fmt.Sprintf("%x", string(s[:])) 56 } 57 58 func (s TreeID) String() string { 59 return Sha1(s).String() 60 } 61 62 func (s CommitID) String() string { 63 return Sha1(s).String() 64 } 65 66 func (s CommitID) CommitID(c *Client) (CommitID, error) { 67 return s, nil 68 } 69 70 // Writes the object to w in compressed form 71 func (s Sha1) CompressedWriter(c *Client, w io.Writer) error { 72 obj, err := c.GetObject(s) 73 if err != nil { 74 return err 75 } 76 zw := zlib.NewWriter(w) 77 defer zw.Close() 78 tee := io.TeeReader(bytes.NewReader(obj.GetContent()), zw) 79 ioutil.ReadAll(tee) 80 return nil 81 } 82 83 func (s Sha1) UncompressedSize(c *Client) uint64 { 84 _, sz, err := c.GetObjectMetadata(s) 85 if err != nil { 86 return 0 87 } 88 return sz 89 } 90 91 func (id Sha1) PackEntryType(c *Client) PackEntryType { 92 switch id.Type(c) { 93 case "commit": 94 return OBJ_COMMIT 95 case "tree": 96 return OBJ_TREE 97 case "blob": 98 return OBJ_BLOB 99 case "tag": 100 return OBJ_TAG 101 default: 102 panic("Unknown Object type") 103 } 104 } 105 106 // Returns the git type of the object this Sha1 represents 107 func (id Sha1) Type(c *Client) string { 108 t, _, err := c.GetObjectMetadata(id) 109 if err != nil { 110 panic(err) 111 return "" 112 } 113 return t 114 } 115 116 // Returns all direct parents of commit c. 117 func (cmt CommitID) Parents(c *Client) ([]CommitID, error) { 118 obj, err := c.GetObject(Sha1(cmt)) 119 if err != nil { 120 return nil, err 121 } 122 val := obj.GetContent() 123 reader := bytes.NewBuffer(val) 124 var parents []CommitID 125 for line, err := reader.ReadBytes('\n'); err == nil; line, err = reader.ReadBytes('\n') { 126 line = bytes.TrimSuffix(line, []byte{'\n'}) 127 if len(line) == 0 { 128 return parents, nil 129 } 130 if bytes.HasPrefix(line, []byte("parent ")) { 131 pc := string(bytes.TrimPrefix(line, []byte("parent "))) 132 parent, err := CommitIDFromString(pc) 133 if err != nil { 134 return nil, err 135 } 136 parents = append(parents, parent) 137 } 138 } 139 return parents, nil 140 } 141 142 func (child CommitID) IsAncestor(c *Client, parent Commitish) bool { 143 p, err := parent.CommitID(c) 144 if err != nil { 145 return false 146 } 147 148 ancestorMap, err := p.AncestorMap(c) 149 if err != nil { 150 return false 151 } 152 153 _, ok := ancestorMap[child] 154 return ok 155 } 156 157 var ancestorMapCache map[CommitID]map[CommitID]struct{} 158 159 // AncestorMap returns a map of empty structs (which can be interpreted as a set) 160 // of ancestors of a CommitID. 161 // 162 // It's useful if you want to know all the ancestors of s, but don't particularly 163 // care about their order. Since commits parents can never be changed, multiple 164 // calls to AncestorMap are cached and the cost of calculating the ancestory tree 165 // is only incurred the first time. 166 func (s CommitID) AncestorMap(c *Client) (map[CommitID]struct{}, error) { 167 if cached, ok := ancestorMapCache[s]; ok { 168 return cached, nil 169 } 170 m := make(map[CommitID]struct{}) 171 parents, err := s.Parents(c) 172 if err != nil { 173 return nil, err 174 } 175 var empty struct{} 176 177 m[s] = empty 178 179 for _, val := range parents { 180 m[val] = empty 181 } 182 183 for _, p := range parents { 184 grandparents, err := p.AncestorMap(c) 185 if err != nil { 186 return nil, err 187 } 188 for k, _ := range grandparents { 189 m[k] = empty 190 } 191 } 192 193 if ancestorMapCache == nil { 194 ancestorMapCache = make(map[CommitID]map[CommitID]struct{}) 195 } 196 ancestorMapCache[s] = m 197 198 return m, nil 199 200 } 201 202 func (cmt CommitID) GetCommitterDate(c *Client) (time.Time, error) { 203 obj, err := c.GetCommitObject(cmt) 204 if err != nil { 205 return time.Time{}, err 206 } 207 committerStr := obj.GetHeader("committer") 208 209 if committerStr == "" { 210 return time.Time{}, fmt.Errorf("Commit %s does not have a committer", cmt) 211 } 212 213 committerPieces := strings.Split(committerStr, " ") 214 if len(committerPieces) < 3 { 215 return time.Time{}, fmt.Errorf("Could not parse committer %s", committerStr) 216 217 } 218 219 unixTime, err := strconv.ParseInt(committerPieces[len(committerPieces)-2], 10, 64) 220 if err != nil { 221 return time.Time{}, err 222 } 223 t := time.Unix(unixTime, 0) 224 timeZone := committerPieces[len(committerPieces)-1] 225 if loc, ok := tzCache[timeZone]; ok { 226 return t.In(loc), nil 227 } 228 tzHours, err := strconv.ParseInt(timeZone[:len(timeZone)-2], 10, 64) 229 if err != nil { 230 panic(err) 231 } 232 loc := time.FixedZone(timeZone, int(tzHours*60*60)) 233 if tzCache == nil { 234 tzCache = make(map[string]*time.Location) 235 236 } 237 tzCache[timeZone] = loc 238 date := t.In(loc) 239 240 return date, nil 241 } 242 243 var tzCache map[string]*time.Location 244 245 func (cmt CommitID) GetDate(c *Client) (time.Time, error) { 246 if cached, ok := ancestorDateCache[cmt]; ok { 247 return cached, nil 248 } 249 250 if ancestorDateCache == nil { 251 ancestorDateCache = make(map[CommitID]time.Time) 252 } 253 254 obj, err := c.GetCommitObject(cmt) 255 if err != nil { 256 return time.Time{}, err 257 } 258 authorStr := obj.GetHeader("author") 259 260 if authorStr == "" { 261 return time.Time{}, fmt.Errorf("Commit %s does not have an author", cmt) 262 } 263 264 // authorStr is in the format: 265 // John Smith <jsmith@example.com> unixtime timezone 266 // 267 // we just split on space and rejoin the use the second last element 268 // to get the date/time. It's not very elegant, and it discards time 269 // zone information since time.Unix() doesn't allow us to specify time 270 // zone and there's no way to set it on a time after the fact. 271 authorPieces := strings.Split(authorStr, " ") 272 if len(authorPieces) < 3 { 273 return time.Time{}, fmt.Errorf("Could not parse author %s", authorStr) 274 275 } 276 277 unixTime, err := strconv.ParseInt(authorPieces[len(authorPieces)-2], 10, 64) 278 if err != nil { 279 return time.Time{}, err 280 } 281 t := time.Unix(unixTime, 0) 282 timeZone := authorPieces[len(authorPieces)-1] 283 if loc, ok := tzCache[timeZone]; ok { 284 return t.In(loc), nil 285 } 286 tzHours, err := strconv.ParseInt(timeZone[:len(timeZone)-2], 10, 64) 287 if err != nil { 288 panic(err) 289 } 290 loc := time.FixedZone(timeZone, int(tzHours*60*60)) 291 if tzCache == nil { 292 tzCache = make(map[string]*time.Location) 293 294 } 295 tzCache[timeZone] = loc 296 date := t.In(loc) 297 298 ancestorDateCache[cmt] = date 299 300 return date, nil 301 302 } 303 304 func (cmt CommitID) GetCommitMessage(c *Client) (CommitMessage, error) { 305 obj, err := c.GetCommitObject(cmt) 306 if err != nil { 307 return "", err 308 } 309 for i, c := range obj.content { 310 if c == '\n' && obj.content[i+1] == '\n' { 311 return CommitMessage(string(obj.content[i+2:])), nil 312 } 313 } 314 return "", nil 315 } 316 317 // Returns the author of the commit (with no time information attached) to 318 // the person object. 319 func (cmt CommitID) GetAuthor(c *Client) (Person, error) { 320 obj, err := c.GetCommitObject(cmt) 321 if err != nil { 322 return Person{}, err 323 } 324 authorStr := obj.GetHeader("author") 325 326 if authorStr == "" { 327 return Person{}, fmt.Errorf("Could not parse author %s from commit %s", authorStr, cmt) 328 } 329 330 // authorStr is in the format: 331 // John Smith <jsmith@example.com> unixtime timezone 332 authorPieces := strings.Split(authorStr, " ") 333 if len(authorPieces) < 3 { 334 return Person{}, fmt.Errorf("Commit %s does not have an author", cmt) 335 } 336 337 // FIXME: This should use 338 email := authorPieces[len(authorPieces)-3] 339 email = email[1 : len(email)-1] // strip off the < > 340 author := strings.Join(authorPieces[:len(authorPieces)-3], " ") 341 return Person{author, email, nil}, nil 342 343 } 344 345 // Returns the committer of the commit. 346 func (cmt CommitID) GetCommitter(c *Client) (Person, error) { 347 obj, err := c.GetCommitObject(cmt) 348 if err != nil { 349 return Person{}, err 350 } 351 committerStr := obj.GetHeader("committer") 352 353 if committerStr == "" { 354 return Person{}, fmt.Errorf("Could not parse committer %s from commit %s", committerStr, cmt) 355 } 356 357 // committerStr is in the format: 358 // John Smith <jsmith@example.com> unixtime timezone 359 committerPieces := strings.Split(committerStr, " ") 360 if len(committerPieces) < 3 { 361 return Person{}, fmt.Errorf("Commit %s does not have a committer", cmt) 362 } 363 364 // FIXME: This should use 365 email := committerPieces[len(committerPieces)-3] 366 email = email[1 : len(email)-1] // strip off the < > 367 committer := strings.Join(committerPieces[:len(committerPieces)-3], " ") 368 return Person{committer, email, nil}, nil 369 370 } 371 372 func (s CommitID) Ancestors(c *Client) ([]CommitID, error) { 373 ancestors, err := s.ancestors(c) 374 if err != nil { 375 return nil, err 376 } 377 if ancestorDateCache == nil { 378 ancestorDateCache = make(map[CommitID]time.Time) 379 380 } 381 382 sort.Slice(ancestors, func(i, j int) bool { 383 var iDate, jDate time.Time 384 var ok bool 385 if ancestors[i].IsAncestor(c, ancestors[j]) { 386 return false 387 } 388 if ancestors[j].IsAncestor(c, ancestors[i]) { 389 return true 390 } 391 if iDate, ok = ancestorDateCache[ancestors[i]]; !ok { 392 var err error 393 iDate, err = ancestors[i].GetDate(c) 394 if err != nil { 395 panic(err) 396 } 397 ancestorDateCache[ancestors[i]] = iDate 398 } 399 if jDate, ok = ancestorDateCache[ancestors[j]]; !ok { 400 var err error 401 jDate, err = ancestors[j].GetDate(c) 402 if err != nil { 403 panic(err) 404 } 405 ancestorDateCache[ancestors[j]] = jDate 406 } 407 return jDate.Before(iDate) 408 }) 409 return ancestors, nil 410 } 411 412 var ancestorCache map[CommitID][]CommitID 413 var ancestorDateCache map[CommitID]time.Time 414 415 func (s CommitID) ancestors(c *Client) (commits []CommitID, err error) { 416 if cached, ok := ancestorCache[s]; ok { 417 return cached, nil 418 } 419 420 parents, err := s.Parents(c) 421 if err != nil { 422 return nil, err 423 } 424 commits = append(commits, s) 425 426 for _, p := range parents { 427 grandparents, err := p.ancestors(c) 428 if err != nil { 429 return nil, err 430 } 431 432 duplicateCheck: 433 for _, gp := range grandparents { 434 for _, cmt := range commits { 435 if cmt == gp { 436 continue duplicateCheck 437 } 438 } 439 commits = append(commits, gp) 440 } 441 } 442 443 if ancestorCache == nil { 444 ancestorCache = make(map[CommitID][]CommitID) 445 } 446 447 ancestorCache[s] = commits 448 return 449 } 450 451 func NearestCommonParent(c *Client, com, other Commitish) (CommitID, error) { 452 s, err := com.CommitID(c) 453 if err != nil { 454 return CommitID{}, err 455 } 456 ancestors, err := s.Ancestors(c) 457 if err != nil { 458 return CommitID{}, err 459 } 460 for _, commit := range ancestors { 461 // This is a horrible algorithm. TODO: Do something better than O(n^3) 462 if commit.IsAncestor(c, other) { 463 return commit, nil 464 } 465 } 466 // Nothing in common isn't an error, it just means the nearest common parent 467 // is 0 (the empty commit) 468 return CommitID{}, nil 469 } 470 471 func (c CommitID) GetAllObjects(cl *Client) ([]Sha1, error) { 472 return c.GetAllObjectsExcept(cl, nil) 473 } 474 475 // returns a list of all objects in c excluding those in excludeList. It also populates excludeList 476 // with any objects encountered. 477 func (c CommitID) GetAllObjectsExcept(cl *Client, excludeList map[Sha1]struct{}) ([]Sha1, error) { 478 var objects []Sha1 479 tree, err := c.TreeID(cl) 480 if err != nil { 481 return nil, err 482 } 483 if excludeList != nil { 484 if _, ok := excludeList[Sha1(tree)]; ok { 485 return nil, nil 486 } 487 } 488 objects = append(objects, Sha1(tree)) 489 children, err := tree.GetAllObjectsExcept(cl, excludeList, "", true, false) 490 if err != nil { 491 return nil, err 492 } 493 for _, s := range children { 494 objects = append(objects, s.Sha1) 495 } 496 excludeList[Sha1(c)] = struct{}{} 497 return objects, nil 498 499 } 500 501 func (c CommitID) getRefNamesList(cl *Client) (string, error) { 502 refs, err := ShowRef(cl, ShowRefOptions{}, []string{}) 503 if err != nil { 504 return "", err 505 } 506 507 headRefSpec, err := SymbolicRefGet(cl, SymbolicRefOptions{}, "HEAD") 508 if err != nil { 509 return "", err 510 } 511 512 refsForCommit := "" 513 for _, ref := range refs { 514 if len(refsForCommit) != 0 { 515 refsForCommit = refsForCommit + ", " 516 } 517 518 if ref.Value == Sha1(c) { 519 520 // TODO relate any other top-level refs to their linked ref, not just HEAD 521 if headRefSpec.String() == ref.Name { 522 refsForCommit = refsForCommit + "HEAD -> " 523 } 524 refsForCommit = refsForCommit + ref.RefName() 525 } 526 } 527 528 return refsForCommit, nil 529 } 530 531 func (c CommitID) FormatMedium(cl *Client) (string, error) { 532 output := "" 533 534 author, err := c.GetAuthor(cl) 535 if err != nil { 536 return "", err 537 } 538 output = output + fmt.Sprintf("commit %s", c) 539 refNameList, _ := c.getRefNamesList(cl) 540 if refNameList != "" { 541 output = output + fmt.Sprintf(" (%s)", refNameList) 542 } 543 output = output + "\n" 544 if parents, err := c.Parents(cl); len(parents) > 1 && err == nil { 545 output = output + "Merge: " 546 for i, p := range parents { 547 output = output + fmt.Sprintf("%s", p) 548 if i != len(parents)-1 { 549 output = output + " " 550 } 551 } 552 output = output + "\n" 553 } 554 date, err := c.GetDate(cl) 555 if err != nil { 556 return "", err 557 } 558 output = output + fmt.Sprintf("Author: %v\nDate: %v\n\n", author, date.Format("Mon Jan 2 15:04:05 2006 -0700")) 559 560 msg, err := c.GetCommitMessage(cl) 561 if err != nil { 562 return "", err 563 } 564 lines := strings.Split(strings.TrimSpace(msg.String()), "\n") 565 for _, l := range lines { 566 output = output + fmt.Sprintf(" %v\n", l) 567 } 568 output = output + "\n" 569 return output, nil 570 } 571 572 func (c CommitID) Format(cl *Client, format string) (string, error) { 573 output := format 574 575 // Newline 576 if strings.Contains(output, "%n") { 577 output = strings.Replace(output, "%n", "\n", -1) 578 } 579 580 // Full commit hash 581 if strings.Contains(output, "%H") { 582 output = strings.Replace(output, "%H", c.String(), -1) 583 } 584 585 // Committer date as unix timestamp 586 if strings.Contains(output, "%ct") { 587 date, err := c.GetCommitterDate(cl) 588 if err != nil { 589 return "", err 590 } 591 output = strings.Replace(output, "%ct", strconv.FormatInt(date.Unix(), 10), -1) 592 } 593 594 // Author date as unix timestamp 595 if strings.Contains(output, "%at") { 596 date, err := c.GetDate(cl) 597 if err != nil { 598 return "", err 599 } 600 output = strings.Replace(output, "%at", strconv.FormatInt(date.Unix(), 10), -1) 601 } 602 603 // Show the non-stylized ref names beside any relevant commit 604 if strings.Contains(output, "%D") { 605 refNameList, _ := c.getRefNamesList(cl) 606 output = strings.Replace(output, "%D", refNameList, -1) 607 } 608 609 // TODO Add more formatting options (there are many) 610 611 return output, nil 612 } 613 614 // A TreeEntry represents an entry inside of a Treeish. 615 type TreeEntry struct { 616 Sha1 Sha1 617 FileMode EntryMode 618 } 619 620 // Returns a map of all paths in the Tree. If recurse is true, it will recurse 621 // into subtrees. If excludeself is true, it will *not* include it's own Sha1. 622 // (Only really meaningful with recurse) 623 func (t TreeID) GetAllObjects(cl *Client, prefix IndexPath, recurse, excludeself bool) (map[IndexPath]TreeEntry, error) { 624 return t.GetAllObjectsExcept(cl, nil, prefix, recurse, excludeself) 625 } 626 627 // parseRawtreeLine parses a single line from a raw tree object starting at 628 // entryStart. It returns the TreeEntry and the number of bytes in treecontent 629 // that it occupies. 630 func parseRawTreeLine(entryStart int, treecontent []byte) (IndexPath, TreeEntry, int, error) { 631 // The format of each tree entry is: 632 // [permission] [name] \0 [20 bytes of Sha1] 633 // so we first search for a nul byte which means the next 20 bytes 634 // are the SHA, and then we can calculate the name and permissions based 635 // on the entryStart and the nil. 636 for i := entryStart; i < len(treecontent); i++ { 637 if treecontent[i] == 0 { 638 // Add 1 when converting the value of the sha1 because 639 // because i is currently set to the nil 640 sha, err := Sha1FromSlice(treecontent[i+1 : i+21]) 641 if err != nil { 642 return "", TreeEntry{}, 0, err 643 } 644 645 // Split up the perm from the name based on the whitespace 646 split := bytes.SplitN(treecontent[entryStart:i], []byte{' '}, 2) 647 perm := split[0] 648 name := split[1] 649 650 var mode EntryMode 651 switch string(perm) { 652 case "40755": // sometimes in git9 repositories 653 mode = modeGit9Tree 654 case "40000": // valid git tree 655 mode = ModeTree 656 case "100644": 657 mode = ModeBlob 658 case "100755": 659 mode = ModeExec 660 case "120000": 661 mode = ModeSymlink 662 case "160000": 663 mode = ModeCommit 664 default: 665 return "", TreeEntry{}, 0, fmt.Errorf("Unsupported mode %v in tree", string(perm)) 666 } 667 entryEnd := i + 20 + 1 668 return IndexPath(name), TreeEntry{ 669 Sha1: sha, 670 FileMode: mode, 671 }, entryEnd - entryStart, nil 672 } 673 } 674 return "", TreeEntry{}, 0, fmt.Errorf("Missing nil byte in tree entry") 675 676 } 677 678 // GetAllObjectsExcept is like GetAllObjects, except that it excludes those objects in excludeList. It also 679 // populates excludeList with any objects encountered. 680 func (t TreeID) GetAllObjectsExcept(cl *Client, excludeList map[Sha1]struct{}, prefix IndexPath, recurse, excludeself bool) (map[IndexPath]TreeEntry, error) { 681 if excludeList != nil { 682 excludeList[Sha1(t)] = struct{}{} 683 } 684 o, err := cl.GetObject(Sha1(t)) 685 if err != nil { 686 return nil, err 687 } 688 689 if o.GetType() != "tree" { 690 return nil, fmt.Errorf("%s is not a tree object", t) 691 } 692 693 treecontent := o.GetContent() 694 val := make(map[IndexPath]TreeEntry) 695 696 i := 0 697 for i < len(treecontent) { 698 name, entry, size, err := parseRawTreeLine(i, treecontent) 699 if err != nil { 700 return nil, err 701 } 702 i += size 703 704 if excludeList != nil { 705 if _, ok := excludeList[entry.Sha1]; ok { 706 continue 707 } 708 } 709 val[IndexPath(name)] = entry 710 711 if entry.FileMode == ModeTree && recurse { 712 childTree := TreeID(entry.Sha1) 713 714 children, err := childTree.GetAllObjectsExcept(cl, excludeList, "", recurse, excludeself) 715 if err != nil { 716 return nil, err 717 } 718 for child, childval := range children { 719 val[IndexPath(name)+"/"+child] = childval 720 } 721 } 722 723 if excludeList != nil { 724 excludeList[entry.Sha1] = struct{}{} 725 } 726 } 727 if excludeList != nil { 728 excludeList[Sha1(t)] = struct{}{} 729 } 730 return val, nil 731 } 732 733 func (c CommitID) TreeID(cl *Client) (TreeID, error) { 734 obj, err := cl.GetCommitObject(c) 735 if err != nil { 736 return TreeID{}, err 737 } 738 treeStr := obj.GetHeader("tree") 739 s, err := Sha1FromString(treeStr) 740 return TreeID(s), err 741 } 742 743 // Ensures the Tree implements Treeish 744 func (t TreeID) TreeID(cl *Client) (TreeID, error) { 745 // Validate that it's a tree 746 if Sha1(t).Type(cl) != "tree" { 747 return TreeID{}, fmt.Errorf("Invalid tree: %v", t) 748 } 749 return t, nil 750 } 751 752 // Converts the Tree into an IndexEntries, to simplify comparisons between 753 // Trees and Indexes 754 func GetIndexMap(c *Client, t Treeish) (IndexMap, error) { 755 indexentries, err := expandGitTreeIntoIndexes(c, t, true, false, false) 756 if err != nil { 757 return nil, err 758 } 759 // Create a fake index, to use the GetMap() function 760 idx := &Index{Objects: indexentries} 761 return idx.GetMap(), nil 762 }