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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 package main
5
6 import (
7 "bytes"
8 "fmt"
9 "strings"
10 "sync"
11
12 "github.com/cheggaaa/pb"
13 "github.com/daviddengcn/go-colortext"
14
15 "infra/libs/git"
16 "infra/libs/gitiles"
17 )
18
19 type stringSet map[string]struct{}
20
21 func (s stringSet) Has(k string) bool {
22 _, ok := s[k]
23 return ok
24 }
25
26 func (s stringSet) Add(k string) bool {
27 if s.Has(k) {
28 return false
29 }
30 s[k] = struct{}{}
31 return true
32 }
33
34 func (s stringSet) Del(k string) {
35 delete(s, k)
36 }
37
38 func (s stringSet) String() string {
39 buf := &bytes.Buffer{}
40 fmt.Fprint(buf, "{")
41 first := true
42 for k := range s {
43 if !first {
44 fmt.Fprint(buf, " ")
45 }
46 first = false
47 fmt.Fprint(buf, k)
48 }
49 fmt.Fprint(buf, "}")
50 return buf.String()
51 }
52
53 type internRequest struct {
54 commit git.ObjectID
55 pathPieces []string
56 }
57
58 func (i internRequest) repoName() (ret string) {
59 ret = i.commit.String()
60 if len(i.pathPieces) > 0 {
61 ret += ":" // root tree
62 ret += strings.Join(i.pathPieces, "/")
63 }
64 return
65 }
66
67 func commitRequest(commit git.ObjectID) internRequest {
68 return internRequest{commit: commit}
69 }
70
71 func rootTreeRequest(commit git.ObjectID) internRequest {
72 return internRequest{commit: commit, pathPieces: []string{""}}
73 }
74
75 func objRequest(commit git.ObjectID, pathPieces []string) internRequest {
76 for _, p := range pathPieces {
77 if p == "" {
78 panic(fmt.Errorf(
79 "Cannot have an empty pathPieces entry in an obj Request! %#v", pathPieces))
80 }
81 }
82 return internRequest{commit: commit, pathPieces: pathPieces}
83 }
84
85 type progMessage bool
86
87 const (
88 progDone progMessage = iota == 0
89 progAdd
90 )
91
92 type lazyProgBar chan<- progMessage
93
94 func newLazyProgBar(estSize int) lazyProgBar {
95 ch := make(chan progMessage, estSize)
96 var bar *pb.ProgressBar
97 go func() {
98 for amt := range ch {
99 if bar == nil {
100 if amt == progDone {
101 panic("cannot start progress with a prog Done")
102 }
103 bar = pb.StartNew(1)
104 } else {
105 switch amt {
106 case progAdd:
107 bar.Total++
108 case progDone:
109 bar.Increment()
110 }
111 }
112 }
113 if bar != nil {
114 bar.Finish()
115 }
116 }()
117 return ch
118 }
119
120 func (l lazyProgBar) Add() { l <- progAdd }
121 func (l lazyProgBar) Done() { l <- progDone }
122 func (l lazyProgBar) Finish() { close(l) }
123
124 func internService(r *git.Repo, g *gitiles.Gitiles, reqs []internRequest) <-chan error {
125 bar := newLazyProgBar(len(reqs))
126 grp := sync.WaitGroup{}
127 grp.Add(len(reqs))
128 allErrCh := make(chan error, len(reqs))
129
130 go func() {
131 grp.Wait()
132 bar.Finish()
133 close(allErrCh)
134 }()
135
136 for _, req := range reqs {
137 req := req
138 go func() {
139 defer grp.Done()
140 if r.HasObject(req.repoName()) {
141 return
142 }
143 bar.Add()
144 defer bar.Done()
145 rslt, err := g.GetObjectFromPath(req.commit.String(), re q.pathPieces...)
146 if err == nil {
147 _, err = r.Intern(rslt)
148 }
149 allErrCh <- err
150 }()
151 }
152
153 return allErrCh
154 }
155
156 func acquireObjects(r *git.Repo, g *gitiles.Gitiles, commit, landCommit *git.Com mit, treeDiff git.TreeDiff) {
157 if len(commit.Parents()) != 1 {
158 panic(fmt.Errorf("Got wrong number of parents for commit %s: %s" ,
159 commit.ID(), commit.Parents()))
160 }
161
162 fmt.Println("Acquiring objects")
163 reqs := make([]internRequest, 0, 5*3*len(treeDiff))
164
165 dedup := stringSet{}
166
167 add := func(req internRequest) {
168 if !dedup.Add(req.repoName()) {
169 return
170 }
171 reqs = append(reqs, req)
172 }
173
174 addBlobTreesFor := func(commit git.ObjectID, path string) {
175 if path == "/dev/null" {
176 return
177 }
178 pathBits := strings.Split(path, "/")
179 add(objRequest(commit, pathBits))
180 add(rootTreeRequest(commit))
181 for i := 1; i < len(pathBits); i++ {
182 add(objRequest(commit, pathBits[:i]))
183 }
184 }
185
186 add(commitRequest(commit.ID()))
187 add(commitRequest(commit.Parents()[0]))
188 add(commitRequest(landCommit.ID()))
189 for _, wholeEntry := range treeDiff {
190 addBlobTreesFor(commit.ID(), wholeEntry.New.Name)
191 addBlobTreesFor(commit.Parents()[0], wholeEntry.Old.Name)
192 addBlobTreesFor(landCommit.ID(), wholeEntry.Old.Name)
193 }
194
195 for e := range internService(r, g, reqs) {
196 failIf(e)
197 }
198 fmt.Println("Done")
199 }
200
201 func hollow(r *git.Repo, commit git.ObjectID, parents []git.ObjectID, t *git.Tre e) git.ObjectID {
202 t.Intern(r)
203 cur := r.GetObjectID(commit).(*git.Commit)
204 if cur == nil {
205 panic("Could not find commit: " + commit.String())
206 }
207 rslt, err := r.Intern(cur.SetTree(t.ID()).SetParents(parents))
208 if err != nil {
209 panic(err)
210 }
211 return rslt
212 }
213
214 func resolveConflict(r *git.Repo) []string {
215 conflicts := []string{}
216 for _, line := range SplitLines(r.RunOutput("status", "--porcelain")) {
217 if strings.Contains(line[:2], "U") {
218 conflicts = append(conflicts, line[3:])
219 }
220 }
221
222 ct.ChangeColor(ct.Red, false, ct.None, false)
223 fmt.Println()
224 fmt.Println("There was a conflict during the cherry-pick operation.")
225 fmt.Println("Please resolve it. If you exit the shell without resolving" )
226 fmt.Println("it, drover will abort.")
227 fmt.Println()
228 ct.ResetColor()
229 fmt.Println("Recap:")
230 ct.ChangeColor(ct.Green, false, ct.None, false)
231 fmt.Println(" until no conflicted files:")
232 fmt.Println(" git status # See conflicted files")
233 fmt.Println(" $EDITOR <file> # Edit conflicted files")
234 fmt.Println(" git add <file> # Let cherry pick know you're done with <file>")
235 ct.ChangeColor(ct.Cyan, false, ct.None, false)
236 fmt.Println(" git cherry-pick --continue # Message doesn't matter")
237 fmt.Println(" exit # Resume drover")
238 fmt.Println()
239 ct.ResetColor()
240 fmt.Println("Conflicts:")
241 ct.ChangeColor(ct.Red, false, ct.None, false)
242 for _, c := range conflicts {
243 fmt.Println(" " + c)
244 }
245 ct.ResetColor()
246 fmt.Println()
247
248 Shell(r.Path)
249
250 if r.RunOk("rev-parse", "CHERRY_PICK_HEAD") {
251 r.Run("cherry-pick", "--abort")
252 failIf(fmt.Errorf("Aborting due to unresolved conflict."))
253 }
254
255 return conflicts
256 }
257
258 func getNewMessage(commit *git.Commit, mode string, conflicts []string) string {
259 var msgLines []string
260 add := func(lines ...string) { msgLines = append(msgLines, lines...) }
261 addConflicts := func() {
262 if len(conflicts) != 0 {
263 add("", "Conflicts:")
264 for _, c := range conflicts {
265 add("\t" + c)
266 }
267 }
268 }
269 if mode == "revert" {
270 rawLines := commit.MessageRawLines()
271 msgLines = make([]string, 0, len(rawLines)+2)
272 add("Revert \"" + rawLines[0] + "\"")
273 add("")
274 add("This reverts commit " + commit.ID().String() + ".")
275 add("")
276 add("Original commit message:")
277 for _, l := range rawLines {
278 add("> " + l)
279 }
280 addConflicts()
281 } else {
282 msgLines = commit.MessageLines()
283 addConflicts()
284 add("")
285 for _, f := range commit.FooterPairs() {
286 add(fmt.Sprintf("%s: %s", f.Key, f.Value))
287 }
288 add(fmt.Sprintf("(cherry picked from commit %s)\n", commit.ID()) )
289 }
290 return strings.Join(msgLines, "\n")
291 }
292
293 func createCommit(mode string, r *git.Repo, parent, commit, land git.ObjectID) * git.Commit {
294 addChild := func(t *git.Tree, e git.TreeDiffEntryHalf) {
295 switch e.Mode.Type() {
296 case git.TreeType:
297 return
298 case git.BlobType:
299 c := e.Child
300 t.SetChild(e.Name, &c)
301 default:
302 panic(fmt.Sprintf("Cannot process mode: %d", e.Mode))
303 }
304 }
305
306 diffEnts, err := r.DiffTree(parent.String(), commit.String())
307 failIf(err)
308
309 parTree := git.NewEmptyTree(git.NoID, len(diffEnts))
310 landTree := git.NewEmptyTree(git.NoID, len(diffEnts))
311 cmtTree := git.NewEmptyTree(git.NoID, len(diffEnts))
312
313 for _, e := range diffEnts {
314 addChild(parTree, e.Old)
315 addChild(cmtTree, e.New)
316
317 landID, ok := r.Run("rev-parse", land.String()+":"+e.Old.Name)
318 if !ok {
319 panic(fmt.Errorf("Could not rev-parse landing blob: %s:% s", land, e.Old.Name))
320 }
321 addChild(landTree, git.TreeDiffEntryHalf{
322 Name: e.Old.Name,
323 Child: *git.NewEmptyChild(
324 e.Old.Mode, git.MakeObjectID(strings.TrimSpace(l andID))),
325 })
326 }
327
328 parHollow := hollow(r, parent, []git.ObjectID{}, parTree)
329 landHollow := hollow(r, land, []git.ObjectID{parHollow}, landTree)
330 cmtHollow := hollow(r, commit, []git.ObjectID{parHollow}, cmtTree)
331
332 conflicts := []string{}
333
334 r.Run("reset", "--hard", landHollow.String())
335 r.Run("checkout", "-f", landHollow.String())
336 if !r.RunOk("cherry-pick", "-Xpatience", cmtHollow.String()) {
337 conflicts = resolveConflict(r)
338 }
339
340 // TODO(iannucci): upload for review if len(conflicts) != 0
341
342 fullTree := r.GetFullTree(land.String()+":", git.NoBlobs, git.MissingOK)
343 diff, err := r.DiffTree(landHollow.String(), "HEAD")
344 failIf(err)
345 for _, e := range diff {
346 if e.Old.Mode.Type() == git.BlobType {
347 fullTree.DelChild(e.Old.Name)
348 }
349 if e.New.Mode.Type() == git.BlobType {
350 c := e.New.Child
351 fullTree.SetChild(e.New.Name, &c)
352 }
353 }
354 fullTree.InternAllowMissing(r)
355
356 cmtMsgSrc := commit
357 if mode == "revert" {
358 cmtMsgSrc = parent
359 }
360 msg := getNewMessage(r.GetObjectID(cmtMsgSrc).(*git.Commit), mode, confl icts)
361
362 cpick := (r.GetObject("HEAD").(*git.Commit).
363 SetTree(fullTree.ID()).
364 SetParents([]git.ObjectID{land}).
365 SetRawMessage(msg))
366 _, err = r.Intern(cpick)
367 failIf(err)
368
369 return cpick
370 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698