##// END OF EJS Templates
convert/darcs: support changelogs with bytes 0x7F-0xFF (issue2411)...
Brodie Rao -
r12717:89df79b3 stable
parent child Browse files
Show More
@@ -1,188 +1,200 b''
1 # darcs.py - darcs support for the convert extension
1 # darcs.py - darcs support for the convert extension
2 #
2 #
3 # Copyright 2007-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2007-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from common import NoRepo, checktool, commandline, commit, converter_source
8 from common import NoRepo, checktool, commandline, commit, converter_source
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10 from mercurial import util
10 from mercurial import encoding, util
11 import os, shutil, tempfile, re
11 import os, shutil, tempfile, re
12
12
13 # The naming drift of ElementTree is fun!
13 # The naming drift of ElementTree is fun!
14
14
15 try:
15 try:
16 from xml.etree.cElementTree import ElementTree
16 from xml.etree.cElementTree import ElementTree, XMLParser
17 except ImportError:
17 except ImportError:
18 try:
18 try:
19 from xml.etree.ElementTree import ElementTree
19 from xml.etree.ElementTree import ElementTree, XMLParser
20 except ImportError:
20 except ImportError:
21 try:
21 try:
22 from elementtree.cElementTree import ElementTree
22 from elementtree.cElementTree import ElementTree, XMLParser
23 except ImportError:
23 except ImportError:
24 try:
24 try:
25 from elementtree.ElementTree import ElementTree
25 from elementtree.ElementTree import ElementTree, XMLParser
26 except ImportError:
26 except ImportError:
27 ElementTree = None
27 ElementTree = None
28
28
29 class darcs_source(converter_source, commandline):
29 class darcs_source(converter_source, commandline):
30 def __init__(self, ui, path, rev=None):
30 def __init__(self, ui, path, rev=None):
31 converter_source.__init__(self, ui, path, rev=rev)
31 converter_source.__init__(self, ui, path, rev=rev)
32 commandline.__init__(self, ui, 'darcs')
32 commandline.__init__(self, ui, 'darcs')
33
33
34 # check for _darcs, ElementTree so that we can easily skip
34 # check for _darcs, ElementTree so that we can easily skip
35 # test-convert-darcs if ElementTree is not around
35 # test-convert-darcs if ElementTree is not around
36 if not os.path.exists(os.path.join(path, '_darcs')):
36 if not os.path.exists(os.path.join(path, '_darcs')):
37 raise NoRepo(_("%s does not look like a darcs repository") % path)
37 raise NoRepo(_("%s does not look like a darcs repository") % path)
38
38
39 checktool('darcs')
39 checktool('darcs')
40 version = self.run0('--version').splitlines()[0].strip()
40 version = self.run0('--version').splitlines()[0].strip()
41 if version < '2.1':
41 if version < '2.1':
42 raise util.Abort(_('darcs version 2.1 or newer needed (found %r)') %
42 raise util.Abort(_('darcs version 2.1 or newer needed (found %r)') %
43 version)
43 version)
44
44
45 if ElementTree is None:
45 if ElementTree is None:
46 raise util.Abort(_("Python ElementTree module is not available"))
46 raise util.Abort(_("Python ElementTree module is not available"))
47
47
48 self.path = os.path.realpath(path)
48 self.path = os.path.realpath(path)
49
49
50 self.lastrev = None
50 self.lastrev = None
51 self.changes = {}
51 self.changes = {}
52 self.parents = {}
52 self.parents = {}
53 self.tags = {}
53 self.tags = {}
54
54
55 # Check darcs repository format
55 # Check darcs repository format
56 format = self.format()
56 format = self.format()
57 if format:
57 if format:
58 if format in ('darcs-1.0', 'hashed'):
58 if format in ('darcs-1.0', 'hashed'):
59 raise NoRepo(_("%s repository format is unsupported, "
59 raise NoRepo(_("%s repository format is unsupported, "
60 "please upgrade") % format)
60 "please upgrade") % format)
61 else:
61 else:
62 self.ui.warn(_('failed to detect repository format!'))
62 self.ui.warn(_('failed to detect repository format!'))
63
63
64 def before(self):
64 def before(self):
65 self.tmppath = tempfile.mkdtemp(
65 self.tmppath = tempfile.mkdtemp(
66 prefix='convert-' + os.path.basename(self.path) + '-')
66 prefix='convert-' + os.path.basename(self.path) + '-')
67 output, status = self.run('init', repodir=self.tmppath)
67 output, status = self.run('init', repodir=self.tmppath)
68 self.checkexit(status)
68 self.checkexit(status)
69
69
70 tree = self.xml('changes', xml_output=True, summary=True,
70 tree = self.xml('changes', xml_output=True, summary=True,
71 repodir=self.path)
71 repodir=self.path)
72 tagname = None
72 tagname = None
73 child = None
73 child = None
74 for elt in tree.findall('patch'):
74 for elt in tree.findall('patch'):
75 node = elt.get('hash')
75 node = elt.get('hash')
76 name = elt.findtext('name', '')
76 name = elt.findtext('name', '')
77 if name.startswith('TAG '):
77 if name.startswith('TAG '):
78 tagname = name[4:].strip()
78 tagname = name[4:].strip()
79 elif tagname is not None:
79 elif tagname is not None:
80 self.tags[tagname] = node
80 self.tags[tagname] = node
81 tagname = None
81 tagname = None
82 self.changes[node] = elt
82 self.changes[node] = elt
83 self.parents[child] = [node]
83 self.parents[child] = [node]
84 child = node
84 child = node
85 self.parents[child] = []
85 self.parents[child] = []
86
86
87 def after(self):
87 def after(self):
88 self.ui.debug('cleaning up %s\n' % self.tmppath)
88 self.ui.debug('cleaning up %s\n' % self.tmppath)
89 shutil.rmtree(self.tmppath, ignore_errors=True)
89 shutil.rmtree(self.tmppath, ignore_errors=True)
90
90
91 def recode(self, s, encoding=None):
92 if isinstance(s, unicode):
93 # XMLParser returns unicode objects for anything it can't
94 # encode into ASCII. We convert them back to str to get
95 # recode's normal conversion behavior.
96 s = s.encode('latin-1')
97 return super(darcs_source, self).recode(s, encoding)
98
91 def xml(self, cmd, **kwargs):
99 def xml(self, cmd, **kwargs):
92 # NOTE: darcs is currently encoding agnostic and will print
100 # NOTE: darcs is currently encoding agnostic and will print
93 # patch metadata byte-for-byte, even in the XML changelog.
101 # patch metadata byte-for-byte, even in the XML changelog.
94 etree = ElementTree()
102 etree = ElementTree()
103 # While we are decoding the XML as latin-1 to be as liberal as
104 # possible, etree will still raise an exception if any
105 # non-printable characters are in the XML changelog.
106 parser = XMLParser(encoding='latin-1')
95 fp = self._run(cmd, **kwargs)
107 fp = self._run(cmd, **kwargs)
96 etree.parse(fp)
108 etree.parse(fp, parser=parser)
97 self.checkexit(fp.close())
109 self.checkexit(fp.close())
98 return etree.getroot()
110 return etree.getroot()
99
111
100 def format(self):
112 def format(self):
101 output, status = self.run('show', 'repo', no_files=True,
113 output, status = self.run('show', 'repo', no_files=True,
102 repodir=self.path)
114 repodir=self.path)
103 self.checkexit(status)
115 self.checkexit(status)
104 m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
116 m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
105 if not m:
117 if not m:
106 return None
118 return None
107 return ','.join(sorted(f.strip() for f in m.group(1).split(',')))
119 return ','.join(sorted(f.strip() for f in m.group(1).split(',')))
108
120
109 def manifest(self):
121 def manifest(self):
110 man = []
122 man = []
111 output, status = self.run('show', 'files', no_directories=True,
123 output, status = self.run('show', 'files', no_directories=True,
112 repodir=self.tmppath)
124 repodir=self.tmppath)
113 self.checkexit(status)
125 self.checkexit(status)
114 for line in output.split('\n'):
126 for line in output.split('\n'):
115 path = line[2:]
127 path = line[2:]
116 if path:
128 if path:
117 man.append(path)
129 man.append(path)
118 return man
130 return man
119
131
120 def getheads(self):
132 def getheads(self):
121 return self.parents[None]
133 return self.parents[None]
122
134
123 def getcommit(self, rev):
135 def getcommit(self, rev):
124 elt = self.changes[rev]
136 elt = self.changes[rev]
125 date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
137 date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
126 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
138 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
127 # etree can return unicode objects for name, comment, and author,
139 # etree can return unicode objects for name, comment, and author,
128 # so recode() is used to ensure str objects are emitted.
140 # so recode() is used to ensure str objects are emitted.
129 return commit(author=self.recode(elt.get('author')),
141 return commit(author=self.recode(elt.get('author')),
130 date=util.datestr(date),
142 date=util.datestr(date),
131 desc=self.recode(desc).strip(),
143 desc=self.recode(desc).strip(),
132 parents=self.parents[rev])
144 parents=self.parents[rev])
133
145
134 def pull(self, rev):
146 def pull(self, rev):
135 output, status = self.run('pull', self.path, all=True,
147 output, status = self.run('pull', self.path, all=True,
136 match='hash %s' % rev,
148 match='hash %s' % rev,
137 no_test=True, no_posthook=True,
149 no_test=True, no_posthook=True,
138 external_merge='/bin/false',
150 external_merge='/bin/false',
139 repodir=self.tmppath)
151 repodir=self.tmppath)
140 if status:
152 if status:
141 if output.find('We have conflicts in') == -1:
153 if output.find('We have conflicts in') == -1:
142 self.checkexit(status, output)
154 self.checkexit(status, output)
143 output, status = self.run('revert', all=True, repodir=self.tmppath)
155 output, status = self.run('revert', all=True, repodir=self.tmppath)
144 self.checkexit(status, output)
156 self.checkexit(status, output)
145
157
146 def getchanges(self, rev):
158 def getchanges(self, rev):
147 copies = {}
159 copies = {}
148 changes = []
160 changes = []
149 man = None
161 man = None
150 for elt in self.changes[rev].find('summary').getchildren():
162 for elt in self.changes[rev].find('summary').getchildren():
151 if elt.tag in ('add_directory', 'remove_directory'):
163 if elt.tag in ('add_directory', 'remove_directory'):
152 continue
164 continue
153 if elt.tag == 'move':
165 if elt.tag == 'move':
154 if man is None:
166 if man is None:
155 man = self.manifest()
167 man = self.manifest()
156 source, dest = elt.get('from'), elt.get('to')
168 source, dest = elt.get('from'), elt.get('to')
157 if source in man:
169 if source in man:
158 # File move
170 # File move
159 changes.append((source, rev))
171 changes.append((source, rev))
160 changes.append((dest, rev))
172 changes.append((dest, rev))
161 copies[dest] = source
173 copies[dest] = source
162 else:
174 else:
163 # Directory move, deduce file moves from manifest
175 # Directory move, deduce file moves from manifest
164 source = source + '/'
176 source = source + '/'
165 for f in man:
177 for f in man:
166 if not f.startswith(source):
178 if not f.startswith(source):
167 continue
179 continue
168 fdest = dest + '/' + f[len(source):]
180 fdest = dest + '/' + f[len(source):]
169 changes.append((f, rev))
181 changes.append((f, rev))
170 changes.append((fdest, rev))
182 changes.append((fdest, rev))
171 copies[fdest] = f
183 copies[fdest] = f
172 else:
184 else:
173 changes.append((elt.text.strip(), rev))
185 changes.append((elt.text.strip(), rev))
174 self.pull(rev)
186 self.pull(rev)
175 self.lastrev = rev
187 self.lastrev = rev
176 return sorted(changes), copies
188 return sorted(changes), copies
177
189
178 def getfile(self, name, rev):
190 def getfile(self, name, rev):
179 if rev != self.lastrev:
191 if rev != self.lastrev:
180 raise util.Abort(_('internal calling inconsistency'))
192 raise util.Abort(_('internal calling inconsistency'))
181 path = os.path.join(self.tmppath, name)
193 path = os.path.join(self.tmppath, name)
182 data = open(path, 'rb').read()
194 data = open(path, 'rb').read()
183 mode = os.lstat(path).st_mode
195 mode = os.lstat(path).st_mode
184 mode = (mode & 0111) and 'x' or ''
196 mode = (mode & 0111) and 'x' or ''
185 return data, mode
197 return data, mode
186
198
187 def gettags(self):
199 def gettags(self):
188 return self.tags
200 return self.tags
@@ -1,83 +1,90 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" darcs || exit 80
3 "$TESTDIR/hghave" darcs || exit 80
4
4
5 echo "[extensions]" >> $HGRCPATH
5 echo "[extensions]" >> $HGRCPATH
6 echo "convert=" >> $HGRCPATH
6 echo "convert=" >> $HGRCPATH
7 echo 'graphlog =' >> $HGRCPATH
7 echo 'graphlog =' >> $HGRCPATH
8
8
9 DARCS_EMAIL='test@example.org'; export DARCS_EMAIL
9 DARCS_EMAIL='test@example.org'; export DARCS_EMAIL
10 HOME=`pwd`/do_not_use_HOME_darcs; export HOME
10 HOME=`pwd`/do_not_use_HOME_darcs; export HOME
11
11
12 # skip if we can't import elementtree
12 # skip if we can't import elementtree
13 mkdir dummy
13 mkdir dummy
14 mkdir dummy/_darcs
14 mkdir dummy/_darcs
15 if hg convert dummy 2>&1 | grep ElementTree > /dev/null; then
15 if hg convert dummy 2>&1 | grep ElementTree > /dev/null; then
16 echo 'skipped: missing feature: elementtree module'
16 echo 'skipped: missing feature: elementtree module'
17 exit 80
17 exit 80
18 fi
18 fi
19
19
20 echo '% try converting darcs1 repository'
20 echo '% try converting darcs1 repository'
21 hg convert -s darcs "$TESTDIR/darcs/darcs1" 2>&1 | grep darcs-1.0
21 hg convert -s darcs "$TESTDIR/darcs/darcs1" 2>&1 | grep darcs-1.0
22
22
23 echo % initialize darcs repo
23 echo % initialize darcs repo
24 mkdir darcs-repo
24 mkdir darcs-repo
25 cd darcs-repo
25 cd darcs-repo
26 darcs init
26 darcs init
27 echo a > a
27 echo a > a
28 darcs record -a -l -m p0
28 darcs record -a -l -m p0
29 cd ..
29 cd ..
30
30
31 echo % branch and update
31 echo % branch and update
32 darcs get darcs-repo darcs-clone >/dev/null
32 darcs get darcs-repo darcs-clone >/dev/null
33 cd darcs-clone
33 cd darcs-clone
34 echo c >> a
34 echo c >> a
35 echo c > c
35 echo c > c
36 darcs record -a -l -m p1.1
36 darcs record -a -l -m p1.1
37 cd ..
37 cd ..
38
38
39 echo % update source
39 echo % update source
40 cd darcs-repo
40 cd darcs-repo
41 echo b >> a
41 echo b >> a
42 echo b > b
42 echo b > b
43 darcs record -a -l -m p1.2
43 darcs record -a -l -m p1.2
44
44
45 echo % merge branch
45 echo % merge branch
46 darcs pull -a ../darcs-clone
46 darcs pull -a ../darcs-clone
47 sleep 1
47 sleep 1
48 echo e > a
48 echo e > a
49 echo f > f
49 echo f > f
50 mkdir dir
50 mkdir dir
51 echo d > dir/d
51 echo d > dir/d
52 echo d > dir/d2
52 echo d > dir/d2
53 darcs record -a -l -m p2
53 darcs record -a -l -m p2
54
54
55 echo % test file and directory move
55 echo % test file and directory move
56 darcs mv f ff
56 darcs mv f ff
57 # Test remove + move
57 # Test remove + move
58 darcs remove dir/d2
58 darcs remove dir/d2
59 rm dir/d2
59 rm dir/d2
60 darcs mv dir dir2
60 darcs mv dir dir2
61 darcs record -a -l -m p3
61 darcs record -a -l -m p3
62
62
63 echo % test utf-8 commit message and author
63 echo % test utf-8 commit message and author
64 echo g > g
64 echo g > g
65 # darcs is encoding agnostic, so it takes whatever bytes it's given
65 # darcs is encoding agnostic, so it takes whatever bytes it's given
66 darcs record -a -l -m 'p4: desc ñ' -A 'author ñ'
66 darcs record -a -l -m 'p4: desc ñ' -A 'author ñ'
67
67
68 echo % test latin-1 commit message
69 echo h > h
70 printf "p5: desc " > ../p5
71 python -c 'print "".join([chr(i) for i in range(128, 256)])' >> ../p5
72 darcs record -a -l --logfile ../p5
73
68 glog()
74 glog()
69 {
75 {
70 HGENCODING=utf-8 hg glog --template '{rev} "{desc|firstline}" ({author}) files: {files}\n' "$@"
76 hg glog --template '{rev} "{desc|firstline}" ({author}) files: {files}\n' "$@"
71 }
77 }
72
78
73 cd ..
79 cd ..
74 hg convert darcs-repo darcs-repo-hg
80 hg convert darcs-repo darcs-repo-hg
75 # The converter does not currently handle patch conflicts very well.
81 # The converter does not currently handle patch conflicts very well.
76 # When they occur, it reverts *all* changes and moves forward,
82 # When they occur, it reverts *all* changes and moves forward,
77 # letting the conflict resolving patch fix collisions.
83 # letting the conflict resolving patch fix collisions.
78 # Unfortunately, non-conflicting changes, like the addition of the
84 # Unfortunately, non-conflicting changes, like the addition of the
79 # "c" file in p1.1 patch are reverted too.
85 # "c" file in p1.1 patch are reverted too.
80 # Just to say that manifest not listing "c" here is a bug.
86 # Just to say that manifest not listing "c" here is a bug.
81 glog -R darcs-repo-hg
87 HGENCODING=latin-1 glog -R darcs-repo-hg -r 6 | "$TESTDIR"/printrepr.py
88 HGENCODING=utf-8 glog -R darcs-repo-hg -r 0:5 | "$TESTDIR"/printrepr.py
82 hg up -q -R darcs-repo-hg
89 hg up -q -R darcs-repo-hg
83 hg -R darcs-repo-hg manifest --debug
90 hg -R darcs-repo-hg manifest --debug
@@ -1,45 +1,51 b''
1 % try converting darcs1 repository
1 % try converting darcs1 repository
2 darcs-1.0 repository format is unsupported, please upgrade
2 darcs-1.0 repository format is unsupported, please upgrade
3 % initialize darcs repo
3 % initialize darcs repo
4 Finished recording patch 'p0'
4 Finished recording patch 'p0'
5 % branch and update
5 % branch and update
6 Finished recording patch 'p1.1'
6 Finished recording patch 'p1.1'
7 % update source
7 % update source
8 Finished recording patch 'p1.2'
8 Finished recording patch 'p1.2'
9 % merge branch
9 % merge branch
10 Backing up ./a(-darcs-backup0)
10 Backing up ./a(-darcs-backup0)
11 We have conflicts in the following files:
11 We have conflicts in the following files:
12 ./a
12 ./a
13 Finished pulling and applying.
13 Finished pulling and applying.
14 Finished recording patch 'p2'
14 Finished recording patch 'p2'
15 % test file and directory move
15 % test file and directory move
16 Finished recording patch 'p3'
16 Finished recording patch 'p3'
17 % test utf-8 commit message and author
17 % test utf-8 commit message and author
18 Finished recording patch 'p4: desc ñ'
18 Finished recording patch 'p4: desc ñ'
19 % test latin-1 commit message
20 Finished recording patch 'p5: desc ��������������������������������������������������������������������������������������������������������������������������������'
19 initializing destination darcs-repo-hg repository
21 initializing destination darcs-repo-hg repository
20 scanning source...
22 scanning source...
21 sorting...
23 sorting...
22 converting...
24 converting...
23 5 p0
25 6 p0
24 4 p1.2
26 5 p1.2
25 3 p1.1
27 4 p1.1
26 2 p2
28 3 p2
27 1 p3
29 2 p3
28 0 p4: desc ?
30 1 p4: desc ?
29 o 5 "p4: desc ñ" (author ñ) files: g
31 0 p5: desc ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
32 o 6 "p5: desc \x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" (test@example.org) files: h
33 |
34 o 5 "p4: desc \xc3\xb1" (author \xc3\xb1) files: g
30 |
35 |
31 o 4 "p3" (test@example.org) files: dir/d dir/d2 dir2/d f ff
36 o 4 "p3" (test@example.org) files: dir/d dir/d2 dir2/d f ff
32 |
37 |
33 o 3 "p2" (test@example.org) files: a dir/d dir/d2 f
38 o 3 "p2" (test@example.org) files: a dir/d dir/d2 f
34 |
39 |
35 o 2 "p1.1" (test@example.org) files:
40 o 2 "p1.1" (test@example.org) files:
36 |
41 |
37 o 1 "p1.2" (test@example.org) files: a b
42 o 1 "p1.2" (test@example.org) files: a b
38 |
43 |
39 o 0 "p0" (test@example.org) files: a
44 o 0 "p0" (test@example.org) files: a
40
45
41 7225b30cdf38257d5cc7780772c051b6f33e6d6b 644 a
46 7225b30cdf38257d5cc7780772c051b6f33e6d6b 644 a
42 1e88685f5ddec574a34c70af492f95b6debc8741 644 b
47 1e88685f5ddec574a34c70af492f95b6debc8741 644 b
43 37406831adc447ec2385014019599dfec953c806 644 dir2/d
48 37406831adc447ec2385014019599dfec953c806 644 dir2/d
44 b783a337463792a5c7d548ad85a7d3253c16ba8c 644 ff
49 b783a337463792a5c7d548ad85a7d3253c16ba8c 644 ff
45 0973eb1b2ecc4de7fafe7447ce1b7462108b4848 644 g
50 0973eb1b2ecc4de7fafe7447ce1b7462108b4848 644 g
51 fe6f8b4f507fe3eb524c527192a84920a4288dac 644 h
General Comments 0
You need to be logged in to leave comments. Login now