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

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

Powered by Google App Engine
This is Rietveld 408576698