2009-05-10 18:29:15 +04:00
|
|
|
import os, errno, sys, time, datetime, pickle, copy, math
|
2009-05-02 07:05:30 +04:00
|
|
|
import toposort
|
2009-04-24 02:26:10 +04:00
|
|
|
import dulwich
|
|
|
|
from dulwich.repo import Repo
|
|
|
|
from dulwich.client import SimpleFetchGraphWalker
|
|
|
|
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-29 01:28:27 +04:00
|
|
|
from dulwich.objects import (
|
|
|
|
Blob,
|
|
|
|
Commit,
|
|
|
|
ShaFile,
|
|
|
|
Tag,
|
|
|
|
Tree,
|
|
|
|
hex_to_sha
|
|
|
|
)
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-28 03:15:48 +04:00
|
|
|
import math
|
|
|
|
|
|
|
|
def seconds_to_offset(time):
|
|
|
|
hours = (float(time) / 60 / 60)
|
|
|
|
hour_diff = math.fmod(time, 60)
|
|
|
|
minutes = int(hour_diff)
|
|
|
|
hours = int(math.floor(hours))
|
|
|
|
if hours > 12:
|
|
|
|
sign = '+'
|
|
|
|
hours = 12 - (hours - 12)
|
2009-04-30 23:47:04 +04:00
|
|
|
elif hours > 0:
|
2009-04-28 03:15:48 +04:00
|
|
|
sign = '-'
|
2009-04-30 23:47:04 +04:00
|
|
|
else:
|
|
|
|
sign = ''
|
2009-04-28 03:15:48 +04:00
|
|
|
return sign + str(hours).rjust(2, '0') + str(minutes).rjust(2, '0')
|
|
|
|
|
|
|
|
def offset_to_seconds(offset):
|
|
|
|
if len(offset) == 5:
|
|
|
|
sign = offset[0:1]
|
|
|
|
hours = int(offset[1:3])
|
|
|
|
minutes = int(offset[3:5])
|
|
|
|
if sign == '+':
|
|
|
|
hours = 12 + (12 - hours)
|
|
|
|
return (hours * 60 * 60) + (minutes) * 60
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
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)
|
2009-05-10 05:03:51 +04:00
|
|
|
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):
|
2009-05-10 05:03:51 +04:00
|
|
|
return self._map_git.get(gitsha)
|
2009-04-27 23:26:44 +04:00
|
|
|
|
|
|
|
def map_git_get(self, hgsha):
|
2009-05-10 05:03:51 +04:00
|
|
|
return self._map_hg.get(hgsha)
|
2009-04-29 06:33:03 +04:00
|
|
|
|
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):
|
2009-05-10 19:54:47 +04:00
|
|
|
file = self.repo.opener('git-mapfile', 'w+', atomictemp=True)
|
2009-05-10 18:36:47 +04:00
|
|
|
for gitsha, hgsha in sorted(self._map_git.iteritems()):
|
2009-04-25 01:05:50 +04:00
|
|
|
file.write("%s %s\n" % (gitsha, hgsha))
|
2009-05-10 19:54:47 +04:00
|
|
|
file.rename()
|
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):
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.status(_("fetching from : %s\n") % remote_name)
|
2009-04-24 02:26:10 +04:00
|
|
|
self.export_git_objects()
|
2009-04-30 00:55:22 +04:00
|
|
|
refs = self.fetch_pack(remote_name)
|
|
|
|
if refs:
|
|
|
|
self.import_git_objects(remote_name)
|
2009-04-25 01:05:50 +04:00
|
|
|
self.save_map()
|
|
|
|
|
2009-05-10 17:14:36 +04:00
|
|
|
def export(self):
|
2009-04-27 04:56:16 +04:00
|
|
|
self.export_git_objects()
|
2009-04-28 03:15:48 +04:00
|
|
|
self.update_references()
|
2009-04-27 04:56:16 +04:00
|
|
|
self.save_map()
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-05-10 17:14:36 +04:00
|
|
|
def push(self, remote_name):
|
|
|
|
self.ui.status(_("pushing to : %s\n") % remote_name)
|
|
|
|
self.export()
|
|
|
|
self.upload_pack(remote_name)
|
|
|
|
|
2009-04-27 01:49:38 +04:00
|
|
|
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-29 03:36:57 +04:00
|
|
|
def remote_remove(self, remote_name):
|
|
|
|
key = 'remote.' + remote_name + '.url'
|
|
|
|
if key in self._config:
|
|
|
|
del self._config[key]
|
|
|
|
self.save_config()
|
|
|
|
|
|
|
|
def remote_show(self, remote_name):
|
|
|
|
key = 'remote.' + remote_name + '.url'
|
|
|
|
if key in self._config:
|
|
|
|
name = self._config[key]
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.status(_("URL for %s : %s\n") % (remote_name, name, ))
|
2009-04-29 03:36:57 +04:00
|
|
|
else:
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.status(_("No remote named : %s\n") % remote_name)
|
2009-04-29 06:33:03 +04:00
|
|
|
return
|
2009-04-29 03:36:57 +04:00
|
|
|
|
|
|
|
def remote_list(self):
|
|
|
|
for key, value in self._config.iteritems():
|
|
|
|
if key[0:6] == 'remote':
|
2009-04-30 03:18:37 +04:00
|
|
|
self.ui.status('%s\t%s\n' % (key, value, ))
|
2009-04-29 06:33:03 +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-28 03:15:48 +04:00
|
|
|
def update_references(self):
|
2009-04-28 03:23:34 +04:00
|
|
|
# TODO : if bookmarks exist, add them as git branches
|
|
|
|
c = self.map_git_get(hex(self.repo.changelog.tip()))
|
|
|
|
self.git.set_ref('refs/heads/master', c)
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-27 23:26:44 +04:00
|
|
|
def export_git_objects(self):
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.status(_("exporting git objects\n"))
|
2009-05-10 18:29:15 +04:00
|
|
|
total = len(self.repo.changelog)
|
|
|
|
magnitude = int(math.log(total, 10)) + 1
|
|
|
|
for i, rev in enumerate(self.repo.changelog):
|
|
|
|
if i%100 == 0:
|
|
|
|
self.ui.status(_("at: %*d/%d\n") % (magnitude, i, total))
|
2009-04-28 03:15:48 +04:00
|
|
|
self.export_hg_commit(rev)
|
2009-05-10 02:06:33 +04:00
|
|
|
self.save_map()
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-28 03:15:48 +04:00
|
|
|
# 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)
|
2009-04-27 23:26:44 +04:00
|
|
|
def export_hg_commit(self, rev):
|
2009-04-28 03:15:48 +04:00
|
|
|
# return if we've already processed this
|
2009-04-29 06:33:03 +04:00
|
|
|
node = self.repo.changelog.lookup(rev)
|
2009-04-28 03:15:48 +04:00
|
|
|
phgsha = hex(node)
|
|
|
|
pgit_sha = self.map_git_get(phgsha)
|
|
|
|
if pgit_sha:
|
|
|
|
return pgit_sha
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.status(_("converting revision %s\n") % str(rev))
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-28 03:15:48 +04:00
|
|
|
# make sure parents are converted first
|
|
|
|
parents = self.repo.parents(rev)
|
|
|
|
for parent in parents:
|
|
|
|
p_rev = parent.rev()
|
|
|
|
hgsha = hex(parent.node())
|
|
|
|
git_sha = self.map_git_get(hgsha)
|
2009-04-28 23:46:51 +04:00
|
|
|
if not p_rev == -1:
|
|
|
|
if not git_sha:
|
|
|
|
self.export_hg_commit(p_rev)
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-27 23:26:44 +04:00
|
|
|
ctx = self.repo.changectx(rev)
|
2009-04-30 23:55:56 +04:00
|
|
|
tree_sha, renames = self.write_git_tree(ctx)
|
|
|
|
|
2009-04-28 03:15:48 +04:00
|
|
|
commit = {}
|
|
|
|
commit['tree'] = tree_sha
|
|
|
|
(time, timezone) = ctx.date()
|
2009-04-30 23:46:54 +04:00
|
|
|
|
|
|
|
# hg authors might not have emails
|
|
|
|
author = ctx.user()
|
2009-05-09 07:57:02 +04:00
|
|
|
if not '>' in author: # TODO : this kills losslessness - die (submodules)?
|
2009-04-30 23:46:54 +04:00
|
|
|
author = author + ' <none@none>'
|
|
|
|
commit['author'] = author + ' ' + str(int(time)) + ' ' + seconds_to_offset(timezone)
|
2009-04-28 03:15:48 +04:00
|
|
|
message = ctx.description()
|
2009-05-08 22:35:14 +04:00
|
|
|
commit['message'] = ctx.description() + "\n"
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-05-09 07:57:02 +04:00
|
|
|
extra = ctx.extra()
|
|
|
|
if 'committer' in extra:
|
|
|
|
commit['committer'] = extra['committer']
|
|
|
|
|
2009-04-30 00:26:13 +04:00
|
|
|
# HG EXTRA INFORMATION
|
|
|
|
add_extras = False
|
2009-04-30 23:55:56 +04:00
|
|
|
extra_message = ''
|
2009-04-30 00:26:13 +04:00
|
|
|
if not ctx.branch() == 'default':
|
|
|
|
add_extras = True
|
2009-04-30 23:55:56 +04:00
|
|
|
extra_message += "branch : " + ctx.branch() + "\n"
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-04-30 23:55:56 +04:00
|
|
|
if renames:
|
|
|
|
add_extras = True
|
|
|
|
for oldfile, newfile in renames:
|
|
|
|
extra_message += "rename : " + oldfile + " => " + newfile + "\n"
|
|
|
|
|
2009-04-30 00:26:13 +04:00
|
|
|
if add_extras:
|
2009-05-08 22:35:14 +04:00
|
|
|
commit['message'] += "\n--HG--\n" + extra_message
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-04-28 03:15:48 +04:00
|
|
|
commit['parents'] = []
|
|
|
|
for parent in parents:
|
|
|
|
hgsha = hex(parent.node())
|
|
|
|
git_sha = self.map_git_get(hgsha)
|
2009-04-28 23:46:51 +04:00
|
|
|
if git_sha:
|
|
|
|
commit['parents'].append(git_sha)
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-28 03:15:48 +04:00
|
|
|
commit_sha = self.git.write_commit_hash(commit) # writing new blobs to git
|
|
|
|
self.map_set(commit_sha, phgsha)
|
|
|
|
return commit_sha
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-28 01:50:54 +04:00
|
|
|
def write_git_tree(self, ctx):
|
|
|
|
trees = {}
|
|
|
|
man = ctx.manifest()
|
2009-04-30 23:55:56 +04:00
|
|
|
renames = []
|
2009-04-29 06:33:03 +04:00
|
|
|
for filenm in man.keys():
|
2009-04-28 01:50:54 +04:00
|
|
|
# write blob if not in our git database
|
|
|
|
fctx = ctx.filectx(filenm)
|
2009-04-30 23:55:56 +04:00
|
|
|
rename = fctx.renamed()
|
|
|
|
if rename:
|
|
|
|
filerename, sha = rename
|
|
|
|
renames.append((filerename, filenm))
|
2009-04-28 01:50:54 +04:00
|
|
|
is_exec = 'x' in fctx.flags()
|
|
|
|
is_link = 'l' in fctx.flags()
|
|
|
|
file_id = hex(fctx.filenode())
|
|
|
|
blob_sha = self.map_git_get(file_id)
|
|
|
|
if not blob_sha:
|
|
|
|
blob_sha = self.git.write_blob(fctx.data()) # writing new blobs to git
|
|
|
|
self.map_set(blob_sha, file_id)
|
|
|
|
|
|
|
|
parts = filenm.split('/')
|
|
|
|
if len(parts) > 1:
|
|
|
|
# get filename and path for leading subdir
|
|
|
|
filepath = parts[-1:][0]
|
|
|
|
dirpath = "/".join([v for v in parts[0:-1]]) + '/'
|
|
|
|
|
|
|
|
# get subdir name and path for parent dir
|
2009-04-30 00:26:13 +04:00
|
|
|
parpath = '/'
|
|
|
|
nparpath = '/'
|
|
|
|
for part in parts[0:-1]:
|
|
|
|
if nparpath == '/':
|
|
|
|
nparpath = part + '/'
|
|
|
|
else:
|
|
|
|
nparpath += part + '/'
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-04-30 00:26:13 +04:00
|
|
|
treeentry = ['tree', part + '/', nparpath]
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-04-30 00:26:13 +04:00
|
|
|
if parpath not in trees:
|
|
|
|
trees[parpath] = []
|
|
|
|
if treeentry not in trees[parpath]:
|
|
|
|
trees[parpath].append( treeentry )
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-04-30 00:26:13 +04:00
|
|
|
parpath = nparpath
|
2009-04-28 01:50:54 +04:00
|
|
|
|
|
|
|
# set file entry
|
|
|
|
fileentry = ['blob', filepath, blob_sha, is_exec, is_link]
|
|
|
|
if dirpath not in trees:
|
|
|
|
trees[dirpath] = []
|
|
|
|
trees[dirpath].append(fileentry)
|
|
|
|
|
|
|
|
else:
|
2009-04-29 06:33:03 +04:00
|
|
|
fileentry = ['blob', parts[0], blob_sha, is_exec, is_link]
|
2009-04-28 01:50:54 +04:00
|
|
|
if '/' not in trees:
|
|
|
|
trees['/'] = []
|
|
|
|
trees['/'].append(fileentry)
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-04-28 01:50:54 +04:00
|
|
|
# sort by tree depth, so we write the deepest trees first
|
|
|
|
dirs = trees.keys()
|
|
|
|
dirs.sort(lambda a, b: len(b.split('/'))-len(a.split('/')))
|
2009-04-29 04:28:04 +04:00
|
|
|
dirs.remove('/')
|
|
|
|
dirs.append('/')
|
2009-05-10 03:52:37 +04:00
|
|
|
|
2009-04-28 01:50:54 +04:00
|
|
|
# write all the trees
|
|
|
|
tree_sha = None
|
|
|
|
tree_shas = {}
|
|
|
|
for dirnm in dirs:
|
|
|
|
tree_data = []
|
|
|
|
for entry in trees[dirnm]:
|
|
|
|
# replace tree path with tree SHA
|
|
|
|
if entry[0] == 'tree':
|
|
|
|
sha = tree_shas[entry[2]]
|
|
|
|
entry[2] = sha
|
|
|
|
tree_data.append(entry)
|
|
|
|
tree_sha = self.git.write_tree_array(tree_data) # writing new trees to git
|
|
|
|
tree_shas[dirnm] = tree_sha
|
2009-05-10 03:52:37 +04:00
|
|
|
|
2009-04-30 23:55:56 +04:00
|
|
|
return (tree_sha, renames) # should be the last root tree sha
|
2009-04-29 06:33:03 +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):
|
2009-04-28 10:35:49 +04:00
|
|
|
git_url = self.remote_name_to_url(remote_name)
|
|
|
|
client, path = self.get_transport_and_path(git_url)
|
2009-04-28 23:46:51 +04:00
|
|
|
changed = self.get_changed_refs
|
2009-04-28 10:35:49 +04:00
|
|
|
genpack = self.generate_pack_contents
|
|
|
|
try:
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.status(_("creating and sending data\n"))
|
2009-04-29 21:03:16 +04:00
|
|
|
changed_refs = client.send_pack(path, changed, genpack)
|
2009-04-29 22:36:38 +04:00
|
|
|
if changed_refs:
|
|
|
|
new_refs = {}
|
2009-05-09 07:54:33 +04:00
|
|
|
for ref, sha in changed_refs.iteritems():
|
|
|
|
self.ui.status(" "+ remote_name + "::" + ref + " => GIT:" + sha[0:8] + "\n")
|
|
|
|
new_refs[ref] = sha
|
2009-04-29 22:36:38 +04:00
|
|
|
self.git.set_remote_refs(new_refs, remote_name)
|
|
|
|
self.update_hg_bookmarks(remote_name)
|
2009-04-28 10:35:49 +04:00
|
|
|
except:
|
2009-05-10 05:04:40 +04:00
|
|
|
# TODO : remove try/except or do something useful here
|
2009-04-28 10:35:49 +04:00
|
|
|
raise
|
2009-04-28 23:46:51 +04:00
|
|
|
|
2009-04-29 04:28:04 +04:00
|
|
|
# TODO : for now, we'll just push all heads that match remote heads
|
2009-04-28 23:46:51 +04:00
|
|
|
# * we should have specified push, tracking branches and --all
|
2009-04-29 06:33:03 +04:00
|
|
|
# takes a dict of refs:shas from the server and returns what should be
|
2009-04-28 23:46:51 +04:00
|
|
|
# pushed up
|
|
|
|
def get_changed_refs(self, refs):
|
|
|
|
keys = refs.keys()
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-05-09 07:54:33 +04:00
|
|
|
changed = {}
|
2009-04-29 06:33:03 +04:00
|
|
|
if not keys:
|
2009-04-29 04:28:04 +04:00
|
|
|
return None
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-29 04:28:04 +04:00
|
|
|
# TODO : this is a huge hack
|
|
|
|
if keys[0] == 'capabilities^{}': # nothing on the server yet - first push
|
2009-05-09 07:54:33 +04:00
|
|
|
changed['refs/heads/master'] = self.git.ref('master')
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-28 23:46:51 +04:00
|
|
|
for ref_name in keys:
|
|
|
|
parts = ref_name.split('/')
|
|
|
|
if parts[0] == 'refs': # strip off 'refs/heads'
|
|
|
|
if parts[1] == 'heads':
|
|
|
|
head = "/".join([v for v in parts[2:]])
|
|
|
|
local_ref = self.git.ref(ref_name)
|
2009-04-29 06:33:03 +04:00
|
|
|
if local_ref:
|
2009-04-28 23:46:51 +04:00
|
|
|
if not local_ref == refs[ref_name]:
|
2009-05-09 07:54:33 +04:00
|
|
|
changed[ref_name] = local_ref
|
2009-04-28 23:46:51 +04:00
|
|
|
return changed
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-28 23:46:51 +04:00
|
|
|
# takes a list of shas the server wants and shas the server has
|
|
|
|
# and generates a list of commit shas we need to push up
|
|
|
|
def generate_pack_contents(self, want, have):
|
|
|
|
graph_walker = SimpleFetchGraphWalker(want, self.git.get_parents)
|
|
|
|
next = graph_walker.next()
|
2009-05-02 07:16:07 +04:00
|
|
|
shas = set()
|
2009-04-28 23:46:51 +04:00
|
|
|
while next:
|
|
|
|
if next in have:
|
|
|
|
graph_walker.ack(next)
|
|
|
|
else:
|
2009-05-02 07:16:07 +04:00
|
|
|
shas.add(next)
|
2009-04-28 23:46:51 +04:00
|
|
|
next = graph_walker.next()
|
2009-05-02 07:16:07 +04:00
|
|
|
|
|
|
|
seen = []
|
|
|
|
|
2009-04-29 06:33:03 +04:00
|
|
|
# so now i have the shas, need to turn them into a list of
|
2009-04-29 01:28:27 +04:00
|
|
|
# tuples (sha, path) for ALL the objects i'm sending
|
|
|
|
# TODO : don't send blobs or trees they already have
|
|
|
|
def get_objects(tree, path):
|
|
|
|
changes = list()
|
|
|
|
changes.append((tree, path))
|
|
|
|
for (mode, name, sha) in tree.entries():
|
2009-05-10 05:04:40 +04:00
|
|
|
if mode == 57344: # TODO : properly handle submodules and document what 57344 means
|
2009-04-29 01:28:27 +04:00
|
|
|
continue
|
2009-05-02 07:16:07 +04:00
|
|
|
if sha in seen:
|
|
|
|
continue
|
|
|
|
|
2009-04-29 01:28:27 +04:00
|
|
|
obj = self.git.get_object(sha)
|
2009-05-02 07:16:07 +04:00
|
|
|
seen.append(sha)
|
2009-05-02 07:05:30 +04:00
|
|
|
if isinstance (obj, Blob):
|
2009-04-29 01:28:27 +04:00
|
|
|
changes.append((obj, path + name))
|
|
|
|
elif isinstance(obj, Tree):
|
2009-05-02 07:16:07 +04:00
|
|
|
changes.extend(get_objects(obj, path + name + '/'))
|
2009-04-29 01:28:27 +04:00
|
|
|
return changes
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-29 01:28:27 +04:00
|
|
|
objects = []
|
|
|
|
for commit_sha in shas:
|
|
|
|
commit = self.git.commit(commit_sha)
|
|
|
|
objects.append((commit, 'commit'))
|
|
|
|
tree = self.git.get_object(commit.tree)
|
|
|
|
objects.extend( get_objects(tree, '/') )
|
2009-04-29 06:33:03 +04:00
|
|
|
|
2009-04-29 01:28:27 +04:00
|
|
|
return objects
|
2009-04-29 06:33:03 +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-30 00:55:22 +04:00
|
|
|
if refs:
|
|
|
|
self.git.set_remote_refs(refs, remote_name)
|
|
|
|
else:
|
|
|
|
self.ui.status(_("nothing new on the server\n"))
|
|
|
|
return refs
|
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
|
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)
|
2009-04-28 10:35:49 +04:00
|
|
|
try:
|
|
|
|
commit = self.git.commit(sha)
|
|
|
|
convert_list[sha] = commit
|
|
|
|
todo.extend([p for p in commit.parents if p not in done])
|
|
|
|
except:
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.warn(_("Cannot import tags yet\n")) # TODO
|
2009-04-27 05:27:47 +04:00
|
|
|
|
|
|
|
# sort the commits
|
2009-05-05 20:43:24 +04:00
|
|
|
commits = toposort.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:
|
2009-05-08 02:07:18 +04:00
|
|
|
if not self.map_hg_get(csha): # it's already here
|
2009-04-30 00:55:22 +04:00
|
|
|
commit = convert_list[csha]
|
|
|
|
self.import_git_commit(commit)
|
2009-04-27 05:27:47 +04:00
|
|
|
|
2009-04-29 21:03:16 +04:00
|
|
|
self.update_hg_bookmarks(remote_name)
|
|
|
|
|
|
|
|
def update_hg_bookmarks(self, remote_name):
|
2009-04-28 17:30:11 +04:00
|
|
|
try:
|
2009-04-29 21:03:16 +04:00
|
|
|
bms = bookmarks.parse(self.repo)
|
|
|
|
for head, sha in self.git.remote_refs(remote_name).iteritems():
|
|
|
|
hgsha = hex_to_sha(self.map_hg_get(sha))
|
|
|
|
if not head == 'HEAD':
|
|
|
|
bms[remote_name + '/' + head] = hgsha
|
2009-04-28 17:30:11 +04:00
|
|
|
bookmarks.write(self.repo, bms)
|
|
|
|
except AttributeError:
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.warn(_('creating bookmarks failed, do you have'
|
|
|
|
' bookmarks enabled?\n'))
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-04-29 22:50:56 +04:00
|
|
|
def convert_git_int_mode(self, mode):
|
2009-05-10 05:04:40 +04:00
|
|
|
# TODO : make these into constants
|
2009-04-29 22:50:56 +04:00
|
|
|
convert = {
|
|
|
|
33188: '',
|
|
|
|
40960: 'l',
|
|
|
|
33261: 'e'}
|
|
|
|
if mode in convert:
|
|
|
|
return convert[mode]
|
|
|
|
return ''
|
2009-04-30 03:18:37 +04:00
|
|
|
|
2009-05-01 00:54:33 +04:00
|
|
|
def extract_hg_metadata(self, message):
|
|
|
|
split = message.split("\n\n--HG--\n", 1)
|
|
|
|
renames = {}
|
2009-05-08 02:07:18 +04:00
|
|
|
branch = False
|
2009-05-01 00:54:33 +04:00
|
|
|
if len(split) == 2:
|
|
|
|
message, meta = split
|
|
|
|
lines = meta.split("\n")
|
|
|
|
for line in lines:
|
|
|
|
if line == '':
|
|
|
|
continue
|
|
|
|
|
|
|
|
command, data = line.split(" : ", 1)
|
|
|
|
if command == 'rename':
|
|
|
|
before, after = data.split(" => ", 1)
|
|
|
|
renames[after] = before
|
|
|
|
if command == 'branch':
|
2009-05-08 02:07:18 +04:00
|
|
|
branch = data
|
|
|
|
return (message, renames, branch)
|
2009-05-01 00:54:33 +04:00
|
|
|
|
2009-04-24 02:26:10 +04:00
|
|
|
def import_git_commit(self, commit):
|
2009-05-10 05:04:19 +04:00
|
|
|
self.ui.debug(_("importing: %s\n") % commit.id)
|
2009-05-08 02:07:18 +04:00
|
|
|
# TODO : find and use hg named branches
|
2009-04-28 22:08:54 +04:00
|
|
|
# TODO : add extra Git data (committer info) as extras to changeset
|
2009-04-29 06:33:03 +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-29 06:33:03 +04:00
|
|
|
# TODO : Do something less coarse-grained than try/except on the
|
|
|
|
# get_file call for removed files
|
2009-04-30 23:55:56 +04:00
|
|
|
|
2009-05-08 02:07:18 +04:00
|
|
|
(strip_message, hg_renames, hg_branch) = self.extract_hg_metadata(commit.message)
|
2009-04-30 23:55:56 +04:00
|
|
|
|
2009-04-24 22:45:10 +04:00
|
|
|
def getfilectx(repo, memctx, f):
|
2009-04-29 06:33:03 +04:00
|
|
|
try:
|
2009-04-29 22:50:56 +04:00
|
|
|
(mode, sha, data) = self.git.get_file(commit, f)
|
|
|
|
e = self.convert_git_int_mode(mode)
|
2009-05-10 03:52:37 +04:00
|
|
|
except TypeError:
|
2009-04-29 06:33:03 +04:00
|
|
|
raise IOError()
|
2009-05-01 00:54:33 +04:00
|
|
|
if f in hg_renames:
|
|
|
|
copied_path = hg_renames[f]
|
|
|
|
else:
|
|
|
|
copied_path = None
|
2009-04-30 23:55:56 +04:00
|
|
|
return context.memfilectx(f, data, 'l' in e, 'x' in e, copied_path)
|
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-05-08 02:07:18 +04:00
|
|
|
# get a list of the changed, added, removed files
|
2009-05-02 07:05:30 +04:00
|
|
|
files = self.git.get_files_changed(commit)
|
2009-04-24 22:45:10 +04:00
|
|
|
|
2009-05-08 02:07:18 +04:00
|
|
|
# if this is a merge commit, don't list renamed files
|
|
|
|
# i'm really confused here - this is the only way my use case will
|
|
|
|
# work, but it seems really hacky - do I need to just remove renames
|
|
|
|
# from one of the parents? AARRGH!
|
|
|
|
if not (p2 == "0"*40):
|
|
|
|
for removefile in hg_renames.values():
|
|
|
|
files.remove(removefile)
|
|
|
|
|
2009-05-02 07:05:30 +04:00
|
|
|
extra = {}
|
2009-05-09 07:57:02 +04:00
|
|
|
|
2009-05-08 02:07:18 +04:00
|
|
|
# if named branch, add to extra
|
|
|
|
if hg_branch:
|
|
|
|
extra['branch'] = hg_branch
|
2009-05-09 07:57:02 +04:00
|
|
|
|
|
|
|
# if committer is different than author, add it to extra
|
|
|
|
if not commit._author_raw == commit._committer_raw:
|
|
|
|
extra['committer'] = commit._committer_raw
|
|
|
|
|
|
|
|
if hg_branch:
|
|
|
|
extra['branch'] = hg_branch
|
2009-05-10 03:52:37 +04:00
|
|
|
|
2009-05-01 00:54:33 +04:00
|
|
|
text = strip_message
|
2009-04-25 01:05:50 +04:00
|
|
|
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 02:26:10 +04:00
|
|
|
def check_bookmarks(self):
|
|
|
|
if self.ui.config('extensions', 'hgext.bookmarks') is not None:
|
2009-04-30 03:18:37 +04:00
|
|
|
self.ui.warn("YOU NEED TO SETUP BOOKMARKS\n")
|
2009-04-24 02:26:10 +04:00
|
|
|
|
|
|
|
def get_transport_and_path(self, uri):
|
|
|
|
from dulwich.client import TCPGitClient, SSHGitClient, SubprocessGitClient
|
2009-04-28 22:08:54 +04:00
|
|
|
for handler, transport in (("git://", TCPGitClient), ("git@", SSHGitClient), ("git+ssh://", SSHGitClient)):
|
2009-04-24 02:26:10 +04:00
|
|
|
if uri.startswith(handler):
|
2009-04-28 10:35:49 +04:00
|
|
|
if handler == 'git@':
|
2009-04-28 17:26:21 +04:00
|
|
|
host, path = uri[len(handler):].split(":", 1)
|
2009-04-28 10:35:49 +04:00
|
|
|
host = 'git@' + host
|
2009-04-28 17:26:21 +04:00
|
|
|
else:
|
2009-04-28 22:08:54 +04:00
|
|
|
host, path = uri[len(handler):].split("/", 1)
|
2009-04-28 10:35:49 +04:00
|
|
|
return transport(host), '/' + path
|
2009-04-24 02:26:10 +04:00
|
|
|
# if its not git or git+ssh, try a local url..
|
|
|
|
return SubprocessGitClient(), uri
|
2009-04-26 07:56:03 +04:00
|
|
|
|
2009-04-29 03:56:05 +04:00
|
|
|
def clear(self):
|
|
|
|
git_dir = self.repo.join('git')
|
|
|
|
mapfile = self.repo.join('git-mapfile')
|
2009-04-29 06:33:03 +04:00
|
|
|
if os.path.exists(git_dir):
|
2009-04-29 03:56:05 +04:00
|
|
|
for root, dirs, files in os.walk(git_dir, topdown=False):
|
|
|
|
for name in files:
|
|
|
|
os.remove(os.path.join(root, name))
|
|
|
|
for name in dirs:
|
|
|
|
os.rmdir(os.path.join(root, name))
|
|
|
|
os.rmdir(git_dir)
|
|
|
|
if os.path.exists(mapfile):
|
|
|
|
os.remove(mapfile)
|