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