blob: f4a7ee400962e3200eec0be298670c7a07240779 [file] [log] [blame]
Jack Neusfc3b5772019-07-03 11:18:42 -06001// Copyright 2019 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4package git
5
6import (
7 "bytes"
8 "context"
9 "fmt"
Jack Neusfc3b5772019-07-03 11:18:42 -060010 "regexp"
11 "strings"
Jack Neus07511722019-07-12 15:41:10 -060012
13 "go.chromium.org/chromiumos/infra/go/internal/cmd"
Jack Neusfc3b5772019-07-03 11:18:42 -060014)
15
16var (
Jack Neus07511722019-07-12 15:41:10 -060017 CommandRunnerImpl cmd.CommandRunner = cmd.RealCommandRunner{}
Jack Neusfc3b5772019-07-03 11:18:42 -060018)
19
Jack Neusfc3b5772019-07-03 11:18:42 -060020type CommandOutput struct {
21 Stdout string
22 Stderr string
23}
24
25// Struct representing a remote ref.
26type RemoteRef struct {
27 Remote string
28 Ref string
29}
30
31// RunGit the specified git command in the specified repo. It returns
32// stdout and stderr.
33func RunGit(gitRepo string, cmd []string) (CommandOutput, error) {
34 ctx := context.Background()
35 var stdoutBuf, stderrBuf bytes.Buffer
Jack Neus07511722019-07-12 15:41:10 -060036 err := CommandRunnerImpl.RunCommand(ctx, &stdoutBuf, &stderrBuf, gitRepo, "git", cmd...)
Jack Neusfc3b5772019-07-03 11:18:42 -060037 cmdOutput := CommandOutput{stdoutBuf.String(), stderrBuf.String()}
38 return cmdOutput, err
39}
40
41// GetCurrentBranch returns current branch of a repo, and an empty string
42// if repo is on detached HEAD.
43func GetCurrentBranch(cwd string) string {
44 output, err := RunGit(cwd, []string{"symbolic-ref", "-q", "HEAD"})
45 if err != nil {
46 return ""
47 }
48 return StripRefsHead(strings.TrimSpace(output.Stdout))
49}
50
51// MatchBranchName returns the names of branches who match the specified
52// regular expression.
53func MatchBranchName(gitRepo string, pattern *regexp.Regexp) ([]string, error) {
54 // Regex should be case insensitive.
55 if !strings.HasPrefix(pattern.String(), "(?i)") {
56 pattern = regexp.MustCompile("(?i)" + pattern.String())
57 }
58
59 output, err := RunGit(gitRepo, []string{"ls-remote", gitRepo})
60 if err != nil {
61 // Could not read branches.
62 return []string{}, fmt.Errorf("git error: %s\nstderr: %s", err.Error(), output.Stderr)
63 }
64 // Find all branches that match the pattern.
65 branches := strings.Split(output.Stdout, "\n")
66 matchedBranches := []string{}
67 for _, branch := range branches {
68 branch = strings.TrimSpace(branch)
69 if branch == "" {
70 continue
71 }
72 branch = strings.Fields(branch)[1]
73 if pattern.Match([]byte(branch)) {
74 matchedBranches = append(matchedBranches, branch)
75 }
76 }
77 return matchedBranches, nil
78}
79
80// GetGitRepoRevision finds and returns the revision of a branch.
81func GetGitRepoRevision(cwd string) (string, error) {
82 output, err := RunGit(cwd, []string{"rev-parse", "HEAD"})
83 return strings.TrimSpace(output.Stdout), err
84}
85
86// StipRefsHead removes leading 'refs/heads/' from a ref name.
87func StripRefsHead(ref string) string {
88 return strings.TrimPrefix(ref, "refs/heads/")
89}
90
91// NormalizeRef converts git branch refs into fully qualified form.
92func NormalizeRef(ref string) string {
93 if ref == "" || strings.HasPrefix(ref, "refs/") {
94 return ref
95 }
96 return fmt.Sprintf("refs/heads/%s", ref)
97}
98
99// StripRefs removes leading 'refs/heads/', 'refs/remotes/[^/]+/' from a ref name.
100func StripRefs(ref string) string {
101 ref = StripRefsHead(ref)
102 // If the ref starts with ref/remotes/, then we want the part of the string
103 // that comes after the third "/".
104 // Example: refs/remotes/origin/master --> master
105 // Example: refs/remotse/origin/foo/bar --> foo/bar
106 if strings.HasPrefix(ref, "refs/remotes/") {
107 refParts := strings.SplitN(ref, "/", 4)
108 return refParts[len(refParts)-1]
109 }
110 return ref
111}