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)