// FullHash gives the full commit hash for the given ref. func (g *GitInfo) FullHash(ref string) (string, error) { output, err := exec.RunCwd(g.dir, "git", "rev-parse", fmt.Sprintf("%s^{commit}", ref)) if err != nil { return "", fmt.Errorf("Failed to obtain full hash: %s", err) } return strings.Trim(output, "\n"), nil }
// Update refreshes the history that GitInfo stores for the repo. If pull is // true then git pull is performed before refreshing. func (g *GitInfo) Update(pull, allBranches bool) error { g.mutex.Lock() defer g.mutex.Unlock() glog.Info("Beginning Update.") if pull { if _, err := exec.RunCwd(g.dir, "git", "pull"); err != nil { return fmt.Errorf("Failed to sync to HEAD: %s", err) } } glog.Info("Finished pull.") var hashes []string var timestamps map[string]time.Time var err error if allBranches { hashes, timestamps, err = readCommitsFromGitAllBranches(g.dir) } else { hashes, timestamps, err = readCommitsFromGit(g.dir, "HEAD") } glog.Infof("Finished reading commits: %s", g.dir) if err != nil { return fmt.Errorf("Failed to read commits from: %s : %s", g.dir, err) } g.hashes = hashes g.timestamps = timestamps return nil }
// GetBranches returns the list of branch heads in a Git repository. // In order to separate local working branches from published branches, only // remote branches in 'origin' are returned. func GetBranches(dir string) ([]*GitBranch, error) { output, err := exec.RunCwd(dir, "git", "show-ref") if err != nil { return nil, fmt.Errorf("Failed to get branch list: %v", err) } branches := []*GitBranch{} lines := strings.Split(output, "\n") for _, line := range lines { if line == "" { continue } parts := strings.SplitN(line, " ", 2) if len(parts) != 2 { return nil, fmt.Errorf("Could not parse output of 'git show-ref'.") } for _, prefix := range includeBranchPrefixes { if strings.HasPrefix(parts[1], prefix) { name := parts[1][len(prefix):] branches = append(branches, &GitBranch{ Name: name, Head: parts[0], }) } } } return branches, nil }
// readCommitsFromGit reads the commit history from a Git repository. func readCommitsFromGit(dir, branch string) ([]string, map[string]time.Time, error) { output, err := exec.RunCwd(dir, "git", "log", "--format=format:%H%x20%ci", branch) if err != nil { return nil, nil, fmt.Errorf("Failed to execute git log: %s", err) } lines := strings.Split(output, "\n") gitHashes := make([]*gitHash, 0, len(lines)) timestamps := map[string]time.Time{} for _, line := range lines { parts := strings.SplitN(line, " ", 2) if len(parts) == 2 { t, err := time.Parse("2006-01-02 15:04:05 -0700", parts[1]) if err != nil { return nil, nil, fmt.Errorf("Failed parsing Git log timestamp: %s", err) } hash := parts[0] gitHashes = append(gitHashes, &gitHash{hash: hash, timeStamp: t}) timestamps[hash] = t } } sort.Sort(gitHashSlice(gitHashes)) hashes := make([]string, len(gitHashes), len(gitHashes)) for i, h := range gitHashes { hashes[i] = h.hash } return hashes, timestamps, nil }
// InitalCommit returns the hash of the initial commit. func (g *GitInfo) InitialCommit() (string, error) { output, err := exec.RunCwd(g.dir, "git", "rev-list", "--max-parents=0", "HEAD") if err != nil { return "", fmt.Errorf("Failed to determine initial commit: %v", err) } return strings.Trim(output, "\n"), nil }
// ShortList returns a slice of ShortCommit for every commit in (begin, end]. func (g *GitInfo) ShortList(begin, end string) (*ShortCommits, error) { command := []string{"git", "log", "--pretty='%H,%an,%s", begin + ".." + end} output, err := exec.RunCwd(g.dir, command...) if err != nil { return nil, err } ret := &ShortCommits{ Commits: []*vcsinfo.ShortCommit{}, } for _, line := range strings.Split(output, "\n") { match := commitLineRe.FindStringSubmatch(line) if match == nil { // This could happen if the subject has new line, in which case we truncate it and ignore the remainder. continue } commit := &vcsinfo.ShortCommit{ Hash: match[1], Author: match[2], Subject: match[3], } ret.Commits = append(ret.Commits, commit) } return ret, nil }
func (g *GitInfo) SetToCommit(hash string) error { _, err := exec.RunCwd(g.dir, "git", "reset", "--hard", hash) if err != nil { return fmt.Errorf("Failed to roll back/forward to commit %s: %s", hash, err) } return nil }
// GetFile returns the contents of the given file at the given commit. func (g *GitInfo) GetFile(fileName, commit string) (string, error) { output, err := exec.RunCwd(g.dir, "git", "show", commit+":"+fileName) if err != nil { return "", err } return output, nil }
// RolledPast determines whether DEPS has rolled past the given commit. func (r *repoManager) RolledPast(hash string) bool { r.mtx.RLock() defer r.mtx.RUnlock() if _, err := exec.RunCwd(r.childDir, "git", "merge-base", "--is-ancestor", hash, r.lastRollRev); err != nil { return false } return true }
// cleanChromium forces the Chromium checkout into a clean state. func (r *repoManager) cleanChromium() error { if _, err := exec.RunCwd(r.chromiumDir, "git", "clean", "-d", "-f", "-f"); err != nil { return err } _, _ = exec.RunCwd(r.chromiumDir, "git", "rebase", "--abort") if _, err := exec.RunCwd(r.chromiumDir, "git", "checkout", "origin/master", "-f"); err != nil { return err } _, _ = exec.RunCwd(r.chromiumDir, "git", "branch", "-D", DEPS_ROLL_BRANCH) if _, err := exec.RunCommand(&exec.Command{ Dir: r.chromiumDir, Env: []string{fmt.Sprintf("PATH=%s:%s", r.depot_tools, os.Getenv("PATH"))}, Name: r.gclient, Args: []string{"revert", "--nohooks"}, }); err != nil { return err } return nil }
// RevList returns the results of "git rev-list". func (g *GitInfo) RevList(args ...string) ([]string, error) { g.mutex.Lock() defer g.mutex.Unlock() output, err := exec.RunCwd(g.dir, append([]string{"git", "rev-list"}, args...)...) if err != nil { return nil, fmt.Errorf("git rev-list failed: %v", err) } res := strings.Trim(output, "\n") if res == "" { return []string{}, nil } return strings.Split(res, "\n"), nil }
// Log returns a --name-only short log for every commit in (begin, end]. // // If end is "" then it returns just the short log for the single commit at // begin. // // Example response: // // commit b7988a21fdf23cc4ace6145a06ea824aa85db099 // Author: Joe Gregorio <*****@*****.**> // Date: Tue Aug 5 16:19:48 2014 -0400 // // A description of the commit. // // perf/go/skiaperf/perf.go // perf/go/types/types.go // perf/res/js/logic.js // func (g *GitInfo) Log(begin, end string) (string, error) { command := []string{"git", "log", "--name-only"} hashrange := begin if end != "" { hashrange += ".." + end command = append(command, hashrange) } else { command = append(command, "-n", "1", hashrange) } output, err := exec.RunCwd(g.dir, command...) if err != nil { return "", err } return output, nil }
// LastSkpCommit returns the time of the last change to the SKP_VERSION file. func (g *GitInfo) LastSkpCommit() (time.Time, error) { // Executes a git log command that looks like: // // git log --format=format:%ct -n 1 SKP_VERSION // // The output should be a single unix timestamp. output, err := exec.RunCwd(g.dir, "git", "log", "--format=format:%ct", "-n", "1", "SKP_VERSION") if err != nil { return time.Time{}, fmt.Errorf("LastSkpCommit: Failed to read git log: %s", err) } ts, err := strconv.ParseInt(output, 10, 64) if err != nil { return time.Time{}, fmt.Errorf("LastSkpCommit: Failed to parse timestamp: %s", err) } return time.Unix(ts, 0), nil }
// getLastRollRev returns the commit hash of the last-completed DEPS roll. func (r *repoManager) getLastRollRev() (string, error) { output, err := exec.RunCwd(r.chromiumDir, r.gclient, "revinfo") if err != nil { return "", err } split := strings.Split(output, "\n") for _, s := range split { if strings.HasPrefix(s, r.childPath) { subs := strings.Split(s, "@") if len(subs) != 2 { return "", fmt.Errorf("Failed to parse output of `gclient revinfo`") } return subs[1], nil } } return "", fmt.Errorf("Failed to parse output of `gclient revinfo`") }
// getBranchesForCommit returns a string set with all the branches that can reach // the commit with the given hash. // TODO(stephana): Speed up this method, there are either better ways to do this // in git or the results can be cached. func (g *GitInfo) getBranchesForCommit(hash string) (map[string]bool, error) { output, err := exec.RunCwd(g.dir, "git", "branch", "--list", "--contains", hash) if err != nil { return nil, fmt.Errorf("Failed to get branches for commit %s: %s", hash, err) } lines := strings.Split(strings.TrimSpace(output), "\n") ret := map[string]bool{} for _, line := range lines { l := strings.TrimSpace(line) if l != "" { // Splitting the line to filter out the '*' that marks the active branch. parts := strings.Split(l, " ") ret[parts[len(parts)-1]] = true } } return ret, nil }
// SkpCommits returns the indices for all the commits that contain SKP updates. func (g *GitInfo) SkpCommits(tile *tiling.Tile) ([]int, error) { // Executes a git log command that looks like: // // git log --format=format:%H 32956400b4d8f33394e2cdef9b66e8369ba2a0f3..e7416bfc9858bde8fc6eb5f3bfc942bc3350953a SKP_VERSION // // The output should be a \n separated list of hashes that match. first, last := tile.CommitRange() output, err := exec.RunCwd(g.dir, "git", "log", "--format=format:%H", first+".."+last, "SKP_VERSION") if err != nil { return nil, fmt.Errorf("SkpCommits: Failed to find git log of SKP_VERSION: %s", err) } hashes := strings.Split(output, "\n") ret := []int{} for i, c := range tile.Commits { if c.CommitTime != 0 && util.In(c.Hash, hashes) { ret = append(ret, i) } } return ret, nil }
// Details returns more information than ShortCommit about a given commit. // See the vcsinfo.VCS interface for details. func (g *GitInfo) Details(hash string, includeBranchInfo bool) (*vcsinfo.LongCommit, error) { g.mutex.Lock() defer g.mutex.Unlock() if c, ok := g.detailsCache[hash]; ok { return c, nil } output, err := exec.RunCwd(g.dir, "git", "log", "-n", "1", "--format=format:%H%n%P%n%an%x20(%ae)%n%s%n%b", hash) if err != nil { return nil, fmt.Errorf("Failed to execute Git: %s", err) } lines := strings.SplitN(output, "\n", 5) if len(lines) != 5 { return nil, fmt.Errorf("Failed to parse output of 'git log'.") } branches := map[string]bool{} if includeBranchInfo { branches, err = g.getBranchesForCommit(hash) if err != nil { return nil, err } } c := vcsinfo.LongCommit{ ShortCommit: &vcsinfo.ShortCommit{ Hash: lines[0], Author: lines[2], Subject: lines[3], }, Parents: strings.Split(lines[1], " "), Body: lines[4], Timestamp: g.timestamps[hash], Branches: branches, } g.detailsCache[hash] = &c return &c, nil }
// CreateNewRoll creates and uploads a new DEPS roll to the given commit. // Returns the issue number of the uploaded roll. func (r *repoManager) CreateNewRoll(emails, cqExtraTrybots []string, dryRun bool) (int64, error) { r.mtx.Lock() defer r.mtx.Unlock() // Clean the checkout, get onto a fresh branch. if err := r.cleanChromium(); err != nil { return 0, err } if _, err := exec.RunCwd(r.chromiumDir, "git", "checkout", "-b", DEPS_ROLL_BRANCH, "-t", "origin/master", "-f"); err != nil { return 0, err } // Defer some more cleanup. defer func() { util.LogErr(r.cleanChromium()) }() // Create the roll CL. if _, err := exec.RunCwd(r.chromiumDir, "git", "config", "user.name", autoroll.ROLL_AUTHOR); err != nil { return 0, err } if _, err := exec.RunCwd(r.chromiumDir, "git", "config", "user.email", autoroll.ROLL_AUTHOR); err != nil { return 0, err } if _, err := exec.RunCommand(&exec.Command{ Dir: r.chromiumDir, Env: []string{fmt.Sprintf("PATH=%s:%s", r.depot_tools, os.Getenv("PATH"))}, Name: r.rollDep, Args: []string{r.childPath, r.childHead}, }); err != nil { return 0, err } // Build the commit message, starting with the message provided by roll-dep. commitMsg, err := exec.RunCwd(r.chromiumDir, "git", "log", "-n1", "--format=%B", "HEAD") if err != nil { return 0, err } if cqExtraTrybots != nil && len(cqExtraTrybots) > 0 { commitMsg += "\n" + fmt.Sprintf(TMPL_CQ_INCLUDE_TRYBOTS, strings.Join(cqExtraTrybots, ",")) } uploadCmd := &exec.Command{ Dir: r.chromiumDir, Env: []string{fmt.Sprintf("PATH=%s:%s", r.depot_tools, os.Getenv("PATH"))}, Name: "git", Args: []string{"cl", "upload", "--bypass-hooks", "-f"}, } if dryRun { uploadCmd.Args = append(uploadCmd.Args, "--cq-dry-run") } else { uploadCmd.Args = append(uploadCmd.Args, "--use-commit-queue") } tbr := "\nTBR=" if emails != nil && len(emails) > 0 { emailStr := strings.Join(emails, ",") tbr += emailStr uploadCmd.Args = append(uploadCmd.Args, "--send-mail", "--cc", emailStr) } commitMsg += tbr uploadCmd.Args = append(uploadCmd.Args, "-m", commitMsg) // Upload the CL. uploadOutput, err := exec.RunCommand(uploadCmd) if err != nil { return 0, err } issues := ISSUE_CREATED_REGEX.FindStringSubmatch(uploadOutput) if len(issues) != 2 { return 0, fmt.Errorf("Failed to find newly-uploaded issue number!") } return strconv.ParseInt(issues[1], 10, 64) }