Imported Upstream version 2.5.1
[scm/test.git] / git / git.go
1 // Package git contains various commands that shell out to git
2 // NOTE: Subject to change, do not rely on this package from outside git-lfs source
3 package git
4
5 import (
6         "bufio"
7         "bytes"
8         "encoding/hex"
9         "errors"
10         "fmt"
11         "io"
12         "io/ioutil"
13         "net/url"
14         "os"
15         "path/filepath"
16         "regexp"
17         "strconv"
18         "strings"
19         "time"
20
21         lfserrors "github.com/git-lfs/git-lfs/errors"
22         "github.com/git-lfs/git-lfs/subprocess"
23         "github.com/git-lfs/git-lfs/tools"
24         "github.com/rubyist/tracerx"
25 )
26
27 type RefType int
28
29 const (
30         RefTypeLocalBranch  = RefType(iota)
31         RefTypeRemoteBranch = RefType(iota)
32         RefTypeLocalTag     = RefType(iota)
33         RefTypeRemoteTag    = RefType(iota)
34         RefTypeHEAD         = RefType(iota) // current checkout
35         RefTypeOther        = RefType(iota) // stash or unknown
36
37         // A ref which can be used as a placeholder for before the first commit
38         // Equivalent to git mktree < /dev/null, useful for diffing before first commit
39         RefBeforeFirstCommit = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
40 )
41
42 // Prefix returns the given RefType's prefix, "refs/heads", "ref/remotes",
43 // etc. It returns an additional value of either true/false, whether or not this
44 // given ref type has a prefix.
45 //
46 // If the RefType is unrecognized, Prefix() will panic.
47 func (t RefType) Prefix() (string, bool) {
48         switch t {
49         case RefTypeLocalBranch:
50                 return "refs/heads", true
51         case RefTypeRemoteBranch:
52                 return "refs/remotes", true
53         case RefTypeLocalTag:
54                 return "refs/tags", true
55         case RefTypeRemoteTag:
56                 return "refs/remotes/tags", true
57         default:
58                 return "", false
59         }
60 }
61
62 func ParseRef(absRef, sha string) *Ref {
63         r := &Ref{Sha: sha}
64         if strings.HasPrefix(absRef, "refs/heads/") {
65                 r.Name = absRef[11:]
66                 r.Type = RefTypeLocalBranch
67         } else if strings.HasPrefix(absRef, "refs/tags/") {
68                 r.Name = absRef[10:]
69                 r.Type = RefTypeLocalTag
70         } else if strings.HasPrefix(absRef, "refs/remotes/tags/") {
71                 r.Name = absRef[18:]
72                 r.Type = RefTypeRemoteTag
73         } else if strings.HasPrefix(absRef, "refs/remotes/") {
74                 r.Name = absRef[13:]
75                 r.Type = RefTypeRemoteBranch
76         } else {
77                 r.Name = absRef
78                 if absRef == "HEAD" {
79                         r.Type = RefTypeHEAD
80                 } else {
81                         r.Type = RefTypeOther
82                 }
83         }
84         return r
85 }
86
87 // A git reference (branch, tag etc)
88 type Ref struct {
89         Name string
90         Type RefType
91         Sha  string
92 }
93
94 // Refspec returns the fully-qualified reference name (including remote), i.e.,
95 // for a remote branch called 'my-feature' on remote 'origin', this function
96 // will return:
97 //
98 //   refs/remotes/origin/my-feature
99 func (r *Ref) Refspec() string {
100         if r == nil {
101                 return ""
102         }
103
104         prefix, ok := r.Type.Prefix()
105         if ok {
106                 return fmt.Sprintf("%s/%s", prefix, r.Name)
107         }
108
109         return r.Name
110 }
111
112 // Some top level information about a commit (only first line of message)
113 type CommitSummary struct {
114         Sha            string
115         ShortSha       string
116         Parents        []string
117         CommitDate     time.Time
118         AuthorDate     time.Time
119         AuthorName     string
120         AuthorEmail    string
121         CommitterName  string
122         CommitterEmail string
123         Subject        string
124 }
125
126 // Prepend Git config instructions to disable Git LFS filter
127 func gitConfigNoLFS(args ...string) []string {
128         // Before git 2.8, setting filters to blank causes lots of warnings, so use cat instead (slightly slower)
129         // Also pre 2.2 it failed completely. We used to use it anyway in git 2.2-2.7 and
130         // suppress the messages in stderr, but doing that with standard StderrPipe suppresses
131         // the git clone output (git thinks it's not a terminal) and makes it look like it's
132         // not working. You can get around that with https://github.com/kr/pty but that
133         // causes difficult issues with passing through Stdin for login prompts
134         // This way is simpler & more practical.
135         filterOverride := ""
136         if !IsGitVersionAtLeast("2.8.0") {
137                 filterOverride = "cat"
138         }
139
140         return append([]string{
141                 "-c", fmt.Sprintf("filter.lfs.smudge=%v", filterOverride),
142                 "-c", fmt.Sprintf("filter.lfs.clean=%v", filterOverride),
143                 "-c", "filter.lfs.process=",
144                 "-c", "filter.lfs.required=false",
145         }, args...)
146 }
147
148 // Invoke Git with disabled LFS filters
149 func gitNoLFS(args ...string) *subprocess.Cmd {
150         return subprocess.ExecCommand("git", gitConfigNoLFS(args...)...)
151 }
152
153 func gitNoLFSSimple(args ...string) (string, error) {
154         return subprocess.SimpleExec("git", gitConfigNoLFS(args...)...)
155 }
156
157 func gitNoLFSBuffered(args ...string) (*subprocess.BufferedCmd, error) {
158         return subprocess.BufferedExec("git", gitConfigNoLFS(args...)...)
159 }
160
161 // Invoke Git with enabled LFS filters
162 func git(args ...string) *subprocess.Cmd {
163         return subprocess.ExecCommand("git", args...)
164 }
165
166 func gitSimple(args ...string) (string, error) {
167         return subprocess.SimpleExec("git", args...)
168 }
169
170 func gitBuffered(args ...string) (*subprocess.BufferedCmd, error) {
171         return subprocess.BufferedExec("git", args...)
172 }
173
174 func CatFile() (*subprocess.BufferedCmd, error) {
175         return gitNoLFSBuffered("cat-file", "--batch-check")
176 }
177
178 func DiffIndex(ref string, cached bool) (*bufio.Scanner, error) {
179         args := []string{"diff-index", "-M"}
180         if cached {
181                 args = append(args, "--cached")
182         }
183         args = append(args, ref)
184
185         cmd, err := gitBuffered(args...)
186         if err != nil {
187                 return nil, err
188         }
189         if err = cmd.Stdin.Close(); err != nil {
190                 return nil, err
191         }
192
193         return bufio.NewScanner(cmd.Stdout), nil
194 }
195
196 func HashObject(r io.Reader) (string, error) {
197         cmd := gitNoLFS("hash-object", "--stdin")
198         cmd.Stdin = r
199         out, err := cmd.Output()
200         if err != nil {
201                 return "", fmt.Errorf("Error building Git blob OID: %s", err)
202         }
203
204         return string(bytes.TrimSpace(out)), nil
205 }
206
207 func Log(args ...string) (*subprocess.BufferedCmd, error) {
208         logArgs := append([]string{"log"}, args...)
209         return gitNoLFSBuffered(logArgs...)
210 }
211
212 func LsRemote(remote, remoteRef string) (string, error) {
213         if remote == "" {
214                 return "", errors.New("remote required")
215         }
216         if remoteRef == "" {
217                 return gitNoLFSSimple("ls-remote", remote)
218
219         }
220         return gitNoLFSSimple("ls-remote", remote, remoteRef)
221 }
222
223 func LsTree(ref string) (*subprocess.BufferedCmd, error) {
224         return gitNoLFSBuffered(
225                 "ls-tree",
226                 "-r",          // recurse
227                 "-l",          // report object size (we'll need this)
228                 "-z",          // null line termination
229                 "--full-tree", // start at the root regardless of where we are in it
230                 ref,
231         )
232 }
233
234 func ResolveRef(ref string) (*Ref, error) {
235         outp, err := gitNoLFSSimple("rev-parse", ref, "--symbolic-full-name", ref)
236         if err != nil {
237                 return nil, fmt.Errorf("Git can't resolve ref: %q", ref)
238         }
239         if outp == "" {
240                 return nil, fmt.Errorf("Git can't resolve ref: %q", ref)
241         }
242
243         lines := strings.Split(outp, "\n")
244         fullref := &Ref{Sha: lines[0]}
245
246         if len(lines) == 1 {
247                 // ref is a sha1 and has no symbolic-full-name
248                 fullref.Name = lines[0]
249                 fullref.Sha = lines[0]
250                 fullref.Type = RefTypeOther
251                 return fullref, nil
252         }
253
254         // parse the symbolic-full-name
255         fullref.Type, fullref.Name = ParseRefToTypeAndName(lines[1])
256         return fullref, nil
257 }
258
259 func ResolveRefs(refnames []string) ([]*Ref, error) {
260         refs := make([]*Ref, len(refnames))
261         for i, name := range refnames {
262                 ref, err := ResolveRef(name)
263                 if err != nil {
264                         return refs, err
265                 }
266
267                 refs[i] = ref
268         }
269         return refs, nil
270 }
271
272 func CurrentRef() (*Ref, error) {
273         return ResolveRef("HEAD")
274 }
275
276 func (c *Configuration) CurrentRemoteRef() (*Ref, error) {
277         remoteref, err := c.RemoteRefNameForCurrentBranch()
278         if err != nil {
279                 return nil, err
280         }
281
282         return ResolveRef(remoteref)
283 }
284
285 // RemoteRefForCurrentBranch returns the full remote ref (refs/remotes/{remote}/{remotebranch})
286 // that the current branch is tracking.
287 func (c *Configuration) RemoteRefNameForCurrentBranch() (string, error) {
288         ref, err := CurrentRef()
289         if err != nil {
290                 return "", err
291         }
292
293         if ref.Type == RefTypeHEAD || ref.Type == RefTypeOther {
294                 return "", errors.New("not on a branch")
295         }
296
297         remote := c.RemoteForBranch(ref.Name)
298         if remote == "" {
299                 return "", fmt.Errorf("remote not found for branch %q", ref.Name)
300         }
301
302         remotebranch := c.RemoteBranchForLocalBranch(ref.Name)
303
304         return fmt.Sprintf("refs/remotes/%s/%s", remote, remotebranch), nil
305 }
306
307 // RemoteForBranch returns the remote name that a given local branch is tracking (blank if none)
308 func (c *Configuration) RemoteForBranch(localBranch string) string {
309         return c.Find(fmt.Sprintf("branch.%s.remote", localBranch))
310 }
311
312 // RemoteBranchForLocalBranch returns the name (only) of the remote branch that the local branch is tracking
313 // If no specific branch is configured, returns local branch name
314 func (c *Configuration) RemoteBranchForLocalBranch(localBranch string) string {
315         // get remote ref to track, may not be same name
316         merge := c.Find(fmt.Sprintf("branch.%s.merge", localBranch))
317         if strings.HasPrefix(merge, "refs/heads/") {
318                 return merge[11:]
319         } else {
320                 return localBranch
321         }
322 }
323
324 func RemoteList() ([]string, error) {
325         cmd := gitNoLFS("remote")
326
327         outp, err := cmd.StdoutPipe()
328         if err != nil {
329                 return nil, fmt.Errorf("Failed to call git remote: %v", err)
330         }
331         cmd.Start()
332         defer cmd.Wait()
333
334         scanner := bufio.NewScanner(outp)
335
336         var ret []string
337         for scanner.Scan() {
338                 ret = append(ret, strings.TrimSpace(scanner.Text()))
339         }
340
341         return ret, nil
342 }
343
344 // Refs returns all of the local and remote branches and tags for the current
345 // repository. Other refs (HEAD, refs/stash, git notes) are ignored.
346 func LocalRefs() ([]*Ref, error) {
347         cmd := gitNoLFS("show-ref", "--heads", "--tags")
348
349         outp, err := cmd.StdoutPipe()
350         if err != nil {
351                 return nil, fmt.Errorf("Failed to call git show-ref: %v", err)
352         }
353
354         var refs []*Ref
355
356         if err := cmd.Start(); err != nil {
357                 return refs, err
358         }
359
360         scanner := bufio.NewScanner(outp)
361         for scanner.Scan() {
362                 line := strings.TrimSpace(scanner.Text())
363                 parts := strings.SplitN(line, " ", 2)
364                 if len(parts) != 2 || len(parts[0]) != 40 || len(parts[1]) < 1 {
365                         tracerx.Printf("Invalid line from git show-ref: %q", line)
366                         continue
367                 }
368
369                 rtype, name := ParseRefToTypeAndName(parts[1])
370                 if rtype != RefTypeLocalBranch && rtype != RefTypeLocalTag {
371                         continue
372                 }
373
374                 refs = append(refs, &Ref{name, rtype, parts[0]})
375         }
376
377         return refs, cmd.Wait()
378 }
379
380 // UpdateRef moves the given ref to a new sha with a given reason (and creates a
381 // reflog entry, if a "reason" was provided). It returns an error if any were
382 // encountered.
383 func UpdateRef(ref *Ref, to []byte, reason string) error {
384         return UpdateRefIn("", ref, to, reason)
385 }
386
387 // UpdateRef moves the given ref to a new sha with a given reason (and creates a
388 // reflog entry, if a "reason" was provided). It operates within the given
389 // working directory "wd". It returns an error if any were encountered.
390 func UpdateRefIn(wd string, ref *Ref, to []byte, reason string) error {
391         args := []string{"update-ref", ref.Refspec(), hex.EncodeToString(to)}
392         if len(reason) > 0 {
393                 args = append(args, "-m", reason)
394         }
395
396         cmd := gitNoLFS(args...)
397         cmd.Dir = wd
398
399         return cmd.Run()
400 }
401
402 // ValidateRemote checks that a named remote is valid for use
403 // Mainly to check user-supplied remotes & fail more nicely
404 func ValidateRemote(remote string) error {
405         remotes, err := RemoteList()
406         if err != nil {
407                 return err
408         }
409         for _, r := range remotes {
410                 if r == remote {
411                         return nil
412                 }
413         }
414
415         if err = ValidateRemoteURL(remote); err == nil {
416                 return nil
417         }
418
419         return fmt.Errorf("Invalid remote name: %q", remote)
420 }
421
422 // ValidateRemoteURL checks that a string is a valid Git remote URL
423 func ValidateRemoteURL(remote string) error {
424         u, _ := url.Parse(remote)
425         if u == nil || u.Scheme == "" {
426                 // This is either an invalid remote name (maybe the user made a typo
427                 // when selecting a named remote) or a bare SSH URL like
428                 // "x@y.com:path/to/resource.git". Guess that this is a URL in the latter
429                 // form if the string contains a colon ":", and an invalid remote if it
430                 // does not.
431                 if strings.Contains(remote, ":") {
432                         return nil
433                 } else {
434                         return fmt.Errorf("Invalid remote name: %q", remote)
435                 }
436         }
437
438         switch u.Scheme {
439         case "ssh", "http", "https", "git":
440                 return nil
441         default:
442                 return fmt.Errorf("Invalid remote url protocol %q in %q", u.Scheme, remote)
443         }
444 }
445
446 func UpdateIndexFromStdin() *subprocess.Cmd {
447         return git("update-index", "-q", "--refresh", "--stdin")
448 }
449
450 // RecentBranches returns branches with commit dates on or after the given date/time
451 // Return full Ref type for easier detection of duplicate SHAs etc
452 // since: refs with commits on or after this date will be included
453 // includeRemoteBranches: true to include refs on remote branches
454 // onlyRemote: set to non-blank to only include remote branches on a single remote
455 func RecentBranches(since time.Time, includeRemoteBranches bool, onlyRemote string) ([]*Ref, error) {
456         cmd := gitNoLFS("for-each-ref",
457                 `--sort=-committerdate`,
458                 `--format=%(refname) %(objectname) %(committerdate:iso)`,
459                 "refs")
460         outp, err := cmd.StdoutPipe()
461         if err != nil {
462                 return nil, fmt.Errorf("Failed to call git for-each-ref: %v", err)
463         }
464         cmd.Start()
465         defer cmd.Wait()
466
467         scanner := bufio.NewScanner(outp)
468
469         // Output is like this:
470         // refs/heads/master f03686b324b29ff480591745dbfbbfa5e5ac1bd5 2015-08-19 16:50:37 +0100
471         // refs/remotes/origin/master ad3b29b773e46ad6870fdf08796c33d97190fe93 2015-08-13 16:50:37 +0100
472
473         // Output is ordered by latest commit date first, so we can stop at the threshold
474         regex := regexp.MustCompile(`^(refs/[^/]+/\S+)\s+([0-9A-Za-z]{40})\s+(\d{4}-\d{2}-\d{2}\s+\d{2}\:\d{2}\:\d{2}\s+[\+\-]\d{4})`)
475         tracerx.Printf("RECENT: Getting refs >= %v", since)
476         var ret []*Ref
477         for scanner.Scan() {
478                 line := scanner.Text()
479                 if match := regex.FindStringSubmatch(line); match != nil {
480                         fullref := match[1]
481                         sha := match[2]
482                         reftype, ref := ParseRefToTypeAndName(fullref)
483                         if reftype == RefTypeRemoteBranch || reftype == RefTypeRemoteTag {
484                                 if !includeRemoteBranches {
485                                         continue
486                                 }
487                                 if onlyRemote != "" && !strings.HasPrefix(ref, onlyRemote+"/") {
488                                         continue
489                                 }
490                         }
491                         // This is a ref we might use
492                         // Check the date
493                         commitDate, err := ParseGitDate(match[3])
494                         if err != nil {
495                                 return ret, err
496                         }
497                         if commitDate.Before(since) {
498                                 // the end
499                                 break
500                         }
501                         tracerx.Printf("RECENT: %v (%v)", ref, commitDate)
502                         ret = append(ret, &Ref{ref, reftype, sha})
503                 }
504         }
505
506         return ret, nil
507
508 }
509
510 // Get the type & name of a git reference
511 func ParseRefToTypeAndName(fullref string) (t RefType, name string) {
512         const localPrefix = "refs/heads/"
513         const remotePrefix = "refs/remotes/"
514         const remoteTagPrefix = "refs/remotes/tags/"
515         const localTagPrefix = "refs/tags/"
516
517         if fullref == "HEAD" {
518                 name = fullref
519                 t = RefTypeHEAD
520         } else if strings.HasPrefix(fullref, localPrefix) {
521                 name = fullref[len(localPrefix):]
522                 t = RefTypeLocalBranch
523         } else if strings.HasPrefix(fullref, remotePrefix) {
524                 name = fullref[len(remotePrefix):]
525                 t = RefTypeRemoteBranch
526         } else if strings.HasPrefix(fullref, remoteTagPrefix) {
527                 name = fullref[len(remoteTagPrefix):]
528                 t = RefTypeRemoteTag
529         } else if strings.HasPrefix(fullref, localTagPrefix) {
530                 name = fullref[len(localTagPrefix):]
531                 t = RefTypeLocalTag
532         } else {
533                 name = fullref
534                 t = RefTypeOther
535         }
536         return
537 }
538
539 // Parse a Git date formatted in ISO 8601 format (%ci/%ai)
540 func ParseGitDate(str string) (time.Time, error) {
541
542         // Unfortunately Go and Git don't overlap in their builtin date formats
543         // Go's time.RFC1123Z and Git's %cD are ALMOST the same, except that
544         // when the day is < 10 Git outputs a single digit, but Go expects a leading
545         // zero - this is enough to break the parsing. Sigh.
546
547         // Format is for 2 Jan 2006, 15:04:05 -7 UTC as per Go
548         return time.Parse("2006-01-02 15:04:05 -0700", str)
549 }
550
551 // FormatGitDate converts a Go date into a git command line format date
552 func FormatGitDate(tm time.Time) string {
553         // Git format is "Fri Jun 21 20:26:41 2013 +0900" but no zero-leading for day
554         return tm.Format("Mon Jan 2 15:04:05 2006 -0700")
555 }
556
557 // Get summary information about a commit
558 func GetCommitSummary(commit string) (*CommitSummary, error) {
559         cmd := gitNoLFS("show", "-s",
560                 `--format=%H|%h|%P|%ai|%ci|%ae|%an|%ce|%cn|%s`, commit)
561
562         out, err := cmd.CombinedOutput()
563         if err != nil {
564                 return nil, fmt.Errorf("Failed to call git show: %v %v", err, string(out))
565         }
566
567         // At most 10 substrings so subject line is not split on anything
568         fields := strings.SplitN(string(out), "|", 10)
569         // Cope with the case where subject is blank
570         if len(fields) >= 9 {
571                 ret := &CommitSummary{}
572                 // Get SHAs from output, not commit input, so we can support symbolic refs
573                 ret.Sha = fields[0]
574                 ret.ShortSha = fields[1]
575                 ret.Parents = strings.Split(fields[2], " ")
576                 // %aD & %cD (RFC2822) matches Go's RFC1123Z format
577                 ret.AuthorDate, _ = ParseGitDate(fields[3])
578                 ret.CommitDate, _ = ParseGitDate(fields[4])
579                 ret.AuthorEmail = fields[5]
580                 ret.AuthorName = fields[6]
581                 ret.CommitterEmail = fields[7]
582                 ret.CommitterName = fields[8]
583                 if len(fields) > 9 {
584                         ret.Subject = strings.TrimRight(fields[9], "\n")
585                 }
586                 return ret, nil
587         } else {
588                 msg := fmt.Sprintf("Unexpected output from git show: %v", string(out))
589                 return nil, errors.New(msg)
590         }
591 }
592
593 func GitAndRootDirs() (string, string, error) {
594         cmd := gitNoLFS("rev-parse", "--git-dir", "--show-toplevel")
595         buf := &bytes.Buffer{}
596         cmd.Stderr = buf
597
598         out, err := cmd.Output()
599         output := string(out)
600         if err != nil {
601                 return "", "", fmt.Errorf("Failed to call git rev-parse --git-dir --show-toplevel: %q", buf.String())
602         }
603
604         paths := strings.Split(output, "\n")
605         pathLen := len(paths)
606
607         for i := 0; i < pathLen; i++ {
608                 paths[i], err = tools.TranslateCygwinPath(paths[i])
609         }
610
611         if pathLen == 0 {
612                 return "", "", fmt.Errorf("Bad git rev-parse output: %q", output)
613         }
614
615         absGitDir, err := filepath.Abs(paths[0])
616         if err != nil {
617                 return "", "", fmt.Errorf("Error converting %q to absolute: %s", paths[0], err)
618         }
619
620         if pathLen == 1 || len(paths[1]) == 0 {
621                 return absGitDir, "", nil
622         }
623
624         absRootDir := paths[1]
625         return absGitDir, absRootDir, nil
626 }
627
628 func RootDir() (string, error) {
629         cmd := gitNoLFS("rev-parse", "--show-toplevel")
630         out, err := cmd.Output()
631         if err != nil {
632                 return "", fmt.Errorf("Failed to call git rev-parse --show-toplevel: %v %v", err, string(out))
633         }
634
635         path := strings.TrimSpace(string(out))
636         path, err = tools.TranslateCygwinPath(path)
637         if len(path) > 0 {
638                 return filepath.Abs(path)
639         }
640         return "", nil
641
642 }
643
644 func GitDir() (string, error) {
645         cmd := gitNoLFS("rev-parse", "--git-dir")
646         out, err := cmd.Output()
647         if err != nil {
648                 return "", fmt.Errorf("Failed to call git rev-parse --git-dir: %v %v", err, string(out))
649         }
650         path := strings.TrimSpace(string(out))
651         if len(path) > 0 {
652                 return filepath.Abs(path)
653         }
654         return "", nil
655 }
656
657 // GetAllWorkTreeHEADs returns the refs that all worktrees are using as HEADs
658 // This returns all worktrees plus the master working copy, and works even if
659 // working dir is actually in a worktree right now
660 // Pass in the git storage dir (parent of 'objects') to work from
661 func GetAllWorkTreeHEADs(storageDir string) ([]*Ref, error) {
662         worktreesdir := filepath.Join(storageDir, "worktrees")
663         dirf, err := os.Open(worktreesdir)
664         if err != nil && !os.IsNotExist(err) {
665                 return nil, err
666         }
667
668         var worktrees []*Ref
669         if err == nil {
670                 // There are some worktrees
671                 defer dirf.Close()
672                 direntries, err := dirf.Readdir(0)
673                 if err != nil {
674                         return nil, err
675                 }
676                 for _, dirfi := range direntries {
677                         if dirfi.IsDir() {
678                                 // to avoid having to chdir and run git commands to identify the commit
679                                 // just read the HEAD file & git rev-parse if necessary
680                                 // Since the git repo is shared the same rev-parse will work from this location
681                                 headfile := filepath.Join(worktreesdir, dirfi.Name(), "HEAD")
682                                 ref, err := parseRefFile(headfile)
683                                 if err != nil {
684                                         tracerx.Printf("Error reading %v for worktree, skipping: %v", headfile, err)
685                                         continue
686                                 }
687                                 worktrees = append(worktrees, ref)
688                         }
689                 }
690         }
691
692         // This has only established the separate worktrees, not the original checkout
693         // If the storageDir contains a HEAD file then there is a main checkout
694         // as well; this mus tbe resolveable whether you're in the main checkout or
695         // a worktree
696         headfile := filepath.Join(storageDir, "HEAD")
697         ref, err := parseRefFile(headfile)
698         if err == nil {
699                 worktrees = append(worktrees, ref)
700         } else if !os.IsNotExist(err) { // ok if not exists, probably bare repo
701                 tracerx.Printf("Error reading %v for main checkout, skipping: %v", headfile, err)
702         }
703
704         return worktrees, nil
705 }
706
707 // Manually parse a reference file like HEAD and return the Ref it resolves to
708 func parseRefFile(filename string) (*Ref, error) {
709         bytes, err := ioutil.ReadFile(filename)
710         if err != nil {
711                 return nil, err
712         }
713         contents := strings.TrimSpace(string(bytes))
714         if strings.HasPrefix(contents, "ref:") {
715                 contents = strings.TrimSpace(contents[4:])
716         }
717         return ResolveRef(contents)
718 }
719
720 // IsBare returns whether or not a repository is bare. It requires that the
721 // current working directory is a repository.
722 //
723 // If there was an error determining whether or not the repository is bare, it
724 // will be returned.
725 func IsBare() (bool, error) {
726         s, err := subprocess.SimpleExec(
727                 "git", "rev-parse", "--is-bare-repository")
728
729         if err != nil {
730                 return false, err
731         }
732
733         return strconv.ParseBool(s)
734 }
735
736 // For compatibility with git clone we must mirror all flags in CloneWithoutFilters
737 type CloneFlags struct {
738         // --template <template_directory>
739         TemplateDirectory string
740         // -l --local
741         Local bool
742         // -s --shared
743         Shared bool
744         // --no-hardlinks
745         NoHardlinks bool
746         // -q --quiet
747         Quiet bool
748         // -n --no-checkout
749         NoCheckout bool
750         // --progress
751         Progress bool
752         // --bare
753         Bare bool
754         // --mirror
755         Mirror bool
756         // -o <name> --origin <name>
757         Origin string
758         // -b <name> --branch <name>
759         Branch string
760         // -u <upload-pack> --upload-pack <pack>
761         Upload string
762         // --reference <repository>
763         Reference string
764         // --reference-if-able <repository>
765         ReferenceIfAble string
766         // --dissociate
767         Dissociate bool
768         // --separate-git-dir <git dir>
769         SeparateGit string
770         // --depth <depth>
771         Depth string
772         // --recursive
773         Recursive bool
774         // --recurse-submodules
775         RecurseSubmodules bool
776         // -c <value> --config <value>
777         Config string
778         // --single-branch
779         SingleBranch bool
780         // --no-single-branch
781         NoSingleBranch bool
782         // --verbose
783         Verbose bool
784         // --ipv4
785         Ipv4 bool
786         // --ipv6
787         Ipv6 bool
788         // --shallow-since <date>
789         ShallowSince string
790         // --shallow-since <date>
791         ShallowExclude string
792         // --shallow-submodules
793         ShallowSubmodules bool
794         // --no-shallow-submodules
795         NoShallowSubmodules bool
796         // jobs <n>
797         Jobs int64
798 }
799
800 // CloneWithoutFilters clones a git repo but without the smudge filter enabled
801 // so that files in the working copy will be pointers and not real LFS data
802 func CloneWithoutFilters(flags CloneFlags, args []string) error {
803
804         cmdargs := []string{"clone"}
805
806         // flags
807         if flags.Bare {
808                 cmdargs = append(cmdargs, "--bare")
809         }
810         if len(flags.Branch) > 0 {
811                 cmdargs = append(cmdargs, "--branch", flags.Branch)
812         }
813         if len(flags.Config) > 0 {
814                 cmdargs = append(cmdargs, "--config", flags.Config)
815         }
816         if len(flags.Depth) > 0 {
817                 cmdargs = append(cmdargs, "--depth", flags.Depth)
818         }
819         if flags.Dissociate {
820                 cmdargs = append(cmdargs, "--dissociate")
821         }
822         if flags.Ipv4 {
823                 cmdargs = append(cmdargs, "--ipv4")
824         }
825         if flags.Ipv6 {
826                 cmdargs = append(cmdargs, "--ipv6")
827         }
828         if flags.Local {
829                 cmdargs = append(cmdargs, "--local")
830         }
831         if flags.Mirror {
832                 cmdargs = append(cmdargs, "--mirror")
833         }
834         if flags.NoCheckout {
835                 cmdargs = append(cmdargs, "--no-checkout")
836         }
837         if flags.NoHardlinks {
838                 cmdargs = append(cmdargs, "--no-hardlinks")
839         }
840         if flags.NoSingleBranch {
841                 cmdargs = append(cmdargs, "--no-single-branch")
842         }
843         if len(flags.Origin) > 0 {
844                 cmdargs = append(cmdargs, "--origin", flags.Origin)
845         }
846         if flags.Progress {
847                 cmdargs = append(cmdargs, "--progress")
848         }
849         if flags.Quiet {
850                 cmdargs = append(cmdargs, "--quiet")
851         }
852         if flags.Recursive {
853                 cmdargs = append(cmdargs, "--recursive")
854         }
855         if flags.RecurseSubmodules {
856                 cmdargs = append(cmdargs, "--recurse-submodules")
857         }
858         if len(flags.Reference) > 0 {
859                 cmdargs = append(cmdargs, "--reference", flags.Reference)
860         }
861         if len(flags.ReferenceIfAble) > 0 {
862                 cmdargs = append(cmdargs, "--reference-if-able", flags.ReferenceIfAble)
863         }
864         if len(flags.SeparateGit) > 0 {
865                 cmdargs = append(cmdargs, "--separate-git-dir", flags.SeparateGit)
866         }
867         if flags.Shared {
868                 cmdargs = append(cmdargs, "--shared")
869         }
870         if flags.SingleBranch {
871                 cmdargs = append(cmdargs, "--single-branch")
872         }
873         if len(flags.TemplateDirectory) > 0 {
874                 cmdargs = append(cmdargs, "--template", flags.TemplateDirectory)
875         }
876         if len(flags.Upload) > 0 {
877                 cmdargs = append(cmdargs, "--upload-pack", flags.Upload)
878         }
879         if flags.Verbose {
880                 cmdargs = append(cmdargs, "--verbose")
881         }
882         if len(flags.ShallowSince) > 0 {
883                 cmdargs = append(cmdargs, "--shallow-since", flags.ShallowSince)
884         }
885         if len(flags.ShallowExclude) > 0 {
886                 cmdargs = append(cmdargs, "--shallow-exclude", flags.ShallowExclude)
887         }
888         if flags.ShallowSubmodules {
889                 cmdargs = append(cmdargs, "--shallow-submodules")
890         }
891         if flags.NoShallowSubmodules {
892                 cmdargs = append(cmdargs, "--no-shallow-submodules")
893         }
894         if flags.Jobs > -1 {
895                 cmdargs = append(cmdargs, "--jobs", strconv.FormatInt(flags.Jobs, 10))
896         }
897
898         // Now args
899         cmdargs = append(cmdargs, args...)
900         cmd := gitNoLFS(cmdargs...)
901
902         // Assign all streams direct
903         cmd.Stdout = os.Stdout
904         cmd.Stderr = os.Stderr
905         cmd.Stdin = os.Stdin
906
907         err := cmd.Start()
908         if err != nil {
909                 return fmt.Errorf("Failed to start git clone: %v", err)
910         }
911
912         err = cmd.Wait()
913         if err != nil {
914                 return fmt.Errorf("git clone failed: %v", err)
915         }
916
917         return nil
918 }
919
920 // Checkout performs an invocation of `git-checkout(1)` applying the given
921 // treeish, paths, and force option, if given.
922 //
923 // If any error was encountered, it will be returned immediately. Otherwise, the
924 // checkout has occurred successfully.
925 func Checkout(treeish string, paths []string, force bool) error {
926         args := []string{"checkout"}
927         if force {
928                 args = append(args, "--force")
929         }
930
931         if len(treeish) > 0 {
932                 args = append(args, treeish)
933         }
934
935         if len(paths) > 0 {
936                 args = append(args, append([]string{"--"}, paths...)...)
937         }
938
939         _, err := gitNoLFSSimple(args...)
940         return err
941 }
942
943 // CachedRemoteRefs returns the list of branches & tags for a remote which are
944 // currently cached locally. No remote request is made to verify them.
945 func CachedRemoteRefs(remoteName string) ([]*Ref, error) {
946         var ret []*Ref
947         cmd := gitNoLFS("show-ref")
948
949         outp, err := cmd.StdoutPipe()
950         if err != nil {
951                 return nil, fmt.Errorf("Failed to call git show-ref: %v", err)
952         }
953         cmd.Start()
954         scanner := bufio.NewScanner(outp)
955
956         r := regexp.MustCompile(fmt.Sprintf(`([0-9a-fA-F]{40})\s+refs/remotes/%v/(.*)`, remoteName))
957         for scanner.Scan() {
958                 if match := r.FindStringSubmatch(scanner.Text()); match != nil {
959                         name := strings.TrimSpace(match[2])
960                         // Don't match head
961                         if name == "HEAD" {
962                                 continue
963                         }
964
965                         sha := match[1]
966                         ret = append(ret, &Ref{name, RefTypeRemoteBranch, sha})
967                 }
968         }
969         return ret, cmd.Wait()
970 }
971
972 // Fetch performs a fetch with no arguments against the given remotes.
973 func Fetch(remotes ...string) error {
974         if len(remotes) == 0 {
975                 return nil
976         }
977
978         var args []string
979         if len(remotes) > 1 {
980                 args = []string{"--multiple", "--"}
981         }
982         args = append(args, remotes...)
983
984         _, err := gitNoLFSSimple(append([]string{"fetch"}, args...)...)
985         return err
986 }
987
988 // RemoteRefs returns a list of branches & tags for a remote by actually
989 // accessing the remote vir git ls-remote
990 func RemoteRefs(remoteName string) ([]*Ref, error) {
991         var ret []*Ref
992         cmd := gitNoLFS("ls-remote", "--heads", "--tags", "-q", remoteName)
993
994         outp, err := cmd.StdoutPipe()
995         if err != nil {
996                 return nil, fmt.Errorf("Failed to call git ls-remote: %v", err)
997         }
998         cmd.Start()
999         scanner := bufio.NewScanner(outp)
1000
1001         r := regexp.MustCompile(`([0-9a-fA-F]{40})\s+refs/(heads|tags)/(.*)`)
1002         for scanner.Scan() {
1003                 if match := r.FindStringSubmatch(scanner.Text()); match != nil {
1004                         name := strings.TrimSpace(match[3])
1005                         // Don't match head
1006                         if name == "HEAD" {
1007                                 continue
1008                         }
1009
1010                         sha := match[1]
1011                         if match[2] == "heads" {
1012                                 ret = append(ret, &Ref{name, RefTypeRemoteBranch, sha})
1013                         } else {
1014                                 ret = append(ret, &Ref{name, RefTypeRemoteTag, sha})
1015                         }
1016                 }
1017         }
1018         return ret, cmd.Wait()
1019 }
1020
1021 // AllRefs returns a slice of all references in a Git repository in the current
1022 // working directory, or an error if those references could not be loaded.
1023 func AllRefs() ([]*Ref, error) {
1024         return AllRefsIn("")
1025 }
1026
1027 // AllRefs returns a slice of all references in a Git repository located in a
1028 // the given working directory "wd", or an error if those references could not
1029 // be loaded.
1030 func AllRefsIn(wd string) ([]*Ref, error) {
1031         cmd := gitNoLFS(
1032                 "for-each-ref", "--format=%(objectname)%00%(refname)")
1033         cmd.Dir = wd
1034
1035         outp, err := cmd.StdoutPipe()
1036         if err != nil {
1037                 return nil, lfserrors.Wrap(err, "cannot open pipe")
1038         }
1039         cmd.Start()
1040
1041         refs := make([]*Ref, 0)
1042
1043         scanner := bufio.NewScanner(outp)
1044         for scanner.Scan() {
1045                 parts := strings.SplitN(scanner.Text(), "\x00", 2)
1046                 if len(parts) != 2 {
1047                         return nil, lfserrors.Errorf(
1048                                 "git: invalid for-each-ref line: %q", scanner.Text())
1049                 }
1050
1051                 sha := parts[0]
1052                 typ, name := ParseRefToTypeAndName(parts[1])
1053
1054                 refs = append(refs, &Ref{
1055                         Name: name,
1056                         Type: typ,
1057                         Sha:  sha,
1058                 })
1059         }
1060
1061         if err := scanner.Err(); err != nil {
1062                 return nil, err
1063         }
1064
1065         return refs, nil
1066 }
1067
1068 // GetTrackedFiles returns a list of files which are tracked in Git which match
1069 // the pattern specified (standard wildcard form)
1070 // Both pattern and the results are relative to the current working directory, not
1071 // the root of the repository
1072 func GetTrackedFiles(pattern string) ([]string, error) {
1073         safePattern := sanitizePattern(pattern)
1074         rootWildcard := len(safePattern) < len(pattern) && strings.ContainsRune(safePattern, '*')
1075
1076         var ret []string
1077         cmd := gitNoLFS(
1078                 "-c", "core.quotepath=false", // handle special chars in filenames
1079                 "ls-files",
1080                 "--cached", // include things which are staged but not committed right now
1081                 "--",       // no ambiguous patterns
1082                 safePattern)
1083
1084         outp, err := cmd.StdoutPipe()
1085         if err != nil {
1086                 return nil, fmt.Errorf("Failed to call git ls-files: %v", err)
1087         }
1088         cmd.Start()
1089         scanner := bufio.NewScanner(outp)
1090         for scanner.Scan() {
1091                 line := scanner.Text()
1092
1093                 // If the given pattern is a root wildcard, skip all files which
1094                 // are not direct descendants of the repository's root.
1095                 //
1096                 // This matches the behavior of how .gitattributes performs
1097                 // filename matches.
1098                 if rootWildcard && filepath.Dir(line) != "." {
1099                         continue
1100                 }
1101
1102                 ret = append(ret, strings.TrimSpace(line))
1103         }
1104         return ret, cmd.Wait()
1105 }
1106
1107 func sanitizePattern(pattern string) string {
1108         if strings.HasPrefix(pattern, "/") {
1109                 return pattern[1:]
1110         }
1111
1112         return pattern
1113 }
1114
1115 // GetFilesChanged returns a list of files which were changed, either between 2
1116 // commits, or at a single commit if you only supply one argument and a blank
1117 // string for the other
1118 func GetFilesChanged(from, to string) ([]string, error) {
1119         var files []string
1120         args := []string{
1121                 "-c", "core.quotepath=false", // handle special chars in filenames
1122                 "diff-tree",
1123                 "--no-commit-id",
1124                 "--name-only",
1125                 "-r",
1126         }
1127
1128         if len(from) > 0 {
1129                 args = append(args, from)
1130         }
1131         if len(to) > 0 {
1132                 args = append(args, to)
1133         }
1134         args = append(args, "--") // no ambiguous patterns
1135
1136         cmd := gitNoLFS(args...)
1137         outp, err := cmd.StdoutPipe()
1138         if err != nil {
1139                 return nil, fmt.Errorf("Failed to call git diff: %v", err)
1140         }
1141         if err := cmd.Start(); err != nil {
1142                 return nil, fmt.Errorf("Failed to start git diff: %v", err)
1143         }
1144         scanner := bufio.NewScanner(outp)
1145         for scanner.Scan() {
1146                 files = append(files, strings.TrimSpace(scanner.Text()))
1147         }
1148         if err := cmd.Wait(); err != nil {
1149                 return nil, fmt.Errorf("Git diff failed: %v", err)
1150         }
1151
1152         return files, err
1153 }
1154
1155 // IsFileModified returns whether the filepath specified is modified according
1156 // to `git status`. A file is modified if it has uncommitted changes in the
1157 // working copy or the index. This includes being untracked.
1158 func IsFileModified(filepath string) (bool, error) {
1159
1160         args := []string{
1161                 "-c", "core.quotepath=false", // handle special chars in filenames
1162                 "status",
1163                 "--porcelain",
1164                 "--", // separator in case filename ambiguous
1165                 filepath,
1166         }
1167         cmd := git(args...)
1168         outp, err := cmd.StdoutPipe()
1169         if err != nil {
1170                 return false, lfserrors.Wrap(err, "Failed to call git status")
1171         }
1172         if err := cmd.Start(); err != nil {
1173                 return false, lfserrors.Wrap(err, "Failed to start git status")
1174         }
1175         matched := false
1176         for scanner := bufio.NewScanner(outp); scanner.Scan(); {
1177                 line := scanner.Text()
1178                 // Porcelain format is "<I><W> <filename>"
1179                 // Where <I> = index status, <W> = working copy status
1180                 if len(line) > 3 {
1181                         // Double-check even though should be only match
1182                         if strings.TrimSpace(line[3:]) == filepath {
1183                                 matched = true
1184                                 // keep consuming output to exit cleanly
1185                                 // will typically fall straight through anyway due to 1 line output
1186                         }
1187                 }
1188         }
1189         if err := cmd.Wait(); err != nil {
1190                 return false, lfserrors.Wrap(err, "Git status failed")
1191         }
1192
1193         return matched, nil
1194 }
1195
1196 // IsWorkingCopyDirty returns true if and only if the working copy in which the
1197 // command was executed is dirty as compared to the index.
1198 //
1199 // If the status of the working copy could not be determined, an error will be
1200 // returned instead.
1201 func IsWorkingCopyDirty() (bool, error) {
1202         bare, err := IsBare()
1203         if bare || err != nil {
1204                 return false, err
1205         }
1206
1207         out, err := gitSimple("status", "--porcelain")
1208         if err != nil {
1209                 return false, err
1210         }
1211         return len(out) != 0, nil
1212 }