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