6 from itertools import imap;
8 class ApplyDeltaError(Exception):
9 """Indicates that applying a delta failed."""
11 def __init__(self, *args, **kwargs):
12 Exception.__init__(self, *args, **kwargs)
14 def chunks_length(chunks):
15 return sum(imap(len, chunks))
17 def apply_delta(src_buf, delta):
18 """Based on the similar function in git's patch-delta.c.
20 :param src_buf: Source buffer
21 :param delta: Delta instructions
23 if type(src_buf) != str:
24 src_buf = ''.join(src_buf)
25 if type(delta) != str:
26 delta = ''.join(delta)
29 delta_length = len(delta)
30 def get_delta_header_size(delta, index):
34 cmd = ord(delta[index])
36 size |= (cmd & ~0x80) << i
41 src_size, index = get_delta_header_size(delta, index)
42 dest_size, index = get_delta_header_size(delta, index)
43 assert src_size == len(src_buf), '%d vs %d' % (src_size, len(src_buf))
44 while index < delta_length:
45 cmd = ord(delta[index])
53 cp_off |= x << (i * 8)
56 if cmd & (1 << (4+i)):
59 cp_size |= x << (i * 8)
62 if (cp_off + cp_size < cp_size or
63 cp_off + cp_size > src_size or
66 out.append(src_buf[cp_off:cp_off+cp_size])
68 out.append(delta[index:index+cmd])
71 raise ApplyDeltaError('Invalid opcode 0')
73 if index != delta_length:
74 raise ApplyDeltaError('delta not empty: %r' % delta[index:])
76 if dest_size != chunks_length(out):
77 raise ApplyDeltaError('dest size incorrect')
82 """Class that handles various aspects of converting a hg commit to git.
85 def __init__(self, warn):
86 """Initializes a new GitHg object with the specified warner.
91 def format_timezone(self, offset):
93 raise ValueError("Unable to handle non-minute offset.")
94 sign = (offset < 0) and '-' or '+'
96 return '%c%02d%02d' % (sign, offset / 3600, (offset / 60) % 60)
98 def get_committer(self, ctx):
101 if 'committer' in extra:
103 (name_timestamp, timezone) = extra['committer'].rsplit(' ', 1)
105 timezone = self.format_timezone(-int(timezone))
106 return '%s %s' % (name_timestamp, timezone)
108 self.warn("Ignoring committer in extra, invalid timezone in r%s: '%s'.\n" % (ctx.rev(), timezone))
112 def get_message(self, ctx):
115 message = ctx.description() + "\n"
116 if 'message' in extra:
117 message = apply_delta(message, extra['message'])
119 # HG EXTRA INFORMATION
120 # TODO FIXME these should go into notes
123 if not ctx.branch() == 'default':
125 extra_message += "branch : " + ctx.branch() + "\n"
128 for f in ctx.files():
129 if f not in ctx.manifest():
131 rename = ctx.filectx(f).renamed()
133 renames.append((rename[0], f))
137 for oldfile, newfile in renames:
138 extra_message += "rename : " + oldfile + " => " + newfile + "\n"
140 for key, value in extra.iteritems():
141 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
145 extra_message += "extra : " + key + " : " + urllib.quote(value) + "\n"
148 message += "\n--HG--\n" + extra_message
152 def get_author(self, ctx):
153 # hg authors might not have emails
156 # check for git author pattern compliance
157 regex = re.compile('^(.*?) \<(.*?)\>(.*)$')
158 a = regex.match(author)
163 if len(a.group(3)) > 0:
164 name += ' ext:(' + urllib.quote(a.group(3)) + ')'
165 author = name + ' <' + email + '>'
167 author = author + ' <none@none>'
169 if 'author' in ctx.extra():
170 author = apply_delta(author, ctx.extra()['author'])
172 (time, timezone) = ctx.date()
173 date = str(int(time)) + ' ' + self.format_timezone(-timezone)
175 return author + ' ' + date
177 def get_parents(self, ctx):
178 def is_octopus_part(ctx):
179 return ctx.extra().get('hg-git', None) in ('octopus', 'octopus-done')
182 if ctx.extra().get('hg-git', None) == 'octopus-done':
183 # implode octopus parents
185 while is_octopus_part(part):
186 (p1, p2) = part.parents()
187 assert not is_octopus_part(p1)
192 parents = ctx.parents()