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