changeset 2655:ddf404954092

merge with tonfa-experimental
author Matt Mackall <mpm@selenic.com>
date Wed, 19 Jul 2006 14:22:08 -0500
parents 02b6fa7bbfbf (diff) 9bd3d44c32f6 (current diff)
children e57df017640d
files mercurial/commands.py
diffstat 26 files changed, 593 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/contrib/sample.hgrc
@@ -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/contrib/vim/hgcommand.vim
+++ b/contrib/vim/hgcommand.vim
@@ -171,7 +171,7 @@ function! s:HGCreateCommandBuffer(cmd, c
   endif
 
   let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd
-  echomsg "DBG :".hgCommand
+  "echomsg "DBG :".hgCommand
   let hgOut = system(hgCommand)
   " HACK:  diff command does not return proper error codes
   if v:shell_error && a:cmdName != 'hgdiff'
@@ -315,18 +315,27 @@ endfunction
 
 function! s:HGGetStatusVars(revisionVar, branchVar, repositoryVar)
   let hgBufferCheck=s:HGCurrentBufferCheck()
+  "echomsg "DBG : in HGGetStatusVars"
   if hgBufferCheck == -1 
     return ""
   endif
   let fileName=bufname(hgBufferCheck)
-  let realFileName = fnamemodify(s:HGResolveLink(fileName), ':t')
-  let oldCwd=s:HGChangeToCurrentFileDir(fileName)
+  let fileNameWithoutLink=s:HGResolveLink(fileName)
+  let realFileName = fnamemodify(fileNameWithoutLink, ':t')
+  let oldCwd=s:HGChangeToCurrentFileDir(realFileName)
   try
-     ""TODO
-    "if !filereadable('HG/Root')
-      "return ""
-    "endif
-    let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . fileName
+    let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " root  " 
+    let roottext=system(hgCommand)
+    " Suppress ending null char ! Does it work in window ?
+    let roottext=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','')
+    if match(getcwd()."/".fileNameWithoutLink, roottext) == -1
+      return ""
+    endif
+    let returnExpression = ""
+    if a:repositoryVar != ""
+      let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . roottext . "'"
+    endif
+    let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . realFileName
     let statustext=system(hgCommand)
     if(v:shell_error)
       return ""
@@ -339,32 +348,23 @@ function! s:HGGetStatusVars(revisionVar,
       let revision="DELETED"
     elseif match(statustext, '^[A]') >= 0 
       let revision="ADDED"
-    endif
+    else
+      " The file is tracked, we can try to get is revision number
+      let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " parents -b  " 
+      let statustext=system(hgCommand)
+      if(v:shell_error)
+	  return ""
+      endif
+      let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "")
 
-    let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " parents -b  " 
-    let statustext=system(hgCommand)
-    if(v:shell_error)
-        return ""
-    endif
-    if exists('revision')
-      let returnExpression = "let " . a:revisionVar . "='" . revision . "'"
-    else
-      let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "")
-      let returnExpression = "let " . a:revisionVar . "='" . revision . "'"
+      if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0
+	let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
+	let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
+      endif
     endif
-
-    if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0
-      let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
-      let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
+    if (exists('revision'))
+      let returnExpression = "let " . a:revisionVar . "='" . revision . "' " . returnExpression
     endif
-    if a:repositoryVar != ""
-      let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " root  " 
-      let roottext=system(hgCommand)
-      let repository=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','')
-      let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . repository . "'"
-    endif
-
-
 
     return returnExpression
   finally
@@ -432,6 +432,11 @@ function! s:HGMarkOrigBufferForSetup(hgB
     if origBuffer != a:hgBuffer
       call setbufvar(origBuffer, "HGBufferSetup", 0)
     endif
+  else
+    "We are presumably in the original buffer
+    let b:HGBufferSetup = 0
+    "We do the setup now as now event will be triggered allowing it later.
+    call s:HGSetupBuffer()
   endif
   return a:hgBuffer
 endfunction
@@ -608,6 +613,7 @@ function! HGEnableBufferSetup()
   augroup HGCommandPlugin
     au!
     au BufEnter * call s:HGSetupBuffer()
+    au BufWritePost * call s:HGSetupBuffer()
     " Force resetting up buffer on external file change (HG update)
     au FileChangedShell * call s:HGSetupBuffer(1)
   augroup END
@@ -686,7 +692,7 @@ function! s:HGAnnotate(...)
   endif
 
   let resultBuffer=s:HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision) 
-  echomsg "DBG: ".resultBuffer
+  "echomsg "DBG: ".resultBuffer
   if resultBuffer !=  -1
     set filetype=HGAnnotate
   endif
