##// END OF EJS Templates
convert: pass absolute paths to git (SEC)...
Blake Burkhart -
r29051:a56296f5 3.8.1 stable
parent child Browse files
Show More
@@ -1,394 +1,398 b''
1 1 # git.py - git support for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@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 from __future__ import absolute_import
8 8
9 9 import os
10 10 from mercurial import (
11 11 config,
12 12 error,
13 13 node as nodemod,
14 14 )
15 15 from mercurial.i18n import _
16 16
17 17 from . import (
18 18 common,
19 19 )
20 20
21 21 class submodule(object):
22 22 def __init__(self, path, node, url):
23 23 self.path = path
24 24 self.node = node
25 25 self.url = url
26 26
27 27 def hgsub(self):
28 28 return "%s = [git]%s" % (self.path, self.url)
29 29
30 30 def hgsubstate(self):
31 31 return "%s %s" % (self.node, self.path)
32 32
33 33 class convert_git(common.converter_source, common.commandline):
34 34 # Windows does not support GIT_DIR= construct while other systems
35 35 # cannot remove environment variable. Just assume none have
36 36 # both issues.
37 37
38 38 def _gitcmd(self, cmd, *args, **kwargs):
39 39 return cmd('--git-dir=%s' % self.path, *args, **kwargs)
40 40
41 41 def gitrun0(self, *args, **kwargs):
42 42 return self._gitcmd(self.run0, *args, **kwargs)
43 43
44 44 def gitrun(self, *args, **kwargs):
45 45 return self._gitcmd(self.run, *args, **kwargs)
46 46
47 47 def gitrunlines0(self, *args, **kwargs):
48 48 return self._gitcmd(self.runlines0, *args, **kwargs)
49 49
50 50 def gitrunlines(self, *args, **kwargs):
51 51 return self._gitcmd(self.runlines, *args, **kwargs)
52 52
53 53 def gitpipe(self, *args, **kwargs):
54 54 return self._gitcmd(self._run3, *args, **kwargs)
55 55
56 56 def __init__(self, ui, path, revs=None):
57 57 super(convert_git, self).__init__(ui, path, revs=revs)
58 58 common.commandline.__init__(self, ui, 'git')
59 59
60 # Pass an absolute path to git to prevent from ever being interpreted
61 # as a URL
62 path = os.path.abspath(path)
63
60 64 if os.path.isdir(path + "/.git"):
61 65 path += "/.git"
62 66 if not os.path.exists(path + "/objects"):
63 67 raise common.NoRepo(_("%s does not look like a Git repository") %
64 68 path)
65 69
66 70 # The default value (50) is based on the default for 'git diff'.
67 71 similarity = ui.configint('convert', 'git.similarity', default=50)
68 72 if similarity < 0 or similarity > 100:
69 73 raise error.Abort(_('similarity must be between 0 and 100'))
70 74 if similarity > 0:
71 75 self.simopt = ['-C%d%%' % similarity]
72 76 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
73 77 False)
74 78 if findcopiesharder:
75 79 self.simopt.append('--find-copies-harder')
76 80 else:
77 81 self.simopt = []
78 82
79 83 common.checktool('git', 'git')
80 84
81 85 self.path = path
82 86 self.submodules = []
83 87
84 88 self.catfilepipe = self.gitpipe('cat-file', '--batch')
85 89
86 90 def after(self):
87 91 for f in self.catfilepipe:
88 92 f.close()
89 93
90 94 def getheads(self):
91 95 if not self.revs:
92 96 output, status = self.gitrun('rev-parse', '--branches', '--remotes')
93 97 heads = output.splitlines()
94 98 if status:
95 99 raise error.Abort(_('cannot retrieve git heads'))
96 100 else:
97 101 heads = []
98 102 for rev in self.revs:
99 103 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
100 104 heads.append(rawhead[:-1])
101 105 if ret:
102 106 raise error.Abort(_('cannot retrieve git head "%s"') % rev)
103 107 return heads
104 108
105 109 def catfile(self, rev, type):
106 110 if rev == nodemod.nullhex:
107 111 raise IOError
108 112 self.catfilepipe[0].write(rev+'\n')
109 113 self.catfilepipe[0].flush()
110 114 info = self.catfilepipe[1].readline().split()
111 115 if info[1] != type:
112 116 raise error.Abort(_('cannot read %r object at %s') % (type, rev))
113 117 size = int(info[2])
114 118 data = self.catfilepipe[1].read(size)
115 119 if len(data) < size:
116 120 raise error.Abort(_('cannot read %r object at %s: unexpected size')
117 121 % (type, rev))
118 122 # read the trailing newline
119 123 self.catfilepipe[1].read(1)
120 124 return data
121 125
122 126 def getfile(self, name, rev):
123 127 if rev == nodemod.nullhex:
124 128 return None, None
125 129 if name == '.hgsub':
126 130 data = '\n'.join([m.hgsub() for m in self.submoditer()])
127 131 mode = ''
128 132 elif name == '.hgsubstate':
129 133 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
130 134 mode = ''
131 135 else:
132 136 data = self.catfile(rev, "blob")
133 137 mode = self.modecache[(name, rev)]
134 138 return data, mode
135 139
136 140 def submoditer(self):
137 141 null = nodemod.nullhex
138 142 for m in sorted(self.submodules, key=lambda p: p.path):
139 143 if m.node != null:
140 144 yield m
141 145
142 146 def parsegitmodules(self, content):
143 147 """Parse the formatted .gitmodules file, example file format:
144 148 [submodule "sub"]\n
145 149 \tpath = sub\n
146 150 \turl = git://giturl\n
147 151 """
148 152 self.submodules = []
149 153 c = config.config()
150 154 # Each item in .gitmodules starts with whitespace that cant be parsed
151 155 c.parse('.gitmodules', '\n'.join(line.strip() for line in
152 156 content.split('\n')))
153 157 for sec in c.sections():
154 158 s = c[sec]
155 159 if 'url' in s and 'path' in s:
156 160 self.submodules.append(submodule(s['path'], '', s['url']))
157 161
158 162 def retrievegitmodules(self, version):
159 163 modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
160 164 if ret:
161 165 # This can happen if a file is in the repo that has permissions
162 166 # 160000, but there is no .gitmodules file.
163 167 self.ui.warn(_("warning: cannot read submodules config file in "
164 168 "%s\n") % version)
165 169 return
166 170
167 171 try:
168 172 self.parsegitmodules(modules)
169 173 except error.ParseError:
170 174 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
171 175 % version)
172 176 return
173 177
174 178 for m in self.submodules:
175 179 node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
176 180 if ret:
177 181 continue
178 182 m.node = node.strip()
179 183
180 184 def getchanges(self, version, full):
181 185 if full:
182 186 raise error.Abort(_("convert from git does not support --full"))
183 187 self.modecache = {}
184 188 cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version]
185 189 output, status = self.gitrun(*cmd)
186 190 if status:
187 191 raise error.Abort(_('cannot read changes in %s') % version)
188 192 changes = []
189 193 copies = {}
190 194 seen = set()
191 195 entry = None
192 196 subexists = [False]
193 197 subdeleted = [False]
194 198 difftree = output.split('\x00')
195 199 lcount = len(difftree)
196 200 i = 0
197 201
198 202 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
199 203 False)
200 204 def add(entry, f, isdest):
201 205 seen.add(f)
202 206 h = entry[3]
203 207 p = (entry[1] == "100755")
204 208 s = (entry[1] == "120000")
205 209 renamesource = (not isdest and entry[4][0] == 'R')
206 210
207 211 if f == '.gitmodules':
208 212 if skipsubmodules:
209 213 return
210 214
211 215 subexists[0] = True
212 216 if entry[4] == 'D' or renamesource:
213 217 subdeleted[0] = True
214 218 changes.append(('.hgsub', nodemod.nullhex))
215 219 else:
216 220 changes.append(('.hgsub', ''))
217 221 elif entry[1] == '160000' or entry[0] == ':160000':
218 222 if not skipsubmodules:
219 223 subexists[0] = True
220 224 else:
221 225 if renamesource:
222 226 h = nodemod.nullhex
223 227 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
224 228 changes.append((f, h))
225 229
226 230 while i < lcount:
227 231 l = difftree[i]
228 232 i += 1
229 233 if not entry:
230 234 if not l.startswith(':'):
231 235 continue
232 236 entry = l.split()
233 237 continue
234 238 f = l
235 239 if entry[4][0] == 'C':
236 240 copysrc = f
237 241 copydest = difftree[i]
238 242 i += 1
239 243 f = copydest
240 244 copies[copydest] = copysrc
241 245 if f not in seen:
242 246 add(entry, f, False)
243 247 # A file can be copied multiple times, or modified and copied
244 248 # simultaneously. So f can be repeated even if fdest isn't.
245 249 if entry[4][0] == 'R':
246 250 # rename: next line is the destination
247 251 fdest = difftree[i]
248 252 i += 1
249 253 if fdest not in seen:
250 254 add(entry, fdest, True)
251 255 # .gitmodules isn't imported at all, so it being copied to
252 256 # and fro doesn't really make sense
253 257 if f != '.gitmodules' and fdest != '.gitmodules':
254 258 copies[fdest] = f
255 259 entry = None
256 260
257 261 if subexists[0]:
258 262 if subdeleted[0]:
259 263 changes.append(('.hgsubstate', nodemod.nullhex))
260 264 else:
261 265 self.retrievegitmodules(version)
262 266 changes.append(('.hgsubstate', ''))
263 267 return (changes, copies, set())
264 268
265 269 def getcommit(self, version):
266 270 c = self.catfile(version, "commit") # read the commit hash
267 271 end = c.find("\n\n")
268 272 message = c[end + 2:]
269 273 message = self.recode(message)
270 274 l = c[:end].splitlines()
271 275 parents = []
272 276 author = committer = None
273 277 for e in l[1:]:
274 278 n, v = e.split(" ", 1)
275 279 if n == "author":
276 280 p = v.split()
277 281 tm, tz = p[-2:]
278 282 author = " ".join(p[:-2])
279 283 if author[0] == "<": author = author[1:-1]
280 284 author = self.recode(author)
281 285 if n == "committer":
282 286 p = v.split()
283 287 tm, tz = p[-2:]
284 288 committer = " ".join(p[:-2])
285 289 if committer[0] == "<": committer = committer[1:-1]
286 290 committer = self.recode(committer)
287 291 if n == "parent":
288 292 parents.append(v)
289 293
290 294 if committer and committer != author:
291 295 message += "\ncommitter: %s\n" % committer
292 296 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
293 297 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
294 298 date = tm + " " + str(tz)
295 299
296 300 c = common.commit(parents=parents, date=date, author=author,
297 301 desc=message,
298 302 rev=version)
299 303 return c
300 304
301 305 def numcommits(self):
302 306 output, ret = self.gitrunlines('rev-list', '--all')
303 307 if ret:
304 308 raise error.Abort(_('cannot retrieve number of commits in %s') \
305 309 % self.path)
306 310 return len(output)
307 311
308 312 def gettags(self):
309 313 tags = {}
310 314 alltags = {}
311 315 output, status = self.gitrunlines('ls-remote', '--tags', self.path)
312 316
313 317 if status:
314 318 raise error.Abort(_('cannot read tags from %s') % self.path)
315 319 prefix = 'refs/tags/'
316 320
317 321 # Build complete list of tags, both annotated and bare ones
318 322 for line in output:
319 323 line = line.strip()
320 324 if line.startswith("error:") or line.startswith("fatal:"):
321 325 raise error.Abort(_('cannot read tags from %s') % self.path)
322 326 node, tag = line.split(None, 1)
323 327 if not tag.startswith(prefix):
324 328 continue
325 329 alltags[tag[len(prefix):]] = node
326 330
327 331 # Filter out tag objects for annotated tag refs
328 332 for tag in alltags:
329 333 if tag.endswith('^{}'):
330 334 tags[tag[:-3]] = alltags[tag]
331 335 else:
332 336 if tag + '^{}' in alltags:
333 337 continue
334 338 else:
335 339 tags[tag] = alltags[tag]
336 340
337 341 return tags
338 342
339 343 def getchangedfiles(self, version, i):
340 344 changes = []
341 345 if i is None:
342 346 output, status = self.gitrunlines('diff-tree', '--root', '-m',
343 347 '-r', version)
344 348 if status:
345 349 raise error.Abort(_('cannot read changes in %s') % version)
346 350 for l in output:
347 351 if "\t" not in l:
348 352 continue
349 353 m, f = l[:-1].split("\t")
350 354 changes.append(f)
351 355 else:
352 356 output, status = self.gitrunlines('diff-tree', '--name-only',
353 357 '--root', '-r', version,
354 358 '%s^%s' % (version, i + 1), '--')
355 359 if status:
356 360 raise error.Abort(_('cannot read changes in %s') % version)
357 361 changes = [f.rstrip('\n') for f in output]
358 362
359 363 return changes
360 364
361 365 def getbookmarks(self):
362 366 bookmarks = {}
363 367
364 368 # Handle local and remote branches
365 369 remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
366 370 reftypes = [
367 371 # (git prefix, hg prefix)
368 372 ('refs/remotes/origin/', remoteprefix + '/'),
369 373 ('refs/heads/', '')
370 374 ]
371 375
372 376 exclude = set([
373 377 'refs/remotes/origin/HEAD',
374 378 ])
375 379
376 380 try:
377 381 output, status = self.gitrunlines('show-ref')
378 382 for line in output:
379 383 line = line.strip()
380 384 rev, name = line.split(None, 1)
381 385 # Process each type of branch
382 386 for gitprefix, hgprefix in reftypes:
383 387 if not name.startswith(gitprefix) or name in exclude:
384 388 continue
385 389 name = '%s%s' % (hgprefix, name[len(gitprefix):])
386 390 bookmarks[name] = rev
387 391 except Exception:
388 392 pass
389 393
390 394 return bookmarks
391 395
392 396 def checkrevformat(self, revstr, mapname='splicemap'):
393 397 """ git revision string is a 40 byte hex """
394 398 self.checkhexformat(revstr, mapname)
@@ -1,753 +1,770 b''
1 1 #require git
2 2
3 3 $ echo "[core]" >> $HOME/.gitconfig
4 4 $ echo "autocrlf = false" >> $HOME/.gitconfig
5 5 $ echo "[core]" >> $HOME/.gitconfig
6 6 $ echo "autocrlf = false" >> $HOME/.gitconfig
7 7 $ echo "[extensions]" >> $HGRCPATH
8 8 $ echo "convert=" >> $HGRCPATH
9 9 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
10 10 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
11 11 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
12 12 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
13 13 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
14 14 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
15 15 $ INVALIDID1=afd12345af
16 16 $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
17 17 $ VALIDID1=39b3d83f9a69a9ba4ebb111461071a0af0027357
18 18 $ VALIDID2=8dd6476bd09d9c7776355dc454dafe38efaec5da
19 19 $ count=10
20 20 $ commit()
21 21 > {
22 22 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
23 23 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
24 24 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
25 25 > count=`expr $count + 1`
26 26 > }
27 27 $ mkdir git-repo
28 28 $ cd git-repo
29 29 $ git init-db >/dev/null 2>/dev/null
30 30 $ echo a > a
31 31 $ mkdir d
32 32 $ echo b > d/b
33 33 $ git add a d
34 34 $ commit -a -m t1
35 35
36 36 Remove the directory, then try to replace it with a file (issue754)
37 37
38 38 $ git rm -f d/b
39 39 rm 'd/b'
40 40 $ commit -m t2
41 41 $ echo d > d
42 42 $ git add d
43 43 $ commit -m t3
44 44 $ echo b >> a
45 45 $ commit -a -m t4.1
46 46 $ git checkout -b other HEAD~ >/dev/null 2>/dev/null
47 47 $ echo c > a
48 48 $ echo a >> a
49 49 $ commit -a -m t4.2
50 50 $ git checkout master >/dev/null 2>/dev/null
51 51 $ git pull --no-commit . other > /dev/null 2>/dev/null
52 52 $ commit -m 'Merge branch other'
53 53 $ cd ..
54 54 $ hg convert --config extensions.progress= --config progress.assume-tty=1 \
55 55 > --config progress.delay=0 --config progress.changedelay=0 \
56 56 > --config progress.refresh=0 --config progress.width=60 \
57 57 > --config progress.format='topic, bar, number' --datesort git-repo
58 58 \r (no-eol) (esc)
59 59 scanning [======> ] 1/6\r (no-eol) (esc)
60 60 scanning [=============> ] 2/6\r (no-eol) (esc)
61 61 scanning [=====================> ] 3/6\r (no-eol) (esc)
62 62 scanning [============================> ] 4/6\r (no-eol) (esc)
63 63 scanning [===================================> ] 5/6\r (no-eol) (esc)
64 64 scanning [===========================================>] 6/6\r (no-eol) (esc)
65 65 \r (no-eol) (esc)
66 66 \r (no-eol) (esc)
67 67 converting [ ] 0/6\r (no-eol) (esc)
68 68 getting files [==================> ] 1/2\r (no-eol) (esc)
69 69 getting files [======================================>] 2/2\r (no-eol) (esc)
70 70 \r (no-eol) (esc)
71 71 \r (no-eol) (esc)
72 72 converting [======> ] 1/6\r (no-eol) (esc)
73 73 getting files [======================================>] 1/1\r (no-eol) (esc)
74 74 \r (no-eol) (esc)
75 75 \r (no-eol) (esc)
76 76 converting [=============> ] 2/6\r (no-eol) (esc)
77 77 getting files [======================================>] 1/1\r (no-eol) (esc)
78 78 \r (no-eol) (esc)
79 79 \r (no-eol) (esc)
80 80 converting [====================> ] 3/6\r (no-eol) (esc)
81 81 getting files [======================================>] 1/1\r (no-eol) (esc)
82 82 \r (no-eol) (esc)
83 83 \r (no-eol) (esc)
84 84 converting [===========================> ] 4/6\r (no-eol) (esc)
85 85 getting files [======================================>] 1/1\r (no-eol) (esc)
86 86 \r (no-eol) (esc)
87 87 \r (no-eol) (esc)
88 88 converting [==================================> ] 5/6\r (no-eol) (esc)
89 89 getting files [======================================>] 1/1\r (no-eol) (esc)
90 90 \r (no-eol) (esc)
91 91 assuming destination git-repo-hg
92 92 initializing destination git-repo-hg repository
93 93 scanning source...
94 94 sorting...
95 95 converting...
96 96 5 t1
97 97 4 t2
98 98 3 t3
99 99 2 t4.1
100 100 1 t4.2
101 101 0 Merge branch other
102 102 updating bookmarks
103 103 $ hg up -q -R git-repo-hg
104 104 $ hg -R git-repo-hg tip -v
105 105 changeset: 5:c78094926be2
106 106 bookmark: master
107 107 tag: tip
108 108 parent: 3:f5f5cb45432b
109 109 parent: 4:4e174f80c67c
110 110 user: test <test@example.org>
111 111 date: Mon Jan 01 00:00:15 2007 +0000
112 112 files: a
113 113 description:
114 114 Merge branch other
115 115
116 116
117 117 $ count=10
118 118 $ mkdir git-repo2
119 119 $ cd git-repo2
120 120 $ git init-db >/dev/null 2>/dev/null
121 121 $ echo foo > foo
122 122 $ git add foo
123 123 $ commit -a -m 'add foo'
124 124 $ echo >> foo
125 125 $ commit -a -m 'change foo'
126 126 $ git checkout -b Bar HEAD~ >/dev/null 2>/dev/null
127 127 $ echo quux >> quux
128 128 $ git add quux
129 129 $ commit -a -m 'add quux'
130 130 $ echo bar > bar
131 131 $ git add bar
132 132 $ commit -a -m 'add bar'
133 133 $ git checkout -b Baz HEAD~ >/dev/null 2>/dev/null
134 134 $ echo baz > baz
135 135 $ git add baz
136 136 $ commit -a -m 'add baz'
137 137 $ git checkout master >/dev/null 2>/dev/null
138 138 $ git pull --no-commit . Bar Baz > /dev/null 2>/dev/null
139 139 $ commit -m 'Octopus merge'
140 140 $ echo bar >> bar
141 141 $ commit -a -m 'change bar'
142 142 $ git checkout -b Foo HEAD~ >/dev/null 2>/dev/null
143 143 $ echo >> foo
144 144 $ commit -a -m 'change foo'
145 145 $ git checkout master >/dev/null 2>/dev/null
146 146 $ git pull --no-commit -s ours . Foo > /dev/null 2>/dev/null
147 147 $ commit -m 'Discard change to foo'
148 148 $ cd ..
149 149 $ glog()
150 150 > {
151 151 > hg log -G --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
152 152 > }
153 153 $ splitrepo()
154 154 > {
155 155 > msg="$1"
156 156 > files="$2"
157 157 > opts=$3
158 158 > echo "% $files: $msg"
159 159 > prefix=`echo "$files" | sed -e 's/ /-/g'`
160 160 > fmap="$prefix.fmap"
161 161 > repo="$prefix.repo"
162 162 > for i in $files; do
163 163 > echo "include $i" >> "$fmap"
164 164 > done
165 165 > hg -q convert $opts --filemap "$fmap" --datesort git-repo2 "$repo"
166 166 > hg up -q -R "$repo"
167 167 > glog -R "$repo"
168 168 > hg -R "$repo" manifest --debug
169 169 > }
170 170
171 171 full conversion
172 172
173 173 $ hg convert --datesort git-repo2 fullrepo \
174 174 > --config extensions.progress= --config progress.assume-tty=1 \
175 175 > --config progress.delay=0 --config progress.changedelay=0 \
176 176 > --config progress.refresh=0 --config progress.width=60 \
177 177 > --config progress.format='topic, bar, number'
178 178 \r (no-eol) (esc)
179 179 scanning [===> ] 1/9\r (no-eol) (esc)
180 180 scanning [========> ] 2/9\r (no-eol) (esc)
181 181 scanning [=============> ] 3/9\r (no-eol) (esc)
182 182 scanning [==================> ] 4/9\r (no-eol) (esc)
183 183 scanning [=======================> ] 5/9\r (no-eol) (esc)
184 184 scanning [============================> ] 6/9\r (no-eol) (esc)
185 185 scanning [=================================> ] 7/9\r (no-eol) (esc)
186 186 scanning [======================================> ] 8/9\r (no-eol) (esc)
187 187 scanning [===========================================>] 9/9\r (no-eol) (esc)
188 188 \r (no-eol) (esc)
189 189 \r (no-eol) (esc)
190 190 converting [ ] 0/9\r (no-eol) (esc)
191 191 getting files [======================================>] 1/1\r (no-eol) (esc)
192 192 \r (no-eol) (esc)
193 193 \r (no-eol) (esc)
194 194 converting [===> ] 1/9\r (no-eol) (esc)
195 195 getting files [======================================>] 1/1\r (no-eol) (esc)
196 196 \r (no-eol) (esc)
197 197 \r (no-eol) (esc)
198 198 converting [========> ] 2/9\r (no-eol) (esc)
199 199 getting files [======================================>] 1/1\r (no-eol) (esc)
200 200 \r (no-eol) (esc)
201 201 \r (no-eol) (esc)
202 202 converting [=============> ] 3/9\r (no-eol) (esc)
203 203 getting files [======================================>] 1/1\r (no-eol) (esc)
204 204 \r (no-eol) (esc)
205 205 \r (no-eol) (esc)
206 206 converting [=================> ] 4/9\r (no-eol) (esc)
207 207 getting files [======================================>] 1/1\r (no-eol) (esc)
208 208 \r (no-eol) (esc)
209 209 \r (no-eol) (esc)
210 210 converting [======================> ] 5/9\r (no-eol) (esc)
211 211 getting files [===> ] 1/8\r (no-eol) (esc)
212 212 getting files [========> ] 2/8\r (no-eol) (esc)
213 213 getting files [=============> ] 3/8\r (no-eol) (esc)
214 214 getting files [==================> ] 4/8\r (no-eol) (esc)
215 215 getting files [=======================> ] 5/8\r (no-eol) (esc)
216 216 getting files [============================> ] 6/8\r (no-eol) (esc)
217 217 getting files [=================================> ] 7/8\r (no-eol) (esc)
218 218 getting files [======================================>] 8/8\r (no-eol) (esc)
219 219 \r (no-eol) (esc)
220 220 \r (no-eol) (esc)
221 221 converting [===========================> ] 6/9\r (no-eol) (esc)
222 222 getting files [======================================>] 1/1\r (no-eol) (esc)
223 223 \r (no-eol) (esc)
224 224 \r (no-eol) (esc)
225 225 converting [===============================> ] 7/9\r (no-eol) (esc)
226 226 getting files [======================================>] 1/1\r (no-eol) (esc)
227 227 \r (no-eol) (esc)
228 228 \r (no-eol) (esc)
229 229 converting [====================================> ] 8/9\r (no-eol) (esc)
230 230 getting files [==================> ] 1/2\r (no-eol) (esc)
231 231 getting files [======================================>] 2/2\r (no-eol) (esc)
232 232 \r (no-eol) (esc)
233 233 initializing destination fullrepo repository
234 234 scanning source...
235 235 sorting...
236 236 converting...
237 237 8 add foo
238 238 7 change foo
239 239 6 add quux
240 240 5 add bar
241 241 4 add baz
242 242 3 Octopus merge
243 243 2 change bar
244 244 1 change foo
245 245 0 Discard change to foo
246 246 updating bookmarks
247 247 $ hg up -q -R fullrepo
248 248 $ glog -R fullrepo
249 249 @ 9 "Discard change to foo" files: foo
250 250 |\
251 251 | o 8 "change foo" files: foo
252 252 | |
253 253 o | 7 "change bar" files: bar
254 254 |/
255 255 o 6 "(octopus merge fixup)" files:
256 256 |\
257 257 | o 5 "Octopus merge" files: baz
258 258 | |\
259 259 o | | 4 "add baz" files: baz
260 260 | | |
261 261 +---o 3 "add bar" files: bar
262 262 | |
263 263 o | 2 "add quux" files: quux
264 264 | |
265 265 | o 1 "change foo" files: foo
266 266 |/
267 267 o 0 "add foo" files: foo
268 268
269 269 $ hg -R fullrepo manifest --debug
270 270 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
271 271 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
272 272 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
273 273 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
274 274 $ splitrepo 'octopus merge' 'foo bar baz'
275 275 % foo bar baz: octopus merge
276 276 @ 8 "Discard change to foo" files: foo
277 277 |\
278 278 | o 7 "change foo" files: foo
279 279 | |
280 280 o | 6 "change bar" files: bar
281 281 |/
282 282 o 5 "(octopus merge fixup)" files:
283 283 |\
284 284 | o 4 "Octopus merge" files: baz
285 285 | |\
286 286 o | | 3 "add baz" files: baz
287 287 | | |
288 288 +---o 2 "add bar" files: bar
289 289 | |
290 290 | o 1 "change foo" files: foo
291 291 |/
292 292 o 0 "add foo" files: foo
293 293
294 294 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
295 295 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
296 296 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
297 297 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
298 298 % foo baz quux: only some parents of an octopus merge; "discard" a head
299 299 @ 6 "Discard change to foo" files: foo
300 300 |
301 301 o 5 "change foo" files: foo
302 302 |
303 303 o 4 "Octopus merge" files:
304 304 |\
305 305 | o 3 "add baz" files: baz
306 306 | |
307 307 | o 2 "add quux" files: quux
308 308 | |
309 309 o | 1 "change foo" files: foo
310 310 |/
311 311 o 0 "add foo" files: foo
312 312
313 313 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
314 314 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
315 315 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
316 316
317 317 test importing git renames and copies
318 318
319 319 $ cd git-repo2
320 320 $ git mv foo foo-renamed
321 321 since bar is not touched in this commit, this copy will not be detected
322 322 $ cp bar bar-copied
323 323 $ cp baz baz-copied
324 324 $ cp baz baz-copied2
325 325 $ cp baz ba-copy
326 326 $ echo baz2 >> baz
327 327 $ git add bar-copied baz-copied baz-copied2 ba-copy
328 328 $ commit -a -m 'rename and copy'
329 329 $ cd ..
330 330
331 331 input validation
332 332 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
333 333 abort: convert.git.similarity is not an integer ('foo')
334 334 [255]
335 335 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
336 336 abort: similarity must be between 0 and 100
337 337 [255]
338 338 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
339 339 abort: similarity must be between 0 and 100
340 340 [255]
341 341
342 342 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
343 343 $ hg -R fullrepo status -C --change master
344 344 M baz
345 345 A ba-copy
346 346 baz
347 347 A bar-copied
348 348 A baz-copied
349 349 baz
350 350 A baz-copied2
351 351 baz
352 352 A foo-renamed
353 353 foo
354 354 R foo
355 355
356 356 Ensure that the modification to the copy source was preserved
357 357 (there was a bug where if the copy dest was alphabetically prior to the copy
358 358 source, the copy source took the contents of the copy dest)
359 359 $ hg cat -r tip fullrepo/baz
360 360 baz
361 361 baz2
362 362
363 363 $ cd git-repo2
364 364 $ echo bar2 >> bar
365 365 $ commit -a -m 'change bar'
366 366 $ cp bar bar-copied2
367 367 $ git add bar-copied2
368 368 $ commit -a -m 'copy with no changes'
369 369 $ cd ..
370 370
371 371 $ hg -q convert --config convert.git.similarity=100 \
372 372 > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
373 373 $ hg -R fullrepo status -C --change master
374 374 A bar-copied2
375 375 bar
376 376
377 377 test binary conversion (issue1359)
378 378
379 379 $ count=19
380 380 $ mkdir git-repo3
381 381 $ cd git-repo3
382 382 $ git init-db >/dev/null 2>/dev/null
383 383 $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
384 384 $ git add b
385 385 $ commit -a -m addbinary
386 386 $ cd ..
387 387
388 388 convert binary file
389 389
390 390 $ hg convert git-repo3 git-repo3-hg
391 391 initializing destination git-repo3-hg repository
392 392 scanning source...
393 393 sorting...
394 394 converting...
395 395 0 addbinary
396 396 updating bookmarks
397 397 $ cd git-repo3-hg
398 398 $ hg up -C
399 399 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 400 $ $PYTHON -c 'print len(file("b", "rb").read())'
401 401 4096
402 402 $ cd ..
403 403
404 404 test author vs committer
405 405
406 406 $ mkdir git-repo4
407 407 $ cd git-repo4
408 408 $ git init-db >/dev/null 2>/dev/null
409 409 $ echo >> foo
410 410 $ git add foo
411 411 $ commit -a -m addfoo
412 412 $ echo >> foo
413 413 $ GIT_AUTHOR_NAME="nottest"
414 414 $ commit -a -m addfoo2
415 415 $ cd ..
416 416
417 417 convert author committer
418 418
419 419 $ hg convert git-repo4 git-repo4-hg
420 420 initializing destination git-repo4-hg repository
421 421 scanning source...
422 422 sorting...
423 423 converting...
424 424 1 addfoo
425 425 0 addfoo2
426 426 updating bookmarks
427 427 $ hg -R git-repo4-hg log -v
428 428 changeset: 1:d63e967f93da
429 429 bookmark: master
430 430 tag: tip
431 431 user: nottest <test@example.org>
432 432 date: Mon Jan 01 00:00:21 2007 +0000
433 433 files: foo
434 434 description:
435 435 addfoo2
436 436
437 437 committer: test <test@example.org>
438 438
439 439
440 440 changeset: 0:0735477b0224
441 441 user: test <test@example.org>
442 442 date: Mon Jan 01 00:00:20 2007 +0000
443 443 files: foo
444 444 description:
445 445 addfoo
446 446
447 447
448 448
449 449 --sourceorder should fail
450 450
451 451 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
452 452 initializing destination git-repo4-sourcesort-hg repository
453 453 abort: --sourcesort is not supported by this data source
454 454 [255]
455 455
456 456 test converting certain branches
457 457
458 458 $ mkdir git-testrevs
459 459 $ cd git-testrevs
460 460 $ git init
461 461 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
462 462 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
463 463 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
464 464 $ git checkout -b goodbranch 'HEAD^'
465 465 Switched to a new branch 'goodbranch'
466 466 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
467 467 $ git checkout -b badbranch 'HEAD^'
468 468 Switched to a new branch 'badbranch'
469 469 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
470 470 $ cd ..
471 471 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
472 472 initializing destination hg-testrevs repository
473 473 scanning source...
474 474 sorting...
475 475 converting...
476 476 2 first
477 477 1 good branch commit
478 478 0 master commit
479 479 updating bookmarks
480 480 $ cd hg-testrevs
481 481 $ hg log -G -T '{rev} {bookmarks}'
482 482 o 2 master
483 483 |
484 484 | o 1 goodbranch
485 485 |/
486 486 o 0
487 487
488 488 $ cd ..
489 489
490 490 test sub modules
491 491
492 492 $ mkdir git-repo5
493 493 $ cd git-repo5
494 494 $ git init-db >/dev/null 2>/dev/null
495 495 $ echo 'sub' >> foo
496 496 $ git add foo
497 497 $ commit -a -m 'addfoo'
498 498 $ BASE=`pwd`
499 499 $ cd ..
500 500 $ mkdir git-repo6
501 501 $ cd git-repo6
502 502 $ git init-db >/dev/null 2>/dev/null
503 503 $ git submodule add ${BASE} >/dev/null 2>/dev/null
504 504 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
505 505
506 506 test non-tab whitespace .gitmodules
507 507
508 508 $ cat >> .gitmodules <<EOF
509 509 > [submodule "git-repo5"]
510 510 > path = git-repo5
511 511 > url = git-repo5
512 512 > EOF
513 513 $ git commit -q -a -m "weird white space submodule"
514 514 $ cd ..
515 515 $ hg convert git-repo6 hg-repo6
516 516 initializing destination hg-repo6 repository
517 517 scanning source...
518 518 sorting...
519 519 converting...
520 520 1 addsubmodule
521 521 0 weird white space submodule
522 522 updating bookmarks
523 523
524 524 $ rm -rf hg-repo6
525 525 $ cd git-repo6
526 526 $ git reset --hard 'HEAD^' > /dev/null
527 527
528 528 test missing .gitmodules
529 529
530 530 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
531 531 $ git checkout HEAD .gitmodules
532 532 $ git rm .gitmodules
533 533 rm '.gitmodules'
534 534 $ git commit -q -m "remove .gitmodules" .gitmodules
535 535 $ git commit -q -m "missing .gitmodules"
536 536 $ cd ..
537 537 $ hg convert git-repo6 hg-repo6 --traceback 2>&1 | grep -v "fatal: Path '.gitmodules' does not exist"
538 538 initializing destination hg-repo6 repository
539 539 scanning source...
540 540 sorting...
541 541 converting...
542 542 2 addsubmodule
543 543 1 remove .gitmodules
544 544 0 missing .gitmodules
545 545 warning: cannot read submodules config file in * (glob)
546 546 updating bookmarks
547 547 $ rm -rf hg-repo6
548 548 $ cd git-repo6
549 549 $ rm -rf git-repo4
550 550 $ git reset --hard 'HEAD^^' > /dev/null
551 551 $ cd ..
552 552
553 553 test invalid splicemap1
554 554
555 555 $ cat > splicemap <<EOF
556 556 > $VALIDID1
557 557 > EOF
558 558 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
559 559 initializing destination git-repo2-splicemap1-hg repository
560 560 abort: syntax error in splicemap(1): child parent1[,parent2] expected
561 561 [255]
562 562
563 563 test invalid splicemap2
564 564
565 565 $ cat > splicemap <<EOF
566 566 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
567 567 > EOF
568 568 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
569 569 initializing destination git-repo2-splicemap2-hg repository
570 570 abort: syntax error in splicemap(1): child parent1[,parent2] expected
571 571 [255]
572 572
573 573 test invalid splicemap3
574 574
575 575 $ cat > splicemap <<EOF
576 576 > $INVALIDID1 $INVALIDID2
577 577 > EOF
578 578 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
579 579 initializing destination git-repo2-splicemap3-hg repository
580 580 abort: splicemap entry afd12345af is not a valid revision identifier
581 581 [255]
582 582
583 583 convert sub modules
584 584 $ hg convert git-repo6 git-repo6-hg
585 585 initializing destination git-repo6-hg repository
586 586 scanning source...
587 587 sorting...
588 588 converting...
589 589 0 addsubmodule
590 590 updating bookmarks
591 591 $ hg -R git-repo6-hg log -v
592 592 changeset: 0:* (glob)
593 593 bookmark: master
594 594 tag: tip
595 595 user: nottest <test@example.org>
596 596 date: Mon Jan 01 00:00:23 2007 +0000
597 597 files: .hgsub .hgsubstate
598 598 description:
599 599 addsubmodule
600 600
601 601 committer: test <test@example.org>
602 602
603 603
604 604
605 605 $ cd git-repo6-hg
606 606 $ hg up >/dev/null 2>/dev/null
607 607 $ cat .hgsubstate
608 608 * git-repo5 (glob)
609 609 $ cd git-repo5
610 610 $ cat foo
611 611 sub
612 612
613 613 $ cd ../..
614 614
615 615 make sure rename detection doesn't break removing and adding gitmodules
616 616
617 617 $ cd git-repo6
618 618 $ git mv .gitmodules .gitmodules-renamed
619 619 $ commit -a -m 'rename .gitmodules'
620 620 $ git mv .gitmodules-renamed .gitmodules
621 621 $ commit -a -m 'rename .gitmodules back'
622 622 $ cd ..
623 623
624 624 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
625 625 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
626 626 rename .gitmodules
627 627 $ hg -R git-repo6-hg status -C --change 'tip^'
628 628 A .gitmodules-renamed
629 629 R .hgsub
630 630 R .hgsubstate
631 631 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
632 632 rename .gitmodules back
633 633 $ hg -R git-repo6-hg status -C --change tip
634 634 A .hgsub
635 635 A .hgsubstate
636 636 R .gitmodules-renamed
637 637
638 638 convert the revision removing '.gitmodules' itself (and related
639 639 submodules)
640 640
641 641 $ cd git-repo6
642 642 $ git rm .gitmodules
643 643 rm '.gitmodules'
644 644 $ git rm --cached git-repo5
645 645 rm 'git-repo5'
646 646 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
647 647 $ cd ..
648 648
649 649 $ hg convert -q git-repo6 git-repo6-hg
650 650 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
651 651 remove .gitmodules and submodule git-repo5
652 652 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
653 653 .hgsub .hgsubstate
654 654
655 655 skip submodules in the conversion
656 656
657 657 $ hg convert -q git-repo6 no-submodules --config convert.git.skipsubmodules=True
658 658 $ hg -R no-submodules manifest --all
659 659 .gitmodules-renamed
660 660
661 661 convert using a different remote prefix
662 662 $ git init git-repo7
663 663 Initialized empty Git repository in $TESTTMP/git-repo7/.git/
664 664 $ cd git-repo7
665 665 TODO: it'd be nice to use (?) lines instead of grep -v to handle the
666 666 git output variance, but that doesn't currently work in the middle of
667 667 a block, so do this for now.
668 668 $ touch a && git add a && git commit -am "commit a" | grep -v changed
669 669 [master (root-commit) 8ae5f69] commit a
670 670 Author: nottest <test@example.org>
671 671 create mode 100644 a
672 672 $ cd ..
673 673 $ git clone git-repo7 git-repo7-client
674 674 Cloning into 'git-repo7-client'...
675 675 done.
676 676 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
677 677 initializing destination hg-repo7 repository
678 678 scanning source...
679 679 sorting...
680 680 converting...
681 681 0 commit a
682 682 updating bookmarks
683 683 $ hg -R hg-repo7 bookmarks
684 684 master 0:03bf38caa4c6
685 685 origin/master 0:03bf38caa4c6
686 686
687 687 Run convert when the remote branches have changed
688 688 (there was an old bug where the local convert read branches from the server)
689 689
690 690 $ cd git-repo7
691 691 $ echo a >> a
692 692 $ git commit -q -am "move master forward"
693 693 $ cd ..
694 694 $ rm -rf hg-repo7
695 695 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
696 696 initializing destination hg-repo7 repository
697 697 scanning source...
698 698 sorting...
699 699 converting...
700 700 0 commit a
701 701 updating bookmarks
702 702 $ hg -R hg-repo7 bookmarks
703 703 master 0:03bf38caa4c6
704 704 origin/master 0:03bf38caa4c6
705 705
706 706 damaged git repository tests:
707 707 In case the hard-coded hashes change, the following commands can be used to
708 708 list the hashes and their corresponding types in the repository:
709 709 cd git-repo4/.git/objects
710 710 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
711 711 cd ../../..
712 712
713 713 damage git repository by renaming a commit object
714 714 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
715 715 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
716 716 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
717 abort: cannot retrieve number of commits in git-repo4/.git
717 abort: cannot retrieve number of commits in $TESTTMP/git-repo4/.git
718 718 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
719 719 damage git repository by renaming a blob object
720 720
721 721 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
722 722 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
723 723 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
724 724 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
725 725 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
726 726 damage git repository by renaming a tree object
727 727
728 728 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
729 729 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
730 730 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
731 731 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
732 732
733 733 #if no-windows
734 734
735 735 test for escaping the repo name (CVE-2016-3069)
736 736
737 737 $ git init '`echo pwned >COMMAND-INJECTION`'
738 738 Initialized empty Git repository in $TESTTMP/`echo pwned >COMMAND-INJECTION`/.git/
739 739 $ cd '`echo pwned >COMMAND-INJECTION`'
740 740 $ git commit -q --allow-empty -m 'empty'
741 741 $ cd ..
742 742 $ hg convert '`echo pwned >COMMAND-INJECTION`' 'converted'
743 743 initializing destination converted repository
744 744 scanning source...
745 745 sorting...
746 746 converting...
747 747 0 empty
748 748 updating bookmarks
749 749 $ test -f COMMAND-INJECTION
750 750 [1]
751 751
752 test for safely passing paths to git (CVE-2016-3105)
753
754 $ git init 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
755 Initialized empty Git repository in $TESTTMP/ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #/.git/
756 $ cd 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
757 $ git commit -q --allow-empty -m 'empty'
758 $ cd ..
759 $ hg convert 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #' 'converted-git-ext'
760 initializing destination converted-git-ext repository
761 scanning source...
762 sorting...
763 converting...
764 0 empty
765 updating bookmarks
766 $ test -f GIT-EXT-COMMAND-INJECTION
767 [1]
768
752 769 #endif
753 770
@@ -1,535 +1,535 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > convert=
4 4 > [convert]
5 5 > hg.saverev=False
6 6 > EOF
7 7 $ hg help convert
8 8 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
9 9
10 10 convert a foreign SCM repository to a Mercurial one.
11 11
12 12 Accepted source formats [identifiers]:
13 13
14 14 - Mercurial [hg]
15 15 - CVS [cvs]
16 16 - Darcs [darcs]
17 17 - git [git]
18 18 - Subversion [svn]
19 19 - Monotone [mtn]
20 20 - GNU Arch [gnuarch]
21 21 - Bazaar [bzr]
22 22 - Perforce [p4]
23 23
24 24 Accepted destination formats [identifiers]:
25 25
26 26 - Mercurial [hg]
27 27 - Subversion [svn] (history on branches is not preserved)
28 28
29 29 If no revision is given, all revisions will be converted. Otherwise,
30 30 convert will only import up to the named revision (given in a format
31 31 understood by the source).
32 32
33 33 If no destination directory name is specified, it defaults to the basename
34 34 of the source with "-hg" appended. If the destination repository doesn't
35 35 exist, it will be created.
36 36
37 37 By default, all sources except Mercurial will use --branchsort. Mercurial
38 38 uses --sourcesort to preserve original revision numbers order. Sort modes
39 39 have the following effects:
40 40
41 41 --branchsort convert from parent to child revision when possible, which
42 42 means branches are usually converted one after the other.
43 43 It generates more compact repositories.
44 44 --datesort sort revisions by date. Converted repositories have good-
45 45 looking changelogs but are often an order of magnitude
46 46 larger than the same ones generated by --branchsort.
47 47 --sourcesort try to preserve source revisions order, only supported by
48 48 Mercurial sources.
49 49 --closesort try to move closed revisions as close as possible to parent
50 50 branches, only supported by Mercurial sources.
51 51
52 52 If "REVMAP" isn't given, it will be put in a default location
53 53 ("<dest>/.hg/shamap" by default). The "REVMAP" is a simple text file that
54 54 maps each source commit ID to the destination ID for that revision, like
55 55 so:
56 56
57 57 <source ID> <destination ID>
58 58
59 59 If the file doesn't exist, it's automatically created. It's updated on
60 60 each commit copied, so 'hg convert' can be interrupted and can be run
61 61 repeatedly to copy new commits.
62 62
63 63 The authormap is a simple text file that maps each source commit author to
64 64 a destination commit author. It is handy for source SCMs that use unix
65 65 logins to identify authors (e.g.: CVS). One line per author mapping and
66 66 the line format is:
67 67
68 68 source author = destination author
69 69
70 70 Empty lines and lines starting with a "#" are ignored.
71 71
72 72 The filemap is a file that allows filtering and remapping of files and
73 73 directories. Each line can contain one of the following directives:
74 74
75 75 include path/to/file-or-dir
76 76
77 77 exclude path/to/file-or-dir
78 78
79 79 rename path/to/source path/to/destination
80 80
81 81 Comment lines start with "#". A specified path matches if it equals the
82 82 full relative name of a file or one of its parent directories. The
83 83 "include" or "exclude" directive with the longest matching path applies,
84 84 so line order does not matter.
85 85
86 86 The "include" directive causes a file, or all files under a directory, to
87 87 be included in the destination repository. The default if there are no
88 88 "include" statements is to include everything. If there are any "include"
89 89 statements, nothing else is included. The "exclude" directive causes files
90 90 or directories to be omitted. The "rename" directive renames a file or
91 91 directory if it is converted. To rename from a subdirectory into the root
92 92 of the repository, use "." as the path to rename to.
93 93
94 94 "--full" will make sure the converted changesets contain exactly the right
95 95 files with the right content. It will make a full conversion of all files,
96 96 not just the ones that have changed. Files that already are correct will
97 97 not be changed. This can be used to apply filemap changes when converting
98 98 incrementally. This is currently only supported for Mercurial and
99 99 Subversion.
100 100
101 101 The splicemap is a file that allows insertion of synthetic history,
102 102 letting you specify the parents of a revision. This is useful if you want
103 103 to e.g. give a Subversion merge two parents, or graft two disconnected
104 104 series of history together. Each entry contains a key, followed by a
105 105 space, followed by one or two comma-separated values:
106 106
107 107 key parent1, parent2
108 108
109 109 The key is the revision ID in the source revision control system whose
110 110 parents should be modified (same format as a key in .hg/shamap). The
111 111 values are the revision IDs (in either the source or destination revision
112 112 control system) that should be used as the new parents for that node. For
113 113 example, if you have merged "release-1.0" into "trunk", then you should
114 114 specify the revision on "trunk" as the first parent and the one on the
115 115 "release-1.0" branch as the second.
116 116
117 117 The branchmap is a file that allows you to rename a branch when it is
118 118 being brought in from whatever external repository. When used in
119 119 conjunction with a splicemap, it allows for a powerful combination to help
120 120 fix even the most badly mismanaged repositories and turn them into nicely
121 121 structured Mercurial repositories. The branchmap contains lines of the
122 122 form:
123 123
124 124 original_branch_name new_branch_name
125 125
126 126 where "original_branch_name" is the name of the branch in the source
127 127 repository, and "new_branch_name" is the name of the branch is the
128 128 destination repository. No whitespace is allowed in the branch names. This
129 129 can be used to (for instance) move code in one repository from "default"
130 130 to a named branch.
131 131
132 132 Mercurial Source
133 133 ################
134 134
135 135 The Mercurial source recognizes the following configuration options, which
136 136 you can set on the command line with "--config":
137 137
138 138 convert.hg.ignoreerrors
139 139 ignore integrity errors when reading. Use it to fix
140 140 Mercurial repositories with missing revlogs, by converting
141 141 from and to Mercurial. Default is False.
142 142 convert.hg.saverev
143 143 store original revision ID in changeset (forces target IDs
144 144 to change). It takes a boolean argument and defaults to
145 145 False.
146 146 convert.hg.startrev
147 147 specify the initial Mercurial revision. The default is 0.
148 148 convert.hg.revs
149 149 revset specifying the source revisions to convert.
150 150
151 151 CVS Source
152 152 ##########
153 153
154 154 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
155 155 indicate the starting point of what will be converted. Direct access to
156 156 the repository files is not needed, unless of course the repository is
157 157 ":local:". The conversion uses the top level directory in the sandbox to
158 158 find the CVS repository, and then uses CVS rlog commands to find files to
159 159 convert. This means that unless a filemap is given, all files under the
160 160 starting directory will be converted, and that any directory
161 161 reorganization in the CVS sandbox is ignored.
162 162
163 163 The following options can be used with "--config":
164 164
165 165 convert.cvsps.cache
166 166 Set to False to disable remote log caching, for testing and
167 167 debugging purposes. Default is True.
168 168 convert.cvsps.fuzz
169 169 Specify the maximum time (in seconds) that is allowed
170 170 between commits with identical user and log message in a
171 171 single changeset. When very large files were checked in as
172 172 part of a changeset then the default may not be long enough.
173 173 The default is 60.
174 174 convert.cvsps.mergeto
175 175 Specify a regular expression to which commit log messages
176 176 are matched. If a match occurs, then the conversion process
177 177 will insert a dummy revision merging the branch on which
178 178 this log message occurs to the branch indicated in the
179 179 regex. Default is "{{mergetobranch ([-\w]+)}}"
180 180 convert.cvsps.mergefrom
181 181 Specify a regular expression to which commit log messages
182 182 are matched. If a match occurs, then the conversion process
183 183 will add the most recent revision on the branch indicated in
184 184 the regex as the second parent of the changeset. Default is
185 185 "{{mergefrombranch ([-\w]+)}}"
186 186 convert.localtimezone
187 187 use local time (as determined by the TZ environment
188 188 variable) for changeset date/times. The default is False
189 189 (use UTC).
190 190 hooks.cvslog Specify a Python function to be called at the end of
191 191 gathering the CVS log. The function is passed a list with
192 192 the log entries, and can modify the entries in-place, or add
193 193 or delete them.
194 194 hooks.cvschangesets
195 195 Specify a Python function to be called after the changesets
196 196 are calculated from the CVS log. The function is passed a
197 197 list with the changeset entries, and can modify the
198 198 changesets in-place, or add or delete them.
199 199
200 200 An additional "debugcvsps" Mercurial command allows the builtin changeset
201 201 merging code to be run without doing a conversion. Its parameters and
202 202 output are similar to that of cvsps 2.1. Please see the command help for
203 203 more details.
204 204
205 205 Subversion Source
206 206 #################
207 207
208 208 Subversion source detects classical trunk/branches/tags layouts. By
209 209 default, the supplied "svn://repo/path/" source URL is converted as a
210 210 single branch. If "svn://repo/path/trunk" exists it replaces the default
211 211 branch. If "svn://repo/path/branches" exists, its subdirectories are
212 212 listed as possible branches. If "svn://repo/path/tags" exists, it is
213 213 looked for tags referencing converted branches. Default "trunk",
214 214 "branches" and "tags" values can be overridden with following options. Set
215 215 them to paths relative to the source URL, or leave them blank to disable
216 216 auto detection.
217 217
218 218 The following options can be set with "--config":
219 219
220 220 convert.svn.branches
221 221 specify the directory containing branches. The default is
222 222 "branches".
223 223 convert.svn.tags
224 224 specify the directory containing tags. The default is
225 225 "tags".
226 226 convert.svn.trunk
227 227 specify the name of the trunk branch. The default is
228 228 "trunk".
229 229 convert.localtimezone
230 230 use local time (as determined by the TZ environment
231 231 variable) for changeset date/times. The default is False
232 232 (use UTC).
233 233
234 234 Source history can be retrieved starting at a specific revision, instead
235 235 of being integrally converted. Only single branch conversions are
236 236 supported.
237 237
238 238 convert.svn.startrev
239 239 specify start Subversion revision number. The default is 0.
240 240
241 241 Git Source
242 242 ##########
243 243
244 244 The Git importer converts commits from all reachable branches (refs in
245 245 refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
246 246 converted to bookmarks with the same name, with the leading 'refs/heads'
247 247 stripped. Git submodules are converted to Git subrepos in Mercurial.
248 248
249 249 The following options can be set with "--config":
250 250
251 251 convert.git.similarity
252 252 specify how similar files modified in a commit must be to be
253 253 imported as renames or copies, as a percentage between "0"
254 254 (disabled) and "100" (files must be identical). For example,
255 255 "90" means that a delete/add pair will be imported as a
256 256 rename if more than 90% of the file hasn't changed. The
257 257 default is "50".
258 258 convert.git.findcopiesharder
259 259 while detecting copies, look at all files in the working
260 260 copy instead of just changed ones. This is very expensive
261 261 for large projects, and is only effective when
262 262 "convert.git.similarity" is greater than 0. The default is
263 263 False.
264 264 convert.git.remoteprefix
265 265 remote refs are converted as bookmarks with
266 266 "convert.git.remoteprefix" as a prefix followed by a /. The
267 267 default is 'remote'.
268 268 convert.git.skipsubmodules
269 269 does not convert root level .gitmodules files or files with
270 270 160000 mode indicating a submodule. Default is False.
271 271
272 272 Perforce Source
273 273 ###############
274 274
275 275 The Perforce (P4) importer can be given a p4 depot path or a client
276 276 specification as source. It will convert all files in the source to a flat
277 277 Mercurial repository, ignoring labels, branches and integrations. Note
278 278 that when a depot path is given you then usually should specify a target
279 279 directory, because otherwise the target may be named "...-hg".
280 280
281 281 The following options can be set with "--config":
282 282
283 283 convert.p4.encoding
284 284 specify the encoding to use when decoding standard output of
285 285 the Perforce command line tool. The default is default
286 286 system encoding.
287 287 convert.p4.startrev
288 288 specify initial Perforce revision (a Perforce changelist
289 289 number).
290 290
291 291 Mercurial Destination
292 292 #####################
293 293
294 294 The Mercurial destination will recognize Mercurial subrepositories in the
295 295 destination directory, and update the .hgsubstate file automatically if
296 296 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
297 297 Converting a repository with subrepositories requires converting a single
298 298 repository at a time, from the bottom up.
299 299
300 300 The following options are supported:
301 301
302 302 convert.hg.clonebranches
303 303 dispatch source branches in separate clones. The default is
304 304 False.
305 305 convert.hg.tagsbranch
306 306 branch name for tag revisions, defaults to "default".
307 307 convert.hg.usebranchnames
308 308 preserve branch names. The default is True.
309 309 convert.hg.sourcename
310 310 records the given string as a 'convert_source' extra value
311 311 on each commit made in the target repository. The default is
312 312 None.
313 313
314 314 All Destinations
315 315 ################
316 316
317 317 All destination types accept the following options:
318 318
319 319 convert.skiptags
320 320 does not convert tags from the source repo to the target
321 321 repo. The default is False.
322 322
323 323 options ([+] can be repeated):
324 324
325 325 -s --source-type TYPE source repository type
326 326 -d --dest-type TYPE destination repository type
327 327 -r --rev REV [+] import up to source revision REV
328 328 -A --authormap FILE remap usernames using this file
329 329 --filemap FILE remap file names using contents of file
330 330 --full apply filemap changes by converting all files again
331 331 --splicemap FILE splice synthesized history into place
332 332 --branchmap FILE change branch names while converting
333 333 --branchsort try to sort changesets by branches
334 334 --datesort try to sort changesets by date
335 335 --sourcesort preserve source changesets order
336 336 --closesort try to reorder closed revisions
337 337
338 338 (some details hidden, use --verbose to show complete help)
339 339 $ hg init a
340 340 $ cd a
341 341 $ echo a > a
342 342 $ hg ci -d'0 0' -Ama
343 343 adding a
344 344 $ hg cp a b
345 345 $ hg ci -d'1 0' -mb
346 346 $ hg rm a
347 347 $ hg ci -d'2 0' -mc
348 348 $ hg mv b a
349 349 $ hg ci -d'3 0' -md
350 350 $ echo a >> a
351 351 $ hg ci -d'4 0' -me
352 352 $ cd ..
353 353 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
354 354 assuming destination a-hg
355 355 initializing destination a-hg repository
356 356 scanning source...
357 357 sorting...
358 358 converting...
359 359 4 a
360 360 3 b
361 361 2 c
362 362 1 d
363 363 0 e
364 364 $ hg --cwd a-hg pull ../a
365 365 pulling from ../a
366 366 searching for changes
367 367 no changes found
368 368
369 369 conversion to existing file should fail
370 370
371 371 $ touch bogusfile
372 372 $ hg convert a bogusfile
373 373 initializing destination bogusfile repository
374 374 abort: cannot create new bundle repository
375 375 [255]
376 376
377 377 #if unix-permissions no-root
378 378
379 379 conversion to dir without permissions should fail
380 380
381 381 $ mkdir bogusdir
382 382 $ chmod 000 bogusdir
383 383
384 384 $ hg convert a bogusdir
385 385 abort: Permission denied: 'bogusdir'
386 386 [255]
387 387
388 388 user permissions should succeed
389 389
390 390 $ chmod 700 bogusdir
391 391 $ hg convert a bogusdir
392 392 initializing destination bogusdir repository
393 393 scanning source...
394 394 sorting...
395 395 converting...
396 396 4 a
397 397 3 b
398 398 2 c
399 399 1 d
400 400 0 e
401 401
402 402 #endif
403 403
404 404 test pre and post conversion actions
405 405
406 406 $ echo 'include b' > filemap
407 407 $ hg convert --debug --filemap filemap a partialb | \
408 408 > grep 'run hg'
409 409 run hg source pre-conversion action
410 410 run hg sink pre-conversion action
411 411 run hg sink post-conversion action
412 412 run hg source post-conversion action
413 413
414 414 converting empty dir should fail "nicely
415 415
416 416 $ mkdir emptydir
417 417
418 418 override $PATH to ensure p4 not visible; use $PYTHON in case we're
419 419 running from a devel copy, not a temp installation
420 420
421 421 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
422 422 assuming destination emptydir-hg
423 423 initializing destination emptydir-hg repository
424 424 emptydir does not look like a CVS checkout
425 emptydir does not look like a Git repository
425 $TESTTMP/emptydir does not look like a Git repository
426 426 emptydir does not look like a Subversion repository
427 427 emptydir is not a local Mercurial repository
428 428 emptydir does not look like a darcs repository
429 429 emptydir does not look like a monotone repository
430 430 emptydir does not look like a GNU Arch repository
431 431 emptydir does not look like a Bazaar repository
432 432 cannot find required "p4" tool
433 433 abort: emptydir: missing or unsupported repository
434 434 [255]
435 435
436 436 convert with imaginary source type
437 437
438 438 $ hg convert --source-type foo a a-foo
439 439 initializing destination a-foo repository
440 440 abort: foo: invalid source repository type
441 441 [255]
442 442
443 443 convert with imaginary sink type
444 444
445 445 $ hg convert --dest-type foo a a-foo
446 446 abort: foo: invalid destination repository type
447 447 [255]
448 448
449 449 testing: convert must not produce duplicate entries in fncache
450 450
451 451 $ hg convert a b
452 452 initializing destination b repository
453 453 scanning source...
454 454 sorting...
455 455 converting...
456 456 4 a
457 457 3 b
458 458 2 c
459 459 1 d
460 460 0 e
461 461
462 462 contents of fncache file:
463 463
464 464 $ cat b/.hg/store/fncache | sort
465 465 data/a.i
466 466 data/b.i
467 467
468 468 test bogus URL
469 469
470 470 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
471 471 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
472 472 [255]
473 473
474 474 test revset converted() lookup
475 475
476 476 $ hg --config convert.hg.saverev=True convert a c
477 477 initializing destination c repository
478 478 scanning source...
479 479 sorting...
480 480 converting...
481 481 4 a
482 482 3 b
483 483 2 c
484 484 1 d
485 485 0 e
486 486 $ echo f > c/f
487 487 $ hg -R c ci -d'0 0' -Amf
488 488 adding f
489 489 created new head
490 490 $ hg -R c log -r "converted(09d945a62ce6)"
491 491 changeset: 1:98c3dd46a874
492 492 user: test
493 493 date: Thu Jan 01 00:00:01 1970 +0000
494 494 summary: b
495 495
496 496 $ hg -R c log -r "converted()"
497 497 changeset: 0:31ed57b2037c
498 498 user: test
499 499 date: Thu Jan 01 00:00:00 1970 +0000
500 500 summary: a
501 501
502 502 changeset: 1:98c3dd46a874
503 503 user: test
504 504 date: Thu Jan 01 00:00:01 1970 +0000
505 505 summary: b
506 506
507 507 changeset: 2:3b9ca06ef716
508 508 user: test
509 509 date: Thu Jan 01 00:00:02 1970 +0000
510 510 summary: c
511 511
512 512 changeset: 3:4e0debd37cf2
513 513 user: test
514 514 date: Thu Jan 01 00:00:03 1970 +0000
515 515 summary: d
516 516
517 517 changeset: 4:9de3bc9349c5
518 518 user: test
519 519 date: Thu Jan 01 00:00:04 1970 +0000
520 520 summary: e
521 521
522 522
523 523 test specifying a sourcename
524 524 $ echo g > a/g
525 525 $ hg -R a ci -d'0 0' -Amg
526 526 adding g
527 527 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
528 528 scanning source...
529 529 sorting...
530 530 converting...
531 531 0 g
532 532 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
533 533 branch=default
534 534 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
535 535 convert_source=mysource
General Comments 0
You need to be logged in to leave comments. Login now