From d95b28e2bad6beba1bf7a6148997be1f79e0c070 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Mon, 5 Nov 2007 19:10:18 -0200 Subject: [PATCH] Include name in short_id; fixes multiple patches with same time stamp. Add unicode fixes --- darcs2git.py | 148 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 121 insertions(+), 27 deletions(-) diff --git a/darcs2git.py b/darcs2git.py index 847e1fc..5d7c84d 100644 --- a/darcs2git.py +++ b/darcs2git.py @@ -22,7 +22,6 @@ """ - # TODO: # # - time zones @@ -32,6 +31,9 @@ # - use binary search to find from-patch in case of conflict. # +import sha +from datetime import datetime +from time import strptime import urlparse import distutils.version import glob @@ -43,6 +45,7 @@ import re import gzip import optparse + ################################################################ # globals @@ -149,14 +152,16 @@ test end result.""") sys.exit (2) if len(urlparse.urlparse(args[0])) == 0: - raise NotImplementedError,"We support local DARCS repos only." + raise NotImplementedError, "We support local DARCS repos only." - git_version = distutils.version.LooseVersion(os.popen("git --version","r").read().strip().split(" ")[-1]) + git_version = distutils.version.LooseVersion( + os.popen("git --version","r").read().strip().split(" ")[-1]) ideal_version = distutils.version.LooseVersion("1.5.0") if git_version= 1.5.0 for this." - options.basename = os.path.basename (os.path.normpath (args[0])).replace ('.darcs', '') + options.basename = os.path.basename ( + os.path.normpath (args[0])).replace ('.darcs', '') if not options.target_git_repo: options.target_git_repo = options.basename + '.git' @@ -208,6 +213,93 @@ def darcs_timezone (x) : ################################################################ # darcs +## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/521889 + +PATCH_DATE_FORMAT = '%Y%m%d%H%M%S' + +patch_pattern = r""" + \[ # Patch start indicator + (?P[^\n]+)\n # Patch name (rest of same line) + (?P[^\*]+) # Patch author + \* # Author/date separator + (?P[-\*]) # Inverted patch indicator + (?P\d{14}) # Patch date + (?:\n(?P(?:^\ [^\n]*\n)+))? # Optional long comment + \] # Patch end indicator + """ +patch_re = re.compile(patch_pattern, re.VERBOSE | re.MULTILINE) +tidy_comment_re = re.compile(r'^ ', re.MULTILINE) + +def parse_inventory(inventory): + """ + Given the contents of a darcs inventory file, generates ``Patch`` + objects representing contained patch details. + """ + for match in patch_re.finditer(inventory): + attrs = match.groupdict(None) + attrs['inverted'] = (attrs['inverted'] == '-') + if attrs['comment'] is not None: + attrs['comment'] = tidy_comment_re.sub('', attrs['comment']).strip() + yield InventoryPatch(**attrs) + +def fix_braindead_darcs_escapes(s): + def insert_hibit(match): + return chr(int(match.group(1), 16)) + + return re.sub(r'\[_\\([0-9a-f][0-9a-f])_\]', + insert_hibit, str(s)) + +class InventoryPatch: + """ + Patch details, as defined in a darcs inventory file. + + Attribute names match those generated by the + ``darcs changes --xml-output`` command. + """ + + def __init__(self, name, author, date, inverted, comment=None): + self.name = name + self.author = author + self.date = datetime(*strptime(date, PATCH_DATE_FORMAT)[:6]) + self.inverted = inverted + self.comment = comment + + def __str__(self): + return self.name + + @property + def complete_patch_details(self): + date_str = self.date.strftime(PATCH_DATE_FORMAT) + return '%s%s%s%s%s' % ( + self.name, self.author, date_str, + self.comment and ''.join([l.rstrip() for l in self.comment.split('\n')]) or '', + self.inverted and 't' or 'f') + + def short_id (self): + inv = '*' + if self.inverted: + inv = '-' + + return unicode('%s%s*%s%s' % (self.name, self.author, inv, self.hash.split ('-')[0]), 'UTF-8') + + @property + def hash(self): + """ + Calculates the filename of the gzipped file containing patch + contents in the repository's ``patches`` directory. + + This consists of the patch date, a partial SHA-1 hash of the + patch author and a full SHA-1 hash of the complete patch + details. + """ + + date_str = self.date.strftime(PATCH_DATE_FORMAT) + return '%s-%s-%s.gz' % (date_str, + sha.new(self.author).hexdigest()[:5], + sha.new(self.complete_patch_details).hexdigest()) + +## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/521889 + class DarcsConversionRepo: """Representation of a Darcs repo. @@ -222,7 +314,6 @@ going back (obliterate) and forward (pull). self._current_number = -1 self._is_valid = -1 self._inventory_dict = None - self._short_id_dict = dict ((p.short_id (), p) for p in patches) def __del__ (self): @@ -230,7 +321,7 @@ going back (obliterate) and forward (pull). system ('rm -fr %s' % self.dir) def is_contiguous (self): - return (len (self.inventory_dict ()) == self._current_number+1 + return (len (self.inventory_dict ()) == self._current_number + 1 and self.contains_contiguous (self._current_number)) def contains_contiguous (self, num): @@ -244,13 +335,13 @@ going back (obliterate) and forward (pull). for p in self.patches[:num + 1]: if not self.has_patch (p): return False - + return True def has_patch (self, p): assert self._is_valid - return self.inventory_dict ().has_key (p.short_id ()) + return p.short_id () in self.inventory_dict () def pristine_tree (self): return self.dir + '/_darcs/pristine' @@ -331,17 +422,23 @@ going back (obliterate) and forward (pull). return i def inventory_dict (self): + if type (self._inventory_dict) != type ({}): self._inventory_dict = {} - def note_patch (m): - self._inventory_dict[m.group (1)] = self._short_id_dict[m.group(1)] + for p in parse_inventory(self.inventory()): + key = p.short_id() + + try: + self._inventory_dict[key] = self._short_id_dict[key] + except KeyError: + print 'key not found', key + print self._short_id_dict + raise - re.sub (r'\n([^*\n]+\*[*-][0-9]+)', note_patch, self.inventory ()) return self._inventory_dict def start_at (self, num): - """Move the repo to NUM. This uses the fishy technique of writing the inventory and @@ -367,11 +464,9 @@ going back (obliterate) and forward (pull). self._is_valid = True def go_to (self, dest): - contiguous = self.is_contiguous () - if not self._is_valid: self.start_at (dest) - elif dest == self._current_number and contiguous: + elif dest == self._current_number and self.is_contiguous (): pass elif (self.contains_contiguous (dest)): self.go_back_to (dest) @@ -432,7 +527,7 @@ class DarcsPatch: if self.attributes['inverted'] == 'True': inv = '-' - return '%s*%s%s' % (self.attributes['author'], inv, self.attributes['hash'].split ('-')[0]) + return '%s%s*%s%s' % (self.name(), self.attributes['author'], inv, self.attributes['hash'].split ('-')[0]) def filename (self): return self.dir + '/_darcs/patches/' + self.attributes['hash'] @@ -487,10 +582,10 @@ class DarcsPatch: self.date = darcs_date_to_git (self.attributes['date']) + ' ' + darcs_timezone (self.attributes['local_date']) def name (self): - patch_name = '(no comment)' + patch_name = '' try: name_elt = self.xml.getElementsByTagName ('name')[0] - patch_name = name_elt.childNodes[0].data + patch_name = unicode(fix_braindead_darcs_escapes(str(name_elt.childNodes[0].data)), 'UTF-8') except IndexError: pass return patch_name @@ -600,9 +695,8 @@ def export_commit (repo, patch, last_patch, gfi): if options.debug: msg += '\n\n#%d\n' % patch.number + msg = msg.encode('utf-8') gfi.write ('data %d\n%s\n' % (len (msg), msg)) - - mergers = [] for (n, p) in pending_patches.items (): if repo.has_patch (p): @@ -622,7 +716,6 @@ def export_commit (repo, patch, last_patch, gfi): pending_patches[patch.number] = patch export_tree (repo.pristine_tree (), gfi) - n = -1 if last_patch: n = last_patch.number @@ -662,8 +755,10 @@ def export_tag (patch, gfi): gfi.write ('tagger %s <%s> %s\n' % (patch.author_name, patch.author_mail, patch.date)) - gfi.write ('data %d\n%s\n' % (len (patch.message), - patch.message)) + + raw_message = patch.message.encode('utf-8') + gfi.write ('data %d\n%s\n' % (len (raw_message), + raw_message)) ################################################################ # main. @@ -703,7 +798,6 @@ def main (): os.environ['GIT_DIR'] = git_repo - quiet = ' --quiet' if options.verbose: quiet = ' ' @@ -715,10 +809,9 @@ def main (): conv_repo.start_at (-1) for p in patches: - parent_patch = None parent_number = -1 - + combinations = [(v, w) for v in pending_patches.values () for w in pending_patches.values ()] candidates = [common_ancestor (git_commits[c[0].number], git_commits[c[1].number]) for c in combinations] @@ -772,7 +865,8 @@ def main (): if options.checkpoint_frequency and p.number % options.checkpoint_frequency == 0: export_checkpoint (gfi) else: - progress ("Can't import patch %d, need conflict resolution patch?" % p.number) + progress ("Can't import patch %d, need conflict resolution patch?" + % p.number) export_pending (gfi) gfi.close () -- 2.11.4.GIT