--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/sample.hgrc Sat Jul 15 17:01:01 2006 +0200
@@ -0,0 +1,123 @@
+### --- User interface
+
+[ui]
+
+### show changed files and be a bit more verbose if True
+
+# verbose = True
+
+### username data to appear in comits
+### it usually takes the form: Joe User <joe.user@host.com>
+
+# username = Joe User <j.user@example.com>
+
+### --- Extensions
+
+[extensions]
+
+### each extension has its own 'extension_name=path' line
+### the default python library path is used when path is left blank
+### the hgext dir is used when 'hgext.extension_name=' is written
+
+### acl - Access control lists
+### hg help acl
+
+# hgext.acl =
+
+### bisect - binary search changesets to detect bugs
+### hg help bisect
+
+# hgext.hbisect =
+
+### bugzilla - update bugzilla bugs when changesets mention them
+### hg help bugzilla
+
+# hgext.bugzilla =
+
+### extdiff - Use external diff application instead of builtin one
+
+# hgext.extdiff =
+
+### gpg - GPG checks and signing
+### hg help gpg
+
+# hgext.gpg =
+
+### hgk - GUI repository browser
+### hg help view
+
+# hgk = /home/user/hg/hg/contrib/hgk.py
+
+### mq - Mercurial patch queues
+### hg help mq
+
+# hgext.mq =
+
+### notify - Template driven e-mail notifications
+### hg help notify
+
+# hgext.notify =
+
+### patchbomb - send changesets as a series of patch emails
+### hg help email
+
+# hgext.patchbomb =
+
+### win32text - line ending conversion filters for the Windows platform
+
+# hgext.win32text =
+
+### --- hgk additional configuration
+
+[hgk]
+
+### set executable path
+
+# path = /home/user/hg/hg/contrib/hgk
+
+### --- Hook to Mercurial actions - See hgrc man page for avaliable hooks
+
+[hooks]
+
+### Example notify hooks (load hgext.notify extension before use)
+
+# incoming.notify = python:hgext.notify.hook
+# changegroup.notify = python:hgext.notify.hook
+
+### Email configuration for the notify and patchbomb extensions
+
+[email]
+
+### Your email address
+
+# from = user@example.com
+
+### Method to send email - smtp or /usr/sbin/sendmail or other program name
+
+# method = smtp
+
+### smtp server to send email to
+
+[smtp]
+
+# host = mail
+# port = 25
+# tls = false
+# username = user
+# password = blivet
+# local_hostname = myhost
+
+### --- Email notification hook for server
+
+[notify]
+### multiple sources can be specified as a whitespace or comma separated list
+
+# sources = serve push pull bundle
+
+### set this to False when you're ready for mail to start sending
+
+# test = True
+
+### path to config file with names of subscribers
+
+# config = /path/to/subscription/file
--- a/mercurial/commands.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/commands.py Sat Jul 15 17:01:01 2006 +0200
@@ -865,11 +865,22 @@
if op2 != nullid:
raise util.Abort(_('outstanding uncommitted merge'))
node = repo.lookup(rev)
- parent, p2 = repo.changelog.parents(node)
- if parent == nullid:
+ p1, p2 = repo.changelog.parents(node)
+ if p1 == nullid:
raise util.Abort(_('cannot back out a change with no parents'))
if p2 != nullid:
- raise util.Abort(_('cannot back out a merge'))
+ if not opts['parent']:
+ raise util.Abort(_('cannot back out a merge changeset without '
+ '--parent'))
+ p = repo.lookup(opts['parent'])
+ if p not in (p1, p2):
+ raise util.Abort(_('%s is not a parent of %s' %
+ (short(p), short(node))))
+ parent = p
+ else:
+ if opts['parent']:
+ raise util.Abort(_('cannot use --parent on non-merge changeset'))
+ parent = p1
repo.update(node, force=True, show_stats=False)
revert_opts = opts.copy()
revert_opts['rev'] = hex(parent)
@@ -959,6 +970,7 @@
ui.setconfig_remoteopts(**opts)
hg.clone(ui, ui.expandpath(source), dest,
pull=opts['pull'],
+ stream=opts['stream'],
rev=opts['rev'],
update=not opts['noupdate'])
@@ -2828,6 +2840,7 @@
('m', 'message', '', _('use <text> as commit message')),
('l', 'logfile', '', _('read commit message from <file>')),
('d', 'date', '', _('record datecode as commit date')),
+ ('', 'parent', '', _('parent to choose when backing out merge')),
('u', 'user', '', _('record user as committer')),
('I', 'include', [], _('include names matching the given patterns')),
('X', 'exclude', [], _('exclude names matching the given patterns'))],
@@ -2850,6 +2863,7 @@
('r', 'rev', [],
_('a changeset you would like to have after cloning')),
('', 'pull', None, _('use pull protocol to copy metadata')),
+ ('', 'stream', None, _('use streaming protocol (fast over LAN)')),
('e', 'ssh', '', _('specify ssh command to use')),
('', 'remotecmd', '',
_('specify hg command to run on the remote side'))],
--- a/mercurial/hg.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/hg.py Sat Jul 15 17:01:01 2006 +0200
@@ -74,7 +74,8 @@
scheme)
return ctor(ui, path)
-def clone(ui, source, dest=None, pull=False, rev=None, update=True):
+def clone(ui, source, dest=None, pull=False, rev=None, update=True,
+ stream=False):
"""Make a copy of an existing repository.
Create a copy of an existing repository in a new directory. The
@@ -96,6 +97,8 @@
pull: always pull from source repository, even in local case
+ stream: stream from repository (fast over LAN, slow over WAN)
+
rev: revision to clone up to (implies pull=True)
update: update working directory after clone completes, if
@@ -179,7 +182,7 @@
revs = [src_repo.lookup(r) for r in rev]
if dest_repo.local():
- dest_repo.pull(src_repo, heads=revs)
+ dest_repo.clone(src_repo, heads=revs, stream=stream)
elif src_repo.local():
src_repo.push(dest_repo, revs=revs)
else:
--- a/mercurial/hgweb/hgweb_mod.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/hgweb/hgweb_mod.py Sat Jul 15 17:01:01 2006 +0200
@@ -11,7 +11,8 @@
import mimetypes
from mercurial.demandload import demandload
demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
-demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
+demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
+demandload(globals(), "mercurial:templater")
demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
from mercurial.node import *
from mercurial.i18n import gettext as _
@@ -859,7 +860,7 @@
or self.t("error", error="%r not found" % fname))
def do_capabilities(self, req):
- resp = 'unbundle'
+ resp = 'unbundle stream=%d' % (self.repo.revlogversion,)
req.httphdr("application/mercurial-0.1", length=len(resp))
req.write(resp)
@@ -950,3 +951,7 @@
finally:
fp.close()
os.unlink(tempname)
+
+ def do_stream_out(self, req):
+ req.httphdr("application/mercurial-0.1")
+ streamclone.stream_out(self.repo, req)
--- a/mercurial/httprepo.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/httprepo.py Sat Jul 15 17:01:01 2006 +0200
@@ -326,6 +326,9 @@
fp.close()
os.unlink(tempname)
+ def stream_out(self):
+ return self.do_cmd('stream_out')
+
class httpsrepository(httprepository):
def __init__(self, ui, path):
if not has_https:
--- a/mercurial/localrepo.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/localrepo.py Sat Jul 15 17:01:01 2006 +0200
@@ -8,17 +8,19 @@
from node import *
from i18n import gettext as _
from demandload import *
+import repo
demandload(globals(), "appendfile changegroup")
-demandload(globals(), "changelog dirstate filelog manifest repo context")
+demandload(globals(), "changelog dirstate filelog manifest context")
demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
-demandload(globals(), "os revlog util")
+demandload(globals(), "os revlog time util")
-class localrepository(object):
+class localrepository(repo.repository):
capabilities = ()
def __del__(self):
self.transhandle = None
def __init__(self, parentui, path=None, create=0):
+ repo.repository.__init__(self)
if not path:
p = os.getcwd()
while not os.path.isdir(os.path.join(p, ".hg")):
@@ -1183,7 +1185,7 @@
# unbundle assumes local user cannot lock remote repo (new ssh
# servers, http servers).
- if 'unbundle' in remote.capabilities:
+ if remote.capable('unbundle'):
return self.push_unbundle(remote, force, revs)
return self.push_addchangegroup(remote, force, revs)
@@ -2201,6 +2203,46 @@
self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
return 1
+ def stream_in(self, remote):
+ self.ui.status(_('streaming all changes\n'))
+ fp = remote.stream_out()
+ total_files, total_bytes = map(int, fp.readline().split(' ', 1))
+ self.ui.status(_('%d files to transfer, %s of data\n') %
+ (total_files, util.bytecount(total_bytes)))
+ start = time.time()
+ for i in xrange(total_files):
+ name, size = fp.readline().split('\0', 1)
+ size = int(size)
+ self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
+ ofp = self.opener(name, 'w')
+ for chunk in util.filechunkiter(fp, limit=size):
+ ofp.write(chunk)
+ ofp.close()
+ elapsed = time.time() - start
+ self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
+ (util.bytecount(total_bytes), elapsed,
+ util.bytecount(total_bytes / elapsed)))
+ self.reload()
+ return len(self.heads()) + 1
+
+ def clone(self, remote, heads=[], stream=False):
+ '''clone remote repository.
+
+ keyword arguments:
+ heads: list of revs to clone (forces use of pull)
+ pull: force use of pull, even if remote can stream'''
+
+ # now, all clients that can stream can read repo formats
+ # supported by all servers that can stream.
+
+ # if revlog format changes, client will have to check version
+ # and format flags on "stream" capability, and stream only if
+ # compatible.
+
+ if stream and not heads and remote.capable('stream'):
+ return self.stream_in(remote)
+ return self.pull(remote, heads)
+
# used to avoid circular references so destructors work
def aftertrans(base):
p = base
--- a/mercurial/remoterepo.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/remoterepo.py Sat Jul 15 17:01:01 2006 +0200
@@ -5,7 +5,9 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-class remoterepository(object):
+import repo
+
+class remoterepository(repo.repository):
def dev(self):
return -1
--- a/mercurial/repo.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/repo.py Sat Jul 15 17:01:01 2006 +0200
@@ -5,4 +5,19 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-class RepoError(Exception): pass
+class RepoError(Exception):
+ pass
+
+class repository(object):
+ def capable(self, name):
+ '''tell whether repo supports named capability.
+ return False if not supported.
+ if boolean capability, return True.
+ if string capability, return string.'''
+ name_eq = name + '='
+ for cap in self.capabilities:
+ if name == cap:
+ return True
+ if cap.startswith(name_eq):
+ return cap[len(name_eq):]
+ return False
--- a/mercurial/sshrepo.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/sshrepo.py Sat Jul 15 17:01:01 2006 +0200
@@ -198,3 +198,6 @@
if not r:
return 1
return int(r)
+
+ def stream_out(self):
+ return self.do_cmd('stream_out')
--- a/mercurial/sshserver.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/sshserver.py Sat Jul 15 17:01:01 2006 +0200
@@ -8,7 +8,7 @@
from demandload import demandload
from i18n import gettext as _
from node import *
-demandload(globals(), "os sys tempfile util")
+demandload(globals(), "os streamclone sys tempfile util")
class sshserver(object):
def __init__(self, ui, repo):
@@ -60,7 +60,7 @@
capabilities: space separated list of tokens
'''
- r = "capabilities: unbundle\n"
+ r = "capabilities: unbundle stream=%d\n" % (self.repo.revlogversion,)
self.respond(r)
def do_lock(self):
@@ -167,3 +167,5 @@
fp.close()
os.unlink(tempname)
+ def do_stream_out(self):
+ streamclone.stream_out(self.repo, self.fout)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/streamclone.py Sat Jul 15 17:01:01 2006 +0200
@@ -0,0 +1,82 @@
+# streamclone.py - streaming clone server support for mercurial
+#
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from demandload import demandload
+from i18n import gettext as _
+demandload(globals(), "os stat util")
+
+# if server supports streaming clone, it advertises "stream"
+# capability with value that is version+flags of repo it is serving.
+# client only streams if it can read that repo format.
+
+def walkrepo(root):
+ '''iterate over metadata files in repository.
+ walk in natural (sorted) order.
+ yields 2-tuples: name of .d or .i file, size of file.'''
+
+ strip_count = len(root) + len(os.sep)
+ def walk(path, recurse):
+ ents = os.listdir(path)
+ ents.sort()
+ for e in ents:
+ pe = os.path.join(path, e)
+ st = os.lstat(pe)
+ if stat.S_ISDIR(st.st_mode):
+ if recurse:
+ for x in walk(pe, True):
+ yield x
+ else:
+ if not stat.S_ISREG(st.st_mode) or len(e) < 2:
+ continue
+ sfx = e[-2:]
+ if sfx in ('.d', '.i'):
+ yield pe[strip_count:], st.st_size
+ # write file data first
+ for x in walk(os.path.join(root, 'data'), True):
+ yield x
+ # write manifest before changelog
+ meta = list(walk(root, False))
+ meta.sort(reverse=True)
+ for x in meta:
+ yield x
+
+# stream file format is simple.
+#
+# server writes out line that says how many files, how many total
+# bytes. separator is ascii space, byte counts are strings.
+#
+# then for each file:
+#
+# server writes out line that says file name, how many bytes in
+# file. separator is ascii nul, byte count is string.
+#
+# server writes out raw file data.
+
+def stream_out(repo, fileobj):
+ '''stream out all metadata files in repository.
+ writes to file-like object, must support write() and optional flush().'''
+ # get consistent snapshot of repo. lock during scan so lock not
+ # needed while we stream, and commits can happen.
+ lock = repo.lock()
+ repo.ui.debug('scanning\n')
+ entries = []
+ total_bytes = 0
+ for name, size in walkrepo(repo.path):
+ entries.append((name, size))
+ total_bytes += size
+ lock.release()
+
+ repo.ui.debug('%d files, %d bytes to transfer\n' %
+ (len(entries), total_bytes))
+ fileobj.write('%d %d\n' % (len(entries), total_bytes))
+ for name, size in entries:
+ repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
+ fileobj.write('%s\0%d\n' % (name, size))
+ for chunk in util.filechunkiter(repo.opener(name), limit=size):
+ fileobj.write(chunk)
+ flush = getattr(fileobj, 'flush', None)
+ if flush: flush()
--- a/mercurial/util.py Sat Jul 15 17:00:23 2006 +0200
+++ b/mercurial/util.py Sat Jul 15 17:01:01 2006 +0200
@@ -961,3 +961,24 @@
else:
_rcpath = os_rcpath()
return _rcpath
+
+def bytecount(nbytes):
+ '''return byte count formatted as readable string, with units'''
+
+ units = (
+ (100, 1<<30, _('%.0f GB')),
+ (10, 1<<30, _('%.1f GB')),
+ (1, 1<<30, _('%.2f GB')),
+ (100, 1<<20, _('%.0f MB')),
+ (10, 1<<20, _('%.1f MB')),
+ (1, 1<<20, _('%.2f MB')),
+ (100, 1<<10, _('%.0f KB')),
+ (10, 1<<10, _('%.1f KB')),
+ (1, 1<<10, _('%.2f KB')),
+ (1, 1, _('%.0f bytes')),
+ )
+
+ for multiplier, divisor, format in units:
+ if nbytes >= divisor * multiplier:
+ return format % (nbytes / float(divisor))
+ return units[-1][2] % nbytes
--- a/tests/test-backout Sat Jul 15 17:00:23 2006 +0200
+++ b/tests/test-backout Sat Jul 15 17:01:01 2006 +0200
@@ -60,4 +60,40 @@
hg backout -d '3 0' 1
hg locate b
+cd ..
+hg init m
+cd m
+echo a > a
+hg commit -d '0 0' -A -m a
+echo b > b
+hg commit -d '1 0' -A -m b
+echo c > c
+hg commit -d '2 0' -A -m b
+hg update 1
+echo d > d
+hg commit -d '3 0' -A -m c
+hg merge 2
+hg commit -d '4 0' -A -m d
+
+echo '# backout of merge should fail'
+
+hg backout 4
+
+echo '# backout of merge with bad parent should fail'
+
+hg backout --parent 0 4
+
+echo '# backout of non-merge with parent should fail'
+
+hg backout --parent 0 3
+
+echo '# backout with valid parent should be ok'
+
+hg backout -d '5 0' --parent 2 4
+
+hg rollback
+hg update -C
+
+hg backout -d '6 0' --parent 3 4
+
exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-http Sat Jul 15 17:01:01 2006 +0200
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+mkdir test
+cd test
+echo foo>foo
+hg init
+hg addremove
+hg commit -m 1
+hg verify
+hg serve -p 20059 -d --pid-file=hg.pid
+cat hg.pid >> $DAEMON_PIDS
+cd ..
+
+echo % clone via stream
+http_proxy= hg clone --stream http://localhost:20059/ copy 2>&1 | \
+ sed -e 's/[0-9][0-9.]*/XXX/g'
+cd copy
+hg verify
+
+cd ..
+
+echo % clone via pull
+http_proxy= hg clone http://localhost:20059/ copy-pull
+cd copy-pull
+hg verify
--- a/tests/test-http-proxy Sat Jul 15 17:00:23 2006 +0200
+++ b/tests/test-http-proxy Sat Jul 15 17:01:01 2006 +0200
@@ -13,8 +13,18 @@
cat proxy.pid >> $DAEMON_PIDS
sleep 2
-echo %% url for proxy
-http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone http://localhost:20059/ b
+echo %% url for proxy, stream
+http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone --stream http://localhost:20059/ b | \
+ sed -e 's/[0-9][0-9.]*/XXX/g'
+cd b
+hg verify
+cd ..
+
+echo %% url for proxy, pull
+http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone http://localhost:20059/ b-pull
+cd b-pull
+hg verify
+cd ..
echo %% host:port for proxy
http_proxy=localhost:20060 hg clone --config http_proxy.always=True http://localhost:20059/ c
--- a/tests/test-http-proxy.out Sat Jul 15 17:00:23 2006 +0200
+++ b/tests/test-http-proxy.out Sat Jul 15 17:01:01 2006 +0200
@@ -1,11 +1,26 @@
adding a
-%% url for proxy
+%% url for proxy, stream
+streaming all changes
+XXX files to transfer, XXX bytes of data
+transferred XXX bytes in XXX seconds (XXX KB/sec)
+XXX files updated, XXX files merged, XXX files removed, XXX files unresolved
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
+%% url for proxy, pull
requesting all changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
%% host:port for proxy
requesting all changes
adding changesets
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-http.out Sat Jul 15 17:01:01 2006 +0200
@@ -0,0 +1,29 @@
+(the addremove command is deprecated; use add and remove --after instead)
+adding foo
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
+% clone via stream
+streaming all changes
+XXX files to transfer, XXX bytes of data
+transferred XXX bytes in XXX seconds (XXX KB/sec)
+XXX files updated, XXX files merged, XXX files removed, XXX files unresolved
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
+% clone via pull
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
--- a/tests/test-pull Sat Jul 15 17:00:23 2006 +0200
+++ b/tests/test-pull Sat Jul 15 17:01:01 2006 +0200
@@ -11,7 +11,7 @@
cat hg.pid >> $DAEMON_PIDS
cd ..
-http_proxy= hg clone http://localhost:20059/ copy
+http_proxy= hg clone --pull http://localhost:20059/ copy
cd copy
hg verify
hg co
--- a/tests/test-ssh Sat Jul 15 17:00:23 2006 +0200
+++ b/tests/test-ssh Sat Jul 15 17:01:01 2006 +0200
@@ -30,7 +30,14 @@
cd ..
-echo "# clone remote"
+echo "# clone remote via stream"
+hg clone -e ./dummyssh --stream ssh://user@dummy/remote local-stream 2>&1 | \
+ sed -e 's/[0-9][0-9.]*/XXX/g'
+cd local-stream
+hg verify
+cd ..
+
+echo "# clone remote via pull"
hg clone -e ./dummyssh ssh://user@dummy/remote local
echo "# verify"
--- a/tests/test-ssh.out Sat Jul 15 17:00:23 2006 +0200
+++ b/tests/test-ssh.out Sat Jul 15 17:01:01 2006 +0200
@@ -1,5 +1,15 @@
# creating 'remote'
-# clone remote
+# clone remote via stream
+streaming all changes
+XXX files to transfer, XXX bytes of data
+transferred XXX bytes in XXX seconds (XXX KB/sec)
+XXX files updated, XXX files merged, XXX files removed, XXX files unresolved
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
+# clone remote via pull
requesting all changes
adding changesets
adding manifests
@@ -70,6 +80,7 @@
Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
Got arguments 1:user@dummy 2:hg -R local serve --stdio 3: 4: 5:
Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5: