Mercurial > hg
comparison hgext/rebase.py @ 34094:5d45a997d11d
rebase: remove complex unhiding code
This is similar to Martin von Zweigbergk's previous patch [1].
Previous patches are adding more `.unfiltered()` to the rebase code. So I
wonder: are we playing whack-a-mole regarding on `unfiltered()` in rebase?
Thinking about it, I believe most of the rebase code *should* just use an
unfiltered repo. The only exception is before we figuring out a
`rebasestate`. This patch makes it so. See added comment in code for why
that's more reasonable.
This would make the code base cleaner (not mangling the `repo` object),
faster (no need to invalidate caches), simpler (less LOC), less error-prone
(no need to think about what to unhide, ex. should we unhide wdir p2? how
about destinations?), and future proof (other code may change visibility in
an unexpected way, ex. directaccess may make the destination only visible
when it's in "--dest" revset tree).
[1]: https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-March/094277.html
Differential Revision: https://phab.mercurial-scm.org/D644
author | Jun Wu <quark@fb.com> |
---|---|
date | Wed, 06 Sep 2017 16:13:04 -0700 |
parents | 15ec3119d500 |
children | 7471193be725 |
comparison
equal
deleted
inserted
replaced
34093:15ec3119d500 | 34094:5d45a997d11d |
---|---|
42 obsutil, | 42 obsutil, |
43 patch, | 43 patch, |
44 phases, | 44 phases, |
45 registrar, | 45 registrar, |
46 repair, | 46 repair, |
47 repoview, | |
48 revset, | 47 revset, |
49 revsetlang, | 48 revsetlang, |
50 scmutil, | 49 scmutil, |
51 smartset, | 50 smartset, |
52 util, | 51 util, |
135 """This class is a container for rebase runtime state""" | 134 """This class is a container for rebase runtime state""" |
136 def __init__(self, repo, ui, opts=None): | 135 def __init__(self, repo, ui, opts=None): |
137 if opts is None: | 136 if opts is None: |
138 opts = {} | 137 opts = {} |
139 | 138 |
140 self.repo = repo | 139 # prepared: whether we have rebasestate prepared or not. Currently it |
140 # decides whether "self.repo" is unfiltered or not. | |
141 # The rebasestate has explicit hash to hash instructions not depending | |
142 # on visibility. If rebasestate exists (in-memory or on-disk), use | |
143 # unfiltered repo to avoid visibility issues. | |
144 # Before knowing rebasestate (i.e. when starting a new rebase (not | |
145 # --continue or --abort)), the original repo should be used so | |
146 # visibility-dependent revsets are correct. | |
147 self.prepared = False | |
148 self._repo = repo | |
149 | |
141 self.ui = ui | 150 self.ui = ui |
142 self.opts = opts | 151 self.opts = opts |
143 self.originalwd = None | 152 self.originalwd = None |
144 self.external = nullrev | 153 self.external = nullrev |
145 # Mapping between the old revision id and either what is the new rebased | 154 # Mapping between the old revision id and either what is the new rebased |
163 self.keepbranchesf = opts.get('keepbranches', False) | 172 self.keepbranchesf = opts.get('keepbranches', False) |
164 # keepopen is not meant for use on the command line, but by | 173 # keepopen is not meant for use on the command line, but by |
165 # other extensions | 174 # other extensions |
166 self.keepopen = opts.get('keepopen', False) | 175 self.keepopen = opts.get('keepopen', False) |
167 self.obsoletenotrebased = {} | 176 self.obsoletenotrebased = {} |
177 | |
178 @property | |
179 def repo(self): | |
180 if self.prepared: | |
181 return self._repo.unfiltered() | |
182 else: | |
183 return self._repo | |
168 | 184 |
169 def storestatus(self, tr=None): | 185 def storestatus(self, tr=None): |
170 """Store the current status to allow recovery""" | 186 """Store the current status to allow recovery""" |
171 if tr: | 187 if tr: |
172 tr.addfilegenerator('rebasestate', ('rebasestate',), | 188 tr.addfilegenerator('rebasestate', ('rebasestate',), |
196 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode)) | 212 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode)) |
197 repo.ui.debug('rebase status stored\n') | 213 repo.ui.debug('rebase status stored\n') |
198 | 214 |
199 def restorestatus(self): | 215 def restorestatus(self): |
200 """Restore a previously stored status""" | 216 """Restore a previously stored status""" |
217 self.prepared = True | |
201 repo = self.repo.unfiltered() | 218 repo = self.repo.unfiltered() |
202 keepbranches = None | 219 keepbranches = None |
203 legacydest = None | 220 legacydest = None |
204 collapse = False | 221 collapse = False |
205 external = nullrev | 222 external = nullrev |
264 skipped.add(old) | 281 skipped.add(old) |
265 seen.add(new) | 282 seen.add(new) |
266 repo.ui.debug('computed skipped revs: %s\n' % | 283 repo.ui.debug('computed skipped revs: %s\n' % |
267 (' '.join(str(r) for r in sorted(skipped)) or None)) | 284 (' '.join(str(r) for r in sorted(skipped)) or None)) |
268 repo.ui.debug('rebase status resumed\n') | 285 repo.ui.debug('rebase status resumed\n') |
269 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd}) | |
270 | 286 |
271 self.originalwd = originalwd | 287 self.originalwd = originalwd |
272 self.destmap = destmap | 288 self.destmap = destmap |
273 self.state = state | 289 self.state = state |
274 self.skipped = skipped | 290 self.skipped = skipped |
353 | 369 |
354 for destrev in sorted(set(destmap.values())): | 370 for destrev in sorted(set(destmap.values())): |
355 dest = self.repo[destrev] | 371 dest = self.repo[destrev] |
356 if dest.closesbranch() and not self.keepbranchesf: | 372 if dest.closesbranch() and not self.keepbranchesf: |
357 self.ui.status(_('reopening closed branch head %s\n') % dest) | 373 self.ui.status(_('reopening closed branch head %s\n') % dest) |
374 | |
375 self.prepared = True | |
358 | 376 |
359 def _performrebase(self, tr): | 377 def _performrebase(self, tr): |
360 repo, ui = self.repo, self.ui | 378 repo, ui = self.repo, self.ui |
361 if self.keepbranchesf: | 379 if self.keepbranchesf: |
362 # insert _savebranch at the start of extrafns so if | 380 # insert _savebranch at the start of extrafns so if |
1321 raise error.Abort(_('missing .hg/last-message.txt for rebase')) | 1339 raise error.Abort(_('missing .hg/last-message.txt for rebase')) |
1322 return collapsemsg | 1340 return collapsemsg |
1323 | 1341 |
1324 def clearstatus(repo): | 1342 def clearstatus(repo): |
1325 'Remove the status files' | 1343 'Remove the status files' |
1326 _clearrebasesetvisibiliy(repo) | |
1327 # Make sure the active transaction won't write the state file | 1344 # Make sure the active transaction won't write the state file |
1328 tr = repo.currenttransaction() | 1345 tr = repo.currenttransaction() |
1329 if tr: | 1346 if tr: |
1330 tr.removefilegenerator('rebasestate') | 1347 tr.removefilegenerator('rebasestate') |
1331 repo.vfs.unlinkpath("rebasestate", ignoremissing=True) | 1348 repo.vfs.unlinkpath("rebasestate", ignoremissing=True) |
1436 repo: repo | 1453 repo: repo |
1437 destmap: {srcrev: destrev} | 1454 destmap: {srcrev: destrev} |
1438 ''' | 1455 ''' |
1439 rebaseset = destmap.keys() | 1456 rebaseset = destmap.keys() |
1440 originalwd = repo['.'].rev() | 1457 originalwd = repo['.'].rev() |
1441 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd}) | |
1442 | 1458 |
1443 # This check isn't strictly necessary, since mq detects commits over an | 1459 # This check isn't strictly necessary, since mq detects commits over an |
1444 # applied patch. But it prevents messing up the working directory when | 1460 # applied patch. But it prevents messing up the working directory when |
1445 # a partially completed rebase is blocked by mq. | 1461 # a partially completed rebase is blocked by mq. |
1446 if 'qtip' in repo.tags(): | 1462 if 'qtip' in repo.tags(): |
1578 raise error.Abort(_('--tool can only be used with --rebase')) | 1594 raise error.Abort(_('--tool can only be used with --rebase')) |
1579 ret = orig(ui, repo, *args, **opts) | 1595 ret = orig(ui, repo, *args, **opts) |
1580 | 1596 |
1581 return ret | 1597 return ret |
1582 | 1598 |
1583 def _setrebasesetvisibility(repo, revs): | |
1584 """store the currently rebased set on the repo object | |
1585 | |
1586 This is used by another function to prevent rebased revision to because | |
1587 hidden (see issue4504)""" | |
1588 repo = repo.unfiltered() | |
1589 repo._rebaseset = revs | |
1590 # invalidate cache if visibility changes | |
1591 hiddens = repo.filteredrevcache.get('visible', set()) | |
1592 if revs & hiddens: | |
1593 repo.invalidatevolatilesets() | |
1594 | |
1595 def _clearrebasesetvisibiliy(repo): | |
1596 """remove rebaseset data from the repo""" | |
1597 repo = repo.unfiltered() | |
1598 if '_rebaseset' in vars(repo): | |
1599 del repo._rebaseset | |
1600 | |
1601 def _rebasedvisible(orig, repo): | |
1602 """ensure rebased revs stay visible (see issue4504)""" | |
1603 blockers = orig(repo) | |
1604 blockers.update(getattr(repo, '_rebaseset', ())) | |
1605 return blockers | |
1606 | |
1607 def _filterobsoleterevs(repo, revs): | 1599 def _filterobsoleterevs(repo, revs): |
1608 """returns a set of the obsolete revisions in revs""" | 1600 """returns a set of the obsolete revisions in revs""" |
1609 return set(r for r in revs if repo[r].obsolete()) | 1601 return set(r for r in revs if repo[r].obsolete()) |
1610 | 1602 |
1611 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap): | 1603 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap): |
1666 cmdutil.unfinishedstates.append( | 1658 cmdutil.unfinishedstates.append( |
1667 ['rebasestate', False, False, _('rebase in progress'), | 1659 ['rebasestate', False, False, _('rebase in progress'), |
1668 _("use 'hg rebase --continue' or 'hg rebase --abort'")]) | 1660 _("use 'hg rebase --continue' or 'hg rebase --abort'")]) |
1669 cmdutil.afterresolvedstates.append( | 1661 cmdutil.afterresolvedstates.append( |
1670 ['rebasestate', _('hg rebase --continue')]) | 1662 ['rebasestate', _('hg rebase --continue')]) |
1671 # ensure rebased rev are not hidden | |
1672 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible) |