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')