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