Sean Abraham | c2c5a6a | 2020-06-23 17:54:41 -0600 | [diff] [blame^] | 1 | package branch |
| 2 | |
| 3 | import ( |
| 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 | |
| 16 | var ( |
| 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. |
| 25 | type 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. |
| 34 | func LogOut(format string, a ...interface{}) { |
| 35 | if StdoutLog != nil { |
| 36 | StdoutLog.Printf(format, a...) |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | // LogOut logs to stderr. |
| 41 | func 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. |
| 48 | func 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 | |
| 67 | func 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. |
| 105 | func 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. |
| 116 | func 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 | } |