##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r5959:0162c6cc merge default
parent child Browse files
Show More
@@ -0,0 +1,54 b''
1 #!/bin/sh
2
3 echo "[extensions]" >> $HGRCPATH
4 echo "hgext.convert = " >> $HGRCPATH
5 echo "[convert]" >> $HGRCPATH
6 echo "hg.tagsbranch=0" >> $HGRCPATH
7
8 hg init source
9 cd source
10 echo a > a
11 hg ci -qAm adda
12 # Add a merge with one parent in the same branch
13 echo a >> a
14 hg ci -qAm changea
15 hg up -qC 0
16 hg branch branch0
17 echo b > b
18 hg ci -qAm addb
19 hg up -qC
20 hg merge
21 hg ci -qm mergeab
22 hg tag -ql mergeab
23 cd ..
24
25 # Miss perl... sometimes
26 cat > filter.py <<EOF
27 import sys, re
28
29 r = re.compile(r'^(?:\d+|pulling from)')
30 sys.stdout.writelines([l for l in sys.stdin if r.search(l)])
31 EOF
32
33 echo % convert
34 hg convert -v --config convert.hg.clonebranches=1 source dest |
35 python filter.py
36
37 # Add a merge with both parents and child in different branches
38 cd source
39 hg branch branch1
40 echo a > file1
41 hg ci -qAm c1
42 hg up -qC mergeab
43 hg branch branch2
44 echo a > file2
45 hg ci -qAm c2
46 hg merge branch1
47 hg branch branch3
48 hg ci -qAm c3
49 cd ..
50
51 echo % incremental conversion
52 hg convert -v --config convert.hg.clonebranches=1 source dest |
53 python filter.py
54
@@ -0,0 +1,29 b''
1 marked working directory as branch branch0
2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 (branch merge, don't forget to commit)
4 % convert
5 3 adda
6 2 addb
7 pulling from default into branch0
8 1 changesets found
9 1 changea
10 0 mergeab
11 pulling from default into branch0
12 1 changesets found
13 marked working directory as branch branch1
14 marked working directory as branch branch2
15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 (branch merge, don't forget to commit)
17 marked working directory as branch branch3
18 % incremental conversion
19 2 c1
20 pulling from branch0 into branch1
21 2 changesets found
22 1 c2
23 pulling from branch0 into branch2
24 2 changesets found
25 0 c3
26 pulling from branch2 into branch3
27 3 changesets found
28 pulling from branch1 into branch3
29 1 changesets found
@@ -1,342 +1,341 b''
1 1 # common code for the convert extension
2 2 import base64, errno
3 3 import os
4 4 import cPickle as pickle
5 5 from mercurial import util
6 6 from mercurial.i18n import _
7 7
8 8 def encodeargs(args):
9 9 def encodearg(s):
10 10 lines = base64.encodestring(s)
11 11 lines = [l.splitlines()[0] for l in lines]
12 12 return ''.join(lines)
13 13
14 14 s = pickle.dumps(args)
15 15 return encodearg(s)
16 16
17 17 def decodeargs(s):
18 18 s = base64.decodestring(s)
19 19 return pickle.loads(s)
20 20
21 21 def checktool(exe, name=None):
22 22 name = name or exe
23 23 if not util.find_exe(exe):
24 24 raise util.Abort('cannot find required "%s" tool' % name)
25 25
26 26 class NoRepo(Exception): pass
27 27
28 28 SKIPREV = 'SKIP'
29 29
30 30 class commit(object):
31 31 def __init__(self, author, date, desc, parents, branch=None, rev=None,
32 32 extra={}):
33 33 self.author = author
34 34 self.date = date
35 35 self.desc = desc
36 36 self.parents = parents
37 37 self.branch = branch
38 38 self.rev = rev
39 39 self.extra = extra
40 40
41 41 class converter_source(object):
42 42 """Conversion source interface"""
43 43
44 44 def __init__(self, ui, path=None, rev=None):
45 45 """Initialize conversion source (or raise NoRepo("message")
46 46 exception if path is not a valid repository)"""
47 47 self.ui = ui
48 48 self.path = path
49 49 self.rev = rev
50 50
51 51 self.encoding = 'utf-8'
52 52
53 53 def before(self):
54 54 pass
55 55
56 56 def after(self):
57 57 pass
58 58
59 59 def setrevmap(self, revmap):
60 60 """set the map of already-converted revisions"""
61 61 pass
62 62
63 63 def getheads(self):
64 64 """Return a list of this repository's heads"""
65 65 raise NotImplementedError()
66 66
67 67 def getfile(self, name, rev):
68 68 """Return file contents as a string"""
69 69 raise NotImplementedError()
70 70
71 71 def getmode(self, name, rev):
72 72 """Return file mode, eg. '', 'x', or 'l'"""
73 73 raise NotImplementedError()
74 74
75 75 def getchanges(self, version):
76 76 """Returns a tuple of (files, copies)
77 77 Files is a sorted list of (filename, id) tuples for all files changed
78 78 in version, where id is the source revision id of the file.
79 79
80 80 copies is a dictionary of dest: source
81 81 """
82 82 raise NotImplementedError()
83 83
84 84 def getcommit(self, version):
85 85 """Return the commit object for version"""
86 86 raise NotImplementedError()
87 87
88 88 def gettags(self):
89 89 """Return the tags as a dictionary of name: revision"""
90 90 raise NotImplementedError()
91 91
92 92 def recode(self, s, encoding=None):
93 93 if not encoding:
94 94 encoding = self.encoding or 'utf-8'
95 95
96 96 if isinstance(s, unicode):
97 97 return s.encode("utf-8")
98 98 try:
99 99 return s.decode(encoding).encode("utf-8")
100 100 except:
101 101 try:
102 102 return s.decode("latin-1").encode("utf-8")
103 103 except:
104 104 return s.decode(encoding, "replace").encode("utf-8")
105 105
106 106 def getchangedfiles(self, rev, i):
107 107 """Return the files changed by rev compared to parent[i].
108 108
109 109 i is an index selecting one of the parents of rev. The return
110 110 value should be the list of files that are different in rev and
111 111 this parent.
112 112
113 113 If rev has no parents, i is None.
114 114
115 115 This function is only needed to support --filemap
116 116 """
117 117 raise NotImplementedError()
118 118
119 119 def converted(self, rev, sinkrev):
120 120 '''Notify the source that a revision has been converted.'''
121 121 pass
122 122
123 123
124 124 class converter_sink(object):
125 125 """Conversion sink (target) interface"""
126 126
127 127 def __init__(self, ui, path):
128 128 """Initialize conversion sink (or raise NoRepo("message")
129 129 exception if path is not a valid repository)
130 130
131 131 created is a list of paths to remove if a fatal error occurs
132 132 later"""
133 133 self.ui = ui
134 134 self.path = path
135 135 self.created = []
136 136
137 137 def getheads(self):
138 138 """Return a list of this repository's heads"""
139 139 raise NotImplementedError()
140 140
141 141 def revmapfile(self):
142 142 """Path to a file that will contain lines
143 143 source_rev_id sink_rev_id
144 144 mapping equivalent revision identifiers for each system."""
145 145 raise NotImplementedError()
146 146
147 147 def authorfile(self):
148 148 """Path to a file that will contain lines
149 149 srcauthor=dstauthor
150 150 mapping equivalent authors identifiers for each system."""
151 151 return None
152 152
153 153 def putfile(self, f, e, data):
154 154 """Put file for next putcommit().
155 155 f: path to file
156 156 e: '', 'x', or 'l' (regular file, executable, or symlink)
157 157 data: file contents"""
158 158 raise NotImplementedError()
159 159
160 160 def delfile(self, f):
161 161 """Delete file for next putcommit().
162 162 f: path to file"""
163 163 raise NotImplementedError()
164 164
165 165 def putcommit(self, files, parents, commit):
166 166 """Create a revision with all changed files listed in 'files'
167 167 and having listed parents. 'commit' is a commit object containing
168 168 at a minimum the author, date, and message for this changeset.
169 169 Called after putfile() and delfile() calls. Note that the sink
170 170 repository is not told to update itself to a particular revision
171 171 (or even what that revision would be) before it receives the
172 172 file data."""
173 173 raise NotImplementedError()
174 174
175 175 def puttags(self, tags):
176 176 """Put tags into sink.
177 177 tags: {tagname: sink_rev_id, ...}"""
178 178 raise NotImplementedError()
179 179
180 def setbranch(self, branch, pbranch, parents):
180 def setbranch(self, branch, pbranches):
181 181 """Set the current branch name. Called before the first putfile
182 182 on the branch.
183 183 branch: branch name for subsequent commits
184 pbranch: branch name of parent commit
185 parents: destination revisions of parent"""
184 pbranches: (converted parent revision, parent branch) tuples"""
186 185 pass
187 186
188 187 def setfilemapmode(self, active):
189 188 """Tell the destination that we're using a filemap
190 189
191 190 Some converter_sources (svn in particular) can claim that a file
192 191 was changed in a revision, even if there was no change. This method
193 192 tells the destination that we're using a filemap and that it should
194 193 filter empty revisions.
195 194 """
196 195 pass
197 196
198 197 def before(self):
199 198 pass
200 199
201 200 def after(self):
202 201 pass
203 202
204 203
205 204 class commandline(object):
206 205 def __init__(self, ui, command):
207 206 self.ui = ui
208 207 self.command = command
209 208
210 209 def prerun(self):
211 210 pass
212 211
213 212 def postrun(self):
214 213 pass
215 214
216 215 def _cmdline(self, cmd, *args, **kwargs):
217 216 cmdline = [self.command, cmd] + list(args)
218 217 for k, v in kwargs.iteritems():
219 218 if len(k) == 1:
220 219 cmdline.append('-' + k)
221 220 else:
222 221 cmdline.append('--' + k.replace('_', '-'))
223 222 try:
224 223 if len(k) == 1:
225 224 cmdline.append('' + v)
226 225 else:
227 226 cmdline[-1] += '=' + v
228 227 except TypeError:
229 228 pass
230 229 cmdline = [util.shellquote(arg) for arg in cmdline]
231 230 cmdline += ['<', util.nulldev]
232 231 cmdline = ' '.join(cmdline)
233 232 self.ui.debug(cmdline, '\n')
234 233 return cmdline
235 234
236 235 def _run(self, cmd, *args, **kwargs):
237 236 cmdline = self._cmdline(cmd, *args, **kwargs)
238 237 self.prerun()
239 238 try:
240 239 return util.popen(cmdline)
241 240 finally:
242 241 self.postrun()
243 242
244 243 def run(self, cmd, *args, **kwargs):
245 244 fp = self._run(cmd, *args, **kwargs)
246 245 output = fp.read()
247 246 self.ui.debug(output)
248 247 return output, fp.close()
249 248
250 249 def checkexit(self, status, output=''):
251 250 if status:
252 251 if output:
253 252 self.ui.warn(_('%s error:\n') % self.command)
254 253 self.ui.warn(output)
255 254 msg = util.explain_exit(status)[0]
256 255 raise util.Abort(_('%s %s') % (self.command, msg))
257 256
258 257 def run0(self, cmd, *args, **kwargs):
259 258 output, status = self.run(cmd, *args, **kwargs)
260 259 self.checkexit(status, output)
261 260 return output
262 261
263 262 def getargmax(self):
264 263 if '_argmax' in self.__dict__:
265 264 return self._argmax
266 265
267 266 # POSIX requires at least 4096 bytes for ARG_MAX
268 267 self._argmax = 4096
269 268 try:
270 269 self._argmax = os.sysconf("SC_ARG_MAX")
271 270 except:
272 271 pass
273 272
274 273 # Windows shells impose their own limits on command line length,
275 274 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
276 275 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
277 276 # details about cmd.exe limitations.
278 277
279 278 # Since ARG_MAX is for command line _and_ environment, lower our limit
280 279 # (and make happy Windows shells while doing this).
281 280
282 281 self._argmax = self._argmax/2 - 1
283 282 return self._argmax
284 283
285 284 def limit_arglist(self, arglist, cmd, *args, **kwargs):
286 285 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
287 286 bytes = 0
288 287 fl = []
289 288 for fn in arglist:
290 289 b = len(fn) + 3
291 290 if bytes + b < limit or len(fl) == 0:
292 291 fl.append(fn)
293 292 bytes += b
294 293 else:
295 294 yield fl
296 295 fl = [fn]
297 296 bytes = b
298 297 if fl:
299 298 yield fl
300 299
301 300 def xargs(self, arglist, cmd, *args, **kwargs):
302 301 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
303 302 self.run0(cmd, *(list(args) + l), **kwargs)
304 303
305 304 class mapfile(dict):
306 305 def __init__(self, ui, path):
307 306 super(mapfile, self).__init__()
308 307 self.ui = ui
309 308 self.path = path
310 309 self.fp = None
311 310 self.order = []
312 311 self._read()
313 312
314 313 def _read(self):
315 314 try:
316 315 fp = open(self.path, 'r')
317 316 except IOError, err:
318 317 if err.errno != errno.ENOENT:
319 318 raise
320 319 return
321 320 for line in fp:
322 321 key, value = line[:-1].split(' ', 1)
323 322 if key not in self:
324 323 self.order.append(key)
325 324 super(mapfile, self).__setitem__(key, value)
326 325 fp.close()
327 326
328 327 def __setitem__(self, key, value):
329 328 if self.fp is None:
330 329 try:
331 330 self.fp = open(self.path, 'a')
332 331 except IOError, err:
333 332 raise util.Abort(_('could not open map file %r: %s') %
334 333 (self.path, err.strerror))
335 334 self.fp.write('%s %s\n' % (key, value))
336 335 self.fp.flush()
337 336 super(mapfile, self).__setitem__(key, value)
338 337
339 338 def close(self):
340 339 if self.fp:
341 340 self.fp.close()
342 341 self.fp = None
@@ -1,321 +1,321 b''
1 1 # convcmd - convert extension commands definition
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
9 9 from cvs import convert_cvs
10 10 from darcs import darcs_source
11 11 from git import convert_git
12 12 from hg import mercurial_source, mercurial_sink
13 13 from subversion import debugsvnlog, svn_source, svn_sink
14 14 import filemap
15 15
16 16 import os, shutil
17 17 from mercurial import hg, util
18 18 from mercurial.i18n import _
19 19
20 20 source_converters = [
21 21 ('cvs', convert_cvs),
22 22 ('git', convert_git),
23 23 ('svn', svn_source),
24 24 ('hg', mercurial_source),
25 25 ('darcs', darcs_source),
26 26 ]
27 27
28 28 sink_converters = [
29 29 ('hg', mercurial_sink),
30 30 ('svn', svn_sink),
31 31 ]
32 32
33 33 def convertsource(ui, path, type, rev):
34 34 exceptions = []
35 35 for name, source in source_converters:
36 36 try:
37 37 if not type or name == type:
38 38 return source(ui, path, rev)
39 39 except NoRepo, inst:
40 40 exceptions.append(inst)
41 41 if not ui.quiet:
42 42 for inst in exceptions:
43 43 ui.write(_("%s\n") % inst)
44 44 raise util.Abort('%s: unknown repository type' % path)
45 45
46 46 def convertsink(ui, path, type):
47 47 for name, sink in sink_converters:
48 48 try:
49 49 if not type or name == type:
50 50 return sink(ui, path)
51 51 except NoRepo, inst:
52 52 ui.note(_("convert: %s\n") % inst)
53 53 raise util.Abort('%s: unknown repository type' % path)
54 54
55 55 class converter(object):
56 56 def __init__(self, ui, source, dest, revmapfile, opts):
57 57
58 58 self.source = source
59 59 self.dest = dest
60 60 self.ui = ui
61 61 self.opts = opts
62 62 self.commitcache = {}
63 63 self.authors = {}
64 64 self.authorfile = None
65 65
66 66 self.map = mapfile(ui, revmapfile)
67 67
68 68 # Read first the dst author map if any
69 69 authorfile = self.dest.authorfile()
70 70 if authorfile and os.path.exists(authorfile):
71 71 self.readauthormap(authorfile)
72 72 # Extend/Override with new author map if necessary
73 73 if opts.get('authors'):
74 74 self.readauthormap(opts.get('authors'))
75 75 self.authorfile = self.dest.authorfile()
76 76
77 77 def walktree(self, heads):
78 78 '''Return a mapping that identifies the uncommitted parents of every
79 79 uncommitted changeset.'''
80 80 visit = heads
81 81 known = {}
82 82 parents = {}
83 83 while visit:
84 84 n = visit.pop(0)
85 85 if n in known or n in self.map: continue
86 86 known[n] = 1
87 87 commit = self.cachecommit(n)
88 88 parents[n] = []
89 89 for p in commit.parents:
90 90 parents[n].append(p)
91 91 visit.append(p)
92 92
93 93 return parents
94 94
95 95 def toposort(self, parents):
96 96 '''Return an ordering such that every uncommitted changeset is
97 97 preceeded by all its uncommitted ancestors.'''
98 98 visit = parents.keys()
99 99 seen = {}
100 100 children = {}
101 101
102 102 while visit:
103 103 n = visit.pop(0)
104 104 if n in seen: continue
105 105 seen[n] = 1
106 106 # Ensure that nodes without parents are present in the 'children'
107 107 # mapping.
108 108 children.setdefault(n, [])
109 109 for p in parents[n]:
110 110 if not p in self.map:
111 111 visit.append(p)
112 112 children.setdefault(p, []).append(n)
113 113
114 114 s = []
115 115 removed = {}
116 116 visit = children.keys()
117 117 while visit:
118 118 n = visit.pop(0)
119 119 if n in removed: continue
120 120 dep = 0
121 121 if n in parents:
122 122 for p in parents[n]:
123 123 if p in self.map: continue
124 124 if p not in removed:
125 125 # we're still dependent
126 126 visit.append(n)
127 127 dep = 1
128 128 break
129 129
130 130 if not dep:
131 131 # all n's parents are in the list
132 132 removed[n] = 1
133 133 if n not in self.map:
134 134 s.append(n)
135 135 if n in children:
136 136 for c in children[n]:
137 137 visit.insert(0, c)
138 138
139 139 if self.opts.get('datesort'):
140 140 depth = {}
141 141 for n in s:
142 142 depth[n] = 0
143 143 pl = [p for p in self.commitcache[n].parents
144 144 if p not in self.map]
145 145 if pl:
146 146 depth[n] = max([depth[p] for p in pl]) + 1
147 147
148 148 s = [(depth[n], util.parsedate(self.commitcache[n].date), n)
149 149 for n in s]
150 150 s.sort()
151 151 s = [e[2] for e in s]
152 152
153 153 return s
154 154
155 155 def writeauthormap(self):
156 156 authorfile = self.authorfile
157 157 if authorfile:
158 158 self.ui.status('Writing author map file %s\n' % authorfile)
159 159 ofile = open(authorfile, 'w+')
160 160 for author in self.authors:
161 161 ofile.write("%s=%s\n" % (author, self.authors[author]))
162 162 ofile.close()
163 163
164 164 def readauthormap(self, authorfile):
165 165 afile = open(authorfile, 'r')
166 166 for line in afile:
167 167 try:
168 168 srcauthor = line.split('=')[0].strip()
169 169 dstauthor = line.split('=')[1].strip()
170 170 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
171 171 self.ui.status(
172 172 'Overriding mapping for author %s, was %s, will be %s\n'
173 173 % (srcauthor, self.authors[srcauthor], dstauthor))
174 174 else:
175 175 self.ui.debug('Mapping author %s to %s\n'
176 176 % (srcauthor, dstauthor))
177 177 self.authors[srcauthor] = dstauthor
178 178 except IndexError:
179 179 self.ui.warn(
180 180 'Ignoring bad line in author file map %s: %s\n'
181 181 % (authorfile, line))
182 182 afile.close()
183 183
184 184 def cachecommit(self, rev):
185 185 commit = self.source.getcommit(rev)
186 186 commit.author = self.authors.get(commit.author, commit.author)
187 187 self.commitcache[rev] = commit
188 188 return commit
189 189
190 190 def copy(self, rev):
191 191 commit = self.commitcache[rev]
192 192 do_copies = hasattr(self.dest, 'copyfile')
193 193 filenames = []
194 194
195 195 changes = self.source.getchanges(rev)
196 196 if isinstance(changes, basestring):
197 197 if changes == SKIPREV:
198 198 dest = SKIPREV
199 199 else:
200 200 dest = self.map[changes]
201 201 self.map[rev] = dest
202 202 return
203 203 files, copies = changes
204 parents = [self.map[r] for r in commit.parents]
204 pbranches = []
205 205 if commit.parents:
206 prev = commit.parents[0]
207 if prev not in self.commitcache:
208 self.cachecommit(prev)
209 pbranch = self.commitcache[prev].branch
210 else:
211 pbranch = None
212 self.dest.setbranch(commit.branch, pbranch, parents)
206 for prev in commit.parents:
207 if prev not in self.commitcache:
208 self.cachecommit(prev)
209 pbranches.append((self.map[prev],
210 self.commitcache[prev].branch))
211 self.dest.setbranch(commit.branch, pbranches)
213 212 for f, v in files:
214 213 filenames.append(f)
215 214 try:
216 215 data = self.source.getfile(f, v)
217 216 except IOError, inst:
218 217 self.dest.delfile(f)
219 218 else:
220 219 e = self.source.getmode(f, v)
221 220 self.dest.putfile(f, e, data)
222 221 if do_copies:
223 222 if f in copies:
224 223 copyf = copies[f]
225 224 # Merely marks that a copy happened.
226 225 self.dest.copyfile(copyf, f)
227 226
227 parents = [b[0] for b in pbranches]
228 228 newnode = self.dest.putcommit(filenames, parents, commit)
229 229 self.source.converted(rev, newnode)
230 230 self.map[rev] = newnode
231 231
232 232 def convert(self):
233 233
234 234 def recode(s):
235 235 return s.decode('utf-8').encode(orig_encoding, 'replace')
236 236
237 237 try:
238 238 self.source.before()
239 239 self.dest.before()
240 240 self.source.setrevmap(self.map)
241 241 self.ui.status("scanning source...\n")
242 242 heads = self.source.getheads()
243 243 parents = self.walktree(heads)
244 244 self.ui.status("sorting...\n")
245 245 t = self.toposort(parents)
246 246 num = len(t)
247 247 c = None
248 248
249 249 self.ui.status("converting...\n")
250 250 for c in t:
251 251 num -= 1
252 252 desc = self.commitcache[c].desc
253 253 if "\n" in desc:
254 254 desc = desc.splitlines()[0]
255 255 # convert log message to local encoding without using
256 256 # tolocal() because util._encoding conver() use it as
257 257 # 'utf-8'
258 258 self.ui.status("%d %s\n" % (num, recode(desc)))
259 259 self.ui.note(_("source: %s\n" % recode(c)))
260 260 self.copy(c)
261 261
262 262 tags = self.source.gettags()
263 263 ctags = {}
264 264 for k in tags:
265 265 v = tags[k]
266 266 if self.map.get(v, SKIPREV) != SKIPREV:
267 267 ctags[k] = self.map[v]
268 268
269 269 if c and ctags:
270 270 nrev = self.dest.puttags(ctags)
271 271 # write another hash correspondence to override the previous
272 272 # one so we don't end up with extra tag heads
273 273 if nrev:
274 274 self.map[c] = nrev
275 275
276 276 self.writeauthormap()
277 277 finally:
278 278 self.cleanup()
279 279
280 280 def cleanup(self):
281 281 try:
282 282 self.dest.after()
283 283 finally:
284 284 self.source.after()
285 285 self.map.close()
286 286
287 287 orig_encoding = 'ascii'
288 288
289 289 def convert(ui, src, dest=None, revmapfile=None, **opts):
290 290 global orig_encoding
291 291 orig_encoding = util._encoding
292 292 util._encoding = 'UTF-8'
293 293
294 294 if not dest:
295 295 dest = hg.defaultdest(src) + "-hg"
296 296 ui.status("assuming destination %s\n" % dest)
297 297
298 298 destc = convertsink(ui, dest, opts.get('dest_type'))
299 299
300 300 try:
301 301 srcc = convertsource(ui, src, opts.get('source_type'),
302 302 opts.get('rev'))
303 303 except Exception:
304 304 for path in destc.created:
305 305 shutil.rmtree(path, True)
306 306 raise
307 307
308 308 fmap = opts.get('filemap')
309 309 if fmap:
310 310 srcc = filemap.filemap_source(ui, srcc, fmap)
311 311 destc.setfilemapmode(True)
312 312
313 313 if not revmapfile:
314 314 try:
315 315 revmapfile = destc.revmapfile()
316 316 except:
317 317 revmapfile = os.path.join(destc, "map")
318 318
319 319 c = converter(ui, srcc, destc, revmapfile, opts)
320 320 c.convert()
321 321
@@ -1,288 +1,301 b''
1 1 # hg backend for convert extension
2 2
3 3 # Notes for hg->hg conversion:
4 4 #
5 5 # * Old versions of Mercurial didn't trim the whitespace from the ends
6 6 # of commit messages, but new versions do. Changesets created by
7 7 # those older versions, then converted, may thus have different
8 8 # hashes for changesets that are otherwise identical.
9 9 #
10 10 # * By default, the source revision is stored in the converted
11 11 # revision. This will cause the converted revision to have a
12 12 # different identity than the source. To avoid this, use the
13 13 # following option: "--config convert.hg.saverev=false"
14 14
15 15
16 16 import os, time
17 17 from mercurial.i18n import _
18 18 from mercurial.node import *
19 19 from mercurial import hg, lock, revlog, util
20 20
21 21 from common import NoRepo, commit, converter_source, converter_sink
22 22
23 23 class mercurial_sink(converter_sink):
24 24 def __init__(self, ui, path):
25 25 converter_sink.__init__(self, ui, path)
26 26 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
27 27 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
28 28 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
29 29 self.lastbranch = None
30 30 if os.path.isdir(path) and len(os.listdir(path)) > 0:
31 31 try:
32 32 self.repo = hg.repository(self.ui, path)
33 33 if not self.repo.local():
34 34 raise NoRepo(_('%s is not a local Mercurial repo') % path)
35 35 except hg.RepoError, err:
36 36 ui.print_exc()
37 37 raise NoRepo(err.args[0])
38 38 else:
39 39 try:
40 40 ui.status(_('initializing destination %s repository\n') % path)
41 41 self.repo = hg.repository(self.ui, path, create=True)
42 42 if not self.repo.local():
43 43 raise NoRepo(_('%s is not a local Mercurial repo') % path)
44 44 self.created.append(path)
45 45 except hg.RepoError, err:
46 46 ui.print_exc()
47 47 raise NoRepo("could not create hg repo %s as sink" % path)
48 48 self.lock = None
49 49 self.wlock = None
50 50 self.filemapmode = False
51 51
52 52 def before(self):
53 53 self.ui.debug(_('run hg sink pre-conversion action\n'))
54 54 self.wlock = self.repo.wlock()
55 55 self.lock = self.repo.lock()
56 56 self.repo.dirstate.clear()
57 57
58 58 def after(self):
59 59 self.ui.debug(_('run hg sink post-conversion action\n'))
60 60 self.repo.dirstate.invalidate()
61 61 self.lock = None
62 62 self.wlock = None
63 63
64 64 def revmapfile(self):
65 65 return os.path.join(self.path, ".hg", "shamap")
66 66
67 67 def authorfile(self):
68 68 return os.path.join(self.path, ".hg", "authormap")
69 69
70 70 def getheads(self):
71 71 h = self.repo.changelog.heads()
72 72 return [ hex(x) for x in h ]
73 73
74 74 def putfile(self, f, e, data):
75 75 self.repo.wwrite(f, data, e)
76 76 if f not in self.repo.dirstate:
77 77 self.repo.dirstate.normallookup(f)
78 78
79 79 def copyfile(self, source, dest):
80 80 self.repo.copy(source, dest)
81 81
82 82 def delfile(self, f):
83 83 try:
84 84 util.unlink(self.repo.wjoin(f))
85 85 #self.repo.remove([f])
86 86 except OSError:
87 87 pass
88 88
89 def setbranch(self, branch, pbranch, parents):
90 if (not self.clonebranches) or (branch == self.lastbranch):
89 def setbranch(self, branch, pbranches):
90 if not self.clonebranches:
91 91 return
92 92
93 setbranch = (branch != self.lastbranch)
93 94 self.lastbranch = branch
94 self.after()
95 95 if not branch:
96 96 branch = 'default'
97 if not pbranch:
98 pbranch = 'default'
97 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
98 pbranch = pbranches and pbranches[0][1] or 'default'
99 99
100 100 branchpath = os.path.join(self.path, branch)
101 try:
102 self.repo = hg.repository(self.ui, branchpath)
103 except:
104 if not parents:
101 if setbranch:
102 self.after()
103 try:
104 self.repo = hg.repository(self.ui, branchpath)
105 except:
105 106 self.repo = hg.repository(self.ui, branchpath, create=True)
106 else:
107 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
108 hg.clone(self.ui, os.path.join(self.path, pbranch),
109 branchpath, rev=parents, update=False,
110 stream=True)
111 self.repo = hg.repository(self.ui, branchpath)
112 self.before()
107 self.before()
108
109 # pbranches may bring revisions from other branches (merge parents)
110 # Make sure we have them, or pull them.
111 missings = {}
112 for b in pbranches:
113 try:
114 self.repo.lookup(b[0])
115 except:
116 missings.setdefault(b[1], []).append(b[0])
117
118 if missings:
119 self.after()
120 for pbranch, heads in missings.iteritems():
121 pbranchpath = os.path.join(self.path, pbranch)
122 prepo = hg.repository(self.ui, pbranchpath)
123 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
124 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
125 self.before()
113 126
114 127 def putcommit(self, files, parents, commit):
115 128 seen = {}
116 129 pl = []
117 130 for p in parents:
118 131 if p not in seen:
119 132 pl.append(p)
120 133 seen[p] = 1
121 134 parents = pl
122 135 nparents = len(parents)
123 136 if self.filemapmode and nparents == 1:
124 137 m1node = self.repo.changelog.read(bin(parents[0]))[0]
125 138 parent = parents[0]
126 139
127 140 if len(parents) < 2: parents.append("0" * 40)
128 141 if len(parents) < 2: parents.append("0" * 40)
129 142 p2 = parents.pop(0)
130 143
131 144 text = commit.desc
132 145 extra = commit.extra.copy()
133 146 if self.branchnames and commit.branch:
134 147 extra['branch'] = commit.branch
135 148 if commit.rev:
136 149 extra['convert_revision'] = commit.rev
137 150
138 151 while parents:
139 152 p1 = p2
140 153 p2 = parents.pop(0)
141 154 a = self.repo.rawcommit(files, text, commit.author, commit.date,
142 155 bin(p1), bin(p2), extra=extra)
143 156 self.repo.dirstate.clear()
144 157 text = "(octopus merge fixup)\n"
145 158 p2 = hg.hex(self.repo.changelog.tip())
146 159
147 160 if self.filemapmode and nparents == 1:
148 161 man = self.repo.manifest
149 162 mnode = self.repo.changelog.read(bin(p2))[0]
150 163 if not man.cmp(m1node, man.revision(mnode)):
151 164 self.repo.rollback()
152 165 self.repo.dirstate.clear()
153 166 return parent
154 167 return p2
155 168
156 169 def puttags(self, tags):
157 170 try:
158 171 old = self.repo.wfile(".hgtags").read()
159 172 oldlines = old.splitlines(1)
160 173 oldlines.sort()
161 174 except:
162 175 oldlines = []
163 176
164 177 k = tags.keys()
165 178 k.sort()
166 179 newlines = []
167 180 for tag in k:
168 181 newlines.append("%s %s\n" % (tags[tag], tag))
169 182
170 183 newlines.sort()
171 184
172 185 if newlines != oldlines:
173 186 self.ui.status("updating tags\n")
174 187 f = self.repo.wfile(".hgtags", "w")
175 188 f.write("".join(newlines))
176 189 f.close()
177 190 if not oldlines: self.repo.add([".hgtags"])
178 191 date = "%s 0" % int(time.mktime(time.gmtime()))
179 192 extra = {}
180 193 if self.tagsbranch != 'default':
181 194 extra['branch'] = self.tagsbranch
182 195 try:
183 196 tagparent = self.repo.changectx(self.tagsbranch).node()
184 197 except hg.RepoError, inst:
185 198 tagparent = nullid
186 199 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
187 200 date, tagparent, nullid, extra=extra)
188 201 return hex(self.repo.changelog.tip())
189 202
190 203 def setfilemapmode(self, active):
191 204 self.filemapmode = active
192 205
193 206 class mercurial_source(converter_source):
194 207 def __init__(self, ui, path, rev=None):
195 208 converter_source.__init__(self, ui, path, rev)
196 209 self.saverev = ui.configbool('convert', 'hg.saverev', True)
197 210 try:
198 211 self.repo = hg.repository(self.ui, path)
199 212 # try to provoke an exception if this isn't really a hg
200 213 # repo, but some other bogus compatible-looking url
201 214 if not self.repo.local():
202 215 raise hg.RepoError()
203 216 except hg.RepoError:
204 217 ui.print_exc()
205 218 raise NoRepo("%s is not a local Mercurial repo" % path)
206 219 self.lastrev = None
207 220 self.lastctx = None
208 221 self._changescache = None
209 222 self.convertfp = None
210 223
211 224 def changectx(self, rev):
212 225 if self.lastrev != rev:
213 226 self.lastctx = self.repo.changectx(rev)
214 227 self.lastrev = rev
215 228 return self.lastctx
216 229
217 230 def getheads(self):
218 231 if self.rev:
219 232 return [hex(self.repo.changectx(self.rev).node())]
220 233 else:
221 234 return [hex(node) for node in self.repo.heads()]
222 235
223 236 def getfile(self, name, rev):
224 237 try:
225 238 return self.changectx(rev).filectx(name).data()
226 239 except revlog.LookupError, err:
227 240 raise IOError(err)
228 241
229 242 def getmode(self, name, rev):
230 243 m = self.changectx(rev).manifest()
231 244 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
232 245
233 246 def getchanges(self, rev):
234 247 ctx = self.changectx(rev)
235 248 if self._changescache and self._changescache[0] == rev:
236 249 m, a, r = self._changescache[1]
237 250 else:
238 251 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
239 252 changes = [(name, rev) for name in m + a + r]
240 253 changes.sort()
241 254 return (changes, self.getcopies(ctx, m + a))
242 255
243 256 def getcopies(self, ctx, files):
244 257 copies = {}
245 258 for name in files:
246 259 try:
247 260 copies[name] = ctx.filectx(name).renamed()[0]
248 261 except TypeError:
249 262 pass
250 263 return copies
251 264
252 265 def getcommit(self, rev):
253 266 ctx = self.changectx(rev)
254 267 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
255 268 if self.saverev:
256 269 crev = rev
257 270 else:
258 271 crev = None
259 272 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
260 273 desc=ctx.description(), rev=crev, parents=parents,
261 274 branch=ctx.branch(), extra=ctx.extra())
262 275
263 276 def gettags(self):
264 277 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
265 278 return dict([(name, hex(node)) for name, node in tags])
266 279
267 280 def getchangedfiles(self, rev, i):
268 281 ctx = self.changectx(rev)
269 282 i = i or 0
270 283 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
271 284
272 285 if i == 0:
273 286 self._changescache = (rev, changes)
274 287
275 288 return changes[0] + changes[1] + changes[2]
276 289
277 290 def converted(self, rev, destrev):
278 291 if self.convertfp is None:
279 292 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
280 293 'a')
281 294 self.convertfp.write('%s %s\n' % (destrev, rev))
282 295 self.convertfp.flush()
283 296
284 297 def before(self):
285 298 self.ui.debug(_('run hg source pre-conversion action\n'))
286 299
287 300 def after(self):
288 301 self.ui.debug(_('run hg source post-conversion action\n'))
General Comments 0
You need to be logged in to leave comments. Login now