##// END OF EJS Templates
convert: remove pycompat.iteritems()...
Gregory Szorc -
r49769:417a1691 default
parent child Browse files
Show More
@@ -1,331 +1,330 b''
1 1 # cvs.py: CVS conversion code inspired by hg-cvs-import and git-cvsimport
2 2 #
3 3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import errno
9 9 import os
10 10 import re
11 11 import socket
12 12
13 13 from mercurial.i18n import _
14 14 from mercurial.pycompat import (
15 15 getattr,
16 16 open,
17 17 )
18 18 from mercurial import (
19 19 encoding,
20 20 error,
21 pycompat,
22 21 util,
23 22 )
24 23 from mercurial.utils import (
25 24 dateutil,
26 25 procutil,
27 26 )
28 27
29 28 from . import (
30 29 common,
31 30 cvsps,
32 31 )
33 32
34 33 stringio = util.stringio
35 34 checktool = common.checktool
36 35 commit = common.commit
37 36 converter_source = common.converter_source
38 37 makedatetimestamp = common.makedatetimestamp
39 38 NoRepo = common.NoRepo
40 39
41 40
42 41 class convert_cvs(converter_source):
43 42 def __init__(self, ui, repotype, path, revs=None):
44 43 super(convert_cvs, self).__init__(ui, repotype, path, revs=revs)
45 44
46 45 cvs = os.path.join(path, b"CVS")
47 46 if not os.path.exists(cvs):
48 47 raise NoRepo(_(b"%s does not look like a CVS checkout") % path)
49 48
50 49 checktool(b'cvs')
51 50
52 51 self.changeset = None
53 52 self.files = {}
54 53 self.tags = {}
55 54 self.lastbranch = {}
56 55 self.socket = None
57 56 self.cvsroot = open(os.path.join(cvs, b"Root"), b'rb').read()[:-1]
58 57 self.cvsrepo = open(os.path.join(cvs, b"Repository"), b'rb').read()[:-1]
59 58 self.encoding = encoding.encoding
60 59
61 60 self._connect()
62 61
63 62 def _parse(self):
64 63 if self.changeset is not None:
65 64 return
66 65 self.changeset = {}
67 66
68 67 maxrev = 0
69 68 if self.revs:
70 69 if len(self.revs) > 1:
71 70 raise error.Abort(
72 71 _(
73 72 b'cvs source does not support specifying '
74 73 b'multiple revs'
75 74 )
76 75 )
77 76 # TODO: handle tags
78 77 try:
79 78 # patchset number?
80 79 maxrev = int(self.revs[0])
81 80 except ValueError:
82 81 raise error.Abort(
83 82 _(b'revision %s is not a patchset number') % self.revs[0]
84 83 )
85 84
86 85 d = encoding.getcwd()
87 86 try:
88 87 os.chdir(self.path)
89 88
90 89 cache = b'update'
91 90 if not self.ui.configbool(b'convert', b'cvsps.cache'):
92 91 cache = None
93 92 db = cvsps.createlog(self.ui, cache=cache)
94 93 db = cvsps.createchangeset(
95 94 self.ui,
96 95 db,
97 96 fuzz=int(self.ui.config(b'convert', b'cvsps.fuzz')),
98 97 mergeto=self.ui.config(b'convert', b'cvsps.mergeto'),
99 98 mergefrom=self.ui.config(b'convert', b'cvsps.mergefrom'),
100 99 )
101 100
102 101 for cs in db:
103 102 if maxrev and cs.id > maxrev:
104 103 break
105 104 id = b"%d" % cs.id
106 105 cs.author = self.recode(cs.author)
107 106 self.lastbranch[cs.branch] = id
108 107 cs.comment = self.recode(cs.comment)
109 108 if self.ui.configbool(b'convert', b'localtimezone'):
110 109 cs.date = makedatetimestamp(cs.date[0])
111 110 date = dateutil.datestr(cs.date, b'%Y-%m-%d %H:%M:%S %1%2')
112 111 self.tags.update(dict.fromkeys(cs.tags, id))
113 112
114 113 files = {}
115 114 for f in cs.entries:
116 115 files[f.file] = b"%s%s" % (
117 116 b'.'.join([(b"%d" % x) for x in f.revision]),
118 117 [b'', b'(DEAD)'][f.dead],
119 118 )
120 119
121 120 # add current commit to set
122 121 c = commit(
123 122 author=cs.author,
124 123 date=date,
125 124 parents=[(b"%d" % p.id) for p in cs.parents],
126 125 desc=cs.comment,
127 126 branch=cs.branch or b'',
128 127 )
129 128 self.changeset[id] = c
130 129 self.files[id] = files
131 130
132 131 self.heads = self.lastbranch.values()
133 132 finally:
134 133 os.chdir(d)
135 134
136 135 def _connect(self):
137 136 root = self.cvsroot
138 137 conntype = None
139 138 user, host = None, None
140 139 cmd = [b'cvs', b'server']
141 140
142 141 self.ui.status(_(b"connecting to %s\n") % root)
143 142
144 143 if root.startswith(b":pserver:"):
145 144 root = root[9:]
146 145 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:/]*)(?::(\d*))?(.*)', root)
147 146 if m:
148 147 conntype = b"pserver"
149 148 user, passw, serv, port, root = m.groups()
150 149 if not user:
151 150 user = b"anonymous"
152 151 if not port:
153 152 port = 2401
154 153 else:
155 154 port = int(port)
156 155 format0 = b":pserver:%s@%s:%s" % (user, serv, root)
157 156 format1 = b":pserver:%s@%s:%d%s" % (user, serv, port, root)
158 157
159 158 if not passw:
160 159 passw = b"A"
161 160 cvspass = os.path.expanduser(b"~/.cvspass")
162 161 try:
163 162 pf = open(cvspass, b'rb')
164 163 for line in pf.read().splitlines():
165 164 part1, part2 = line.split(b' ', 1)
166 165 # /1 :pserver:user@example.com:2401/cvsroot/foo
167 166 # Ah<Z
168 167 if part1 == b'/1':
169 168 part1, part2 = part2.split(b' ', 1)
170 169 format = format1
171 170 # :pserver:user@example.com:/cvsroot/foo Ah<Z
172 171 else:
173 172 format = format0
174 173 if part1 == format:
175 174 passw = part2
176 175 break
177 176 pf.close()
178 177 except IOError as inst:
179 178 if inst.errno != errno.ENOENT:
180 179 if not getattr(inst, 'filename', None):
181 180 inst.filename = cvspass
182 181 raise
183 182
184 183 sck = socket.socket()
185 184 sck.connect((serv, port))
186 185 sck.send(
187 186 b"\n".join(
188 187 [
189 188 b"BEGIN AUTH REQUEST",
190 189 root,
191 190 user,
192 191 passw,
193 192 b"END AUTH REQUEST",
194 193 b"",
195 194 ]
196 195 )
197 196 )
198 197 if sck.recv(128) != b"I LOVE YOU\n":
199 198 raise error.Abort(_(b"CVS pserver authentication failed"))
200 199
201 200 self.writep = self.readp = sck.makefile(b'r+')
202 201
203 202 if not conntype and root.startswith(b":local:"):
204 203 conntype = b"local"
205 204 root = root[7:]
206 205
207 206 if not conntype:
208 207 # :ext:user@host/home/user/path/to/cvsroot
209 208 if root.startswith(b":ext:"):
210 209 root = root[5:]
211 210 m = re.match(br'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
212 211 # Do not take Windows path "c:\foo\bar" for a connection strings
213 212 if os.path.isdir(root) or not m:
214 213 conntype = b"local"
215 214 else:
216 215 conntype = b"rsh"
217 216 user, host, root = m.group(1), m.group(2), m.group(3)
218 217
219 218 if conntype != b"pserver":
220 219 if conntype == b"rsh":
221 220 rsh = encoding.environ.get(b"CVS_RSH") or b"ssh"
222 221 if user:
223 222 cmd = [rsh, b'-l', user, host] + cmd
224 223 else:
225 224 cmd = [rsh, host] + cmd
226 225
227 226 # popen2 does not support argument lists under Windows
228 227 cmd = b' '.join(procutil.shellquote(arg) for arg in cmd)
229 228 self.writep, self.readp = procutil.popen2(cmd)
230 229
231 230 self.realroot = root
232 231
233 232 self.writep.write(b"Root %s\n" % root)
234 233 self.writep.write(
235 234 b"Valid-responses ok error Valid-requests Mode"
236 235 b" M Mbinary E Checked-in Created Updated"
237 236 b" Merged Removed\n"
238 237 )
239 238 self.writep.write(b"valid-requests\n")
240 239 self.writep.flush()
241 240 r = self.readp.readline()
242 241 if not r.startswith(b"Valid-requests"):
243 242 raise error.Abort(
244 243 _(
245 244 b'unexpected response from CVS server '
246 245 b'(expected "Valid-requests", but got %r)'
247 246 )
248 247 % r
249 248 )
250 249 if b"UseUnchanged" in r:
251 250 self.writep.write(b"UseUnchanged\n")
252 251 self.writep.flush()
253 252 self.readp.readline()
254 253
255 254 def getheads(self):
256 255 self._parse()
257 256 return self.heads
258 257
259 258 def getfile(self, name, rev):
260 259 def chunkedread(fp, count):
261 260 # file-objects returned by socket.makefile() do not handle
262 261 # large read() requests very well.
263 262 chunksize = 65536
264 263 output = stringio()
265 264 while count > 0:
266 265 data = fp.read(min(count, chunksize))
267 266 if not data:
268 267 raise error.Abort(
269 268 _(b"%d bytes missing from remote file") % count
270 269 )
271 270 count -= len(data)
272 271 output.write(data)
273 272 return output.getvalue()
274 273
275 274 self._parse()
276 275 if rev.endswith(b"(DEAD)"):
277 276 return None, None
278 277
279 278 args = (b"-N -P -kk -r %s --" % rev).split()
280 279 args.append(self.cvsrepo + b'/' + name)
281 280 for x in args:
282 281 self.writep.write(b"Argument %s\n" % x)
283 282 self.writep.write(b"Directory .\n%s\nco\n" % self.realroot)
284 283 self.writep.flush()
285 284
286 285 data = b""
287 286 mode = None
288 287 while True:
289 288 line = self.readp.readline()
290 289 if line.startswith(b"Created ") or line.startswith(b"Updated "):
291 290 self.readp.readline() # path
292 291 self.readp.readline() # entries
293 292 mode = self.readp.readline()[:-1]
294 293 count = int(self.readp.readline()[:-1])
295 294 data = chunkedread(self.readp, count)
296 295 elif line.startswith(b" "):
297 296 data += line[1:]
298 297 elif line.startswith(b"M "):
299 298 pass
300 299 elif line.startswith(b"Mbinary "):
301 300 count = int(self.readp.readline()[:-1])
302 301 data = chunkedread(self.readp, count)
303 302 else:
304 303 if line == b"ok\n":
305 304 if mode is None:
306 305 raise error.Abort(_(b'malformed response from CVS'))
307 306 return (data, b"x" in mode and b"x" or b"")
308 307 elif line.startswith(b"E "):
309 308 self.ui.warn(_(b"cvs server: %s\n") % line[2:])
310 309 elif line.startswith(b"Remove"):
311 310 self.readp.readline()
312 311 else:
313 312 raise error.Abort(_(b"unknown CVS response: %s") % line)
314 313
315 314 def getchanges(self, rev, full):
316 315 if full:
317 316 raise error.Abort(_(b"convert from cvs does not support --full"))
318 317 self._parse()
319 return sorted(pycompat.iteritems(self.files[rev])), {}, set()
318 return sorted(self.files[rev].items()), {}, set()
320 319
321 320 def getcommit(self, rev):
322 321 self._parse()
323 322 return self.changeset[rev]
324 323
325 324 def gettags(self):
326 325 self._parse()
327 326 return self.tags
328 327
329 328 def getchangedfiles(self, rev, i):
330 329 self._parse()
331 330 return sorted(self.files[rev])
@@ -1,732 +1,731 b''
1 1 # hg.py - hg backend for convert extension
2 2 #
3 3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # Notes for hg->hg conversion:
9 9 #
10 10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 11 # of commit messages, but new versions do. Changesets created by
12 12 # those older versions, then converted, may thus have different
13 13 # hashes for changesets that are otherwise identical.
14 14 #
15 15 # * Using "--config convert.hg.saverev=true" will make the source
16 16 # identifier to be stored in the converted revision. This will cause
17 17 # the converted revision to have a different identity than the
18 18 # source.
19 19
20 20 import os
21 21 import re
22 22 import time
23 23
24 24 from mercurial.i18n import _
25 25 from mercurial.pycompat import open
26 26 from mercurial.node import (
27 27 bin,
28 28 hex,
29 29 sha1nodeconstants,
30 30 )
31 31 from mercurial import (
32 32 bookmarks,
33 33 context,
34 34 error,
35 35 exchange,
36 36 hg,
37 37 lock as lockmod,
38 38 logcmdutil,
39 39 merge as mergemod,
40 40 mergestate,
41 41 phases,
42 pycompat,
43 42 util,
44 43 )
45 44 from mercurial.utils import dateutil
46 45
47 46 stringio = util.stringio
48 47
49 48 from . import common
50 49
51 50 mapfile = common.mapfile
52 51 NoRepo = common.NoRepo
53 52
54 53 sha1re = re.compile(br'\b[0-9a-f]{12,40}\b')
55 54
56 55
57 56 class mercurial_sink(common.converter_sink):
58 57 def __init__(self, ui, repotype, path):
59 58 common.converter_sink.__init__(self, ui, repotype, path)
60 59 self.branchnames = ui.configbool(b'convert', b'hg.usebranchnames')
61 60 self.clonebranches = ui.configbool(b'convert', b'hg.clonebranches')
62 61 self.tagsbranch = ui.config(b'convert', b'hg.tagsbranch')
63 62 self.lastbranch = None
64 63 if os.path.isdir(path) and len(os.listdir(path)) > 0:
65 64 try:
66 65 self.repo = hg.repository(self.ui, path)
67 66 if not self.repo.local():
68 67 raise NoRepo(
69 68 _(b'%s is not a local Mercurial repository') % path
70 69 )
71 70 except error.RepoError as err:
72 71 ui.traceback()
73 72 raise NoRepo(err.args[0])
74 73 else:
75 74 try:
76 75 ui.status(_(b'initializing destination %s repository\n') % path)
77 76 self.repo = hg.repository(self.ui, path, create=True)
78 77 if not self.repo.local():
79 78 raise NoRepo(
80 79 _(b'%s is not a local Mercurial repository') % path
81 80 )
82 81 self.created.append(path)
83 82 except error.RepoError:
84 83 ui.traceback()
85 84 raise NoRepo(
86 85 _(b"could not create hg repository %s as sink") % path
87 86 )
88 87 self.lock = None
89 88 self.wlock = None
90 89 self.filemapmode = False
91 90 self.subrevmaps = {}
92 91
93 92 def before(self):
94 93 self.ui.debug(b'run hg sink pre-conversion action\n')
95 94 self.wlock = self.repo.wlock()
96 95 self.lock = self.repo.lock()
97 96
98 97 def after(self):
99 98 self.ui.debug(b'run hg sink post-conversion action\n')
100 99 if self.lock:
101 100 self.lock.release()
102 101 if self.wlock:
103 102 self.wlock.release()
104 103
105 104 def revmapfile(self):
106 105 return self.repo.vfs.join(b"shamap")
107 106
108 107 def authorfile(self):
109 108 return self.repo.vfs.join(b"authormap")
110 109
111 110 def setbranch(self, branch, pbranches):
112 111 if not self.clonebranches:
113 112 return
114 113
115 114 setbranch = branch != self.lastbranch
116 115 self.lastbranch = branch
117 116 if not branch:
118 117 branch = b'default'
119 118 pbranches = [(b[0], b[1] and b[1] or b'default') for b in pbranches]
120 119
121 120 branchpath = os.path.join(self.path, branch)
122 121 if setbranch:
123 122 self.after()
124 123 try:
125 124 self.repo = hg.repository(self.ui, branchpath)
126 125 except Exception:
127 126 self.repo = hg.repository(self.ui, branchpath, create=True)
128 127 self.before()
129 128
130 129 # pbranches may bring revisions from other branches (merge parents)
131 130 # Make sure we have them, or pull them.
132 131 missings = {}
133 132 for b in pbranches:
134 133 try:
135 134 self.repo.lookup(b[0])
136 135 except Exception:
137 136 missings.setdefault(b[1], []).append(b[0])
138 137
139 138 if missings:
140 139 self.after()
141 140 for pbranch, heads in sorted(missings.items()):
142 141 pbranchpath = os.path.join(self.path, pbranch)
143 142 prepo = hg.peer(self.ui, {}, pbranchpath)
144 143 self.ui.note(
145 144 _(b'pulling from %s into %s\n') % (pbranch, branch)
146 145 )
147 146 exchange.pull(
148 147 self.repo, prepo, heads=[prepo.lookup(h) for h in heads]
149 148 )
150 149 self.before()
151 150
152 151 def _rewritetags(self, source, revmap, data):
153 152 fp = stringio()
154 153 for line in data.splitlines():
155 154 s = line.split(b' ', 1)
156 155 if len(s) != 2:
157 156 self.ui.warn(_(b'invalid tag entry: "%s"\n') % line)
158 157 fp.write(b'%s\n' % line) # Bogus, but keep for hash stability
159 158 continue
160 159 revid = revmap.get(source.lookuprev(s[0]))
161 160 if not revid:
162 161 if s[0] == sha1nodeconstants.nullhex:
163 162 revid = s[0]
164 163 else:
165 164 # missing, but keep for hash stability
166 165 self.ui.warn(_(b'missing tag entry: "%s"\n') % line)
167 166 fp.write(b'%s\n' % line)
168 167 continue
169 168 fp.write(b'%s %s\n' % (revid, s[1]))
170 169 return fp.getvalue()
171 170
172 171 def _rewritesubstate(self, source, data):
173 172 fp = stringio()
174 173 for line in data.splitlines():
175 174 s = line.split(b' ', 1)
176 175 if len(s) != 2:
177 176 continue
178 177
179 178 revid = s[0]
180 179 subpath = s[1]
181 180 if revid != sha1nodeconstants.nullhex:
182 181 revmap = self.subrevmaps.get(subpath)
183 182 if revmap is None:
184 183 revmap = mapfile(
185 184 self.ui, self.repo.wjoin(subpath, b'.hg/shamap')
186 185 )
187 186 self.subrevmaps[subpath] = revmap
188 187
189 188 # It is reasonable that one or more of the subrepos don't
190 189 # need to be converted, in which case they can be cloned
191 190 # into place instead of converted. Therefore, only warn
192 191 # once.
193 192 msg = _(b'no ".hgsubstate" updates will be made for "%s"\n')
194 193 if len(revmap) == 0:
195 194 sub = self.repo.wvfs.reljoin(subpath, b'.hg')
196 195
197 196 if self.repo.wvfs.exists(sub):
198 197 self.ui.warn(msg % subpath)
199 198
200 199 newid = revmap.get(revid)
201 200 if not newid:
202 201 if len(revmap) > 0:
203 202 self.ui.warn(
204 203 _(b"%s is missing from %s/.hg/shamap\n")
205 204 % (revid, subpath)
206 205 )
207 206 else:
208 207 revid = newid
209 208
210 209 fp.write(b'%s %s\n' % (revid, subpath))
211 210
212 211 return fp.getvalue()
213 212
214 213 def _calculatemergedfiles(self, source, p1ctx, p2ctx):
215 214 """Calculates the files from p2 that we need to pull in when merging p1
216 215 and p2, given that the merge is coming from the given source.
217 216
218 217 This prevents us from losing files that only exist in the target p2 and
219 218 that don't come from the source repo (like if you're merging multiple
220 219 repositories together).
221 220 """
222 221 anc = [p1ctx.ancestor(p2ctx)]
223 222 # Calculate what files are coming from p2
224 223 # TODO: mresult.commitinfo might be able to get that info
225 224 mresult = mergemod.calculateupdates(
226 225 self.repo,
227 226 p1ctx,
228 227 p2ctx,
229 228 anc,
230 229 branchmerge=True,
231 230 force=True,
232 231 acceptremote=False,
233 232 followcopies=False,
234 233 )
235 234
236 235 for file, (action, info, msg) in mresult.filemap():
237 236 if source.targetfilebelongstosource(file):
238 237 # If the file belongs to the source repo, ignore the p2
239 238 # since it will be covered by the existing fileset.
240 239 continue
241 240
242 241 # If the file requires actual merging, abort. We don't have enough
243 242 # context to resolve merges correctly.
244 243 if action in mergestate.CONVERT_MERGE_ACTIONS:
245 244 raise error.Abort(
246 245 _(
247 246 b"unable to convert merge commit "
248 247 b"since target parents do not merge cleanly (file "
249 248 b"%s, parents %s and %s)"
250 249 )
251 250 % (file, p1ctx, p2ctx)
252 251 )
253 252 elif action == mergestate.ACTION_KEEP:
254 253 # 'keep' means nothing changed from p1
255 254 continue
256 255 else:
257 256 # Any other change means we want to take the p2 version
258 257 yield file
259 258
260 259 def putcommit(
261 260 self, files, copies, parents, commit, source, revmap, full, cleanp2
262 261 ):
263 262 files = dict(files)
264 263
265 264 def getfilectx(repo, memctx, f):
266 265 if p2ctx and f in p2files and f not in copies:
267 266 self.ui.debug(b'reusing %s from p2\n' % f)
268 267 try:
269 268 return p2ctx[f]
270 269 except error.ManifestLookupError:
271 270 # If the file doesn't exist in p2, then we're syncing a
272 271 # delete, so just return None.
273 272 return None
274 273 try:
275 274 v = files[f]
276 275 except KeyError:
277 276 return None
278 277 data, mode = source.getfile(f, v)
279 278 if data is None:
280 279 return None
281 280 if f == b'.hgtags':
282 281 data = self._rewritetags(source, revmap, data)
283 282 if f == b'.hgsubstate':
284 283 data = self._rewritesubstate(source, data)
285 284 return context.memfilectx(
286 285 self.repo,
287 286 memctx,
288 287 f,
289 288 data,
290 289 b'l' in mode,
291 290 b'x' in mode,
292 291 copies.get(f),
293 292 )
294 293
295 294 pl = []
296 295 for p in parents:
297 296 if p not in pl:
298 297 pl.append(p)
299 298 parents = pl
300 299 nparents = len(parents)
301 300 if self.filemapmode and nparents == 1:
302 301 m1node = self.repo.changelog.read(bin(parents[0]))[0]
303 302 parent = parents[0]
304 303
305 304 if len(parents) < 2:
306 305 parents.append(self.repo.nullid)
307 306 if len(parents) < 2:
308 307 parents.append(self.repo.nullid)
309 308 p2 = parents.pop(0)
310 309
311 310 text = commit.desc
312 311
313 312 sha1s = re.findall(sha1re, text)
314 313 for sha1 in sha1s:
315 314 oldrev = source.lookuprev(sha1)
316 315 newrev = revmap.get(oldrev)
317 316 if newrev is not None:
318 317 text = text.replace(sha1, newrev[: len(sha1)])
319 318
320 319 extra = commit.extra.copy()
321 320
322 321 sourcename = self.repo.ui.config(b'convert', b'hg.sourcename')
323 322 if sourcename:
324 323 extra[b'convert_source'] = sourcename
325 324
326 325 for label in (
327 326 b'source',
328 327 b'transplant_source',
329 328 b'rebase_source',
330 329 b'intermediate-source',
331 330 ):
332 331 node = extra.get(label)
333 332
334 333 if node is None:
335 334 continue
336 335
337 336 # Only transplant stores its reference in binary
338 337 if label == b'transplant_source':
339 338 node = hex(node)
340 339
341 340 newrev = revmap.get(node)
342 341 if newrev is not None:
343 342 if label == b'transplant_source':
344 343 newrev = bin(newrev)
345 344
346 345 extra[label] = newrev
347 346
348 347 if self.branchnames and commit.branch:
349 348 extra[b'branch'] = commit.branch
350 349 if commit.rev and commit.saverev:
351 350 extra[b'convert_revision'] = commit.rev
352 351
353 352 while parents:
354 353 p1 = p2
355 354 p2 = parents.pop(0)
356 355 p1ctx = self.repo[p1]
357 356 p2ctx = None
358 357 if p2 != self.repo.nullid:
359 358 p2ctx = self.repo[p2]
360 359 fileset = set(files)
361 360 if full:
362 361 fileset.update(self.repo[p1])
363 362 fileset.update(self.repo[p2])
364 363
365 364 if p2ctx:
366 365 p2files = set(cleanp2)
367 366 for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
368 367 p2files.add(file)
369 368 fileset.add(file)
370 369
371 370 ctx = context.memctx(
372 371 self.repo,
373 372 (p1, p2),
374 373 text,
375 374 fileset,
376 375 getfilectx,
377 376 commit.author,
378 377 commit.date,
379 378 extra,
380 379 )
381 380
382 381 # We won't know if the conversion changes the node until after the
383 382 # commit, so copy the source's phase for now.
384 383 self.repo.ui.setconfig(
385 384 b'phases',
386 385 b'new-commit',
387 386 phases.phasenames[commit.phase],
388 387 b'convert',
389 388 )
390 389
391 390 with self.repo.transaction(b"convert") as tr:
392 391 if self.repo.ui.config(b'convert', b'hg.preserve-hash'):
393 392 origctx = commit.ctx
394 393 else:
395 394 origctx = None
396 395 node = hex(self.repo.commitctx(ctx, origctx=origctx))
397 396
398 397 # If the node value has changed, but the phase is lower than
399 398 # draft, set it back to draft since it hasn't been exposed
400 399 # anywhere.
401 400 if commit.rev != node:
402 401 ctx = self.repo[node]
403 402 if ctx.phase() < phases.draft:
404 403 phases.registernew(
405 404 self.repo, tr, phases.draft, [ctx.rev()]
406 405 )
407 406
408 407 text = b"(octopus merge fixup)\n"
409 408 p2 = node
410 409
411 410 if self.filemapmode and nparents == 1:
412 411 man = self.repo.manifestlog.getstorage(b'')
413 412 mnode = self.repo.changelog.read(bin(p2))[0]
414 413 closed = b'close' in commit.extra
415 414 if not closed and not man.cmp(m1node, man.revision(mnode)):
416 415 self.ui.status(_(b"filtering out empty revision\n"))
417 416 self.repo.rollback(force=True)
418 417 return parent
419 418 return p2
420 419
421 420 def puttags(self, tags):
422 421 tagparent = self.repo.branchtip(self.tagsbranch, ignoremissing=True)
423 422 tagparent = tagparent or self.repo.nullid
424 423
425 424 oldlines = set()
426 for branch, heads in pycompat.iteritems(self.repo.branchmap()):
425 for branch, heads in self.repo.branchmap().items():
427 426 for h in heads:
428 427 if b'.hgtags' in self.repo[h]:
429 428 oldlines.update(
430 429 set(self.repo[h][b'.hgtags'].data().splitlines(True))
431 430 )
432 431 oldlines = sorted(list(oldlines))
433 432
434 433 newlines = sorted([(b"%s %s\n" % (tags[tag], tag)) for tag in tags])
435 434 if newlines == oldlines:
436 435 return None, None
437 436
438 437 # if the old and new tags match, then there is nothing to update
439 438 oldtags = set()
440 439 newtags = set()
441 440 for line in oldlines:
442 441 s = line.strip().split(b' ', 1)
443 442 if len(s) != 2:
444 443 continue
445 444 oldtags.add(s[1])
446 445 for line in newlines:
447 446 s = line.strip().split(b' ', 1)
448 447 if len(s) != 2:
449 448 continue
450 449 if s[1] not in oldtags:
451 450 newtags.add(s[1].strip())
452 451
453 452 if not newtags:
454 453 return None, None
455 454
456 455 data = b"".join(newlines)
457 456
458 457 def getfilectx(repo, memctx, f):
459 458 return context.memfilectx(repo, memctx, f, data, False, False, None)
460 459
461 460 self.ui.status(_(b"updating tags\n"))
462 461 date = b"%d 0" % int(time.mktime(time.gmtime()))
463 462 extra = {b'branch': self.tagsbranch}
464 463 ctx = context.memctx(
465 464 self.repo,
466 465 (tagparent, None),
467 466 b"update tags",
468 467 [b".hgtags"],
469 468 getfilectx,
470 469 b"convert-repo",
471 470 date,
472 471 extra,
473 472 )
474 473 node = self.repo.commitctx(ctx)
475 474 return hex(node), hex(tagparent)
476 475
477 476 def setfilemapmode(self, active):
478 477 self.filemapmode = active
479 478
480 479 def putbookmarks(self, updatedbookmark):
481 480 if not len(updatedbookmark):
482 481 return
483 482 wlock = lock = tr = None
484 483 try:
485 484 wlock = self.repo.wlock()
486 485 lock = self.repo.lock()
487 486 tr = self.repo.transaction(b'bookmark')
488 487 self.ui.status(_(b"updating bookmarks\n"))
489 488 destmarks = self.repo._bookmarks
490 489 changes = [
491 490 (bookmark, bin(updatedbookmark[bookmark]))
492 491 for bookmark in updatedbookmark
493 492 ]
494 493 destmarks.applychanges(self.repo, tr, changes)
495 494 tr.close()
496 495 finally:
497 496 lockmod.release(lock, wlock, tr)
498 497
499 498 def hascommitfrommap(self, rev):
500 499 # the exact semantics of clonebranches is unclear so we can't say no
501 500 return rev in self.repo or self.clonebranches
502 501
503 502 def hascommitforsplicemap(self, rev):
504 503 if rev not in self.repo and self.clonebranches:
505 504 raise error.Abort(
506 505 _(
507 506 b'revision %s not found in destination '
508 507 b'repository (lookups with clonebranches=true '
509 508 b'are not implemented)'
510 509 )
511 510 % rev
512 511 )
513 512 return rev in self.repo
514 513
515 514
516 515 class mercurial_source(common.converter_source):
517 516 def __init__(self, ui, repotype, path, revs=None):
518 517 common.converter_source.__init__(self, ui, repotype, path, revs)
519 518 self.ignoreerrors = ui.configbool(b'convert', b'hg.ignoreerrors')
520 519 self.ignored = set()
521 520 self.saverev = ui.configbool(b'convert', b'hg.saverev')
522 521 try:
523 522 self.repo = hg.repository(self.ui, path)
524 523 # try to provoke an exception if this isn't really a hg
525 524 # repo, but some other bogus compatible-looking url
526 525 if not self.repo.local():
527 526 raise error.RepoError
528 527 except error.RepoError:
529 528 ui.traceback()
530 529 raise NoRepo(_(b"%s is not a local Mercurial repository") % path)
531 530 self.lastrev = None
532 531 self.lastctx = None
533 532 self._changescache = None, None
534 533 self.convertfp = None
535 534 # Restrict converted revisions to startrev descendants
536 535 startnode = ui.config(b'convert', b'hg.startrev')
537 536 hgrevs = ui.config(b'convert', b'hg.revs')
538 537 if hgrevs is None:
539 538 if startnode is not None:
540 539 try:
541 540 startnode = self.repo.lookup(startnode)
542 541 except error.RepoError:
543 542 raise error.Abort(
544 543 _(b'%s is not a valid start revision') % startnode
545 544 )
546 545 startrev = self.repo.changelog.rev(startnode)
547 546 children = {startnode: 1}
548 547 for r in self.repo.changelog.descendants([startrev]):
549 548 children[self.repo.changelog.node(r)] = 1
550 549 self.keep = children.__contains__
551 550 else:
552 551 self.keep = util.always
553 552 if revs:
554 553 self._heads = [self.repo.lookup(r) for r in revs]
555 554 else:
556 555 self._heads = self.repo.heads()
557 556 else:
558 557 if revs or startnode is not None:
559 558 raise error.Abort(
560 559 _(
561 560 b'hg.revs cannot be combined with '
562 561 b'hg.startrev or --rev'
563 562 )
564 563 )
565 564 nodes = set()
566 565 parents = set()
567 566 for r in logcmdutil.revrange(self.repo, [hgrevs]):
568 567 ctx = self.repo[r]
569 568 nodes.add(ctx.node())
570 569 parents.update(p.node() for p in ctx.parents())
571 570 self.keep = nodes.__contains__
572 571 self._heads = nodes - parents
573 572
574 573 def _changectx(self, rev):
575 574 if self.lastrev != rev:
576 575 self.lastctx = self.repo[rev]
577 576 self.lastrev = rev
578 577 return self.lastctx
579 578
580 579 def _parents(self, ctx):
581 580 return [p for p in ctx.parents() if p and self.keep(p.node())]
582 581
583 582 def getheads(self):
584 583 return [hex(h) for h in self._heads if self.keep(h)]
585 584
586 585 def getfile(self, name, rev):
587 586 try:
588 587 fctx = self._changectx(rev)[name]
589 588 return fctx.data(), fctx.flags()
590 589 except error.LookupError:
591 590 return None, None
592 591
593 592 def _changedfiles(self, ctx1, ctx2):
594 593 ma, r = [], []
595 594 maappend = ma.append
596 595 rappend = r.append
597 596 d = ctx1.manifest().diff(ctx2.manifest())
598 597 for f, ((node1, flag1), (node2, flag2)) in d.items():
599 598 if node2 is None:
600 599 rappend(f)
601 600 else:
602 601 maappend(f)
603 602 return ma, r
604 603
605 604 def getchanges(self, rev, full):
606 605 ctx = self._changectx(rev)
607 606 parents = self._parents(ctx)
608 607 if full or not parents:
609 608 files = copyfiles = ctx.manifest()
610 609 if parents:
611 610 if self._changescache[0] == rev:
612 611 ma, r = self._changescache[1]
613 612 else:
614 613 ma, r = self._changedfiles(parents[0], ctx)
615 614 if not full:
616 615 files = ma + r
617 616 copyfiles = ma
618 617 # _getcopies() is also run for roots and before filtering so missing
619 618 # revlogs are detected early
620 619 copies = self._getcopies(ctx, parents, copyfiles)
621 620 cleanp2 = set()
622 621 if len(parents) == 2:
623 622 d = parents[1].manifest().diff(ctx.manifest(), clean=True)
624 623 for f, value in d.items():
625 624 if value is None:
626 625 cleanp2.add(f)
627 626 changes = [(f, rev) for f in files if f not in self.ignored]
628 627 changes.sort()
629 628 return changes, copies, cleanp2
630 629
631 630 def _getcopies(self, ctx, parents, files):
632 631 copies = {}
633 632 for name in files:
634 633 if name in self.ignored:
635 634 continue
636 635 try:
637 636 copysource = ctx.filectx(name).copysource()
638 637 if copysource in self.ignored:
639 638 continue
640 639 # Ignore copy sources not in parent revisions
641 640 if not any(copysource in p for p in parents):
642 641 continue
643 642 copies[name] = copysource
644 643 except TypeError:
645 644 pass
646 645 except error.LookupError as e:
647 646 if not self.ignoreerrors:
648 647 raise
649 648 self.ignored.add(name)
650 649 self.ui.warn(_(b'ignoring: %s\n') % e)
651 650 return copies
652 651
653 652 def getcommit(self, rev):
654 653 ctx = self._changectx(rev)
655 654 _parents = self._parents(ctx)
656 655 parents = [p.hex() for p in _parents]
657 656 optparents = [p.hex() for p in ctx.parents() if p and p not in _parents]
658 657 crev = rev
659 658
660 659 return common.commit(
661 660 author=ctx.user(),
662 661 date=dateutil.datestr(ctx.date(), b'%Y-%m-%d %H:%M:%S %1%2'),
663 662 desc=ctx.description(),
664 663 rev=crev,
665 664 parents=parents,
666 665 optparents=optparents,
667 666 branch=ctx.branch(),
668 667 extra=ctx.extra(),
669 668 sortkey=ctx.rev(),
670 669 saverev=self.saverev,
671 670 phase=ctx.phase(),
672 671 ctx=ctx,
673 672 )
674 673
675 674 def numcommits(self):
676 675 return len(self.repo)
677 676
678 677 def gettags(self):
679 678 # This will get written to .hgtags, filter non global tags out.
680 679 tags = [
681 680 t
682 681 for t in self.repo.tagslist()
683 682 if self.repo.tagtype(t[0]) == b'global'
684 683 ]
685 684 return {name: hex(node) for name, node in tags if self.keep(node)}
686 685
687 686 def getchangedfiles(self, rev, i):
688 687 ctx = self._changectx(rev)
689 688 parents = self._parents(ctx)
690 689 if not parents and i is None:
691 690 i = 0
692 691 ma, r = ctx.manifest().keys(), []
693 692 else:
694 693 i = i or 0
695 694 ma, r = self._changedfiles(parents[i], ctx)
696 695 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
697 696
698 697 if i == 0:
699 698 self._changescache = (rev, (ma, r))
700 699
701 700 return ma + r
702 701
703 702 def converted(self, rev, destrev):
704 703 if self.convertfp is None:
705 704 self.convertfp = open(self.repo.vfs.join(b'shamap'), b'ab')
706 705 self.convertfp.write(util.tonativeeol(b'%s %s\n' % (destrev, rev)))
707 706 self.convertfp.flush()
708 707
709 708 def before(self):
710 709 self.ui.debug(b'run hg source pre-conversion action\n')
711 710
712 711 def after(self):
713 712 self.ui.debug(b'run hg source post-conversion action\n')
714 713
715 714 def hasnativeorder(self):
716 715 return True
717 716
718 717 def hasnativeclose(self):
719 718 return True
720 719
721 720 def lookuprev(self, rev):
722 721 try:
723 722 return hex(self.repo.lookup(rev))
724 723 except (error.RepoError, error.LookupError):
725 724 return None
726 725
727 726 def getbookmarks(self):
728 727 return bookmarks.listbookmarks(self.repo)
729 728
730 729 def checkrevformat(self, revstr, mapname=b'splicemap'):
731 730 """Mercurial, revision string is a 40 byte hex"""
732 731 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now