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.
--- 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 ]