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