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

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

Powered by Google App Engine
This is Rietveld 408576698