Mercurial > hg > hg-git
changeset 408:2dcfd4bbfc1a
Support for hg incoming
author | Brendan Cully <brendan@kublai.com> |
---|---|
date | Tue, 24 May 2011 11:16:45 -0700 |
parents | 3a58fe455b0b |
children | 2e773ed95066 |
files | hggit/__init__.py hggit/git_handler.py hggit/overlay.py tests/test-incoming tests/test-incoming.out |
diffstat | 5 files changed, 489 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/hggit/__init__.py +++ b/hggit/__init__.py @@ -18,6 +18,7 @@ import inspect import os +from mercurial import bundlerepo from mercurial import commands from mercurial import demandimport from mercurial import extensions @@ -154,6 +155,21 @@ except ImportError: pass +def getremotechanges(orig, ui, repo, other, revs, *args, **opts): + if isinstance(other, gitrepo.gitrepo): + git = GitHandler(repo, ui) + r, c, cleanup = git.getremotechanges(other, revs) + # ugh. This is ugly even by mercurial API compatibility standards + if 'onlyheads' not in orig.func_code.co_varnames: + cleanup = None + return r, c, cleanup + return orig(ui, repo, other, revs, *args, **opts) +try: + extensions.wrapfunction(bundlerepo, 'getremotechanges', getremotechanges) +except AttributeError: + # 1.7+ + pass + cmdtable = { "gimport": (gimport, [], _('hg gimport')),
--- a/hggit/git_handler.py +++ b/hggit/git_handler.py @@ -20,6 +20,7 @@ import _ssh import util +from overlay import overlayrepo class GitHandler(object): mapfile = 'git-mapfile' @@ -122,9 +123,9 @@ rn = remote_name or 'default' return 'refs/remotes/' + rn + ref[10:] - modheads = set([refs[k] for k in refs if k.startswith('refs/heads/') - and not k.endswith('^{}') - and refs[k] != oldrefs.get(remoteref(k))]) + modheads = [refs[k] for k in refs if k.startswith('refs/heads/') + and not k.endswith('^{}') + and refs[k] != oldrefs.get(remoteref(k))] if not modheads: self.ui.status(_("no changes found\n")) @@ -190,6 +191,27 @@ if os.path.exists(mapfile): os.remove(mapfile) + # incoming support + def getremotechanges(self, remote, revs): + self.export_commits() + refs = self.fetch_pack(remote.path, revs) + + # refs contains all remote refs. Prune to only those requested. + if revs: + reqrefs = {} + for rev in revs: + for n in ('refs/heads/' + rev, 'refs/tags/' + rev): + if n in refs: + reqrefs[n] = refs[n] + else: + reqrefs = refs + + commits = [bin(c) for c in self.getnewgitcommits(reqrefs)[1]] + + b = overlayrepo(self, commits, refs) + + return (b, commits, lambda: None) + ## CHANGESET CONVERSION METHODS def export_git_objects(self): @@ -374,7 +396,7 @@ yield f, blobid, mode - def import_git_objects(self, remote_name=None, refs=None): + def getnewgitcommits(self, refs=None): self.init_if_missing() # import heads and fetched tags as remote references @@ -427,7 +449,10 @@ done.add(sha) todo.pop() - commits = [commit for commit in commits if not commit in self._map_git] + return convert_list, [commit for commit in commits if not commit in self._map_git] + + def import_git_objects(self, remote_name=None, refs=None): + convert_list, commits = self.getnewgitcommits(refs) # import each of the commits, oldest first total = len(commits) if total:
new file mode 100644 --- /dev/null +++ b/hggit/overlay.py @@ -0,0 +1,254 @@ +# overlay classes for repositories +# unifies access to unimported git objects and committed hg objects +# designed to support incoming +# +# incomplete, implemented on demand + +from mercurial import context +from mercurial.node import bin, hex, nullid + +class overlaymanifest(object): + def __init__(self, repo, sha): + self.repo = repo + self.tree = repo.handler.git.get_object(sha) + self._map = None + self._flagmap = None + + def copy(self): + return overlaymanifest(self.repo, self.tree.id) + + def keys(self): + self.load() + return self._map.keys() + + def flags(self, path): + self.load() + + def hgflag(gitflag): + if gitflag & 0100: + return 'x' + elif gitflag & 020000: + return 'l' + else: + return '' + + return hgflag(self._flagmap[path]) + + def load(self): + if self._map is not None: + return + + self._map = {} + self._flagmap = {} + + def addtree(tree, dirname): + for entry in tree.entries(): + if entry[0] & 040000: + # expand directory + subtree = self.repo.handler.git.get_object(entry[2]) + addtree(subtree, dirname + entry[1] + '/') + else: + path = dirname + entry[1] + self._map[path] = bin(entry[2]) + self._flagmap[path] = entry[0] + + addtree(self.tree, '') + + def __iter__(self): + self.load() + return self._map.__iter__() + + def __getitem__(self, path): + self.load() + return self._map[path] + + def __delitem__(self, path): + del self._map[path] + +class overlayfilectx(object): + def __init__(self, repo, path, fileid=None): + self.repo = repo + self._path = path + self.fileid = fileid + + # this is a hack to skip copy detection + def ancestors(self): + return [self, self] + + def rev(self): + return -1 + + def path(self): + return self._path + + def filelog(self): + return self.fileid + + def data(self): + blob = self.repo.handler.git.get_object(self.fileid) + return blob.data + +class overlaychangectx(context.changectx): + def __init__(self, repo, sha): + self.repo = repo + self.commit = repo.handler.git.get_object(sha) + + def node(self): + return bin(self.commit.id) + + def rev(self): + return self.repo.rev(bin(self.commit.id)) + + def date(self): + return self.commit.author_time, self.commit.author_timezone + + def branch(self): + return 'default' + + def user(self): + return self.commit.author + + def files(self): + return [] + + def extra(self): + return {} + + def description(self): + return self.commit.message + + def parents(self): + return [overlaychangectx(self.repo, sha) for sha in self.commit.parents] + + def manifestnode(self): + return bin(self.commit.tree) + + def hex(self): + return self.commit.id + + def tags(self): + return [] + + def bookmarks(self): + return [] + + def manifest(self): + return overlaymanifest(self.repo, self.commit.tree) + + def filectx(self, path, filelog=None): + mf = self.manifest() + return overlayfilectx(self.repo, path, mf[path]) + + def flags(self, path): + mf = self.manifest() + return mf.flags(path) + + def __nonzero__(self): + return True + +class overlayrevlog(object): + def __init__(self, repo, base): + self.repo = repo + self.base = base + + def parents(self, n): + gitrev = self.repo.revmap.get(n) + if not gitrev: + # we've reached a revision we have + return self.base.parents(n) + commit = self.repo.handler.git.get_object(n) + + def gitorhg(n): + hn = self.repo.handler.map_hg_get(hex(n)) + if hn is not None: + return bin(hn) + return n + + # currently ignores the octopus + p1 = gitorhg(bin(commit.parents[0])) + if len(commit.parents) > 1: + p2 = gitorhg(bin(commit.parents[1])) + else: + p2 = nullid + + return [p1, p2] + + def parentrevs(self, rev): + return [self.rev(p) for p in self.parents(self.node(rev))] + + def node(self, rev): + gitnode = self.repo.nodemap.get(rev) + if gitnode is None: + return self.base.node(rev) + return gitnode + + def rev(self, n): + gitrev = self.repo.revmap.get(n) + if gitrev is None: + return self.base.rev(n) + return gitrev + + def nodesbetween(self, nodelist, revs): + # this is called by pre-1.9 incoming with the nodelist we returned from + # getremotechanges. Just return it back. + return [nodelist] + + def __len__(self): + return len(self.repo.handler.repo) + len(self.repo.revmap) + + +class overlayrepo(object): + def __init__(self, handler, commits, refs): + self.handler = handler + + self.changelog = overlayrevlog(self, handler.repo.changelog) + self.manifest = overlayrevlog(self, handler.repo.manifest) + + # for incoming -p + self.root = handler.repo.root + self.getcwd = handler.repo.getcwd + self.status = handler.repo.status + self.ui = handler.repo.ui + + self.revmap = None + self.nodemap = None + self.refmap = None + self.tagmap = None + + self._makemaps(commits, refs) + + def __getitem__(self, n): + if n not in self.revmap: + return self.handler.repo[n] + return overlaychangectx(self, n) + + def nodebookmarks(self, n): + return self.refmap.get(n, []) + + def nodetags(self, n): + return self.tagmap.get(n, []) + + def rev(self, n): + return self.revmap[n] + + def filectx(self, path, fileid=None): + return overlayfilectx(self, path, fileid=fileid) + + def _makemaps(self, commits, refs): + baserev = self.handler.repo['tip'].rev() + self.revmap = {} + self.nodemap = {} + for i, n in enumerate(commits): + rev = baserev + i + 1 + self.revmap[n] = rev + self.nodemap[rev] = n + + self.refmap = {} + self.tagmap = {} + for ref in refs: + if ref.startswith('refs/heads/'): + refname = ref[11:] + self.refmap.setdefault(bin(refs[ref]), []).append(refname) + elif ref.startswith('refs/tags/'): + tagname = ref[10:] + self.tagmap.setdefault(bin(refs[ref]), []).append(tagname)
new file mode 100755 --- /dev/null +++ b/tests/test-incoming @@ -0,0 +1,93 @@ +#!/bin/sh + +# Fails for some reason, need to investigate +# "$TESTDIR/hghave" git || exit 80 + +# bail if the user does not have dulwich +python -c 'import dulwich, dulwich.repo' || 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)))/hggit" >> $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 "git 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="$DAEMON_PIDS" \ + --detach --reuseaddr \ + --enable=receive-pack + +hg clone git://localhost/gitrepo hgrepo | grep -v '^updating' + +cd hgrepo +hg incoming + +cd ../gitrepo +echo beta > beta +git add beta +commit -m 'add beta' + +cd ../hgrepo +hg incoming + +cd ../gitrepo +git checkout -b b1 HEAD^ +mkdir d +echo gamma > d/gamma +git add d/gamma +commit -m'add d/gamma' +git tag t1 + +echo gamma 2 >> d/gamma +git add d/gamma +commit -m'add d/gamma line 2' + +cd ../hgrepo +hg incoming -p + +echo % incoming -r +hg incoming -r master +hg incoming -r b1 +hg incoming -r t1 + +echo % nothing incoming after pull +hg pull +hg incoming + +echo 'done' \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/tests/test-incoming.out @@ -0,0 +1,96 @@ +Initialized empty Git repository in gitrepo/.git/ + +Counting objects: 3, done. +Total 3 (delta 0), reused 0 (delta 0) +importing git objects into hg +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +comparing with git://localhost/gitrepo +no changes found +comparing with git://localhost/gitrepo +Counting objects: 3, 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) +changeset: 1:9497a4ee62e1 +bookmark: master +user: test <test@example.org> +date: Mon Jan 01 00:00:11 2007 +0000 +summary: add beta + +Switched to a new branch 'b1' +comparing with git://localhost/gitrepo +Counting objects: 8, done. +Compressing objects: 25% (1/4) Compressing objects: 50% (2/4) Compressing objects: 75% (3/4) Compressing objects: 100% (4/4) Compressing objects: 100% (4/4), done. +Total 8 (delta 0), reused 0 (delta 0) +changeset: 1:9497a4ee62e1 +bookmark: master +user: test <test@example.org> +date: Mon Jan 01 00:00:11 2007 +0000 +summary: add beta + +diff -r 3442585be8a6 -r 9497a4ee62e1 beta +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/beta Mon Jan 01 00:00:11 2007 +0000 +@@ -0,0 +1,1 @@ ++beta + +changeset: 2:9865e289be73 +tag: t1 +parent: 0:3442585be8a6 +user: test <test@example.org> +date: Mon Jan 01 00:00:12 2007 +0000 +summary: add d/gamma + +diff -r 3442585be8a6 -r 9865e289be73 d/gamma +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/d/gamma Mon Jan 01 00:00:12 2007 +0000 +@@ -0,0 +1,1 @@ ++gamma + +changeset: 3:5202f48c20c9 +bookmark: b1 +user: test <test@example.org> +date: Mon Jan 01 00:00:13 2007 +0000 +summary: add d/gamma line 2 + +diff -r 9865e289be73 -r 5202f48c20c9 d/gamma +--- a/d/gamma Mon Jan 01 00:00:12 2007 +0000 ++++ b/d/gamma Mon Jan 01 00:00:13 2007 +0000 +@@ -1,1 +1,2 @@ + gamma ++gamma 2 + +% incoming -r +comparing with git://localhost/gitrepo +changeset: 1:9497a4ee62e1 +bookmark: master +user: test <test@example.org> +date: Mon Jan 01 00:00:11 2007 +0000 +summary: add beta + +comparing with git://localhost/gitrepo +changeset: 1:9865e289be73 +tag: t1 +user: test <test@example.org> +date: Mon Jan 01 00:00:12 2007 +0000 +summary: add d/gamma + +changeset: 2:5202f48c20c9 +bookmark: b1 +user: test <test@example.org> +date: Mon Jan 01 00:00:13 2007 +0000 +summary: add d/gamma line 2 + +comparing with git://localhost/gitrepo +changeset: 1:9865e289be73 +tag: t1 +user: test <test@example.org> +date: Mon Jan 01 00:00:12 2007 +0000 +summary: add d/gamma + +% nothing incoming after pull +pulling from git://localhost/gitrepo +importing git objects into hg +(run 'hg heads' to see heads, 'hg merge' to merge) +comparing with git://localhost/gitrepo +no changes found +done