##// END OF EJS Templates
convert: hg.clonebranches must pull missing parents (issue941)
Patrick Mezard -
r5934:e495f3f3 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,186 +1,185 b''
1 1 # common code for the convert extension
2 2 import base64
3 3 import cPickle as pickle
4 4
5 5 def encodeargs(args):
6 6 def encodearg(s):
7 7 lines = base64.encodestring(s)
8 8 lines = [l.splitlines()[0] for l in lines]
9 9 return ''.join(lines)
10 10
11 11 s = pickle.dumps(args)
12 12 return encodearg(s)
13 13
14 14 def decodeargs(s):
15 15 s = base64.decodestring(s)
16 16 return pickle.loads(s)
17 17
18 18 class NoRepo(Exception): pass
19 19
20 20 SKIPREV = 'SKIP'
21 21
22 22 class commit(object):
23 23 def __init__(self, author, date, desc, parents, branch=None, rev=None,
24 24 extra={}):
25 25 self.author = author
26 26 self.date = date
27 27 self.desc = desc
28 28 self.parents = parents
29 29 self.branch = branch
30 30 self.rev = rev
31 31 self.extra = extra
32 32
33 33 class converter_source(object):
34 34 """Conversion source interface"""
35 35
36 36 def __init__(self, ui, path, rev=None):
37 37 """Initialize conversion source (or raise NoRepo("message")
38 38 exception if path is not a valid repository)"""
39 39 self.ui = ui
40 40 self.path = path
41 41 self.rev = rev
42 42
43 43 self.encoding = 'utf-8'
44 44
45 45 def before(self):
46 46 pass
47 47
48 48 def after(self):
49 49 pass
50 50
51 51 def setrevmap(self, revmap, order):
52 52 """set the map of already-converted revisions
53 53
54 54 order is a list with the keys from revmap in the order they
55 55 appear in the revision map file."""
56 56 pass
57 57
58 58 def getheads(self):
59 59 """Return a list of this repository's heads"""
60 60 raise NotImplementedError()
61 61
62 62 def getfile(self, name, rev):
63 63 """Return file contents as a string"""
64 64 raise NotImplementedError()
65 65
66 66 def getmode(self, name, rev):
67 67 """Return file mode, eg. '', 'x', or 'l'"""
68 68 raise NotImplementedError()
69 69
70 70 def getchanges(self, version):
71 71 """Returns a tuple of (files, copies)
72 72 Files is a sorted list of (filename, id) tuples for all files changed
73 73 in version, where id is the source revision id of the file.
74 74
75 75 copies is a dictionary of dest: source
76 76 """
77 77 raise NotImplementedError()
78 78
79 79 def getcommit(self, version):
80 80 """Return the commit object for version"""
81 81 raise NotImplementedError()
82 82
83 83 def gettags(self):
84 84 """Return the tags as a dictionary of name: revision"""
85 85 raise NotImplementedError()
86 86
87 87 def recode(self, s, encoding=None):
88 88 if not encoding:
89 89 encoding = self.encoding or 'utf-8'
90 90
91 91 if isinstance(s, unicode):
92 92 return s.encode("utf-8")
93 93 try:
94 94 return s.decode(encoding).encode("utf-8")
95 95 except:
96 96 try:
97 97 return s.decode("latin-1").encode("utf-8")
98 98 except:
99 99 return s.decode(encoding, "replace").encode("utf-8")
100 100
101 101 def getchangedfiles(self, rev, i):
102 102 """Return the files changed by rev compared to parent[i].
103 103
104 104 i is an index selecting one of the parents of rev. The return
105 105 value should be the list of files that are different in rev and
106 106 this parent.
107 107
108 108 If rev has no parents, i is None.
109 109
110 110 This function is only needed to support --filemap
111 111 """
112 112 raise NotImplementedError()
113 113
114 114 class converter_sink(object):
115 115 """Conversion sink (target) interface"""
116 116
117 117 def __init__(self, ui, path):
118 118 """Initialize conversion sink (or raise NoRepo("message")
119 119 exception if path is not a valid repository)
120 120
121 121 created is a list of paths to remove if a fatal error occurs
122 122 later"""
123 123 self.ui = ui
124 124 self.path = path
125 125 self.created = []
126 126
127 127 def getheads(self):
128 128 """Return a list of this repository's heads"""
129 129 raise NotImplementedError()
130 130
131 131 def revmapfile(self):
132 132 """Path to a file that will contain lines
133 133 source_rev_id sink_rev_id
134 134 mapping equivalent revision identifiers for each system."""
135 135 raise NotImplementedError()
136 136
137 137 def authorfile(self):
138 138 """Path to a file that will contain lines
139 139 srcauthor=dstauthor
140 140 mapping equivalent authors identifiers for each system."""
141 141 return None
142 142
143 143 def putfile(self, f, e, data):
144 144 """Put file for next putcommit().
145 145 f: path to file
146 146 e: '', 'x', or 'l' (regular file, executable, or symlink)
147 147 data: file contents"""
148 148 raise NotImplementedError()
149 149
150 150 def delfile(self, f):
151 151 """Delete file for next putcommit().
152 152 f: path to file"""
153 153 raise NotImplementedError()
154 154
155 155 def putcommit(self, files, parents, commit):
156 156 """Create a revision with all changed files listed in 'files'
157 157 and having listed parents. 'commit' is a commit object containing
158 158 at a minimum the author, date, and message for this changeset.
159 159 Called after putfile() and delfile() calls. Note that the sink
160 160 repository is not told to update itself to a particular revision
161 161 (or even what that revision would be) before it receives the
162 162 file data."""
163 163 raise NotImplementedError()
164 164
165 165 def puttags(self, tags):
166 166 """Put tags into sink.
167 167 tags: {tagname: sink_rev_id, ...}"""
168 168 raise NotImplementedError()
169 169
170 def setbranch(self, branch, pbranch, parents):
170 def setbranch(self, branch, pbranches):
171 171 """Set the current branch name. Called before the first putfile
172 172 on the branch.
173 173 branch: branch name for subsequent commits
174 pbranch: branch name of parent commit
175 parents: destination revisions of parent"""
174 pbranches: (converted parent revision, parent branch) tuples"""
176 175 pass
177 176
178 177 def setfilemapmode(self, active):
179 178 """Tell the destination that we're using a filemap
180 179
181 180 Some converter_sources (svn in particular) can claim that a file
182 181 was changed in a revision, even if there was no change. This method
183 182 tells the destination that we're using a filemap and that it should
184 183 filter empty revisions.
185 184 """
186 185 pass
@@ -1,330 +1,330 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
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 svn_source, debugsvnlog
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 ]
31 31
32 32 def convertsource(ui, path, type, rev):
33 33 exceptions = []
34 34 for name, source in source_converters:
35 35 try:
36 36 if not type or name == type:
37 37 return source(ui, path, rev)
38 38 except NoRepo, inst:
39 39 exceptions.append(inst)
40 40 if not ui.quiet:
41 41 for inst in exceptions:
42 42 ui.write(_("%s\n") % inst)
43 43 raise util.Abort('%s: unknown repository type' % path)
44 44
45 45 def convertsink(ui, path, type):
46 46 for name, sink in sink_converters:
47 47 try:
48 48 if not type or name == type:
49 49 return sink(ui, path)
50 50 except NoRepo, inst:
51 51 ui.note(_("convert: %s\n") % inst)
52 52 raise util.Abort('%s: unknown repository type' % path)
53 53
54 54 class converter(object):
55 55 def __init__(self, ui, source, dest, revmapfile, opts):
56 56
57 57 self.source = source
58 58 self.dest = dest
59 59 self.ui = ui
60 60 self.opts = opts
61 61 self.commitcache = {}
62 62 self.revmapfile = revmapfile
63 63 self.revmapfilefd = None
64 64 self.authors = {}
65 65 self.authorfile = None
66 66
67 67 self.maporder = []
68 68 self.map = {}
69 69 try:
70 70 origrevmapfile = open(self.revmapfile, 'r')
71 71 for l in origrevmapfile:
72 72 sv, dv = l[:-1].split()
73 73 if sv not in self.map:
74 74 self.maporder.append(sv)
75 75 self.map[sv] = dv
76 76 origrevmapfile.close()
77 77 except IOError:
78 78 pass
79 79
80 80 # Read first the dst author map if any
81 81 authorfile = self.dest.authorfile()
82 82 if authorfile and os.path.exists(authorfile):
83 83 self.readauthormap(authorfile)
84 84 # Extend/Override with new author map if necessary
85 85 if opts.get('authors'):
86 86 self.readauthormap(opts.get('authors'))
87 87 self.authorfile = self.dest.authorfile()
88 88
89 89 def walktree(self, heads):
90 90 '''Return a mapping that identifies the uncommitted parents of every
91 91 uncommitted changeset.'''
92 92 visit = heads
93 93 known = {}
94 94 parents = {}
95 95 while visit:
96 96 n = visit.pop(0)
97 97 if n in known or n in self.map: continue
98 98 known[n] = 1
99 99 commit = self.cachecommit(n)
100 100 parents[n] = []
101 101 for p in commit.parents:
102 102 parents[n].append(p)
103 103 visit.append(p)
104 104
105 105 return parents
106 106
107 107 def toposort(self, parents):
108 108 '''Return an ordering such that every uncommitted changeset is
109 109 preceeded by all its uncommitted ancestors.'''
110 110 visit = parents.keys()
111 111 seen = {}
112 112 children = {}
113 113
114 114 while visit:
115 115 n = visit.pop(0)
116 116 if n in seen: continue
117 117 seen[n] = 1
118 118 # Ensure that nodes without parents are present in the 'children'
119 119 # mapping.
120 120 children.setdefault(n, [])
121 121 for p in parents[n]:
122 122 if not p in self.map:
123 123 visit.append(p)
124 124 children.setdefault(p, []).append(n)
125 125
126 126 s = []
127 127 removed = {}
128 128 visit = children.keys()
129 129 while visit:
130 130 n = visit.pop(0)
131 131 if n in removed: continue
132 132 dep = 0
133 133 if n in parents:
134 134 for p in parents[n]:
135 135 if p in self.map: continue
136 136 if p not in removed:
137 137 # we're still dependent
138 138 visit.append(n)
139 139 dep = 1
140 140 break
141 141
142 142 if not dep:
143 143 # all n's parents are in the list
144 144 removed[n] = 1
145 145 if n not in self.map:
146 146 s.append(n)
147 147 if n in children:
148 148 for c in children[n]:
149 149 visit.insert(0, c)
150 150
151 151 if self.opts.get('datesort'):
152 152 depth = {}
153 153 for n in s:
154 154 depth[n] = 0
155 155 pl = [p for p in self.commitcache[n].parents
156 156 if p not in self.map]
157 157 if pl:
158 158 depth[n] = max([depth[p] for p in pl]) + 1
159 159
160 160 s = [(depth[n], self.commitcache[n].date, n) for n in s]
161 161 s.sort()
162 162 s = [e[2] for e in s]
163 163
164 164 return s
165 165
166 166 def mapentry(self, src, dst):
167 167 if self.revmapfilefd is None:
168 168 try:
169 169 self.revmapfilefd = open(self.revmapfile, "a")
170 170 except IOError, (errno, strerror):
171 171 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
172 172 self.map[src] = dst
173 173 self.revmapfilefd.write("%s %s\n" % (src, dst))
174 174 self.revmapfilefd.flush()
175 175
176 176 def writeauthormap(self):
177 177 authorfile = self.authorfile
178 178 if authorfile:
179 179 self.ui.status('Writing author map file %s\n' % authorfile)
180 180 ofile = open(authorfile, 'w+')
181 181 for author in self.authors:
182 182 ofile.write("%s=%s\n" % (author, self.authors[author]))
183 183 ofile.close()
184 184
185 185 def readauthormap(self, authorfile):
186 186 afile = open(authorfile, 'r')
187 187 for line in afile:
188 188 try:
189 189 srcauthor = line.split('=')[0].strip()
190 190 dstauthor = line.split('=')[1].strip()
191 191 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
192 192 self.ui.status(
193 193 'Overriding mapping for author %s, was %s, will be %s\n'
194 194 % (srcauthor, self.authors[srcauthor], dstauthor))
195 195 else:
196 196 self.ui.debug('Mapping author %s to %s\n'
197 197 % (srcauthor, dstauthor))
198 198 self.authors[srcauthor] = dstauthor
199 199 except IndexError:
200 200 self.ui.warn(
201 201 'Ignoring bad line in author file map %s: %s\n'
202 202 % (authorfile, line))
203 203 afile.close()
204 204
205 205 def cachecommit(self, rev):
206 206 commit = self.source.getcommit(rev)
207 207 commit.author = self.authors.get(commit.author, commit.author)
208 208 self.commitcache[rev] = commit
209 209 return commit
210 210
211 211 def copy(self, rev):
212 212 commit = self.commitcache[rev]
213 213 do_copies = hasattr(self.dest, 'copyfile')
214 214 filenames = []
215 215
216 216 changes = self.source.getchanges(rev)
217 217 if isinstance(changes, basestring):
218 218 if changes == SKIPREV:
219 219 dest = SKIPREV
220 220 else:
221 221 dest = self.map[changes]
222 222 self.mapentry(rev, dest)
223 223 return
224 224 files, copies = changes
225 parents = [self.map[r] for r in commit.parents]
225 pbranches = []
226 226 if commit.parents:
227 prev = commit.parents[0]
227 for prev in commit.parents:
228 228 if prev not in self.commitcache:
229 229 self.cachecommit(prev)
230 pbranch = self.commitcache[prev].branch
231 else:
232 pbranch = None
233 self.dest.setbranch(commit.branch, pbranch, parents)
230 pbranches.append((self.map[prev],
231 self.commitcache[prev].branch))
232 self.dest.setbranch(commit.branch, pbranches)
234 233 for f, v in files:
235 234 filenames.append(f)
236 235 try:
237 236 data = self.source.getfile(f, v)
238 237 except IOError, inst:
239 238 self.dest.delfile(f)
240 239 else:
241 240 e = self.source.getmode(f, v)
242 241 self.dest.putfile(f, e, data)
243 242 if do_copies:
244 243 if f in copies:
245 244 copyf = copies[f]
246 245 # Merely marks that a copy happened.
247 246 self.dest.copyfile(copyf, f)
248 247
248 parents = [b[0] for b in pbranches]
249 249 newnode = self.dest.putcommit(filenames, parents, commit)
250 250 self.mapentry(rev, newnode)
251 251
252 252 def convert(self):
253 253 try:
254 254 self.source.before()
255 255 self.dest.before()
256 256 self.source.setrevmap(self.map, self.maporder)
257 257 self.ui.status("scanning source...\n")
258 258 heads = self.source.getheads()
259 259 parents = self.walktree(heads)
260 260 self.ui.status("sorting...\n")
261 261 t = self.toposort(parents)
262 262 num = len(t)
263 263 c = None
264 264
265 265 self.ui.status("converting...\n")
266 266 for c in t:
267 267 num -= 1
268 268 desc = self.commitcache[c].desc
269 269 if "\n" in desc:
270 270 desc = desc.splitlines()[0]
271 271 self.ui.status("%d %s\n" % (num, desc))
272 272 self.copy(c)
273 273
274 274 tags = self.source.gettags()
275 275 ctags = {}
276 276 for k in tags:
277 277 v = tags[k]
278 278 if self.map.get(v, SKIPREV) != SKIPREV:
279 279 ctags[k] = self.map[v]
280 280
281 281 if c and ctags:
282 282 nrev = self.dest.puttags(ctags)
283 283 # write another hash correspondence to override the previous
284 284 # one so we don't end up with extra tag heads
285 285 if nrev:
286 286 self.mapentry(c, nrev)
287 287
288 288 self.writeauthormap()
289 289 finally:
290 290 self.cleanup()
291 291
292 292 def cleanup(self):
293 293 try:
294 294 self.dest.after()
295 295 finally:
296 296 self.source.after()
297 297 if self.revmapfilefd:
298 298 self.revmapfilefd.close()
299 299
300 300 def convert(ui, src, dest=None, revmapfile=None, **opts):
301 301 util._encoding = 'UTF-8'
302 302
303 303 if not dest:
304 304 dest = hg.defaultdest(src) + "-hg"
305 305 ui.status("assuming destination %s\n" % dest)
306 306
307 307 destc = convertsink(ui, dest, opts.get('dest_type'))
308 308
309 309 try:
310 310 srcc = convertsource(ui, src, opts.get('source_type'),
311 311 opts.get('rev'))
312 312 except Exception:
313 313 for path in destc.created:
314 314 shutil.rmtree(path, True)
315 315 raise
316 316
317 317 fmap = opts.get('filemap')
318 318 if fmap:
319 319 srcc = filemap.filemap_source(ui, srcc, fmap)
320 320 destc.setfilemapmode(True)
321 321
322 322 if not revmapfile:
323 323 try:
324 324 revmapfile = destc.revmapfile()
325 325 except:
326 326 revmapfile = os.path.join(destc, "map")
327 327
328 328 c = converter(ui, srcc, destc, revmapfile, opts)
329 329 c.convert()
330 330
@@ -1,264 +1,277 b''
1 1 # hg backend for convert extension
2 2
3 3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
4 4 # the whitespace from the ends of commit messages, but new versions
5 5 # do. Changesets created by those older versions, then converted, may
6 6 # thus have different hashes for changesets that are otherwise
7 7 # identical.
8 8
9 9
10 10 import os, time
11 11 from mercurial.i18n import _
12 12 from mercurial.node import *
13 13 from mercurial import hg, lock, revlog, util
14 14
15 15 from common import NoRepo, commit, converter_source, converter_sink
16 16
17 17 class mercurial_sink(converter_sink):
18 18 def __init__(self, ui, path):
19 19 converter_sink.__init__(self, ui, path)
20 20 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
21 21 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
22 22 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
23 23 self.lastbranch = None
24 24 if os.path.isdir(path) and len(os.listdir(path)) > 0:
25 25 try:
26 26 self.repo = hg.repository(self.ui, path)
27 27 if not self.repo.local():
28 28 raise NoRepo(_('%s is not a local Mercurial repo') % path)
29 29 ui.status(_('destination %s is a Mercurial repository\n') %
30 30 path)
31 31 except hg.RepoError, err:
32 32 ui.print_exc()
33 33 raise NoRepo(err.args[0])
34 34 else:
35 35 try:
36 36 ui.status(_('initializing destination %s repository\n') % path)
37 37 self.repo = hg.repository(self.ui, path, create=True)
38 38 if not self.repo.local():
39 39 raise NoRepo(_('%s is not a local Mercurial repo') % path)
40 40 self.created.append(path)
41 41 except hg.RepoError, err:
42 42 ui.print_exc()
43 43 raise NoRepo("could not create hg repo %s as sink" % path)
44 44 self.lock = None
45 45 self.wlock = None
46 46 self.filemapmode = False
47 47
48 48 def before(self):
49 49 self.wlock = self.repo.wlock()
50 50 self.lock = self.repo.lock()
51 51 self.repo.dirstate.clear()
52 52
53 53 def after(self):
54 54 self.repo.dirstate.invalidate()
55 55 self.lock = None
56 56 self.wlock = None
57 57
58 58 def revmapfile(self):
59 59 return os.path.join(self.path, ".hg", "shamap")
60 60
61 61 def authorfile(self):
62 62 return os.path.join(self.path, ".hg", "authormap")
63 63
64 64 def getheads(self):
65 65 h = self.repo.changelog.heads()
66 66 return [ hex(x) for x in h ]
67 67
68 68 def putfile(self, f, e, data):
69 69 self.repo.wwrite(f, data, e)
70 70 if f not in self.repo.dirstate:
71 71 self.repo.dirstate.normallookup(f)
72 72
73 73 def copyfile(self, source, dest):
74 74 self.repo.copy(source, dest)
75 75
76 76 def delfile(self, f):
77 77 try:
78 78 util.unlink(self.repo.wjoin(f))
79 79 #self.repo.remove([f])
80 80 except OSError:
81 81 pass
82 82
83 def setbranch(self, branch, pbranch, parents):
84 if (not self.clonebranches) or (branch == self.lastbranch):
83 def setbranch(self, branch, pbranches):
84 if not self.clonebranches:
85 85 return
86 86
87 setbranch = (branch != self.lastbranch)
87 88 self.lastbranch = branch
88 self.after()
89 89 if not branch:
90 90 branch = 'default'
91 if not pbranch:
92 pbranch = 'default'
91 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
92 pbranch = pbranches and pbranches[0][1] or 'default'
93 93
94 94 branchpath = os.path.join(self.path, branch)
95 if setbranch:
96 self.after()
95 97 try:
96 98 self.repo = hg.repository(self.ui, branchpath)
97 99 except:
98 if not parents:
99 100 self.repo = hg.repository(self.ui, branchpath, create=True)
100 else:
101 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
102 hg.clone(self.ui, os.path.join(self.path, pbranch),
103 branchpath, rev=parents, update=False,
104 stream=True)
105 self.repo = hg.repository(self.ui, branchpath)
101 self.before()
102
103 # pbranches may bring revisions from other branches (merge parents)
104 # Make sure we have them, or pull them.
105 missings = {}
106 for b in pbranches:
107 try:
108 self.repo.lookup(b[0])
109 except:
110 missings.setdefault(b[1], []).append(b[0])
111
112 if missings:
113 self.after()
114 for pbranch, heads in missings.iteritems():
115 pbranchpath = os.path.join(self.path, pbranch)
116 prepo = hg.repository(self.ui, pbranchpath)
117 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
118 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
106 119 self.before()
107 120
108 121 def putcommit(self, files, parents, commit):
109 122 seen = {}
110 123 pl = []
111 124 for p in parents:
112 125 if p not in seen:
113 126 pl.append(p)
114 127 seen[p] = 1
115 128 parents = pl
116 129 nparents = len(parents)
117 130 if self.filemapmode and nparents == 1:
118 131 m1node = self.repo.changelog.read(bin(parents[0]))[0]
119 132 parent = parents[0]
120 133
121 134 if len(parents) < 2: parents.append("0" * 40)
122 135 if len(parents) < 2: parents.append("0" * 40)
123 136 p2 = parents.pop(0)
124 137
125 138 text = commit.desc
126 139 extra = commit.extra.copy()
127 140 if self.branchnames and commit.branch:
128 141 extra['branch'] = commit.branch
129 142 if commit.rev:
130 143 extra['convert_revision'] = commit.rev
131 144
132 145 while parents:
133 146 p1 = p2
134 147 p2 = parents.pop(0)
135 148 a = self.repo.rawcommit(files, text, commit.author, commit.date,
136 149 bin(p1), bin(p2), extra=extra)
137 150 self.repo.dirstate.clear()
138 151 text = "(octopus merge fixup)\n"
139 152 p2 = hg.hex(self.repo.changelog.tip())
140 153
141 154 if self.filemapmode and nparents == 1:
142 155 man = self.repo.manifest
143 156 mnode = self.repo.changelog.read(bin(p2))[0]
144 157 if not man.cmp(m1node, man.revision(mnode)):
145 158 self.repo.rollback()
146 159 self.repo.dirstate.clear()
147 160 return parent
148 161 return p2
149 162
150 163 def puttags(self, tags):
151 164 try:
152 165 old = self.repo.wfile(".hgtags").read()
153 166 oldlines = old.splitlines(1)
154 167 oldlines.sort()
155 168 except:
156 169 oldlines = []
157 170
158 171 k = tags.keys()
159 172 k.sort()
160 173 newlines = []
161 174 for tag in k:
162 175 newlines.append("%s %s\n" % (tags[tag], tag))
163 176
164 177 newlines.sort()
165 178
166 179 if newlines != oldlines:
167 180 self.ui.status("updating tags\n")
168 181 f = self.repo.wfile(".hgtags", "w")
169 182 f.write("".join(newlines))
170 183 f.close()
171 184 if not oldlines: self.repo.add([".hgtags"])
172 185 date = "%s 0" % int(time.mktime(time.gmtime()))
173 186 extra = {}
174 187 if self.tagsbranch != 'default':
175 188 extra['branch'] = self.tagsbranch
176 189 try:
177 190 tagparent = self.repo.changectx(self.tagsbranch).node()
178 191 except hg.RepoError, inst:
179 192 tagparent = nullid
180 193 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
181 194 date, tagparent, nullid)
182 195 return hex(self.repo.changelog.tip())
183 196
184 197 def setfilemapmode(self, active):
185 198 self.filemapmode = active
186 199
187 200 class mercurial_source(converter_source):
188 201 def __init__(self, ui, path, rev=None):
189 202 converter_source.__init__(self, ui, path, rev)
190 203 try:
191 204 self.repo = hg.repository(self.ui, path)
192 205 # try to provoke an exception if this isn't really a hg
193 206 # repo, but some other bogus compatible-looking url
194 207 if not self.repo.local():
195 208 raise hg.RepoError()
196 209 except hg.RepoError:
197 210 ui.print_exc()
198 211 raise NoRepo("%s is not a local Mercurial repo" % path)
199 212 self.lastrev = None
200 213 self.lastctx = None
201 214 self._changescache = None
202 215
203 216 def changectx(self, rev):
204 217 if self.lastrev != rev:
205 218 self.lastctx = self.repo.changectx(rev)
206 219 self.lastrev = rev
207 220 return self.lastctx
208 221
209 222 def getheads(self):
210 223 if self.rev:
211 224 return [hex(self.repo.changectx(self.rev).node())]
212 225 else:
213 226 return [hex(node) for node in self.repo.heads()]
214 227
215 228 def getfile(self, name, rev):
216 229 try:
217 230 return self.changectx(rev).filectx(name).data()
218 231 except revlog.LookupError, err:
219 232 raise IOError(err)
220 233
221 234 def getmode(self, name, rev):
222 235 m = self.changectx(rev).manifest()
223 236 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
224 237
225 238 def getchanges(self, rev):
226 239 ctx = self.changectx(rev)
227 240 if self._changescache and self._changescache[0] == rev:
228 241 m, a, r = self._changescache[1]
229 242 else:
230 243 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
231 244 changes = [(name, rev) for name in m + a + r]
232 245 changes.sort()
233 246 return (changes, self.getcopies(ctx, m + a))
234 247
235 248 def getcopies(self, ctx, files):
236 249 copies = {}
237 250 for name in files:
238 251 try:
239 252 copies[name] = ctx.filectx(name).renamed()[0]
240 253 except TypeError:
241 254 pass
242 255 return copies
243 256
244 257 def getcommit(self, rev):
245 258 ctx = self.changectx(rev)
246 259 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
247 260 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
248 261 desc=ctx.description(), parents=parents,
249 262 branch=ctx.branch(), extra=ctx.extra())
250 263
251 264 def gettags(self):
252 265 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
253 266 return dict([(name, hex(node)) for name, node in tags])
254 267
255 268 def getchangedfiles(self, rev, i):
256 269 ctx = self.changectx(rev)
257 270 i = i or 0
258 271 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
259 272
260 273 if i == 0:
261 274 self._changescache = (rev, changes)
262 275
263 276 return changes[0] + changes[1] + changes[2]
264 277
General Comments 0
You need to be logged in to leave comments. Login now