Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/subrepo.py @ 43919:4b7d5d10c45d
exchange: ensure all outgoing subrepo references are present before pushing
We've run into occasional problems with people committing a repo, and then
amending or rebasing in the subrepo. That makes it so that the revision in the
parent can't be checked out, and the problem gets propagated on push. Mercurial
already tries to defend against this sort of dangling reference by pushing *all*
subrepo revisions first. This reuses the checks that trigger warnings in
`hg verify` to bail on the push unless using `--force`.
I thought about putting this on the server side, but at that point, all of the
data has been transferred, only to bail out. Additionally, SCM Manager hosts
subrepos in a location that isn't nested in the parent, so normal subrepo code
would complain that the subrepo is missing when run on the server.
Because the push command pushes subrepos before calling this exchange code, a
subrepo will be pushed before the parent is verified. Not great, but no
dangling references are exchanged, so it solves the problem. This code isn't in
the loop that pushes the subrepos because:
1) the list of outgoing revisions is needed to limit the scope of the check
2) the loop only accesses the current revision, and therefore can miss
subrepos that were dropped in previous commits
3) this code is called when pushing a subrepo, so the protection is recursive
I'm not sure if there's a cheap check for the list of files in the outgoing
bundle. If there is, that would provide a fast path to bypass this check for
people not using subrepos (or if no subrepo changes were made). There's
probably also room for verifying other references like tags. But since that
doesn't break checkouts, it's much less of a problem.
Differential Revision: https://phab.mercurial-scm.org/D7616
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Thu, 12 Dec 2019 12:30:15 -0500 |
parents | aea70ca7dd85 |
children | e685fac56693 |
comparison
equal
deleted
inserted
replaced
43918:a89381e04c58 | 43919:4b7d5d10c45d |
---|---|
427 def unshare(self): | 427 def unshare(self): |
428 ''' | 428 ''' |
429 convert this repository from shared to normal storage. | 429 convert this repository from shared to normal storage. |
430 ''' | 430 ''' |
431 | 431 |
432 def verify(self): | 432 def verify(self, onpush=False): |
433 '''verify the integrity of the repository. Return 0 on success or | 433 """verify the revision of this repository that is held in `_state` is |
434 warning, 1 on any error. | 434 present and not hidden. Return 0 on success or warning, 1 on any |
435 ''' | 435 error. In the case of ``onpush``, warnings or errors will raise an |
436 exception if the result of pushing would be a broken remote repository. | |
437 """ | |
436 return 0 | 438 return 0 |
437 | 439 |
438 @propertycache | 440 @propertycache |
439 def wvfs(self): | 441 def wvfs(self): |
440 """return vfs to access the working directory of this subrepository | 442 """return vfs to access the working directory of this subrepository |
1011 if self._repo.shared(): | 1013 if self._repo.shared(): |
1012 self.ui.status(_(b"unsharing subrepo '%s'\n") % self._relpath) | 1014 self.ui.status(_(b"unsharing subrepo '%s'\n") % self._relpath) |
1013 | 1015 |
1014 hg.unshare(self.ui, self._repo) | 1016 hg.unshare(self.ui, self._repo) |
1015 | 1017 |
1016 def verify(self): | 1018 def verify(self, onpush=False): |
1017 try: | 1019 try: |
1018 rev = self._state[1] | 1020 rev = self._state[1] |
1019 ctx = self._repo.unfiltered()[rev] | 1021 ctx = self._repo.unfiltered()[rev] |
1020 if ctx.hidden(): | 1022 if ctx.hidden(): |
1021 # Since hidden revisions aren't pushed/pulled, it seems worth an | 1023 # Since hidden revisions aren't pushed/pulled, it seems worth an |
1022 # explicit warning. | 1024 # explicit warning. |
1023 ui = self._repo.ui | 1025 msg = _(b"subrepo '%s' is hidden in revision %s") % ( |
1024 ui.warn( | 1026 self._relpath, |
1025 _(b"subrepo '%s' is hidden in revision %s\n") | 1027 node.short(self._ctx.node()), |
1026 % (self._relpath, node.short(self._ctx.node())) | |
1027 ) | 1028 ) |
1029 | |
1030 if onpush: | |
1031 raise error.Abort(msg) | |
1032 else: | |
1033 self._repo.ui.warn(b'%s\n' % msg) | |
1028 return 0 | 1034 return 0 |
1029 except error.RepoLookupError: | 1035 except error.RepoLookupError: |
1030 # A missing subrepo revision may be a case of needing to pull it, so | 1036 # A missing subrepo revision may be a case of needing to pull it, so |
1031 # don't treat this as an error. | 1037 # don't treat this as an error for `hg verify`. |
1032 self._repo.ui.warn( | 1038 msg = _(b"subrepo '%s' not found in revision %s") % ( |
1033 _(b"subrepo '%s' not found in revision %s\n") | 1039 self._relpath, |
1034 % (self._relpath, node.short(self._ctx.node())) | 1040 node.short(self._ctx.node()), |
1035 ) | 1041 ) |
1042 | |
1043 if onpush: | |
1044 raise error.Abort(msg) | |
1045 else: | |
1046 self._repo.ui.warn(b'%s\n' % msg) | |
1036 return 0 | 1047 return 0 |
1037 | 1048 |
1038 @propertycache | 1049 @propertycache |
1039 def wvfs(self): | 1050 def wvfs(self): |
1040 """return own wvfs for efficiency and consistency | 1051 """return own wvfs for efficiency and consistency |