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