mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
initial import of the hg-git bridging extension for mercurial
This commit is contained in:
commit
01bddec68d
124
__init__.py
Normal file
124
__init__.py
Normal file
@ -0,0 +1,124 @@
|
||||
# git.py - git server bridge
|
||||
#
|
||||
# Copyright 2008 Scott Chacon <schacon at gmail dot com>
|
||||
# also some code (and help) borrowed from durin42
|
||||
#
|
||||
# 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.
|
||||
This way you can use Git hosting for your project or collaborate with a
|
||||
project that is in Git. A bridger of worlds, this plugin be.
|
||||
|
||||
'''
|
||||
|
||||
#
|
||||
# Stage One - use Git commands to do the import / pushes, all in one big uggo file
|
||||
#
|
||||
# Stage Two - implement the Git packfile generation and server communication
|
||||
# in native Python, so we don't need Git locally and don't need
|
||||
# to keep all the git repo data around. We should just need a SHA
|
||||
# mapping - since everything is append only in both systems it should
|
||||
# be pretty simple to do.
|
||||
#
|
||||
|
||||
# just importing every damn thing because i don't know python that well
|
||||
# and I have no idea what I actually need
|
||||
from mercurial import util, repair, merge, cmdutil, commands, error, hg, url
|
||||
from mercurial import extensions, ancestor
|
||||
from mercurial.commands import templateopts
|
||||
from mercurial.node import nullrev, nullid, short
|
||||
from mercurial.i18n import _
|
||||
import os, errno
|
||||
import subprocess
|
||||
|
||||
def gclone(ui, git_url, hg_repo_path=None):
|
||||
## TODO : add git_url as the default remote path
|
||||
if not hg_repo_path:
|
||||
hg_repo_path = hg.defaultdest(git_url)
|
||||
if hg_repo_path.endswith('.git'):
|
||||
hg_repo_path = hg_repo_path[:-4]
|
||||
hg_repo_path += '-hg'
|
||||
subprocess.call(['hg', 'init', hg_repo_path])
|
||||
clone_git(git_url, git_path(hg_repo_path))
|
||||
import_git_heads(hg_repo_path)
|
||||
|
||||
def gpull(ui, repo, source='default', **opts):
|
||||
"""fetch from a git repo
|
||||
"""
|
||||
lock = wlock = None
|
||||
try:
|
||||
lock = repo.lock()
|
||||
wlock = repo.wlock()
|
||||
ui.write("fetching from the remote\n")
|
||||
git_fetch()
|
||||
import_git_heads()
|
||||
# do the pull
|
||||
finally:
|
||||
del lock, wlock
|
||||
|
||||
def gpush(ui, repo, dest='default', **opts):
|
||||
"""push to a git repo
|
||||
"""
|
||||
lock = wlock = None
|
||||
try:
|
||||
lock = repo.lock()
|
||||
wlock = repo.wlock()
|
||||
ui.write("pushing to the remote\n")
|
||||
# do the push
|
||||
finally:
|
||||
del lock, wlock
|
||||
|
||||
def git_path(hg_path=None):
|
||||
return os.path.join(hg_path, '.hg', 'git-remote')
|
||||
|
||||
def clone_git(git_url, hg_path=None):
|
||||
git_initialize(git_path(hg_path), git_url)
|
||||
git_fetch(git_path(hg_path))
|
||||
|
||||
def git_initialize(git_path, git_url):
|
||||
# TODO: implement this in pure python - should be strait-forward
|
||||
subprocess.call(['git', '--bare', 'init', git_path])
|
||||
oldwd = os.getcwd()
|
||||
os.chdir(git_path)
|
||||
subprocess.call(['git', 'remote', 'add', 'origin', git_url])
|
||||
os.chdir(oldwd)
|
||||
|
||||
def git_fetch(git_path, remote='origin'):
|
||||
# TODO: implement this in pure python
|
||||
# - we'll have to handle ssh and git
|
||||
oldwd = os.getcwd()
|
||||
os.chdir(git_path)
|
||||
subprocess.call(['git', 'fetch', remote])
|
||||
os.chdir(oldwd)
|
||||
|
||||
def git_push():
|
||||
# find all the local changesets that aren't mapped
|
||||
# create git commit object shas and map them
|
||||
# stick those objects in a packfile and push them up (over ssh)
|
||||
|
||||
def import_git_heads(hg_path=None):
|
||||
# go through each branch
|
||||
# add all commits we don't have locally
|
||||
# write a SHA<->SHA mapping table
|
||||
# update the local branches to match
|
||||
return subprocess.call(['hg', 'convert', git_path(hg_path), hg_path])
|
||||
|
||||
|
||||
commands.norepo += " gclone"
|
||||
cmdtable = {
|
||||
"gclone":
|
||||
(gclone,
|
||||
[ #('A', 'authors', '', 'username mapping filename'),
|
||||
],
|
||||
'Clone a git repository into an hg repository.',
|
||||
),
|
||||
"gpush":
|
||||
(gpush,
|
||||
[('m', 'merge', None, _('merge automatically'))],
|
||||
_('hg gpush remote')),
|
||||
"gpull":
|
||||
(gpull, [], _('hg gpull [--merge] remote')),
|
||||
}
|
146
git.py
Normal file
146
git.py
Normal file
@ -0,0 +1,146 @@
|
||||
# git support for the convert extension
|
||||
|
||||
import os
|
||||
from mercurial import util
|
||||
|
||||
from common import NoRepo, commit, converter_source, checktool
|
||||
|
||||
class git_tool:
|
||||
# Windows does not support GIT_DIR= construct while other systems
|
||||
# cannot remove environment variable. Just assume none have
|
||||
# both issues.
|
||||
if hasattr(os, 'unsetenv'):
|
||||
def gitcmd(self, s):
|
||||
prevgitdir = os.environ.get('GIT_DIR')
|
||||
os.environ['GIT_DIR'] = self.path
|
||||
try:
|
||||
return util.popen(s, 'rb')
|
||||
finally:
|
||||
if prevgitdir is None:
|
||||
del os.environ['GIT_DIR']
|
||||
else:
|
||||
os.environ['GIT_DIR'] = prevgitdir
|
||||
else:
|
||||
def gitcmd(self, s):
|
||||
return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
|
||||
|
||||
def __init__(self, ui, path, rev=None):
|
||||
super(convert_git, self).__init__(ui, path, rev=rev)
|
||||
|
||||
if os.path.isdir(path + "/.git"):
|
||||
path += "/.git"
|
||||
if not os.path.exists(path + "/objects"):
|
||||
raise NoRepo("%s does not look like a Git repo" % path)
|
||||
|
||||
checktool('git', 'git')
|
||||
|
||||
self.path = path
|
||||
|
||||
def getheads(self):
|
||||
if not self.rev:
|
||||
return self.gitcmd('git rev-parse --branches --remotes').read().splitlines()
|
||||
else:
|
||||
fh = self.gitcmd("git rev-parse --verify %s" % self.rev)
|
||||
return [fh.read()[:-1]]
|
||||
|
||||
def catfile(self, rev, type):
|
||||
if rev == "0" * 40: raise IOError()
|
||||
fh = self.gitcmd("git cat-file %s %s" % (type, rev))
|
||||
return fh.read()
|
||||
|
||||
def getfile(self, name, rev):
|
||||
return self.catfile(rev, "blob")
|
||||
|
||||
def getmode(self, name, rev):
|
||||
return self.modecache[(name, rev)]
|
||||
|
||||
def getchanges(self, version):
|
||||
self.modecache = {}
|
||||
fh = self.gitcmd("git diff-tree -z --root -m -r %s" % version)
|
||||
changes = []
|
||||
seen = {}
|
||||
entry = None
|
||||
for l in fh.read().split('\x00'):
|
||||
if not entry:
|
||||
if not l.startswith(':'):
|
||||
continue
|
||||
entry = l
|
||||
continue
|
||||
f = l
|
||||
if f not in seen:
|
||||
seen[f] = 1
|
||||
entry = entry.split()
|
||||
h = entry[3]
|
||||
p = (entry[1] == "100755")
|
||||
s = (entry[1] == "120000")
|
||||
self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
|
||||
changes.append((f, h))
|
||||
entry = None
|
||||
return (changes, {})
|
||||
|
||||
def getcommit(self, version):
|
||||
c = self.catfile(version, "commit") # read the commit hash
|
||||
end = c.find("\n\n")
|
||||
message = c[end+2:]
|
||||
message = self.recode(message)
|
||||
l = c[:end].splitlines()
|
||||
manifest = l[0].split()[1]
|
||||
parents = []
|
||||
for e in l[1:]:
|
||||
n, v = e.split(" ", 1)
|
||||
if n == "author":
|
||||
p = v.split()
|
||||
tm, tz = p[-2:]
|
||||
author = " ".join(p[:-2])
|
||||
if author[0] == "<": author = author[1:-1]
|
||||
author = self.recode(author)
|
||||
if n == "committer":
|
||||
p = v.split()
|
||||
tm, tz = p[-2:]
|
||||
committer = " ".join(p[:-2])
|
||||
if committer[0] == "<": committer = committer[1:-1]
|
||||
committer = self.recode(committer)
|
||||
message += "\ncommitter: %s\n" % committer
|
||||
if n == "parent": parents.append(v)
|
||||
|
||||
tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
|
||||
tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
|
||||
date = tm + " " + str(tz)
|
||||
|
||||
c = commit(parents=parents, date=date, author=author, desc=message,
|
||||
rev=version)
|
||||
return c
|
||||
|
||||
def gettags(self):
|
||||
tags = {}
|
||||
fh = self.gitcmd('git ls-remote --tags "%s"' % self.path)
|
||||
prefix = 'refs/tags/'
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line.endswith("^{}"):
|
||||
continue
|
||||
node, tag = line.split(None, 1)
|
||||
if not tag.startswith(prefix):
|
||||
continue
|
||||
tag = tag[len(prefix):-3]
|
||||
tags[tag] = node
|
||||
|
||||
return tags
|
||||
|
||||
def getchangedfiles(self, version, i):
|
||||
changes = []
|
||||
if i is None:
|
||||
fh = self.gitcmd("git diff-tree --root -m -r %s" % version)
|
||||
for l in fh:
|
||||
if "\t" not in l:
|
||||
continue
|
||||
m, f = l[:-1].split("\t")
|
||||
changes.append(f)
|
||||
fh.close()
|
||||
else:
|
||||
fh = self.gitcmd('git diff-tree --name-only --root -r %s "%s^%s" --'
|
||||
% (version, version, i+1))
|
||||
changes = [f.rstrip('\n') for f in fh]
|
||||
fh.close()
|
||||
|
||||
return changes
|
Loading…
Reference in New Issue
Block a user