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