Mercurial > hg
comparison hgext/convert/__init__.py @ 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 |
comparison
equal
deleted
inserted
replaced
4588:9855939d0c82 | 4589:451e91ed535e |
---|---|
35 self.ui = ui | 35 self.ui = ui |
36 self.opts = opts | 36 self.opts = opts |
37 self.commitcache = {} | 37 self.commitcache = {} |
38 self.mapfile = mapfile | 38 self.mapfile = mapfile |
39 self.mapfilefd = None | 39 self.mapfilefd = None |
40 self.authors = {} | |
41 self.writeauthors = False | |
40 | 42 |
41 self.map = {} | 43 self.map = {} |
42 try: | 44 try: |
43 origmapfile = open(self.mapfile, 'r') | 45 origmapfile = open(self.mapfile, 'r') |
44 for l in origmapfile: | 46 for l in origmapfile: |
45 sv, dv = l[:-1].split() | 47 sv, dv = l[:-1].split() |
46 self.map[sv] = dv | 48 self.map[sv] = dv |
47 origmapfile.close() | 49 origmapfile.close() |
48 except IOError: | 50 except IOError: |
49 pass | 51 pass |
52 | |
53 # Read first the dst author map if any | |
54 if hasattr(self.dest, 'authorfile'): | |
55 self.readauthormap(self.dest.authorfile()) | |
56 # Extend/Override with new author map if necessary | |
57 if 'authors' in opts: | |
58 self.readauthormap(opts.get('authors')) | |
59 self.writeauthors = True | |
50 | 60 |
51 def walktree(self, heads): | 61 def walktree(self, heads): |
52 visit = heads | 62 visit = heads |
53 known = {} | 63 known = {} |
54 parents = {} | 64 parents = {} |
129 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.mapfile, errno, strerror)) | 139 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.mapfile, errno, strerror)) |
130 self.map[src] = dst | 140 self.map[src] = dst |
131 self.mapfilefd.write("%s %s\n" % (src, dst)) | 141 self.mapfilefd.write("%s %s\n" % (src, dst)) |
132 self.mapfilefd.flush() | 142 self.mapfilefd.flush() |
133 | 143 |
144 def writeauthormap(self): | |
145 if self.writeauthors == True and len(self.authors) > 0 and hasattr(self.dest, 'authorfile'): | |
146 authorfile = self.dest.authorfile() | |
147 self.ui.status('Writing author map file %s\n' % authorfile) | |
148 ofile = open(authorfile, 'w+') | |
149 for author in self.authors: | |
150 ofile.write("%s=%s\n" % (author, self.authors[author])) | |
151 ofile.close() | |
152 | |
153 def readauthormap(self, authorfile): | |
154 try: | |
155 afile = open(authorfile, 'r') | |
156 for line in afile: | |
157 try: | |
158 srcauthor = line.split('=')[0].strip() | |
159 dstauthor = line.split('=')[1].strip() | |
160 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]: | |
161 self.ui.status( | |
162 'Overriding mapping for author %s, was %s, will be %s\n' | |
163 % (srcauthor, self.authors[srcauthor], dstauthor)) | |
164 else: | |
165 self.ui.debug('Mapping author %s to %s\n' | |
166 % (srcauthor, dstauthor)) | |
167 self.authors[srcauthor] = dstauthor | |
168 | |
169 except IndexError: | |
170 self.ui.warn( | |
171 'Ignoring bad line in author file map %s: %s\n' | |
172 % (authorfile, line)) | |
173 afile.close() | |
174 except IOError: | |
175 self.ui.warn('Error reading author file map %s.\n' % authorfile) | |
176 | |
134 def copy(self, rev): | 177 def copy(self, rev): |
135 c = self.commitcache[rev] | 178 c = self.commitcache[rev] |
136 files = self.source.getchanges(rev) | 179 files = self.source.getchanges(rev) |
137 | 180 |
138 for f, v in files: | 181 for f, v in files: |
163 for c in t: | 206 for c in t: |
164 num -= 1 | 207 num -= 1 |
165 desc = self.commitcache[c].desc | 208 desc = self.commitcache[c].desc |
166 if "\n" in desc: | 209 if "\n" in desc: |
167 desc = desc.splitlines()[0] | 210 desc = desc.splitlines()[0] |
211 author = self.commitcache[c].author | |
212 author = self.authors.get(author, author) | |
213 self.commitcache[c].author = author | |
168 self.ui.status("%d %s\n" % (num, desc)) | 214 self.ui.status("%d %s\n" % (num, desc)) |
169 self.copy(c) | 215 self.copy(c) |
170 | 216 |
171 tags = self.source.gettags() | 217 tags = self.source.gettags() |
172 ctags = {} | 218 ctags = {} |
179 nrev = self.dest.puttags(ctags) | 225 nrev = self.dest.puttags(ctags) |
180 # write another hash correspondence to override the previous | 226 # write another hash correspondence to override the previous |
181 # one so we don't end up with extra tag heads | 227 # one so we don't end up with extra tag heads |
182 if nrev: | 228 if nrev: |
183 self.mapentry(c, nrev) | 229 self.mapentry(c, nrev) |
230 | |
231 self.writeauthormap() | |
184 finally: | 232 finally: |
185 self.cleanup() | 233 self.cleanup() |
186 | 234 |
187 def cleanup(self): | 235 def cleanup(self): |
188 if self.mapfilefd: | 236 if self.mapfilefd: |
202 be created. If <mapfile> isn't given, it will be put in a default | 250 be created. If <mapfile> isn't given, it will be put in a default |
203 location (<dest>/.hg/shamap by default) | 251 location (<dest>/.hg/shamap by default) |
204 | 252 |
205 The <mapfile> is a simple text file that maps each source commit ID to | 253 The <mapfile> is a simple text file that maps each source commit ID to |
206 the destination ID for that revision, like so: | 254 the destination ID for that revision, like so: |
207 | |
208 <source ID> <destination ID> | 255 <source ID> <destination ID> |
209 | 256 |
210 If the file doesn't exist, it's automatically created. It's updated | 257 If the file doesn't exist, it's automatically created. It's updated |
211 on each commit copied, so convert-repo can be interrupted and can | 258 on each commit copied, so convert-repo can be interrupted and can |
212 be run repeatedly to copy new commits. | 259 be run repeatedly to copy new commits. |
260 | |
261 The [username mapping] file is a simple text file that maps each source | |
262 commit author to a destination commit author. It is handy for source SCMs | |
263 that use unix logins to identify authors (eg: CVS). One line per author | |
264 mapping and the line format is: | |
265 srcauthor=whatever string you want | |
213 ''' | 266 ''' |
214 | 267 |
215 srcc = converter(ui, src) | 268 srcc = converter(ui, src) |
216 if not hasattr(srcc, "getcommit"): | 269 if not hasattr(srcc, "getcommit"): |
217 raise util.Abort("%s: can't read from this repo type" % src) | 270 raise util.Abort("%s: can't read from this repo type" % src) |
255 c.convert() | 308 c.convert() |
256 | 309 |
257 cmdtable = { | 310 cmdtable = { |
258 "convert": | 311 "convert": |
259 (_convert, | 312 (_convert, |
260 [('', 'datesort', None, 'try to sort changesets by date')], | 313 [('A', 'authors', '', 'username mapping filename'), |
314 ('', 'datesort', None, 'try to sort changesets by date')], | |
261 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'), | 315 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'), |
262 } | 316 } |