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