2009-02-21 05:27:51 +03:00
|
|
|
# git.py - git server bridge
|
|
|
|
#
|
|
|
|
# Copyright 2008 Scott Chacon <schacon at gmail dot com>
|
2009-09-26 06:09:33 +04:00
|
|
|
# also some code (and help) borrowed from durin42
|
2009-02-21 05:27:51 +03:00
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms
|
|
|
|
# of the GNU General Public License, incorporated herein by reference.
|
|
|
|
|
|
|
|
'''push and pull from a Git server
|
|
|
|
|
|
|
|
This extension lets you communicate (push and pull) with a Git server.
|
2009-09-26 06:09:33 +04:00
|
|
|
This way you can use Git hosting for your project or collaborate with a
|
2009-02-21 05:27:51 +03:00
|
|
|
project that is in Git. A bridger of worlds, this plugin be.
|
|
|
|
|
2009-09-26 06:09:33 +04:00
|
|
|
Try hg clone git:// or hg clone git+ssh://
|
2012-08-23 07:39:45 +04:00
|
|
|
|
|
|
|
For more information and instructions, see :hg:`help git`
|
2009-02-21 05:27:51 +03:00
|
|
|
'''
|
|
|
|
|
2012-08-23 07:39:45 +04:00
|
|
|
from bisect import insort
|
2010-07-31 02:24:28 +04:00
|
|
|
import inspect
|
2009-10-08 04:27:00 +04:00
|
|
|
import os
|
|
|
|
|
2011-05-24 22:16:45 +04:00
|
|
|
from mercurial import bundlerepo
|
2010-06-13 06:14:33 +04:00
|
|
|
from mercurial import commands
|
2010-10-29 17:31:42 +04:00
|
|
|
from mercurial import demandimport
|
2013-11-27 18:27:59 +04:00
|
|
|
from mercurial import dirstate
|
2012-09-14 02:47:11 +04:00
|
|
|
from mercurial import discovery
|
2010-06-13 06:14:33 +04:00
|
|
|
from mercurial import extensions
|
2012-08-23 07:39:45 +04:00
|
|
|
from mercurial import help
|
2010-06-13 06:14:33 +04:00
|
|
|
from mercurial import hg
|
2013-11-27 18:27:59 +04:00
|
|
|
from mercurial import ignore
|
2010-06-13 06:14:33 +04:00
|
|
|
from mercurial import localrepo
|
2012-08-23 07:39:45 +04:00
|
|
|
from mercurial import revset
|
2012-08-23 07:39:45 +04:00
|
|
|
from mercurial import templatekw
|
2010-06-13 06:14:33 +04:00
|
|
|
from mercurial import util as hgutil
|
2011-04-05 15:31:28 +04:00
|
|
|
from mercurial import url
|
2009-02-21 05:27:51 +03:00
|
|
|
from mercurial.i18n import _
|
2009-07-23 11:38:20 +04:00
|
|
|
|
2010-10-29 17:31:42 +04:00
|
|
|
demandimport.ignore.extend([
|
|
|
|
'collections',
|
|
|
|
])
|
|
|
|
|
2013-12-14 20:19:25 +04:00
|
|
|
import gitrepo, hgrepo
|
2009-04-24 02:26:10 +04:00
|
|
|
from git_handler import GitHandler
|
2009-02-21 05:27:51 +03:00
|
|
|
|
2013-12-14 20:59:39 +04:00
|
|
|
testedwith = '1.9.3 2.0.2 2.1.2 2.2.3 2.8.1'
|
2012-09-28 06:42:41 +04:00
|
|
|
buglink = 'https://bitbucket.org/durin42/hg-git/issues'
|
|
|
|
|
2009-04-29 11:54:13 +04:00
|
|
|
# support for `hg clone git://github.com/defunkt/facebox.git`
|
2009-06-02 07:17:11 +04:00
|
|
|
# also hg clone git+ssh://git@github.com/schacon/simplegit.git
|
2011-10-06 00:44:29 +04:00
|
|
|
_gitschemes = ('git', 'git+ssh', 'git+http', 'git+https')
|
|
|
|
for _scheme in _gitschemes:
|
|
|
|
hg.schemes[_scheme] = gitrepo
|
2009-06-04 21:07:05 +04:00
|
|
|
|
2009-10-08 04:27:00 +04:00
|
|
|
# support for `hg clone localgitrepo`
|
2009-07-23 11:38:20 +04:00
|
|
|
_oldlocal = hg.schemes['file']
|
|
|
|
|
2011-05-15 21:26:03 +04:00
|
|
|
try:
|
|
|
|
urlcls = hgutil.url
|
|
|
|
except AttributeError:
|
|
|
|
class urlcls(object):
|
|
|
|
def __init__(self, path):
|
|
|
|
self.p = hgutil.drop_scheme('file', path)
|
|
|
|
|
|
|
|
def localpath(self):
|
|
|
|
return self.p
|
|
|
|
|
2009-07-23 11:38:20 +04:00
|
|
|
def _local(path):
|
2011-05-15 21:26:03 +04:00
|
|
|
p = urlcls(path).localpath()
|
2010-01-12 05:46:52 +03:00
|
|
|
if (os.path.exists(os.path.join(p, '.git')) and
|
2009-10-08 04:27:00 +04:00
|
|
|
not os.path.exists(os.path.join(p, '.hg'))):
|
2009-07-23 11:38:20 +04:00
|
|
|
return gitrepo
|
2009-10-26 19:52:32 +03:00
|
|
|
# detect a bare repository
|
2010-01-12 05:46:52 +03:00
|
|
|
if (os.path.exists(os.path.join(p, 'HEAD')) and
|
|
|
|
os.path.exists(os.path.join(p, 'objects')) and
|
|
|
|
os.path.exists(os.path.join(p, 'refs')) and
|
2009-10-26 19:52:32 +03:00
|
|
|
not os.path.exists(os.path.join(p, '.hg'))):
|
|
|
|
return gitrepo
|
2009-10-08 04:27:00 +04:00
|
|
|
return _oldlocal(path)
|
2009-07-23 11:38:20 +04:00
|
|
|
|
|
|
|
hg.schemes['file'] = _local
|
|
|
|
|
2010-01-12 05:48:43 +03:00
|
|
|
hgdefaultdest = hg.defaultdest
|
|
|
|
def defaultdest(source):
|
2011-10-06 00:44:29 +04:00
|
|
|
for scheme in _gitschemes:
|
2010-01-12 05:48:43 +03:00
|
|
|
if source.startswith('%s://' % scheme) and source.endswith('.git'):
|
|
|
|
source = source[:-4]
|
|
|
|
break
|
|
|
|
return hgdefaultdest(source)
|
|
|
|
hg.defaultdest = defaultdest
|
|
|
|
|
2010-03-26 04:23:00 +03:00
|
|
|
# defend against tracebacks if we specify -r in 'hg pull'
|
|
|
|
def safebranchrevs(orig, lrepo, repo, branches, revs):
|
|
|
|
revs, co = orig(lrepo, repo, branches, revs)
|
|
|
|
if getattr(lrepo, 'changelog', False) and co not in lrepo.changelog:
|
|
|
|
co = None
|
|
|
|
return revs, co
|
2010-03-28 06:37:58 +04:00
|
|
|
if getattr(hg, 'addbranchrevs', False):
|
|
|
|
extensions.wrapfunction(hg, 'addbranchrevs', safebranchrevs)
|
2010-03-26 04:23:00 +03:00
|
|
|
|
2012-08-23 07:39:45 +04:00
|
|
|
def extsetup():
|
2012-08-23 07:39:45 +04:00
|
|
|
templatekw.keywords.update({'gitnode': gitnodekw})
|
2012-08-23 07:39:45 +04:00
|
|
|
revset.symbols.update({
|
|
|
|
'fromgit': revset_fromgit, 'gitnode': revset_gitnode
|
|
|
|
})
|
2012-08-23 07:39:45 +04:00
|
|
|
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,)
|
|
|
|
|
2009-07-02 00:31:35 +04:00
|
|
|
def reposetup(ui, repo):
|
2010-03-26 04:23:00 +03:00
|
|
|
if not isinstance(repo, gitrepo.gitrepo):
|
|
|
|
klass = hgrepo.generate_repo_subclass(repo.__class__)
|
|
|
|
repo.__class__ = klass
|
2009-04-29 11:54:13 +04:00
|
|
|
|
2009-05-15 02:48:24 +04:00
|
|
|
def gimport(ui, repo, remote_name=None):
|
2014-02-20 03:12:20 +04:00
|
|
|
repo.githandler.import_commits(remote_name)
|
2009-05-15 02:48:24 +04:00
|
|
|
|
2009-05-10 17:14:36 +04:00
|
|
|
def gexport(ui, repo):
|
2014-02-20 03:12:42 +04:00
|
|
|
repo.githandler.export_commits()
|
2009-05-10 17:14:36 +04:00
|
|
|
|
2009-04-29 03:56:05 +04:00
|
|
|
def gclear(ui, repo):
|
|
|
|
repo.ui.status(_("clearing out the git cache data\n"))
|
2014-02-20 03:12:59 +04:00
|
|
|
repo.githandler.clear()
|
2013-12-14 20:19:25 +04:00
|
|
|
|
|
|
|
from mercurial import dirstate
|
|
|
|
from mercurial import ignore
|
|
|
|
if (getattr(dirstate, 'rootcache', False) and
|
|
|
|
getattr(ignore, 'readpats', False)):
|
|
|
|
# only install our dirstate wrapper if it has a hope of working
|
|
|
|
import gitdirstate
|
|
|
|
extensions.wrapfunction(ignore, 'ignore', gitdirstate.gignore)
|
|
|
|
dirstate.dirstate = gitdirstate.gitdirstate
|
2009-04-30 00:55:22 +04:00
|
|
|
|
2010-03-26 04:24:00 +03:00
|
|
|
def git_cleanup(ui, repo):
|
|
|
|
new_map = []
|
|
|
|
for line in repo.opener(GitHandler.mapfile):
|
|
|
|
gitsha, hgsha = line.strip().split(' ', 1)
|
|
|
|
if hgsha in repo:
|
|
|
|
new_map.append('%s %s\n' % (gitsha, hgsha))
|
|
|
|
f = repo.opener(GitHandler.mapfile, 'wb')
|
|
|
|
map(f.write, new_map)
|
|
|
|
ui.status(_('git commit map cleaned\n'))
|
|
|
|
|
2010-06-13 06:14:33 +04:00
|
|
|
# drop this when we're 1.6-only, this just backports new behavior
|
|
|
|
def sortednodetags(orig, *args, **kwargs):
|
|
|
|
ret = orig(*args, **kwargs)
|
|
|
|
ret.sort()
|
|
|
|
return ret
|
|
|
|
extensions.wrapfunction(localrepo.localrepository, 'nodetags', sortednodetags)
|
|
|
|
|
2012-09-14 02:47:11 +04:00
|
|
|
def findcommonoutgoing(orig, repo, other, *args, **kwargs):
|
|
|
|
if isinstance(other, gitrepo.gitrepo):
|
|
|
|
git = GitHandler(repo, repo.ui)
|
|
|
|
heads = git.get_refs(other.path)[0]
|
|
|
|
kw = {}
|
|
|
|
kw.update(kwargs)
|
|
|
|
for val, k in zip(args,
|
|
|
|
('onlyheads', 'force', 'commoninc', 'portable')):
|
|
|
|
kw[k] = val
|
|
|
|
force = kw.get('force', False)
|
|
|
|
commoninc = kw.get('commoninc', None)
|
|
|
|
if commoninc is None:
|
|
|
|
commoninc = discovery.findcommonincoming(repo, other,
|
|
|
|
heads=heads, force=force)
|
|
|
|
kw['commoninc'] = commoninc
|
|
|
|
return orig(repo, other, **kw)
|
|
|
|
return orig(repo, other, *args, **kwargs)
|
|
|
|
extensions.wrapfunction(discovery, 'findcommonoutgoing', findcommonoutgoing)
|
2010-06-13 06:49:14 +04:00
|
|
|
|
2011-06-17 20:23:52 +04:00
|
|
|
def getremotechanges(orig, ui, repo, other, *args, **opts):
|
2011-05-24 22:16:45 +04:00
|
|
|
if isinstance(other, gitrepo.gitrepo):
|
2011-09-10 00:42:24 +04:00
|
|
|
if args:
|
|
|
|
revs = args[0]
|
|
|
|
else:
|
|
|
|
revs = opts.get('onlyheads', opts.get('revs'))
|
2011-05-24 22:16:45 +04:00
|
|
|
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
|
2011-06-17 20:23:52 +04:00
|
|
|
return orig(ui, repo, other, *args, **opts)
|
2011-05-24 22:16:45 +04:00
|
|
|
try:
|
|
|
|
extensions.wrapfunction(bundlerepo, 'getremotechanges', getremotechanges)
|
|
|
|
except AttributeError:
|
|
|
|
# 1.7+
|
|
|
|
pass
|
|
|
|
|
2012-10-26 03:54:05 +04:00
|
|
|
def peer(orig, uiorrepo, *args, **opts):
|
|
|
|
newpeer = orig(uiorrepo, *args, **opts)
|
|
|
|
if isinstance(newpeer, gitrepo.gitrepo):
|
|
|
|
if isinstance(uiorrepo, localrepo.localrepository):
|
|
|
|
newpeer.localrepo = uiorrepo
|
|
|
|
return newpeer
|
|
|
|
extensions.wrapfunction(hg, 'peer', peer)
|
|
|
|
|
2012-08-23 07:39:45 +04:00
|
|
|
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)]
|
|
|
|
|
2012-08-23 07:39:45 +04:00
|
|
|
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
|
|
|
|
|
2009-02-21 05:27:51 +03:00
|
|
|
cmdtable = {
|
2009-05-15 02:48:24 +04:00
|
|
|
"gimport":
|
|
|
|
(gimport, [], _('hg gimport')),
|
2009-05-10 17:14:36 +04:00
|
|
|
"gexport":
|
|
|
|
(gexport, [], _('hg gexport')),
|
2009-04-29 03:56:05 +04:00
|
|
|
"gclear":
|
|
|
|
(gclear, [], _('Clears out the Git cached data')),
|
2010-03-26 04:24:00 +03:00
|
|
|
"git-cleanup": (git_cleanup, [], _(
|
|
|
|
"Cleans up git repository after history editing"))
|
2009-06-08 22:15:58 +04:00
|
|
|
}
|