| OLD | NEW |
| (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 } |
| OLD | NEW |