hgext/rebase.py
changeset 34024 32528419db64
parent 34023 5e83a8fe6bc4
child 34025 06b0cc2588de
equal deleted inserted replaced
34023:5e83a8fe6bc4 34024:32528419db64
   359             dest = self.repo[destrev]
   359             dest = self.repo[destrev]
   360             if dest.closesbranch() and not self.keepbranchesf:
   360             if dest.closesbranch() and not self.keepbranchesf:
   361                 self.ui.status(_('reopening closed branch head %s\n') % dest)
   361                 self.ui.status(_('reopening closed branch head %s\n') % dest)
   362 
   362 
   363     def _performrebase(self, tr):
   363     def _performrebase(self, tr):
   364         repo, ui, opts = self.repo, self.ui, self.opts
   364         repo, ui = self.repo, self.ui
   365         if self.keepbranchesf:
   365         if self.keepbranchesf:
   366             # insert _savebranch at the start of extrafns so if
   366             # insert _savebranch at the start of extrafns so if
   367             # there's a user-provided extrafn it can clobber branch if
   367             # there's a user-provided extrafn it can clobber branch if
   368             # desired
   368             # desired
   369             self.extrafns.insert(0, _savebranch)
   369             self.extrafns.insert(0, _savebranch)
   382 
   382 
   383         # Store the state before we begin so users can run 'hg rebase --abort'
   383         # Store the state before we begin so users can run 'hg rebase --abort'
   384         # if we fail before the transaction closes.
   384         # if we fail before the transaction closes.
   385         self.storestatus()
   385         self.storestatus()
   386 
   386 
   387         sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
       
   388         cands = [k for k, v in self.state.iteritems() if v == revtodo]
   387         cands = [k for k, v in self.state.iteritems() if v == revtodo]
   389         total = len(cands)
   388         total = len(cands)
   390         pos = 0
   389         pos = 0
       
   390         for subset in sortsource(self.destmap):
       
   391             pos = self._performrebasesubset(tr, subset, pos, total)
       
   392         ui.progress(_('rebasing'), None)
       
   393         ui.note(_('rebase merging completed\n'))
       
   394 
       
   395     def _performrebasesubset(self, tr, subset, pos, total):
       
   396         repo, ui, opts = self.repo, self.ui, self.opts
       
   397         sortedrevs = repo.revs('sort(%ld, -topo)', subset)
   391         for rev in sortedrevs:
   398         for rev in sortedrevs:
   392             dest = self.destmap[rev]
   399             dest = self.destmap[rev]
   393             ctx = repo[rev]
   400             ctx = repo[rev]
   394             desc = _ctxdesc(ctx)
   401             desc = _ctxdesc(ctx)
   395             if self.state[rev] == rev:
   402             if self.state[rev] == rev:
   447                     self.state[rev] = p1
   454                     self.state[rev] = p1
   448                     ui.debug('next revision set to %s\n' % p1)
   455                     ui.debug('next revision set to %s\n' % p1)
   449             else:
   456             else:
   450                 ui.status(_('already rebased %s as %s\n') %
   457                 ui.status(_('already rebased %s as %s\n') %
   451                           (desc, repo[self.state[rev]]))
   458                           (desc, repo[self.state[rev]]))
   452 
   459         return pos
   453         ui.progress(_('rebasing'), None)
       
   454         ui.note(_('rebase merging completed\n'))
       
   455 
   460 
   456     def _finishrebase(self):
   461     def _finishrebase(self):
   457         repo, ui, opts = self.repo, self.ui, self.opts
   462         repo, ui, opts = self.repo, self.ui, self.opts
   458         if self.collapsef and not self.keepopen:
   463         if self.collapsef and not self.keepopen:
   459             p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
   464             p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
   967         | C D       |
   972         | C D       |
   968         | |/        |
   973         | |/        |
   969         | B         | ...
   974         | B         | ...
   970         |/          |/
   975         |/          |/
   971         A           A
   976         A           A
       
   977 
       
   978     Besides, adjust dest according to existing rebase information. For example,
       
   979 
       
   980       B C D    B needs to be rebased on top of C, C needs to be rebased on top
       
   981        \|/     of D. We will rebase C first.
       
   982         A
       
   983 
       
   984           C'   After rebasing C, when considering B's destination, use C'
       
   985           |    instead of the original C.
       
   986       B   D
       
   987        \ /
       
   988         A
   972     """
   989     """
   973     # pick already rebased revs with same dest from state as interesting source
   990     # pick already rebased revs with same dest from state as interesting source
   974     dest = destmap[rev]
   991     dest = destmap[rev]
   975     source = [s for s, d in state.items() if d > 0 and destmap[s] == dest]
   992     source = [s for s, d in state.items() if d > 0 and destmap[s] == dest]
   976 
   993 
   979         adjusted = dest
   996         adjusted = dest
   980         if prev != nullrev:
   997         if prev != nullrev:
   981             candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
   998             candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
   982             if candidate is not None:
   999             if candidate is not None:
   983                 adjusted = state[candidate]
  1000                 adjusted = state[candidate]
       
  1001         if adjusted == dest and dest in state:
       
  1002             adjusted = state[dest]
       
  1003             if adjusted == revtodo:
       
  1004                 # sortsource should produce an order that makes this impossible
       
  1005                 raise error.ProgrammingError(
       
  1006                     'rev %d should be rebased already at this time' % dest)
   984         result.append(adjusted)
  1007         result.append(adjusted)
   985     return result
  1008     return result
   986 
  1009 
   987 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
  1010 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
   988     """
  1011     """
  1126         if set(newps) == set(oldps) and dest not in newps:
  1149         if set(newps) == set(oldps) and dest not in newps:
  1127             raise error.Abort(_('cannot rebase %d:%s without '
  1150             raise error.Abort(_('cannot rebase %d:%s without '
  1128                                 'moving at least one of its parents')
  1151                                 'moving at least one of its parents')
  1129                               % (rev, repo[rev]))
  1152                               % (rev, repo[rev]))
  1130 
  1153 
       
  1154     # Source should not be ancestor of dest. The check here guarantees it's
       
  1155     # impossible. With multi-dest, the initial check does not cover complex
       
  1156     # cases since we don't have abstractions to dry-run rebase cheaply.
       
  1157     if any(p != nullrev and isancestor(rev, p) for p in newps):
       
  1158         raise error.Abort(_('source is ancestor of destination'))
       
  1159 
  1131     # "rebasenode" updates to new p1, use the corresponding merge base.
  1160     # "rebasenode" updates to new p1, use the corresponding merge base.
  1132     if bases[0] != nullrev:
  1161     if bases[0] != nullrev:
  1133         base = bases[0]
  1162         base = bases[0]
  1134     else:
  1163     else:
  1135         base = None
  1164         base = None
  1352         clearstatus(repo)
  1381         clearstatus(repo)
  1353         clearcollapsemsg(repo)
  1382         clearcollapsemsg(repo)
  1354         repo.ui.warn(_('rebase aborted\n'))
  1383         repo.ui.warn(_('rebase aborted\n'))
  1355     return 0
  1384     return 0
  1356 
  1385 
       
  1386 def sortsource(destmap):
       
  1387     """yield source revisions in an order that we only rebase things once
       
  1388 
       
  1389     If source and destination overlaps, we should filter out revisions
       
  1390     depending on other revisions which hasn't been rebased yet.
       
  1391 
       
  1392     Yield a sorted list of revisions each time.
       
  1393 
       
  1394     For example, when rebasing A to B, B to C. This function yields [B], then
       
  1395     [A], indicating B needs to be rebased first.
       
  1396 
       
  1397     Raise if there is a cycle so the rebase is impossible.
       
  1398     """
       
  1399     srcset = set(destmap)
       
  1400     while srcset:
       
  1401         srclist = sorted(srcset)
       
  1402         result = []
       
  1403         for r in srclist:
       
  1404             if destmap[r] not in srcset:
       
  1405                 result.append(r)
       
  1406         if not result:
       
  1407             raise error.Abort(_('source and destination form a cycle'))
       
  1408         srcset -= set(result)
       
  1409         yield result
       
  1410 
  1357 def buildstate(repo, destmap, collapse, obsoletenotrebased):
  1411 def buildstate(repo, destmap, collapse, obsoletenotrebased):
  1358     '''Define which revisions are going to be rebased and where
  1412     '''Define which revisions are going to be rebased and where
  1359 
  1413 
  1360     repo: repo
  1414     repo: repo
  1361     destmap: {srcrev: destrev}
  1415     destmap: {srcrev: destrev}
  1370     if 'qtip' in repo.tags():
  1424     if 'qtip' in repo.tags():
  1371         mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
  1425         mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
  1372         if set(destmap.values()) & mqapplied:
  1426         if set(destmap.values()) & mqapplied:
  1373             raise error.Abort(_('cannot rebase onto an applied mq patch'))
  1427             raise error.Abort(_('cannot rebase onto an applied mq patch'))
  1374 
  1428 
  1375     roots = list(repo.set('roots(%ld)', rebaseset))
  1429     # Get "cycle" error early by exhausting the generator.
       
  1430     sortedsrc = list(sortsource(destmap)) # a list of sorted revs
       
  1431     if not sortedsrc:
       
  1432         raise error.Abort(_('no matching revisions'))
       
  1433 
       
  1434     # Only check the first batch of revisions to rebase not depending on other
       
  1435     # rebaseset. This means "source is ancestor of destination" for the second
       
  1436     # (and following) batches of revisions are not checked here. We rely on
       
  1437     # "defineparents" to do that check.
       
  1438     roots = list(repo.set('roots(%ld)', sortedsrc[0]))
  1376     if not roots:
  1439     if not roots:
  1377         raise error.Abort(_('no matching revisions'))
  1440         raise error.Abort(_('no matching revisions'))
  1378     roots.sort()
  1441     roots.sort()
  1379     state = dict.fromkeys(rebaseset, revtodo)
  1442     state = dict.fromkeys(rebaseset, revtodo)
  1380     emptyrebase = True
  1443     emptyrebase = (len(sortedsrc) == 1)
  1381     for root in roots:
  1444     for root in roots:
  1382         dest = repo[destmap[root.rev()]]
  1445         dest = repo[destmap[root.rev()]]
  1383         commonbase = root.ancestor(dest)
  1446         commonbase = root.ancestor(dest)
  1384         if commonbase == root:
  1447         if commonbase == root:
  1385             raise error.Abort(_('source is ancestor of destination'))
  1448             raise error.Abort(_('source is ancestor of destination'))