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