@@ -754,8 +760,7 @@ function! s:HGCommit(...)
           \ ':call <SID>HGFinishCommit("' . messageFileName . '",' .
           \                             '"' . newCwd . '",' .
           \                             '"' . realFileName . '",' .
-          \                             hgBufferCheck . ')<CR>'.
-          \ ':call <SID>HGBufferSetup(1)<CR>'
+          \                             hgBufferCheck . ')<CR>'
 
     silent 0put ='HG: ----------------------------------------------------------------------'
     silent put =\"HG: Enter Log.  Lines beginning with `HG:' are removed automatically\"
@@ -840,7 +845,7 @@ function! s:HGFinishCommit(messageFile, 
     if strlen(a:targetDir) > 0
       execute 'cd' escape(a:targetDir, ' ')
     endif
-    let resultBuffer=s:HGCreateCommandBuffer('commit -F "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
+    let resultBuffer=s:HGCreateCommandBuffer('commit -l "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
     execute 'cd' escape(oldCwd, ' ')
     execute 'bw' escape(a:messageFile, ' *?\')
     silent execute 'call delete("' . a:messageFile . '")'
@@ -908,7 +913,6 @@ endfunction
 " Function: s:HGUpdate() {{{2
 function! s:HGUpdate()
   return s:HGMarkOrigBufferForSetup(s:HGDoCommand('update', 'update', ''))
-  call s:HGSetupBuffer(1)
 endfunction
 
 " Function: s:HGVimDiff(...) {{{2
@@ -1673,6 +1677,11 @@ 8. Known bugs						      *hgcommand-bugs
    may still be bugs in here, depending on many configuration details.
 
 ==============================================================================
+
+9. TODO  						      *hgcommand-todo*
+
+   Integrate symlink tracking once HG will support them.
+==============================================================================
 === END_DOC
 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 " v im:tw=78:ts=8:ft=help:norl:
--- a/doc/hgrc.5.txt
+++ b/doc/hgrc.5.txt
@@ -317,7 +317,7 @@ paths::
   Assigns symbolic names to repositories.  The left side is the
   symbolic name, and the right gives the directory or URL that is the
   location of the repository.  Default paths can be declared by
- setting the following entries.
+  setting the following entries.
   default;;
     Directory or URL to use when pulling if no source is specified.
     Default is set to repository from which the current repository
@@ -326,6 +326,18 @@ paths::
     Optional.  Directory or URL to use when pushing if no destination
     is specified.
 
+server::
+  Controls generic server settings.
+  uncompressed;;
+    Whether to allow clients to clone a repo using the uncompressed
+    streaming protocol.  This transfers about 40% more data than a
+    regular clone, but uses less memory and CPU on both server and
+    client.  Over a LAN (100Mbps or better) or a very fast WAN, an
+    uncompressed streaming clone is a lot faster (~10x) than a regular
+    clone.  Over most WAN connections (anything slower than about
+    6Mbps), uncompressed streaming is slower, because of the extra
+    data transfer overhead.  Default is False.
+
 ui::
   User interface controls.
   debug;;
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -869,11 +869,22 @@ def backout(ui, repo, rev, **opts):
     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)
@@ -963,6 +974,7 @@ def clone(ui, source, dest=None, **opts)
     ui.setconfig_remoteopts(**opts)
     hg.clone(ui, ui.expandpath(source), dest,
              pull=opts['pull'],
+             stream=opts['uncompressed'],
              rev=opts['rev'],
              update=not opts['noupdate'])
 
@@ -2612,6 +2624,7 @@ def status(ui, repo, *pats, **opts):
     ! = deleted, but still tracked
     ? = not tracked
     I = ignored (not shown by default)
+      = the previous added file was copied from here
     """
 
     show_ignored = opts['ignored'] and True or False
@@ -2640,6 +2653,9 @@ def status(ui, repo, *pats, **opts):
 
         for f in changes:
             ui.write(format % f)
+            if (opts.get('copies') and not opts.get('no_status')
+                and opt == 'added' and repo.dirstate.copies.has_key(f)):
+                ui.write('  %s%s' % (repo.dirstate.copies[f], end))
 
 def tag(ui, repo, name, rev_=None, **opts):
     """add a tag for the current tip or a given revision
@@ -2847,6 +2863,7 @@ table = {
           ('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'))],
@@ -2869,6 +2886,8 @@ table = {
           ('r', 'rev', [],
            _('a changeset you would like to have after cloning')),
           ('', 'pull', None, _('use pull protocol to copy metadata')),
+          ('', 'uncompressed', None,
+           _('use uncompressed transfer (fast over LAN)')),
           ('e', 'ssh', '', _('specify ssh command to use')),
           ('', 'remotecmd', '',
            _('specify hg command to run on the remote side'))],
@@ -3127,6 +3146,7 @@ table = {
           ('u', 'unknown', None, _('show only unknown (not tracked) files')),
           ('i', 'ignored', None, _('show ignored files')),
           ('n', 'no-status', None, _('hide status prefix')),
+          ('C', 'copies', None, _('show source of copied files')),
           ('0', 'print0', None,
            _('end filenames with NUL, for use with xargs')),
           ('I', 'include', [], _('include names matching the given patterns')),
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -74,7 +74,8 @@ def repository(ui, path=None, create=0):
                              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,9 @@ def clone(ui, source, dest=None, pull=Fa
 
     pull: always pull from source repository, even in local case
 
+    stream: stream raw data uncompressed 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
@@ -153,9 +157,9 @@ def clone(ui, source, dest=None, pull=Fa
         # we lock here to avoid premature writing to the target
         dest_lock = lock.lock(os.path.join(dest_path, ".hg", "lock"))
 
-	# we need to remove the (empty) data dir in dest so copyfiles
-	# can do its work
-	os.rmdir(os.path.join(dest_path, ".hg", "data"))
+        # we need to remove the (empty) data dir in dest so copyfiles
+        # can do its work
+        os.rmdir(os.path.join(dest_path, ".hg", "data"))
         files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
         for f in files.split():
             src = os.path.join(source, ".hg", f)
@@ -166,8 +170,8 @@ def clone(ui, source, dest=None, pull=Fa
                 if inst.errno != errno.ENOENT:
                     raise
 
-	# we need to re-init the repo after manually copying the data
-	# into it
+        # we need to re-init the repo after manually copying the data
+        # into it
         dest_repo = repository(ui, dest)
 
     else:
@@ -179,7 +183,7 @@ def clone(ui, source, dest=None, pull=Fa
             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
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -11,7 +11,8 @@ import os.path
 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,10 @@ class hgweb(object):
                   or self.t("error", error="%r not found" % fname))
 
     def do_capabilities(self, req):
-        resp = 'unbundle'
+        caps = ['unbundle']
+        if self.repo.ui.configbool('server', 'uncompressed'):
+            caps.append('stream=%d' % self.repo.revlogversion)
+        resp = ' '.join(caps)
         req.httphdr("application/mercurial-0.1", length=len(resp))
         req.write(resp)
 
@@ -950,3 +954,7 @@ class hgweb(object):
         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
+++ b/mercurial/httprepo.py
@@ -326,6 +326,9 @@ class httprepository(remoterepository):
             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
+++ b/mercurial/localrepo.py
@@ -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 @@ class localrepository(object):
         # 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,50 @@ class localrepository(object):
             self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
             return 1
 
+    def stream_in(self, remote):
+        fp = remote.stream_out()
+        resp = int(fp.readline())
+        if resp != 0:
+            raise util.Abort(_('operation forbidden by server'))
+        self.ui.status(_('streaming all changes\n'))
+        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)
+        stream: use streaming clone if possible'''
+
+        # now, all clients that can request uncompressed clones can
+        # read repo formats supported by all servers that can serve
+        # them.
+
+        # if revlog format changes, client will have to check version
+        # and format flags on "stream" capability, and use
+        # uncompressed 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
+++ b/mercurial/remoterepo.py
@@ -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
+++ b/mercurial/repo.py
@@ -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
+++ b/mercurial/sshrepo.py
@@ -198,3 +198,6 @@ class sshrepository(remoterepository):
         if not r:
             return 1
         return int(r)
+
+    def stream_out(self):
+        return self.do_cmd('stream_out')
--- a/mercurial/sshserver.py
+++ b/mercurial/sshserver.py
@@ -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,8 +60,10 @@ class sshserver(object):
         capabilities: space separated list of tokens
         '''
 
-        r = "capabilities: unbundle\n"
-        self.respond(r)
+        caps = ['unbundle']
+        if self.ui.configbool('server', 'uncompressed'):
+            caps.append('stream=%d' % self.repo.revlogversion)
+        self.respond("capabilities: %s\n" % (' '.join(caps),))
 
     def do_lock(self):
         '''DEPRECATED - allowing remote client to lock repo is not safe'''
@@ -167,3 +169,5 @@ class sshserver(object):
             fp.close()
             os.unlink(tempname)
 
+    def do_stream_out(self):
+        streamclone.stream_out(self.repo, self.fout)
new file mode 100644
--- /dev/null
+++ b/mercurial/streamclone.py
@@ -0,0 +1,90 @@
+# 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()
+    meta.reverse()
+    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().'''
+
+    if not repo.ui.configbool('server', 'uncompressed'):
+        fileobj.write('1\n')
+        return
+
+    fileobj.write('0\n')
+
+    # 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
+++ b/mercurial/util.py
@@ -961,3 +961,24 @@ def rcpath():
         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
+++ b/tests/test-backout
@@ -60,4 +60,40 @@ hg commit -d '2 0' -A -m c
 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
--- a/tests/test-backout.out
+++ b/tests/test-backout.out
@@ -29,3 +29,23 @@ changeset 3:4cbb1e70196a backs out chang
 the backout changeset is a new head - do not forget to merge
 (use "backout -m" if you want to auto-merge)
 b: No such file or directory
+adding a
+adding b
+adding c
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+adding d
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+# backout of merge should fail
+abort: cannot back out a merge changeset without --parent
+# backout of merge with bad parent should fail
+abort: cb9a9f314b8b is not a parent of b2f3bb92043e
+# backout of non-merge with parent should fail
+abort: cannot use --parent on non-merge changeset
+# backout with valid parent should be ok
+removing d
+changeset 5:11fbd9be634c backs out changeset 4:b2f3bb92043e
+rolling back last transaction
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+removing c
+changeset 5:1a5f1a63bf2c backs out changeset 4:b2f3bb92043e
--- a/tests/test-help.out
+++ b/tests/test-help.out
@@ -195,6 +195,7 @@ show changed files in the working direct
     ! = deleted, but still tracked
     ? = not tracked
     I = ignored (not shown by default)
+      = the previous added file was copied from here
 
 aliases: st
 
@@ -207,6 +208,7 @@ options:
  -u --unknown    show only unknown (not tracked) files
  -i --ignored    show ignored files
  -n --no-status  hide status prefix
+ -C --copies     show source of copied files
  -0 --print0     end filenames with NUL, for use with xargs
  -I --include    include names matching the given patterns
  -X --exclude    exclude names matching the given patterns
new file mode 100755
--- /dev/null
+++ b/tests/test-http
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+hg init test
+cd test
+echo foo>foo
+hg commit -A -d '0 0' -m 1
+hg --config server.uncompressed=True serve -p 20059 -d --pid-file=hg1.pid
+cat hg1.pid >> $DAEMON_PIDS
+hg serve -p 20060 -d --pid-file=hg2.pid
+cat hg2.pid >> $DAEMON_PIDS
+cd ..
+
+echo % clone via stream
+http_proxy= hg clone --uncompressed http://localhost:20059/ copy 2>&1 | \
+  sed -e 's/[0-9][0-9.]*/XXX/g'
+cd copy
+hg verify
+
+echo % try to clone via stream, should use pull instead
+http_proxy= hg clone --uncompressed http://localhost:20060/ copy2
+
+echo % clone via pull
+http_proxy= hg clone http://localhost:20059/ copy-pull
+cd copy-pull
+hg verify
--- a/tests/test-http-proxy
+++ b/tests/test-http-proxy
@@ -4,7 +4,7 @@ hg init a
 cd a
 echo a > a
 hg ci -Ama -d '1123456789 0'
-hg serve -p 20059 -d --pid-file=hg.pid
+hg --config server.uncompressed=True serve -p 20059 -d --pid-file=hg.pid
 cat hg.pid >> $DAEMON_PIDS
 
 cd ..
@@ -13,8 +13,18 @@ echo $! > proxy.pid)
 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 --uncompressed 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
+++ b/tests/test-http-proxy.out
@@ -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
new file mode 100644
--- /dev/null
+++ b/tests/test-http.out
@@ -0,0 +1,30 @@
+adding foo
+% 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
+% try to clone via stream, should use pull instead
+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
+% 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
+++ b/tests/test-pull
@@ -11,7 +11,7 @@ hg serve -p 20059 -d --pid-file=hg.pid
 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
+++ b/tests/test-ssh
@@ -27,10 +27,19 @@ hg init remote
 cd remote
 echo this > foo
 hg ci -A -m "init" -d "1000000 0" foo
+echo '[server]' > .hg/hgrc
+echo 'uncompressed = True' >> .hg/hgrc
 
 cd ..
 
-echo "# clone remote"
+echo "# clone remote via stream"
+hg clone -e ./dummyssh --uncompressed 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
+++ b/tests/test-ssh.out
@@ -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 @@ remote: added 1 changesets with 1 change
 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:
--- a/tests/test-status
+++ b/tests/test-status
@@ -32,3 +32,6 @@ echo "hg status:"
 hg status
 echo "hg status modified added removed deleted unknown never-existed ignored:"
 hg status modified added removed deleted unknown never-existed ignored
+hg copy modified copied
+echo "hg status -C:"
+hg status -C
--- a/tests/test-status.out
+++ b/tests/test-status.out
@@ -101,3 +101,10 @@ R removed
 ! deleted
 ? ignored
 ? unknown
+hg status -C:
+A added
+A copied
+  modified
+R removed
+! deleted
+? unknown