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}