// 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 }
// 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 }
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) } }
// 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) }
// 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 }
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) }
// 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 }
// 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 }
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 }
// 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 }
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 }
// 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) } } }
// 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) } }
// 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) } }
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 }
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 }
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 }
// 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 }
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 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) } }
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) } }
// 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) } }
// 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 }
// 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 }
// 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 }
// 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 }
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 }
// 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 }
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 }