blob: 2fe9e3af40ddb42ddf781c48ef11aea2541b271d [file] [log] [blame]
Sean Abrahamc2c5a6a2020-06-23 17:54:41 -06001package branch
2
3import (
4 "fmt"
5 "go.chromium.org/chromiumos/infra/go/internal/git"
6 "go.chromium.org/chromiumos/infra/go/internal/repo"
7 "go.chromium.org/luci/common/errors"
8 "io/ioutil"
9 "log"
10 "net/url"
11 "path"
12 "path/filepath"
13 "strconv"
14)
15
16var (
17 StdoutLog *log.Logger
18 StderrLog *log.Logger
19 WorkingManifest repo.Manifest
20 RepoToolPath string
21 ManifestCheckout string
22)
23
24// CheckoutOptions describes how to check out a Git repo.
25type CheckoutOptions struct {
26 // If set, will get only this Ref.
27 // If not set, will get the full repo.
28 Ref string
29 // To be used with the git clone --depth flag.
30 Depth int
31}
32
33// LogOut logs to stdout.
34func LogOut(format string, a ...interface{}) {
35 if StdoutLog != nil {
36 StdoutLog.Printf(format, a...)
37 }
38}
39
40// LogOut logs to stderr.
41func LogErr(format string, a ...interface{}) {
42 if StderrLog != nil {
43 StderrLog.Printf(format, a...)
44 }
45}
46
47// ProjectFetchUrl returns the fetch URL for a remote project.
48func ProjectFetchUrl(projectPath string) (string, error) {
49 project, err := WorkingManifest.GetProjectByPath(projectPath)
50 if err != nil {
51 return "", err
52 }
53
54 remote := WorkingManifest.GetRemoteByName(project.RemoteName)
55 if remote == nil {
56 return "", fmt.Errorf("remote %s does not exist in working manifest", project.RemoteName)
57 }
58 projectUrl, err := url.Parse(remote.Fetch)
59 if err != nil {
60 return "", errors.Annotate(err, "failed to parse fetch location for remote %s", remote.Name).Err()
61 }
62 projectUrl.Path = path.Join(projectUrl.Path, project.Name)
63
64 return projectUrl.String(), nil
65}
66
67func getProjectCheckoutFromUrl(projectUrl string, opts *CheckoutOptions) (string, error) {
68 checkoutDir, err := ioutil.TempDir("", "cros-branch-")
69 if err != nil {
70 return "", errors.Annotate(err, "tmp dir could not be created").Err()
71 }
72
73 if err := git.Init(checkoutDir, false); err != nil {
74 return "", err
75 }
76 if err := git.AddRemote(checkoutDir, "origin", projectUrl); err != nil {
77 return "", errors.Annotate(err, "could not add %s as remote", projectUrl).Err()
78 }
79
80 cmd := []string{"fetch", "origin"}
81 if opts != nil {
82 if opts.Ref != "" {
83 cmd = append(cmd, git.StripRefs(opts.Ref))
84 }
85 if opts.Depth > 0 {
86 cmd = append(cmd, "--depth", strconv.Itoa(opts.Depth))
87 }
88 }
89 output, err := git.RunGit(checkoutDir, cmd)
90 if err != nil {
91 return "", fmt.Errorf("failed to fetch %s: %s", projectUrl, output.Stderr)
92 }
93 checkoutBranch := "master"
94 if opts != nil && opts.Ref != "" {
95 checkoutBranch = git.StripRefs(opts.Ref)
96 }
97 if err := git.Checkout(checkoutDir, checkoutBranch); err != nil {
98 return "", fmt.Errorf("failed to checkout %s", checkoutBranch)
99 }
100
101 return checkoutDir, nil
102}
103
104// GetProjectCheckout gets a local checkout of a particular project.
105func GetProjectCheckout(projectPath string, opts *CheckoutOptions) (string, error) {
106 projectUrl, err := ProjectFetchUrl(projectPath)
107
108 if err != nil {
109 return "", errors.Annotate(err, "failed to get project fetch url").Err()
110 }
111 return getProjectCheckoutFromUrl(projectUrl, opts)
112}
113
114// InitWorkingManifest initializes a local working manifest (a.k.a. buildspec)
115// from a Gerrit path.
116func InitWorkingManifest(manifestUrl, br string) error {
117 opts := &CheckoutOptions{
118 Depth: 1,
119 Ref: br,
120 }
121 var err error
122 ManifestCheckout, err = getProjectCheckoutFromUrl(manifestUrl, opts)
123 if err != nil {
124 return errors.Annotate(err, "could not checkout %s", manifestUrl).Err()
125 }
126
127 if br != "" {
128 err := git.Checkout(ManifestCheckout, br)
129 if err != nil {
130 return errors.Annotate(err, "failed to checkout br %s of %s", br, manifestUrl).Err()
131 }
132 }
133
134 manifestPath := filepath.Join(ManifestCheckout, "default.xml")
135
136 // Read in manifest from file (and resolve includes).
137 manifest, err := repo.LoadManifestFromFileWithIncludes(manifestPath)
138 if err != nil {
139 return errors.Annotate(err, "failed to load manifests").Err()
140 }
141 WorkingManifest = *manifest
142 return nil
143}