# HG changeset patch # User Abderrahim Kitouni # Date 1248988193 -3600 # Node ID 51e4d6ebbc4049ca551c0e677d6ac76243cb2d78 # Parent f2c2061aacd175ac205975933ed0c9a72c5d1a35 rework pushing to support --rev and --force options diff -r f2c2061aacd1 -r 51e4d6ebbc40 git_handler.py --- a/git_handler.py Tue Jul 28 23:12:38 2009 +0100 +++ b/git_handler.py Thu Jul 30 22:09:53 2009 +0100 @@ -1,6 +1,7 @@ import os, sys, math, urllib, re import toposort +from dulwich.errors import HangupException from dulwich.index import commit_tree from dulwich.objects import Blob, Commit, Tag, Tree, format_timezone from dulwich.pack import create_delta, apply_delta @@ -122,9 +123,9 @@ self.update_references() self.save_map() - def push(self, remote): + def push(self, remote, revs, force): self.export_commits() - changed_refs = self.upload_pack(remote) + changed_refs = self.upload_pack(remote, revs, force) remote_name = self.remote_name(remote) if remote_name and changed_refs: @@ -479,70 +480,54 @@ ## PACK UPLOADING AND FETCHING - def upload_pack(self, remote): + def upload_pack(self, remote, revs, force): client, path = self.get_transport_and_path(remote) - changed = self.get_changed_refs + def changed(refs): + to_push = revs or set(self.local_heads().values() + self.tags.values()) + if not to_push and refs.keys()[0] == 'capabilities^{}': + to_push = [self.repo.lookup('tip')] + return self.get_changed_refs(refs, to_push, force) + genpack = self.git.object_store.generate_pack_contents try: self.ui.status(_("creating and sending data\n")) changed_refs = client.send_pack(path, changed, genpack) return changed_refs - except: - # TODO: remove try/except or do something useful here - raise - - # TODO: for now, we'll just push all heads that match remote heads - # * we should have specified push, tracking branches and --all - # takes a dict of refs:shas from the server and returns what should be - # pushed up - def get_changed_refs(self, refs): - keys = refs.keys() + except HangupException: + raise hgutil.Abort("the remote end hung up unexpectedly") - changed = {} - if not keys: - return None + def get_changed_refs(self, refs, revs, force): + new_refs = refs.copy() + for rev in revs: + ctx = self.repo[rev] + heads = [t for t in ctx.tags() if t in self.local_heads()] + tags = [t for t in ctx.tags() if t in self.tags] - # TODO: this is a huge hack - if keys[0] == 'capabilities^{}': - # nothing on the server yet - first push - if not 'master' in self.repo.tags(): - tip = self.repo.lookup('tip') - changed['refs/heads/master'] = self.map_git_get(hex(tip)) - - for tag, sha in self.tags.iteritems(): - tag_name = 'refs/tags/' + tag - if tag_name not in refs: - changed[tag_name] = self.map_git_get(sha) + if not (heads or tags): + raise hgutil.Abort("revision %s cannot be pushed since" + " it doesn't have a ref" % ctx) - for ref_name in keys: - parts = ref_name.split('/') - if parts[0] == 'refs' and parts[1] == 'heads': - # strip off 'refs/heads' - head = "/".join([v for v in parts[2:]]) - try: - local_ref = self.repo.lookup(head) - remote_ref = self.map_hg_get(refs[ref_name]) - if remote_ref: - remotectx = self.repo[remote_ref] - localctx = self.repo[local_ref] - if remotectx.ancestor(localctx) == remotectx: - # fast forward push - changed[ref_name] = self.map_git_get(hex(local_ref)) - else: - # XXX: maybe abort completely - self.ui.warn('not pushing branch %s, please merge\n'% head) - except RepoError: #pragma: no cover - # remote_ref is not here - pass + for r in heads + tags: + if r in heads: + ref = 'refs/heads/'+r + else: + ref = 'refs/tags/'+r - # Also push any local branches not on the server yet - for head in self.local_heads(): - ref = 'refs/heads/' + head - if not ref in refs: - node = self.repo.lookup(head) - changed[ref] = self.map_git_get(hex(node)) + if ref not in refs: + new_refs[ref] = self.map_git_get(ctx.hex()) + elif new_refs[ref] in self._map_git: + rctx = self.repo[self.map_hg_get(new_refs[ref])] + if rctx.ancestor(ctx) == rctx or force: + new_refs[ref] = self.map_git_get(ctx.hex()) + else: + raise hgutil.Abort("pushing %s overwrites %s" + % (ref, ctx)) + else: + raise hgutil.Abort("%s changed on the server, please pull " + "and merge before pushing" % ref) - return changed + return new_refs + def fetch_pack(self, remote_name): client, path = self.get_transport_and_path(remote_name) @@ -551,6 +536,8 @@ f, commit = self.git.object_store.add_pack() try: return client.fetch_pack(path, determine_wants, graphwalker, f.write, self.ui.status) + except HangupException: + raise hgutil.Abort("the remote end hung up unexpectedly") finally: commit() @@ -562,7 +549,7 @@ # Create a local Git branch name for each # Mercurial bookmark. for key in heads: - self.git.refs['refs/heads/' + key] = heads[key] + self.git.refs['refs/heads/' + key] = self.map_git_get(heads[key]) def export_hg_tags(self): for tag, sha in self.repo.tags().iteritems(): @@ -573,7 +560,7 @@ def local_heads(self): try: bms = bookmarks.parse(self.repo) - return dict([(bm, self.map_git_get(hex(bms[bm]))) for bm in bms]) + return dict([(bm, hex(bms[bm])) for bm in bms]) except AttributeError: #pragma: no cover return {} diff -r f2c2061aacd1 -r 51e4d6ebbc40 hgrepo.py --- a/hgrepo.py Tue Jul 28 23:12:38 2009 +0100 +++ b/hgrepo.py Thu Jul 30 22:09:53 2009 +0100 @@ -20,7 +20,7 @@ def push(self, remote, force=False, revs=None): if isinstance(remote, gitrepo): git = GitHandler(self, self.ui) - git.push(remote.path) + git.push(remote.path, revs, force) else: #pragma: no cover return super(hgrepo, self).push(remote, force, revs) diff -r f2c2061aacd1 -r 51e4d6ebbc40 tests/test-push --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push Thu Jul 30 22:09:53 2009 +0100 @@ -0,0 +1,100 @@ +#!/bin/sh + +# Fails for some reason, need to investigate +# "$TESTDIR/hghave" git || exit 80 + +# bail early if the user is already running git-daemon +echo hi | nc localhost 9418 2>/dev/null && exit 80 + +echo "[extensions]" >> $HGRCPATH +echo "hggit=$(echo $(dirname $(dirname $0)))" >> $HGRCPATH +echo 'hgext.graphlog =' >> $HGRCPATH +echo 'hgext.bookmarks =' >> $HGRCPATH + +GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME +GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL +GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE +GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME +GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL +GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE + +count=10 +commit() +{ + GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000" + GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" + git commit "$@" >/dev/null 2>/dev/null || echo "hg commit error" + count=`expr $count + 1` +} +hgcommit() +{ + HGDATE="2007-01-01 00:00:$count +0000" + hg commit -d "$HGDATE" "$@" >/dev/null 2>/dev/null || echo "hg commit error" + count=`expr $count + 1` +} + +mkdir gitrepo +cd gitrepo +git init | python -c "import sys; print sys.stdin.read().replace('$(dirname $(pwd))/', '')" + +echo alpha > alpha +git add alpha +commit -m "add alpha" + +# dulwich does not presently support local git repos, workaround +cd .. +git daemon --base-path="$(pwd)"\ + --listen=localhost\ + --export-all\ + --pid-file=gitdaemon.pid \ + --detach --reuseaddr \ + --enable=receive-pack + +hg clone git://localhost/gitrepo hgrepo + +cd hgrepo +echo beta > beta +hg add beta +hgcommit -m 'add beta' + + +echo gamma > gamma +hg add gamma +hgcommit -m 'add gamma' + +hg book -r 1 beta +hg push -r beta + +cd .. + +echo % should have two different branches +cd gitrepo +git branch -v + +echo % some more work on master from git +git checkout master +echo delta > delta +git add delta +commit -m "add delta" + +cd .. + +cd hgrepo +echo % this should fail +hg push -r master + +echo % ... even with -f +hg push -fr master + +hg pull +echo % master and default/master should be diferent +hg tags + +echo % this should also fail +hg push -r master + +echo % ... but succeed with -f +hg push -fr master + +cd .. +kill `cat gitdaemon.pid` diff -r f2c2061aacd1 -r 51e4d6ebbc40 tests/test-push.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push.out Thu Jul 30 22:09:53 2009 +0100 @@ -0,0 +1,55 @@ +Initialized empty Git repository in gitrepo/.git/ + +importing Hg objects into Git +Counting objects: 3, done. +Total 3 (delta 0), reused 0 (delta 0) +importing Git objects into Hg +at: 0/1 +updating working directory +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +pushing to git://localhost/gitrepo +importing Hg objects into Git +at: 0/2 +creating and sending data + default::refs/heads/beta => GIT:cffa0e8d + default::refs/heads/master => GIT:7eeab2ea +% should have two different branches + beta cffa0e8 add beta +* master 7eeab2e add alpha +% some more work on master from git +Already on "master" +% this should fail +pushing to git://localhost/gitrepo +importing Hg objects into Git +creating and sending data +abort: refs/heads/master changed on the server, please pull and merge before pushing +% ... even with -f +pushing to git://localhost/gitrepo +importing Hg objects into Git +creating and sending data +abort: refs/heads/master changed on the server, please pull and merge before pushing +pulling from git://localhost/gitrepo +importing Hg objects into Git +Counting objects: 4, done. +Compressing objects: 50% (1/2) Compressing objects: 100% (2/2) Compressing objects: 100% (2/2), done. +Total 3 (delta 0), reused 0 (delta 0) +importing Git objects into Hg +at: 0/1 +(run 'hg update' to get a working copy) +% master and default/master should be diferent +tip 3:1436150b86c2 +default/master 3:1436150b86c2 +master 2:72f56395749d +default/beta 1:0564f526fb0f +beta 1:0564f526fb0f +% this should also fail +pushing to git://localhost/gitrepo +importing Hg objects into Git +creating and sending data +abort: pushing refs/heads/master overwrites 72f56395749d +% ... but succeed with -f +pushing to git://localhost/gitrepo +importing Hg objects into Git +creating and sending data + default::refs/heads/beta => GIT:cffa0e8d + default::refs/heads/master => GIT:cc119202