2009-04-25 01:05:50 +04:00
|
|
|
import os, errno, sys, time, datetime, pickle, copy
|
2009-04-24 02:26:10 +04:00
|
|
|
import dulwich
|
|
|
|
from dulwich.repo import Repo
|
|
|
|
from dulwich.client import SimpleFetchGraphWalker
|
2009-04-27 01:49:38 +04:00
|
|
|
from dulwich.objects import hex_to_sha
|
2009-04-24 02:26:10 +04:00
|
|
|
from hgext import bookmarks
|
|
|
|
from mercurial.i18n import _
|
2009-04-24 22:45:10 +04:00
|
|
|
from mercurial.node import bin, hex, nullid
|
|
|
|
from mercurial import hg, util, context, error
|
2009-04-24 02:26:10 +04:00
|
|
|
|
|
|
|
class GitHandler(object):
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-24 02:26:10 +04:00
|
|
|
def __init__(self, dest_repo, ui):
|
|
|
|
self.repo = dest_repo
|
|
|
|
self.ui = ui
|
2009-04-27 04:23:06 +04:00
|
|
|
self.init_if_missing()
|
2009-04-25 01:05:50 +04:00
|
|
|
self.load_git()
|
|
|
|
self.load_map()
|
2009-04-27 03:25:04 +04:00
|
|
|
self.load_config()
|
2009-04-27 05:27:47 +04:00
|
|
|
|
|
|
|
# make the git data directory
|
2009-04-27 04:23:06 +04:00
|
|
|
def init_if_missing(self):
|
|
|
|
git_hg_path = os.path.join(self.repo.path, 'git')
|
2009-04-27 04:56:16 +04:00
|
|
|
if not os.path.exists(git_hg_path):
|
|
|
|
os.mkdir(git_hg_path)
|
|
|
|
dulwich.repo.Repo.init_bare(git_hg_path)
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-25 01:05:50 +04:00
|
|
|
def load_git(self):
|
2009-04-24 02:26:10 +04:00
|
|
|
git_dir = os.path.join(self.repo.path, 'git')
|
|
|
|
self.git = Repo(git_dir)
|
2009-04-25 01:05:50 +04:00
|
|
|
|
2009-04-27 03:25:04 +04:00
|
|
|
## FILE LOAD AND SAVE METHODS
|
|
|
|
|
2009-04-27 23:26:44 +04:00
|
|
|
def map_set(self, gitsha, hgsha):
|
|
|
|
self._map_git[gitsha] = hgsha
|
|
|
|
self._map_hg[hgsha] = gitsha
|
|
|
|
|
|
|
|
def map_hg_get(self, gitsha):
|
|
|
|
if gitsha in self._map_git:
|
|
|
|
return self._map_git[gitsha]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def map_git_get(self, hgsha):
|
|
|
|
if hgsha in self._map_hg:
|
|
|
|
return self._map_hg[hgsha]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2009-04-25 01:05:50 +04:00
|
|
|
def load_map(self):
|
2009-04-27 23:26:44 +04:00
|
|
|
self._map_git = {}
|
|
|
|
self._map_hg = {}
|
2009-04-25 01:05:50 +04:00
|
|
|
if os.path.exists(self.repo.join('git-mapfile')):
|
|
|
|
for line in self.repo.opener('git-mapfile'):
|
|
|
|
gitsha, hgsha = line.strip().split(' ', 1)
|
2009-04-27 23:26:44 +04:00
|
|
|
self._map_git[gitsha] = hgsha
|
|
|
|
self._map_hg[hgsha] = gitsha
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-25 01:05:50 +04:00
|
|
|
def save_map(self):
|
|
|
|
file = self.repo.opener('git-mapfile', 'w+')
|
2009-04-27 23:26:44 +04:00
|
|
|
for gitsha, hgsha in self._map_git.iteritems():
|
2009-04-25 01:05:50 +04:00
|
|
|
file.write("%s %s\n" % (gitsha, hgsha))
|
|
|
|
file.close()
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 03:25:04 +04:00
|
|
|
def load_config(self):
|
|
|
|
self._config = {}
|
|
|
|
if os.path.exists(self.repo.join('git-config')):
|
|
|
|
for line in self.repo.opener('git-config'):
|
|
|
|
key, value = line.strip().split(' ', 1)
|
|
|
|
self._config[key] = value
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 03:25:04 +04:00
|
|
|
def save_config(self):
|
|
|
|
file = self.repo.opener('git-config', 'w+')
|
|
|
|
for key, value in self._config.iteritems():
|
|
|
|
file.write("%s %s\n" % (key, value))
|
|
|
|
file.close()
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 03:25:04 +04:00
|
|
|
|
|
|
|
## END FILE LOAD AND SAVE METHODS
|
2009-04-25 01:05:50 +04:00
|
|
|
|
2009-04-27 01:49:38 +04:00
|
|
|
def fetch(self, remote_name):
|
|
|
|
self.ui.status(_("fetching from : " + remote_name + "\n"))
|
2009-04-24 02:26:10 +04:00
|
|
|
self.export_git_objects()
|
2009-04-27 01:49:38 +04:00
|
|
|
self.fetch_pack(remote_name)
|
|
|
|
self.import_git_objects(remote_name)
|
2009-04-25 01:05:50 +04:00
|
|
|
self.save_map()
|
|
|
|
|
2009-04-27 04:56:16 +04:00
|
|
|
def push(self, remote_name):
|
|
|
|
self.ui.status(_("pushing to : " + remote_name + "\n"))
|
|
|
|
self.export_git_objects()
|
|
|
|
self.upload_pack(remote_name)
|
|
|
|
self.save_map()
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 01:49:38 +04:00
|
|
|
# TODO: make these actually save and recall
|
|
|
|
def remote_add(self, remote_name, git_url):
|
2009-04-27 03:25:04 +04:00
|
|
|
self._config['remote.' + remote_name + '.url'] = git_url
|
|
|
|
self.save_config()
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 01:49:38 +04:00
|
|
|
def remote_name_to_url(self, remote_name):
|
2009-04-27 03:25:04 +04:00
|
|
|
return self._config['remote.' + remote_name + '.url']
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 23:26:44 +04:00
|
|
|
def export_git_objects(self):
|
|
|
|
print "exporting git objects"
|
|
|
|
for rev in self.repo.changelog:
|
|
|
|
node = self.repo.changelog.lookup(rev)
|
|
|
|
hgsha = hex(node)
|
|
|
|
git_sha = self.map_git_get(hgsha)
|
|
|
|
if not git_sha:
|
|
|
|
self.export_hg_commit(rev)
|
|
|
|
|
|
|
|
def export_hg_commit(self, rev):
|
|
|
|
# convert this commit into git objects
|
|
|
|
# go through the manifest, convert all blobs/trees we don't have
|
|
|
|
# write the commit object (with metadata info)
|
|
|
|
print "EXPORT"
|
|
|
|
node = self.repo.changelog.lookup(rev)
|
|
|
|
parents = self.repo.parents(rev)
|
|
|
|
|
|
|
|
print parents # TODO: make sure parents are converted first
|
|
|
|
|
|
|
|
ctx = self.repo.changectx(rev)
|
|
|
|
man = ctx.manifest()
|
2009-04-27 23:37:14 +04:00
|
|
|
|
|
|
|
trees = {}
|
2009-04-27 23:26:44 +04:00
|
|
|
for filename in man.keys():
|
|
|
|
fctx = ctx.filectx(filename)
|
|
|
|
file_id = hex(fctx.filenode())
|
|
|
|
git_sha = self.map_git_get(file_id)
|
2009-04-27 23:37:14 +04:00
|
|
|
if not git_sha:
|
|
|
|
sha = self.git.write_blob(fctx.data())
|
|
|
|
self.map_set(sha, file_id)
|
|
|
|
print sha
|
|
|
|
# TODO : check for subtrees and write them seperately
|
|
|
|
parts = filename.split('/')
|
|
|
|
if len(parts) > 1:
|
|
|
|
print parts
|
|
|
|
print trees
|
|
|
|
|
2009-04-27 23:26:44 +04:00
|
|
|
print "WRITE COMMIT OBJECT"
|
|
|
|
print ctx.user()
|
|
|
|
print ctx.date()
|
|
|
|
print ctx.description()
|
|
|
|
print ctx.branch()
|
|
|
|
print ctx.tags()
|
2009-04-27 23:37:14 +04:00
|
|
|
print parents
|
2009-04-27 23:26:44 +04:00
|
|
|
|
2009-04-27 02:51:05 +04:00
|
|
|
def remote_head(self, remote_name):
|
|
|
|
for head, sha in self.git.remote_refs(remote_name).iteritems():
|
|
|
|
if head == 'HEAD':
|
2009-04-27 23:26:44 +04:00
|
|
|
return self.map_hg_get(sha)
|
2009-04-27 02:51:05 +04:00
|
|
|
return None
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 04:56:16 +04:00
|
|
|
def upload_pack(self, remote_name):
|
|
|
|
self.ui.status(_("upload pack\n"))
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 01:49:38 +04:00
|
|
|
def fetch_pack(self, remote_name):
|
|
|
|
git_url = self.remote_name_to_url(remote_name)
|
2009-04-24 02:26:10 +04:00
|
|
|
client, path = self.get_transport_and_path(git_url)
|
|
|
|
graphwalker = SimpleFetchGraphWalker(self.git.heads().values(), self.git.get_parents)
|
|
|
|
f, commit = self.git.object_store.add_pack()
|
|
|
|
try:
|
|
|
|
determine_wants = self.git.object_store.determine_wants_all
|
|
|
|
refs = client.fetch_pack(path, determine_wants, graphwalker, f.write, sys.stdout.write)
|
|
|
|
f.close()
|
|
|
|
commit()
|
2009-04-27 01:49:38 +04:00
|
|
|
self.git.set_remote_refs(refs, remote_name)
|
2009-04-24 02:26:10 +04:00
|
|
|
except:
|
|
|
|
f.close()
|
2009-04-27 05:27:47 +04:00
|
|
|
raise
|
2009-04-24 02:26:10 +04:00
|
|
|
|
2009-04-27 01:49:38 +04:00
|
|
|
def import_git_objects(self, remote_name):
|
2009-04-24 02:26:10 +04:00
|
|
|
self.ui.status(_("importing Git objects into Hg\n"))
|
|
|
|
# import heads as remote references
|
|
|
|
todo = []
|
|
|
|
done = set()
|
2009-04-26 07:56:03 +04:00
|
|
|
convert_list = {}
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-24 22:45:10 +04:00
|
|
|
# get a list of all the head shas
|
2009-04-27 01:49:38 +04:00
|
|
|
for head, sha in self.git.remote_refs(remote_name).iteritems():
|
2009-04-24 02:26:10 +04:00
|
|
|
todo.append(sha)
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-24 22:45:10 +04:00
|
|
|
# traverse the heads getting a list of all the unique commits
|
|
|
|
# TODO : stop when we hit a SHA we've already imported
|
2009-04-24 02:26:10 +04:00
|
|
|
while todo:
|
|
|
|
sha = todo.pop()
|
|
|
|
assert isinstance(sha, str)
|
|
|
|
if sha in done:
|
|
|
|
continue
|
|
|
|
done.add(sha)
|
|
|
|
commit = self.git.commit(sha)
|
2009-04-26 07:56:03 +04:00
|
|
|
convert_list[sha] = commit
|
2009-04-24 02:26:10 +04:00
|
|
|
todo.extend([p for p in commit.parents if p not in done])
|
2009-04-27 05:27:47 +04:00
|
|
|
|
|
|
|
# sort the commits
|
2009-04-26 07:56:03 +04:00
|
|
|
commits = TopoSort(convert_list).items()
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-24 22:45:10 +04:00
|
|
|
# import each of the commits, oldest first
|
2009-04-26 07:56:03 +04:00
|
|
|
for csha in commits:
|
|
|
|
commit = convert_list[csha]
|
2009-04-24 02:26:10 +04:00
|
|
|
self.import_git_commit(commit)
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-27 02:51:05 +04:00
|
|
|
# update Hg bookmarks
|
2009-04-27 01:49:38 +04:00
|
|
|
bms = {}
|
|
|
|
for head, sha in self.git.remote_refs(remote_name).iteritems():
|
2009-04-27 23:26:44 +04:00
|
|
|
hgsha = hex_to_sha(self.map_hg_get(sha))
|
2009-04-27 02:51:05 +04:00
|
|
|
if not head == 'HEAD':
|
2009-04-27 05:27:47 +04:00
|
|
|
bms[remote_name + '/' + head] = hgsha
|
2009-04-27 23:26:44 +04:00
|
|
|
bookmarks.write(self.repo, bms)
|
2009-04-24 02:26:10 +04:00
|
|
|
|
|
|
|
def import_git_commit(self, commit):
|
2009-04-26 03:57:11 +04:00
|
|
|
print "importing: " + commit.id
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-26 07:59:53 +04:00
|
|
|
# TODO : (?) have to handle merge contexts at some point (two parent files, etc)
|
2009-04-24 22:45:10 +04:00
|
|
|
def getfilectx(repo, memctx, f):
|
2009-04-26 03:57:11 +04:00
|
|
|
(e, sha, data) = self.git.get_file(commit, f)
|
|
|
|
e = '' # TODO : make this a real mode
|
2009-04-24 22:45:10 +04:00
|
|
|
return context.memfilectx(f, data, 'l' in e, 'x' in e, None)
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-24 22:45:10 +04:00
|
|
|
p1 = "0" * 40
|
|
|
|
p2 = "0" * 40
|
2009-04-25 01:05:50 +04:00
|
|
|
if len(commit.parents) > 0:
|
|
|
|
sha = commit.parents[0]
|
2009-04-27 23:26:44 +04:00
|
|
|
p1 = self.map_hg_get(sha)
|
2009-04-25 01:05:50 +04:00
|
|
|
if len(commit.parents) > 1:
|
|
|
|
sha = commit.parents[1]
|
2009-04-27 23:26:44 +04:00
|
|
|
p2 = self.map_hg_get(sha)
|
2009-04-25 01:05:50 +04:00
|
|
|
if len(commit.parents) > 2:
|
|
|
|
# TODO : map extra parents to the extras file
|
|
|
|
pass
|
2009-04-24 22:45:10 +04:00
|
|
|
|
2009-04-26 03:57:11 +04:00
|
|
|
files = self.git.get_files_changed(commit)
|
2009-04-26 07:56:03 +04:00
|
|
|
#print files
|
2009-04-24 22:45:10 +04:00
|
|
|
|
|
|
|
# get a list of the changed, added, removed files
|
|
|
|
extra = {}
|
2009-04-25 01:05:50 +04:00
|
|
|
text = commit.message
|
|
|
|
date = datetime.datetime.fromtimestamp(commit.author_time).strftime("%Y-%m-%d %H:%M:%S")
|
2009-04-24 22:45:10 +04:00
|
|
|
ctx = context.memctx(self.repo, (p1, p2), text, files, getfilectx,
|
|
|
|
commit.author, date, extra)
|
|
|
|
a = self.repo.commitctx(ctx)
|
2009-04-25 01:05:50 +04:00
|
|
|
|
|
|
|
# get changeset id
|
|
|
|
p2 = hex(self.repo.changelog.tip())
|
|
|
|
# save changeset to mapping file
|
|
|
|
gitsha = commit.id
|
2009-04-27 23:26:44 +04:00
|
|
|
self.map_set(gitsha, p2)
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-24 22:45:10 +04:00
|
|
|
def getfilectx(self, source, repo, memctx, f):
|
|
|
|
v = files[f]
|
|
|
|
data = source.getfile(f, v)
|
|
|
|
e = source.getmode(f, v)
|
|
|
|
return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
|
2009-04-24 02:26:10 +04:00
|
|
|
|
|
|
|
def check_bookmarks(self):
|
|
|
|
if self.ui.config('extensions', 'hgext.bookmarks') is not None:
|
|
|
|
print "YOU NEED TO SETUP BOOKMARKS"
|
|
|
|
|
|
|
|
def get_transport_and_path(self, uri):
|
|
|
|
from dulwich.client import TCPGitClient, SSHGitClient, SubprocessGitClient
|
|
|
|
for handler, transport in (("git://", TCPGitClient), ("git+ssh://", SSHGitClient)):
|
|
|
|
if uri.startswith(handler):
|
|
|
|
host, path = uri[len(handler):].split("/", 1)
|
|
|
|
return transport(host), "/"+path
|
|
|
|
# if its not git or git+ssh, try a local url..
|
|
|
|
return SubprocessGitClient(), uri
|
2009-04-26 07:56:03 +04:00
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
Tarjan's algorithm and topological sorting implementation in Python
|
|
|
|
by Paul Harrison
|
|
|
|
Public domain, do with it as you will
|
|
|
|
"""
|
|
|
|
class TopoSort(object):
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-26 07:56:03 +04:00
|
|
|
def __init__(self, commitdict):
|
|
|
|
self._sorted = self.robust_topological_sort(commitdict)
|
|
|
|
self._shas = []
|
|
|
|
for level in self._sorted:
|
2009-04-26 22:44:28 +04:00
|
|
|
for sha in level:
|
|
|
|
self._shas.append(sha)
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-26 07:56:03 +04:00
|
|
|
def items(self):
|
|
|
|
self._shas.reverse()
|
|
|
|
return self._shas
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-26 07:56:03 +04:00
|
|
|
def strongly_connected_components(self, graph):
|
|
|
|
""" Find the strongly connected components in a graph using
|
|
|
|
Tarjan's algorithm.
|
|
|
|
|
|
|
|
graph should be a dictionary mapping node names to
|
|
|
|
lists of successor nodes.
|
|
|
|
"""
|
|
|
|
|
|
|
|
result = [ ]
|
|
|
|
stack = [ ]
|
|
|
|
low = { }
|
|
|
|
|
|
|
|
def visit(node):
|
|
|
|
if node in low: return
|
|
|
|
|
|
|
|
num = len(low)
|
|
|
|
low[node] = num
|
|
|
|
stack_pos = len(stack)
|
|
|
|
stack.append(node)
|
|
|
|
|
|
|
|
for successor in graph[node].parents:
|
|
|
|
visit(successor)
|
|
|
|
low[node] = min(low[node], low[successor])
|
|
|
|
|
|
|
|
if num == low[node]:
|
|
|
|
component = tuple(stack[stack_pos:])
|
|
|
|
del stack[stack_pos:]
|
|
|
|
result.append(component)
|
|
|
|
for item in component:
|
|
|
|
low[item] = len(graph)
|
|
|
|
|
|
|
|
for node in graph:
|
|
|
|
visit(node)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def topological_sort(self, graph):
|
|
|
|
count = { }
|
|
|
|
for node in graph:
|
|
|
|
count[node] = 0
|
|
|
|
for node in graph:
|
|
|
|
for successor in graph[node]:
|
|
|
|
count[successor] += 1
|
|
|
|
|
|
|
|
ready = [ node for node in graph if count[node] == 0 ]
|
|
|
|
|
|
|
|
result = [ ]
|
|
|
|
while ready:
|
|
|
|
node = ready.pop(-1)
|
|
|
|
result.append(node)
|
|
|
|
|
|
|
|
for successor in graph[node]:
|
|
|
|
count[successor] -= 1
|
|
|
|
if count[successor] == 0:
|
|
|
|
ready.append(successor)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def robust_topological_sort(self, graph):
|
|
|
|
""" First identify strongly connected components,
|
|
|
|
then perform a topological sort on these components. """
|
|
|
|
|
|
|
|
components = self.strongly_connected_components(graph)
|
|
|
|
|
|
|
|
node_component = { }
|
|
|
|
for component in components:
|
|
|
|
for node in component:
|
|
|
|
node_component[node] = component
|
|
|
|
|
|
|
|
component_graph = { }
|
|
|
|
for component in components:
|
|
|
|
component_graph[component] = [ ]
|
|
|
|
|
|
|
|
for node in graph:
|
|
|
|
node_c = node_component[node]
|
|
|
|
for successor in graph[node].parents:
|
|
|
|
successor_c = node_component[successor]
|
|
|
|
if node_c != successor_c:
|
2009-04-27 05:27:47 +04:00
|
|
|
component_graph[node_c].append(successor_c)
|
2009-04-26 07:56:03 +04:00
|
|
|
|
|
|
|
return self.topological_sort(component_graph)
|