// TestCLSync checks the operation of the "jiri cl sync" command. func TestCLSync(t *testing.T) { fake, _, _, _, cleanup := setupTest(t, true) defer cleanup() // Create some dependent CLs. if err := newCL(fake.X, []string{"feature1"}); err != nil { t.Fatalf("%v", err) } if err := newCL(fake.X, []string{"feature2"}); err != nil { t.Fatalf("%v", err) } // Add the "test" file to the master. if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil { t.Fatalf("%v", err) } commitFiles(t, fake.X, []string{"test"}) // Sync the dependent CLs. if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("feature2"); err != nil { t.Fatalf("%v", err) } if err := syncCL(fake.X); err != nil { t.Fatalf("%v", err) } // Check that the "test" file exists in the dependent CLs. for _, branch := range []string{"feature1", "feature2"} { if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil { t.Fatalf("%v", err) } assertFilesExist(t, fake.X, []string{"test"}) } }
// TestCreateReviewBranch checks that the temporary review branch is // created correctly. func TestCreateReviewBranch(t *testing.T) { fake, _, _, _, cleanup := setupTest(t, true) defer cleanup() branch := "my-branch" if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { t.Fatalf("%v", err) } files := []string{"file1", "file2", "file3"} commitFiles(t, fake.X, files) review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{}) if err != nil { t.Fatalf("%v", err) } if expected, got := branch+"-REVIEW", review.reviewBranch; expected != got { t.Fatalf("Unexpected review branch name: expected %v, got %v", expected, got) } commitMessage := "squashed commit" if err := review.createReviewBranch(commitMessage); err != nil { t.Fatalf("%v", err) } // Verify that the branch exists. if !gitutil.New(fake.X.NewSeq()).BranchExists(review.reviewBranch) { t.Fatalf("review branch not found") } if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(review.reviewBranch); err != nil { t.Fatalf("%v", err) } assertCommitCount(t, fake.X, review.reviewBranch, "master", 1) assertFilesCommitted(t, fake.X, files) }
// createRepoFromOrigin creates a Git repo tracking origin/master. func createRepoFromOrigin(t *testing.T, jirix *jiri.X, subpath string, originPath string) string { repoPath := createRepo(t, jirix, subpath) chdir(t, jirix, repoPath) if err := gitutil.New(jirix.NewSeq()).AddRemote("origin", originPath); err != nil { t.Fatalf("%v", err) } if err := gitutil.New(jirix.NewSeq()).Pull("origin", "master"); err != nil { t.Fatalf("%v", err) } return repoPath }
// EnableRemoteManifestPush enables pushes to the remote manifest // repository. func (fake FakeJiriRoot) EnableRemoteManifestPush() error { dir := gitutil.RootDirOpt(filepath.Join(fake.remote, manifestProjectPath)) if !gitutil.New(fake.X.NewSeq(), dir).BranchExists("non-master") { if err := gitutil.New(fake.X.NewSeq(), dir).CreateBranch("non-master"); err != nil { return err } } if err := gitutil.New(fake.X.NewSeq(), dir).CheckoutBranch("non-master"); err != nil { return err } return nil }
// CreateRemoteProject creates a new remote project. func (fake FakeJiriRoot) CreateRemoteProject(name string) error { projectDir := filepath.Join(fake.remote, name) if err := fake.X.NewSeq().MkdirAll(projectDir, os.FileMode(0700)).Done(); err != nil { return err } if err := gitutil.New(fake.X.NewSeq()).Init(projectDir); err != nil { return err } if err := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(projectDir)).CommitWithMessage("initial commit"); err != nil { return err } fake.Projects[name] = projectDir return nil }
// defaultCommitMessage creates the default commit message from the // list of commits on the branch. func (review *review) defaultCommitMessage() (string, error) { commitMessages := "" var err error if commitMessageBodyFlag != "" { msg, tmpErr := ioutil.ReadFile(commitMessageBodyFlag) commitMessages = string(msg) err = tmpErr } else { commitMessages, err = gitutil.New(review.jirix.NewSeq()).CommitMessages(review.CLOpts.Branch, review.reviewBranch) } if err != nil { return "", err } // Strip "MultiPart ..." from the commit messages. strippedMessages := multiPartRE.ReplaceAllLiteralString(commitMessages, "") // Strip "Change-Id: ..." from the commit messages. strippedMessages = changeIDRE.ReplaceAllLiteralString(strippedMessages, "") // Add comment markers (#) to every line. commentedMessages := "# " + strings.Replace(strippedMessages, "\n", "\n# ", -1) message := defaultMessageHeader + commentedMessages if multipart := review.readMultiPart(); multipart != "" { message = message + "\n" + multipart + "\n" } return message, nil }
// DisableRemoteManifestPush disables pushes to the remote manifest // repository. func (fake FakeJiriRoot) DisableRemoteManifestPush() error { dir := gitutil.RootDirOpt(filepath.Join(fake.remote, manifestProjectPath)) if err := gitutil.New(fake.X.NewSeq(), dir).CheckoutBranch("master"); err != nil { return err } return nil }
// commitAndPushChanges commits changes identified by the given manifest file // and label to the containing repository and pushes these changes to the // remote repository. func commitAndPushChanges(jirix *jiri.X, snapshotDir, snapshotFile, label string) (e error) { cwd, err := os.Getwd() if err != nil { return err } defer collect.Error(func() error { return jirix.NewSeq().Chdir(cwd).Done() }, &e) if err := jirix.NewSeq().Chdir(snapshotDir).Done(); err != nil { return err } relativeSnapshotPath := strings.TrimPrefix(snapshotFile, snapshotDir+string(os.PathSeparator)) git := gitutil.New(jirix.NewSeq()) // Pull from master so we are up-to-date. if err := git.Pull("origin", "master"); err != nil { return err } if err := git.Add(relativeSnapshotPath); err != nil { return err } if err := git.Add(label); err != nil { return err } name := strings.TrimPrefix(snapshotFile, snapshotDir) if err := git.CommitNoVerify(fmt.Sprintf("adding snapshot %q for label %q", name, label)); err != nil { return err } if err := git.Push("origin", "master", gitutil.VerifyOpt(false)); err != nil { return err } return nil }
func getCommitMessageFileName(jirix *jiri.X, branch string) (string, error) { topLevel, err := gitutil.New(jirix.NewSeq()).TopLevel() if err != nil { return "", err } return filepath.Join(topLevel, jiri.ProjectMetaDir, branch, commitMessageFileName), nil }
// 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(jirix *jiri.X) (e error) { originalBranch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName() if err != nil { return err } branches, err := getDependentCLs(jirix, originalBranch) if err != nil { return err } for i := 1; i < len(branches); i++ { file, err := getCommitMessageFileName(jirix, branches[i]) if err != nil { return err } if _, err := jirix.NewSeq().Stat(file); err != nil { if !runutil.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 }
// assertFilesNotCommitted asserts that the files exist and are *not* // committed in the current branch. func assertFilesNotCommitted(t *testing.T, jirix *jiri.X, files []string) { assertFilesExist(t, jirix, files) for _, file := range files { if gitutil.New(jirix.NewSeq()).IsFileCommitted(file) { t.Fatalf("expected file %v not to be committed but it is", file) } } }
// assertFilesPushedToRef asserts that the given files have been // pushed to the given remote repository reference. func assertFilesPushedToRef(t *testing.T, jirix *jiri.X, repoPath, gerritPath, pushedRef string, files []string) { chdir(t, jirix, gerritPath) assertCommitCount(t, jirix, pushedRef, "master", 1) if err := gitutil.New(jirix.NewSeq()).CheckoutBranch(pushedRef); err != nil { t.Fatalf("%v", err) } assertFilesCommitted(t, jirix, files) chdir(t, jirix, repoPath) }
// assertStashSize asserts that the stash size matches the expected // size. func assertStashSize(t *testing.T, jirix *jiri.X, want int) { got, err := gitutil.New(jirix.NewSeq()).StashSize() if err != nil { t.Fatalf("%v", err) } if got != want { t.Fatalf("unxpected stash size: got %v, want %v", got, want) } }
// assertCommitCount asserts that the commit count between two // branches matches the expectedCount. func assertCommitCount(t *testing.T, jirix *jiri.X, branch, baseBranch string, expectedCount int) { got, err := gitutil.New(jirix.NewSeq()).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, jirix *jiri.X, filename string, content string) { s := jirix.NewSeq() if err := s.WriteFile(filename, []byte(content), 0644).Done(); err != nil { t.Fatalf("%v", err) } commitMessage := "Commit " + filename if err := gitutil.New(jirix.NewSeq()).CommitFile(filename, commitMessage); err != nil { t.Fatalf("%v", err) } }
// ensureChangeID makes sure that the last commit contains a Change-Id, and // returns an error if it does not. func (review *review) ensureChangeID() error { latestCommitMessage, err := gitutil.New(review.jirix.NewSeq()).LatestCommitMessage() if err != nil { return err } changeID := changeIDRE.FindString(latestCommitMessage) if changeID == "" { return noChangeIDError(struct{}{}) } return nil }
// TestCleanupDirty checks that cleanup is a no-op if the branch to be // cleaned up has unmerged changes. func TestCleanupDirty(t *testing.T) { fake, _, _, _, cleanup := setupTest(t, true) defer cleanup() branch := "my-branch" if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { t.Fatalf("%v", err) } files := []string{"file1", "file2"} commitFiles(t, fake.X, files) if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil { t.Fatalf("%v", err) } if err := cleanupCL(fake.X, []string{branch}); err == nil { t.Fatalf("cleanup did not fail when it should") } if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch(branch); err != nil { t.Fatalf("%v", err) } assertFilesCommitted(t, fake.X, files) }
// TestCleanupClean checks that cleanup succeeds if the branch to be // cleaned up has been merged with the master. func TestCleanupClean(t *testing.T) { fake, repoPath, originPath, _, cleanup := setupTest(t, true) defer cleanup() branch := "my-branch" if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { t.Fatalf("%v", err) } commitFiles(t, fake.X, []string{"file1", "file2"}) if err := gitutil.New(fake.X.NewSeq()).CheckoutBranch("master"); err != nil { t.Fatalf("%v", err) } chdir(t, fake.X, originPath) commitFiles(t, fake.X, []string{"file1", "file2"}) chdir(t, fake.X, repoPath) if err := cleanupCL(fake.X, []string{branch}); err != nil { t.Fatalf("cleanup() failed: %v", err) } if gitutil.New(fake.X.NewSeq()).BranchExists(branch) { t.Fatalf("cleanup failed to remove the feature branch") } }
// 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, jirix *jiri.X, originPath string, gerritPath string, review *review) { cwd, err := os.Getwd() if err != nil { t.Fatalf("Getwd() failed: %v", err) } chdir(t, jirix, originPath) expectedRef := gerrit.Reference(review.CLOpts) if err := gitutil.New(jirix.NewSeq()).Pull(gerritPath, expectedRef); err != nil { t.Fatalf("Pull gerrit to origin failed: %v", err) } chdir(t, jirix, cwd) }
func (fake FakeJiriRoot) writeManifest(manifest *project.Manifest, dir, path string) error { git := gitutil.New(fake.X.NewSeq(), gitutil.RootDirOpt(dir)) if err := manifest.ToFile(fake.X, path); err != nil { return err } if err := git.Add(path); err != nil { return err } if err := git.Commit(); err != nil { return err } return nil }
func commitFile(t *testing.T, jirix *jiri.X, dir, file, msg string) { cwd, err := os.Getwd() if err != nil { t.Fatal(err) } defer jirix.NewSeq().Chdir(cwd) if err := jirix.NewSeq().Chdir(dir).Done(); err != nil { t.Fatal(err) } if err := gitutil.New(jirix.NewSeq()).CommitFile(file, msg); err != nil { t.Fatal(err) } }
func newCL(jirix *jiri.X, args []string) error { git := gitutil.New(jirix.NewSeq()) topLevel, err := git.TopLevel() if err != nil { return err } originalBranch, err := git.CurrentBranchName() if err != nil { return err } // Create a new branch using the current branch. newBranch := args[0] if err := git.CreateAndCheckoutBranch(newBranch); err != nil { return err } // Register a cleanup handler in case of subsequent errors. cleanup := true defer func() { if cleanup { git.CheckoutBranch(originalBranch, gitutil.ForceOpt(true)) git.DeleteBranch(newBranch, gitutil.ForceOpt(true)) } }() s := jirix.NewSeq() // 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(jirix, originalBranch) if err != nil { return err } branches = append(branches, originalBranch) newMetadataDir := filepath.Join(topLevel, jiri.ProjectMetaDir, newBranch) if err := s.MkdirAll(newMetadataDir, os.FileMode(0755)).Done(); err != nil { return err } file, err := getDependencyPathFileName(jirix, newBranch) if err != nil { return err } if err := s.WriteFile(file, []byte(strings.Join(branches, "\n")), os.FileMode(0644)).Done(); err != nil { return err } cleanup = false return nil }
// runCLMail is a wrapper that sets up and runs a review instance across // multiple projects. func runCLMail(jirix *jiri.X, _ []string) error { mp, err := initForMultiPart(jirix) if err != nil { return err } if mp.clean { if err := mp.cleanMultiPartMetadata(jirix); err != nil { return err } return nil } if mp.current { return runCLMailCurrent(jirix, []string{}) } // multipart mode if err := mp.writeMultiPartMetadata(jirix); err != nil { mp.cleanMultiPartMetadata(jirix) return err } if err := runCLMailCurrent(jirix, []string{}); err != nil { return err } git := gitutil.New(jirix.NewSeq()) branch, err := git.CurrentBranchName() if err != nil { return err } initialMessage, err := strippedGerritCommitMessage(jirix, branch) if err != nil { return err } s := jirix.NewSeq() tmp, err := s.TempFile("", branch+"-") if err != nil { return err } defer func() { tmp.Close() os.Remove(tmp.Name()) }() if _, err := io.WriteString(tmp, initialMessage); err != nil { return err } // Use Capture to make sure that all output from the subcommands is // sent to stdout/stderr. flags := clMailMultiFlags() flags = append(flags, "--commit-message-body-file="+tmp.Name()) return s.Capture(jirix.Stdout(), jirix.Stderr()).Last("jiri", mp.commandline(mp.currentKey, flags)...) }
// 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, jirix *jiri.X) (string, string, string) { // Create origin. originPath := createRepo(t, jirix, "origin") chdir(t, jirix, originPath) if err := gitutil.New(jirix.NewSeq()).CommitWithMessage("initial commit"); err != nil { t.Fatalf("%v", err) } // Create test repo. repoPath := createRepoFromOrigin(t, jirix, "test", originPath) // Add Gerrit remote. gerritPath := createRepoFromOrigin(t, jirix, "gerrit", originPath) // Switch back to test repo. chdir(t, jirix, repoPath) return repoPath, originPath, gerritPath }
// cleanup cleans up after the review. func (review *review) cleanup(stashed bool) error { git := gitutil.New(review.jirix.NewSeq()) if err := git.CheckoutBranch(review.CLOpts.Branch); err != nil { return err } if git.BranchExists(review.reviewBranch) { if err := git.DeleteBranch(review.reviewBranch, gitutil.ForceOpt(true)); err != nil { return err } } if stashed { if err := git.StashPop(); err != nil { return err } } return nil }
// createRepo creates a new repository with the given prefix. func createRepo(t *testing.T, jirix *jiri.X, prefix string) string { s := jirix.NewSeq() repoPath, err := s.TempDir(jirix.Root, "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 := gitutil.New(jirix.NewSeq()).Init(repoPath); err != nil { t.Fatalf("%v", err) } if err := s.MkdirAll(filepath.Join(repoPath, jiri.ProjectMetaDir), os.FileMode(0755)).Done(); err != nil { t.Fatalf("%v", err) } return repoPath }
func writeReadme(t *testing.T, jirix *jiri.X, projectDir, message string) { s := jirix.NewSeq() path, perm := filepath.Join(projectDir, "README"), os.FileMode(0644) if err := s.WriteFile(path, []byte(message), perm).Done(); err != nil { t.Fatalf("%v", err) } cwd, err := os.Getwd() if err != nil { t.Fatalf("%v", err) } defer jirix.NewSeq().Chdir(cwd) if err := s.Chdir(projectDir).Done(); err != nil { t.Fatalf("%v", err) } if err := gitutil.New(jirix.NewSeq()).CommitFile(path, "creating README"); err != nil { t.Fatalf("%v", err) } }
func setProjectState(jirix *jiri.X, state *ProjectState, checkDirty bool, ch chan<- error) { var err error switch state.Project.Protocol { case "git": scm := gitutil.New(jirix.NewSeq(), gitutil.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, jiri.ProjectMetaDir, branch, ".gerrit_commit_message") hasFile := true if _, err := jirix.NewSeq().Stat(file); err != nil { if !runutil.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 }
// TestUpdateUniverseWithRevision checks that UpdateUniverse will pull remote // projects at the specified revision. func TestUpdateUniverseWithRevision(t *testing.T) { localProjects, fake, cleanup := setupUniverse(t) defer cleanup() s := fake.X.NewSeq() // Set project 1's revision in the manifest to the current revision. git := gitutil.New(s, gitutil.RootDirOpt(fake.Projects[localProjects[1].Name])) rev, err := git.CurrentRevision() if err != nil { t.Fatal(err) } m, err := fake.ReadRemoteManifest() if err != nil { t.Fatal(err) } projects := []project.Project{} for _, p := range m.Projects { if p.Name == localProjects[1].Name { p.Revision = rev } projects = append(projects, p) } m.Projects = projects if err := fake.WriteRemoteManifest(m); err != nil { t.Fatal(err) } // Update README in all projects. for _, remoteProjectDir := range fake.Projects { writeReadme(t, fake.X, remoteProjectDir, "new revision") } // Check that calling UpdateUniverse() updates all projects except for // project 1. if err := fake.UpdateUniverse(false); err != nil { t.Fatal(err) } for i, p := range localProjects { if i == 1 { checkReadme(t, fake.X, p, "initial readme") } else { checkReadme(t, fake.X, p, "new revision") } } }
// TestRunInSubdirectory checks that the command will succeed when run from // within a subdirectory of a branch that does not exist on master branch, and // will return the user to the subdirectory after completion. func TestRunInSubdirectory(t *testing.T) { fake, repoPath, _, gerritPath, cleanup := setupTest(t, true) defer cleanup() s := fake.X.NewSeq() branch := "my-branch" if err := gitutil.New(fake.X.NewSeq()).CreateAndCheckoutBranch(branch); err != nil { t.Fatalf("%v", err) } subdir := "sub/directory" subdirPerms := os.FileMode(0744) if err := s.MkdirAll(subdir, subdirPerms).Done(); err != nil { t.Fatalf("MkdirAll(%v, %v) failed: %v", subdir, subdirPerms, err) } files := []string{path.Join(subdir, "file1")} commitFiles(t, fake.X, files) chdir(t, fake.X, subdir) review, err := newReview(fake.X, project.Project{}, gerrit.CLOpts{Remote: gerritPath}) if err != nil { t.Fatalf("%v", err) } setTopicFlag = false if err := review.run(); err != nil { t.Fatalf("run() failed: %v", err) } path := path.Join(repoPath, subdir) want, err := filepath.EvalSymlinks(path) if err != nil { t.Fatalf("EvalSymlinks(%v) failed: %v", path, err) } cwd, err := os.Getwd() if err != nil { t.Fatalf("%v", err) } got, err := filepath.EvalSymlinks(cwd) if err != nil { t.Fatalf("EvalSymlinks(%v) failed: %v", cwd, err) } if got != want { t.Fatalf("unexpected working directory: got %v, want %v", got, want) } expectedRef := gerrit.Reference(review.CLOpts) assertFilesPushedToRef(t, fake.X, repoPath, gerritPath, expectedRef, files) }