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