fork of go-git with some jj specific features
at v4.3.1 19 kB view raw
1package git 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 stdioutil "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "gopkg.in/src-d/go-git.v4/config" 14 "gopkg.in/src-d/go-git.v4/plumbing" 15 "gopkg.in/src-d/go-git.v4/plumbing/filemode" 16 "gopkg.in/src-d/go-git.v4/plumbing/format/index" 17 "gopkg.in/src-d/go-git.v4/plumbing/object" 18 "gopkg.in/src-d/go-git.v4/plumbing/storer" 19 "gopkg.in/src-d/go-git.v4/utils/ioutil" 20 "gopkg.in/src-d/go-git.v4/utils/merkletrie" 21 22 "gopkg.in/src-d/go-billy.v4" 23 "gopkg.in/src-d/go-billy.v4/util" 24) 25 26var ( 27 ErrWorktreeNotClean = errors.New("worktree is not clean") 28 ErrSubmoduleNotFound = errors.New("submodule not found") 29 ErrUnstagedChanges = errors.New("worktree contains unstaged changes") 30) 31 32// Worktree represents a git worktree. 33type Worktree struct { 34 // Filesystem underlying filesystem. 35 Filesystem billy.Filesystem 36 37 r *Repository 38} 39 40// Pull incorporates changes from a remote repository into the current branch. 41// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are 42// no changes to be fetched, or an error. 43// 44// Pull only supports merges where the can be resolved as a fast-forward. 45func (w *Worktree) Pull(o *PullOptions) error { 46 return w.PullContext(context.Background(), o) 47} 48 49// PullContext incorporates changes from a remote repository into the current 50// branch. Returns nil if the operation is successful, NoErrAlreadyUpToDate if 51// there are no changes to be fetched, or an error. 52// 53// Pull only supports merges where the can be resolved as a fast-forward. 54// 55// The provided Context must be non-nil. If the context expires before the 56// operation is complete, an error is returned. The context only affects to the 57// transport operations. 58func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error { 59 if err := o.Validate(); err != nil { 60 return err 61 } 62 63 remote, err := w.r.Remote(o.RemoteName) 64 if err != nil { 65 return err 66 } 67 68 fetchHead, err := remote.fetch(ctx, &FetchOptions{ 69 RemoteName: o.RemoteName, 70 Depth: o.Depth, 71 Auth: o.Auth, 72 Progress: o.Progress, 73 Force: o.Force, 74 }) 75 76 updated := true 77 if err == NoErrAlreadyUpToDate { 78 updated = false 79 } else if err != nil { 80 return err 81 } 82 83 ref, err := storer.ResolveReference(fetchHead, o.ReferenceName) 84 if err != nil { 85 return err 86 } 87 88 head, err := w.r.Head() 89 if err == nil { 90 if !updated && head.Hash() == ref.Hash() { 91 return NoErrAlreadyUpToDate 92 } 93 94 ff, err := isFastForward(w.r.Storer, head.Hash(), ref.Hash()) 95 if err != nil { 96 return err 97 } 98 99 if !ff { 100 return fmt.Errorf("non-fast-forward update") 101 } 102 } 103 104 if err != nil && err != plumbing.ErrReferenceNotFound { 105 return err 106 } 107 108 if err := w.updateHEAD(ref.Hash()); err != nil { 109 return err 110 } 111 112 if err := w.Reset(&ResetOptions{ 113 Mode: MergeReset, 114 Commit: ref.Hash(), 115 }); err != nil { 116 return err 117 } 118 119 if o.RecurseSubmodules != NoRecurseSubmodules { 120 return w.updateSubmodules(&SubmoduleUpdateOptions{ 121 RecurseSubmodules: o.RecurseSubmodules, 122 Auth: o.Auth, 123 }) 124 } 125 126 return nil 127} 128 129func (w *Worktree) updateSubmodules(o *SubmoduleUpdateOptions) error { 130 s, err := w.Submodules() 131 if err != nil { 132 return err 133 } 134 o.Init = true 135 return s.Update(o) 136} 137 138// Checkout switch branches or restore working tree files. 139func (w *Worktree) Checkout(opts *CheckoutOptions) error { 140 if err := opts.Validate(); err != nil { 141 return err 142 } 143 144 if opts.Create { 145 if err := w.createBranch(opts); err != nil { 146 return err 147 } 148 } 149 150 if !opts.Force { 151 unstaged, err := w.containsUnstagedChanges() 152 if err != nil { 153 return err 154 } 155 156 if unstaged { 157 return ErrUnstagedChanges 158 } 159 } 160 161 c, err := w.getCommitFromCheckoutOptions(opts) 162 if err != nil { 163 return err 164 } 165 166 ro := &ResetOptions{Commit: c, Mode: MergeReset} 167 if opts.Force { 168 ro.Mode = HardReset 169 } 170 171 if !opts.Hash.IsZero() && !opts.Create { 172 err = w.setHEADToCommit(opts.Hash) 173 } else { 174 err = w.setHEADToBranch(opts.Branch, c) 175 } 176 177 if err != nil { 178 return err 179 } 180 181 return w.Reset(ro) 182} 183func (w *Worktree) createBranch(opts *CheckoutOptions) error { 184 _, err := w.r.Storer.Reference(opts.Branch) 185 if err == nil { 186 return fmt.Errorf("a branch named %q already exists", opts.Branch) 187 } 188 189 if err != plumbing.ErrReferenceNotFound { 190 return err 191 } 192 193 if opts.Hash.IsZero() { 194 ref, err := w.r.Head() 195 if err != nil { 196 return err 197 } 198 199 opts.Hash = ref.Hash() 200 } 201 202 return w.r.Storer.SetReference( 203 plumbing.NewHashReference(opts.Branch, opts.Hash), 204 ) 205} 206 207func (w *Worktree) getCommitFromCheckoutOptions(opts *CheckoutOptions) (plumbing.Hash, error) { 208 if !opts.Hash.IsZero() { 209 return opts.Hash, nil 210 } 211 212 b, err := w.r.Reference(opts.Branch, true) 213 if err != nil { 214 return plumbing.ZeroHash, err 215 } 216 217 if !b.Name().IsTag() { 218 return b.Hash(), nil 219 } 220 221 o, err := w.r.Object(plumbing.AnyObject, b.Hash()) 222 if err != nil { 223 return plumbing.ZeroHash, err 224 } 225 226 switch o := o.(type) { 227 case *object.Tag: 228 if o.TargetType != plumbing.CommitObject { 229 return plumbing.ZeroHash, fmt.Errorf("unsupported tag object target %q", o.TargetType) 230 } 231 232 return o.Target, nil 233 case *object.Commit: 234 return o.Hash, nil 235 } 236 237 return plumbing.ZeroHash, fmt.Errorf("unsupported tag target %q", o.Type()) 238} 239 240func (w *Worktree) setHEADToCommit(commit plumbing.Hash) error { 241 head := plumbing.NewHashReference(plumbing.HEAD, commit) 242 return w.r.Storer.SetReference(head) 243} 244 245func (w *Worktree) setHEADToBranch(branch plumbing.ReferenceName, commit plumbing.Hash) error { 246 target, err := w.r.Storer.Reference(branch) 247 if err != nil { 248 return err 249 } 250 251 var head *plumbing.Reference 252 if target.Name().IsBranch() { 253 head = plumbing.NewSymbolicReference(plumbing.HEAD, target.Name()) 254 } else { 255 head = plumbing.NewHashReference(plumbing.HEAD, commit) 256 } 257 258 return w.r.Storer.SetReference(head) 259} 260 261// Reset the worktree to a specified state. 262func (w *Worktree) Reset(opts *ResetOptions) error { 263 if err := opts.Validate(w.r); err != nil { 264 return err 265 } 266 267 if opts.Mode == MergeReset { 268 unstaged, err := w.containsUnstagedChanges() 269 if err != nil { 270 return err 271 } 272 273 if unstaged { 274 return ErrUnstagedChanges 275 } 276 } 277 278 if err := w.setHEADCommit(opts.Commit); err != nil { 279 return err 280 } 281 282 if opts.Mode == SoftReset { 283 return nil 284 } 285 286 t, err := w.getTreeFromCommitHash(opts.Commit) 287 if err != nil { 288 return err 289 } 290 291 if opts.Mode == MixedReset || opts.Mode == MergeReset || opts.Mode == HardReset { 292 if err := w.resetIndex(t); err != nil { 293 return err 294 } 295 } 296 297 if opts.Mode == MergeReset || opts.Mode == HardReset { 298 if err := w.resetWorktree(t); err != nil { 299 return err 300 } 301 } 302 303 return nil 304} 305 306func (w *Worktree) resetIndex(t *object.Tree) error { 307 idx, err := w.r.Storer.Index() 308 if err != nil { 309 return err 310 } 311 312 changes, err := w.diffTreeWithStaging(t, true) 313 if err != nil { 314 return err 315 } 316 317 for _, ch := range changes { 318 a, err := ch.Action() 319 if err != nil { 320 return err 321 } 322 323 var name string 324 var e *object.TreeEntry 325 326 switch a { 327 case merkletrie.Modify, merkletrie.Insert: 328 name = ch.To.String() 329 e, err = t.FindEntry(name) 330 if err != nil { 331 return err 332 } 333 case merkletrie.Delete: 334 name = ch.From.String() 335 } 336 337 _, _ = idx.Remove(name) 338 if e == nil { 339 continue 340 } 341 342 idx.Entries = append(idx.Entries, &index.Entry{ 343 Name: name, 344 Hash: e.Hash, 345 Mode: e.Mode, 346 }) 347 348 } 349 350 return w.r.Storer.SetIndex(idx) 351} 352 353func (w *Worktree) resetWorktree(t *object.Tree) error { 354 changes, err := w.diffStagingWithWorktree(true) 355 if err != nil { 356 return err 357 } 358 359 idx, err := w.r.Storer.Index() 360 if err != nil { 361 return err 362 } 363 364 for _, ch := range changes { 365 if err := w.checkoutChange(ch, t, idx); err != nil { 366 return err 367 } 368 } 369 370 return w.r.Storer.SetIndex(idx) 371} 372 373func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *index.Index) error { 374 a, err := ch.Action() 375 if err != nil { 376 return err 377 } 378 379 var e *object.TreeEntry 380 var name string 381 var isSubmodule bool 382 383 switch a { 384 case merkletrie.Modify, merkletrie.Insert: 385 name = ch.To.String() 386 e, err = t.FindEntry(name) 387 if err != nil { 388 return err 389 } 390 391 isSubmodule = e.Mode == filemode.Submodule 392 case merkletrie.Delete: 393 return rmFileAndDirIfEmpty(w.Filesystem, ch.From.String()) 394 } 395 396 if isSubmodule { 397 return w.checkoutChangeSubmodule(name, a, e, idx) 398 } 399 400 return w.checkoutChangeRegularFile(name, a, t, e, idx) 401} 402 403func (w *Worktree) containsUnstagedChanges() (bool, error) { 404 ch, err := w.diffStagingWithWorktree(false) 405 if err != nil { 406 return false, err 407 } 408 409 for _, c := range ch { 410 a, err := c.Action() 411 if err != nil { 412 return false, err 413 } 414 415 if a == merkletrie.Insert { 416 continue 417 } 418 419 return true, nil 420 } 421 422 return false, nil 423} 424 425func (w *Worktree) setHEADCommit(commit plumbing.Hash) error { 426 head, err := w.r.Reference(plumbing.HEAD, false) 427 if err != nil { 428 return err 429 } 430 431 if head.Type() == plumbing.HashReference { 432 head = plumbing.NewHashReference(plumbing.HEAD, commit) 433 return w.r.Storer.SetReference(head) 434 } 435 436 branch, err := w.r.Reference(head.Target(), false) 437 if err != nil { 438 return err 439 } 440 441 if !branch.Name().IsBranch() { 442 return fmt.Errorf("invalid HEAD target should be a branch, found %s", branch.Type()) 443 } 444 445 branch = plumbing.NewHashReference(branch.Name(), commit) 446 return w.r.Storer.SetReference(branch) 447} 448 449func (w *Worktree) checkoutChangeSubmodule(name string, 450 a merkletrie.Action, 451 e *object.TreeEntry, 452 idx *index.Index, 453) error { 454 switch a { 455 case merkletrie.Modify: 456 sub, err := w.Submodule(name) 457 if err != nil { 458 return err 459 } 460 461 if !sub.initialized { 462 return nil 463 } 464 465 return w.addIndexFromTreeEntry(name, e, idx) 466 case merkletrie.Insert: 467 mode, err := e.Mode.ToOSFileMode() 468 if err != nil { 469 return err 470 } 471 472 if err := w.Filesystem.MkdirAll(name, mode); err != nil { 473 return err 474 } 475 476 return w.addIndexFromTreeEntry(name, e, idx) 477 } 478 479 return nil 480} 481 482func (w *Worktree) checkoutChangeRegularFile(name string, 483 a merkletrie.Action, 484 t *object.Tree, 485 e *object.TreeEntry, 486 idx *index.Index, 487) error { 488 switch a { 489 case merkletrie.Modify: 490 _, _ = idx.Remove(name) 491 492 // to apply perm changes the file is deleted, billy doesn't implement 493 // chmod 494 if err := w.Filesystem.Remove(name); err != nil { 495 return err 496 } 497 498 fallthrough 499 case merkletrie.Insert: 500 f, err := t.File(name) 501 if err != nil { 502 return err 503 } 504 505 if err := w.checkoutFile(f); err != nil { 506 return err 507 } 508 509 return w.addIndexFromFile(name, e.Hash, idx) 510 } 511 512 return nil 513} 514 515func (w *Worktree) checkoutFile(f *object.File) (err error) { 516 mode, err := f.Mode.ToOSFileMode() 517 if err != nil { 518 return 519 } 520 521 if mode&os.ModeSymlink != 0 { 522 return w.checkoutFileSymlink(f) 523 } 524 525 from, err := f.Reader() 526 if err != nil { 527 return 528 } 529 530 defer ioutil.CheckClose(from, &err) 531 532 to, err := w.Filesystem.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) 533 if err != nil { 534 return 535 } 536 537 defer ioutil.CheckClose(to, &err) 538 539 _, err = io.Copy(to, from) 540 return 541} 542 543func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) { 544 from, err := f.Reader() 545 if err != nil { 546 return 547 } 548 549 defer ioutil.CheckClose(from, &err) 550 551 bytes, err := stdioutil.ReadAll(from) 552 if err != nil { 553 return 554 } 555 556 err = w.Filesystem.Symlink(string(bytes), f.Name) 557 558 // On windows, this might fail. 559 // Follow Git on Windows behavior by writing the link as it is. 560 if err != nil && isSymlinkWindowsNonAdmin(err) { 561 mode, _ := f.Mode.ToOSFileMode() 562 563 to, err := w.Filesystem.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) 564 if err != nil { 565 return err 566 } 567 568 defer ioutil.CheckClose(to, &err) 569 570 _, err = to.Write(bytes) 571 return err 572 } 573 return 574} 575 576func (w *Worktree) addIndexFromTreeEntry(name string, f *object.TreeEntry, idx *index.Index) error { 577 _, _ = idx.Remove(name) 578 idx.Entries = append(idx.Entries, &index.Entry{ 579 Hash: f.Hash, 580 Name: name, 581 Mode: filemode.Submodule, 582 }) 583 584 return nil 585} 586 587func (w *Worktree) addIndexFromFile(name string, h plumbing.Hash, idx *index.Index) error { 588 _, _ = idx.Remove(name) 589 fi, err := w.Filesystem.Lstat(name) 590 if err != nil { 591 return err 592 } 593 594 mode, err := filemode.NewFromOSFileMode(fi.Mode()) 595 if err != nil { 596 return err 597 } 598 599 e := &index.Entry{ 600 Hash: h, 601 Name: name, 602 Mode: mode, 603 ModifiedAt: fi.ModTime(), 604 Size: uint32(fi.Size()), 605 } 606 607 // if the FileInfo.Sys() comes from os the ctime, dev, inode, uid and gid 608 // can be retrieved, otherwise this doesn't apply 609 if fillSystemInfo != nil { 610 fillSystemInfo(e, fi.Sys()) 611 } 612 613 idx.Entries = append(idx.Entries, e) 614 return nil 615} 616 617func (w *Worktree) getTreeFromCommitHash(commit plumbing.Hash) (*object.Tree, error) { 618 c, err := w.r.CommitObject(commit) 619 if err != nil { 620 return nil, err 621 } 622 623 return c.Tree() 624} 625 626var fillSystemInfo func(e *index.Entry, sys interface{}) 627 628const gitmodulesFile = ".gitmodules" 629 630// Submodule returns the submodule with the given name 631func (w *Worktree) Submodule(name string) (*Submodule, error) { 632 l, err := w.Submodules() 633 if err != nil { 634 return nil, err 635 } 636 637 for _, m := range l { 638 if m.Config().Name == name { 639 return m, nil 640 } 641 } 642 643 return nil, ErrSubmoduleNotFound 644} 645 646// Submodules returns all the available submodules 647func (w *Worktree) Submodules() (Submodules, error) { 648 l := make(Submodules, 0) 649 m, err := w.readGitmodulesFile() 650 if err != nil || m == nil { 651 return l, err 652 } 653 654 c, err := w.r.Config() 655 if err != nil { 656 return nil, err 657 } 658 659 for _, s := range m.Submodules { 660 l = append(l, w.newSubmodule(s, c.Submodules[s.Name])) 661 } 662 663 return l, nil 664} 665 666func (w *Worktree) newSubmodule(fromModules, fromConfig *config.Submodule) *Submodule { 667 m := &Submodule{w: w} 668 m.initialized = fromConfig != nil 669 670 if !m.initialized { 671 m.c = fromModules 672 return m 673 } 674 675 m.c = fromConfig 676 m.c.Path = fromModules.Path 677 return m 678} 679 680func (w *Worktree) readGitmodulesFile() (*config.Modules, error) { 681 f, err := w.Filesystem.Open(gitmodulesFile) 682 if err != nil { 683 if os.IsNotExist(err) { 684 return nil, nil 685 } 686 687 return nil, err 688 } 689 690 defer f.Close() 691 input, err := stdioutil.ReadAll(f) 692 if err != nil { 693 return nil, err 694 } 695 696 m := config.NewModules() 697 return m, m.Unmarshal(input) 698} 699 700// Clean the worktree by removing untracked files. 701func (w *Worktree) Clean(opts *CleanOptions) error { 702 s, err := w.Status() 703 if err != nil { 704 return err 705 } 706 707 // Check Worktree status to be Untracked, obtain absolute path and delete. 708 for relativePath, status := range s { 709 // Check if the path contains a directory and if Dir options is false, 710 // skip the path. 711 if relativePath != filepath.Base(relativePath) && !opts.Dir { 712 continue 713 } 714 715 // Remove the file only if it's an untracked file. 716 if status.Worktree == Untracked { 717 absPath := filepath.Join(w.Filesystem.Root(), relativePath) 718 if err := os.Remove(absPath); err != nil { 719 return err 720 } 721 } 722 } 723 724 return nil 725} 726 727// GrepResult is structure of a grep result. 728type GrepResult struct { 729 // FileName is the name of file which contains match. 730 FileName string 731 // LineNumber is the line number of a file at which a match was found. 732 LineNumber int 733 // Content is the content of the file at the matching line. 734 Content string 735 // TreeName is the name of the tree (reference name/commit hash) at 736 // which the match was performed. 737 TreeName string 738} 739 740func (gr GrepResult) String() string { 741 return fmt.Sprintf("%s:%s:%d:%s", gr.TreeName, gr.FileName, gr.LineNumber, gr.Content) 742} 743 744// Grep performs grep on a worktree. 745func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) { 746 if err := opts.Validate(w); err != nil { 747 return nil, err 748 } 749 750 // Obtain commit hash from options (CommitHash or ReferenceName). 751 var commitHash plumbing.Hash 752 // treeName contains the value of TreeName in GrepResult. 753 var treeName string 754 755 if opts.ReferenceName != "" { 756 ref, err := w.r.Reference(opts.ReferenceName, true) 757 if err != nil { 758 return nil, err 759 } 760 commitHash = ref.Hash() 761 treeName = opts.ReferenceName.String() 762 } else if !opts.CommitHash.IsZero() { 763 commitHash = opts.CommitHash 764 treeName = opts.CommitHash.String() 765 } 766 767 // Obtain a tree from the commit hash and get a tracked files iterator from 768 // the tree. 769 tree, err := w.getTreeFromCommitHash(commitHash) 770 if err != nil { 771 return nil, err 772 } 773 fileiter := tree.Files() 774 775 return findMatchInFiles(fileiter, treeName, opts) 776} 777 778// findMatchInFiles takes a FileIter, worktree name and GrepOptions, and 779// returns a slice of GrepResult containing the result of regex pattern matching 780// in content of all the files. 781func findMatchInFiles(fileiter *object.FileIter, treeName string, opts *GrepOptions) ([]GrepResult, error) { 782 var results []GrepResult 783 784 err := fileiter.ForEach(func(file *object.File) error { 785 var fileInPathSpec bool 786 787 // When no pathspecs are provided, search all the files. 788 if len(opts.PathSpecs) == 0 { 789 fileInPathSpec = true 790 } 791 792 // Check if the file name matches with the pathspec. Break out of the 793 // loop once a match is found. 794 for _, pathSpec := range opts.PathSpecs { 795 if pathSpec != nil && pathSpec.MatchString(file.Name) { 796 fileInPathSpec = true 797 break 798 } 799 } 800 801 // If the file does not match with any of the pathspec, skip it. 802 if !fileInPathSpec { 803 return nil 804 } 805 806 grepResults, err := findMatchInFile(file, treeName, opts) 807 if err != nil { 808 return err 809 } 810 results = append(results, grepResults...) 811 812 return nil 813 }) 814 815 return results, err 816} 817 818// findMatchInFile takes a single File, worktree name and GrepOptions, 819// and returns a slice of GrepResult containing the result of regex pattern 820// matching in the given file. 821func findMatchInFile(file *object.File, treeName string, opts *GrepOptions) ([]GrepResult, error) { 822 var grepResults []GrepResult 823 824 content, err := file.Contents() 825 if err != nil { 826 return grepResults, err 827 } 828 829 // Split the file content and parse line-by-line. 830 contentByLine := strings.Split(content, "\n") 831 for lineNum, cnt := range contentByLine { 832 addToResult := false 833 834 // Match the patterns and content. Break out of the loop once a 835 // match is found. 836 for _, pattern := range opts.Patterns { 837 if pattern != nil && pattern.MatchString(cnt) { 838 // Add to result only if invert match is not enabled. 839 if !opts.InvertMatch { 840 addToResult = true 841 break 842 } 843 } else if opts.InvertMatch { 844 // If matching fails, and invert match is enabled, add to 845 // results. 846 addToResult = true 847 break 848 } 849 } 850 851 if addToResult { 852 grepResults = append(grepResults, GrepResult{ 853 FileName: file.Name, 854 LineNumber: lineNum + 1, 855 Content: cnt, 856 TreeName: treeName, 857 }) 858 } 859 } 860 861 return grepResults, nil 862} 863 864func rmFileAndDirIfEmpty(fs billy.Filesystem, name string) error { 865 if err := util.RemoveAll(fs, name); err != nil { 866 return err 867 } 868 869 path := filepath.Dir(name) 870 files, err := fs.ReadDir(path) 871 if err != nil { 872 return err 873 } 874 875 if len(files) == 0 { 876 fs.Remove(path) 877 } 878 879 return nil 880}