Mercurial > public > mercurial-scm > hg
comparison mercurial/localrepo.py @ 10925:a101a743c570 stable
prepush: rewrite most of the code from scratch
For servers with branchmap support, the algorithm now works as follows:
1. A list of branches in outgoing changesets is created.
2. Using the remote branchmap, a check for new branches is performed.
3. A map (from branch to head list) of locally known remote heads before
the push is created, and one which, after step 4, will contain the locally
known remote heads after the push.
4. The post-push head map is updated with the outgoing changesets, using the
branch cache update mechanism.
5. A check for new heads is performed, by comparing the length of the head list
before and after push, for each branch. If there are new heads, an error
depending on whether or not there are incoming changes on the branch,
is returned.
6. If the push is allowed, a warning is written if there are incoming changes
on any branches involved in the push.
For old servers, an algorithm similar to step 4-6 above is used to check for
new topological heads only.
Two bugs are also fixed:
1. Sometimes you would be allowed to push new branch heads without --force.
A test for this case was added.
2. You would get the "note: unsynced remote changes!" warning if there were any
incoming changesets, even if they were on unrelated branches.
author | Sune Foldager <cryo@cyanite.org> |
---|---|
date | Thu, 15 Apr 2010 21:59:21 +0200 |
parents | 816bac2f9452 |
children | 4d81cbd8a851 17031fea4e95 |
comparison
equal
deleted
inserted
replaced
10923:1782278bab8a | 10925:a101a743c570 |
---|---|
1498 ''' | 1498 ''' |
1499 common = {} | 1499 common = {} |
1500 remote_heads = remote.heads() | 1500 remote_heads = remote.heads() |
1501 inc = self.findincoming(remote, common, remote_heads, force=force) | 1501 inc = self.findincoming(remote, common, remote_heads, force=force) |
1502 | 1502 |
1503 cl = self.changelog | |
1503 update, updated_heads = self.findoutgoing(remote, common, remote_heads) | 1504 update, updated_heads = self.findoutgoing(remote, common, remote_heads) |
1504 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs) | 1505 outg, bases, heads = cl.nodesbetween(update, revs) |
1505 | 1506 |
1506 def checkbranch(lheads, rheads, lheadcnt, branchname=None): | 1507 if not bases: |
1507 ''' | 1508 self.ui.status(_("no changes found\n")) |
1508 check whether there are more local heads than remote heads on | 1509 return None, 1 |
1509 a specific branch. | 1510 |
1510 | 1511 if not force and remote_heads != [nullid]: |
1511 lheads: local branch heads | 1512 |
1512 rheads: remote branch heads | 1513 def fail_multiple_heads(unsynced, branch=None): |
1513 lheadcnt: total number of local branch heads | 1514 if branch: |
1514 ''' | |
1515 | |
1516 warn = 0 | |
1517 | |
1518 if len(lheads) > len(rheads): | |
1519 warn = 1 | |
1520 else: | |
1521 # add local heads involved in the push | |
1522 updatelheads = [self.changelog.heads(x, lheads) | |
1523 for x in update] | |
1524 newheads = set(sum(updatelheads, [])) & set(lheads) | |
1525 | |
1526 if not newheads: | |
1527 return True | |
1528 | |
1529 # add heads we don't have or that are not involved in the push | |
1530 for r in rheads: | |
1531 if r in self.changelog.nodemap: | |
1532 desc = self.changelog.heads(r, heads) | |
1533 l = [h for h in heads if h in desc] | |
1534 if not l: | |
1535 newheads.add(r) | |
1536 else: | |
1537 newheads.add(r) | |
1538 if len(newheads) > len(rheads): | |
1539 warn = 1 | |
1540 | |
1541 if warn: | |
1542 if branchname is not None: | |
1543 msg = _("abort: push creates new remote heads" | 1515 msg = _("abort: push creates new remote heads" |
1544 " on branch '%s'!\n") % branchname | 1516 " on branch '%s'!\n") % branch |
1545 else: | 1517 else: |
1546 msg = _("abort: push creates new remote heads!\n") | 1518 msg = _("abort: push creates new remote heads!\n") |
1547 self.ui.warn(msg) | 1519 self.ui.warn(msg) |
1548 if lheadcnt > len(rheads): | 1520 if unsynced: |
1521 self.ui.status(_("(you should pull and merge or" | |
1522 " use push -f to force)\n")) | |
1523 else: | |
1549 self.ui.status(_("(did you forget to merge?" | 1524 self.ui.status(_("(did you forget to merge?" |
1550 " use push -f to force)\n")) | 1525 " use push -f to force)\n")) |
1551 else: | 1526 return None, 0 |
1552 self.ui.status(_("(you should pull and merge or" | 1527 |
1553 " use push -f to force)\n")) | 1528 if remote.capable('branchmap'): |
1554 return False | 1529 # Check for each named branch if we're creating new remote heads. |
1555 return True | 1530 # To be a remote head after push, node must be either: |
1556 | 1531 # - unknown locally |
1557 if not bases: | 1532 # - a local outgoing head descended from update |
1558 self.ui.status(_("no changes found\n")) | 1533 # - a remote head that's known locally and not |
1559 return None, 1 | 1534 # ancestral to an outgoing head |
1560 elif not force: | 1535 # |
1561 # Check for each named branch if we're creating new remote heads. | 1536 # New named branches cannot be created without --force. |
1562 # To be a remote head after push, node must be either: | 1537 |
1563 # - unknown locally | 1538 # 1. Create set of branches involved in the push. |
1564 # - a local outgoing head descended from update | 1539 branches = set(self[n].branch() for n in outg) |
1565 # - a remote head that's known locally and not | 1540 |
1566 # ancestral to an outgoing head | 1541 # 2. Check for new branches on the remote. |
1567 # | 1542 remotemap = remote.branchmap() |
1568 # New named branches cannot be created without --force. | 1543 newbranches = branches - set(remotemap) |
1569 | 1544 if newbranches: # new branch requires --force |
1570 if remote_heads != [nullid]: | 1545 branchnames = ', '.join("%s" % b for b in newbranches) |
1571 if remote.capable('branchmap'): | 1546 self.ui.warn(_("abort: push creates " |
1572 remotebrheads = remote.branchmap() | 1547 "new remote branches: %s!\n") |
1573 | 1548 % branchnames) |
1574 lbrmap = self.branchmap() | 1549 self.ui.status(_("(use 'hg push -f' to force)\n")) |
1575 localbrheads = {} | 1550 return None, 0 |
1576 if not revs: | 1551 |
1577 for br, hds in lbrmap.iteritems(): | 1552 # 3. Construct the initial oldmap and newmap dicts. |
1578 localbrheads[br] = (len(hds), hds) | 1553 # They contain information about the remote heads before and |
1579 else: | 1554 # after the push, respectively. |
1580 ctxgen = (self[n] for n in msng_cl) | 1555 # Heads not found locally are not included in either dict, |
1581 self._updatebranchcache(localbrheads, ctxgen) | 1556 # since they won't be affected by the push. |
1582 for br, hds in localbrheads.iteritems(): | 1557 # unsynced contains all branches with incoming changesets. |
1583 localbrheads[br] = (len(lbrmap[br]), hds) | 1558 oldmap = {} |
1584 | 1559 newmap = {} |
1585 newbranches = list(set(localbrheads) - set(remotebrheads)) | 1560 unsynced = set() |
1586 if newbranches: # new branch requires --force | 1561 for branch in branches: |
1587 branchnames = ', '.join("%s" % b for b in newbranches) | 1562 remoteheads = remotemap[branch] |
1588 self.ui.warn(_("abort: push creates " | 1563 prunedheads = [h for h in remoteheads if h in cl.nodemap] |
1589 "new remote branches: %s!\n") | 1564 oldmap[branch] = prunedheads |
1590 % branchnames) | 1565 newmap[branch] = list(prunedheads) |
1591 # propose 'push -b .' in the msg too? | 1566 if len(remoteheads) > len(prunedheads): |
1592 self.ui.status(_("(use 'hg push -f' to force)\n")) | 1567 unsynced.add(branch) |
1593 return None, 0 | 1568 |
1594 for branch, x in localbrheads.iteritems(): | 1569 # 4. Update newmap with outgoing changes. |
1595 if branch in remotebrheads: | 1570 # This will possibly add new heads and remove existing ones. |
1596 headcnt, lheads = x | 1571 ctxgen = (self[n] for n in outg) |
1597 rheads = remotebrheads[branch] | 1572 self._updatebranchcache(newmap, ctxgen) |
1598 if not checkbranch(lheads, rheads, headcnt, branch): | 1573 |
1599 return None, 0 | 1574 # 5. Check for new heads. |
1600 else: | 1575 # If there are more heads after the push than before, a suitable |
1601 if not checkbranch(heads, remote_heads, len(heads)): | 1576 # warning, depending on unsynced status, is displayed. |
1602 return None, 0 | 1577 for branch in branches: |
1603 | 1578 if len(newmap[branch]) > len(oldmap[branch]): |
1604 if inc: | 1579 return fail_multiple_heads(branch in unsynced, branch) |
1605 self.ui.warn(_("note: unsynced remote changes!\n")) | 1580 |
1606 | 1581 # 6. Check for unsynced changes on involved branches. |
1582 if unsynced: | |
1583 self.ui.warn(_("note: unsynced remote changes!\n")) | |
1584 | |
1585 else: | |
1586 # Old servers: Check for new topological heads. | |
1587 # Code based on _updatebranchcache. | |
1588 newheads = set(h for h in remote_heads if h in cl.nodemap) | |
1589 oldheadcnt = len(newheads) | |
1590 newheads.update(outg) | |
1591 if len(newheads) > 1: | |
1592 for latest in reversed(outg): | |
1593 if latest not in newheads: | |
1594 continue | |
1595 minhrev = min(cl.rev(h) for h in newheads) | |
1596 reachable = cl.reachable(latest, cl.node(minhrev)) | |
1597 reachable.remove(latest) | |
1598 newheads.difference_update(reachable) | |
1599 if len(newheads) > oldheadcnt: | |
1600 return fail_multiple_heads(inc) | |
1601 if inc: | |
1602 self.ui.warn(_("note: unsynced remote changes!\n")) | |
1607 | 1603 |
1608 if revs is None: | 1604 if revs is None: |
1609 # use the fast path, no race possible on push | 1605 # use the fast path, no race possible on push |
1610 nodes = self.changelog.findmissing(common.keys()) | 1606 nodes = self.changelog.findmissing(common.keys()) |
1611 cg = self._changegroup(nodes, 'push') | 1607 cg = self._changegroup(nodes, 'push') |