# HG changeset patch # User Thomas Arendsen Hein # Date 1140592312 -3600 # Node ID 1ed9e97d9d6db32aa56c408deeef753fdd44044f # Parent d5248726d22fc6f166ae69756d774f5d92f71ac3# Parent 88f0345d82e96255693aeae97f2e5b1a9bd9f626 Merge with jeffpc's hg-static diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -247,6 +247,9 @@ ui:: remote command to use for clone/push/pull operations. Default is 'hg'. ssh;; command to use for SSH connections. Default is 'ssh'. + timeout;; + The timeout used when a lock is held (in seconds), a negative value + means no timeout. Default is 600. username;; The committer of a changeset created when running "commit". Typically a person's name and email address, e.g. "Fred Widget diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -1800,7 +1800,7 @@ def pull(ui, repo, source="default", **o return r -def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None): +def push(ui, repo, dest="default-push", **opts): """push changes to the specified destination Push changes from the local repository to the given destination. @@ -1825,13 +1825,16 @@ def push(ui, repo, dest="default-push", dest = ui.expandpath(dest, repo.root) ui.status('pushing to %s\n' % (dest)) - if ssh: - ui.setconfig("ui", "ssh", ssh) - if remotecmd: - ui.setconfig("ui", "remotecmd", remotecmd) + if opts['ssh']: + ui.setconfig("ui", "ssh", opts['ssh']) + if opts['remotecmd']: + ui.setconfig("ui", "remotecmd", opts['remotecmd']) other = hg.repository(ui, dest) - r = repo.push(other, force) + revs = None + if opts['rev']: + revs = [repo.lookup(rev) for rev in opts['rev']] + r = repo.push(other, opts['force'], revs=revs) return r def rawcommit(ui, repo, *flist, **rc): @@ -2505,14 +2508,15 @@ table = { ('r', 'rev', [], _('a specific revision you would like to pull')), ('', 'remotecmd', '', _('specify hg command to run on the remote side'))], - _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')), + _('hg pull [-u] [-e FILE] [-r rev]... [--remotecmd FILE] [SOURCE]')), "^push": (push, [('f', 'force', None, _('force push')), ('e', 'ssh', '', _('specify ssh command to use')), + ('r', 'rev', [], _('a specific revision you would like to push')), ('', 'remotecmd', '', _('specify hg command to run on the remote side'))], - _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')), + _('hg push [-f] [-e FILE] [-r rev]... [--remotecmd FILE] [DEST]')), "rawcommit": (rawcommit, [('p', 'parent', [], _('parent')), diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -235,8 +235,7 @@ class localrepository(object): if os.path.exists(self.join("journal")): self.ui.status(_("rolling back interrupted transaction\n")) transaction.rollback(self.opener, self.join("journal")) - self.manifest = manifest.manifest(self.opener) - self.changelog = changelog.changelog(self.opener) + self.reload() return True else: self.ui.warn(_("no interrupted transaction available\n")) @@ -250,10 +249,20 @@ class localrepository(object): self.ui.status(_("rolling back last transaction\n")) transaction.rollback(self.opener, self.join("undo")) util.rename(self.join("undo.dirstate"), self.join("dirstate")) - self.dirstate.read() + self.reload() + self.wreload() else: self.ui.warn(_("no undo information available\n")) + def wreload(self): + self.dirstate.read() + + def reload(self): + self.changelog.load() + self.manifest.load() + self.tagscache = None + self.nodetagscache = None + def do_lock(self, lockname, wait, releasefn=None, acquirefn=None): try: l = lock.lock(self.join(lockname), 0, releasefn) @@ -261,18 +270,25 @@ class localrepository(object): if not wait: raise inst self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0]) - l = lock.lock(self.join(lockname), wait, releasefn) + try: + # default to 600 seconds timeout + l = lock.lock(self.join(lockname), + int(self.ui.config("ui", "timeout") or 600), + releasefn) + except lock.LockHeld, inst: + raise util.Abort(_("timeout while waiting for " + "lock held by %s") % inst.args[0]) if acquirefn: acquirefn() return l def lock(self, wait=1): - return self.do_lock("lock", wait) + return self.do_lock("lock", wait, acquirefn=self.reload) def wlock(self, wait=1): return self.do_lock("wlock", wait, self.dirstate.write, - self.dirstate.read) + self.wreload) def checkfilemerge(self, filename, text, filelog, manifest1, manifest2): "determine whether a new filenode is needed" @@ -950,8 +966,8 @@ class localrepository(object): cg = remote.changegroupsubset(fetch, heads, 'pull') return self.addchangegroup(cg) - def push(self, remote, force=False): - l = remote.lock() + def push(self, remote, force=False, revs=None): + lock = remote.lock() base = {} heads = remote.heads() @@ -962,17 +978,25 @@ class localrepository(object): return 1 update = self.findoutgoing(remote, base) - if not update: + if revs is not None: + msng_cl, bases, heads = self.changelog.nodesbetween(update, revs) + else: + bases, heads = update, self.changelog.heads() + + if not bases: self.ui.status(_("no changes found\n")) return 1 elif not force: - if len(heads) < len(self.changelog.heads()): + if len(bases) < len(heads): self.ui.warn(_("abort: push creates new remote branches!\n")) self.ui.status(_("(did you forget to merge?" " use push -f to force)\n")) return 1 - cg = self.changegroup(update, 'push') + if revs is None: + cg = self.changegroup(update, 'push') + else: + cg = self.changegroupsubset(update, revs, 'push') return remote.addchangegroup(cg) def changegroupsubset(self, bases, heads, source): diff --git a/mercurial/lock.py b/mercurial/lock.py --- a/mercurial/lock.py +++ b/mercurial/lock.py @@ -16,10 +16,10 @@ class LockUnavailable(LockException): pass class lock(object): - def __init__(self, file, wait=1, releasefn=None): + def __init__(self, file, timeout=-1, releasefn=None): self.f = file self.held = 0 - self.wait = wait + self.timeout = timeout self.releasefn = releasefn self.lock() @@ -27,13 +27,16 @@ class lock(object): self.release() def lock(self): + timeout = self.timeout while 1: try: self.trylock() return 1 except LockHeld, inst: - if self.wait: + if timeout != 0: time.sleep(1) + if timeout > 0: + timeout -= 1 continue raise inst diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -13,7 +13,7 @@ of the GNU General Public License, incor from node import * from i18n import gettext as _ from demandload import demandload -demandload(globals(), "binascii errno heapq mdiff sha struct zlib") +demandload(globals(), "binascii errno heapq mdiff os sha struct zlib") def hash(text, p1, p2): """generate a hash from the given text and its parent hashes @@ -187,15 +187,33 @@ class revlog(object): self.indexfile = indexfile self.datafile = datafile self.opener = opener + + self.indexstat = None self.cache = None self.chunkcache = None + self.load() + def load(self): try: - i = self.opener(self.indexfile).read() + f = self.opener(self.indexfile) except IOError, inst: if inst.errno != errno.ENOENT: raise i = "" + else: + try: + st = os.fstat(f.fileno()) + except AttributeError, inst: + st = None + else: + oldst = self.indexstat + if (oldst and st.st_dev == oldst.st_dev + and st.st_ino == oldst.st_ino + and st.st_mtime == oldst.st_mtime + and st.st_ctime == oldst.st_ctime): + return + self.indexstat = st + i = f.read() if i and i[:4] != "\0\0\0\0": raise RevlogError(_("incompatible revlog signature on %s") % diff --git a/tests/test-archive b/tests/test-archive --- a/tests/test-archive +++ b/tests/test-archive @@ -18,8 +18,7 @@ echo "name = test-archive" >> .hg/hgrc echo "allowzip = true" >> .hg/hgrc echo "allowgz = true" >> .hg/hgrc echo "allowbz2 = true" >> .hg/hgrc -serverpid=`mktemp` -hg serve -p 20059 -d --pid-file=$serverpid +hg serve -p 20059 -d --pid-file=hg.pid TIP=`hg id -v | cut -f1 -d' '` QTIP=`hg id -q` @@ -35,5 +34,4 @@ http_proxy= python getarchive.py "$TIP" http_proxy= python getarchive.py "$TIP" zip > archive.zip unzip -t archive.zip | sed "s/$QTIP/TIP/" -kill `cat $serverpid` -rm $serverpid +kill `cat hg.pid` diff --git a/tests/test-clone-pull-corruption b/tests/test-clone-pull-corruption new file mode 100755 --- /dev/null +++ b/tests/test-clone-pull-corruption @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Corrupt an hg repo with a pull started during an aborted commit +# + +# Create two repos, so that one of them can pull from the other one. +hg init source +cd source +touch foo +hg add foo +hg ci -m 'add foo' +hg clone . ../corrupted +echo >> foo +hg ci -m 'change foo' + +# Add a hook to wait 5 seconds and then abort the commit +cd ../corrupted +echo '[hooks]' >> .hg/hgrc +echo 'pretxncommit = sleep 5; exit 1' >> .hg/hgrc + +# start a commit... +touch bar +hg add bar +hg ci -m 'add bar' & + +# ... and start a pull while the commit is still running +sleep 1 +hg pull ../source 2>/dev/null + +# see what happened +wait +hg verify diff --git a/tests/test-clone-pull-corruption.out b/tests/test-clone-pull-corruption.out new file mode 100644 --- /dev/null +++ b/tests/test-clone-pull-corruption.out @@ -0,0 +1,15 @@ +pulling from ../source +abort: pretxncommit hook exited with status 1 +transaction abort! +rollback completed +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions diff --git a/tests/test-pull b/tests/test-pull --- a/tests/test-pull +++ b/tests/test-pull @@ -7,8 +7,7 @@ hg init hg addremove hg commit -m 1 hg verify -serverpid=`mktemp` -hg serve -p 20059 -d --pid-file=$serverpid +hg serve -p 20059 -d --pid-file=hg.pid cd .. hg clone http://localhost:20059/ copy @@ -19,5 +18,4 @@ cat foo hg manifest hg pull -kill `cat $serverpid` -rm $serverpid +kill `cat ../test/hg.pid` diff --git a/tests/test-pull-pull-corruption b/tests/test-pull-pull-corruption new file mode 100755 --- /dev/null +++ b/tests/test-pull-pull-corruption @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Corrupt an hg repo with two pulls. +# + +# create one repo with a long history +hg init source1 +cd source1 +touch foo +hg add foo +for i in 1 2 3 4 5 6 7 8 9 10; do + echo $i >> foo + hg ci -m $i +done +cd .. + +# create one repo with a shorter history +hg clone -r 0 source1 source2 +cd source2 +echo a >> foo +hg ci -m a +cd .. + +# create a third repo to pull both other repos into it +hg init corrupted +cd corrupted +# use a hook to make the second pull start while the first one is still running +echo '[hooks]' >> .hg/hgrc +echo 'prechangegroup = sleep 5' >> .hg/hgrc + +# start a pull... +hg pull ../source1 & + +# ... and start another pull before the first one has finished +sleep 1 +hg pull ../source2 2>/dev/null + +# see the result +wait +hg verify + diff --git a/tests/test-pull-pull-corruption.out b/tests/test-pull-pull-corruption.out new file mode 100644 --- /dev/null +++ b/tests/test-pull-pull-corruption.out @@ -0,0 +1,24 @@ +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +pulling from ../source2 +pulling from ../source1 +requesting all changes +adding changesets +adding manifests +adding file changes +added 10 changesets with 10 changes to 1 files +(run 'hg update' to get a working copy) +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg update' to get a working copy) +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 11 changesets, 11 total revisions diff --git a/tests/test-push-r b/tests/test-push-r new file mode 100755 --- /dev/null +++ b/tests/test-push-r @@ -0,0 +1,61 @@ +#!/bin/bash + +hg init test +cd test +cat >>afile <>afile <>afile <>afile <>afile <>afile <fred <>afile <