TO-231: gerrit topic integration
Checkout entire Gerrit topic at once
Change-Id: I9cff3b60ba9c0423709afe827c967ced76fb32cf
diff --git a/cmd/jiri/patch.go b/cmd/jiri/patch.go
index 8d61930..013b560 100644
--- a/cmd/jiri/patch.go
+++ b/cmd/jiri/patch.go
@@ -7,26 +7,34 @@
import (
"fmt"
"net/url"
+ "os"
"strconv"
"strings"
"fuchsia.googlesource.com/jiri"
"fuchsia.googlesource.com/jiri/cmdline"
"fuchsia.googlesource.com/jiri/gerrit"
+ "fuchsia.googlesource.com/jiri/git"
"fuchsia.googlesource.com/jiri/gitutil"
"fuchsia.googlesource.com/jiri/project"
)
var (
- rebaseFlag bool
+ patchRebaseFlag bool
+ patchTopicFlag bool
+ patchBranchFlag string
+ patchDeleteFlag bool
+ patchHostFlag string
+ patchForceFlag bool
)
func init() {
- cmdPatch.Flags.StringVar(&branchFlag, "branch", "", "Name of the branch the patch will be applied to")
- cmdPatch.Flags.BoolVar(&deleteFlag, "delete", false, "Delete the existing branch if already exists")
- cmdPatch.Flags.BoolVar(&forceFlag, "force", false, "Use force when deleting the existing branch")
- cmdPatch.Flags.BoolVar(&rebaseFlag, "rebase", false, "Rebase the change after downloading")
- cmdPatch.Flags.StringVar(&hostFlag, "host", "", `Gerrit host to use. Defaults to gerrit host specified in manifest.`)
+ cmdPatch.Flags.StringVar(&patchBranchFlag, "branch", "", "Name of the branch the patch will be applied to")
+ cmdPatch.Flags.BoolVar(&patchDeleteFlag, "delete", false, "Delete the existing branch if already exists")
+ cmdPatch.Flags.BoolVar(&patchForceFlag, "force", false, "Use force when deleting the existing branch")
+ cmdPatch.Flags.BoolVar(&patchRebaseFlag, "rebase", false, "Rebase the change after downloading")
+ cmdPatch.Flags.StringVar(&patchHostFlag, "host", "", `Gerrit host to use. Defaults to gerrit host specified in manifest.`)
+ cmdPatch.Flags.BoolVar(&patchTopicFlag, "topic", false, `Patch whole topic.`)
}
// cmdPatch represents the "jiri patch" command.
@@ -44,62 +52,82 @@
-branch flag. The command will fail if the branch already exists. The -delete
flag will delete the branch if already exists. Use the -force flag to force
deleting the branch even if it contains unmerged changes).
+
+if -topic flag is true jiri will fetch whole topic and will try to apply to
+indivisual projects. Patch will assume topic is of form {USER}-{BRANCH} and
+will try to create branch name out of it. If this fails default branch name
+would be same as topic. Currently patch does not support the scenario when
+change "B" is created on top of "A" and both have same topic.
`,
- ArgsName: "<change>",
- ArgsLong: "<change> is a change ID or a full reference.",
+ ArgsName: "<change or topic>",
+ ArgsLong: "<change or topic> is a change ID, full reference or topic when -topic is true.",
}
// patchProject checks out the given change.
-func patchProject(jirix *jiri.X, project project.Project, ref string) error {
- var branch string
- if branchFlag != "" {
- branch = branchFlag
- } else {
+func patchProject(jirix *jiri.X, project project.Project, ref, branch, remote string) (bool, error) {
+ if branch == "" {
cl, ps, err := gerrit.ParseRefString(ref)
if err != nil {
- return err
+ return false, err
}
branch = fmt.Sprintf("change/%v/%v", cl, ps)
}
-
- git := gitutil.New(jirix, gitutil.RootDirOpt(project.Path))
- if git.BranchExists(branch) {
- if deleteFlag {
- if err := git.CheckoutBranch("origin/master"); err != nil {
- return err
+ jirix.Logger.Infof("Patching project %s(%s) on branch %q\n", project.Name, project.Path, branch)
+ scm := gitutil.New(jirix, gitutil.RootDirOpt(project.Path))
+ g := git.NewGit(project.Path)
+ if scm.BranchExists(branch) {
+ if patchDeleteFlag {
+ if err := scm.CheckoutBranch("origin/master"); err != nil {
+ return false, err
}
- if err := git.DeleteBranch(branch, gitutil.ForceOpt(forceFlag)); err != nil {
- return err
+ if err := scm.DeleteBranch(branch, gitutil.ForceOpt(patchForceFlag)); err != nil {
+ jirix.Logger.Errorf("Cannot delete branch %q: %s", branch, err)
+ jirix.IncrementFailures()
+ return false, nil
}
} else {
- return fmt.Errorf("branch %v already exists in project %q", branch, project.Name)
+ jirix.Logger.Errorf("Branch %q already exists in project %q", branch, project.Name)
+ jirix.IncrementFailures()
+ return false, nil
}
}
- if err := git.FetchRefspec("origin", ref); err != nil {
- return err
- }
- if err := git.CreateBranchWithUpstream(branch, "FETCH_HEAD"); err != nil {
- return err
- }
- if err := git.CheckoutBranch(branch); err != nil {
- return err
+ if err := scm.FetchRefspec("origin", ref); err != nil {
+ return false, err
}
- return nil
+ if err := g.CreateBranchFromRef(branch, "FETCH_HEAD"); err != nil {
+ return false, err
+ }
+
+ if err := g.SetUpstream(branch, "origin/"+remote); err != nil {
+ return false, err
+ }
+
+ if err := scm.CheckoutBranch(branch); err != nil {
+ return false, err
+ }
+ jirix.Logger.Infof("Project patched\n")
+ return true, nil
}
// rebaseProject rebases the current branch on top of a given branch.
-func rebaseProject(jirix *jiri.X, project project.Project, change *gerrit.Change) error {
- git := gitutil.New(jirix, gitutil.UserNameOpt(change.Owner.Name), gitutil.UserEmailOpt(change.Owner.Email), gitutil.RootDirOpt(project.Path))
- if err := git.FetchRefspec("origin", change.Branch); err != nil {
- return err
+func rebaseProject(jirix *jiri.X, project project.Project, change gerrit.Change) error {
+ jirix.Logger.Infof("Rebasing project %s(%s)\n", project.Name, project.Path)
+ scm := gitutil.New(jirix, gitutil.UserNameOpt(change.Owner.Name), gitutil.UserEmailOpt(change.Owner.Email), gitutil.RootDirOpt(project.Path))
+ if err := scm.FetchRefspec("origin", change.Branch); err != nil {
+ jirix.Logger.Errorf("Not able to fetch branch %q: %s", change.Branch, err)
+ jirix.IncrementFailures()
+ return nil
}
- if err := git.Rebase("origin/" + change.Branch); err != nil {
- if err := git.RebaseAbort(); err != nil {
+ if err := scm.Rebase("origin/" + change.Branch); err != nil {
+ if err := scm.RebaseAbort(); err != nil {
return err
}
- return fmt.Errorf("Cannot rebase the change: %v", err)
+ jirix.Logger.Errorf("Cannot rebase the change: %s", err)
+ jirix.IncrementFailures()
+ return nil
}
+ jirix.Logger.Infof("Project rebased\n")
return nil
}
@@ -109,16 +137,22 @@
}
arg := args[0]
- cl, ps, err := gerrit.ParseRefString(arg)
- if err != nil {
- cl, err = strconv.Atoi(arg)
+ var cl int
+ var ps int
+ var err error
+ if !patchTopicFlag {
+ cl, ps, err = gerrit.ParseRefString(arg)
if err != nil {
- return fmt.Errorf("invalid argument: %v", arg)
+ cl, err = strconv.Atoi(arg)
+ if err != nil {
+ return fmt.Errorf("invalid argument: %v", arg)
+ }
}
}
- if p, err := currentProject(jirix); err == nil {
- host := hostFlag
+ p, perr := currentProject(jirix)
+ if !patchTopicFlag && perr == nil {
+ host := patchHostFlag
if host == "" {
if p.GerritHost == "" {
return fmt.Errorf("no Gerrit host; use the '--host' flag, or add a 'gerrithost' attribute for project %q", p.Name)
@@ -135,23 +169,32 @@
if err != nil {
return err
}
+ branch := patchBranchFlag
+ ok := false
if ps != -1 {
- if err := patchProject(jirix, p, arg); err != nil {
+ if ok, err = patchProject(jirix, p, arg, branch, change.Branch); err != nil {
return err
}
} else {
- if err := patchProject(jirix, p, change.Reference()); err != nil {
+ if ok, err = patchProject(jirix, p, change.Reference(), branch, change.Branch); err != nil {
return err
}
}
- if rebaseFlag {
- if err := rebaseProject(jirix, p, change); err != nil {
+ if ok && patchRebaseFlag {
+ if err := rebaseProject(jirix, p, *change); err != nil {
return err
}
}
} else {
- host := hostFlag
- if host == "" {
+ host := patchHostFlag
+ if host == "" && patchTopicFlag {
+ if perr == nil {
+ host = p.GerritHost
+ }
+ if host == "" {
+ return fmt.Errorf("no Gerrit host; use the '--host' flag or run from inside a project with gerrit host")
+ }
+ } else if host == "" {
return fmt.Errorf("no Gerrit host; use the '--host' flag")
}
hostUrl, err := url.Parse(host)
@@ -160,36 +203,72 @@
}
g := jirix.Gerrit(hostUrl)
- change, err := g.GetChange(cl)
- if err != nil {
- return err
- }
- var ref string
- if ps != -1 {
- ref = arg
+ var changes gerrit.CLList
+ branch := patchBranchFlag
+ if patchTopicFlag {
+ changes, err = g.ListOpenChangesByTopic(arg)
+ if err != nil {
+ return err
+ }
+ if len(changes) == 0 {
+ return fmt.Errorf("No changes found with topic %q", arg)
+ }
+ ps = -1
+ if branch == "" {
+ userPrefix := os.Getenv("USER") + "-"
+ if strings.HasPrefix(arg, userPrefix) {
+ branch = strings.Replace(arg, userPrefix, "", 1)
+ } else {
+ branch = arg
+ }
+ }
} else {
- ref = change.Reference()
+ change, err := g.GetChange(cl)
+ if err != nil {
+ return err
+ }
+ changes = append(changes, *change)
}
-
- projects, _, err := project.LoadManifest(jirix)
+ projects, err := project.LocalProjects(jirix, project.FastScan)
if err != nil {
return err
}
-
- for _, p := range projects {
- if strings.HasSuffix(p.Remote, change.Project) {
- if err := patchProject(jirix, p, ref); err != nil {
+ for _, change := range changes {
+ var ref string
+ if ps != -1 {
+ ref = arg
+ } else {
+ ref = change.Reference()
+ }
+ projFound := false
+ for _, p := range projects {
+ if strings.HasSuffix(p.Remote, "/"+change.Project) {
+ projFound = true
+ if ok, err := patchProject(jirix, p, ref, branch, change.Branch); err != nil {
+ return err
+ } else if ok {
+ if patchRebaseFlag {
+ if err := rebaseProject(jirix, p, change); err != nil {
+ return err
+ }
+ }
+ }
+ fmt.Println()
+ }
+ }
+ if !projFound {
+ cl, _, err := gerrit.ParseRefString(ref)
+ if err != nil {
return err
}
- if rebaseFlag {
- if err := rebaseProject(jirix, p, change); err != nil {
- return err
- }
- }
- break
+ jirix.Logger.Errorf("Cannot find project to patch CL %s\n", g.GetChangeURL(cl))
+ jirix.IncrementFailures()
+ fmt.Println()
}
}
}
-
+ if jirix.Failures() != 0 {
+ return fmt.Errorf("Patch failed")
+ }
return nil
}
diff --git a/gerrit/gerrit.go b/gerrit/gerrit.go
index 0b6a4b2..a909ae6 100644
--- a/gerrit/gerrit.go
+++ b/gerrit/gerrit.go
@@ -367,6 +367,10 @@
return parseQueryResults(res.Body)
}
+func (g *Gerrit) ListOpenChangesByTopic(topic string) (CLList, error) {
+ return g.Query("topic:\"" + topic + "\" status:open")
+}
+
// GetChange returns a Change object for the given changeId number.
func (g *Gerrit) GetChange(changeNumber int) (*Change, error) {
clList, err := g.Query(fmt.Sprintf("%d", changeNumber))
@@ -384,6 +388,10 @@
return &clList[0], nil
}
+func (g *Gerrit) GetChangeURL(changeNumber int) string {
+ return fmt.Sprintf("%s/c/%d", g.host, changeNumber)
+}
+
// Submit submits the given changelist through Gerrit.
func (g *Gerrit) Submit(changeID string) (e error) {
cred, err := hostCredentials(g.s, g.host)
diff --git a/git/git.go b/git/git.go
index f1c2b08..c7064bb 100644
--- a/git/git.go
+++ b/git/git.go
@@ -120,6 +120,44 @@
}
}
+func (g *Git) SetUpstream(branch, upstream string) error {
+ repo, err := git2go.OpenRepository(g.rootDir)
+ if err != nil {
+ return err
+ }
+ defer repo.Free()
+ b, err := repo.LookupBranch(branch, git2go.BranchLocal)
+ if err != nil {
+ return err
+ }
+ return b.SetUpstream(upstream)
+}
+
+func (g *Git) CreateBranchFromRef(branch, ref string) error {
+ repo, err := git2go.OpenRepository(g.rootDir)
+ if err != nil {
+ return err
+ }
+ defer repo.Free()
+ obj, err := repo.RevparseSingle(ref)
+ if err != nil {
+ return err
+ }
+ defer obj.Free()
+ c, err := obj.Peel(git2go.ObjectCommit)
+ if err != nil {
+ return err
+ }
+ defer c.Free()
+ commit, err := c.AsCommit()
+ if err != nil {
+ return err
+ }
+ defer commit.Free()
+ _, err = repo.CreateBranch(branch, commit, false)
+ return err
+}
+
func (g *Git) HasUntrackedFiles() (bool, error) {
repo, err := git2go.OpenRepository(g.rootDir)
if err != nil {