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