changeset 2440:cc1011a7ed09

merge with self.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Thu, 15 Jun 2006 16:38:23 -0700
parents a765f853439d (current diff) e8c4f3d3df8c (diff)
children 801dfe0aa53a
files mercurial/sshrepo.py
diffstat 6 files changed, 156 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/httprepo.py
+++ b/mercurial/httprepo.py
@@ -71,6 +71,7 @@ def netlocunsplit(host, port, user=None,
 
 class httprepository(remoterepository):
     def __init__(self, ui, path):
+        self.capabilities = ()
         scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
         if query or frag:
             raise util.Abort(_('unsupported URL component: "%s"') %
@@ -234,5 +235,8 @@ class httprepository(remoterepository):
 
         return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
 
+    def unbundle(self, cg, heads, source):
+        raise util.Abort(_('operation not supported over http'))
+
 class httpsrepository(httprepository):
     pass
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -15,6 +15,8 @@ demandload(globals(), "re lock transacti
 demandload(globals(), "revlog")
 
 class localrepository(object):
+    capabilities = ()
+
     def __del__(self):
         self.transhandle = None
     def __init__(self, parentui, path=None, create=0):
@@ -1105,8 +1107,20 @@ class localrepository(object):
         return self.addchangegroup(cg, 'pull')
 
     def push(self, remote, force=False, revs=None):
-        lock = remote.lock()
+        # there are two ways to push to remote repo:
+        #
+        # addchangegroup assumes local user can lock remote
+        # repo (local filesystem, old ssh servers).
+        #
+        # unbundle assumes local user cannot lock remote repo (new ssh
+        # servers, http servers).
 
+        if 'unbundle' in remote.capabilities:
+            self.push_unbundle(remote, force, revs)
+        else:
+            self.push_addchangegroup(remote, force, revs)
+
+    def prepush(self, remote, force, revs):
         base = {}
         remote_heads = remote.heads()
         inc = self.findincoming(remote, base, remote_heads, force=force)
@@ -1114,7 +1128,7 @@ class localrepository(object):
             self.ui.warn(_("abort: unsynced remote changes!\n"))
             self.ui.status(_("(did you forget to sync?"
                              " use push -f to force)\n"))
-            return 1
+            return None, 1
 
         update, updated_heads = self.findoutgoing(remote, base, remote_heads)
         if revs is not None:
@@ -1124,7 +1138,7 @@ class localrepository(object):
 
         if not bases:
             self.ui.status(_("no changes found\n"))
-            return 1
+            return None, 1
         elif not force:
             # FIXME we don't properly detect creation of new heads
             # in the push -r case, assume the user knows what he's doing
@@ -1133,13 +1147,35 @@ class localrepository(object):
                 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
+                return None, 1
 
         if revs is None:
             cg = self.changegroup(update, 'push')
         else:
             cg = self.changegroupsubset(update, revs, 'push')
-        return remote.addchangegroup(cg, 'push')
+        return cg, remote_heads
+
+    def push_addchangegroup(self, remote, force, revs):
+        lock = remote.lock()
+
+        ret = self.prepush(remote, force, revs)
+        if ret[0] is not None:
+            cg, remote_heads = ret
+            return remote.addchangegroup(cg, 'push')
+        return ret[1]
+
+    def push_unbundle(self, remote, force, revs):
+        # local repo finds heads on server, finds out what revs it
+        # must push.  once revs transferred, if server finds it has
+        # different heads (someone else won commit/push race), server
+        # aborts.
+
+        ret = self.prepush(remote, force, revs)
+        if ret[0] is not None:
+            cg, remote_heads = ret
+            if force: remote_heads = ['force']
+            return remote.unbundle(cg, remote_heads, 'push')
+        return ret[1]
 
     def changegroupsubset(self, bases, heads, source):
         """This function generates a changegroup consisting of all the nodes
--- a/mercurial/sshrepo.py
+++ b/mercurial/sshrepo.py
@@ -141,11 +141,36 @@ class sshrepository(remoterepository):
         f = self.do_cmd("changegroup", roots=n)
         return self.pipei
 
+    def unbundle(self, cg, heads, source):
+        d = self.call("unbundle", heads=' '.join(map(hex, heads)))
+        if d:
+            raise hg.RepoError(_("push refused: %s") % d)
+
+        while 1:
+            d = cg.read(4096)
+            if not d: break
+            self.pipeo.write(str(len(d)) + '\n')
+            self.pipeo.write(d)
+            self.readerr()
+
+        self.pipeo.write('0\n')
+        self.pipeo.flush()
+
+        self.readerr()
+        d = self.pipei.readline()
+        if d != '\n':
+            return 1
+
+        l = int(self.pipei.readline())
+        r = self.pipei.read(l)
+        if not r:
+            return 1
+        return int(r)
+
     def addchangegroup(self, cg, source):
         d = self.call("addchangegroup")
         if d:
             raise hg.RepoError(_("push refused: %s") % d)
-
         while 1:
             d = cg.read(4096)
             if not d: break
--- 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(), "sys util")
+demandload(globals(), "os sys tempfile util")
 
 class sshserver(object):
     def __init__(self, ui, repo):
@@ -60,14 +60,18 @@ class sshserver(object):
         capabilities: space separated list of tokens
         '''
 
-        r = "capabilities:\n"
+        r = "capabilities: unbundle\n"
         self.respond(r)
 
     def do_lock(self):
+        '''DEPRECATED - allowing remote client to lock repo is not safe'''
+
         self.lock = self.repo.lock()
         self.respond("")
 
     def do_unlock(self):
+        '''DEPRECATED'''
+
         if self.lock:
             self.lock.release()
         self.lock = None
@@ -104,6 +108,8 @@ class sshserver(object):
         self.fout.flush()
 
     def do_addchangegroup(self):
+        '''DEPRECATED'''
+
         if not self.lock:
             self.respond("not locked")
             return
@@ -111,3 +117,53 @@ class sshserver(object):
         self.respond("")
         r = self.repo.addchangegroup(self.fin, 'serve')
         self.respond(str(r))
+
+    def do_unbundle(self):
+        their_heads = self.getarg()[1].split()
+
+        def check_heads():
+            heads = map(hex, self.repo.heads())
+            return their_heads == [hex('force')] or their_heads == heads
+
+        # fail early if possible
+        if not check_heads():
+            self.respond(_('unsynced changes'))
+            return
+
+        self.respond('')
+
+        # write bundle data to temporary file because it can be big
+
+        try:
+            fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
+            fp = os.fdopen(fd, 'wb+')
+
+            count = int(self.fin.readline())
+            while count:
+                fp.write(self.fin.read(count))
+                count = int(self.fin.readline())
+
+            was_locked = self.lock is not None
+            if not was_locked:
+                self.lock = self.repo.lock()
+            try:
+                if not check_heads():
+                    # someone else committed/pushed/unbundled while we
+                    # were transferring data
+                    self.respond(_('unsynced changes'))
+                    return
+                self.respond('')
+
+                # push can proceed
+
+                fp.seek(0)
+                r = self.repo.addchangegroup(fp, 'serve')
+                self.respond(str(r))
+            finally:
+                if not was_locked:
+                    self.lock.release()
+                    self.lock = None
+        finally:
+            fp.close()
+            os.unlink(tempname)
+
--- a/tests/test-ssh
+++ b/tests/test-ssh
@@ -66,5 +66,18 @@ hg tip
 hg verify
 hg cat foo
 
+echo z > z
+hg ci -A -m z -d '1000001 0' z
+
+cd ../local
+echo r > r
+hg ci -A -m z -d '1000002 0' r
+
+echo "# push should fail"
+hg push
+
+echo "# push should succeed"
+hg push -f
+
 cd ..
 cat dummylog
--- a/tests/test-ssh.out
+++ b/tests/test-ssh.out
@@ -55,8 +55,22 @@ crosschecking files in changesets and ma
 checking files
 1 files, 2 changesets, 2 total revisions
 bleah
+# push should fail
+pushing to ssh://user@dummy/remote
+searching for changes
+abort: unsynced remote changes!
+(did you forget to sync? use push -f to force)
+# push should succeed
+pushing to ssh://user@dummy/remote
+searching for changes
+remote: adding changesets
+remote: adding manifests
+remote: adding file changes
+remote: added 1 changesets with 1 changes to 1 files
 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:
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5: