Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Unified Diff: go/src/infra/tools/drover/merge.go

Issue 662113003: Drover's back, baby! (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git/+/master
Patch Set: Lots of fixes Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: go/src/infra/tools/drover/merge.go
diff --git a/go/src/infra/tools/drover/merge.go b/go/src/infra/tools/drover/merge.go
new file mode 100644
index 0000000000000000000000000000000000000000..ce7d2f9e3f3fe422b45d69bd161a8e0cc60d7839
--- /dev/null
+++ b/go/src/infra/tools/drover/merge.go
@@ -0,0 +1,370 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/cheggaaa/pb"
+ "github.com/daviddengcn/go-colortext"
+
+ "infra/libs/git"
+ "infra/libs/gitiles"
+)
+
+type stringSet map[string]struct{}
+
+func (s stringSet) Has(k string) bool {
+ _, ok := s[k]
+ return ok
+}
+
+func (s stringSet) Add(k string) bool {
+ if s.Has(k) {
+ return false
+ }
+ s[k] = struct{}{}
+ return true
+}
+
+func (s stringSet) Del(k string) {
+ delete(s, k)
+}
+
+func (s stringSet) String() string {
+ buf := &bytes.Buffer{}
+ fmt.Fprint(buf, "{")
+ first := true
+ for k := range s {
+ if !first {
+ fmt.Fprint(buf, " ")
+ }
+ first = false
+ fmt.Fprint(buf, k)
+ }
+ fmt.Fprint(buf, "}")
+ return buf.String()
+}
+
+type internRequest struct {
+ commit git.ObjectID
+ pathPieces []string
+}
+
+func (i internRequest) repoName() (ret string) {
+ ret = i.commit.String()
+ if len(i.pathPieces) > 0 {
+ ret += ":" // root tree
+ ret += strings.Join(i.pathPieces, "/")
+ }
+ return
+}
+
+func commitRequest(commit git.ObjectID) internRequest {
+ return internRequest{commit: commit}
+}
+
+func rootTreeRequest(commit git.ObjectID) internRequest {
+ return internRequest{commit: commit, pathPieces: []string{""}}
+}
+
+func objRequest(commit git.ObjectID, pathPieces []string) internRequest {
+ for _, p := range pathPieces {
+ if p == "" {
+ panic(fmt.Errorf(
+ "Cannot have an empty pathPieces entry in an objRequest! %#v", pathPieces))
+ }
+ }
+ return internRequest{commit: commit, pathPieces: pathPieces}
+}
+
+type progMessage bool
+
+const (
+ progDone progMessage = iota == 0
+ progAdd
+)
+
+type lazyProgBar chan<- progMessage
+
+func newLazyProgBar(estSize int) lazyProgBar {
+ ch := make(chan progMessage, estSize)
+ var bar *pb.ProgressBar
+ go func() {
+ for amt := range ch {
+ if bar == nil {
+ if amt == progDone {
+ panic("cannot start progress with a progDone")
+ }
+ bar = pb.StartNew(1)
+ } else {
+ switch amt {
+ case progAdd:
+ bar.Total++
+ case progDone:
+ bar.Increment()
+ }
+ }
+ }
+ if bar != nil {
+ bar.Finish()
+ }
+ }()
+ return ch
+}
+
+func (l lazyProgBar) Add() { l <- progAdd }
+func (l lazyProgBar) Done() { l <- progDone }
+func (l lazyProgBar) Finish() { close(l) }
+
+func internService(r *git.Repo, g *gitiles.Gitiles, reqs []internRequest) <-chan error {
+ bar := newLazyProgBar(len(reqs))
+ grp := sync.WaitGroup{}
+ grp.Add(len(reqs))
+ allErrCh := make(chan error, len(reqs))
+
+ go func() {
+ grp.Wait()
+ bar.Finish()
+ close(allErrCh)
+ }()
+
+ for _, req := range reqs {
+ req := req
+ go func() {
+ defer grp.Done()
+ if r.HasObject(req.repoName()) {
+ return
+ }
+ bar.Add()
+ defer bar.Done()
+ rslt, err := g.GetObjectFromPath(req.commit.String(), req.pathPieces...)
+ if err == nil {
+ _, err = r.Intern(rslt)
+ }
+ allErrCh <- err
+ }()
+ }
+
+ return allErrCh
+}
+
+func acquireObjects(r *git.Repo, g *gitiles.Gitiles, commit, landCommit *git.Commit, treeDiff git.TreeDiff) {
+ if len(commit.Parents()) != 1 {
+ panic(fmt.Errorf("Got wrong number of parents for commit %s: %s",
+ commit.ID(), commit.Parents()))
+ }
+
+ fmt.Println("Acquiring objects")
+ reqs := make([]internRequest, 0, 5*3*len(treeDiff))
+
+ dedup := stringSet{}
+
+ add := func(req internRequest) {
+ if !dedup.Add(req.repoName()) {
+ return
+ }
+ reqs = append(reqs, req)
+ }
+
+ addBlobTreesFor := func(commit git.ObjectID, path string) {
+ if path == "/dev/null" {
+ return
+ }
+ pathBits := strings.Split(path, "/")
+ add(objRequest(commit, pathBits))
+ add(rootTreeRequest(commit))
+ for i := 1; i < len(pathBits); i++ {
+ add(objRequest(commit, pathBits[:i]))
+ }
+ }
+
+ add(commitRequest(commit.ID()))
+ add(commitRequest(commit.Parents()[0]))
+ add(commitRequest(landCommit.ID()))
+ for _, wholeEntry := range treeDiff {
+ addBlobTreesFor(commit.ID(), wholeEntry.New.Name)
+ addBlobTreesFor(commit.Parents()[0], wholeEntry.Old.Name)
+ addBlobTreesFor(landCommit.ID(), wholeEntry.Old.Name)
+ }
+
+ for e := range internService(r, g, reqs) {
+ failIf(e)
+ }
+ fmt.Println("Done")
+}
+
+func hollow(r *git.Repo, commit git.ObjectID, parents []git.ObjectID, t *git.Tree) git.ObjectID {
+ t.Intern(r)
+ cur := r.GetObjectID(commit).(*git.Commit)
+ if cur == nil {
+ panic("Could not find commit: " + commit.String())
+ }
+ rslt, err := r.Intern(cur.SetTree(t.ID()).SetParents(parents))
+ if err != nil {
+ panic(err)
+ }
+ return rslt
+}
+
+func resolveConflict(r *git.Repo) []string {
+ conflicts := []string{}
+ for _, line := range SplitLines(r.RunOutput("status", "--porcelain")) {
+ if strings.Contains(line[:2], "U") {
+ conflicts = append(conflicts, line[3:])
+ }
+ }
+
+ ct.ChangeColor(ct.Red, false, ct.None, false)
+ fmt.Println()
+ fmt.Println("There was a conflict during the cherry-pick operation.")
+ fmt.Println("Please resolve it. If you exit the shell without resolving")
+ fmt.Println("it, drover will abort.")
+ fmt.Println()
+ ct.ResetColor()
+ fmt.Println("Recap:")
+ ct.ChangeColor(ct.Green, false, ct.None, false)
+ fmt.Println(" until no conflicted files:")
+ fmt.Println(" git status # See conflicted files")
+ fmt.Println(" $EDITOR <file> # Edit conflicted files")
+ fmt.Println(" git add <file> # Let cherry pick know you're done with <file>")
+ ct.ChangeColor(ct.Cyan, false, ct.None, false)
+ fmt.Println(" git cherry-pick --continue # Message doesn't matter")
+ fmt.Println(" exit # Resume drover")
+ fmt.Println()
+ ct.ResetColor()
+ fmt.Println("Conflicts:")
+ ct.ChangeColor(ct.Red, false, ct.None, false)
+ for _, c := range conflicts {
+ fmt.Println(" " + c)
+ }
+ ct.ResetColor()
+ fmt.Println()
+
+ Shell(r.Path)
+
+ if r.RunOk("rev-parse", "CHERRY_PICK_HEAD") {
+ r.Run("cherry-pick", "--abort")
+ failIf(fmt.Errorf("Aborting due to unresolved conflict."))
+ }
+
+ return conflicts
+}
+
+func getNewMessage(commit *git.Commit, mode string, conflicts []string) string {
+ var msgLines []string
+ add := func(lines ...string) { msgLines = append(msgLines, lines...) }
+ addConflicts := func() {
+ if len(conflicts) != 0 {
+ add("", "Conflicts:")
+ for _, c := range conflicts {
+ add("\t" + c)
+ }
+ }
+ }
+ if mode == "revert" {
+ rawLines := commit.MessageRawLines()
+ msgLines = make([]string, 0, len(rawLines)+2)
+ add("Revert \"" + rawLines[0] + "\"")
+ add("")
+ add("This reverts commit " + commit.ID().String() + ".")
+ add("")
+ add("Original commit message:")
+ for _, l := range rawLines {
+ add("> " + l)
+ }
+ addConflicts()
+ } else {
+ msgLines = commit.MessageLines()
+ addConflicts()
+ add("")
+ for _, f := range commit.FooterPairs() {
+ add(fmt.Sprintf("%s: %s", f.Key, f.Value))
+ }
+ add(fmt.Sprintf("(cherry picked from commit %s)\n", commit.ID()))
+ }
+ return strings.Join(msgLines, "\n")
+}
+
+func createCommit(mode string, r *git.Repo, parent, commit, land git.ObjectID) *git.Commit {
+ addChild := func(t *git.Tree, e git.TreeDiffEntryHalf) {
+ switch e.Mode.Type() {
+ case git.TreeType:
+ return
+ case git.BlobType:
+ c := e.Child
+ t.SetChild(e.Name, &c)
+ default:
+ panic(fmt.Sprintf("Cannot process mode: %d", e.Mode))
+ }
+ }
+
+ diffEnts, err := r.DiffTree(parent.String(), commit.String())
+ failIf(err)
+
+ parTree := git.NewEmptyTree(git.NoID, len(diffEnts))
+ landTree := git.NewEmptyTree(git.NoID, len(diffEnts))
+ cmtTree := git.NewEmptyTree(git.NoID, len(diffEnts))
+
+ for _, e := range diffEnts {
+ addChild(parTree, e.Old)
+ addChild(cmtTree, e.New)
+
+ landID, ok := r.Run("rev-parse", land.String()+":"+e.Old.Name)
+ if !ok {
+ panic(fmt.Errorf("Could not rev-parse landing blob: %s:%s", land, e.Old.Name))
+ }
+ addChild(landTree, git.TreeDiffEntryHalf{
+ Name: e.Old.Name,
+ Child: *git.NewEmptyChild(
+ e.Old.Mode, git.MakeObjectID(strings.TrimSpace(landID))),
+ })
+ }
+
+ parHollow := hollow(r, parent, []git.ObjectID{}, parTree)
+ landHollow := hollow(r, land, []git.ObjectID{parHollow}, landTree)
+ cmtHollow := hollow(r, commit, []git.ObjectID{parHollow}, cmtTree)
+
+ conflicts := []string{}
+
+ r.Run("reset", "--hard", landHollow.String())
+ r.Run("checkout", "-f", landHollow.String())
+ if !r.RunOk("cherry-pick", "-Xpatience", cmtHollow.String()) {
+ conflicts = resolveConflict(r)
+ }
+
+ // TODO(iannucci): upload for review if len(conflicts) != 0
+
+ fullTree := r.GetFullTree(land.String()+":", git.NoBlobs, git.MissingOK)
+ diff, err := r.DiffTree(landHollow.String(), "HEAD")
+ failIf(err)
+ for _, e := range diff {
+ if e.Old.Mode.Type() == git.BlobType {
+ fullTree.DelChild(e.Old.Name)
+ }
+ if e.New.Mode.Type() == git.BlobType {
+ c := e.New.Child
+ fullTree.SetChild(e.New.Name, &c)
+ }
+ }
+ fullTree.InternAllowMissing(r)
+
+ cmtMsgSrc := commit
+ if mode == "revert" {
+ cmtMsgSrc = parent
+ }
+ msg := getNewMessage(r.GetObjectID(cmtMsgSrc).(*git.Commit), mode, conflicts)
+
+ cpick := (r.GetObject("HEAD").(*git.Commit).
+ SetTree(fullTree.ID()).
+ SetParents([]git.ObjectID{land}).
+ SetRawMessage(msg))
+ _, err = r.Intern(cpick)
+ failIf(err)
+
+ return cpick
+}

Powered by Google App Engine
This is Rietveld 408576698