| package cmd |
| |
| import ( |
| "errors" |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "regexp" |
| "strings" |
| ) |
| |
| type GitVCS struct{} |
| |
| var _ VCS = &GitVCS{} |
| |
| var ( |
| WrongVCS error = errors.New("Wrong VCS detected") |
| CannotDetermineRepo error = errors.New("Unable to determine repository") |
| ) |
| |
| var remoteRegex = regexp.MustCompile("^origin\\s+(\\S+)\\s+\\S+$") |
| |
| // returns the currently checked out remote repository |
| // according to the state of the working directory |
| func (g *GitVCS) currentRepository(directory string) (string, error) { |
| |
| // Make sure to stop bubbling up the directory structures. If the |
| // repo is another VCS sype, such as HG, git will try to bubble |
| // up to a parent git repo. |
| location, err := exec.Command("git", "rev-parse", "--git-dir").CombinedOutput() |
| if err != nil { |
| return "", WrongVCS |
| } |
| absPath, err := filepath.Abs(string(location)) |
| repoDir := filepath.Dir(absPath) |
| if repoDir != directory { |
| return "", WrongVCS |
| } |
| |
| out, err := exec.Command("git", "remote", "-v").CombinedOutput() |
| if err != nil { |
| return "", WrongVCS |
| } |
| |
| for _, line := range strings.Split(string(out), "\n") { |
| if m := remoteRegex.FindStringSubmatch(line); len(m) == 2 { |
| return m[1], nil |
| } |
| } |
| |
| return "", CannotDetermineRepo |
| } |
| |
| func (g *GitVCS) currentCheckoutMatchesTagOrRef(ref string) bool { |
| out, err := exec.Command("git", "log", "-n", "1", "--pretty=format:%H%d").CombinedOutput() |
| if err != nil { |
| return false |
| } |
| parts := strings.SplitN(string(out), " ", 2) |
| |
| sha := parts[0] |
| if strings.HasPrefix(sha, ref) { |
| return true |
| } |
| |
| if len(parts) > 1 && strings.Contains(parts[1], "tag: ") { |
| re := regexp.MustCompile("tag: ([^,)]*)") |
| for _, tagMatch := range re.FindAllStringSubmatch(parts[1], 10) { |
| if len(tagMatch) == 2 && tagMatch[1] == ref { |
| return true |
| } |
| } |
| } |
| |
| return false |
| } |
| |
| // GitGet implements the getting logic for Git. |
| func (g *GitVCS) Get(dep *Dependency) error { |
| dest := fmt.Sprintf("%s/src/%s", os.Getenv("GOPATH"), dep.Name) |
| //Info("Cloning %s into %s\n", dep.Repository, dest) |
| Info("Git: ") |
| out, err := exec.Command("git", "clone", dep.Repository, dest).CombinedOutput() |
| fmt.Print(string(out)) |
| return err |
| } |
| |
| func (g *GitVCS) Update(dep *Dependency) error { |
| dest := fmt.Sprintf("%s/src/%s", os.Getenv("GOPATH"), dep.Name) |
| |
| if _, err := os.Stat(dest); err != nil { |
| Info("Looks like %s is a new package. Cloning.\n", dep.Name) |
| return g.Get(dep) |
| } |
| |
| oldDir, err := os.Getwd() |
| if err != nil { |
| return err |
| } |
| if err := os.Chdir(dest); err != nil { |
| return err |
| } |
| |
| defer os.Chdir(oldDir) |
| |
| if oldRepo, err := g.currentRepository(dest); err != nil || oldRepo != dep.Repository { |
| switch err { |
| case WrongVCS: |
| Info("VCS type changed ('%s'). I'm doing a fresh clone.\n", err) |
| case nil: |
| Info("Repository changed from %s to %s. I'm doing a clean checkout.\n", oldRepo, dep.Repository) |
| default: |
| Info("Unable to determine currently checkout out repository ('%s'). I'm doing a fresh clone.\n", err) |
| } |
| os.Chdir(oldDir) |
| if err := os.RemoveAll(dest); err != nil { |
| return err |
| } |
| return g.Get(dep) |
| } |
| |
| if g.currentCheckoutMatchesTagOrRef(dep.Reference) { |
| Info("%s is up to date.\n", dep.Name) |
| return nil |
| } |
| |
| // Because we can't predict which branch we want to be on, and since |
| // we want to set checkouts explicitly, we should probably fetch. |
| //out, err := exec.Command("git", "pull", "--ff-only").CombinedOutput() |
| Info("Git: ") |
| out, err := exec.Command("git", "fetch", "--all").CombinedOutput() |
| fmt.Print(string(out)) |
| return err |
| } |
| |
| func (g *GitVCS) Version(dep *Dependency) error { |
| dest := fmt.Sprintf("%s/src/%s", os.Getenv("GOPATH"), dep.Name) |
| |
| oldDir, err := os.Getwd() |
| if err != nil { |
| return err |
| } |
| os.Chdir(dest) |
| defer os.Chdir(oldDir) |
| |
| //Info("Setting %s with 'git checkout'\n", dep.Name) |
| |
| // Now get to the right reference. |
| if out, err := exec.Command("git", "checkout", dep.Reference).CombinedOutput(); err != nil { |
| fmt.Println(string(out)) |
| return err |
| } else { |
| updatedTo := "the latest" |
| if dep.Reference != "" { |
| updatedTo = dep.Reference |
| } |
| Info("Setting version of %s to %s\n", dep.Name, updatedTo) |
| //fmt.Print(string(out)) |
| } |
| |
| // EXPERIMENTAL: This will keep the repo up to date according to the |
| // master branch on Git. Since 'master' is convention only, this isn't |
| // a 100% reliable way to do things. |
| if dep.Reference == "" { |
| Info("No Git reference set. Trying to update 'master'...\n") |
| dep.Reference = "master" |
| } |
| |
| branchref := fmt.Sprintf("origin/%s", dep.Reference) |
| //err = exec.Command("git", "show-ref", "-q", branchref).Run() |
| out, err := exec.Command("git", "show-ref", branchref).CombinedOutput() |
| if err == nil { |
| //Info("Git: Found branch %s", string(out)) |
| //Debug("Reference %s is to a branch.", dep.Reference) |
| // git merge --ff-only origin $VERSION |
| out, err := exec.Command("git", "pull", "--ff-only", "origin", dep.Reference).CombinedOutput() |
| Info("Git: (merge) %s", string(out)) |
| if err != nil { |
| return err |
| } |
| } |
| |
| // EXPERIMENTAL: Show how far behind we are. |
| out, err = exec.Command("git", "rev-list", "--count", "HEAD..origin").CombinedOutput() |
| if err == nil { |
| count := strings.TrimSpace(string(out)) |
| if count != "0" { |
| var c string |
| switch len(count) { |
| // 0-9, not that bad |
| case 1: |
| c = Green |
| // 10-99, we're getting behind |
| case 2: |
| c = Yellow |
| // Whoa! We're falling way behind! |
| default: |
| c = Red |
| } |
| Info(Color(c, fmt.Sprintf("Git: %s is %s behind origin.\n", dep.Name, count))) |
| } |
| } |
| |
| return nil |
| } |
| |
| func (g *GitVCS) LastCommit(dep *Dependency) (string, error) { |
| dest := fmt.Sprintf("%s/src/%s", os.Getenv("GOPATH"), dep.Name) |
| |
| oldDir, err := os.Getwd() |
| if err != nil { |
| return "", err |
| } |
| os.Chdir(dest) |
| defer os.Chdir(oldDir) |
| |
| out, err := exec.Command("git", "log", "-n", "1", "--pretty=format:%h%d").CombinedOutput() |
| if err != nil { |
| return "", err |
| } |
| parts := strings.SplitN(string(out), " ", 2) |
| |
| sha := parts[0] |
| |
| // Send back a tag if a tag matches. |
| if len(parts) > 1 && strings.Contains(parts[1], "tag: ") { |
| re := regexp.MustCompile("tag: ([^,)]*)") |
| subs := re.FindStringSubmatch(parts[1]) |
| if len(subs) > 1 { |
| return subs[1], nil |
| } |
| } |
| |
| return sha, nil |
| } |