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