##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r7477:1e8d7339 merge default
parent child Browse files
Show More
@@ -0,0 +1,415
1 SVN-fs-dump-format-version: 2
2
3 UUID: 7b60030a-5a1f-4344-a009-73f0c1c2adf2
4
5 Revision-number: 0
6 Prop-content-length: 56
7 Content-length: 56
8
9 K 8
10 svn:date
11 V 27
12 2008-12-06T12:47:52.296168Z
13 PROPS-END
14
15 Revision-number: 1
16 Prop-content-length: 112
17 Content-length: 112
18
19 K 7
20 svn:log
21 V 10
22 init projA
23 K 10
24 svn:author
25 V 7
26 pmezard
27 K 8
28 svn:date
29 V 27
30 2008-12-06T12:47:52.342238Z
31 PROPS-END
32
33 Node-path: branches
34 Node-kind: dir
35 Node-action: add
36 Prop-content-length: 10
37 Content-length: 10
38
39 PROPS-END
40
41
42 Node-path: tags
43 Node-kind: dir
44 Node-action: add
45 Prop-content-length: 10
46 Content-length: 10
47
48 PROPS-END
49
50
51 Node-path: trunk
52 Node-kind: dir
53 Node-action: add
54 Prop-content-length: 10
55 Content-length: 10
56
57 PROPS-END
58
59
60 Revision-number: 2
61 Prop-content-length: 106
62 Content-length: 106
63
64 K 7
65 svn:log
66 V 5
67 hello
68 K 10
69 svn:author
70 V 7
71 pmezard
72 K 8
73 svn:date
74 V 27
75 2008-12-06T12:47:53.190046Z
76 PROPS-END
77
78 Node-path: branches/notinbranch
79 Node-kind: file
80 Node-action: add
81 Prop-content-length: 10
82 Text-content-length: 2
83 Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b
84 Content-length: 12
85
86 PROPS-END
87 d
88
89
90 Node-path: trunk/a
91 Node-kind: file
92 Node-action: add
93 Prop-content-length: 10
94 Text-content-length: 2
95 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
96 Content-length: 12
97
98 PROPS-END
99 a
100
101
102 Node-path: trunk/b
103 Node-kind: file
104 Node-action: add
105 Prop-content-length: 10
106 Text-content-length: 2
107 Text-content-md5: 3b5d5c3712955042212316173ccf37be
108 Content-length: 12
109
110 PROPS-END
111 b
112
113
114 Node-path: trunk/c
115 Node-kind: file
116 Node-action: add
117 Prop-content-length: 10
118 Text-content-length: 2
119 Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
120 Content-length: 12
121
122 PROPS-END
123 c
124
125
126 Revision-number: 3
127 Prop-content-length: 124
128 Content-length: 124
129
130 K 7
131 svn:log
132 V 22
133 branch trunk, remove c
134 K 10
135 svn:author
136 V 7
137 pmezard
138 K 8
139 svn:date
140 V 27
141 2008-12-06T12:47:55.188535Z
142 PROPS-END
143
144 Node-path: branches/old
145 Node-kind: dir
146 Node-action: add
147 Node-copyfrom-rev: 1
148 Node-copyfrom-path: trunk
149 Prop-content-length: 34
150 Content-length: 34
151
152 K 13
153 svn:mergeinfo
154 V 0
155
156 PROPS-END
157
158
159 Node-path: branches/old/a
160 Node-kind: file
161 Node-action: add
162 Node-copyfrom-rev: 2
163 Node-copyfrom-path: trunk/a
164 Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
165
166
167 Node-path: branches/old/b
168 Node-kind: file
169 Node-action: add
170 Node-copyfrom-rev: 2
171 Node-copyfrom-path: trunk/b
172 Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
173
174
175 Revision-number: 4
176 Prop-content-length: 109
177 Content-length: 109
178
179 K 7
180 svn:log
181 V 8
182 change a
183 K 10
184 svn:author
185 V 7
186 pmezard
187 K 8
188 svn:date
189 V 27
190 2008-12-06T12:47:57.146347Z
191 PROPS-END
192
193 Node-path: trunk/a
194 Node-kind: file
195 Node-action: change
196 Text-content-length: 4
197 Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
198 Content-length: 4
199
200 a
201 a
202
203
204 Revision-number: 5
205 Prop-content-length: 109
206 Content-length: 109
207
208 K 7
209 svn:log
210 V 8
211 change b
212 K 10
213 svn:author
214 V 7
215 pmezard
216 K 8
217 svn:date
218 V 27
219 2008-12-06T12:47:58.150124Z
220 PROPS-END
221
222 Node-path: branches/old/b
223 Node-kind: file
224 Node-action: change
225 Text-content-length: 4
226 Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2
227 Content-length: 4
228
229 b
230 b
231
232
233 Revision-number: 6
234 Prop-content-length: 119
235 Content-length: 119
236
237 K 7
238 svn:log
239 V 17
240 move and update c
241 K 10
242 svn:author
243 V 7
244 pmezard
245 K 8
246 svn:date
247 V 27
248 2008-12-06T12:48:00.161336Z
249 PROPS-END
250
251 Node-path: branches/old/c
252 Node-kind: file
253 Node-action: add
254 Node-copyfrom-rev: 3
255 Node-copyfrom-path: trunk/b
256 Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
257 Prop-content-length: 34
258 Text-content-length: 4
259 Text-content-md5: 33cb6785d50937d8d307ebb66d6259a7
260 Content-length: 38
261
262 K 13
263 svn:mergeinfo
264 V 0
265
266 PROPS-END
267 b
268 c
269
270
271 Node-path: trunk/b
272 Node-action: delete
273
274
275 Revision-number: 7
276 Prop-content-length: 116
277 Content-length: 116
278
279 K 7
280 svn:log
281 V 14
282 change b again
283 K 10
284 svn:author
285 V 7
286 pmezard
287 K 8
288 svn:date
289 V 27
290 2008-12-06T12:48:01.153724Z
291 PROPS-END
292
293 Node-path: branches/old/b
294 Node-kind: file
295 Node-action: change
296 Text-content-length: 6
297 Text-content-md5: cdcfb41554e2d092c13f5e6839e63577
298 Content-length: 6
299
300 b
301 b
302 b
303
304
305 Revision-number: 8
306 Prop-content-length: 114
307 Content-length: 114
308
309 K 7
310 svn:log
311 V 12
312 move to old2
313 K 10
314 svn:author
315 V 7
316 pmezard
317 K 8
318 svn:date
319 V 27
320 2008-12-06T12:48:04.150915Z
321 PROPS-END
322
323 Node-path: branches/old2
324 Node-kind: dir
325 Node-action: add
326 Node-copyfrom-rev: 7
327 Node-copyfrom-path: branches/old
328
329
330 Node-path: branches/old
331 Node-action: delete
332
333
334 Revision-number: 9
335 Prop-content-length: 118
336 Content-length: 118
337
338 K 7
339 svn:log
340 V 16
341 move back to old
342 K 10
343 svn:author
344 V 7
345 pmezard
346 K 8
347 svn:date
348 V 27
349 2008-12-06T12:48:06.149560Z
350 PROPS-END
351
352 Node-path: branches/old
353 Node-kind: dir
354 Node-action: add
355 Node-copyfrom-rev: 8
356 Node-copyfrom-path: branches/old2
357
358
359 Node-path: branches/old2
360 Node-action: delete
361
362
363 Revision-number: 10
364 Prop-content-length: 118
365 Content-length: 118
366
367 K 7
368 svn:log
369 V 16
370 last change to a
371 K 10
372 svn:author
373 V 7
374 pmezard
375 K 8
376 svn:date
377 V 27
378 2008-12-06T12:48:07.268498Z
379 PROPS-END
380
381 Node-path: trunk/a
382 Node-kind: file
383 Node-action: change
384 Text-content-length: 2
385 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
386 Content-length: 2
387
388 a
389
390
391 Revision-number: 11
392 Prop-content-length: 126
393 Content-length: 126
394
395 K 7
396 svn:log
397 V 24
398 branch trunk@1 into old3
399 K 10
400 svn:author
401 V 7
402 pmezard
403 K 8
404 svn:date
405 V 27
406 2008-12-06T12:48:09.151702Z
407 PROPS-END
408
409 Node-path: branches/old3
410 Node-kind: dir
411 Node-action: add
412 Node-copyfrom-rev: 1
413 Node-copyfrom-path: trunk
414
415
@@ -0,0 +1,401
1 SVN-fs-dump-format-version: 2
2
3 UUID: 0682b859-320d-4a69-a164-a7cab5695072
4
5 Revision-number: 0
6 Prop-content-length: 56
7 Content-length: 56
8
9 K 8
10 svn:date
11 V 27
12 2008-12-06T13:33:36.768573Z
13 PROPS-END
14
15 Revision-number: 1
16 Prop-content-length: 112
17 Content-length: 112
18
19 K 7
20 svn:log
21 V 10
22 init projA
23 K 10
24 svn:author
25 V 7
26 pmezard
27 K 8
28 svn:date
29 V 27
30 2008-12-06T13:33:37.083146Z
31 PROPS-END
32
33 Node-path: trunk
34 Node-kind: dir
35 Node-action: add
36 Prop-content-length: 10
37 Content-length: 10
38
39 PROPS-END
40
41
42 Node-path: trunk/a
43 Node-kind: file
44 Node-action: add
45 Prop-content-length: 10
46 Text-content-length: 2
47 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
48 Content-length: 12
49
50 PROPS-END
51 a
52
53
54 Node-path: trunk/d1
55 Node-kind: dir
56 Node-action: add
57 Prop-content-length: 10
58 Content-length: 10
59
60 PROPS-END
61
62
63 Node-path: trunk/d1/b
64 Node-kind: file
65 Node-action: add
66 Prop-content-length: 10
67 Text-content-length: 2
68 Text-content-md5: 3b5d5c3712955042212316173ccf37be
69 Content-length: 12
70
71 PROPS-END
72 b
73
74
75 Node-path: trunk/d1/c
76 Node-kind: file
77 Node-action: add
78 Prop-content-length: 10
79 Text-content-length: 2
80 Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
81 Content-length: 12
82
83 PROPS-END
84 c
85
86
87 Node-path: trunk/d2
88 Node-kind: dir
89 Node-action: add
90 Prop-content-length: 10
91 Content-length: 10
92
93 PROPS-END
94
95
96 Node-path: trunk/d2/d
97 Node-kind: file
98 Node-action: add
99 Prop-content-length: 10
100 Text-content-length: 2
101 Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b
102 Content-length: 12
103
104 PROPS-END
105 d
106
107
108 Revision-number: 2
109 Prop-content-length: 118
110 Content-length: 118
111
112 K 7
113 svn:log
114 V 16
115 commitbeforemove
116 K 10
117 svn:author
118 V 7
119 pmezard
120 K 8
121 svn:date
122 V 27
123 2008-12-06T13:33:38.152773Z
124 PROPS-END
125
126 Node-path: trunk/a
127 Node-kind: file
128 Node-action: change
129 Text-content-length: 4
130 Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
131 Content-length: 4
132
133 a
134 a
135
136
137 Node-path: trunk/d1/c
138 Node-kind: file
139 Node-action: change
140 Text-content-length: 4
141 Text-content-md5: 63fad9092ad37713ebe26b3193f89c41
142 Content-length: 4
143
144 c
145 c
146
147
148 Revision-number: 3
149 Prop-content-length: 112
150 Content-length: 112
151
152 K 7
153 svn:log
154 V 10
155 movedtrunk
156 K 10
157 svn:author
158 V 7
159 pmezard
160 K 8
161 svn:date
162 V 27
163 2008-12-06T13:33:39.146388Z
164 PROPS-END
165
166 Node-path: subproject
167 Node-kind: dir
168 Node-action: add
169 Node-copyfrom-rev: 2
170 Node-copyfrom-path: trunk
171
172
173 Node-path: trunk
174 Node-action: delete
175
176
177 Revision-number: 4
178 Prop-content-length: 113
179 Content-length: 113
180
181 K 7
182 svn:log
183 V 11
184 createtrunk
185 K 10
186 svn:author
187 V 7
188 pmezard
189 K 8
190 svn:date
191 V 27
192 2008-12-06T13:33:40.179944Z
193 PROPS-END
194
195 Node-path: subproject/trunk
196 Node-kind: dir
197 Node-action: add
198 Prop-content-length: 10
199 Content-length: 10
200
201 PROPS-END
202
203
204 Revision-number: 5
205 Prop-content-length: 116
206 Content-length: 116
207
208 K 7
209 svn:log
210 V 14
211 createbranches
212 K 10
213 svn:author
214 V 7
215 pmezard
216 K 8
217 svn:date
218 V 27
219 2008-12-06T13:33:41.184505Z
220 PROPS-END
221
222 Node-path: subproject/branches
223 Node-kind: dir
224 Node-action: add
225 Prop-content-length: 10
226 Content-length: 10
227
228 PROPS-END
229
230
231 Revision-number: 6
232 Prop-content-length: 107
233 Content-length: 107
234
235 K 7
236 svn:log
237 V 6
238 moved1
239 K 10
240 svn:author
241 V 7
242 pmezard
243 K 8
244 svn:date
245 V 27
246 2008-12-06T13:33:42.153312Z
247 PROPS-END
248
249 Node-path: subproject/trunk/d1
250 Node-kind: dir
251 Node-action: add
252 Node-copyfrom-rev: 5
253 Node-copyfrom-path: subproject/d1
254
255
256 Node-path: subproject/d1
257 Node-action: delete
258
259
260 Revision-number: 7
261 Prop-content-length: 107
262 Content-length: 107
263
264 K 7
265 svn:log
266 V 6
267 moved2
268 K 10
269 svn:author
270 V 7
271 pmezard
272 K 8
273 svn:date
274 V 27
275 2008-12-06T13:33:42.206313Z
276 PROPS-END
277
278 Node-path: subproject/trunk/d2
279 Node-kind: dir
280 Node-action: add
281 Node-copyfrom-rev: 6
282 Node-copyfrom-path: subproject/d2
283
284
285 Node-path: subproject/d2
286 Node-action: delete
287
288
289 Revision-number: 8
290 Prop-content-length: 119
291 Content-length: 119
292
293 K 7
294 svn:log
295 V 17
296 changeb and rm d2
297 K 10
298 svn:author
299 V 7
300 pmezard
301 K 8
302 svn:date
303 V 27
304 2008-12-06T13:33:43.182355Z
305 PROPS-END
306
307 Node-path: subproject/trunk/d1/b
308 Node-kind: file
309 Node-action: change
310 Text-content-length: 4
311 Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2
312 Content-length: 4
313
314 b
315 b
316
317
318 Node-path: subproject/trunk/d2
319 Node-action: delete
320
321
322 Revision-number: 9
323 Prop-content-length: 113
324 Content-length: 113
325
326 K 7
327 svn:log
328 V 11
329 moved1again
330 K 10
331 svn:author
332 V 7
333 pmezard
334 K 8
335 svn:date
336 V 27
337 2008-12-06T13:33:44.153682Z
338 PROPS-END
339
340 Node-path: subproject/branches/d1
341 Node-kind: dir
342 Node-action: add
343 Node-copyfrom-rev: 8
344 Node-copyfrom-path: subproject/trunk/d1
345
346
347 Node-path: subproject/trunk/d1
348 Node-action: delete
349
350
351 Revision-number: 10
352 Prop-content-length: 118
353 Content-length: 118
354
355 K 7
356 svn:log
357 V 16
358 copyfilefrompast
359 K 10
360 svn:author
361 V 7
362 pmezard
363 K 8
364 svn:date
365 V 27
366 2008-12-06T13:33:44.298011Z
367 PROPS-END
368
369 Node-path: subproject/trunk/d
370 Node-kind: file
371 Node-action: add
372 Node-copyfrom-rev: 7
373 Node-copyfrom-path: subproject/trunk/d2/d
374 Text-copy-source-md5: e29311f6f1bf1af907f9ef9f44b8328b
375
376
377 Revision-number: 11
378 Prop-content-length: 117
379 Content-length: 117
380
381 K 7
382 svn:log
383 V 15
384 copydirfrompast
385 K 10
386 svn:author
387 V 7
388 pmezard
389 K 8
390 svn:date
391 V 27
392 2008-12-06T13:33:44.349920Z
393 PROPS-END
394
395 Node-path: subproject/trunk/d2
396 Node-kind: dir
397 Node-action: add
398 Node-copyfrom-rev: 7
399 Node-copyfrom-path: subproject/trunk/d2
400
401
@@ -0,0 +1,240
1 SVN-fs-dump-format-version: 2
2
3 UUID: c731c652-65e9-4325-a17e-fed96a319f22
4
5 Revision-number: 0
6 Prop-content-length: 56
7 Content-length: 56
8
9 K 8
10 svn:date
11 V 27
12 2008-12-06T13:44:21.642421Z
13 PROPS-END
14
15 Revision-number: 1
16 Prop-content-length: 112
17 Content-length: 112
18
19 K 7
20 svn:log
21 V 10
22 init projA
23 K 10
24 svn:author
25 V 7
26 pmezard
27 K 8
28 svn:date
29 V 27
30 2008-12-06T13:44:21.759281Z
31 PROPS-END
32
33 Node-path: branches
34 Node-kind: dir
35 Node-action: add
36 Prop-content-length: 10
37 Content-length: 10
38
39 PROPS-END
40
41
42 Node-path: tags
43 Node-kind: dir
44 Node-action: add
45 Prop-content-length: 10
46 Content-length: 10
47
48 PROPS-END
49
50
51 Node-path: trunk
52 Node-kind: dir
53 Node-action: add
54 Prop-content-length: 10
55 Content-length: 10
56
57 PROPS-END
58
59
60 Revision-number: 2
61 Prop-content-length: 109
62 Content-length: 109
63
64 K 7
65 svn:log
66 V 8
67 createab
68 K 10
69 svn:author
70 V 7
71 pmezard
72 K 8
73 svn:date
74 V 27
75 2008-12-06T13:44:22.179257Z
76 PROPS-END
77
78 Node-path: trunk/a
79 Node-kind: file
80 Node-action: add
81 Prop-content-length: 10
82 Text-content-length: 2
83 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
84 Content-length: 12
85
86 PROPS-END
87 a
88
89
90 Node-path: trunk/b
91 Node-kind: file
92 Node-action: add
93 Prop-content-length: 10
94 Text-content-length: 2
95 Text-content-md5: 3b5d5c3712955042212316173ccf37be
96 Content-length: 12
97
98 PROPS-END
99 b
100
101
102 Revision-number: 3
103 Prop-content-length: 108
104 Content-length: 108
105
106 K 7
107 svn:log
108 V 7
109 removeb
110 K 10
111 svn:author
112 V 7
113 pmezard
114 K 8
115 svn:date
116 V 27
117 2008-12-06T13:44:23.176546Z
118 PROPS-END
119
120 Node-path: trunk/b
121 Node-action: delete
122
123
124 Revision-number: 4
125 Prop-content-length: 109
126 Content-length: 109
127
128 K 7
129 svn:log
130 V 8
131 changeaa
132 K 10
133 svn:author
134 V 7
135 pmezard
136 K 8
137 svn:date
138 V 27
139 2008-12-06T13:44:25.147151Z
140 PROPS-END
141
142 Node-path: trunk/a
143 Node-kind: file
144 Node-action: change
145 Text-content-length: 4
146 Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
147 Content-length: 4
148
149 a
150 a
151
152
153 Revision-number: 5
154 Prop-content-length: 119
155 Content-length: 119
156
157 K 7
158 svn:log
159 V 17
160 branch, changeaaa
161 K 10
162 svn:author
163 V 7
164 pmezard
165 K 8
166 svn:date
167 V 27
168 2008-12-06T13:44:28.158475Z
169 PROPS-END
170
171 Node-path: branches/branch1
172 Node-kind: dir
173 Node-action: add
174 Node-copyfrom-rev: 4
175 Node-copyfrom-path: trunk
176 Prop-content-length: 34
177 Content-length: 34
178
179 K 13
180 svn:mergeinfo
181 V 0
182
183 PROPS-END
184
185
186 Node-path: branches/branch1/a
187 Node-kind: file
188 Node-action: change
189 Text-content-length: 6
190 Text-content-md5: 7d4ebf8f298d22fc349a91725b00af1c
191 Content-length: 6
192
193 a
194 a
195 a
196
197
198 Revision-number: 6
199 Prop-content-length: 117
200 Content-length: 117
201
202 K 7
203 svn:log
204 V 15
205 addc,changeaaaa
206 K 10
207 svn:author
208 V 7
209 pmezard
210 K 8
211 svn:date
212 V 27
213 2008-12-06T13:44:29.180655Z
214 PROPS-END
215
216 Node-path: branches/branch1/a
217 Node-kind: file
218 Node-action: change
219 Text-content-length: 8
220 Text-content-md5: d12178e74d8774e34361e0a08d1fd2b7
221 Content-length: 8
222
223 a
224 a
225 a
226 a
227
228
229 Node-path: branches/branch1/c
230 Node-kind: file
231 Node-action: add
232 Prop-content-length: 10
233 Text-content-length: 2
234 Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
235 Content-length: 12
236
237 PROPS-END
238 c
239
240
@@ -0,0 +1,70
1 #!/bin/sh
2 #
3 # Use this script to generate branches.svndump
4 #
5
6 mkdir temp
7 cd temp
8
9 mkdir project-orig
10 cd project-orig
11 mkdir trunk
12 mkdir branches
13 mkdir tags
14 cd ..
15
16 svnadmin create svn-repo
17 svnurl=file://`pwd`/svn-repo
18 svn import project-orig $svnurl -m "init projA"
19
20 svn co $svnurl project
21 cd project
22 echo a > trunk/a
23 echo b > trunk/b
24 echo c > trunk/c
25 # Add a file within branches, used to confuse branch detection
26 echo d > branches/notinbranch
27 svn add trunk/a trunk/b trunk/c branches/notinbranch
28 svn ci -m hello
29
30 # Branch to old
31 svn copy trunk branches/old
32 svn rm branches/old/c
33 svn ci -m "branch trunk, remove c"
34 svn up
35
36 # Update trunk
37 echo a >> trunk/a
38 svn ci -m "change a"
39
40 # Update old branch
41 echo b >> branches/old/b
42 svn ci -m "change b"
43
44 # Create a cross-branch revision
45 svn move trunk/b branches/old/c
46 echo c >> branches/old/c
47 svn ci -m "move and update c"
48
49 # Update old branch again
50 echo b >> branches/old/b
51 svn ci -m "change b again"
52
53 # Move back and forth between branch of similar names
54 # This used to generate fake copy records
55 svn up
56 svn move branches/old branches/old2
57 svn ci -m "move to old2"
58 svn move branches/old2 branches/old
59 svn ci -m "move back to old"
60
61 # Update trunk again
62 echo a > trunk/a
63 svn ci -m "last change to a"
64
65 # Branch again from a converted revision
66 svn copy -r 1 $svnurl/trunk branches/old3
67 svn ci -m "branch trunk@1 into old3"
68 cd ..
69
70 svnadmin dump svn-repo > ../branches.svndump
@@ -0,0 +1,62
1 #!/bin/sh
2 #
3 # Use this script to generate move.svndump
4 #
5
6 mkdir temp
7 cd temp
8
9 mkdir project-orig
10 cd project-orig
11 mkdir trunk
12 echo a > trunk/a
13 mkdir trunk/d1
14 mkdir trunk/d2
15 echo b > trunk/d1/b
16 echo c > trunk/d1/c
17 echo d > trunk/d2/d
18 cd ..
19
20 svnadmin create svn-repo
21 svnurl=file://`pwd`/svn-repo
22 svn import project-orig $svnurl -m "init projA"
23
24 svn co $svnurl project
25 cd project
26 # Build a module renaming chain which used to confuse the converter.
27 # Update svn repository
28 echo a >> trunk/a
29 echo c >> trunk/d1/c
30 svn ci -m commitbeforemove
31 svn mv $svnurl/trunk $svnurl/subproject -m movedtrunk
32 svn up
33 mkdir subproject/trunk
34 svn add subproject/trunk
35 svn ci -m createtrunk
36 mkdir subproject/branches
37 svn add subproject/branches
38 svn ci -m createbranches
39 svn mv $svnurl/subproject/d1 $svnurl/subproject/trunk/d1 -m moved1
40 svn mv $svnurl/subproject/d2 $svnurl/subproject/trunk/d2 -m moved2
41 svn up
42 echo b >> subproject/trunk/d1/b
43
44 svn rm subproject/trunk/d2
45 svn ci -m "changeb and rm d2"
46 svn mv $svnurl/subproject/trunk/d1 $svnurl/subproject/branches/d1 -m moved1again
47
48 if svn help copy | grep 'SRC\[@REV\]' > /dev/null 2>&1; then
49 # SVN >= 1.5 replaced the -r REV syntax with @REV
50 # Copy a file from a past revision
51 svn copy $svnurl/subproject/trunk/d2/d@7 $svnurl/subproject/trunk -m copyfilefrompast
52 # Copy a directory from a past revision
53 svn copy $svnurl/subproject/trunk/d2@7 $svnurl/subproject/trunk -m copydirfrompast
54 else
55 # Copy a file from a past revision
56 svn copy -r 7 $svnurl/subproject/trunk/d2/d $svnurl/subproject/trunk -m copyfilefrompast
57 # Copy a directory from a past revision
58 svn copy -r 7 $svnurl/subproject/trunk/d2 $svnurl/subproject/trunk -m copydirfrompast
59 fi
60 cd ..
61
62 svnadmin dump svn-repo > ../move.svndump No newline at end of file
@@ -0,0 +1,45
1 #!/bin/sh
2 #
3 # Use this script to generate startrev.svndump
4 #
5
6 mkdir temp
7 cd temp
8
9 mkdir project-orig
10 cd project-orig
11 mkdir trunk
12 mkdir branches
13 mkdir tags
14 cd ..
15
16 svnadmin create svn-repo
17 svnurl=file://`pwd`/svn-repo
18 svn import project-orig $svnurl -m "init projA"
19
20 svn co $svnurl project
21 cd project
22 echo a > trunk/a
23 echo b > trunk/b
24 svn add trunk/a trunk/b
25 svn ci -m createab
26 svn rm trunk/b
27 svn ci -m removeb
28 svn up
29 echo a >> trunk/a
30 svn ci -m changeaa
31
32 # Branch
33 svn up
34 svn copy trunk branches/branch1
35 echo a >> branches/branch1/a
36 svn ci -m "branch, changeaaa"
37
38 echo a >> branches/branch1/a
39 echo c > branches/branch1/c
40 svn add branches/branch1/c
41 svn ci -m "addc,changeaaaa"
42 svn up
43 cd ..
44
45 svnadmin dump svn-repo > ../startrev.svndump No newline at end of file
@@ -0,0 +1,49
1 #!/bin/sh
2 #
3 # Use this script to generate tags.svndump
4 #
5
6 mkdir temp
7 cd temp
8
9 mkdir project-orig
10 cd project-orig
11 mkdir trunk
12 mkdir branches
13 mkdir tags
14 mkdir unrelated
15 cd ..
16
17 svnadmin create svn-repo
18 svnurl=file://`pwd`/svn-repo
19 svn import project-orig $svnurl -m "init projA"
20
21 svn co $svnurl project
22 cd project
23 echo a > trunk/a
24 svn add trunk/a
25 svn ci -m adda
26 echo a >> trunk/a
27 svn ci -m changea
28 echo a >> trunk/a
29 svn ci -m changea2
30 # Add an unrelated commit to test that tags are bound to the
31 # correct "from" revision and not a dummy one
32 echo a >> unrelated/dummy
33 svn add unrelated/dummy
34 svn ci -m unrelatedchange
35 # Tag current revision
36 svn up
37 svn copy trunk tags/trunk.v1
38 svn copy trunk tags/trunk.badtag
39 svn ci -m "tagging trunk.v1 trunk.badtag"
40 echo a >> trunk/a
41 svn ci -m changea3
42 # Fix the bad tag
43 # trunk.badtag should not show in converted tags
44 svn up
45 svn mv tags/trunk.badtag tags/trunk.goodtag
46 svn ci -m "fix trunk.badtag"
47 cd ..
48
49 svnadmin dump svn-repo > ../tags.svndump No newline at end of file
@@ -0,0 +1,295
1 SVN-fs-dump-format-version: 2
2
3 UUID: 65371b91-a2cf-4cb1-a047-08b28c3b4c40
4
5 Revision-number: 0
6 Prop-content-length: 56
7 Content-length: 56
8
9 K 8
10 svn:date
11 V 27
12 2008-12-06T13:50:23.869747Z
13 PROPS-END
14
15 Revision-number: 1
16 Prop-content-length: 112
17 Content-length: 112
18
19 K 7
20 svn:log
21 V 10
22 init projA
23 K 10
24 svn:author
25 V 7
26 pmezard
27 K 8
28 svn:date
29 V 27
30 2008-12-06T13:50:23.944361Z
31 PROPS-END
32
33 Node-path: branches
34 Node-kind: dir
35 Node-action: add
36 Prop-content-length: 10
37 Content-length: 10
38
39 PROPS-END
40
41
42 Node-path: tags
43 Node-kind: dir
44 Node-action: add
45 Prop-content-length: 10
46 Content-length: 10
47
48 PROPS-END
49
50
51 Node-path: trunk
52 Node-kind: dir
53 Node-action: add
54 Prop-content-length: 10
55 Content-length: 10
56
57 PROPS-END
58
59
60 Node-path: unrelated
61 Node-kind: dir
62 Node-action: add
63 Prop-content-length: 10
64 Content-length: 10
65
66 PROPS-END
67
68
69 Revision-number: 2
70 Prop-content-length: 105
71 Content-length: 105
72
73 K 7
74 svn:log
75 V 4
76 adda
77 K 10
78 svn:author
79 V 7
80 pmezard
81 K 8
82 svn:date
83 V 27
84 2008-12-06T13:50:25.174397Z
85 PROPS-END
86
87 Node-path: trunk/a
88 Node-kind: file
89 Node-action: add
90 Prop-content-length: 10
91 Text-content-length: 2
92 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
93 Content-length: 12
94
95 PROPS-END
96 a
97
98
99 Revision-number: 3
100 Prop-content-length: 108
101 Content-length: 108
102
103 K 7
104 svn:log
105 V 7
106 changea
107 K 10
108 svn:author
109 V 7
110 pmezard
111 K 8
112 svn:date
113 V 27
114 2008-12-06T13:50:26.148468Z
115 PROPS-END
116
117 Node-path: trunk/a
118 Node-kind: file
119 Node-action: change
120 Text-content-length: 4
121 Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
122 Content-length: 4
123
124 a
125 a
126
127
128 Revision-number: 4
129 Prop-content-length: 109
130 Content-length: 109
131
132 K 7
133 svn:log
134 V 8
135 changea2
136 K 10
137 svn:author
138 V 7
139 pmezard
140 K 8
141 svn:date
142 V 27
143 2008-12-06T13:50:27.147988Z
144 PROPS-END
145
146 Node-path: trunk/a
147 Node-kind: file
148 Node-action: change
149 Text-content-length: 6
150 Text-content-md5: 7d4ebf8f298d22fc349a91725b00af1c
151 Content-length: 6
152
153 a
154 a
155 a
156
157
158 Revision-number: 5
159 Prop-content-length: 117
160 Content-length: 117
161
162 K 7
163 svn:log
164 V 15
165 unrelatedchange
166 K 10
167 svn:author
168 V 7
169 pmezard
170 K 8
171 svn:date
172 V 27
173 2008-12-06T13:50:28.174989Z
174 PROPS-END
175
176 Node-path: unrelated/dummy
177 Node-kind: file
178 Node-action: add
179 Prop-content-length: 10
180 Text-content-length: 2
181 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
182 Content-length: 12
183
184 PROPS-END
185 a
186
187
188 Revision-number: 6
189 Prop-content-length: 131
190 Content-length: 131
191
192 K 7
193 svn:log
194 V 29
195 tagging trunk.v1 trunk.badtag
196 K 10
197 svn:author
198 V 7
199 pmezard
200 K 8
201 svn:date
202 V 27
203 2008-12-06T13:50:32.157783Z
204 PROPS-END
205
206 Node-path: tags/trunk.badtag
207 Node-kind: dir
208 Node-action: add
209 Node-copyfrom-rev: 5
210 Node-copyfrom-path: trunk
211 Prop-content-length: 34
212 Content-length: 34
213
214 K 13
215 svn:mergeinfo
216 V 0
217
218 PROPS-END
219
220
221 Node-path: tags/trunk.v1
222 Node-kind: dir
223 Node-action: add
224 Node-copyfrom-rev: 5
225 Node-copyfrom-path: trunk
226 Prop-content-length: 34
227 Content-length: 34
228
229 K 13
230 svn:mergeinfo
231 V 0
232
233 PROPS-END
234
235
236 Revision-number: 7
237 Prop-content-length: 109
238 Content-length: 109
239
240 K 7
241 svn:log
242 V 8
243 changea3
244 K 10
245 svn:author
246 V 7
247 pmezard
248 K 8
249 svn:date
250 V 27
251 2008-12-06T13:50:33.145803Z
252 PROPS-END
253
254 Node-path: trunk/a
255 Node-kind: file
256 Node-action: change
257 Text-content-length: 8
258 Text-content-md5: d12178e74d8774e34361e0a08d1fd2b7
259 Content-length: 8
260
261 a
262 a
263 a
264 a
265
266
267 Revision-number: 8
268 Prop-content-length: 118
269 Content-length: 118
270
271 K 7
272 svn:log
273 V 16
274 fix trunk.badtag
275 K 10
276 svn:author
277 V 7
278 pmezard
279 K 8
280 svn:date
281 V 27
282 2008-12-06T13:50:36.153842Z
283 PROPS-END
284
285 Node-path: tags/trunk.goodtag
286 Node-kind: dir
287 Node-action: add
288 Node-copyfrom-rev: 7
289 Node-copyfrom-path: tags/trunk.badtag
290
291
292 Node-path: tags/trunk.badtag
293 Node-action: delete
294
295
@@ -1,1184 +1,1167
1 # Subversion 1.4/1.5 Python API backend
1 # Subversion 1.4/1.5 Python API backend
2 #
2 #
3 # Copyright(C) 2007 Daniel Holth et al
3 # Copyright(C) 2007 Daniel Holth et al
4 #
4 #
5 # Configuration options:
5 # Configuration options:
6 #
6 #
7 # convert.svn.trunk
7 # convert.svn.trunk
8 # Relative path to the trunk (default: "trunk")
8 # Relative path to the trunk (default: "trunk")
9 # convert.svn.branches
9 # convert.svn.branches
10 # Relative path to tree of branches (default: "branches")
10 # Relative path to tree of branches (default: "branches")
11 # convert.svn.tags
11 # convert.svn.tags
12 # Relative path to tree of tags (default: "tags")
12 # Relative path to tree of tags (default: "tags")
13 #
13 #
14 # Set these in a hgrc, or on the command line as follows:
14 # Set these in a hgrc, or on the command line as follows:
15 #
15 #
16 # hg convert --config convert.svn.trunk=wackoname [...]
16 # hg convert --config convert.svn.trunk=wackoname [...]
17
17
18 import locale
18 import locale
19 import os
19 import os
20 import re
20 import re
21 import sys
21 import sys
22 import cPickle as pickle
22 import cPickle as pickle
23 import tempfile
23 import tempfile
24 import urllib
24 import urllib
25
25
26 from mercurial import strutil, util
26 from mercurial import strutil, util
27 from mercurial.i18n import _
27 from mercurial.i18n import _
28
28
29 # Subversion stuff. Works best with very recent Python SVN bindings
29 # Subversion stuff. Works best with very recent Python SVN bindings
30 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
30 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
31 # these bindings.
31 # these bindings.
32
32
33 from cStringIO import StringIO
33 from cStringIO import StringIO
34
34
35 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
35 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
36 from common import commandline, converter_source, converter_sink, mapfile
36 from common import commandline, converter_source, converter_sink, mapfile
37
37
38 try:
38 try:
39 from svn.core import SubversionException, Pool
39 from svn.core import SubversionException, Pool
40 import svn
40 import svn
41 import svn.client
41 import svn.client
42 import svn.core
42 import svn.core
43 import svn.ra
43 import svn.ra
44 import svn.delta
44 import svn.delta
45 import transport
45 import transport
46 except ImportError:
46 except ImportError:
47 pass
47 pass
48
48
49 class SvnPathNotFound(Exception):
49 class SvnPathNotFound(Exception):
50 pass
50 pass
51
51
52 def geturl(path):
52 def geturl(path):
53 try:
53 try:
54 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
54 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
55 except SubversionException:
55 except SubversionException:
56 pass
56 pass
57 if os.path.isdir(path):
57 if os.path.isdir(path):
58 path = os.path.normpath(os.path.abspath(path))
58 path = os.path.normpath(os.path.abspath(path))
59 if os.name == 'nt':
59 if os.name == 'nt':
60 path = '/' + util.normpath(path)
60 path = '/' + util.normpath(path)
61 return 'file://%s' % urllib.quote(path)
61 return 'file://%s' % urllib.quote(path)
62 return path
62 return path
63
63
64 def optrev(number):
64 def optrev(number):
65 optrev = svn.core.svn_opt_revision_t()
65 optrev = svn.core.svn_opt_revision_t()
66 optrev.kind = svn.core.svn_opt_revision_number
66 optrev.kind = svn.core.svn_opt_revision_number
67 optrev.value.number = number
67 optrev.value.number = number
68 return optrev
68 return optrev
69
69
70 class changedpath(object):
70 class changedpath(object):
71 def __init__(self, p):
71 def __init__(self, p):
72 self.copyfrom_path = p.copyfrom_path
72 self.copyfrom_path = p.copyfrom_path
73 self.copyfrom_rev = p.copyfrom_rev
73 self.copyfrom_rev = p.copyfrom_rev
74 self.action = p.action
74 self.action = p.action
75
75
76 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
76 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
77 strict_node_history=False):
77 strict_node_history=False):
78 protocol = -1
78 protocol = -1
79 def receiver(orig_paths, revnum, author, date, message, pool):
79 def receiver(orig_paths, revnum, author, date, message, pool):
80 if orig_paths is not None:
80 if orig_paths is not None:
81 for k, v in orig_paths.iteritems():
81 for k, v in orig_paths.iteritems():
82 orig_paths[k] = changedpath(v)
82 orig_paths[k] = changedpath(v)
83 pickle.dump((orig_paths, revnum, author, date, message),
83 pickle.dump((orig_paths, revnum, author, date, message),
84 fp, protocol)
84 fp, protocol)
85
85
86 try:
86 try:
87 # Use an ra of our own so that our parent can consume
87 # Use an ra of our own so that our parent can consume
88 # our results without confusing the server.
88 # our results without confusing the server.
89 t = transport.SvnRaTransport(url=url)
89 t = transport.SvnRaTransport(url=url)
90 svn.ra.get_log(t.ra, paths, start, end, limit,
90 svn.ra.get_log(t.ra, paths, start, end, limit,
91 discover_changed_paths,
91 discover_changed_paths,
92 strict_node_history,
92 strict_node_history,
93 receiver)
93 receiver)
94 except SubversionException, (inst, num):
94 except SubversionException, (inst, num):
95 pickle.dump(num, fp, protocol)
95 pickle.dump(num, fp, protocol)
96 except IOError:
96 except IOError:
97 # Caller may interrupt the iteration
97 # Caller may interrupt the iteration
98 pickle.dump(None, fp, protocol)
98 pickle.dump(None, fp, protocol)
99 else:
99 else:
100 pickle.dump(None, fp, protocol)
100 pickle.dump(None, fp, protocol)
101 fp.close()
101 fp.close()
102 # With large history, cleanup process goes crazy and suddenly
102 # With large history, cleanup process goes crazy and suddenly
103 # consumes *huge* amount of memory. The output file being closed,
103 # consumes *huge* amount of memory. The output file being closed,
104 # there is no need for clean termination.
104 # there is no need for clean termination.
105 os._exit(0)
105 os._exit(0)
106
106
107 def debugsvnlog(ui, **opts):
107 def debugsvnlog(ui, **opts):
108 """Fetch SVN log in a subprocess and channel them back to parent to
108 """Fetch SVN log in a subprocess and channel them back to parent to
109 avoid memory collection issues.
109 avoid memory collection issues.
110 """
110 """
111 util.set_binary(sys.stdin)
111 util.set_binary(sys.stdin)
112 util.set_binary(sys.stdout)
112 util.set_binary(sys.stdout)
113 args = decodeargs(sys.stdin.read())
113 args = decodeargs(sys.stdin.read())
114 get_log_child(sys.stdout, *args)
114 get_log_child(sys.stdout, *args)
115
115
116 class logstream:
116 class logstream:
117 """Interruptible revision log iterator."""
117 """Interruptible revision log iterator."""
118 def __init__(self, stdout):
118 def __init__(self, stdout):
119 self._stdout = stdout
119 self._stdout = stdout
120
120
121 def __iter__(self):
121 def __iter__(self):
122 while True:
122 while True:
123 entry = pickle.load(self._stdout)
123 entry = pickle.load(self._stdout)
124 try:
124 try:
125 orig_paths, revnum, author, date, message = entry
125 orig_paths, revnum, author, date, message = entry
126 except:
126 except:
127 if entry is None:
127 if entry is None:
128 break
128 break
129 raise SubversionException("child raised exception", entry)
129 raise SubversionException("child raised exception", entry)
130 yield entry
130 yield entry
131
131
132 def close(self):
132 def close(self):
133 if self._stdout:
133 if self._stdout:
134 self._stdout.close()
134 self._stdout.close()
135 self._stdout = None
135 self._stdout = None
136
136
137 # SVN conversion code stolen from bzr-svn and tailor
137 # SVN conversion code stolen from bzr-svn and tailor
138 #
138 #
139 # Subversion looks like a versioned filesystem, branches structures
139 # Subversion looks like a versioned filesystem, branches structures
140 # are defined by conventions and not enforced by the tool. First,
140 # are defined by conventions and not enforced by the tool. First,
141 # we define the potential branches (modules) as "trunk" and "branches"
141 # we define the potential branches (modules) as "trunk" and "branches"
142 # children directories. Revisions are then identified by their
142 # children directories. Revisions are then identified by their
143 # module and revision number (and a repository identifier).
143 # module and revision number (and a repository identifier).
144 #
144 #
145 # The revision graph is really a tree (or a forest). By default, a
145 # The revision graph is really a tree (or a forest). By default, a
146 # revision parent is the previous revision in the same module. If the
146 # revision parent is the previous revision in the same module. If the
147 # module directory is copied/moved from another module then the
147 # module directory is copied/moved from another module then the
148 # revision is the module root and its parent the source revision in
148 # revision is the module root and its parent the source revision in
149 # the parent module. A revision has at most one parent.
149 # the parent module. A revision has at most one parent.
150 #
150 #
151 class svn_source(converter_source):
151 class svn_source(converter_source):
152 def __init__(self, ui, url, rev=None):
152 def __init__(self, ui, url, rev=None):
153 super(svn_source, self).__init__(ui, url, rev=rev)
153 super(svn_source, self).__init__(ui, url, rev=rev)
154
154
155 try:
155 try:
156 SubversionException
156 SubversionException
157 except NameError:
157 except NameError:
158 raise MissingTool(_('Subversion python bindings could not be loaded'))
158 raise MissingTool(_('Subversion python bindings could not be loaded'))
159
159
160 try:
160 try:
161 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
161 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
162 if version < (1, 4):
162 if version < (1, 4):
163 raise MissingTool(_('Subversion python bindings %d.%d found, '
163 raise MissingTool(_('Subversion python bindings %d.%d found, '
164 '1.4 or later required') % version)
164 '1.4 or later required') % version)
165 except AttributeError:
165 except AttributeError:
166 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
166 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
167 'or later required'))
167 'or later required'))
168
168
169 self.encoding = locale.getpreferredencoding()
169 self.encoding = locale.getpreferredencoding()
170 self.lastrevs = {}
170 self.lastrevs = {}
171
171
172 latest = None
172 latest = None
173 try:
173 try:
174 # Support file://path@rev syntax. Useful e.g. to convert
174 # Support file://path@rev syntax. Useful e.g. to convert
175 # deleted branches.
175 # deleted branches.
176 at = url.rfind('@')
176 at = url.rfind('@')
177 if at >= 0:
177 if at >= 0:
178 latest = int(url[at+1:])
178 latest = int(url[at+1:])
179 url = url[:at]
179 url = url[:at]
180 except ValueError, e:
180 except ValueError, e:
181 pass
181 pass
182 self.url = geturl(url)
182 self.url = geturl(url)
183 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
183 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
184 try:
184 try:
185 self.transport = transport.SvnRaTransport(url=self.url)
185 self.transport = transport.SvnRaTransport(url=self.url)
186 self.ra = self.transport.ra
186 self.ra = self.transport.ra
187 self.ctx = self.transport.client
187 self.ctx = self.transport.client
188 self.baseurl = svn.ra.get_repos_root(self.ra)
188 self.baseurl = svn.ra.get_repos_root(self.ra)
189 # Module is either empty or a repository path starting with
189 # Module is either empty or a repository path starting with
190 # a slash and not ending with a slash.
190 # a slash and not ending with a slash.
191 self.module = urllib.unquote(self.url[len(self.baseurl):])
191 self.module = urllib.unquote(self.url[len(self.baseurl):])
192 self.prevmodule = None
192 self.prevmodule = None
193 self.rootmodule = self.module
193 self.rootmodule = self.module
194 self.commits = {}
194 self.commits = {}
195 self.paths = {}
195 self.paths = {}
196 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
196 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
197 except SubversionException, e:
197 except SubversionException, e:
198 ui.print_exc()
198 ui.print_exc()
199 raise NoRepo("%s does not look like a Subversion repo" % self.url)
199 raise NoRepo("%s does not look like a Subversion repo" % self.url)
200
200
201 if rev:
201 if rev:
202 try:
202 try:
203 latest = int(rev)
203 latest = int(rev)
204 except ValueError:
204 except ValueError:
205 raise util.Abort(_('svn: revision %s is not an integer') % rev)
205 raise util.Abort(_('svn: revision %s is not an integer') % rev)
206
206
207 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
207 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
208 try:
208 try:
209 self.startrev = int(self.startrev)
209 self.startrev = int(self.startrev)
210 if self.startrev < 0:
210 if self.startrev < 0:
211 self.startrev = 0
211 self.startrev = 0
212 except ValueError:
212 except ValueError:
213 raise util.Abort(_('svn: start revision %s is not an integer')
213 raise util.Abort(_('svn: start revision %s is not an integer')
214 % self.startrev)
214 % self.startrev)
215
215
216 try:
216 try:
217 self.get_blacklist()
217 self.get_blacklist()
218 except IOError, e:
218 except IOError, e:
219 pass
219 pass
220
220
221 self.head = self.latest(self.module, latest)
221 self.head = self.latest(self.module, latest)
222 if not self.head:
222 if not self.head:
223 raise util.Abort(_('no revision found in module %s') %
223 raise util.Abort(_('no revision found in module %s') %
224 self.module.encode(self.encoding))
224 self.module.encode(self.encoding))
225 self.last_changed = self.revnum(self.head)
225 self.last_changed = self.revnum(self.head)
226
226
227 self._changescache = None
227 self._changescache = None
228
228
229 if os.path.exists(os.path.join(url, '.svn/entries')):
229 if os.path.exists(os.path.join(url, '.svn/entries')):
230 self.wc = url
230 self.wc = url
231 else:
231 else:
232 self.wc = None
232 self.wc = None
233 self.convertfp = None
233 self.convertfp = None
234
234
235 def setrevmap(self, revmap):
235 def setrevmap(self, revmap):
236 lastrevs = {}
236 lastrevs = {}
237 for revid in revmap.iterkeys():
237 for revid in revmap.iterkeys():
238 uuid, module, revnum = self.revsplit(revid)
238 uuid, module, revnum = self.revsplit(revid)
239 lastrevnum = lastrevs.setdefault(module, revnum)
239 lastrevnum = lastrevs.setdefault(module, revnum)
240 if revnum > lastrevnum:
240 if revnum > lastrevnum:
241 lastrevs[module] = revnum
241 lastrevs[module] = revnum
242 self.lastrevs = lastrevs
242 self.lastrevs = lastrevs
243
243
244 def exists(self, path, optrev):
244 def exists(self, path, optrev):
245 try:
245 try:
246 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
246 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
247 optrev, False, self.ctx)
247 optrev, False, self.ctx)
248 return True
248 return True
249 except SubversionException, err:
249 except SubversionException, err:
250 return False
250 return False
251
251
252 def getheads(self):
252 def getheads(self):
253
253
254 def isdir(path, revnum):
254 def isdir(path, revnum):
255 kind = self._checkpath(path, revnum)
255 kind = self._checkpath(path, revnum)
256 return kind == svn.core.svn_node_dir
256 return kind == svn.core.svn_node_dir
257
257
258 def getcfgpath(name, rev):
258 def getcfgpath(name, rev):
259 cfgpath = self.ui.config('convert', 'svn.' + name)
259 cfgpath = self.ui.config('convert', 'svn.' + name)
260 if cfgpath is not None and cfgpath.strip() == '':
260 if cfgpath is not None and cfgpath.strip() == '':
261 return None
261 return None
262 path = (cfgpath or name).strip('/')
262 path = (cfgpath or name).strip('/')
263 if not self.exists(path, rev):
263 if not self.exists(path, rev):
264 if cfgpath:
264 if cfgpath:
265 raise util.Abort(_('expected %s to be at %r, but not found')
265 raise util.Abort(_('expected %s to be at %r, but not found')
266 % (name, path))
266 % (name, path))
267 return None
267 return None
268 self.ui.note(_('found %s at %r\n') % (name, path))
268 self.ui.note(_('found %s at %r\n') % (name, path))
269 return path
269 return path
270
270
271 rev = optrev(self.last_changed)
271 rev = optrev(self.last_changed)
272 oldmodule = ''
272 oldmodule = ''
273 trunk = getcfgpath('trunk', rev)
273 trunk = getcfgpath('trunk', rev)
274 self.tags = getcfgpath('tags', rev)
274 self.tags = getcfgpath('tags', rev)
275 branches = getcfgpath('branches', rev)
275 branches = getcfgpath('branches', rev)
276
276
277 # If the project has a trunk or branches, we will extract heads
277 # If the project has a trunk or branches, we will extract heads
278 # from them. We keep the project root otherwise.
278 # from them. We keep the project root otherwise.
279 if trunk:
279 if trunk:
280 oldmodule = self.module or ''
280 oldmodule = self.module or ''
281 self.module += '/' + trunk
281 self.module += '/' + trunk
282 self.head = self.latest(self.module, self.last_changed)
282 self.head = self.latest(self.module, self.last_changed)
283 if not self.head:
283 if not self.head:
284 raise util.Abort(_('no revision found in module %s') %
284 raise util.Abort(_('no revision found in module %s') %
285 self.module.encode(self.encoding))
285 self.module.encode(self.encoding))
286
286
287 # First head in the list is the module's head
287 # First head in the list is the module's head
288 self.heads = [self.head]
288 self.heads = [self.head]
289 if self.tags is not None:
289 if self.tags is not None:
290 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
290 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
291
291
292 # Check if branches bring a few more heads to the list
292 # Check if branches bring a few more heads to the list
293 if branches:
293 if branches:
294 rpath = self.url.strip('/')
294 rpath = self.url.strip('/')
295 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
295 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
296 rev, False, self.ctx)
296 rev, False, self.ctx)
297 for branch in branchnames.keys():
297 for branch in branchnames.keys():
298 module = '%s/%s/%s' % (oldmodule, branches, branch)
298 module = '%s/%s/%s' % (oldmodule, branches, branch)
299 if not isdir(module, self.last_changed):
299 if not isdir(module, self.last_changed):
300 continue
300 continue
301 brevid = self.latest(module, self.last_changed)
301 brevid = self.latest(module, self.last_changed)
302 if not brevid:
302 if not brevid:
303 self.ui.note(_('ignoring empty branch %s\n') %
303 self.ui.note(_('ignoring empty branch %s\n') %
304 branch.encode(self.encoding))
304 branch.encode(self.encoding))
305 continue
305 continue
306 self.ui.note(_('found branch %s at %d\n') %
306 self.ui.note(_('found branch %s at %d\n') %
307 (branch, self.revnum(brevid)))
307 (branch, self.revnum(brevid)))
308 self.heads.append(brevid)
308 self.heads.append(brevid)
309
309
310 if self.startrev and self.heads:
310 if self.startrev and self.heads:
311 if len(self.heads) > 1:
311 if len(self.heads) > 1:
312 raise util.Abort(_('svn: start revision is not supported with '
312 raise util.Abort(_('svn: start revision is not supported with '
313 'with more than one branch'))
313 'with more than one branch'))
314 revnum = self.revnum(self.heads[0])
314 revnum = self.revnum(self.heads[0])
315 if revnum < self.startrev:
315 if revnum < self.startrev:
316 raise util.Abort(_('svn: no revision found after start revision %d')
316 raise util.Abort(_('svn: no revision found after start revision %d')
317 % self.startrev)
317 % self.startrev)
318
318
319 return self.heads
319 return self.heads
320
320
321 def getfile(self, file, rev):
321 def getfile(self, file, rev):
322 data, mode = self._getfile(file, rev)
322 data, mode = self._getfile(file, rev)
323 self.modecache[(file, rev)] = mode
323 self.modecache[(file, rev)] = mode
324 return data
324 return data
325
325
326 def getmode(self, file, rev):
326 def getmode(self, file, rev):
327 return self.modecache[(file, rev)]
327 return self.modecache[(file, rev)]
328
328
329 def getchanges(self, rev):
329 def getchanges(self, rev):
330 if self._changescache and self._changescache[0] == rev:
330 if self._changescache and self._changescache[0] == rev:
331 return self._changescache[1]
331 return self._changescache[1]
332 self._changescache = None
332 self._changescache = None
333 self.modecache = {}
333 self.modecache = {}
334 (paths, parents) = self.paths[rev]
334 (paths, parents) = self.paths[rev]
335 if parents:
335 if parents:
336 files, copies = self.expandpaths(rev, paths, parents)
336 files, copies = self.expandpaths(rev, paths, parents)
337 else:
337 else:
338 # Perform a full checkout on roots
338 # Perform a full checkout on roots
339 uuid, module, revnum = self.revsplit(rev)
339 uuid, module, revnum = self.revsplit(rev)
340 entries = svn.client.ls(self.baseurl + urllib.quote(module),
340 entries = svn.client.ls(self.baseurl + urllib.quote(module),
341 optrev(revnum), True, self.ctx)
341 optrev(revnum), True, self.ctx)
342 files = [n for n,e in entries.iteritems()
342 files = [n for n,e in entries.iteritems()
343 if e.kind == svn.core.svn_node_file]
343 if e.kind == svn.core.svn_node_file]
344 copies = {}
344 copies = {}
345
345
346 files.sort()
346 files.sort()
347 files = zip(files, [rev] * len(files))
347 files = zip(files, [rev] * len(files))
348
348
349 # caller caches the result, so free it here to release memory
349 # caller caches the result, so free it here to release memory
350 del self.paths[rev]
350 del self.paths[rev]
351 return (files, copies)
351 return (files, copies)
352
352
353 def getchangedfiles(self, rev, i):
353 def getchangedfiles(self, rev, i):
354 changes = self.getchanges(rev)
354 changes = self.getchanges(rev)
355 self._changescache = (rev, changes)
355 self._changescache = (rev, changes)
356 return [f[0] for f in changes[0]]
356 return [f[0] for f in changes[0]]
357
357
358 def getcommit(self, rev):
358 def getcommit(self, rev):
359 if rev not in self.commits:
359 if rev not in self.commits:
360 uuid, module, revnum = self.revsplit(rev)
360 uuid, module, revnum = self.revsplit(rev)
361 self.module = module
361 self.module = module
362 self.reparent(module)
362 self.reparent(module)
363 # We assume that:
363 # We assume that:
364 # - requests for revisions after "stop" come from the
364 # - requests for revisions after "stop" come from the
365 # revision graph backward traversal. Cache all of them
365 # revision graph backward traversal. Cache all of them
366 # down to stop, they will be used eventually.
366 # down to stop, they will be used eventually.
367 # - requests for revisions before "stop" come to get
367 # - requests for revisions before "stop" come to get
368 # isolated branches parents. Just fetch what is needed.
368 # isolated branches parents. Just fetch what is needed.
369 stop = self.lastrevs.get(module, 0)
369 stop = self.lastrevs.get(module, 0)
370 if revnum < stop:
370 if revnum < stop:
371 stop = revnum + 1
371 stop = revnum + 1
372 self._fetch_revisions(revnum, stop)
372 self._fetch_revisions(revnum, stop)
373 commit = self.commits[rev]
373 commit = self.commits[rev]
374 # caller caches the result, so free it here to release memory
374 # caller caches the result, so free it here to release memory
375 del self.commits[rev]
375 del self.commits[rev]
376 return commit
376 return commit
377
377
378 def gettags(self):
378 def gettags(self):
379 tags = {}
379 tags = {}
380 if self.tags is None:
380 if self.tags is None:
381 return tags
381 return tags
382
382
383 # svn tags are just a convention, project branches left in a
383 # svn tags are just a convention, project branches left in a
384 # 'tags' directory. There is no other relationship than
384 # 'tags' directory. There is no other relationship than
385 # ancestry, which is expensive to discover and makes them hard
385 # ancestry, which is expensive to discover and makes them hard
386 # to update incrementally. Worse, past revisions may be
386 # to update incrementally. Worse, past revisions may be
387 # referenced by tags far away in the future, requiring a deep
387 # referenced by tags far away in the future, requiring a deep
388 # history traversal on every calculation. Current code
388 # history traversal on every calculation. Current code
389 # performs a single backward traversal, tracking moves within
389 # performs a single backward traversal, tracking moves within
390 # the tags directory (tag renaming) and recording a new tag
390 # the tags directory (tag renaming) and recording a new tag
391 # everytime a project is copied from outside the tags
391 # everytime a project is copied from outside the tags
392 # directory. It also lists deleted tags, this behaviour may
392 # directory. It also lists deleted tags, this behaviour may
393 # change in the future.
393 # change in the future.
394 pendings = []
394 pendings = []
395 tagspath = self.tags
395 tagspath = self.tags
396 start = svn.ra.get_latest_revnum(self.ra)
396 start = svn.ra.get_latest_revnum(self.ra)
397 try:
397 try:
398 for entry in self._getlog([self.tags], start, self.startrev):
398 for entry in self._getlog([self.tags], start, self.startrev):
399 origpaths, revnum, author, date, message = entry
399 origpaths, revnum, author, date, message = entry
400 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
400 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
401 in origpaths.iteritems() if e.copyfrom_path]
401 in origpaths.iteritems() if e.copyfrom_path]
402 copies.sort()
402 copies.sort()
403 # Apply moves/copies from more specific to general
403 # Apply moves/copies from more specific to general
404 copies.reverse()
404 copies.reverse()
405
405
406 srctagspath = tagspath
406 srctagspath = tagspath
407 if copies and copies[-1][2] == tagspath:
407 if copies and copies[-1][2] == tagspath:
408 # Track tags directory moves
408 # Track tags directory moves
409 srctagspath = copies.pop()[0]
409 srctagspath = copies.pop()[0]
410
410
411 for source, sourcerev, dest in copies:
411 for source, sourcerev, dest in copies:
412 if not dest.startswith(tagspath + '/'):
412 if not dest.startswith(tagspath + '/'):
413 continue
413 continue
414 for tag in pendings:
414 for tag in pendings:
415 if tag[0].startswith(dest):
415 if tag[0].startswith(dest):
416 tagpath = source + tag[0][len(dest):]
416 tagpath = source + tag[0][len(dest):]
417 tag[:2] = [tagpath, sourcerev]
417 tag[:2] = [tagpath, sourcerev]
418 break
418 break
419 else:
419 else:
420 pendings.append([source, sourcerev, dest.split('/')[-1]])
420 pendings.append([source, sourcerev, dest.split('/')[-1]])
421
421
422 # Tell tag renamings from tag creations
422 # Tell tag renamings from tag creations
423 remainings = []
423 remainings = []
424 for source, sourcerev, tagname in pendings:
424 for source, sourcerev, tagname in pendings:
425 if source.startswith(srctagspath):
425 if source.startswith(srctagspath):
426 remainings.append([source, sourcerev, tagname])
426 remainings.append([source, sourcerev, tagname])
427 continue
427 continue
428 # From revision may be fake, get one with changes
428 # From revision may be fake, get one with changes
429 try:
429 try:
430 tagid = self.latest(source, sourcerev)
430 tagid = self.latest(source, sourcerev)
431 if tagid:
431 if tagid:
432 tags[tagname] = tagid
432 tags[tagname] = tagid
433 except SvnPathNotFound:
433 except SvnPathNotFound:
434 # It happens when we are following directories we assumed
434 # It happens when we are following directories we assumed
435 # were copied with their parents but were really created
435 # were copied with their parents but were really created
436 # in the tag directory.
436 # in the tag directory.
437 pass
437 pass
438 pendings = remainings
438 pendings = remainings
439 tagspath = srctagspath
439 tagspath = srctagspath
440
440
441 except SubversionException, (inst, num):
441 except SubversionException, (inst, num):
442 self.ui.note(_('no tags found at revision %d\n') % start)
442 self.ui.note(_('no tags found at revision %d\n') % start)
443 return tags
443 return tags
444
444
445 def converted(self, rev, destrev):
445 def converted(self, rev, destrev):
446 if not self.wc:
446 if not self.wc:
447 return
447 return
448 if self.convertfp is None:
448 if self.convertfp is None:
449 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
449 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
450 'a')
450 'a')
451 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
451 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
452 self.convertfp.flush()
452 self.convertfp.flush()
453
453
454 # -- helper functions --
454 # -- helper functions --
455
455
456 def revid(self, revnum, module=None):
456 def revid(self, revnum, module=None):
457 if not module:
457 if not module:
458 module = self.module
458 module = self.module
459 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
459 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
460 revnum)
460 revnum)
461
461
462 def revnum(self, rev):
462 def revnum(self, rev):
463 return int(rev.split('@')[-1])
463 return int(rev.split('@')[-1])
464
464
465 def revsplit(self, rev):
465 def revsplit(self, rev):
466 url, revnum = rev.encode(self.encoding).split('@', 1)
466 url, revnum = rev.encode(self.encoding).split('@', 1)
467 revnum = int(revnum)
467 revnum = int(revnum)
468 parts = url.split('/', 1)
468 parts = url.split('/', 1)
469 uuid = parts.pop(0)[4:]
469 uuid = parts.pop(0)[4:]
470 mod = ''
470 mod = ''
471 if parts:
471 if parts:
472 mod = '/' + parts[0]
472 mod = '/' + parts[0]
473 return uuid, mod, revnum
473 return uuid, mod, revnum
474
474
475 def latest(self, path, stop=0):
475 def latest(self, path, stop=0):
476 """Find the latest revid affecting path, up to stop. It may return
476 """Find the latest revid affecting path, up to stop. It may return
477 a revision in a different module, since a branch may be moved without
477 a revision in a different module, since a branch may be moved without
478 a change being reported. Return None if computed module does not
478 a change being reported. Return None if computed module does not
479 belong to rootmodule subtree.
479 belong to rootmodule subtree.
480 """
480 """
481 if not path.startswith(self.rootmodule):
481 if not path.startswith(self.rootmodule):
482 # Requests on foreign branches may be forbidden at server level
482 # Requests on foreign branches may be forbidden at server level
483 self.ui.debug(_('ignoring foreign branch %r\n') % path)
483 self.ui.debug(_('ignoring foreign branch %r\n') % path)
484 return None
484 return None
485
485
486 if not stop:
486 if not stop:
487 stop = svn.ra.get_latest_revnum(self.ra)
487 stop = svn.ra.get_latest_revnum(self.ra)
488 try:
488 try:
489 prevmodule = self.reparent('')
489 prevmodule = self.reparent('')
490 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
490 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
491 self.reparent(prevmodule)
491 self.reparent(prevmodule)
492 except SubversionException:
492 except SubversionException:
493 dirent = None
493 dirent = None
494 if not dirent:
494 if not dirent:
495 raise SvnPathNotFound(_('%s not found up to revision %d') % (path, stop))
495 raise SvnPathNotFound(_('%s not found up to revision %d') % (path, stop))
496
496
497 # stat() gives us the previous revision on this line of development, but
497 # stat() gives us the previous revision on this line of development, but
498 # it might be in *another module*. Fetch the log and detect renames down
498 # it might be in *another module*. Fetch the log and detect renames down
499 # to the latest revision.
499 # to the latest revision.
500 stream = self._getlog([path], stop, dirent.created_rev)
500 stream = self._getlog([path], stop, dirent.created_rev)
501 try:
501 try:
502 for entry in stream:
502 for entry in stream:
503 paths, revnum, author, date, message = entry
503 paths, revnum, author, date, message = entry
504 if revnum <= dirent.created_rev:
504 if revnum <= dirent.created_rev:
505 break
505 break
506
506
507 for p in paths:
507 for p in paths:
508 if not path.startswith(p) or not paths[p].copyfrom_path:
508 if not path.startswith(p) or not paths[p].copyfrom_path:
509 continue
509 continue
510 newpath = paths[p].copyfrom_path + path[len(p):]
510 newpath = paths[p].copyfrom_path + path[len(p):]
511 self.ui.debug(_("branch renamed from %s to %s at %d\n") %
511 self.ui.debug(_("branch renamed from %s to %s at %d\n") %
512 (path, newpath, revnum))
512 (path, newpath, revnum))
513 path = newpath
513 path = newpath
514 break
514 break
515 finally:
515 finally:
516 stream.close()
516 stream.close()
517
517
518 if not path.startswith(self.rootmodule):
518 if not path.startswith(self.rootmodule):
519 self.ui.debug(_('ignoring foreign branch %r\n') % path)
519 self.ui.debug(_('ignoring foreign branch %r\n') % path)
520 return None
520 return None
521 return self.revid(dirent.created_rev, path)
521 return self.revid(dirent.created_rev, path)
522
522
523 def get_blacklist(self):
523 def get_blacklist(self):
524 """Avoid certain revision numbers.
524 """Avoid certain revision numbers.
525 It is not uncommon for two nearby revisions to cancel each other
525 It is not uncommon for two nearby revisions to cancel each other
526 out, e.g. 'I copied trunk into a subdirectory of itself instead
526 out, e.g. 'I copied trunk into a subdirectory of itself instead
527 of making a branch'. The converted repository is significantly
527 of making a branch'. The converted repository is significantly
528 smaller if we ignore such revisions."""
528 smaller if we ignore such revisions."""
529 self.blacklist = util.set()
529 self.blacklist = util.set()
530 blacklist = self.blacklist
530 blacklist = self.blacklist
531 for line in file("blacklist.txt", "r"):
531 for line in file("blacklist.txt", "r"):
532 if not line.startswith("#"):
532 if not line.startswith("#"):
533 try:
533 try:
534 svn_rev = int(line.strip())
534 svn_rev = int(line.strip())
535 blacklist.add(svn_rev)
535 blacklist.add(svn_rev)
536 except ValueError, e:
536 except ValueError, e:
537 pass # not an integer or a comment
537 pass # not an integer or a comment
538
538
539 def is_blacklisted(self, svn_rev):
539 def is_blacklisted(self, svn_rev):
540 return svn_rev in self.blacklist
540 return svn_rev in self.blacklist
541
541
542 def reparent(self, module):
542 def reparent(self, module):
543 """Reparent the svn transport and return the previous parent."""
543 """Reparent the svn transport and return the previous parent."""
544 if self.prevmodule == module:
544 if self.prevmodule == module:
545 return module
545 return module
546 svnurl = self.baseurl + urllib.quote(module)
546 svnurl = self.baseurl + urllib.quote(module)
547 prevmodule = self.prevmodule
547 prevmodule = self.prevmodule
548 if prevmodule is None:
548 if prevmodule is None:
549 prevmodule = ''
549 prevmodule = ''
550 self.ui.debug(_("reparent to %s\n") % svnurl)
550 self.ui.debug(_("reparent to %s\n") % svnurl)
551 svn.ra.reparent(self.ra, svnurl)
551 svn.ra.reparent(self.ra, svnurl)
552 self.prevmodule = module
552 self.prevmodule = module
553 return prevmodule
553 return prevmodule
554
554
555 def expandpaths(self, rev, paths, parents):
555 def expandpaths(self, rev, paths, parents):
556 entries = []
556 entries = []
557 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
557 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
558 copies = {}
558 copies = {}
559
559
560 new_module, revnum = self.revsplit(rev)[1:]
560 new_module, revnum = self.revsplit(rev)[1:]
561 if new_module != self.module:
561 if new_module != self.module:
562 self.module = new_module
562 self.module = new_module
563 self.reparent(self.module)
563 self.reparent(self.module)
564
564
565 for path, ent in paths:
565 for path, ent in paths:
566 entrypath = self.getrelpath(path)
566 entrypath = self.getrelpath(path)
567 entry = entrypath.decode(self.encoding)
567 entry = entrypath.decode(self.encoding)
568
568
569 kind = self._checkpath(entrypath, revnum)
569 kind = self._checkpath(entrypath, revnum)
570 if kind == svn.core.svn_node_file:
570 if kind == svn.core.svn_node_file:
571 entries.append(self.recode(entry))
571 entries.append(self.recode(entry))
572 if not ent.copyfrom_path or not parents:
572 if not ent.copyfrom_path or not parents:
573 continue
573 continue
574 # Copy sources not in parent revisions cannot be represented,
574 # Copy sources not in parent revisions cannot be represented,
575 # ignore their origin for now
575 # ignore their origin for now
576 pmodule, prevnum = self.revsplit(parents[0])[1:]
576 pmodule, prevnum = self.revsplit(parents[0])[1:]
577 if ent.copyfrom_rev < prevnum:
577 if ent.copyfrom_rev < prevnum:
578 continue
578 continue
579 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
579 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
580 if not copyfrom_path:
580 if not copyfrom_path:
581 continue
581 continue
582 self.ui.debug(_("copied to %s from %s@%s\n") %
582 self.ui.debug(_("copied to %s from %s@%s\n") %
583 (entrypath, copyfrom_path, ent.copyfrom_rev))
583 (entrypath, copyfrom_path, ent.copyfrom_rev))
584 copies[self.recode(entry)] = self.recode(copyfrom_path)
584 copies[self.recode(entry)] = self.recode(copyfrom_path)
585 elif kind == 0: # gone, but had better be a deleted *file*
585 elif kind == 0: # gone, but had better be a deleted *file*
586 self.ui.debug(_("gone from %s\n") % ent.copyfrom_rev)
586 self.ui.debug(_("gone from %s\n") % ent.copyfrom_rev)
587
587
588 # if a branch is created but entries are removed in the same
588 # if a branch is created but entries are removed in the same
589 # changeset, get the right fromrev
589 # changeset, get the right fromrev
590 # parents cannot be empty here, you cannot remove things from
590 # parents cannot be empty here, you cannot remove things from
591 # a root revision.
591 # a root revision.
592 uuid, old_module, fromrev = self.revsplit(parents[0])
592 uuid, old_module, fromrev = self.revsplit(parents[0])
593
593
594 basepath = old_module + "/" + self.getrelpath(path)
594 basepath = old_module + "/" + self.getrelpath(path)
595 entrypath = basepath
595 entrypath = basepath
596
596
597 def lookup_parts(p):
597 def lookup_parts(p):
598 rc = None
598 rc = None
599 parts = p.split("/")
599 parts = p.split("/")
600 for i in range(len(parts)):
600 for i in range(len(parts)):
601 part = "/".join(parts[:i])
601 part = "/".join(parts[:i])
602 info = part, copyfrom.get(part, None)
602 info = part, copyfrom.get(part, None)
603 if info[1] is not None:
603 if info[1] is not None:
604 self.ui.debug(_("Found parent directory %s\n") % info[1])
604 self.ui.debug(_("Found parent directory %s\n") % info[1])
605 rc = info
605 rc = info
606 return rc
606 return rc
607
607
608 self.ui.debug(_("base, entry %s %s\n") % (basepath, entrypath))
608 self.ui.debug(_("base, entry %s %s\n") % (basepath, entrypath))
609
609
610 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
610 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
611
611
612 # need to remove fragment from lookup_parts and replace with copyfrom_path
612 # need to remove fragment from lookup_parts and replace with copyfrom_path
613 if frompath is not None:
613 if frompath is not None:
614 self.ui.debug(_("munge-o-matic\n"))
614 self.ui.debug(_("munge-o-matic\n"))
615 self.ui.debug(entrypath + '\n')
615 self.ui.debug(entrypath + '\n')
616 self.ui.debug(entrypath[len(frompath):] + '\n')
616 self.ui.debug(entrypath[len(frompath):] + '\n')
617 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
617 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
618 fromrev = froment.copyfrom_rev
618 fromrev = froment.copyfrom_rev
619 self.ui.debug(_("Info: %s %s %s %s\n") % (frompath, froment, ent, entrypath))
619 self.ui.debug(_("Info: %s %s %s %s\n") % (frompath, froment, ent, entrypath))
620
620
621 # We can avoid the reparent calls if the module has not changed
621 # We can avoid the reparent calls if the module has not changed
622 # but it probably does not worth the pain.
622 # but it probably does not worth the pain.
623 prevmodule = self.reparent('')
623 prevmodule = self.reparent('')
624 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
624 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
625 self.reparent(prevmodule)
625 self.reparent(prevmodule)
626
626
627 if fromkind == svn.core.svn_node_file: # a deleted file
627 if fromkind == svn.core.svn_node_file: # a deleted file
628 entries.append(self.recode(entry))
628 entries.append(self.recode(entry))
629 elif fromkind == svn.core.svn_node_dir:
629 elif fromkind == svn.core.svn_node_dir:
630 # print "Deleted/moved non-file:", revnum, path, ent
630 # print "Deleted/moved non-file:", revnum, path, ent
631 # children = self._find_children(path, revnum - 1)
631 # children = self._find_children(path, revnum - 1)
632 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
632 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
633 # Sometimes this is tricky. For example: in
633 # Sometimes this is tricky. For example: in
634 # The Subversion Repository revision 6940 a dir
634 # The Subversion Repository revision 6940 a dir
635 # was copied and one of its files was deleted
635 # was copied and one of its files was deleted
636 # from the new location in the same commit. This
636 # from the new location in the same commit. This
637 # code can't deal with that yet.
637 # code can't deal with that yet.
638 if ent.action == 'C':
638 if ent.action == 'C':
639 children = self._find_children(path, fromrev)
639 children = self._find_children(path, fromrev)
640 else:
640 else:
641 oroot = entrypath.strip('/')
641 oroot = entrypath.strip('/')
642 nroot = path.strip('/')
642 nroot = path.strip('/')
643 children = self._find_children(oroot, fromrev)
643 children = self._find_children(oroot, fromrev)
644 children = [s.replace(oroot,nroot) for s in children]
644 children = [s.replace(oroot,nroot) for s in children]
645 # Mark all [files, not directories] as deleted.
645 # Mark all [files, not directories] as deleted.
646 for child in children:
646 for child in children:
647 # Can we move a child directory and its
647 # Can we move a child directory and its
648 # parent in the same commit? (probably can). Could
648 # parent in the same commit? (probably can). Could
649 # cause problems if instead of revnum -1,
649 # cause problems if instead of revnum -1,
650 # we have to look in (copyfrom_path, revnum - 1)
650 # we have to look in (copyfrom_path, revnum - 1)
651 entrypath = self.getrelpath("/" + child, module=old_module)
651 entrypath = self.getrelpath("/" + child, module=old_module)
652 if entrypath:
652 if entrypath:
653 entry = self.recode(entrypath.decode(self.encoding))
653 entry = self.recode(entrypath.decode(self.encoding))
654 if entry in copies:
654 if entry in copies:
655 # deleted file within a copy
655 # deleted file within a copy
656 del copies[entry]
656 del copies[entry]
657 else:
657 else:
658 entries.append(entry)
658 entries.append(entry)
659 else:
659 else:
660 self.ui.debug(_('unknown path in revision %d: %s\n') % \
660 self.ui.debug(_('unknown path in revision %d: %s\n') % \
661 (revnum, path))
661 (revnum, path))
662 elif kind == svn.core.svn_node_dir:
662 elif kind == svn.core.svn_node_dir:
663 # Should probably synthesize normal file entries
663 # Should probably synthesize normal file entries
664 # and handle as above to clean up copy/rename handling.
664 # and handle as above to clean up copy/rename handling.
665
665
666 # If the directory just had a prop change,
666 # If the directory just had a prop change,
667 # then we shouldn't need to look for its children.
667 # then we shouldn't need to look for its children.
668 if ent.action == 'M':
668 if ent.action == 'M':
669 continue
669 continue
670
670
671 # Also this could create duplicate entries. Not sure
671 # Also this could create duplicate entries. Not sure
672 # whether this will matter. Maybe should make entries a set.
672 # whether this will matter. Maybe should make entries a set.
673 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
673 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
674 # This will fail if a directory was copied
674 # This will fail if a directory was copied
675 # from another branch and then some of its files
675 # from another branch and then some of its files
676 # were deleted in the same transaction.
676 # were deleted in the same transaction.
677 children = util.sort(self._find_children(path, revnum))
677 children = util.sort(self._find_children(path, revnum))
678 for child in children:
678 for child in children:
679 # Can we move a child directory and its
679 # Can we move a child directory and its
680 # parent in the same commit? (probably can). Could
680 # parent in the same commit? (probably can). Could
681 # cause problems if instead of revnum -1,
681 # cause problems if instead of revnum -1,
682 # we have to look in (copyfrom_path, revnum - 1)
682 # we have to look in (copyfrom_path, revnum - 1)
683 entrypath = self.getrelpath("/" + child)
683 entrypath = self.getrelpath("/" + child)
684 # print child, self.module, entrypath
684 # print child, self.module, entrypath
685 if entrypath:
685 if entrypath:
686 # Need to filter out directories here...
686 # Need to filter out directories here...
687 kind = self._checkpath(entrypath, revnum)
687 kind = self._checkpath(entrypath, revnum)
688 if kind != svn.core.svn_node_dir:
688 if kind != svn.core.svn_node_dir:
689 entries.append(self.recode(entrypath))
689 entries.append(self.recode(entrypath))
690
690
691 # Copies here (must copy all from source)
691 # Copies here (must copy all from source)
692 # Probably not a real problem for us if
692 # Probably not a real problem for us if
693 # source does not exist
693 # source does not exist
694 if not ent.copyfrom_path or not parents:
694 if not ent.copyfrom_path or not parents:
695 continue
695 continue
696 # Copy sources not in parent revisions cannot be represented,
696 # Copy sources not in parent revisions cannot be represented,
697 # ignore their origin for now
697 # ignore their origin for now
698 pmodule, prevnum = self.revsplit(parents[0])[1:]
698 pmodule, prevnum = self.revsplit(parents[0])[1:]
699 if ent.copyfrom_rev < prevnum:
699 if ent.copyfrom_rev < prevnum:
700 continue
700 continue
701 copyfrompath = ent.copyfrom_path.decode(self.encoding)
701 copyfrompath = ent.copyfrom_path.decode(self.encoding)
702 copyfrompath = self.getrelpath(copyfrompath, pmodule)
702 copyfrompath = self.getrelpath(copyfrompath, pmodule)
703 if not copyfrompath:
703 if not copyfrompath:
704 continue
704 continue
705 copyfrom[path] = ent
705 copyfrom[path] = ent
706 self.ui.debug(_("mark %s came from %s:%d\n")
706 self.ui.debug(_("mark %s came from %s:%d\n")
707 % (path, copyfrompath, ent.copyfrom_rev))
707 % (path, copyfrompath, ent.copyfrom_rev))
708 children = self._find_children(ent.copyfrom_path, ent.copyfrom_rev)
708 children = self._find_children(ent.copyfrom_path, ent.copyfrom_rev)
709 children.sort()
709 children.sort()
710 for child in children:
710 for child in children:
711 entrypath = self.getrelpath("/" + child, pmodule)
711 entrypath = self.getrelpath("/" + child, pmodule)
712 if not entrypath:
712 if not entrypath:
713 continue
713 continue
714 entry = entrypath.decode(self.encoding)
714 entry = entrypath.decode(self.encoding)
715 copytopath = path + entry[len(copyfrompath):]
715 copytopath = path + entry[len(copyfrompath):]
716 copytopath = self.getrelpath(copytopath)
716 copytopath = self.getrelpath(copytopath)
717 copies[self.recode(copytopath)] = self.recode(entry, pmodule)
717 copies[self.recode(copytopath)] = self.recode(entry, pmodule)
718
718
719 return (util.unique(entries), copies)
719 return (util.unique(entries), copies)
720
720
721 def _fetch_revisions(self, from_revnum, to_revnum):
721 def _fetch_revisions(self, from_revnum, to_revnum):
722 if from_revnum < to_revnum:
722 if from_revnum < to_revnum:
723 from_revnum, to_revnum = to_revnum, from_revnum
723 from_revnum, to_revnum = to_revnum, from_revnum
724
724
725 self.child_cset = None
725 self.child_cset = None
726
726
727 def isdescendantof(parent, child):
728 if not child or not parent or not child.startswith(parent):
729 return False
730 subpath = child[len(parent):]
731 return len(subpath) > 1 and subpath[0] == '/'
732
733 def parselogentry(orig_paths, revnum, author, date, message):
727 def parselogentry(orig_paths, revnum, author, date, message):
734 """Return the parsed commit object or None, and True if
728 """Return the parsed commit object or None, and True if
735 the revision is a branch root.
729 the revision is a branch root.
736 """
730 """
737 self.ui.debug(_("parsing revision %d (%d changes)\n") %
731 self.ui.debug(_("parsing revision %d (%d changes)\n") %
738 (revnum, len(orig_paths)))
732 (revnum, len(orig_paths)))
739
733
740 branched = False
734 branched = False
741 rev = self.revid(revnum)
735 rev = self.revid(revnum)
742 # branch log might return entries for a parent we already have
736 # branch log might return entries for a parent we already have
743
737
744 if (rev in self.commits or revnum < to_revnum):
738 if (rev in self.commits or revnum < to_revnum):
745 return None, branched
739 return None, branched
746
740
747 parents = []
741 parents = []
748 # check whether this revision is the start of a branch or part
742 # check whether this revision is the start of a branch or part
749 # of a branch renaming
743 # of a branch renaming
750 orig_paths = util.sort(orig_paths.items())
744 orig_paths = util.sort(orig_paths.items())
751 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
745 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
752 if root_paths:
746 if root_paths:
753 path, ent = root_paths[-1]
747 path, ent = root_paths[-1]
754 if ent.copyfrom_path:
748 if ent.copyfrom_path:
755 # If dir was moved while one of its file was removed
756 # the log may look like:
757 # A /dir (from /dir:x)
758 # A /dir/a (from /dir/a:y)
759 # A /dir/b (from /dir/b:z)
760 # ...
761 # for all remaining children.
762 # Let's take the highest child element from rev as source.
763 copies = [(p,e) for p,e in orig_paths[:-1]
764 if isdescendantof(ent.copyfrom_path, e.copyfrom_path)]
765 fromrev = max([e.copyfrom_rev for p,e in copies] + [ent.copyfrom_rev])
766 branched = True
749 branched = True
767 newpath = ent.copyfrom_path + self.module[len(path):]
750 newpath = ent.copyfrom_path + self.module[len(path):]
768 # ent.copyfrom_rev may not be the actual last revision
751 # ent.copyfrom_rev may not be the actual last revision
769 previd = self.latest(newpath, fromrev)
752 previd = self.latest(newpath, ent.copyfrom_rev)
770 if previd is not None:
753 if previd is not None:
771 prevmodule, prevnum = self.revsplit(previd)[1:]
754 prevmodule, prevnum = self.revsplit(previd)[1:]
772 if prevnum >= self.startrev:
755 if prevnum >= self.startrev:
773 parents = [previd]
756 parents = [previd]
774 self.ui.note(_('found parent of branch %s at %d: %s\n') %
757 self.ui.note(_('found parent of branch %s at %d: %s\n') %
775 (self.module, prevnum, prevmodule))
758 (self.module, prevnum, prevmodule))
776 else:
759 else:
777 self.ui.debug(_("No copyfrom path, don't know what to do.\n"))
760 self.ui.debug(_("No copyfrom path, don't know what to do.\n"))
778
761
779 paths = []
762 paths = []
780 # filter out unrelated paths
763 # filter out unrelated paths
781 for path, ent in orig_paths:
764 for path, ent in orig_paths:
782 if self.getrelpath(path) is None:
765 if self.getrelpath(path) is None:
783 continue
766 continue
784 paths.append((path, ent))
767 paths.append((path, ent))
785
768
786 # Example SVN datetime. Includes microseconds.
769 # Example SVN datetime. Includes microseconds.
787 # ISO-8601 conformant
770 # ISO-8601 conformant
788 # '2007-01-04T17:35:00.902377Z'
771 # '2007-01-04T17:35:00.902377Z'
789 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
772 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
790
773
791 log = message and self.recode(message) or ''
774 log = message and self.recode(message) or ''
792 author = author and self.recode(author) or ''
775 author = author and self.recode(author) or ''
793 try:
776 try:
794 branch = self.module.split("/")[-1]
777 branch = self.module.split("/")[-1]
795 if branch == 'trunk':
778 if branch == 'trunk':
796 branch = ''
779 branch = ''
797 except IndexError:
780 except IndexError:
798 branch = None
781 branch = None
799
782
800 cset = commit(author=author,
783 cset = commit(author=author,
801 date=util.datestr(date),
784 date=util.datestr(date),
802 desc=log,
785 desc=log,
803 parents=parents,
786 parents=parents,
804 branch=branch,
787 branch=branch,
805 rev=rev.encode('utf-8'))
788 rev=rev.encode('utf-8'))
806
789
807 self.commits[rev] = cset
790 self.commits[rev] = cset
808 # The parents list is *shared* among self.paths and the
791 # The parents list is *shared* among self.paths and the
809 # commit object. Both will be updated below.
792 # commit object. Both will be updated below.
810 self.paths[rev] = (paths, cset.parents)
793 self.paths[rev] = (paths, cset.parents)
811 if self.child_cset and not self.child_cset.parents:
794 if self.child_cset and not self.child_cset.parents:
812 self.child_cset.parents[:] = [rev]
795 self.child_cset.parents[:] = [rev]
813 self.child_cset = cset
796 self.child_cset = cset
814 return cset, branched
797 return cset, branched
815
798
816 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
799 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
817 (self.module, from_revnum, to_revnum))
800 (self.module, from_revnum, to_revnum))
818
801
819 try:
802 try:
820 firstcset = None
803 firstcset = None
821 lastonbranch = False
804 lastonbranch = False
822 stream = self._getlog([self.module], from_revnum, to_revnum)
805 stream = self._getlog([self.module], from_revnum, to_revnum)
823 try:
806 try:
824 for entry in stream:
807 for entry in stream:
825 paths, revnum, author, date, message = entry
808 paths, revnum, author, date, message = entry
826 if revnum < self.startrev:
809 if revnum < self.startrev:
827 lastonbranch = True
810 lastonbranch = True
828 break
811 break
829 if self.is_blacklisted(revnum):
812 if self.is_blacklisted(revnum):
830 self.ui.note(_('skipping blacklisted revision %d\n')
813 self.ui.note(_('skipping blacklisted revision %d\n')
831 % revnum)
814 % revnum)
832 continue
815 continue
833 if paths is None:
816 if paths is None:
834 self.ui.debug(_('revision %d has no entries\n') % revnum)
817 self.ui.debug(_('revision %d has no entries\n') % revnum)
835 continue
818 continue
836 cset, lastonbranch = parselogentry(paths, revnum, author,
819 cset, lastonbranch = parselogentry(paths, revnum, author,
837 date, message)
820 date, message)
838 if cset:
821 if cset:
839 firstcset = cset
822 firstcset = cset
840 if lastonbranch:
823 if lastonbranch:
841 break
824 break
842 finally:
825 finally:
843 stream.close()
826 stream.close()
844
827
845 if not lastonbranch and firstcset and not firstcset.parents:
828 if not lastonbranch and firstcset and not firstcset.parents:
846 # The first revision of the sequence (the last fetched one)
829 # The first revision of the sequence (the last fetched one)
847 # has invalid parents if not a branch root. Find the parent
830 # has invalid parents if not a branch root. Find the parent
848 # revision now, if any.
831 # revision now, if any.
849 try:
832 try:
850 firstrevnum = self.revnum(firstcset.rev)
833 firstrevnum = self.revnum(firstcset.rev)
851 if firstrevnum > 1:
834 if firstrevnum > 1:
852 latest = self.latest(self.module, firstrevnum - 1)
835 latest = self.latest(self.module, firstrevnum - 1)
853 if latest:
836 if latest:
854 firstcset.parents.append(latest)
837 firstcset.parents.append(latest)
855 except SvnPathNotFound:
838 except SvnPathNotFound:
856 pass
839 pass
857 except SubversionException, (inst, num):
840 except SubversionException, (inst, num):
858 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
841 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
859 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
842 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
860 raise
843 raise
861
844
862 def _getfile(self, file, rev):
845 def _getfile(self, file, rev):
863 # TODO: ra.get_file transmits the whole file instead of diffs.
846 # TODO: ra.get_file transmits the whole file instead of diffs.
864 mode = ''
847 mode = ''
865 try:
848 try:
866 new_module, revnum = self.revsplit(rev)[1:]
849 new_module, revnum = self.revsplit(rev)[1:]
867 if self.module != new_module:
850 if self.module != new_module:
868 self.module = new_module
851 self.module = new_module
869 self.reparent(self.module)
852 self.reparent(self.module)
870 io = StringIO()
853 io = StringIO()
871 info = svn.ra.get_file(self.ra, file, revnum, io)
854 info = svn.ra.get_file(self.ra, file, revnum, io)
872 data = io.getvalue()
855 data = io.getvalue()
873 # ra.get_files() seems to keep a reference on the input buffer
856 # ra.get_files() seems to keep a reference on the input buffer
874 # preventing collection. Release it explicitely.
857 # preventing collection. Release it explicitely.
875 io.close()
858 io.close()
876 if isinstance(info, list):
859 if isinstance(info, list):
877 info = info[-1]
860 info = info[-1]
878 mode = ("svn:executable" in info) and 'x' or ''
861 mode = ("svn:executable" in info) and 'x' or ''
879 mode = ("svn:special" in info) and 'l' or mode
862 mode = ("svn:special" in info) and 'l' or mode
880 except SubversionException, e:
863 except SubversionException, e:
881 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
864 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
882 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
865 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
883 if e.apr_err in notfound: # File not found
866 if e.apr_err in notfound: # File not found
884 raise IOError()
867 raise IOError()
885 raise
868 raise
886 if mode == 'l':
869 if mode == 'l':
887 link_prefix = "link "
870 link_prefix = "link "
888 if data.startswith(link_prefix):
871 if data.startswith(link_prefix):
889 data = data[len(link_prefix):]
872 data = data[len(link_prefix):]
890 return data, mode
873 return data, mode
891
874
892 def _find_children(self, path, revnum):
875 def _find_children(self, path, revnum):
893 path = path.strip('/')
876 path = path.strip('/')
894 pool = Pool()
877 pool = Pool()
895 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
878 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
896 return ['%s/%s' % (path, x) for x in
879 return ['%s/%s' % (path, x) for x in
897 svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
880 svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
898
881
899 def getrelpath(self, path, module=None):
882 def getrelpath(self, path, module=None):
900 if module is None:
883 if module is None:
901 module = self.module
884 module = self.module
902 # Given the repository url of this wc, say
885 # Given the repository url of this wc, say
903 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
886 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
904 # extract the "entry" portion (a relative path) from what
887 # extract the "entry" portion (a relative path) from what
905 # svn log --xml says, ie
888 # svn log --xml says, ie
906 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
889 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
907 # that is to say "tests/PloneTestCase.py"
890 # that is to say "tests/PloneTestCase.py"
908 if path.startswith(module):
891 if path.startswith(module):
909 relative = path.rstrip('/')[len(module):]
892 relative = path.rstrip('/')[len(module):]
910 if relative.startswith('/'):
893 if relative.startswith('/'):
911 return relative[1:]
894 return relative[1:]
912 elif relative == '':
895 elif relative == '':
913 return relative
896 return relative
914
897
915 # The path is outside our tracked tree...
898 # The path is outside our tracked tree...
916 self.ui.debug(_('%r is not under %r, ignoring\n') % (path, module))
899 self.ui.debug(_('%r is not under %r, ignoring\n') % (path, module))
917 return None
900 return None
918
901
919 def _checkpath(self, path, revnum):
902 def _checkpath(self, path, revnum):
920 # ra.check_path does not like leading slashes very much, it leads
903 # ra.check_path does not like leading slashes very much, it leads
921 # to PROPFIND subversion errors
904 # to PROPFIND subversion errors
922 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
905 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
923
906
924 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
907 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
925 strict_node_history=False):
908 strict_node_history=False):
926 # Normalize path names, svn >= 1.5 only wants paths relative to
909 # Normalize path names, svn >= 1.5 only wants paths relative to
927 # supplied URL
910 # supplied URL
928 relpaths = []
911 relpaths = []
929 for p in paths:
912 for p in paths:
930 if not p.startswith('/'):
913 if not p.startswith('/'):
931 p = self.module + '/' + p
914 p = self.module + '/' + p
932 relpaths.append(p.strip('/'))
915 relpaths.append(p.strip('/'))
933 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
916 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
934 strict_node_history]
917 strict_node_history]
935 arg = encodeargs(args)
918 arg = encodeargs(args)
936 hgexe = util.hgexecutable()
919 hgexe = util.hgexecutable()
937 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
920 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
938 stdin, stdout = util.popen2(cmd, 'b')
921 stdin, stdout = util.popen2(cmd, 'b')
939 stdin.write(arg)
922 stdin.write(arg)
940 stdin.close()
923 stdin.close()
941 return logstream(stdout)
924 return logstream(stdout)
942
925
943 pre_revprop_change = '''#!/bin/sh
926 pre_revprop_change = '''#!/bin/sh
944
927
945 REPOS="$1"
928 REPOS="$1"
946 REV="$2"
929 REV="$2"
947 USER="$3"
930 USER="$3"
948 PROPNAME="$4"
931 PROPNAME="$4"
949 ACTION="$5"
932 ACTION="$5"
950
933
951 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
934 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
952 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
935 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
953 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
936 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
954
937
955 echo "Changing prohibited revision property" >&2
938 echo "Changing prohibited revision property" >&2
956 exit 1
939 exit 1
957 '''
940 '''
958
941
959 class svn_sink(converter_sink, commandline):
942 class svn_sink(converter_sink, commandline):
960 commit_re = re.compile(r'Committed revision (\d+).', re.M)
943 commit_re = re.compile(r'Committed revision (\d+).', re.M)
961
944
962 def prerun(self):
945 def prerun(self):
963 if self.wc:
946 if self.wc:
964 os.chdir(self.wc)
947 os.chdir(self.wc)
965
948
966 def postrun(self):
949 def postrun(self):
967 if self.wc:
950 if self.wc:
968 os.chdir(self.cwd)
951 os.chdir(self.cwd)
969
952
970 def join(self, name):
953 def join(self, name):
971 return os.path.join(self.wc, '.svn', name)
954 return os.path.join(self.wc, '.svn', name)
972
955
973 def revmapfile(self):
956 def revmapfile(self):
974 return self.join('hg-shamap')
957 return self.join('hg-shamap')
975
958
976 def authorfile(self):
959 def authorfile(self):
977 return self.join('hg-authormap')
960 return self.join('hg-authormap')
978
961
979 def __init__(self, ui, path):
962 def __init__(self, ui, path):
980 converter_sink.__init__(self, ui, path)
963 converter_sink.__init__(self, ui, path)
981 commandline.__init__(self, ui, 'svn')
964 commandline.__init__(self, ui, 'svn')
982 self.delete = []
965 self.delete = []
983 self.setexec = []
966 self.setexec = []
984 self.delexec = []
967 self.delexec = []
985 self.copies = []
968 self.copies = []
986 self.wc = None
969 self.wc = None
987 self.cwd = os.getcwd()
970 self.cwd = os.getcwd()
988
971
989 path = os.path.realpath(path)
972 path = os.path.realpath(path)
990
973
991 created = False
974 created = False
992 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
975 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
993 self.wc = path
976 self.wc = path
994 self.run0('update')
977 self.run0('update')
995 else:
978 else:
996 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
979 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
997
980
998 if os.path.isdir(os.path.dirname(path)):
981 if os.path.isdir(os.path.dirname(path)):
999 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
982 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
1000 ui.status(_('initializing svn repo %r\n') %
983 ui.status(_('initializing svn repo %r\n') %
1001 os.path.basename(path))
984 os.path.basename(path))
1002 commandline(ui, 'svnadmin').run0('create', path)
985 commandline(ui, 'svnadmin').run0('create', path)
1003 created = path
986 created = path
1004 path = util.normpath(path)
987 path = util.normpath(path)
1005 if not path.startswith('/'):
988 if not path.startswith('/'):
1006 path = '/' + path
989 path = '/' + path
1007 path = 'file://' + path
990 path = 'file://' + path
1008
991
1009 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
992 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
1010 self.run0('checkout', path, wcpath)
993 self.run0('checkout', path, wcpath)
1011
994
1012 self.wc = wcpath
995 self.wc = wcpath
1013 self.opener = util.opener(self.wc)
996 self.opener = util.opener(self.wc)
1014 self.wopener = util.opener(self.wc)
997 self.wopener = util.opener(self.wc)
1015 self.childmap = mapfile(ui, self.join('hg-childmap'))
998 self.childmap = mapfile(ui, self.join('hg-childmap'))
1016 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
999 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
1017
1000
1018 if created:
1001 if created:
1019 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1002 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1020 fp = open(hook, 'w')
1003 fp = open(hook, 'w')
1021 fp.write(pre_revprop_change)
1004 fp.write(pre_revprop_change)
1022 fp.close()
1005 fp.close()
1023 util.set_flags(hook, False, True)
1006 util.set_flags(hook, False, True)
1024
1007
1025 xport = transport.SvnRaTransport(url=geturl(path))
1008 xport = transport.SvnRaTransport(url=geturl(path))
1026 self.uuid = svn.ra.get_uuid(xport.ra)
1009 self.uuid = svn.ra.get_uuid(xport.ra)
1027
1010
1028 def wjoin(self, *names):
1011 def wjoin(self, *names):
1029 return os.path.join(self.wc, *names)
1012 return os.path.join(self.wc, *names)
1030
1013
1031 def putfile(self, filename, flags, data):
1014 def putfile(self, filename, flags, data):
1032 if 'l' in flags:
1015 if 'l' in flags:
1033 self.wopener.symlink(data, filename)
1016 self.wopener.symlink(data, filename)
1034 else:
1017 else:
1035 try:
1018 try:
1036 if os.path.islink(self.wjoin(filename)):
1019 if os.path.islink(self.wjoin(filename)):
1037 os.unlink(filename)
1020 os.unlink(filename)
1038 except OSError:
1021 except OSError:
1039 pass
1022 pass
1040 self.wopener(filename, 'w').write(data)
1023 self.wopener(filename, 'w').write(data)
1041
1024
1042 if self.is_exec:
1025 if self.is_exec:
1043 was_exec = self.is_exec(self.wjoin(filename))
1026 was_exec = self.is_exec(self.wjoin(filename))
1044 else:
1027 else:
1045 # On filesystems not supporting execute-bit, there is no way
1028 # On filesystems not supporting execute-bit, there is no way
1046 # to know if it is set but asking subversion. Setting it
1029 # to know if it is set but asking subversion. Setting it
1047 # systematically is just as expensive and much simpler.
1030 # systematically is just as expensive and much simpler.
1048 was_exec = 'x' not in flags
1031 was_exec = 'x' not in flags
1049
1032
1050 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1033 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1051 if was_exec:
1034 if was_exec:
1052 if 'x' not in flags:
1035 if 'x' not in flags:
1053 self.delexec.append(filename)
1036 self.delexec.append(filename)
1054 else:
1037 else:
1055 if 'x' in flags:
1038 if 'x' in flags:
1056 self.setexec.append(filename)
1039 self.setexec.append(filename)
1057
1040
1058 def _copyfile(self, source, dest):
1041 def _copyfile(self, source, dest):
1059 # SVN's copy command pukes if the destination file exists, but
1042 # SVN's copy command pukes if the destination file exists, but
1060 # our copyfile method expects to record a copy that has
1043 # our copyfile method expects to record a copy that has
1061 # already occurred. Cross the semantic gap.
1044 # already occurred. Cross the semantic gap.
1062 wdest = self.wjoin(dest)
1045 wdest = self.wjoin(dest)
1063 exists = os.path.exists(wdest)
1046 exists = os.path.exists(wdest)
1064 if exists:
1047 if exists:
1065 fd, tempname = tempfile.mkstemp(
1048 fd, tempname = tempfile.mkstemp(
1066 prefix='hg-copy-', dir=os.path.dirname(wdest))
1049 prefix='hg-copy-', dir=os.path.dirname(wdest))
1067 os.close(fd)
1050 os.close(fd)
1068 os.unlink(tempname)
1051 os.unlink(tempname)
1069 os.rename(wdest, tempname)
1052 os.rename(wdest, tempname)
1070 try:
1053 try:
1071 self.run0('copy', source, dest)
1054 self.run0('copy', source, dest)
1072 finally:
1055 finally:
1073 if exists:
1056 if exists:
1074 try:
1057 try:
1075 os.unlink(wdest)
1058 os.unlink(wdest)
1076 except OSError:
1059 except OSError:
1077 pass
1060 pass
1078 os.rename(tempname, wdest)
1061 os.rename(tempname, wdest)
1079
1062
1080 def dirs_of(self, files):
1063 def dirs_of(self, files):
1081 dirs = util.set()
1064 dirs = util.set()
1082 for f in files:
1065 for f in files:
1083 if os.path.isdir(self.wjoin(f)):
1066 if os.path.isdir(self.wjoin(f)):
1084 dirs.add(f)
1067 dirs.add(f)
1085 for i in strutil.rfindall(f, '/'):
1068 for i in strutil.rfindall(f, '/'):
1086 dirs.add(f[:i])
1069 dirs.add(f[:i])
1087 return dirs
1070 return dirs
1088
1071
1089 def add_dirs(self, files):
1072 def add_dirs(self, files):
1090 add_dirs = [d for d in util.sort(self.dirs_of(files))
1073 add_dirs = [d for d in util.sort(self.dirs_of(files))
1091 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1074 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1092 if add_dirs:
1075 if add_dirs:
1093 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1076 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1094 return add_dirs
1077 return add_dirs
1095
1078
1096 def add_files(self, files):
1079 def add_files(self, files):
1097 if files:
1080 if files:
1098 self.xargs(files, 'add', quiet=True)
1081 self.xargs(files, 'add', quiet=True)
1099 return files
1082 return files
1100
1083
1101 def tidy_dirs(self, names):
1084 def tidy_dirs(self, names):
1102 dirs = util.sort(self.dirs_of(names))
1085 dirs = util.sort(self.dirs_of(names))
1103 dirs.reverse()
1086 dirs.reverse()
1104 deleted = []
1087 deleted = []
1105 for d in dirs:
1088 for d in dirs:
1106 wd = self.wjoin(d)
1089 wd = self.wjoin(d)
1107 if os.listdir(wd) == '.svn':
1090 if os.listdir(wd) == '.svn':
1108 self.run0('delete', d)
1091 self.run0('delete', d)
1109 deleted.append(d)
1092 deleted.append(d)
1110 return deleted
1093 return deleted
1111
1094
1112 def addchild(self, parent, child):
1095 def addchild(self, parent, child):
1113 self.childmap[parent] = child
1096 self.childmap[parent] = child
1114
1097
1115 def revid(self, rev):
1098 def revid(self, rev):
1116 return u"svn:%s@%s" % (self.uuid, rev)
1099 return u"svn:%s@%s" % (self.uuid, rev)
1117
1100
1118 def putcommit(self, files, copies, parents, commit, source):
1101 def putcommit(self, files, copies, parents, commit, source):
1119 # Apply changes to working copy
1102 # Apply changes to working copy
1120 for f, v in files:
1103 for f, v in files:
1121 try:
1104 try:
1122 data = source.getfile(f, v)
1105 data = source.getfile(f, v)
1123 except IOError, inst:
1106 except IOError, inst:
1124 self.delete.append(f)
1107 self.delete.append(f)
1125 else:
1108 else:
1126 e = source.getmode(f, v)
1109 e = source.getmode(f, v)
1127 self.putfile(f, e, data)
1110 self.putfile(f, e, data)
1128 if f in copies:
1111 if f in copies:
1129 self.copies.append([copies[f], f])
1112 self.copies.append([copies[f], f])
1130 files = [f[0] for f in files]
1113 files = [f[0] for f in files]
1131
1114
1132 for parent in parents:
1115 for parent in parents:
1133 try:
1116 try:
1134 return self.revid(self.childmap[parent])
1117 return self.revid(self.childmap[parent])
1135 except KeyError:
1118 except KeyError:
1136 pass
1119 pass
1137 entries = util.set(self.delete)
1120 entries = util.set(self.delete)
1138 files = util.frozenset(files)
1121 files = util.frozenset(files)
1139 entries.update(self.add_dirs(files.difference(entries)))
1122 entries.update(self.add_dirs(files.difference(entries)))
1140 if self.copies:
1123 if self.copies:
1141 for s, d in self.copies:
1124 for s, d in self.copies:
1142 self._copyfile(s, d)
1125 self._copyfile(s, d)
1143 self.copies = []
1126 self.copies = []
1144 if self.delete:
1127 if self.delete:
1145 self.xargs(self.delete, 'delete')
1128 self.xargs(self.delete, 'delete')
1146 self.delete = []
1129 self.delete = []
1147 entries.update(self.add_files(files.difference(entries)))
1130 entries.update(self.add_files(files.difference(entries)))
1148 entries.update(self.tidy_dirs(entries))
1131 entries.update(self.tidy_dirs(entries))
1149 if self.delexec:
1132 if self.delexec:
1150 self.xargs(self.delexec, 'propdel', 'svn:executable')
1133 self.xargs(self.delexec, 'propdel', 'svn:executable')
1151 self.delexec = []
1134 self.delexec = []
1152 if self.setexec:
1135 if self.setexec:
1153 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1136 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1154 self.setexec = []
1137 self.setexec = []
1155
1138
1156 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1139 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1157 fp = os.fdopen(fd, 'w')
1140 fp = os.fdopen(fd, 'w')
1158 fp.write(commit.desc)
1141 fp.write(commit.desc)
1159 fp.close()
1142 fp.close()
1160 try:
1143 try:
1161 output = self.run0('commit',
1144 output = self.run0('commit',
1162 username=util.shortuser(commit.author),
1145 username=util.shortuser(commit.author),
1163 file=messagefile,
1146 file=messagefile,
1164 encoding='utf-8')
1147 encoding='utf-8')
1165 try:
1148 try:
1166 rev = self.commit_re.search(output).group(1)
1149 rev = self.commit_re.search(output).group(1)
1167 except AttributeError:
1150 except AttributeError:
1168 self.ui.warn(_('unexpected svn output:\n'))
1151 self.ui.warn(_('unexpected svn output:\n'))
1169 self.ui.warn(output)
1152 self.ui.warn(output)
1170 raise util.Abort(_('unable to cope with svn output'))
1153 raise util.Abort(_('unable to cope with svn output'))
1171 if commit.rev:
1154 if commit.rev:
1172 self.run('propset', 'hg:convert-rev', commit.rev,
1155 self.run('propset', 'hg:convert-rev', commit.rev,
1173 revprop=True, revision=rev)
1156 revprop=True, revision=rev)
1174 if commit.branch and commit.branch != 'default':
1157 if commit.branch and commit.branch != 'default':
1175 self.run('propset', 'hg:convert-branch', commit.branch,
1158 self.run('propset', 'hg:convert-branch', commit.branch,
1176 revprop=True, revision=rev)
1159 revprop=True, revision=rev)
1177 for parent in parents:
1160 for parent in parents:
1178 self.addchild(parent, rev)
1161 self.addchild(parent, rev)
1179 return self.revid(rev)
1162 return self.revid(rev)
1180 finally:
1163 finally:
1181 os.unlink(messagefile)
1164 os.unlink(messagefile)
1182
1165
1183 def puttags(self, tags):
1166 def puttags(self, tags):
1184 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
1167 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
@@ -1,98 +1,36
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
4
4
5 fix_path()
5 fix_path()
6 {
6 {
7 tr '\\' /
7 tr '\\' /
8 }
8 }
9
9
10 echo "[extensions]" >> $HGRCPATH
10 echo "[extensions]" >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
13
13
14 svnadmin create svn-repo
14 svnadmin create svn-repo
15 cat "$TESTDIR/svn/branches.svndump" | svnadmin load svn-repo > /dev/null
15
16
16 svnpath=`pwd | fix_path`
17 svnpath=`pwd | fix_path`
17 # SVN wants all paths to start with a slash. Unfortunately,
18 # SVN wants all paths to start with a slash. Unfortunately,
18 # Windows ones don't. Handle that.
19 # Windows ones don't. Handle that.
19 expr $svnpath : "\/" > /dev/null
20 expr $svnpath : "\/" > /dev/null
20 if [ $? -ne 0 ]; then
21 if [ $? -ne 0 ]; then
21 svnpath='/'$svnpath
22 svnpath='/'$svnpath
22 fi
23 fi
23
24 svnurl=file://$svnpath/svn-repo
24 echo % initial svn import
25 mkdir projA
26 cd projA
27 mkdir trunk
28 mkdir branches
29 mkdir tags
30 cd ..
31
32 svnurl=file://$svnpath/svn-repo/projA
33 svn import -m "init projA" projA $svnurl | fix_path
34
35 echo % update svn repository
36 svn co $svnurl A | fix_path
37 cd A
38 echo a > trunk/a
39 echo b > trunk/b
40 echo c > trunk/c
41 # Add a file within branches, used to confuse branch detection
42 echo d > branches/notinbranch
43 svn add trunk/a trunk/b trunk/c branches/notinbranch
44 svn ci -m hello
45
46 echo % branch to old
47 svn copy trunk branches/old
48 svn rm branches/old/c
49 svn ci -m "branch trunk, remove c"
50 svn up
51
52 echo % update trunk
53 "$TESTDIR/svn-safe-append.py" a trunk/a
54 svn ci -m "change a"
55
56 echo % update old branch
57 "$TESTDIR/svn-safe-append.py" b branches/old/b
58 svn ci -m "change b"
59
60 echo % create a cross-branch revision
61 svn move trunk/b branches/old/c
62 "$TESTDIR/svn-safe-append.py" c branches/old/c
63 svn ci -m "move and update c"
64
65 echo % update old branch again
66 "$TESTDIR/svn-safe-append.py" b branches/old/b
67 svn ci -m "change b again"
68
69 echo % move back and forth between branch of similar names
70 # This used to generate fake copy records
71 svn up
72 svn move branches/old branches/old2
73 svn ci -m "move to old2"
74 svn move branches/old2 branches/old
75 svn ci -m "move back to old"
76
77 echo % update trunk again
78 "$TESTDIR/svn-safe-append.py" a trunk/a
79 svn ci -m "last change to a"
80 cd ..
81
25
82 echo % convert trunk and branches
26 echo % convert trunk and branches
83 hg convert --datesort $svnurl A-hg
27 hg convert --datesort -r 10 $svnurl A-hg
84
85 echo % branch again from a converted revision
86 cd A
87 svn copy -r 1 $svnurl/trunk branches/old3
88 svn ci -m "branch trunk@1 into old3"
89 cd ..
90
28
91 echo % convert again
29 echo % convert again
92 hg convert --datesort $svnurl A-hg
30 hg convert --datesort $svnurl A-hg
93
31
94 cd A-hg
32 cd A-hg
95 hg glog --template 'branch=#branches# #rev# #desc|firstline# files: #files#\n'
33 hg glog --template 'branch=#branches# #rev# #desc|firstline# files: #files#\n'
96 hg branches | sed 's/:.*/:/'
34 hg branches | sed 's/:.*/:/'
97 hg tags -q
35 hg tags -q
98 cd ..
36 cd ..
@@ -1,130 +1,50
1 % initial svn import
2 Adding projA/trunk
3 Adding projA/branches
4 Adding projA/tags
5
6 Committed revision 1.
7 % update svn repository
8 A A/trunk
9 A A/branches
10 A A/tags
11 Checked out revision 1.
12 A trunk/a
13 A trunk/b
14 A trunk/c
15 A branches/notinbranch
16 Adding branches/notinbranch
17 Adding trunk/a
18 Adding trunk/b
19 Adding trunk/c
20 Transmitting file data ....
21 Committed revision 2.
22 % branch to old
23 A branches/old
24 D branches/old/c
25 Adding branches/old
26 Adding branches/old/a
27 Adding branches/old/b
28 Deleting branches/old/c
29
30 Committed revision 3.
31 At revision 3.
32 % update trunk
33 Sending trunk/a
34 Transmitting file data .
35 Committed revision 4.
36 % update old branch
37 Sending branches/old/b
38 Transmitting file data .
39 Committed revision 5.
40 % create a cross-branch revision
41 A branches/old/c
42 D trunk/b
43 Adding branches/old/c
44 Deleting trunk/b
45 Transmitting file data .
46 Committed revision 6.
47 % update old branch again
48 Sending branches/old/b
49 Transmitting file data .
50 Committed revision 7.
51 % move back and forth between branch of similar names
52 At revision 7.
53 A branches/old2
54 D branches/old/a
55 D branches/old/b
56 D branches/old/c
57 D branches/old
58 Deleting branches/old
59 Adding branches/old2
60
61 Committed revision 8.
62 A branches/old
63 D branches/old2/a
64 D branches/old2/b
65 D branches/old2/c
66 D branches/old2
67 Adding branches/old
68 Deleting branches/old2
69
70 Committed revision 9.
71 % update trunk again
72 Sending trunk/a
73 Transmitting file data .
74 Committed revision 10.
75 % convert trunk and branches
1 % convert trunk and branches
76 initializing destination A-hg repository
2 initializing destination A-hg repository
77 scanning source...
3 scanning source...
78 sorting...
4 sorting...
79 converting...
5 converting...
80 10 init projA
6 10 init projA
81 9 hello
7 9 hello
82 8 branch trunk, remove c
8 8 branch trunk, remove c
83 7 change a
9 7 change a
84 6 change b
10 6 change b
85 5 move and update c
11 5 move and update c
86 4 move and update c
12 4 move and update c
87 3 change b again
13 3 change b again
88 2 move to old2
14 2 move to old2
89 1 move back to old
15 1 move back to old
90 0 last change to a
16 0 last change to a
91 % branch again from a converted revision
92 Checked out revision 1.
93 A branches/old3
94 Adding branches/old3
95
96 Committed revision 11.
97 % convert again
17 % convert again
98 scanning source...
18 scanning source...
99 sorting...
19 sorting...
100 converting...
20 converting...
101 0 branch trunk@1 into old3
21 0 branch trunk@1 into old3
102 o branch=old3 11 branch trunk@1 into old3 files:
22 o branch=old3 11 branch trunk@1 into old3 files:
103 |
23 |
104 | o branch= 10 last change to a files: a
24 | o branch= 10 last change to a files: a
105 | |
25 | |
106 | | o branch=old 9 move back to old files:
26 | | o branch=old 9 move back to old files:
107 | | |
27 | | |
108 | | o branch=old2 8 move to old2 files:
28 | | o branch=old2 8 move to old2 files:
109 | | |
29 | | |
110 | | o branch=old 7 change b again files: b
30 | | o branch=old 7 change b again files: b
111 | | |
31 | | |
112 | o | branch= 6 move and update c files: b
32 | o | branch= 6 move and update c files: b
113 | | |
33 | | |
114 | | o branch=old 5 move and update c files: c
34 | | o branch=old 5 move and update c files: c
115 | | |
35 | | |
116 | | o branch=old 4 change b files: b
36 | | o branch=old 4 change b files: b
117 | | |
37 | | |
118 | o | branch= 3 change a files: a
38 | o | branch= 3 change a files: a
119 | | |
39 | | |
120 | | o branch=old 2 branch trunk, remove c files:
40 +---o branch=old 2 branch trunk, remove c files: a b
121 | |/
41 | |
122 | o branch= 1 hello files: a b c
42 | o branch= 1 hello files: a b c
123 |/
43 |/
124 o branch= 0 init projA files:
44 o branch= 0 init projA files:
125
45
126 old3 11:
46 old3 11:
127 default 10:
47 default 10:
128 old 9:
48 old 9:
129 old2 8:
49 old2 8:
130 tip
50 tip
@@ -1,82 +1,32
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
4
4
5 fix_path()
5 fix_path()
6 {
6 {
7 tr '\\' /
7 tr '\\' /
8 }
8 }
9
9
10 echo "[extensions]" >> $HGRCPATH
10 echo "[extensions]" >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
13
13
14 svnadmin create svn-repo
14 svnadmin create svn-repo
15 cat "$TESTDIR/svn/move.svndump" | svnadmin load svn-repo > /dev/null
15
16
16 svnpath=`pwd | fix_path`
17 svnpath=`pwd | fix_path`
17 # SVN wants all paths to start with a slash. Unfortunately,
18 # SVN wants all paths to start with a slash. Unfortunately,
18 # Windows ones don't. Handle that.
19 # Windows ones don't. Handle that.
19 expr $svnpath : "\/" > /dev/null
20 expr $svnpath : "\/" > /dev/null
20 if [ $? -ne 0 ]; then
21 if [ $? -ne 0 ]; then
21 svnpath='/'$svnpath
22 svnpath='/'$svnpath
22 fi
23 fi
23
24 svnurl=file://$svnpath/svn-repo
24 echo % initial svn import
25 mkdir projA
26 cd projA
27 mkdir trunk
28 echo a > trunk/a
29 mkdir trunk/d1
30 mkdir trunk/d2
31 echo b > trunk/d1/b
32 echo c > trunk/d1/c
33 echo d > trunk/d2/d
34 cd ..
35
36 svnurl=file://$svnpath/svn-repo/projA
37 svn import -m "init projA" projA $svnurl | fix_path
38
39 # Build a module renaming chain which used to confuse the converter.
40 echo % update svn repository
41 svn co $svnurl A | fix_path
42 cd A
43 "$TESTDIR/svn-safe-append.py" a trunk/a
44 "$TESTDIR/svn-safe-append.py" c trunk/d1/c
45 svn ci -m commitbeforemove
46 svn mv $svnurl/trunk $svnurl/subproject -m movedtrunk
47 svn up
48 mkdir subproject/trunk
49 svn add subproject/trunk
50 svn ci -m createtrunk
51 mkdir subproject/branches
52 svn add subproject/branches
53 svn ci -m createbranches
54 svn mv $svnurl/subproject/d1 $svnurl/subproject/trunk/d1 -m moved1
55 svn mv $svnurl/subproject/d2 $svnurl/subproject/trunk/d2 -m moved2
56 svn up
57 "$TESTDIR/svn-safe-append.py" b subproject/trunk/d1/b
58 svn rm subproject/trunk/d2
59 svn ci -m "changeb and rm d2"
60 svn mv $svnurl/subproject/trunk/d1 $svnurl/subproject/branches/d1 -m moved1again
61
62 if svn help copy | grep 'SRC\[@REV\]' > /dev/null 2>&1; then
63 # SVN >= 1.5 replaced the -r REV syntax with @REV
64 echo % copy a file from a past revision
65 svn copy $svnurl/subproject/trunk/d2/d@7 $svnurl/subproject/trunk -m copyfilefrompast
66 echo % copy a directory from a past revision
67 svn copy $svnurl/subproject/trunk/d2@7 $svnurl/subproject/trunk -m copydirfrompast
68 else
69 echo % copy a file from a past revision
70 svn copy -r 7 $svnurl/subproject/trunk/d2/d $svnurl/subproject/trunk -m copyfilefrompast
71 echo % copy a directory from a past revision
72 svn copy -r 7 $svnurl/subproject/trunk/d2 $svnurl/subproject/trunk -m copydirfrompast
73 fi
74 cd ..
75
25
76 echo % convert trunk and branches
26 echo % convert trunk and branches
77 hg convert --datesort $svnurl/subproject A-hg
27 hg convert --datesort $svnurl/subproject A-hg
78
28
79 cd A-hg
29 cd A-hg
80 hg glog --template '#rev# #desc|firstline# files: #files#\n'
30 hg glog --template '#rev# #desc|firstline# files: #files#\n'
81 hg branches | sed 's/:.*/:/'
31 hg branches | sed 's/:.*/:/'
82 cd ..
32 cd ..
@@ -1,105 +1,37
1 % initial svn import
2 Adding projA/trunk
3 Adding projA/trunk/a
4 Adding projA/trunk/d1
5 Adding projA/trunk/d1/b
6 Adding projA/trunk/d1/c
7 Adding projA/trunk/d2
8 Adding projA/trunk/d2/d
9
10 Committed revision 1.
11 % update svn repository
12 A A/trunk
13 A A/trunk/a
14 A A/trunk/d1
15 A A/trunk/d1/b
16 A A/trunk/d1/c
17 A A/trunk/d2
18 A A/trunk/d2/d
19 Checked out revision 1.
20 Sending trunk/a
21 Sending trunk/d1/c
22 Transmitting file data ..
23 Committed revision 2.
24
25 Committed revision 3.
26 D trunk
27 A subproject
28 A subproject/a
29 A subproject/d1
30 A subproject/d1/b
31 A subproject/d1/c
32 A subproject/d2
33 A subproject/d2/d
34 Updated to revision 3.
35 A subproject/trunk
36 Adding subproject/trunk
37
38 Committed revision 4.
39 A subproject/branches
40 Adding subproject/branches
41
42 Committed revision 5.
43
44 Committed revision 6.
45
46 Committed revision 7.
47 A subproject/trunk/d1
48 A subproject/trunk/d1/b
49 A subproject/trunk/d1/c
50 A subproject/trunk/d2
51 A subproject/trunk/d2/d
52 D subproject/d1
53 D subproject/d2
54 Updated to revision 7.
55 D subproject/trunk/d2/d
56 D subproject/trunk/d2
57 Sending subproject/trunk/d1/b
58 Deleting subproject/trunk/d2
59 Transmitting file data .
60 Committed revision 8.
61
62 Committed revision 9.
63 % copy a file from a past revision
64
65 Committed revision 10.
66 % copy a directory from a past revision
67
68 Committed revision 11.
69 % convert trunk and branches
1 % convert trunk and branches
70 initializing destination A-hg repository
2 initializing destination A-hg repository
71 scanning source...
3 scanning source...
72 sorting...
4 sorting...
73 converting...
5 converting...
74 9 createtrunk
6 9 createtrunk
75 8 moved1
7 8 moved1
76 7 moved1
8 7 moved1
77 6 moved2
9 6 moved2
78 5 changeb and rm d2
10 5 changeb and rm d2
79 4 changeb and rm d2
11 4 changeb and rm d2
80 3 moved1again
12 3 moved1again
81 2 moved1again
13 2 moved1again
82 1 copyfilefrompast
14 1 copyfilefrompast
83 0 copydirfrompast
15 0 copydirfrompast
84 o 9 copydirfrompast files: d2/d
16 o 9 copydirfrompast files: d2/d
85 |
17 |
86 o 8 copyfilefrompast files: d
18 o 8 copyfilefrompast files: d
87 |
19 |
88 o 7 moved1again files: d1/b d1/c
20 o 7 moved1again files: d1/b d1/c
89 |
21 |
90 | o 6 moved1again files:
22 | o 6 moved1again files:
91 | |
23 | |
92 o | 5 changeb and rm d2 files: d1/b d2/d
24 o | 5 changeb and rm d2 files: d1/b d2/d
93 | |
25 | |
94 | o 4 changeb and rm d2 files: b
26 | o 4 changeb and rm d2 files: b
95 | |
27 | |
96 o | 3 moved2 files: d2/d
28 o | 3 moved2 files: d2/d
97 | |
29 | |
98 o | 2 moved1 files: d1/b d1/c
30 o | 2 moved1 files: d1/b d1/c
99 | |
31 | |
100 | o 1 moved1 files: b c
32 | o 1 moved1 files: b c
101 |
33 |
102 o 0 createtrunk files:
34 o 0 createtrunk files:
103
35
104 default 9:
36 default 9:
105 d1 6:
37 d1 6:
@@ -1,81 +1,46
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
4
4
5 fix_path()
5 fix_path()
6 {
6 {
7 tr '\\' /
7 tr '\\' /
8 }
8 }
9
9
10 echo "[extensions]" >> $HGRCPATH
10 echo "[extensions]" >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
13
13
14 svnadmin create svn-repo
14 svnadmin create svn-repo
15 cat "$TESTDIR/svn/startrev.svndump" | svnadmin load svn-repo > /dev/null
15
16
16 svnpath=`pwd | fix_path`
17 svnpath=`pwd | fix_path`
17 # SVN wants all paths to start with a slash. Unfortunately,
18 # SVN wants all paths to start with a slash. Unfortunately,
18 # Windows ones don't. Handle that.
19 # Windows ones don't. Handle that.
19 expr $svnpath : "\/" > /dev/null
20 expr $svnpath : "\/" > /dev/null
20 if [ $? -ne 0 ]; then
21 if [ $? -ne 0 ]; then
21 svnpath='/'$svnpath
22 svnpath='/'$svnpath
22 fi
23 fi
23
24 svnurl=file://$svnpath/svn-repo
24 echo % initial svn import
25 mkdir projA
26 cd projA
27 mkdir trunk
28 mkdir branches
29 mkdir tags
30 cd ..
31
32 svnurl=file://$svnpath/svn-repo/projA
33 svn import -m "init projA" projA $svnurl | fix_path
34
35 echo % update svn repository
36 svn co $svnurl A | fix_path
37 cd A
38 echo a > trunk/a
39 echo b > trunk/b
40 svn add trunk/a trunk/b
41 svn ci -m createab
42 svn rm trunk/b
43 svn ci -m removeb
44 svn up
45 "$TESTDIR/svn-safe-append.py" a trunk/a
46 svn ci -m changeaa
47
48 echo % branch
49 svn up
50 svn copy trunk branches/branch1
51 "$TESTDIR/svn-safe-append.py" a branches/branch1/a
52 svn ci -m "branch, changeaaa"
53
54 "$TESTDIR/svn-safe-append.py" a branches/branch1/a
55 echo c > branches/branch1/c
56 svn add branches/branch1/c
57 svn ci -m "addc,changeaaaa"
58 svn up
59 cd ..
60
25
61 convert()
26 convert()
62 {
27 {
63 startrev=$1
28 startrev=$1
64 repopath=A-r$startrev-hg
29 repopath=A-r$startrev-hg
65 hg convert --config convert.svn.startrev=$startrev \
30 hg convert --config convert.svn.startrev=$startrev \
66 --config convert.svn.trunk=branches/branch1 \
31 --config convert.svn.trunk=branches/branch1 \
67 --config convert.svn.branches=" " \
32 --config convert.svn.branches=" " \
68 --config convert.svn.tags= \
33 --config convert.svn.tags= \
69 --datesort $svnurl $repopath
34 --datesort $svnurl $repopath
70 hg -R $repopath glog --template '#rev# #desc|firstline# files: #files#\n'
35 hg -R $repopath glog --template '#rev# #desc|firstline# files: #files#\n'
71 echo
36 echo
72 }
37 }
73
38
74 echo % convert before branching point
39 echo % convert before branching point
75 convert 3
40 convert 3
76 echo % convert before branching point
41 echo % convert before branching point
77 convert 4
42 convert 4
78 echo % convert at branching point
43 echo % convert at branching point
79 convert 5
44 convert 5
80 echo % convert last revision only
45 echo % convert last revision only
81 convert 6
46 convert 6
@@ -1,92 +1,54
1 % initial svn import
2 Adding projA/trunk
3 Adding projA/branches
4 Adding projA/tags
5
6 Committed revision 1.
7 % update svn repository
8 A A/trunk
9 A A/branches
10 A A/tags
11 Checked out revision 1.
12 A trunk/a
13 A trunk/b
14 Adding trunk/a
15 Adding trunk/b
16 Transmitting file data ..
17 Committed revision 2.
18 D trunk/b
19 Deleting trunk/b
20
21 Committed revision 3.
22 At revision 3.
23 Sending trunk/a
24 Transmitting file data .
25 Committed revision 4.
26 % branch
27 At revision 4.
28 A branches/branch1
29 Adding branches/branch1
30 Sending branches/branch1/a
31 Transmitting file data .
32 Committed revision 5.
33 A branches/branch1/c
34 Sending branches/branch1/a
35 Adding branches/branch1/c
36 Transmitting file data ..
37 Committed revision 6.
38 At revision 6.
39 % convert before branching point
1 % convert before branching point
40 initializing destination A-r3-hg repository
2 initializing destination A-r3-hg repository
41 scanning source...
3 scanning source...
42 sorting...
4 sorting...
43 converting...
5 converting...
44 3 removeb
6 3 removeb
45 2 changeaa
7 2 changeaa
46 1 branch, changeaaa
8 1 branch, changeaaa
47 0 addc,changeaaaa
9 0 addc,changeaaaa
48 o 3 addc,changeaaaa files: a c
10 o 3 addc,changeaaaa files: a c
49 |
11 |
50 o 2 branch, changeaaa files: a
12 o 2 branch, changeaaa files: a
51 |
13 |
52 o 1 changeaa files: a
14 o 1 changeaa files: a
53 |
15 |
54 o 0 removeb files: a
16 o 0 removeb files: a
55
17
56
18
57 % convert before branching point
19 % convert before branching point
58 initializing destination A-r4-hg repository
20 initializing destination A-r4-hg repository
59 scanning source...
21 scanning source...
60 sorting...
22 sorting...
61 converting...
23 converting...
62 2 changeaa
24 2 changeaa
63 1 branch, changeaaa
25 1 branch, changeaaa
64 0 addc,changeaaaa
26 0 addc,changeaaaa
65 o 2 addc,changeaaaa files: a c
27 o 2 addc,changeaaaa files: a c
66 |
28 |
67 o 1 branch, changeaaa files: a
29 o 1 branch, changeaaa files: a
68 |
30 |
69 o 0 changeaa files: a
31 o 0 changeaa files: a
70
32
71
33
72 % convert at branching point
34 % convert at branching point
73 initializing destination A-r5-hg repository
35 initializing destination A-r5-hg repository
74 scanning source...
36 scanning source...
75 sorting...
37 sorting...
76 converting...
38 converting...
77 1 branch, changeaaa
39 1 branch, changeaaa
78 0 addc,changeaaaa
40 0 addc,changeaaaa
79 o 1 addc,changeaaaa files: a c
41 o 1 addc,changeaaaa files: a c
80 |
42 |
81 o 0 branch, changeaaa files: a
43 o 0 branch, changeaaa files: a
82
44
83
45
84 % convert last revision only
46 % convert last revision only
85 initializing destination A-r6-hg repository
47 initializing destination A-r6-hg repository
86 scanning source...
48 scanning source...
87 sorting...
49 sorting...
88 converting...
50 converting...
89 0 addc,changeaaaa
51 0 addc,changeaaaa
90 o 0 addc,changeaaaa files: a c
52 o 0 addc,changeaaaa files: a c
91
53
92
54
@@ -1,76 +1,37
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
3 "$TESTDIR/hghave" svn svn-bindings || exit 80
4
4
5 fix_path()
5 fix_path()
6 {
6 {
7 tr '\\' /
7 tr '\\' /
8 }
8 }
9
9
10 echo "[extensions]" >> $HGRCPATH
10 echo "[extensions]" >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
11 echo "convert = " >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
12 echo "hgext.graphlog =" >> $HGRCPATH
13
13
14 svnadmin create svn-repo
14 svnadmin create svn-repo
15 cat "$TESTDIR/svn/tags.svndump" | svnadmin load svn-repo > /dev/null
15
16
16 svnpath=`pwd | fix_path`
17 svnpath=`pwd | fix_path`
17 # SVN wants all paths to start with a slash. Unfortunately,
18 # SVN wants all paths to start with a slash. Unfortunately,
18 # Windows ones don't. Handle that.
19 # Windows ones don't. Handle that.
19 expr $svnpath : "\/" > /dev/null
20 expr $svnpath : "\/" > /dev/null
20 if [ $? -ne 0 ]; then
21 if [ $? -ne 0 ]; then
21 svnpath='/'$svnpath
22 svnpath='/'$svnpath
22 fi
23 fi
23
24 svnurl=file://$svnpath/svn-repo
24 echo % initial svn import
25 mkdir projA
26 cd projA
27 mkdir trunk
28 mkdir branches
29 mkdir tags
30 mkdir unrelated
31 cd ..
32
33 svnurl=file://$svnpath/svn-repo/projA
34 svn import -m "init projA" projA $svnurl | fix_path | sort
35
36 echo % update svn repository
37 svn co $svnurl A | fix_path
38 cd A
39 echo a > trunk/a
40 svn add trunk/a
41 svn ci -m adda
42 "$TESTDIR/svn-safe-append.py" a trunk/a
43 svn ci -m changea
44 "$TESTDIR/svn-safe-append.py" a trunk/a
45 svn ci -m changea2
46 # Add an unrelated commit to test that tags are bound to the
47 # correct "from" revision and not a dummy one
48 "$TESTDIR/svn-safe-append.py" a unrelated/dummy
49 svn add unrelated/dummy
50 svn ci -m unrelatedchange
51 echo % tag current revision
52 svn up
53 svn copy trunk tags/trunk.v1
54 svn copy trunk tags/trunk.badtag
55 svn ci -m "tagging trunk.v1 trunk.badtag"
56 "$TESTDIR/svn-safe-append.py" a trunk/a
57 svn ci -m changea3
58 echo % fix the bad tag
59 # trunk.badtag should not show in converted tags
60 svn up
61 svn mv tags/trunk.badtag tags/trunk.goodtag
62 svn ci -m "fix trunk.badtag"
63 cd ..
64
25
65 echo % convert
26 echo % convert
66 hg convert --datesort $svnurl A-hg
27 hg convert --datesort $svnurl A-hg
67
28
68 cd A-hg
29 cd A-hg
69 hg glog --template '#rev# #desc|firstline# tags: #tags#\n'
30 hg glog --template '#rev# #desc|firstline# tags: #tags#\n'
70 hg tags -q
31 hg tags -q
71 cd ..
32 cd ..
72
33
73 echo % convert without tags
34 echo % convert without tags
74 hg convert --datesort --config convert.svn.tags= $svnurl A-notags-hg
35 hg convert --datesort --config convert.svn.tags= $svnurl A-notags-hg
75 hg -R A-notags-hg tags -q
36 hg -R A-notags-hg tags -q
76
37
@@ -1,84 +1,37
1 % initial svn import
2
3 Adding projA/branches
4 Adding projA/tags
5 Adding projA/trunk
6 Adding projA/unrelated
7 Committed revision 1.
8 % update svn repository
9 A A/trunk
10 A A/unrelated
11 A A/branches
12 A A/tags
13 Checked out revision 1.
14 A trunk/a
15 Adding trunk/a
16 Transmitting file data .
17 Committed revision 2.
18 Sending trunk/a
19 Transmitting file data .
20 Committed revision 3.
21 Sending trunk/a
22 Transmitting file data .
23 Committed revision 4.
24 A unrelated/dummy
25 Adding unrelated/dummy
26 Transmitting file data .
27 Committed revision 5.
28 % tag current revision
29 At revision 5.
30 A tags/trunk.v1
31 A tags/trunk.badtag
32 Adding tags/trunk.badtag
33 Adding tags/trunk.v1
34
35 Committed revision 6.
36 Sending trunk/a
37 Transmitting file data .
38 Committed revision 7.
39 % fix the bad tag
40 At revision 7.
41 A tags/trunk.goodtag
42 D tags/trunk.badtag/a
43 D tags/trunk.badtag
44 Deleting tags/trunk.badtag
45 Adding tags/trunk.goodtag
46
47 Committed revision 8.
48 % convert
1 % convert
49 initializing destination A-hg repository
2 initializing destination A-hg repository
50 scanning source...
3 scanning source...
51 sorting...
4 sorting...
52 converting...
5 converting...
53 4 init projA
6 4 init projA
54 3 adda
7 3 adda
55 2 changea
8 2 changea
56 1 changea2
9 1 changea2
57 0 changea3
10 0 changea3
58 updating tags
11 updating tags
59 o 5 update tags tags: tip
12 o 5 update tags tags: tip
60 |
13 |
61 o 4 changea3 tags:
14 o 4 changea3 tags:
62 |
15 |
63 o 3 changea2 tags: trunk.v1 trunk.goodtag
16 o 3 changea2 tags: trunk.v1 trunk.goodtag
64 |
17 |
65 o 2 changea tags:
18 o 2 changea tags:
66 |
19 |
67 o 1 adda tags:
20 o 1 adda tags:
68 |
21 |
69 o 0 init projA tags:
22 o 0 init projA tags:
70
23
71 tip
24 tip
72 trunk.v1
25 trunk.v1
73 trunk.goodtag
26 trunk.goodtag
74 % convert without tags
27 % convert without tags
75 initializing destination A-notags-hg repository
28 initializing destination A-notags-hg repository
76 scanning source...
29 scanning source...
77 sorting...
30 sorting...
78 converting...
31 converting...
79 4 init projA
32 4 init projA
80 3 adda
33 3 adda
81 2 changea
34 2 changea
82 1 changea2
35 1 changea2
83 0 changea3
36 0 changea3
84 tip
37 tip
General Comments 0
You need to be logged in to leave comments. Login now