// 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 }
// reportNonMaster checks if the given project is on master branch and // if not, reports this fact along with information on how to update it. func reportNonMaster(ctx *tool.Context, project Project) (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(project.Path); err != nil { return err } switch project.Protocol { case "git": current, err := ctx.Git().CurrentBranchName() if err != nil { return err } if current != "master" { line1 := fmt.Sprintf(`NOTE: "jiri update" only updates the "master" branch and the current branch is %q`, current) line2 := fmt.Sprintf(`to update the %q branch once the master branch is updated, run "git merge master"`, current) opts := runutil.Opts{Verbose: true} ctx.Run().OutputWithOpts(opts, []string{line1, line2}) } return nil default: return UnsupportedProtocolErr(project.Protocol) } }
func getDependencyPathFileName(ctx *tool.Context, branch string) (string, error) { topLevel, err := ctx.Git().TopLevel() if err != nil { return "", err } return filepath.Join(topLevel, project.MetadataDirName(), branch, dependencyPathFileName), nil }
// 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 }
// DisableRemoteManifestPush disables pushes to the remote manifest // repository. func (root FakeJiriRoot) DisableRemoteManifestPush(ctx *tool.Context) error { dir := tool.RootDirOpt(filepath.Join(root.remote, manifestProject)) if err := ctx.Git(dir).CheckoutBranch("master"); err != nil { return err } return nil }
// assertFilesNotCommitted asserts that the files exist and are *not* // committed in the current branch. func assertFilesNotCommitted(t *testing.T, ctx *tool.Context, files []string) { assertFilesExist(t, ctx, files) for _, file := range files { if ctx.Git().IsFileCommitted(file) { t.Fatalf("expected file %v not to be committed but it is", file) } } }
// assertStashSize asserts that the stash size matches the expected // size. func assertStashSize(t *testing.T, ctx *tool.Context, want int) { got, err := ctx.Git().StashSize() if err != nil { t.Fatalf("%v", err) } if got != want { t.Fatalf("unxpected stash size: got %v, want %v", got, want) } }
// assertFilesPushedToRef asserts that the given files have been // pushed to the given remote repository reference. func assertFilesPushedToRef(t *testing.T, ctx *tool.Context, repoPath, gerritPath, pushedRef string, files []string) { chdir(t, ctx, gerritPath) assertCommitCount(t, ctx, pushedRef, "master", 1) if err := ctx.Git().CheckoutBranch(pushedRef); err != nil { t.Fatalf("%v", err) } assertFilesCommitted(t, ctx, files) chdir(t, ctx, repoPath) }
// assertCommitCount asserts that the commit count between two // branches matches the expectedCount. func assertCommitCount(t *testing.T, ctx *tool.Context, branch, baseBranch string, expectedCount int) { got, err := ctx.Git().CountCommits(branch, baseBranch) if err != nil { t.Fatalf("%v", err) } if want := 1; got != want { t.Fatalf("unexpected number of commits: got %v, want %v", got, want) } }
// 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) } }
// createRepoFromOrigin creates a Git repo tracking origin/master. func createRepoFromOrigin(t *testing.T, ctx *tool.Context, workingDir string, subpath string, originPath string) string { repoPath := createRepo(t, ctx, workingDir, subpath) chdir(t, ctx, repoPath) if err := ctx.Git().AddRemote("origin", originPath); err != nil { t.Fatalf("%v", err) } if err := ctx.Git().Pull("origin", "master"); err != nil { t.Fatalf("%v", err) } return repoPath }
// submit mocks a Gerrit review submit by pushing the Gerrit remote to origin. // Actually origin pulls from Gerrit since origin isn't actually a bare git repo. // Some of our tests actually rely on accessing .git in origin, so it must be non-bare. func submit(t *testing.T, ctx *tool.Context, originPath string, gerritPath string, review *review) { cwd, err := os.Getwd() if err != nil { t.Fatalf("Getwd() failed: %v", err) } chdir(t, ctx, originPath) expectedRef := gerrit.Reference(review.CLOpts) if err := ctx.Git().Pull(gerritPath, expectedRef); err != nil { t.Fatalf("Pull gerrit to origin failed: %v", err) } chdir(t, ctx, cwd) }
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) } }
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) } }
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) } }
// 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 }
// 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 }
func (op deleteOperation) Run(ctx *tool.Context, _ *Manifest) error { if op.gc { // Never delete the <JiriProject>. if op.project.Name == JiriProject { lines := []string{ fmt.Sprintf("NOTE: project %v was not found in the project manifest", op.project.Name), "however this project is required for correct operation of the jiri", "development tools and will thus not be deleted", } opts := runutil.Opts{Verbose: true} ctx.Run().OutputWithOpts(opts, lines) return nil } // Never delete projects with non-master branches, uncommitted // work, or untracked content. git := ctx.Git(tool.RootDirOpt(op.project.Path)) branches, _, err := git.GetBranches() if err != nil { return err } uncommitted, err := git.HasUncommittedChanges() if err != nil { return err } untracked, err := git.HasUntrackedFiles() if err != nil { return err } if len(branches) != 1 || uncommitted || untracked { lines := []string{ fmt.Sprintf("NOTE: project %v was not found in the project manifest", op.project.Name), "however this project either contains non-master branches, uncommitted", "work, or untracked files and will thus not be deleted", } opts := runutil.Opts{Verbose: true} ctx.Run().OutputWithOpts(opts, lines) return nil } return ctx.Run().RemoveAll(op.source) } lines := []string{ fmt.Sprintf("NOTE: project %v was not found in the project manifest", op.project.Name), "it was not automatically removed to avoid deleting uncommitted work", fmt.Sprintf(`if you no longer need it, invoke "rm -rf %v"`, op.source), `or invoke "jiri update -gc" to remove all such local projects`, } opts := runutil.Opts{Verbose: true} ctx.Run().OutputWithOpts(opts, lines) return nil }
// createTestRepos sets up three local repositories: origin, gerrit, // and the main test repository which pulls from origin and can push // to gerrit. func createTestRepos(t *testing.T, ctx *tool.Context, workingDir string) (string, string, string) { // Create origin. originPath := createRepo(t, ctx, workingDir, "origin") chdir(t, ctx, originPath) if err := ctx.Git().CommitWithMessage("initial commit"); err != nil { t.Fatalf("%v", err) } // Create test repo. repoPath := createRepoFromOrigin(t, ctx, workingDir, "test", originPath) // Add Gerrit remote. gerritPath := createRepoFromOrigin(t, ctx, workingDir, "gerrit", originPath) // Switch back to test repo. chdir(t, ctx, repoPath) return repoPath, originPath, gerritPath }
// 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 }
// createRepo creates a new repository in the given working directory. func createRepo(t *testing.T, ctx *tool.Context, workingDir, prefix string) string { repoPath, err := ctx.Run().TempDir(workingDir, "repo-"+prefix) if err != nil { t.Fatalf("TempDir() failed: %v", err) } if err := os.Chmod(repoPath, 0777); err != nil { t.Fatalf("Chmod(%v) failed: %v", repoPath, err) } if err := ctx.Git().Init(repoPath); err != nil { t.Fatalf("%v", err) } if err := ctx.Run().MkdirAll(filepath.Join(repoPath, project.MetadataDirName()), os.FileMode(0755)); err != nil { t.Fatalf("%v", err) } return repoPath }
func (root FakeJiriRoot) writeManifest(ctx *tool.Context, manifest *Manifest, dir, path string) error { bytes, err := xml.Marshal(manifest) if err != nil { return fmt.Errorf("Marshal(%v) failed: %v", manifest, err) } if err := ctx.Run().WriteFile(path, bytes, os.FileMode(0600)); err != nil { return err } if err := ctx.Git(tool.RootDirOpt(dir)).Add(path); err != nil { return err } if err := ctx.Git(tool.RootDirOpt(dir)).Commit(); err != nil { return err } return nil }
// ApplyToLocalMaster applies an operation expressed as the given function to // the local master branch of the given projects. func ApplyToLocalMaster(ctx *tool.Context, projects Projects, fn func() error) (e error) { cwd, err := os.Getwd() if err != nil { return err } defer collect.Error(func() error { return ctx.Run().Chdir(cwd) }, &e) // Loop through all projects, checking out master and stashing any unstaged // changes. for _, project := range projects { p := project if err := ctx.Run().Chdir(p.Path); err != nil { return err } switch p.Protocol { case "git": branch, err := ctx.Git().CurrentBranchName() if err != nil { return err } stashed, err := ctx.Git().Stash() if err != nil { return err } if err := ctx.Git().CheckoutBranch("master"); err != nil { return err } // After running the function, return to this project's directory, // checkout the original branch, and stash pop if necessary. defer collect.Error(func() error { if err := ctx.Run().Chdir(p.Path); err != nil { return err } if err := ctx.Git().CheckoutBranch(branch); err != nil { return err } if stashed { return ctx.Git().StashPop() } return nil }, &e) default: return UnsupportedProtocolErr(p.Protocol) } } return fn() }
func writeReadme(t *testing.T, ctx *tool.Context, projectDir, message string) { path, perm := filepath.Join(projectDir, "README"), os.FileMode(0644) if err := ctx.Run().WriteFile(path, []byte(message), 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().CommitFile(path, "creating README"); err != nil { t.Fatalf("%v", err) } }
func setProjectState(ctx *tool.Context, state *ProjectState, checkDirty bool, ch chan<- error) { var err error switch state.Project.Protocol { case "git": scm := ctx.Git(tool.RootDirOpt(state.Project.Path)) var branches []string branches, state.CurrentBranch, err = scm.GetBranches() if err != nil { ch <- err return } for _, branch := range branches { file := filepath.Join(state.Project.Path, MetadataDirName(), branch, ".gerrit_commit_message") hasFile := true if _, err := ctx.Run().Stat(file); err != nil { if !os.IsNotExist(err) { ch <- err return } hasFile = false } state.Branches = append(state.Branches, BranchState{ Name: branch, HasGerritMessage: hasFile, }) } if checkDirty { state.HasUncommitted, err = scm.HasUncommittedChanges() if err != nil { ch <- err return } state.HasUntracked, err = scm.HasUntrackedFiles() if err != nil { ch <- err return } } default: ch <- UnsupportedProtocolErr(state.Project.Protocol) return } ch <- nil }
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 }
// checkSnapshotDir makes sure that he local snapshot directory exists // and is initialized properly. func checkSnapshotDir(ctx *tool.Context) (e error) { snapshotDir, err := getSnapshotDir() if err != nil { return err } if _, err := ctx.Run().Stat(snapshotDir); err != nil { if !os.IsNotExist(err) { return err } if remoteFlag { if err := ctx.Run().MkdirAll(snapshotDir, 0755); err != nil { return err } return nil } createFn := func() (err error) { if err := ctx.Run().MkdirAll(snapshotDir, 0755); err != nil { return err } if err := ctx.Git().Init(snapshotDir); err != nil { return err } 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 } if err := ctx.Git().Commit(); err != nil { return err } return nil } if err := createFn(); err != nil { ctx.Run().RemoveAll(snapshotDir) return err } } return nil }
// CurrentProjectName gets the name of the current project from the // current directory by reading the jiri project metadata located in a // directory at the root of the current repository. func CurrentProjectName(ctx *tool.Context) (string, error) { topLevel, err := ctx.Git().TopLevel() if err != nil { return "", nil } metadataDir := filepath.Join(topLevel, metadataDirName) if _, err := ctx.Run().Stat(metadataDir); err == nil { metadataFile := filepath.Join(metadataDir, metadataFileName) bytes, err := ctx.Run().ReadFile(metadataFile) if err != nil { return "", err } var project Project if err := xml.Unmarshal(bytes, &project); err != nil { return "", fmt.Errorf("Unmarshal() failed: %v", err) } return project.Name, nil } return "", nil }
func commitManifest(t *testing.T, ctx *tool.Context, manifest *Manifest, manifestDir string) { data, err := xml.Marshal(*manifest) if err != nil { t.Fatalf("%v", err) } manifestFile, perm := filepath.Join(manifestDir, "v2", "default"), os.FileMode(0644) if err := ioutil.WriteFile(manifestFile, data, perm); err != nil { t.Fatalf("WriteFile(%v, %v) failed: %v", manifestFile, err, perm) } cwd, err := os.Getwd() if err != nil { t.Fatalf("%v", err) } defer ctx.Run().Chdir(cwd) if err := ctx.Run().Chdir(manifestDir); err != nil { t.Fatalf("%v", err) } if err := ctx.Git().CommitFile(manifestFile, "creating manifest"); err != nil { t.Fatalf("%v", err) } }
// GitCloneRepo clones a repo at a specific revision in outDir. func GitCloneRepo(ctx *tool.Context, remote, revision, outDir string, outDirPerm os.FileMode) (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().MkdirAll(outDir, outDirPerm); err != nil { return err } if err := ctx.Git().Clone(remote, outDir); err != nil { return err } if err := ctx.Run().Chdir(outDir); err != nil { return err } if err := ctx.Git().Reset(revision); err != nil { return err } return nil }