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