changeset 4589:451e91ed535e

convert extension: Add support for username mapping Allows mapping usernames to new ones during conversion process. - Use -A option for first import - Then at the end of the conversion process and if the destination repo supports authorfile attribute, author map content is copied to the file pointed by the authorfile call. - On incremental conversions w/o any -A option specified, the destination authorfile, if any, gets read automatically. EG: This allows mapping unix system usernames used in CVS accounts to a more typical "Firstname Lastname <address@server.org>" pair.
author Edouard Gomez <ed.gomez@free.fr>
date Thu, 14 Jun 2007 23:25:55 +0200
parents 9855939d0c82
children 80fb4ec512b5
files hgext/convert/__init__.py hgext/convert/common.py hgext/convert/hg.py
diffstat 3 files changed, 65 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/convert/__init__.py	Thu Jun 14 23:25:55 2007 +0200
+++ b/hgext/convert/__init__.py	Thu Jun 14 23:25:55 2007 +0200
@@ -37,6 +37,8 @@
         self.commitcache = {}
         self.mapfile = mapfile
         self.mapfilefd = None
+        self.authors = {}
+        self.writeauthors = False
 
         self.map = {}
         try:
@@ -48,6 +50,14 @@
         except IOError:
             pass
 
+        # Read first the dst author map if any
+        if  hasattr(self.dest, 'authorfile'):
+            self.readauthormap(self.dest.authorfile())
+        # Extend/Override with new author map if necessary
+        if 'authors' in opts:
+            self.readauthormap(opts.get('authors'))
+            self.writeauthors = True
+
     def walktree(self, heads):
         visit = heads
         known = {}
@@ -131,6 +141,39 @@
         self.mapfilefd.write("%s %s\n" % (src, dst))
         self.mapfilefd.flush()
 
+    def writeauthormap(self):
+        if self.writeauthors == True and len(self.authors) > 0 and hasattr(self.dest, 'authorfile'):
+           authorfile = self.dest.authorfile()
+           self.ui.status('Writing author map file %s\n' % authorfile)
+           ofile = open(authorfile, 'w+')
+           for author in self.authors:
+               ofile.write("%s=%s\n" % (author, self.authors[author]))
+           ofile.close()
+
+    def readauthormap(self, authorfile):
+        try:
+            afile = open(authorfile, 'r')
+            for line in afile:
+                try:
+                    srcauthor = line.split('=')[0].strip()
+                    dstauthor = line.split('=')[1].strip()
+                    if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
+                        self.ui.status(
+                            'Overriding mapping for author %s, was %s, will be %s\n'
+                            % (srcauthor, self.authors[srcauthor], dstauthor))
+                    else:
+                        self.ui.debug('Mapping author %s to %s\n'
+			    % (srcauthor, dstauthor))
+                    self.authors[srcauthor] = dstauthor
+		    
+                except IndexError:
+                    self.ui.warn(
+                        'Ignoring bad line in author file map %s: %s\n'
+                        % (authorfile, line))
+            afile.close()
+        except IOError:
+            self.ui.warn('Error reading author file map %s.\n' % authorfile)
+
     def copy(self, rev):
         c = self.commitcache[rev]
         files = self.source.getchanges(rev)
@@ -165,6 +208,9 @@
                 desc = self.commitcache[c].desc
                 if "\n" in desc:
                     desc = desc.splitlines()[0]
+                author = self.commitcache[c].author
+                author = self.authors.get(author, author)
+                self.commitcache[c].author = author
                 self.ui.status("%d %s\n" % (num, desc))
                 self.copy(c)
 
@@ -181,6 +227,8 @@
                 # one so we don't end up with extra tag heads
                 if nrev:
                     self.mapentry(c, nrev)
+
+            self.writeauthormap()
         finally:
             self.cleanup()
 
@@ -204,12 +252,17 @@
 
     The <mapfile> is a simple text file that maps each source commit ID to
     the destination ID for that revision, like so:
-
     <source ID> <destination ID>
 
     If the file doesn't exist, it's automatically created.  It's updated
     on each commit copied, so convert-repo can be interrupted and can
     be run repeatedly to copy new commits.
+
+    The [username mapping] file is a simple text file that maps each source
+    commit author to a destination commit author. It is handy for source SCMs
+    that use unix logins to identify authors (eg: CVS). One line per author
+    mapping and the line format is:
+    srcauthor=whatever string you want
     '''
 
     srcc = converter(ui, src)
@@ -257,6 +310,7 @@
 cmdtable = {
     "convert":
         (_convert,
-         [('', 'datesort', None, 'try to sort changesets by date')],
+         [('A', 'authors', '', 'username mapping filename'),
+          ('', 'datesort', None, 'try to sort changesets by date')],
          'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
 }
--- a/hgext/convert/common.py	Thu Jun 14 23:25:55 2007 +0200
+++ b/hgext/convert/common.py	Thu Jun 14 23:25:55 2007 +0200
@@ -62,6 +62,12 @@
         mapping equivalent revision identifiers for each system."""
         raise NotImplementedError()
 
+    def authorfile(self):
+        """Path to a file that will contain lines
+        srcauthor=dstauthor
+        mapping equivalent authors identifiers for each system."""
+        raise NotImplementedError()
+
     def putfile(self, f, e, data):
         """Put file for next putcommit().
         f: path to file
--- a/hgext/convert/hg.py	Thu Jun 14 23:25:55 2007 +0200
+++ b/hgext/convert/hg.py	Thu Jun 14 23:25:55 2007 +0200
@@ -17,6 +17,9 @@
     def mapfile(self):
         return os.path.join(self.path, ".hg", "shamap")
 
+    def authorfile(self):
+        return os.path.join(self.path, ".hg", "authormap")
+
     def getheads(self):
         h = self.repo.changelog.heads()
         return [ hg.hex(x) for x in h ]