Mercurial > hg > hg-git
changeset 490:ac644c0e16d4
Merge master into next.
author | Augie Fackler <raf@durin42.com> |
---|---|
date | Sun, 09 Sep 2012 16:13:02 -0500 |
parents | 5c1d4311440d (current diff) ccd521a1f585 (diff) |
children | 2af7e9b67e20 |
files | hggit/git_handler.py setup.py |
diffstat | 17 files changed, 283 insertions(+), 37 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile +++ b/Makefile @@ -20,8 +20,12 @@ (cd $(CREW) ; $(MAKE) clean ) && \ cd tests && $(PYTHON) $(CREW)/tests/run-tests.py $(TESTFLAGS) -all-version-tests: tests-1.5.4 tests-1.6.4 tests-1.7.5 tests-1.8.4 \ - tests-1.9.3 tests-2.0.2 tests-2.1.2 tests-2.2.3 \ - tests-2.3 tests-tip +# This is intended to be the authoritative list of Hg versions that this +# extension is tested with. Versions prior to the version that ships in the +# latest Ubuntu LTS release (2.0.2 for 12.04 LTS) may be dropped if they +# interfere with new development. The latest released minor version should be +# listed for each major version; earlier minor versions are not needed. +all-version-tests: tests-1.7.5 tests-1.8.4 tests-1.9.3 tests-2.0.2 \ + tests-2.1.2 tests-2.2.3 tests-2.3.1 tests-tip .PHONY: tests all-version-tests
--- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ Usage ===== -You can clone a Git repository from Hg by running `hg clone [url]`. For +You can clone a Git repository from Hg by running `hg clone <url> [dest]`. For example, if you were to run $ hg clone git://github.com/schacon/hg-git.git -hg-git would clone the repository down into the directory 'munger.git', then -convert it to an Hg repository for you. +Hg-Git would clone the repository and convert it to an Hg repository +for you. If you want to clone a github repository for later pushing (or any other repository you access via ssh), you need to convert the ssh url @@ -55,16 +55,16 @@ $ hg clone git+ssh://git@github.com/schacon/hg-git.git -If you are starting from an existing Hg repository, you have to setup -a Git repository somewhere that you have push access to, add it as -default path or default-push path in your .hg/hgrc and then run `hg -push` from within your project. For example: +If you are starting from an existing Hg repository, you have to set up +a Git repository somewhere that you have push access to, add a path entry +for it in your .hg/hgrc file, and then run `hg push [name]` from within +your repository. For example: $ cd hg-git # (an Hg repository) $ # edit .hg/hgrc and add the target git url in the paths section $ hg push -This will convert all your Hg data into Git objects and push them up to the Git server. +This will convert all your Hg data into Git objects and push them to the Git server. Now that you have an Hg repository that can push/pull to/from a Git repository, you can fetch updates with `hg pull`. @@ -140,6 +140,16 @@ That will enable the Hg-Git extension for you. The bookmarks section is not compulsory, but it makes some things a bit nicer for you. +This plugin is currently tested against the following Mercurial versions: + * 1.6.4 + * 1.7.5 + * 1.8.4 + * 1.9.3 + * 2.0.2 + * 2.1.2 + * 2.2.3 + * 2.3 + Configuration =============
--- a/hggit/__init__.py +++ b/hggit/__init__.py @@ -13,8 +13,11 @@ project that is in Git. A bridger of worlds, this plugin be. Try hg clone git:// or hg clone git+ssh:// + +For more information and instructions, see :hg:`help git` ''' +from bisect import insort import inspect import os @@ -22,8 +25,11 @@ from mercurial import commands from mercurial import demandimport from mercurial import extensions +from mercurial import help from mercurial import hg from mercurial import localrepo +from mercurial import revset +from mercurial import templatekw from mercurial import util as hgutil from mercurial import url from mercurial.i18n import _ @@ -87,6 +93,20 @@ if getattr(hg, 'addbranchrevs', False): extensions.wrapfunction(hg, 'addbranchrevs', safebranchrevs) +def extsetup(): + templatekw.keywords.update({'gitnode': gitnodekw}) + revset.symbols.update({ + 'fromgit': revset_fromgit, 'gitnode': revset_gitnode + }) + helpdir = os.path.join(os.path.dirname(__file__), 'help') + entry = (['git'], _("Working with Git Repositories"), + lambda: open(os.path.join(helpdir, 'git.rst')).read()) + # in 1.6 and earler the help table is a tuple + if getattr(help.helptable, 'extend', None): + insort(help.helptable, entry) + else: + help.helptable = help.helptable + (entry,) + def reposetup(ui, repo): if not isinstance(repo, gitrepo.gitrepo): klass = hgrepo.generate_repo_subclass(repo.__class__) @@ -161,6 +181,39 @@ # 1.7+ pass +def revset_fromgit(repo, subset, x): + '''``fromgit()`` + Select changesets that originate from Git. + ''' + args = revset.getargs(x, 0, 0, "fromgit takes no arguments") + git = GitHandler(repo, repo.ui) + return [r for r in subset if git.map_git_get(repo[r].hex()) is not None] + +def revset_gitnode(repo, subset, x): + '''``gitnode(hash)`` + Select changesets that originate in the given Git revision. + ''' + args = revset.getargs(x, 1, 1, "gitnode takes one argument") + rev = revset.getstring(args[0], + "the argument to gitnode() must be a hash") + git = GitHandler(repo, repo.ui) + def matches(r): + gitnode = git.map_git_get(repo[r].hex()) + if gitnode is None: + return False + return rev in [gitnode, gitnode[:12]] + return [r for r in subset if matches(r)] + +def gitnodekw(**args): + """:gitnode: String. The Git changeset identification hash, as a 40 hexadecimal digit string.""" + node = args['ctx'] + repo = args['repo'] + git = GitHandler(repo, repo.ui) + gitnode = git.map_git_get(node.hex()) + if gitnode is None: + gitnode = '' + return gitnode + cmdtable = { "gimport": (gimport, [], _('hg gimport')),
--- a/hggit/git_handler.py +++ b/hggit/git_handler.py @@ -243,15 +243,26 @@ def push(self, remote, revs, force): self.export_commits() - changed_refs = self.upload_pack(remote, revs, force) + old_refs, new_refs = self.upload_pack(remote, revs, force) remote_name = self.remote_name(remote) - if remote_name and changed_refs: - for ref, sha in changed_refs.iteritems(): - self.ui.status(" %s::%s => GIT:%s\n" % - (remote_name, ref, sha[0:8])) + if remote_name and new_refs: + for ref, new_sha in new_refs.iteritems(): + if new_sha != old_refs.get(ref): + self.ui.status(" %s::%s => GIT:%s\n" % + (remote_name, ref, new_sha[0:8])) - self.update_remote_branches(remote_name, changed_refs) + self.update_remote_branches(remote_name, new_refs) + if old_refs == new_refs: + self.ui.status(_("no changes found\n")) + ret = None + elif len(new_refs) > len(old_refs): + ret = 1 + (len(new_refs) - len(old_refs)) + elif len(old_refs) > len(new_refs): + ret = -1 - (len(new_refs) - len(old_refs)) + else: + ret = 1 + return ret def clear(self): mapfile = self.repo.join(self.mapfile) @@ -821,15 +832,17 @@ def upload_pack(self, remote, revs, force): client, path = self.get_transport_and_path(remote) + old_refs = {} def changed(refs): + old_refs.update(refs) to_push = revs or set(self.local_heads().values() + self.tags.values()) 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 + new_refs = client.send_pack(path, changed, genpack) + return old_refs, new_refs except (HangupException, GitProtocolError), e: raise hgutil.Abort(_("git remote error: ") + str(e))
new file mode 100644 --- /dev/null +++ b/hggit/help/git.rst @@ -0,0 +1,82 @@ +Basic Use +--------- + +You can clone a Git repository from Hg by running `hg clone <url> [dest]`. +For example, if you were to run:: + + $ hg clone git://github.com/schacon/hg-git.git + +Hg-Git would clone the repository and convert it to an Hg repository for +you. There are a number of different protocols that can be used for Git +repositories. Examples of Git repository URLs include:: + + https://github.com/schacon/hg-git.git + http://code.google.com/p/guava-libraries + ssh://git@github.com:schacon/hg-git.git + git://github.com/schacon/hg-git.git + +For protocols other than git://, it isn't clear whether these should be +interpreted as Mercurial or Git URLs. Thus, to specify that a URL should +use Git, prepend the URL with "git+". For example, an HTTPS URL would +start with "git+https://". Also, note that Git doesn't require the +specification of the protocol for SSH, but Mercurial does. + +If you are starting from an existing Hg repository, you have to set up a +Git repository somewhere that you have push access to, add a path entry +for it in your .hg/hgrc file, and then run `hg push [name]` from within +your repository. For example:: + + $ cd hg-git # (an Hg repository) + $ # edit .hg/hgrc and add the target Git URL in the paths section + $ hg push + +This will convert all your Hg data into Git objects and push them to the +Git server. + +Pulling new revisions into a repository is the same as from any other +Mercurial source. Within the earlier examples, the following commands are +all equivalent:: + + $ hg pull + $ hg pull default + $ hg pull git://github.com/schacon/hg-git.git + +Git branches are exposed in Hg as bookmarks, while Git remotes are exposed +as Hg local tags. See `hg help bookmarks` and `hg help tags` for further +information. + +Finding and displaying Git revisions +------------------------------------ + +For displaying the Git revision ID, Hg-Git provides a template keyword: + + :gitnode: String. The Git changeset identification hash, as a 40 hexadecimal + digit string. + +For example:: + + $ hg log --template='{rev}:{node|short}:{gitnode|short} {desc}\n' + $ hg log --template='hg: {node}\ngit: {gitnode}\n{date|isodate} {author}\n{desc}\n\n' + +For finding changesets from Git, Hg-Git extends revsets to provide two new +selectors: + + :fromgit: Select changesets that originate from Git. Takes no arguments. + :gitnode: Select changesets that originate in a specific Git revision. Takes + a revision argument. + +For example:: + + $ hg log -r 'fromgit()' + $ hg log -r 'gitnode(84f75b909fc3)' + +Revsets are accepted by several Mercurial commands for specifying revisions. +See ``hg help revsets`` for details. + +Limitations +----------- + +- Cloning/pushing/pulling local Git repositories is not supported (due to + lack of support in Dulwich) +- The `hg incoming` and `hg outgoing` commands are not currently + supported. \ No newline at end of file
--- a/hggit/hgrepo.py +++ b/hggit/hgrepo.py @@ -19,7 +19,7 @@ def push(self, remote, force=False, revs=None, newbranch=None): if isinstance(remote, gitrepo): git = GitHandler(self, self.ui) - git.push(remote.path, revs, force) + return git.push(remote.path, revs, force) else: #pragma: no cover # newbranch was added in 1.6 if newbranch is None:
--- a/setup.py +++ b/setup.py @@ -25,5 +25,6 @@ keywords='hg git mercurial', license='GPLv2', packages=['hggit'], + package_data={ 'hggit': ['help/git.rst'] }, install_requires=['dulwich>=0.8.0'] + extra_req, )
--- a/tests/test-git-tags.out +++ b/tests/test-git-tags.out @@ -19,6 +19,4 @@ pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/tags/beta => GIT:e6f255c6 - default::refs/tags/alpha => GIT:7eeab2ea default::refs/heads/master => GIT:3b7fd1b3
new file mode 100755 --- /dev/null +++ b/tests/test-help @@ -0,0 +1,10 @@ +#!/bin/sh + +# Tests that the various help files are properly registered + +echo "[extensions]" >> $HGRCPATH +echo "hggit=$(echo $(dirname $(dirname $0)))/hggit" >> $HGRCPATH + +hg help | grep 'git' | sed 's/ */ /g' +hg help hggit | grep 'help git' | sed 's/:hg:`help git`/"hg help git"/g' +hg help git | grep 'Working with Git Repositories'
new file mode 100644 --- /dev/null +++ b/tests/test-help.out @@ -0,0 +1,4 @@ + hggit push and pull from a Git server + git Working with Git Repositories +For more information and instructions, see "hg help git" +Working with Git Repositories
--- a/tests/test-hg-author.out +++ b/tests/test-hg-author.out @@ -7,42 +7,34 @@ pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:cffa0e8d pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:2b9ec6a4 pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:fee30180 pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:d1659250 pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:ee985f12 pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:d21e26b4 pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:8c878c97 pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:1e03e913 @ changeset: 8:d3c51ce68cfd | tag: default/master
--- a/tests/test-hg-branch.out +++ b/tests/test-hg-branch.out @@ -7,13 +7,11 @@ pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:05c2bcbe marked working directory as branch gamma pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:296802ef @ changeset: 2:05aed681ccb3 | branch: gamma
--- a/tests/test-hg-tags.out +++ b/tests/test-hg-tags.out @@ -7,7 +7,6 @@ pushing to git://localhost/gitrepo exporting hg objects to git creating and sending data - default::refs/heads/not-master => GIT:7eeab2ea default::refs/tags/alpha => GIT:7eeab2ea default::refs/heads/master => GIT:9a2616b9 @ changeset: 1:d529e9229f6d
new file mode 100755 --- /dev/null +++ b/tests/test-keywords @@ -0,0 +1,54 @@ +#!/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.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` +} + +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' +echo beta > beta +git add beta +commit -m 'add beta' + +cd .. + +hg clone gitrepo hgrepo | grep -v '^updating' +cd hgrepo +echo gamma > gamma +hg add gamma +hg commit -m 'add gamma' + +hg log --template "{rev} {node} {node|short} {gitnode} {gitnode|short}\n" +hg log --template "fromgit {rev}\n" --rev "fromgit()" +hg log --template "gitnode_existsA {rev}\n" --rev "gitnode(9497a4ee62e16ee641860d7677cdb2589ea15554)" +hg log --template "gitnode_existsB {rev}\n" --rev "gitnode(7eeab2ea75ec)" +hg log --template "gitnode_notexists {rev}\n" --rev "gitnode(1234567890ab)"
new file mode 100644 --- /dev/null +++ b/tests/test-keywords.out @@ -0,0 +1,11 @@ +Initialized empty Git repository in gitrepo/.git/ + +importing git objects into hg +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +2 a9da0c7c9bb7574b0f3139ab65cabac7468d6b8d a9da0c7c9bb7 +1 7bcd915dc873c654b822f01b0a39269b2739e86d 7bcd915dc873 9497a4ee62e16ee641860d7677cdb2589ea15554 9497a4ee62e1 +0 3442585be8a60c6cd476bbc4e45755339f2a23ef 3442585be8a6 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 7eeab2ea75ec +fromgit 0 +fromgit 1 +gitnode_existsA 1 +gitnode_existsB 0
--- a/tests/test-push +++ b/tests/test-push @@ -69,6 +69,7 @@ hg book -r 1 beta hg push -r beta +echo [$?] cd .. @@ -88,9 +89,11 @@ cd hgrepo echo % this should fail hg push -r master +echo [$?] echo % ... even with -f hg push -fr master +echo [$?] hg pull # TODO shouldn't need to do this since we're (in theory) pushing master explicitly, @@ -102,8 +105,16 @@ echo % this should also fail hg push -r master +echo [$?] echo % ... but succeed with -f hg push -fr master +echo [$?] + +echo % this should fail, no changes to push +hg push -r master +# This was broken in Mercurial (incorrectly returning 0) until issue3228 was +# fixed in 2.1 +echo [$?] | sed s/0/1/ cd ..
--- a/tests/test-push.out +++ b/tests/test-push.out @@ -7,8 +7,7 @@ exporting hg objects to git creating and sending data default::refs/heads/beta => GIT:cffa0e8d - default::refs/heads/not-master => GIT:7eeab2ea - default::refs/heads/master => GIT:7eeab2ea +[0] % should have two different branches beta cffa0e8 add beta master 7eeab2e add alpha @@ -20,10 +19,12 @@ pushing to git://localhost/gitrepo creating and sending data abort: refs/heads/master changed on the server, please pull and merge before pushing +[255] % ... even with -f pushing to git://localhost/gitrepo creating and sending data abort: refs/heads/master changed on the server, please pull and merge before pushing +[255] pulling from git://localhost/gitrepo importing git objects into hg (run 'hg update' to get a working copy) @@ -45,9 +46,14 @@ pushing to git://localhost/gitrepo creating and sending data abort: pushing refs/heads/master overwrites 72f56395749d +[255] % ... but succeed with -f pushing to git://localhost/gitrepo creating and sending data - default::refs/heads/beta => GIT:cffa0e8d - default::refs/heads/not-master => GIT:7eeab2ea default::refs/heads/master => GIT:cc119202 +[0] +% this should fail, no changes to push +pushing to git://localhost/gitrepo +creating and sending data +no changes found +[1]