Exemple #1
0
// checkDependents makes sure that all CLs in the sequence of
// dependent CLs leading to (but not including) the current branch
// have been exported to Gerrit.
func checkDependents(ctx *tool.Context) (e error) {
	originalBranch, err := ctx.Git().CurrentBranchName()
	if err != nil {
		return err
	}
	branches, err := getDependentCLs(ctx, originalBranch)
	if err != nil {
		return err
	}
	for i := 1; i < len(branches); i++ {
		file, err := getCommitMessageFileName(ctx, branches[i])
		if err != nil {
			return err
		}
		if _, err := ctx.Run().Stat(file); err != nil {
			if !os.IsNotExist(err) {
				return err
			}
			return fmt.Errorf(`Failed to export the branch %q to Gerrit because its ancestor %q has not been exported to Gerrit yet.
The following steps are needed before the operation can be retried:
$ git checkout %v
$ jiri cl mail
$ git checkout %v
# retry the original command
`, originalBranch, branches[i], branches[i], originalBranch)
		}
	}

	return nil
}
Exemple #2
0
// writeMetadata stores the given project metadata in the directory
// identified by the given path.
func writeMetadata(ctx *tool.Context, project Project, dir string) (e error) {
	metadataDir := filepath.Join(dir, metadataDirName)
	cwd, err := os.Getwd()
	if err != nil {
		return err
	}
	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
	if err := ctx.Run().MkdirAll(metadataDir, os.FileMode(0755)); err != nil {
		return err
	}
	if err := ctx.Run().Chdir(metadataDir); err != nil {
		return err
	}
	// Replace absolute project paths with relative paths to make it
	// possible to move the $JIRI_ROOT directory locally.
	relPath, err := ToRel(project.Path)
	if err != nil {
		return err
	}
	project.Path = relPath
	bytes, err := xml.Marshal(project)
	if err != nil {
		return fmt.Errorf("Marhsal() failed: %v", err)
	}
	metadataFile := filepath.Join(metadataDir, metadataFileName)
	tmpMetadataFile := metadataFile + ".tmp"
	if err := ctx.Run().WriteFile(tmpMetadataFile, bytes, os.FileMode(0644)); err != nil {
		return err
	}
	if err := ctx.Run().Rename(tmpMetadataFile, metadataFile); err != nil {
		return err
	}
	return nil
}
Exemple #3
0
func createOncallFile(t *testing.T, ctx *tool.Context) {
	content := `<?xml version="1.0" ?>
<rotation>
  <shift>
    <primary>spetrovic</primary>
    <secondary>suharshs</secondary>
    <startDate>Nov 5, 2014 12:00:00 PM</startDate>
  </shift>
  <shift>
    <primary>suharshs</primary>
    <secondary>jingjin</secondary>
    <startDate>Nov 12, 2014 12:00:00 PM</startDate>
  </shift>
  <shift>
    <primary>jsimsa</primary>
    <secondary>toddw</secondary>
    <startDate>Nov 19, 2014 12:00:00 PM</startDate>
  </shift>
</rotation>`
	oncallRotationsFile, err := OncallRotationPath(ctx)
	if err != nil {
		t.Fatalf("%v", err)
	}
	dir := filepath.Dir(oncallRotationsFile)
	dirMode := os.FileMode(0700)
	if err := ctx.Run().MkdirAll(dir, dirMode); err != nil {
		t.Fatalf("MkdirAll(%q, %v) failed: %v", dir, dirMode, err)
	}
	fileMode := os.FileMode(0644)
	if err := ioutil.WriteFile(oncallRotationsFile, []byte(content), fileMode); err != nil {
		t.Fatalf("WriteFile(%q, %q, %v) failed: %v", oncallRotationsFile, content, fileMode, err)
	}
}
Exemple #4
0
// UpdateUniverse updates all local projects and tools to match the
// remote counterparts identified by the given manifest. Optionally,
// the 'gc' flag can be used to indicate that local projects that no
// longer exist remotely should be removed.
func UpdateUniverse(ctx *tool.Context, gc bool) (e error) {
	ctx.TimerPush("update universe")
	defer ctx.TimerPop()
	_, remoteProjects, remoteTools, remoteHooks, err := readManifest(ctx, true)
	if err != nil {
		return err
	}
	// 1. Update all local projects to match their remote counterparts.
	if err := updateProjects(ctx, remoteProjects, gc); err != nil {
		return err
	}
	// 2. Build all tools in a temporary directory.
	tmpDir, err := ctx.Run().TempDir("", "tmp-jiri-tools-build")
	if err != nil {
		return fmt.Errorf("TempDir() failed: %v", err)
	}
	defer collect.Error(func() error { return ctx.Run().RemoveAll(tmpDir) }, &e)
	if err := buildToolsFromMaster(ctx, remoteTools, tmpDir); err != nil {
		return err
	}
	// 3. Install the tools into $JIRI_ROOT/devtools/bin.
	if err := InstallTools(ctx, tmpDir); err != nil {
		return err
	}
	// 4. Run all specified hooks
	return runHooks(ctx, remoteHooks)
}
Exemple #5
0
// setProjectRevisions sets the current project revision from the master for
// each project as found on the filesystem
func setProjectRevisions(ctx *tool.Context, projects Projects) (_ Projects, e error) {
	cwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)

	for name, project := range projects {
		switch project.Protocol {
		case "git":
			if err := ctx.Run().Chdir(project.Path); err != nil {
				return nil, err
			}
			revision, err := ctx.Git().CurrentRevisionOfBranch("master")
			if err != nil {
				return nil, err
			}
			project.Revision = revision
		default:
			return nil, UnsupportedProtocolErr(project.Protocol)
		}
		projects[name] = project
	}
	return projects, nil
}
Exemple #6
0
func createRemoteManifest(t *testing.T, ctx *tool.Context, dir string, remotes []string) {
	manifestDir, perm := filepath.Join(dir, "v2"), os.FileMode(0755)
	if err := ctx.Run().MkdirAll(manifestDir, perm); err != nil {
		t.Fatalf("%v", err)
	}
	manifest := Manifest{}
	for i, remote := range remotes {
		project := Project{
			Name:     remote,
			Path:     localProjectName(i),
			Protocol: "git",
			Remote:   remote,
		}
		manifest.Projects = append(manifest.Projects, project)
	}
	manifest.Hosts = []Host{
		Host{
			Name:     "gerrit",
			Location: "git://example.com/gerrit",
		},
		Host{
			Name:     "git",
			Location: "git://example.com/git",
		},
	}
	commitManifest(t, ctx, &manifest, dir)
}
Exemple #7
0
// revisionChanges commits changes identified by the given manifest
// file and label to the manifest repository and (if applicable)
// pushes these changes to the remote repository.
func revisionChanges(ctx *tool.Context, snapshotDir, snapshotFile, label string) (e error) {
	cwd, err := os.Getwd()
	if err != nil {
		return err
	}
	defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e)
	if err := ctx.Run().Chdir(snapshotDir); err != nil {
		return err
	}
	relativeSnapshotPath := strings.TrimPrefix(snapshotFile, snapshotDir+string(os.PathSeparator))
	if err := ctx.Git().Add(relativeSnapshotPath); err != nil {
		return err
	}
	if err := ctx.Git().Add(label); err != nil {
		return err
	}
	name := strings.TrimPrefix(snapshotFile, snapshotDir)
	if err := ctx.Git().CommitWithMessage(fmt.Sprintf("adding snapshot %q for label %q", name, label)); err != nil {
		return err
	}
	if remoteFlag {
		if err := ctx.Git().Push("origin", "master", gitutil.VerifyOpt(false)); err != nil {
			return err
		}
	}
	return nil
}
Exemple #8
0
// gitCookies attempts to read and parse cookies from the .gitcookies file in
// the users home directory.
func gitCookies(ctx *tool.Context) []*http.Cookie {
	cookies := []*http.Cookie{}

	homeDir := os.Getenv("HOME")
	if homeDir == "" {
		return cookies
	}

	cookieFile := filepath.Join(homeDir, ".gitcookies")
	bytes, err := ctx.Run().ReadFile(cookieFile)
	if err != nil {
		return cookies
	}

	lines := strings.Split(string(bytes), "\n")
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}
		cookie, err := parseCookie(line)
		if err != nil {
			fmt.Fprintf(ctx.Stderr(), "error parsing cookie in .gitcookies: %v\n", err)
		} else {
			cookies = append(cookies, cookie)
		}
	}
	return cookies
}
Exemple #9
0
func loadAliases(ctx *tool.Context) (*aliasMaps, error) {
	aliasesFile := aliasesFlag
	if aliasesFile == "" {
		dataDir, err := project.DataDirPath(ctx, tool.Name)
		if err != nil {
			return nil, err
		}
		aliasesFile = filepath.Join(dataDir, aliasesFileName)
	}
	bytes, err := ctx.Run().ReadFile(aliasesFile)
	if err != nil {
		return nil, err
	}
	var data aliasesSchema
	if err := xml.Unmarshal(bytes, &data); err != nil {
		return nil, fmt.Errorf("Unmarshal(%v) failed: %v", string(bytes), err)
	}
	aliases := &aliasMaps{
		emails: map[string]string{},
		names:  map[string]string{},
	}
	for _, email := range data.Emails {
		for _, alias := range email.Aliases {
			aliases.emails[alias] = email.Canonical
		}
	}
	for _, name := range data.Names {
		for _, alias := range name.Aliases {
			aliases.names[alias] = name.Canonical
		}
	}
	return aliases, nil
}
Exemple #10
0
// setPathHelper is a utility function for setting path environment
// variables for different types of workspaces.
func setPathHelper(ctx *tool.Context, env *envvar.Vars, name, root string, workspaces []string, suffix string) error {
	path := env.GetTokens(name, ":")
	projects, _, err := project.ReadManifest(ctx)
	if err != nil {
		return err
	}
	for _, workspace := range workspaces {
		absWorkspace := filepath.Join(root, workspace, suffix)
		// Only append an entry to the path if the workspace is rooted
		// under a jiri project that exists locally or vice versa.
		for _, project := range projects {
			// We check if <project.Path> is a prefix of <absWorkspace> to
			// account for Go workspaces nested under a single jiri project,
			// such as: $JIRI_ROOT/release/projects/chat/go.
			//
			// We check if <absWorkspace> is a prefix of <project.Path> to
			// account for Go workspaces that span multiple jiri projects,
			// such as: $JIRI_ROOT/release/go.
			if strings.HasPrefix(absWorkspace, project.Path) || strings.HasPrefix(project.Path, absWorkspace) {
				if _, err := ctx.Run().Stat(filepath.Join(absWorkspace)); err == nil {
					path = append(path, absWorkspace)
					break
				}
			}
		}
	}
	env.SetTokens(name, path, ":")
	return nil
}
Exemple #11
0
func saveConfig(ctx *tool.Context, config *Config, path string) error {
	var data configSchema
	data.APICheckProjects = set.String.ToSlice(config.apiCheckProjects)
	sort.Strings(data.APICheckProjects)
	data.CopyrightCheckProjects = set.String.ToSlice(config.copyrightCheckProjects)
	sort.Strings(data.CopyrightCheckProjects)
	for _, workspace := range config.goWorkspaces {
		data.GoWorkspaces = append(data.GoWorkspaces, workspace)
	}
	sort.Strings(data.GoWorkspaces)
	for _, job := range config.jenkinsMatrixJobs {
		data.JenkinsMatrixJobs = append(data.JenkinsMatrixJobs, job)
	}
	sort.Sort(data.JenkinsMatrixJobs)
	for name, tests := range config.projectTests {
		data.ProjectTests = append(data.ProjectTests, testGroupSchema{
			Name:  name,
			Tests: tests,
		})
	}
	sort.Sort(data.ProjectTests)
	for name, dependencies := range config.testDependencies {
		data.TestDependencies = append(data.TestDependencies, dependencyGroupSchema{
			Name:         name,
			Dependencies: dependencies,
		})
	}
	sort.Sort(data.TestDependencies)
	for name, tests := range config.testGroups {
		data.TestGroups = append(data.TestGroups, testGroupSchema{
			Name:  name,
			Tests: tests,
		})
	}
	sort.Sort(data.TestGroups)
	for name, parts := range config.testParts {
		data.TestParts = append(data.TestParts, partGroupSchema{
			Name:  name,
			Parts: parts,
		})
	}
	sort.Sort(data.TestParts)
	for _, workspace := range config.vdlWorkspaces {
		data.VDLWorkspaces = append(data.VDLWorkspaces, workspace)
	}
	sort.Strings(data.VDLWorkspaces)
	bytes, err := xml.MarshalIndent(data, "", "  ")
	if err != nil {
		return fmt.Errorf("MarshalIndent(%v) failed: %v", data, err)
	}
	if err := ctx.Run().MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil {
		return err
	}
	if err := ctx.Run().WriteFile(path, bytes, os.FileMode(0644)); err != nil {
		return err
	}
	return nil
}
Exemple #12
0
// assertFilesDoNotExist asserts that the files do not exist.
func assertFilesDoNotExist(t *testing.T, ctx *tool.Context, files []string) {
	for _, file := range files {
		if _, err := ctx.Run().Stat(file); err != nil && !os.IsNotExist(err) {
			t.Fatalf("%v", err)
		} else if err == nil {
			t.Fatalf("expected file %v to not exist but it did", file)
		}
	}
}
Exemple #13
0
// commitFile commits a file with the specified content into a branch
func commitFile(t *testing.T, ctx *tool.Context, filename string, content string) {
	if err := ctx.Run().WriteFile(filename, []byte(content), 0644); err != nil {
		t.Fatalf("%v", err)
	}
	commitMessage := "Commit " + filename
	if err := ctx.Git().CommitFile(filename, commitMessage); err != nil {
		t.Fatalf("%v", err)
	}
}
Exemple #14
0
// assertFileContent asserts that the content of the given file
// matches the expected content.
func assertFileContent(t *testing.T, ctx *tool.Context, file, want string) {
	got, err := ctx.Run().ReadFile(file)
	if err != nil {
		t.Fatalf("%v\n", err)
	}
	if string(got) != want {
		t.Fatalf("unexpected content of file %v: got %v, want %v", file, got, want)
	}
}
Exemple #15
0
func (op deleteOperation) Test(ctx *tool.Context) error {
	if _, err := ctx.Run().Stat(op.source); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("cannot delete %q as it does not exist", op.source)
		}
		return err
	}
	return nil
}
Exemple #16
0
func (root FakeJiriRoot) readManifest(ctx *tool.Context, path string) (*Manifest, error) {
	bytes, err := ctx.Run().ReadFile(path)
	if err != nil {
		return nil, err
	}
	var manifest Manifest
	if err := xml.Unmarshal(bytes, &manifest); err != nil {
		return nil, fmt.Errorf("Unmarshal(%v) failed: %v", string(bytes), err)
	}
	return &manifest, nil
}
Exemple #17
0
func (op createOperation) Test(ctx *tool.Context) error {
	// Check the local file system.
	if _, err := ctx.Run().Stat(op.destination); err != nil {
		if !os.IsNotExist(err) {
			return err
		}
	} else {
		return fmt.Errorf("cannot create %q as it already exists", op.destination)
	}
	return nil
}
Exemple #18
0
// RunCommand runs the specified command with the specified args and environment
// whilst logging the output to ctx.Stdout() on error or if tracing is requested
// via the -v flag.
func RunCommand(ctx *tool.Context, env map[string]string, bin string, args ...string) error {
	var out bytes.Buffer
	opts := ctx.Run().Opts()
	opts.Stdout = &out
	opts.Stderr = &out
	opts.Env = env
	err := ctx.Run().CommandWithOpts(opts, bin, args...)
	if err != nil || tool.VerboseFlag {
		fmt.Fprintf(ctx.Stdout(), "%s", out.String())
	}
	return err
}
Exemple #19
0
func resetToOriginMaster(t *testing.T, ctx *tool.Context, projectDir string) {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("%v", err)
	}
	defer ctx.Run().Chdir(cwd)
	if err := ctx.Run().Chdir(projectDir); err != nil {
		t.Fatalf("%v", err)
	}
	if err := ctx.Git().Reset("origin/master"); err != nil {
		t.Fatalf("%v", err)
	}
}
Exemple #20
0
func createAndCheckoutBranch(t *testing.T, ctx *tool.Context, projectDir, branch string) {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("%v", err)
	}
	defer ctx.Run().Chdir(cwd)
	if err := ctx.Run().Chdir(projectDir); err != nil {
		t.Fatalf("%v", err)
	}
	if err := ctx.Git().CreateAndCheckoutBranch(branch); err != nil {
		t.Fatalf("%v", err)
	}
}
Exemple #21
0
func checkReadme(t *testing.T, ctx *tool.Context, project, message string) {
	if _, err := ctx.Run().Stat(project); err != nil {
		t.Fatalf("%v", err)
	}
	readmeFile := filepath.Join(project, "README")
	data, err := ctx.Run().ReadFile(readmeFile)
	if err != nil {
		t.Fatalf("%v", err)
	}
	if got, want := data, []byte(message); bytes.Compare(got, want) != 0 {
		t.Fatalf("unexpected content %v:\ngot\n%s\nwant\n%s\n", project, got, want)
	}
}
Exemple #22
0
func addRemote(t *testing.T, ctx *tool.Context, localProject, name, remoteProject string) {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("%v", err)
	}
	defer ctx.Run().Chdir(cwd)
	if err := ctx.Run().Chdir(localProject); err != nil {
		t.Fatalf("%v", err)
	}
	if err := ctx.Git().AddRemote(name, remoteProject); err != nil {
		t.Fatalf("%v", err)
	}
}
Exemple #23
0
// Checks that /.jiri/ is ignored in a local project checkout
func checkGitIgnore(t *testing.T, ctx *tool.Context, project string) {
	if _, err := ctx.Run().Stat(project); err != nil {
		t.Fatalf("%v", err)
	}
	gitInfoExcludeFile := filepath.Join(project, ".git", "info", "exclude")
	data, err := ioutil.ReadFile(gitInfoExcludeFile)
	if err != nil {
		t.Fatalf("ReadFile(%v) failed: %v", gitInfoExcludeFile, err)
	}
	excludeString := "/.jiri/"
	if !strings.Contains(string(data), excludeString) {
		t.Fatalf("Did not find \"%v\" in exclude file", excludeString)
	}
}
Exemple #24
0
// CreateRemoteProject creates a new remote project.
func (root FakeJiriRoot) CreateRemoteProject(ctx *tool.Context, name string) error {
	projectDir := filepath.Join(root.remote, name)
	if err := ctx.Run().MkdirAll(projectDir, os.FileMode(0700)); err != nil {
		return err
	}
	if err := ctx.Git().Init(projectDir); err != nil {
		return err
	}
	if err := ctx.Git(tool.RootDirOpt(projectDir)).CommitWithMessage("initial commit"); err != nil {
		return err
	}
	root.Projects[name] = projectDir
	return nil
}
Exemple #25
0
// writeCurrentManifest writes the given manifest to a file that
// stores the result of the most recent "jiri update" invocation.
func writeCurrentManifest(ctx *tool.Context, manifest *Manifest) error {
	currentManifestPath, err := ToAbs(currentManifestFileName)
	if err != nil {
		return err
	}
	bytes, err := xml.MarshalIndent(manifest, "", "  ")
	if err != nil {
		return fmt.Errorf("MarshalIndent(%v) failed: %v", manifest, err)
	}
	if err := ctx.Run().WriteFile(currentManifestPath, bytes, os.FileMode(0644)); err != nil {
		return err
	}
	return nil
}
Exemple #26
0
// CreateSnapshot creates a manifest that encodes the current state of
// master branches of all projects and writes this snapshot out to the
// given file.
func CreateSnapshot(ctx *tool.Context, path string) error {
	ctx.TimerPush("create snapshot")
	defer ctx.TimerPop()

	manifest := Manifest{}

	// Add all local projects to manifest.
	localProjects, err := LocalProjects(ctx, FullScan)
	if err != nil {
		return err
	}
	for _, project := range localProjects {
		relPath, err := ToRel(project.Path)
		if err != nil {
			return err
		}
		project.Path = relPath
		manifest.Projects = append(manifest.Projects, project)
	}

	// Add all hosts, tools, and hooks from the current manifest to the
	// snapshot manifest.
	hosts, _, tools, hooks, err := readManifest(ctx, true)
	if err != nil {
		return err
	}
	for _, tool := range tools {
		manifest.Tools = append(manifest.Tools, tool)
	}
	for _, host := range hosts {
		manifest.Hosts = append(manifest.Hosts, host)
	}
	for _, hook := range hooks {
		manifest.Hooks = append(manifest.Hooks, hook)
	}

	perm := os.FileMode(0755)
	if err := ctx.Run().MkdirAll(filepath.Dir(path), perm); err != nil {
		return err
	}
	data, err := xml.MarshalIndent(manifest, "", "  ")
	if err != nil {
		return fmt.Errorf("MarshalIndent(%v) failed: %v", manifest, err)
	}
	perm = os.FileMode(0644)
	if err := ctx.Run().WriteFile(path, data, perm); err != nil {
		return err
	}
	return nil
}
Exemple #27
0
// Cleanup cleans up the given Vanadium root fake.
func (root FakeJiriRoot) Cleanup(ctx *tool.Context) error {
	var errs []error
	collect.Errors(func() error {
		if root.Dir == "" {
			return nil
		}
		return ctx.Run().RemoveAll(root.Dir)
	}, &errs)
	collect.Errors(func() error { return ctx.Run().RemoveAll(root.remote) }, &errs)
	if len(errs) != 0 {
		return fmt.Errorf("Cleanup() failed: %v", errs)
	}
	return nil
}
Exemple #28
0
func setupNewProject(t *testing.T, ctx *tool.Context, dir, name string, ignore bool) string {
	projectDir, perm := filepath.Join(dir, name), os.FileMode(0755)
	if err := ctx.Run().MkdirAll(projectDir, perm); err != nil {
		t.Fatalf("%v", err)
	}
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("%v", err)
	}
	defer ctx.Run().Chdir(cwd)
	if err := ctx.Run().Chdir(projectDir); err != nil {
		t.Fatalf("%v", err)
	}
	if err := ctx.Git().Init(projectDir); err != nil {
		t.Fatalf("%v", err)
	}
	if ignore {
		ignoreFile := filepath.Join(projectDir, ".gitignore")
		if err := ctx.Run().WriteFile(ignoreFile, []byte(metadataDirName), os.FileMode(0644)); err != nil {
			t.Fatalf("%v", err)
		}
		if err := ctx.Git().Add(ignoreFile); err != nil {
			t.Fatalf("%v", err)
		}
	}
	if err := ctx.Git().Commit(); err != nil {
		t.Fatalf("%v", err)
	}
	return projectDir
}
Exemple #29
0
// Identify the current revision for a given project.
func currentRevision(t *testing.T, ctx *tool.Context, project string) string {
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("%v", err)
	}
	defer ctx.Run().Chdir(cwd)
	if err := ctx.Run().Chdir(project); err != nil {
		t.Fatalf("%v", err)
	}
	revision, err := ctx.Git().CurrentRevision()
	if err != nil {
		t.Fatalf("%v", err)
	}
	return revision
}
Exemple #30
0
func newCL(ctx *tool.Context, args []string) error {
	topLevel, err := ctx.Git().TopLevel()
	if err != nil {
		return err
	}
	originalBranch, err := ctx.Git().CurrentBranchName()
	if err != nil {
		return err
	}

	// Create a new branch using the current branch.
	newBranch := args[0]
	if err := ctx.Git().CreateAndCheckoutBranch(newBranch); err != nil {
		return err
	}

	// Register a cleanup handler in case of subsequent errors.
	cleanup := true
	defer func() {
		if cleanup {
			ctx.Git().CheckoutBranch(originalBranch, gitutil.ForceOpt(true))
			ctx.Git().DeleteBranch(newBranch, gitutil.ForceOpt(true))
		}
	}()

	// Record the dependent CLs for the new branch. The dependent CLs
	// are recorded in a <dependencyPathFileName> file as a
	// newline-separated list of branch names.
	branches, err := getDependentCLs(ctx, originalBranch)
	if err != nil {
		return err
	}
	branches = append(branches, originalBranch)
	newMetadataDir := filepath.Join(topLevel, project.MetadataDirName(), newBranch)
	if err := ctx.Run().MkdirAll(newMetadataDir, os.FileMode(0755)); err != nil {
		return err
	}
	file, err := getDependencyPathFileName(ctx, newBranch)
	if err != nil {
		return err
	}
	if err := ctx.Run().WriteFile(file, []byte(strings.Join(branches, "\n")), os.FileMode(0644)); err != nil {
		return err
	}

	cleanup = false
	return nil
}