##// END OF EJS Templates
Add option to heads to show only heads for current branch.
Eric Hopper -
r4648:8e503fa5 default
parent child Browse files
Show More
@@ -0,0 +1,108 b''
1 #!/bin/sh
2
3 hg init a
4 cd a
5 echo 'root' >root
6 hg add root
7 hg commit -d '0 0' -u test -m "Adding root node"
8 hg heads
9 echo '-------'
10 hg heads .
11
12 echo '======='
13 echo 'a' >a
14 hg add a
15 hg branch a
16 hg commit -d '1 0' -u test -m "Adding a branch"
17 hg heads
18 echo '-------'
19 hg heads .
20
21 echo '======='
22 hg update -C 0
23 echo 'b' >b
24 hg add b
25 hg branch b
26 hg commit -d '2 0' -u test -m "Adding b branch"
27 hg heads
28 echo '-------'
29 hg heads .
30
31 echo '======='
32 echo 'bh1' >bh1
33 hg add bh1
34 hg commit -d '3 0' -u test -m "Adding b branch head 1"
35 hg heads
36 echo '-------'
37 hg heads .
38
39 echo '======='
40 hg update -C 2
41 echo 'bh2' >bh2
42 hg add bh2
43 hg commit -d '4 0' -u test -m "Adding b branch head 2"
44 hg heads
45 echo '-------'
46 hg heads .
47
48 echo '======='
49 hg update -C 2
50 echo 'bh3' >bh3
51 hg add bh3
52 hg commit -d '5 0' -u test -m "Adding b branch head 3"
53 hg heads
54 echo '-------'
55 hg heads .
56
57 echo '======='
58 hg merge 4
59 hg commit -d '6 0' -u test -m "Merging b branch head 2 and b branch head 3"
60 hg heads
61 echo '-------'
62 hg heads .
63
64 echo '======='
65 echo 'c' >c
66 hg add c
67 hg branch c
68 hg commit -d '7 0' -u test -m "Adding c branch"
69 hg heads
70 echo '-------'
71 hg heads .
72
73 echo '======='
74 hg heads -r 3 .
75 echo $?
76 echo '-------'
77 hg heads -r 2 .
78 echo $?
79 echo '-------'
80 hg update -C 4
81 echo $?
82 echo '-------'
83 hg heads -r 3 .
84 echo $?
85 echo '-------'
86 hg heads -r 2 .
87 echo $?
88 echo '-------'
89 hg heads -r 7 .
90 echo $?
91
92 echo '======='
93 for i in 0 1 2 3 4 5 6 7; do
94 hg update -C "$i"
95 hg heads
96 echo '-------'
97 hg heads .
98 echo '-------'
99 done
100
101 echo '======='
102 for i in a b c z; do
103 hg heads "$i"
104 echo '-------'
105 done
106
107 echo '======='
108 hg heads 0 1 2 3 4 5 6 7
This diff has been collapsed as it changes many lines, (608 lines changed) Show them Hide them
@@ -0,0 +1,608 b''
1 changeset: 0:19709c5a4e75
2 tag: tip
3 user: test
4 date: Thu Jan 01 00:00:00 1970 +0000
5 summary: Adding root node
6
7 -------
8 changeset: 0:19709c5a4e75
9 tag: tip
10 user: test
11 date: Thu Jan 01 00:00:00 1970 +0000
12 summary: Adding root node
13
14 =======
15 marked working directory as branch a
16 changeset: 1:dd6b440dd85a
17 branch: a
18 tag: tip
19 user: test
20 date: Thu Jan 01 00:00:01 1970 +0000
21 summary: Adding a branch
22
23 -------
24 changeset: 1:dd6b440dd85a
25 branch: a
26 tag: tip
27 user: test
28 date: Thu Jan 01 00:00:01 1970 +0000
29 summary: Adding a branch
30
31 =======
32 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
33 marked working directory as branch b
34 changeset: 2:ac22033332d1
35 branch: b
36 tag: tip
37 parent: 0:19709c5a4e75
38 user: test
39 date: Thu Jan 01 00:00:02 1970 +0000
40 summary: Adding b branch
41
42 changeset: 1:dd6b440dd85a
43 branch: a
44 user: test
45 date: Thu Jan 01 00:00:01 1970 +0000
46 summary: Adding a branch
47
48 -------
49 changeset: 2:ac22033332d1
50 branch: b
51 tag: tip
52 parent: 0:19709c5a4e75
53 user: test
54 date: Thu Jan 01 00:00:02 1970 +0000
55 summary: Adding b branch
56
57 =======
58 changeset: 3:aee39cd168d0
59 branch: b
60 tag: tip
61 user: test
62 date: Thu Jan 01 00:00:03 1970 +0000
63 summary: Adding b branch head 1
64
65 changeset: 1:dd6b440dd85a
66 branch: a
67 user: test
68 date: Thu Jan 01 00:00:01 1970 +0000
69 summary: Adding a branch
70
71 -------
72 changeset: 3:aee39cd168d0
73 branch: b
74 tag: tip
75 user: test
76 date: Thu Jan 01 00:00:03 1970 +0000
77 summary: Adding b branch head 1
78
79 =======
80 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
81 changeset: 4:22df7444f7c1
82 branch: b
83 tag: tip
84 parent: 2:ac22033332d1
85 user: test
86 date: Thu Jan 01 00:00:04 1970 +0000
87 summary: Adding b branch head 2
88
89 changeset: 3:aee39cd168d0
90 branch: b
91 user: test
92 date: Thu Jan 01 00:00:03 1970 +0000
93 summary: Adding b branch head 1
94
95 changeset: 1:dd6b440dd85a
96 branch: a
97 user: test
98 date: Thu Jan 01 00:00:01 1970 +0000
99 summary: Adding a branch
100
101 -------
102 changeset: 4:22df7444f7c1
103 branch: b
104 tag: tip
105 parent: 2:ac22033332d1
106 user: test
107 date: Thu Jan 01 00:00:04 1970 +0000
108 summary: Adding b branch head 2
109
110 changeset: 3:aee39cd168d0
111 branch: b
112 user: test
113 date: Thu Jan 01 00:00:03 1970 +0000
114 summary: Adding b branch head 1
115
116 =======
117 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
118 changeset: 5:0d57af4f9583
119 branch: b
120 tag: tip
121 parent: 2:ac22033332d1
122 user: test
123 date: Thu Jan 01 00:00:05 1970 +0000
124 summary: Adding b branch head 3
125
126 changeset: 4:22df7444f7c1
127 branch: b
128 parent: 2:ac22033332d1
129 user: test
130 date: Thu Jan 01 00:00:04 1970 +0000
131 summary: Adding b branch head 2
132
133 changeset: 3:aee39cd168d0
134 branch: b
135 user: test
136 date: Thu Jan 01 00:00:03 1970 +0000
137 summary: Adding b branch head 1
138
139 changeset: 1:dd6b440dd85a
140 branch: a
141 user: test
142 date: Thu Jan 01 00:00:01 1970 +0000
143 summary: Adding a branch
144
145 -------
146 changeset: 5:0d57af4f9583
147 branch: b
148 tag: tip
149 parent: 2:ac22033332d1
150 user: test
151 date: Thu Jan 01 00:00:05 1970 +0000
152 summary: Adding b branch head 3
153
154 changeset: 4:22df7444f7c1
155 branch: b
156 parent: 2:ac22033332d1
157 user: test
158 date: Thu Jan 01 00:00:04 1970 +0000
159 summary: Adding b branch head 2
160
161 changeset: 3:aee39cd168d0
162 branch: b
163 user: test
164 date: Thu Jan 01 00:00:03 1970 +0000
165 summary: Adding b branch head 1
166
167 =======
168 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 (branch merge, don't forget to commit)
170 changeset: 6:00432327d822
171 branch: b
172 tag: tip
173 parent: 5:0d57af4f9583
174 parent: 4:22df7444f7c1
175 user: test
176 date: Thu Jan 01 00:00:06 1970 +0000
177 summary: Merging b branch head 2 and b branch head 3
178
179 changeset: 3:aee39cd168d0
180 branch: b
181 user: test
182 date: Thu Jan 01 00:00:03 1970 +0000
183 summary: Adding b branch head 1
184
185 changeset: 1:dd6b440dd85a
186 branch: a
187 user: test
188 date: Thu Jan 01 00:00:01 1970 +0000
189 summary: Adding a branch
190
191 -------
192 changeset: 6:00432327d822
193 branch: b
194 tag: tip
195 parent: 5:0d57af4f9583
196 parent: 4:22df7444f7c1
197 user: test
198 date: Thu Jan 01 00:00:06 1970 +0000
199 summary: Merging b branch head 2 and b branch head 3
200
201 changeset: 3:aee39cd168d0
202 branch: b
203 user: test
204 date: Thu Jan 01 00:00:03 1970 +0000
205 summary: Adding b branch head 1
206
207 =======
208 marked working directory as branch c
209 changeset: 7:9fb091bb9835
210 branch: c
211 tag: tip
212 user: test
213 date: Thu Jan 01 00:00:07 1970 +0000
214 summary: Adding c branch
215
216 changeset: 3:aee39cd168d0
217 branch: b
218 user: test
219 date: Thu Jan 01 00:00:03 1970 +0000
220 summary: Adding b branch head 1
221
222 changeset: 1:dd6b440dd85a
223 branch: a
224 user: test
225 date: Thu Jan 01 00:00:01 1970 +0000
226 summary: Adding a branch
227
228 -------
229 changeset: 7:9fb091bb9835
230 branch: c
231 tag: tip
232 user: test
233 date: Thu Jan 01 00:00:07 1970 +0000
234 summary: Adding c branch
235
236 =======
237 No changes on branch c (the branch of revision .) are reachable from revision 3
238 1
239 -------
240 changeset: 7:9fb091bb9835
241 branch: c
242 tag: tip
243 user: test
244 date: Thu Jan 01 00:00:07 1970 +0000
245 summary: Adding c branch
246
247 0
248 -------
249 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
250 0
251 -------
252 changeset: 3:aee39cd168d0
253 branch: b
254 user: test
255 date: Thu Jan 01 00:00:03 1970 +0000
256 summary: Adding b branch head 1
257
258 0
259 -------
260 changeset: 3:aee39cd168d0
261 branch: b
262 user: test
263 date: Thu Jan 01 00:00:03 1970 +0000
264 summary: Adding b branch head 1
265
266 changeset: 6:00432327d822
267 branch: b
268 parent: 5:0d57af4f9583
269 parent: 4:22df7444f7c1
270 user: test
271 date: Thu Jan 01 00:00:06 1970 +0000
272 summary: Merging b branch head 2 and b branch head 3
273
274 0
275 -------
276 No changes on branch b (the branch of revision .) are reachable from revision 7
277 1
278 =======
279 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
280 changeset: 7:9fb091bb9835
281 branch: c
282 tag: tip
283 user: test
284 date: Thu Jan 01 00:00:07 1970 +0000
285 summary: Adding c branch
286
287 changeset: 3:aee39cd168d0
288 branch: b
289 user: test
290 date: Thu Jan 01 00:00:03 1970 +0000
291 summary: Adding b branch head 1
292
293 changeset: 1:dd6b440dd85a
294 branch: a
295 user: test
296 date: Thu Jan 01 00:00:01 1970 +0000
297 summary: Adding a branch
298
299 -------
300 changeset: 0:19709c5a4e75
301 user: test
302 date: Thu Jan 01 00:00:00 1970 +0000
303 summary: Adding root node
304
305 -------
306 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
307 changeset: 7:9fb091bb9835
308 branch: c
309 tag: tip
310 user: test
311 date: Thu Jan 01 00:00:07 1970 +0000
312 summary: Adding c branch
313
314 changeset: 3:aee39cd168d0
315 branch: b
316 user: test
317 date: Thu Jan 01 00:00:03 1970 +0000
318 summary: Adding b branch head 1
319
320 changeset: 1:dd6b440dd85a
321 branch: a
322 user: test
323 date: Thu Jan 01 00:00:01 1970 +0000
324 summary: Adding a branch
325
326 -------
327 changeset: 1:dd6b440dd85a
328 branch: a
329 user: test
330 date: Thu Jan 01 00:00:01 1970 +0000
331 summary: Adding a branch
332
333 -------
334 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
335 changeset: 7:9fb091bb9835
336 branch: c
337 tag: tip
338 user: test
339 date: Thu Jan 01 00:00:07 1970 +0000
340 summary: Adding c branch
341
342 changeset: 3:aee39cd168d0
343 branch: b
344 user: test
345 date: Thu Jan 01 00:00:03 1970 +0000
346 summary: Adding b branch head 1
347
348 changeset: 1:dd6b440dd85a
349 branch: a
350 user: test
351 date: Thu Jan 01 00:00:01 1970 +0000
352 summary: Adding a branch
353
354 -------
355 changeset: 6:00432327d822
356 branch: b
357 parent: 5:0d57af4f9583
358 parent: 4:22df7444f7c1
359 user: test
360 date: Thu Jan 01 00:00:06 1970 +0000
361 summary: Merging b branch head 2 and b branch head 3
362
363 changeset: 3:aee39cd168d0
364 branch: b
365 user: test
366 date: Thu Jan 01 00:00:03 1970 +0000
367 summary: Adding b branch head 1
368
369 -------
370 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 changeset: 7:9fb091bb9835
372 branch: c
373 tag: tip
374 user: test
375 date: Thu Jan 01 00:00:07 1970 +0000
376 summary: Adding c branch
377
378 changeset: 3:aee39cd168d0
379 branch: b
380 user: test
381 date: Thu Jan 01 00:00:03 1970 +0000
382 summary: Adding b branch head 1
383
384 changeset: 1:dd6b440dd85a
385 branch: a
386 user: test
387 date: Thu Jan 01 00:00:01 1970 +0000
388 summary: Adding a branch
389
390 -------
391 changeset: 6:00432327d822
392 branch: b
393 parent: 5:0d57af4f9583
394 parent: 4:22df7444f7c1
395 user: test
396 date: Thu Jan 01 00:00:06 1970 +0000
397 summary: Merging b branch head 2 and b branch head 3
398
399 changeset: 3:aee39cd168d0
400 branch: b
401 user: test
402 date: Thu Jan 01 00:00:03 1970 +0000
403 summary: Adding b branch head 1
404
405 -------
406 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
407 changeset: 7:9fb091bb9835
408 branch: c
409 tag: tip
410 user: test
411 date: Thu Jan 01 00:00:07 1970 +0000
412 summary: Adding c branch
413
414 changeset: 3:aee39cd168d0
415 branch: b
416 user: test
417 date: Thu Jan 01 00:00:03 1970 +0000
418 summary: Adding b branch head 1
419
420 changeset: 1:dd6b440dd85a
421 branch: a
422 user: test
423 date: Thu Jan 01 00:00:01 1970 +0000
424 summary: Adding a branch
425
426 -------
427 changeset: 6:00432327d822
428 branch: b
429 parent: 5:0d57af4f9583
430 parent: 4:22df7444f7c1
431 user: test
432 date: Thu Jan 01 00:00:06 1970 +0000
433 summary: Merging b branch head 2 and b branch head 3
434
435 changeset: 3:aee39cd168d0
436 branch: b
437 user: test
438 date: Thu Jan 01 00:00:03 1970 +0000
439 summary: Adding b branch head 1
440
441 -------
442 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
443 changeset: 7:9fb091bb9835
444 branch: c
445 tag: tip
446 user: test
447 date: Thu Jan 01 00:00:07 1970 +0000
448 summary: Adding c branch
449
450 changeset: 3:aee39cd168d0
451 branch: b
452 user: test
453 date: Thu Jan 01 00:00:03 1970 +0000
454 summary: Adding b branch head 1
455
456 changeset: 1:dd6b440dd85a
457 branch: a
458 user: test
459 date: Thu Jan 01 00:00:01 1970 +0000
460 summary: Adding a branch
461
462 -------
463 changeset: 6:00432327d822
464 branch: b
465 parent: 5:0d57af4f9583
466 parent: 4:22df7444f7c1
467 user: test
468 date: Thu Jan 01 00:00:06 1970 +0000
469 summary: Merging b branch head 2 and b branch head 3
470
471 changeset: 3:aee39cd168d0
472 branch: b
473 user: test
474 date: Thu Jan 01 00:00:03 1970 +0000
475 summary: Adding b branch head 1
476
477 -------
478 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
479 changeset: 7:9fb091bb9835
480 branch: c
481 tag: tip
482 user: test
483 date: Thu Jan 01 00:00:07 1970 +0000
484 summary: Adding c branch
485
486 changeset: 3:aee39cd168d0
487 branch: b
488 user: test
489 date: Thu Jan 01 00:00:03 1970 +0000
490 summary: Adding b branch head 1
491
492 changeset: 1:dd6b440dd85a
493 branch: a
494 user: test
495 date: Thu Jan 01 00:00:01 1970 +0000
496 summary: Adding a branch
497
498 -------
499 changeset: 6:00432327d822
500 branch: b
501 parent: 5:0d57af4f9583
502 parent: 4:22df7444f7c1
503 user: test
504 date: Thu Jan 01 00:00:06 1970 +0000
505 summary: Merging b branch head 2 and b branch head 3
506
507 changeset: 3:aee39cd168d0
508 branch: b
509 user: test
510 date: Thu Jan 01 00:00:03 1970 +0000
511 summary: Adding b branch head 1
512
513 -------
514 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
515 changeset: 7:9fb091bb9835
516 branch: c
517 tag: tip
518 user: test
519 date: Thu Jan 01 00:00:07 1970 +0000
520 summary: Adding c branch
521
522 changeset: 3:aee39cd168d0
523 branch: b
524 user: test
525 date: Thu Jan 01 00:00:03 1970 +0000
526 summary: Adding b branch head 1
527
528 changeset: 1:dd6b440dd85a
529 branch: a
530 user: test
531 date: Thu Jan 01 00:00:01 1970 +0000
532 summary: Adding a branch
533
534 -------
535 changeset: 7:9fb091bb9835
536 branch: c
537 tag: tip
538 user: test
539 date: Thu Jan 01 00:00:07 1970 +0000
540 summary: Adding c branch
541
542 -------
543 =======
544 changeset: 1:dd6b440dd85a
545 branch: a
546 user: test
547 date: Thu Jan 01 00:00:01 1970 +0000
548 summary: Adding a branch
549
550 -------
551 changeset: 6:00432327d822
552 branch: b
553 parent: 5:0d57af4f9583
554 parent: 4:22df7444f7c1
555 user: test
556 date: Thu Jan 01 00:00:06 1970 +0000
557 summary: Merging b branch head 2 and b branch head 3
558
559 changeset: 3:aee39cd168d0
560 branch: b
561 user: test
562 date: Thu Jan 01 00:00:03 1970 +0000
563 summary: Adding b branch head 1
564
565 -------
566 changeset: 7:9fb091bb9835
567 branch: c
568 tag: tip
569 user: test
570 date: Thu Jan 01 00:00:07 1970 +0000
571 summary: Adding c branch
572
573 -------
574 abort: unknown revision 'z'!
575 -------
576 =======
577 changeset: 0:19709c5a4e75
578 user: test
579 date: Thu Jan 01 00:00:00 1970 +0000
580 summary: Adding root node
581
582 changeset: 1:dd6b440dd85a
583 branch: a
584 user: test
585 date: Thu Jan 01 00:00:01 1970 +0000
586 summary: Adding a branch
587
588 changeset: 6:00432327d822
589 branch: b
590 parent: 5:0d57af4f9583
591 parent: 4:22df7444f7c1
592 user: test
593 date: Thu Jan 01 00:00:06 1970 +0000
594 summary: Merging b branch head 2 and b branch head 3
595
596 changeset: 3:aee39cd168d0
597 branch: b
598 user: test
599 date: Thu Jan 01 00:00:03 1970 +0000
600 summary: Adding b branch head 1
601
602 changeset: 7:9fb091bb9835
603 branch: c
604 tag: tip
605 user: test
606 date: Thu Jan 01 00:00:07 1970 +0000
607 summary: Adding c branch
608
@@ -1,3012 +1,3044 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import demandimport; demandimport.enable()
9 9 from node import *
10 10 from i18n import _
11 11 import bisect, os, re, sys, urllib, shlex, stat
12 12 import ui, hg, util, revlog, bundlerepo, extensions
13 13 import difflib, patch, time, help, mdiff, tempfile
14 14 import errno, version, socket
15 15 import archival, changegroup, cmdutil, hgweb.server, sshserver
16 16
17 17 # Commands start here, listed alphabetically
18 18
19 19 def add(ui, repo, *pats, **opts):
20 20 """add the specified files on the next commit
21 21
22 22 Schedule files to be version controlled and added to the repository.
23 23
24 24 The files will be added to the repository at the next commit. To
25 25 undo an add before that, see hg revert.
26 26
27 27 If no names are given, add all files in the repository.
28 28 """
29 29
30 30 names = []
31 31 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
32 32 if exact:
33 33 if ui.verbose:
34 34 ui.status(_('adding %s\n') % rel)
35 35 names.append(abs)
36 36 elif repo.dirstate.state(abs) == '?':
37 37 ui.status(_('adding %s\n') % rel)
38 38 names.append(abs)
39 39 if not opts.get('dry_run'):
40 40 repo.add(names)
41 41
42 42 def addremove(ui, repo, *pats, **opts):
43 43 """add all new files, delete all missing files
44 44
45 45 Add all new files and remove all missing files from the repository.
46 46
47 47 New files are ignored if they match any of the patterns in .hgignore. As
48 48 with add, these changes take effect at the next commit.
49 49
50 50 Use the -s option to detect renamed files. With a parameter > 0,
51 51 this compares every removed file with every added file and records
52 52 those similar enough as renames. This option takes a percentage
53 53 between 0 (disabled) and 100 (files must be identical) as its
54 54 parameter. Detecting renamed files this way can be expensive.
55 55 """
56 56 sim = float(opts.get('similarity') or 0)
57 57 if sim < 0 or sim > 100:
58 58 raise util.Abort(_('similarity must be between 0 and 100'))
59 59 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
60 60
61 61 def annotate(ui, repo, *pats, **opts):
62 62 """show changeset information per file line
63 63
64 64 List changes in files, showing the revision id responsible for each line
65 65
66 66 This command is useful to discover who did a change or when a change took
67 67 place.
68 68
69 69 Without the -a option, annotate will avoid processing files it
70 70 detects as binary. With -a, annotate will generate an annotation
71 71 anyway, probably with undesirable results.
72 72 """
73 73 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
74 74
75 75 if not pats:
76 76 raise util.Abort(_('at least one file name or pattern required'))
77 77
78 78 opmap = [['user', lambda x: ui.shortuser(x.user())],
79 79 ['number', lambda x: str(x.rev())],
80 80 ['changeset', lambda x: short(x.node())],
81 81 ['date', getdate], ['follow', lambda x: x.path()]]
82 82 if (not opts['user'] and not opts['changeset'] and not opts['date']
83 83 and not opts['follow']):
84 84 opts['number'] = 1
85 85
86 86 ctx = repo.changectx(opts['rev'])
87 87
88 88 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
89 89 node=ctx.node()):
90 90 fctx = ctx.filectx(abs)
91 91 if not opts['text'] and util.binary(fctx.data()):
92 92 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
93 93 continue
94 94
95 95 lines = fctx.annotate(follow=opts.get('follow'))
96 96 pieces = []
97 97
98 98 for o, f in opmap:
99 99 if opts[o]:
100 100 l = [f(n) for n, dummy in lines]
101 101 if l:
102 102 m = max(map(len, l))
103 103 pieces.append(["%*s" % (m, x) for x in l])
104 104
105 105 if pieces:
106 106 for p, l in zip(zip(*pieces), lines):
107 107 ui.write("%s: %s" % (" ".join(p), l[1]))
108 108
109 109 def archive(ui, repo, dest, **opts):
110 110 '''create unversioned archive of a repository revision
111 111
112 112 By default, the revision used is the parent of the working
113 113 directory; use "-r" to specify a different revision.
114 114
115 115 To specify the type of archive to create, use "-t". Valid
116 116 types are:
117 117
118 118 "files" (default): a directory full of files
119 119 "tar": tar archive, uncompressed
120 120 "tbz2": tar archive, compressed using bzip2
121 121 "tgz": tar archive, compressed using gzip
122 122 "uzip": zip archive, uncompressed
123 123 "zip": zip archive, compressed using deflate
124 124
125 125 The exact name of the destination archive or directory is given
126 126 using a format string; see "hg help export" for details.
127 127
128 128 Each member added to an archive file has a directory prefix
129 129 prepended. Use "-p" to specify a format string for the prefix.
130 130 The default is the basename of the archive, with suffixes removed.
131 131 '''
132 132
133 133 node = repo.changectx(opts['rev']).node()
134 134 dest = cmdutil.make_filename(repo, dest, node)
135 135 if os.path.realpath(dest) == repo.root:
136 136 raise util.Abort(_('repository root cannot be destination'))
137 137 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
138 138 kind = opts.get('type') or 'files'
139 139 prefix = opts['prefix']
140 140 if dest == '-':
141 141 if kind == 'files':
142 142 raise util.Abort(_('cannot archive plain files to stdout'))
143 143 dest = sys.stdout
144 144 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
145 145 prefix = cmdutil.make_filename(repo, prefix, node)
146 146 archival.archive(repo, dest, node, kind, not opts['no_decode'],
147 147 matchfn, prefix)
148 148
149 149 def backout(ui, repo, node=None, rev=None, **opts):
150 150 '''reverse effect of earlier changeset
151 151
152 152 Commit the backed out changes as a new changeset. The new
153 153 changeset is a child of the backed out changeset.
154 154
155 155 If you back out a changeset other than the tip, a new head is
156 156 created. This head is the parent of the working directory. If
157 157 you back out an old changeset, your working directory will appear
158 158 old after the backout. You should merge the backout changeset
159 159 with another head.
160 160
161 161 The --merge option remembers the parent of the working directory
162 162 before starting the backout, then merges the new head with that
163 163 changeset afterwards. This saves you from doing the merge by
164 164 hand. The result of this merge is not committed, as for a normal
165 165 merge.'''
166 166 if rev and node:
167 167 raise util.Abort(_("please specify just one revision"))
168 168
169 169 if not rev:
170 170 rev = node
171 171
172 172 cmdutil.bail_if_changed(repo)
173 173 op1, op2 = repo.dirstate.parents()
174 174 if op2 != nullid:
175 175 raise util.Abort(_('outstanding uncommitted merge'))
176 176 node = repo.lookup(rev)
177 177 p1, p2 = repo.changelog.parents(node)
178 178 if p1 == nullid:
179 179 raise util.Abort(_('cannot back out a change with no parents'))
180 180 if p2 != nullid:
181 181 if not opts['parent']:
182 182 raise util.Abort(_('cannot back out a merge changeset without '
183 183 '--parent'))
184 184 p = repo.lookup(opts['parent'])
185 185 if p not in (p1, p2):
186 186 raise util.Abort(_('%s is not a parent of %s') %
187 187 (short(p), short(node)))
188 188 parent = p
189 189 else:
190 190 if opts['parent']:
191 191 raise util.Abort(_('cannot use --parent on non-merge changeset'))
192 192 parent = p1
193 193 hg.clean(repo, node, show_stats=False)
194 194 revert_opts = opts.copy()
195 195 revert_opts['date'] = None
196 196 revert_opts['all'] = True
197 197 revert_opts['rev'] = hex(parent)
198 198 revert(ui, repo, **revert_opts)
199 199 commit_opts = opts.copy()
200 200 commit_opts['addremove'] = False
201 201 if not commit_opts['message'] and not commit_opts['logfile']:
202 202 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
203 203 commit_opts['force_editor'] = True
204 204 commit(ui, repo, **commit_opts)
205 205 def nice(node):
206 206 return '%d:%s' % (repo.changelog.rev(node), short(node))
207 207 ui.status(_('changeset %s backs out changeset %s\n') %
208 208 (nice(repo.changelog.tip()), nice(node)))
209 209 if op1 != node:
210 210 if opts['merge']:
211 211 ui.status(_('merging with changeset %s\n') % nice(op1))
212 212 hg.merge(repo, hex(op1))
213 213 else:
214 214 ui.status(_('the backout changeset is a new head - '
215 215 'do not forget to merge\n'))
216 216 ui.status(_('(use "backout --merge" '
217 217 'if you want to auto-merge)\n'))
218 218
219 219 def branch(ui, repo, label=None, **opts):
220 220 """set or show the current branch name
221 221
222 222 With no argument, show the current branch name. With one argument,
223 223 set the working directory branch name (the branch does not exist in
224 224 the repository until the next commit).
225 225
226 226 Unless --force is specified, branch will not let you set a
227 227 branch name that shadows an existing branch.
228 228 """
229 229
230 230 if label:
231 231 if not opts.get('force') and label in repo.branchtags():
232 232 if label not in [p.branch() for p in repo.workingctx().parents()]:
233 233 raise util.Abort(_('a branch of the same name already exists'
234 234 ' (use --force to override)'))
235 235 repo.dirstate.setbranch(util.fromlocal(label))
236 236 ui.status(_('marked working directory as branch %s\n') % label)
237 237 else:
238 238 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
239 239
240 240 def branches(ui, repo):
241 241 """list repository named branches
242 242
243 243 List the repository's named branches.
244 244 """
245 245 b = repo.branchtags()
246 246 l = [(-repo.changelog.rev(n), n, t) for t, n in b.items()]
247 247 l.sort()
248 248 for r, n, t in l:
249 249 hexfunc = ui.debugflag and hex or short
250 250 if ui.quiet:
251 251 ui.write("%s\n" % t)
252 252 else:
253 253 spaces = " " * (30 - util.locallen(t))
254 254 ui.write("%s%s %s:%s\n" % (t, spaces, -r, hexfunc(n)))
255 255
256 256 def bundle(ui, repo, fname, dest=None, **opts):
257 257 """create a changegroup file
258 258
259 259 Generate a compressed changegroup file collecting changesets not
260 260 found in the other repository.
261 261
262 262 If no destination repository is specified the destination is assumed
263 263 to have all the nodes specified by one or more --base parameters.
264 264
265 265 The bundle file can then be transferred using conventional means and
266 266 applied to another repository with the unbundle or pull command.
267 267 This is useful when direct push and pull are not available or when
268 268 exporting an entire repository is undesirable.
269 269
270 270 Applying bundles preserves all changeset contents including
271 271 permissions, copy/rename information, and revision history.
272 272 """
273 273 revs = opts.get('rev') or None
274 274 if revs:
275 275 revs = [repo.lookup(rev) for rev in revs]
276 276 base = opts.get('base')
277 277 if base:
278 278 if dest:
279 279 raise util.Abort(_("--base is incompatible with specifiying "
280 280 "a destination"))
281 281 base = [repo.lookup(rev) for rev in base]
282 282 # create the right base
283 283 # XXX: nodesbetween / changegroup* should be "fixed" instead
284 284 o = []
285 285 has = {nullid: None}
286 286 for n in base:
287 287 has.update(repo.changelog.reachable(n))
288 288 if revs:
289 289 visit = list(revs)
290 290 else:
291 291 visit = repo.changelog.heads()
292 292 seen = {}
293 293 while visit:
294 294 n = visit.pop(0)
295 295 parents = [p for p in repo.changelog.parents(n) if p not in has]
296 296 if len(parents) == 0:
297 297 o.insert(0, n)
298 298 else:
299 299 for p in parents:
300 300 if p not in seen:
301 301 seen[p] = 1
302 302 visit.append(p)
303 303 else:
304 304 cmdutil.setremoteconfig(ui, opts)
305 305 dest, revs = cmdutil.parseurl(
306 306 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
307 307 other = hg.repository(ui, dest)
308 308 o = repo.findoutgoing(other, force=opts['force'])
309 309
310 310 if revs:
311 311 cg = repo.changegroupsubset(o, revs, 'bundle')
312 312 else:
313 313 cg = repo.changegroup(o, 'bundle')
314 314 changegroup.writebundle(cg, fname, "HG10BZ")
315 315
316 316 def cat(ui, repo, file1, *pats, **opts):
317 317 """output the current or given revision of files
318 318
319 319 Print the specified files as they were at the given revision.
320 320 If no revision is given, the parent of the working directory is used,
321 321 or tip if no revision is checked out.
322 322
323 323 Output may be to a file, in which case the name of the file is
324 324 given using a format string. The formatting rules are the same as
325 325 for the export command, with the following additions:
326 326
327 327 %s basename of file being printed
328 328 %d dirname of file being printed, or '.' if in repo root
329 329 %p root-relative path name of file being printed
330 330 """
331 331 ctx = repo.changectx(opts['rev'])
332 332 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
333 333 ctx.node()):
334 334 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
335 335 fp.write(ctx.filectx(abs).data())
336 336
337 337 def clone(ui, source, dest=None, **opts):
338 338 """make a copy of an existing repository
339 339
340 340 Create a copy of an existing repository in a new directory.
341 341
342 342 If no destination directory name is specified, it defaults to the
343 343 basename of the source.
344 344
345 345 The location of the source is added to the new repository's
346 346 .hg/hgrc file, as the default to be used for future pulls.
347 347
348 348 For efficiency, hardlinks are used for cloning whenever the source
349 349 and destination are on the same filesystem (note this applies only
350 350 to the repository data, not to the checked out files). Some
351 351 filesystems, such as AFS, implement hardlinking incorrectly, but
352 352 do not report errors. In these cases, use the --pull option to
353 353 avoid hardlinking.
354 354
355 355 You can safely clone repositories and checked out files using full
356 356 hardlinks with
357 357
358 358 $ cp -al REPO REPOCLONE
359 359
360 360 which is the fastest way to clone. However, the operation is not
361 361 atomic (making sure REPO is not modified during the operation is
362 362 up to you) and you have to make sure your editor breaks hardlinks
363 363 (Emacs and most Linux Kernel tools do so).
364 364
365 365 If you use the -r option to clone up to a specific revision, no
366 366 subsequent revisions will be present in the cloned repository.
367 367 This option implies --pull, even on local repositories.
368 368
369 369 See pull for valid source format details.
370 370
371 371 It is possible to specify an ssh:// URL as the destination, but no
372 372 .hg/hgrc and working directory will be created on the remote side.
373 373 Look at the help text for the pull command for important details
374 374 about ssh:// URLs.
375 375 """
376 376 cmdutil.setremoteconfig(ui, opts)
377 377 hg.clone(ui, source, dest,
378 378 pull=opts['pull'],
379 379 stream=opts['uncompressed'],
380 380 rev=opts['rev'],
381 381 update=not opts['noupdate'])
382 382
383 383 def commit(ui, repo, *pats, **opts):
384 384 """commit the specified files or all outstanding changes
385 385
386 386 Commit changes to the given files into the repository.
387 387
388 388 If a list of files is omitted, all changes reported by "hg status"
389 389 will be committed.
390 390
391 391 If no commit message is specified, the editor configured in your hgrc
392 392 or in the EDITOR environment variable is started to enter a message.
393 393 """
394 394 message = cmdutil.logmessage(opts)
395 395
396 396 if opts['addremove']:
397 397 cmdutil.addremove(repo, pats, opts)
398 398 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
399 399 if pats:
400 400 status = repo.status(files=fns, match=match)
401 401 modified, added, removed, deleted, unknown = status[:5]
402 402 files = modified + added + removed
403 403 slist = None
404 404 for f in fns:
405 405 if f == '.':
406 406 continue
407 407 if f not in files:
408 408 rf = repo.wjoin(f)
409 409 try:
410 410 mode = os.lstat(rf)[stat.ST_MODE]
411 411 except OSError:
412 412 raise util.Abort(_("file %s not found!") % rf)
413 413 if stat.S_ISDIR(mode):
414 414 name = f + '/'
415 415 if slist is None:
416 416 slist = list(files)
417 417 slist.sort()
418 418 i = bisect.bisect(slist, name)
419 419 if i >= len(slist) or not slist[i].startswith(name):
420 420 raise util.Abort(_("no match under directory %s!")
421 421 % rf)
422 422 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
423 423 raise util.Abort(_("can't commit %s: "
424 424 "unsupported file type!") % rf)
425 425 elif repo.dirstate.state(f) == '?':
426 426 raise util.Abort(_("file %s not tracked!") % rf)
427 427 else:
428 428 files = []
429 429 try:
430 430 repo.commit(files, message, opts['user'], opts['date'], match,
431 431 force_editor=opts.get('force_editor'))
432 432 except ValueError, inst:
433 433 raise util.Abort(str(inst))
434 434
435 435 def docopy(ui, repo, pats, opts, wlock):
436 436 # called with the repo lock held
437 437 #
438 438 # hgsep => pathname that uses "/" to separate directories
439 439 # ossep => pathname that uses os.sep to separate directories
440 440 cwd = repo.getcwd()
441 441 errors = 0
442 442 copied = []
443 443 targets = {}
444 444
445 445 # abs: hgsep
446 446 # rel: ossep
447 447 # return: hgsep
448 448 def okaytocopy(abs, rel, exact):
449 449 reasons = {'?': _('is not managed'),
450 450 'a': _('has been marked for add'),
451 451 'r': _('has been marked for remove')}
452 452 state = repo.dirstate.state(abs)
453 453 reason = reasons.get(state)
454 454 if reason:
455 455 if state == 'a':
456 456 origsrc = repo.dirstate.copied(abs)
457 457 if origsrc is not None:
458 458 return origsrc
459 459 if exact:
460 460 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
461 461 else:
462 462 return abs
463 463
464 464 # origsrc: hgsep
465 465 # abssrc: hgsep
466 466 # relsrc: ossep
467 467 # otarget: ossep
468 468 def copy(origsrc, abssrc, relsrc, otarget, exact):
469 469 abstarget = util.canonpath(repo.root, cwd, otarget)
470 470 reltarget = repo.pathto(abstarget, cwd)
471 471 prevsrc = targets.get(abstarget)
472 472 src = repo.wjoin(abssrc)
473 473 target = repo.wjoin(abstarget)
474 474 if prevsrc is not None:
475 475 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
476 476 (reltarget, repo.pathto(abssrc, cwd),
477 477 repo.pathto(prevsrc, cwd)))
478 478 return
479 479 if (not opts['after'] and os.path.exists(target) or
480 480 opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
481 481 if not opts['force']:
482 482 ui.warn(_('%s: not overwriting - file exists\n') %
483 483 reltarget)
484 484 return
485 485 if not opts['after'] and not opts.get('dry_run'):
486 486 os.unlink(target)
487 487 if opts['after']:
488 488 if not os.path.exists(target):
489 489 return
490 490 else:
491 491 targetdir = os.path.dirname(target) or '.'
492 492 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
493 493 os.makedirs(targetdir)
494 494 try:
495 495 restore = repo.dirstate.state(abstarget) == 'r'
496 496 if restore and not opts.get('dry_run'):
497 497 repo.undelete([abstarget], wlock)
498 498 try:
499 499 if not opts.get('dry_run'):
500 500 util.copyfile(src, target)
501 501 restore = False
502 502 finally:
503 503 if restore:
504 504 repo.remove([abstarget], wlock=wlock)
505 505 except IOError, inst:
506 506 if inst.errno == errno.ENOENT:
507 507 ui.warn(_('%s: deleted in working copy\n') % relsrc)
508 508 else:
509 509 ui.warn(_('%s: cannot copy - %s\n') %
510 510 (relsrc, inst.strerror))
511 511 errors += 1
512 512 return
513 513 if ui.verbose or not exact:
514 514 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
515 515 targets[abstarget] = abssrc
516 516 if abstarget != origsrc and not opts.get('dry_run'):
517 517 repo.copy(origsrc, abstarget, wlock)
518 518 copied.append((abssrc, relsrc, exact))
519 519
520 520 # pat: ossep
521 521 # dest ossep
522 522 # srcs: list of (hgsep, hgsep, ossep, bool)
523 523 # return: function that takes hgsep and returns ossep
524 524 def targetpathfn(pat, dest, srcs):
525 525 if os.path.isdir(pat):
526 526 abspfx = util.canonpath(repo.root, cwd, pat)
527 527 abspfx = util.localpath(abspfx)
528 528 if destdirexists:
529 529 striplen = len(os.path.split(abspfx)[0])
530 530 else:
531 531 striplen = len(abspfx)
532 532 if striplen:
533 533 striplen += len(os.sep)
534 534 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
535 535 elif destdirexists:
536 536 res = lambda p: os.path.join(dest,
537 537 os.path.basename(util.localpath(p)))
538 538 else:
539 539 res = lambda p: dest
540 540 return res
541 541
542 542 # pat: ossep
543 543 # dest ossep
544 544 # srcs: list of (hgsep, hgsep, ossep, bool)
545 545 # return: function that takes hgsep and returns ossep
546 546 def targetpathafterfn(pat, dest, srcs):
547 547 if util.patkind(pat, None)[0]:
548 548 # a mercurial pattern
549 549 res = lambda p: os.path.join(dest,
550 550 os.path.basename(util.localpath(p)))
551 551 else:
552 552 abspfx = util.canonpath(repo.root, cwd, pat)
553 553 if len(abspfx) < len(srcs[0][0]):
554 554 # A directory. Either the target path contains the last
555 555 # component of the source path or it does not.
556 556 def evalpath(striplen):
557 557 score = 0
558 558 for s in srcs:
559 559 t = os.path.join(dest, util.localpath(s[0])[striplen:])
560 560 if os.path.exists(t):
561 561 score += 1
562 562 return score
563 563
564 564 abspfx = util.localpath(abspfx)
565 565 striplen = len(abspfx)
566 566 if striplen:
567 567 striplen += len(os.sep)
568 568 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
569 569 score = evalpath(striplen)
570 570 striplen1 = len(os.path.split(abspfx)[0])
571 571 if striplen1:
572 572 striplen1 += len(os.sep)
573 573 if evalpath(striplen1) > score:
574 574 striplen = striplen1
575 575 res = lambda p: os.path.join(dest,
576 576 util.localpath(p)[striplen:])
577 577 else:
578 578 # a file
579 579 if destdirexists:
580 580 res = lambda p: os.path.join(dest,
581 581 os.path.basename(util.localpath(p)))
582 582 else:
583 583 res = lambda p: dest
584 584 return res
585 585
586 586
587 587 pats = util.expand_glob(pats)
588 588 if not pats:
589 589 raise util.Abort(_('no source or destination specified'))
590 590 if len(pats) == 1:
591 591 raise util.Abort(_('no destination specified'))
592 592 dest = pats.pop()
593 593 destdirexists = os.path.isdir(dest)
594 594 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
595 595 raise util.Abort(_('with multiple sources, destination must be an '
596 596 'existing directory'))
597 597 if opts['after']:
598 598 tfn = targetpathafterfn
599 599 else:
600 600 tfn = targetpathfn
601 601 copylist = []
602 602 for pat in pats:
603 603 srcs = []
604 604 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
605 605 globbed=True):
606 606 origsrc = okaytocopy(abssrc, relsrc, exact)
607 607 if origsrc:
608 608 srcs.append((origsrc, abssrc, relsrc, exact))
609 609 if not srcs:
610 610 continue
611 611 copylist.append((tfn(pat, dest, srcs), srcs))
612 612 if not copylist:
613 613 raise util.Abort(_('no files to copy'))
614 614
615 615 for targetpath, srcs in copylist:
616 616 for origsrc, abssrc, relsrc, exact in srcs:
617 617 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
618 618
619 619 if errors:
620 620 ui.warn(_('(consider using --after)\n'))
621 621 return errors, copied
622 622
623 623 def copy(ui, repo, *pats, **opts):
624 624 """mark files as copied for the next commit
625 625
626 626 Mark dest as having copies of source files. If dest is a
627 627 directory, copies are put in that directory. If dest is a file,
628 628 there can only be one source.
629 629
630 630 By default, this command copies the contents of files as they
631 631 stand in the working directory. If invoked with --after, the
632 632 operation is recorded, but no copying is performed.
633 633
634 634 This command takes effect in the next commit. To undo a copy
635 635 before that, see hg revert.
636 636 """
637 637 wlock = repo.wlock(0)
638 638 errs, copied = docopy(ui, repo, pats, opts, wlock)
639 639 return errs
640 640
641 641 def debugancestor(ui, index, rev1, rev2):
642 642 """find the ancestor revision of two revisions in a given index"""
643 643 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
644 644 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
645 645 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
646 646
647 647 def debugcomplete(ui, cmd='', **opts):
648 648 """returns the completion list associated with the given command"""
649 649
650 650 if opts['options']:
651 651 options = []
652 652 otables = [globalopts]
653 653 if cmd:
654 654 aliases, entry = cmdutil.findcmd(ui, cmd)
655 655 otables.append(entry[1])
656 656 for t in otables:
657 657 for o in t:
658 658 if o[0]:
659 659 options.append('-%s' % o[0])
660 660 options.append('--%s' % o[1])
661 661 ui.write("%s\n" % "\n".join(options))
662 662 return
663 663
664 664 clist = cmdutil.findpossible(ui, cmd).keys()
665 665 clist.sort()
666 666 ui.write("%s\n" % "\n".join(clist))
667 667
668 668 def debugrebuildstate(ui, repo, rev=""):
669 669 """rebuild the dirstate as it would look like for the given revision"""
670 670 if rev == "":
671 671 rev = repo.changelog.tip()
672 672 ctx = repo.changectx(rev)
673 673 files = ctx.manifest()
674 674 wlock = repo.wlock()
675 675 repo.dirstate.rebuild(rev, files)
676 676
677 677 def debugcheckstate(ui, repo):
678 678 """validate the correctness of the current dirstate"""
679 679 parent1, parent2 = repo.dirstate.parents()
680 680 dc = repo.dirstate
681 681 m1 = repo.changectx(parent1).manifest()
682 682 m2 = repo.changectx(parent2).manifest()
683 683 errors = 0
684 684 for f in dc:
685 685 state = repo.dirstate.state(f)
686 686 if state in "nr" and f not in m1:
687 687 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
688 688 errors += 1
689 689 if state in "a" and f in m1:
690 690 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
691 691 errors += 1
692 692 if state in "m" and f not in m1 and f not in m2:
693 693 ui.warn(_("%s in state %s, but not in either manifest\n") %
694 694 (f, state))
695 695 errors += 1
696 696 for f in m1:
697 697 state = repo.dirstate.state(f)
698 698 if state not in "nrm":
699 699 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
700 700 errors += 1
701 701 if errors:
702 702 error = _(".hg/dirstate inconsistent with current parent's manifest")
703 703 raise util.Abort(error)
704 704
705 705 def showconfig(ui, repo, *values, **opts):
706 706 """show combined config settings from all hgrc files
707 707
708 708 With no args, print names and values of all config items.
709 709
710 710 With one arg of the form section.name, print just the value of
711 711 that config item.
712 712
713 713 With multiple args, print names and values of all config items
714 714 with matching section names."""
715 715
716 716 untrusted = bool(opts.get('untrusted'))
717 717 if values:
718 718 if len([v for v in values if '.' in v]) > 1:
719 719 raise util.Abort(_('only one config item permitted'))
720 720 for section, name, value in ui.walkconfig(untrusted=untrusted):
721 721 sectname = section + '.' + name
722 722 if values:
723 723 for v in values:
724 724 if v == section:
725 725 ui.write('%s=%s\n' % (sectname, value))
726 726 elif v == sectname:
727 727 ui.write(value, '\n')
728 728 else:
729 729 ui.write('%s=%s\n' % (sectname, value))
730 730
731 731 def debugsetparents(ui, repo, rev1, rev2=None):
732 732 """manually set the parents of the current working directory
733 733
734 734 This is useful for writing repository conversion tools, but should
735 735 be used with care.
736 736 """
737 737
738 738 if not rev2:
739 739 rev2 = hex(nullid)
740 740
741 741 wlock = repo.wlock()
742 742 try:
743 743 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
744 744 finally:
745 745 wlock.release()
746 746
747 747 def debugstate(ui, repo):
748 748 """show the contents of the current dirstate"""
749 749 dc = repo.dirstate
750 750 for file_ in dc:
751 751 if dc[file_][3] == -1:
752 752 # Pad or slice to locale representation
753 753 locale_len = len(time.strftime("%x %X", time.localtime(0)))
754 754 timestr = 'unset'
755 755 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
756 756 else:
757 757 timestr = time.strftime("%x %X", time.localtime(dc[file_][3]))
758 758 ui.write("%c %3o %10d %s %s\n"
759 759 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
760 760 timestr, file_))
761 761 for f in repo.dirstate.copies():
762 762 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
763 763
764 764 def debugdata(ui, file_, rev):
765 765 """dump the contents of a data file revision"""
766 766 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
767 767 try:
768 768 ui.write(r.revision(r.lookup(rev)))
769 769 except KeyError:
770 770 raise util.Abort(_('invalid revision identifier %s') % rev)
771 771
772 772 def debugdate(ui, date, range=None, **opts):
773 773 """parse and display a date"""
774 774 if opts["extended"]:
775 775 d = util.parsedate(date, util.extendeddateformats)
776 776 else:
777 777 d = util.parsedate(date)
778 778 ui.write("internal: %s %s\n" % d)
779 779 ui.write("standard: %s\n" % util.datestr(d))
780 780 if range:
781 781 m = util.matchdate(range)
782 782 ui.write("match: %s\n" % m(d[0]))
783 783
784 784 def debugindex(ui, file_):
785 785 """dump the contents of an index file"""
786 786 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
787 787 ui.write(" rev offset length base linkrev" +
788 788 " nodeid p1 p2\n")
789 789 for i in xrange(r.count()):
790 790 node = r.node(i)
791 791 pp = r.parents(node)
792 792 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
793 793 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
794 794 short(node), short(pp[0]), short(pp[1])))
795 795
796 796 def debugindexdot(ui, file_):
797 797 """dump an index DAG as a .dot file"""
798 798 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
799 799 ui.write("digraph G {\n")
800 800 for i in xrange(r.count()):
801 801 node = r.node(i)
802 802 pp = r.parents(node)
803 803 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
804 804 if pp[1] != nullid:
805 805 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
806 806 ui.write("}\n")
807 807
808 808 def debuginstall(ui):
809 809 '''test Mercurial installation'''
810 810
811 811 def writetemp(contents):
812 812 (fd, name) = tempfile.mkstemp()
813 813 f = os.fdopen(fd, "wb")
814 814 f.write(contents)
815 815 f.close()
816 816 return name
817 817
818 818 problems = 0
819 819
820 820 # encoding
821 821 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
822 822 try:
823 823 util.fromlocal("test")
824 824 except util.Abort, inst:
825 825 ui.write(" %s\n" % inst)
826 826 ui.write(_(" (check that your locale is properly set)\n"))
827 827 problems += 1
828 828
829 829 # compiled modules
830 830 ui.status(_("Checking extensions...\n"))
831 831 try:
832 832 import bdiff, mpatch, base85
833 833 except Exception, inst:
834 834 ui.write(" %s\n" % inst)
835 835 ui.write(_(" One or more extensions could not be found"))
836 836 ui.write(_(" (check that you compiled the extensions)\n"))
837 837 problems += 1
838 838
839 839 # templates
840 840 ui.status(_("Checking templates...\n"))
841 841 try:
842 842 import templater
843 843 t = templater.templater(templater.templatepath("map-cmdline.default"))
844 844 except Exception, inst:
845 845 ui.write(" %s\n" % inst)
846 846 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
847 847 problems += 1
848 848
849 849 # patch
850 850 ui.status(_("Checking patch...\n"))
851 851 patcher = ui.config('ui', 'patch')
852 852 patcher = ((patcher and util.find_exe(patcher)) or
853 853 util.find_exe('gpatch') or
854 854 util.find_exe('patch'))
855 855 if not patcher:
856 856 ui.write(_(" Can't find patch or gpatch in PATH\n"))
857 857 ui.write(_(" (specify a patch utility in your .hgrc file)\n"))
858 858 problems += 1
859 859 else:
860 860 # actually attempt a patch here
861 861 a = "1\n2\n3\n4\n"
862 862 b = "1\n2\n3\ninsert\n4\n"
863 863 fa = writetemp(a)
864 864 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
865 865 fd = writetemp(d)
866 866
867 867 files = {}
868 868 try:
869 869 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
870 870 except util.Abort, e:
871 871 ui.write(_(" patch call failed:\n"))
872 872 ui.write(" " + str(e) + "\n")
873 873 problems += 1
874 874 else:
875 875 if list(files) != [os.path.basename(fa)]:
876 876 ui.write(_(" unexpected patch output!"))
877 877 ui.write(_(" (you may have an incompatible version of patch)\n"))
878 878 problems += 1
879 879 a = file(fa).read()
880 880 if a != b:
881 881 ui.write(_(" patch test failed!"))
882 882 ui.write(_(" (you may have an incompatible version of patch)\n"))
883 883 problems += 1
884 884
885 885 os.unlink(fa)
886 886 os.unlink(fd)
887 887
888 888 # merge helper
889 889 ui.status(_("Checking merge helper...\n"))
890 890 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
891 891 or "hgmerge")
892 892 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
893 893 if not cmdpath:
894 894 if cmd == 'hgmerge':
895 895 ui.write(_(" No merge helper set and can't find default"
896 896 " hgmerge script in PATH\n"))
897 897 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
898 898 else:
899 899 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
900 900 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
901 901 problems += 1
902 902 else:
903 903 # actually attempt a patch here
904 904 fa = writetemp("1\n2\n3\n4\n")
905 905 fl = writetemp("1\n2\n3\ninsert\n4\n")
906 906 fr = writetemp("begin\n1\n2\n3\n4\n")
907 907 r = os.system('%s %s %s %s' % (cmd, fl, fa, fr))
908 908 if r:
909 909 ui.write(_(" got unexpected merge error %d!") % r)
910 910 problems += 1
911 911 m = file(fl).read()
912 912 if m != "begin\n1\n2\n3\ninsert\n4\n":
913 913 ui.write(_(" got unexpected merge results!") % r)
914 914 ui.write(_(" (your merge helper may have the"
915 915 " wrong argument order)\n"))
916 916 ui.write(m)
917 917 os.unlink(fa)
918 918 os.unlink(fl)
919 919 os.unlink(fr)
920 920
921 921 # editor
922 922 ui.status(_("Checking commit editor...\n"))
923 923 editor = (os.environ.get("HGEDITOR") or
924 924 ui.config("ui", "editor") or
925 925 os.environ.get("EDITOR", "vi"))
926 926 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
927 927 if not cmdpath:
928 928 if editor == 'vi':
929 929 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
930 930 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
931 931 else:
932 932 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
933 933 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
934 934 problems += 1
935 935
936 936 # check username
937 937 ui.status(_("Checking username...\n"))
938 938 user = os.environ.get("HGUSER")
939 939 if user is None:
940 940 user = ui.config("ui", "username")
941 941 if user is None:
942 942 user = os.environ.get("EMAIL")
943 943 if not user:
944 944 ui.warn(" ")
945 945 ui.username()
946 946 ui.write(_(" (specify a username in your .hgrc file)\n"))
947 947
948 948 if not problems:
949 949 ui.status(_("No problems detected\n"))
950 950 else:
951 951 ui.write(_("%s problems detected,"
952 952 " please check your install!\n") % problems)
953 953
954 954 return problems
955 955
956 956 def debugrename(ui, repo, file1, *pats, **opts):
957 957 """dump rename information"""
958 958
959 959 ctx = repo.changectx(opts.get('rev', 'tip'))
960 960 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
961 961 ctx.node()):
962 962 m = ctx.filectx(abs).renamed()
963 963 if m:
964 964 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
965 965 else:
966 966 ui.write(_("%s not renamed\n") % rel)
967 967
968 968 def debugwalk(ui, repo, *pats, **opts):
969 969 """show how files match on given patterns"""
970 970 items = list(cmdutil.walk(repo, pats, opts))
971 971 if not items:
972 972 return
973 973 fmt = '%%s %%-%ds %%-%ds %%s' % (
974 974 max([len(abs) for (src, abs, rel, exact) in items]),
975 975 max([len(rel) for (src, abs, rel, exact) in items]))
976 976 for src, abs, rel, exact in items:
977 977 line = fmt % (src, abs, rel, exact and 'exact' or '')
978 978 ui.write("%s\n" % line.rstrip())
979 979
980 980 def diff(ui, repo, *pats, **opts):
981 981 """diff repository (or selected files)
982 982
983 983 Show differences between revisions for the specified files.
984 984
985 985 Differences between files are shown using the unified diff format.
986 986
987 987 NOTE: diff may generate unexpected results for merges, as it will
988 988 default to comparing against the working directory's first parent
989 989 changeset if no revisions are specified.
990 990
991 991 When two revision arguments are given, then changes are shown
992 992 between those revisions. If only one revision is specified then
993 993 that revision is compared to the working directory, and, when no
994 994 revisions are specified, the working directory files are compared
995 995 to its parent.
996 996
997 997 Without the -a option, diff will avoid generating diffs of files
998 998 it detects as binary. With -a, diff will generate a diff anyway,
999 999 probably with undesirable results.
1000 1000 """
1001 1001 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1002 1002
1003 1003 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1004 1004
1005 1005 patch.diff(repo, node1, node2, fns, match=matchfn,
1006 1006 opts=patch.diffopts(ui, opts))
1007 1007
1008 1008 def export(ui, repo, *changesets, **opts):
1009 1009 """dump the header and diffs for one or more changesets
1010 1010
1011 1011 Print the changeset header and diffs for one or more revisions.
1012 1012
1013 1013 The information shown in the changeset header is: author,
1014 1014 changeset hash, parent(s) and commit comment.
1015 1015
1016 1016 NOTE: export may generate unexpected diff output for merge changesets,
1017 1017 as it will compare the merge changeset against its first parent only.
1018 1018
1019 1019 Output may be to a file, in which case the name of the file is
1020 1020 given using a format string. The formatting rules are as follows:
1021 1021
1022 1022 %% literal "%" character
1023 1023 %H changeset hash (40 bytes of hexadecimal)
1024 1024 %N number of patches being generated
1025 1025 %R changeset revision number
1026 1026 %b basename of the exporting repository
1027 1027 %h short-form changeset hash (12 bytes of hexadecimal)
1028 1028 %n zero-padded sequence number, starting at 1
1029 1029 %r zero-padded changeset revision number
1030 1030
1031 1031 Without the -a option, export will avoid generating diffs of files
1032 1032 it detects as binary. With -a, export will generate a diff anyway,
1033 1033 probably with undesirable results.
1034 1034
1035 1035 With the --switch-parent option, the diff will be against the second
1036 1036 parent. It can be useful to review a merge.
1037 1037 """
1038 1038 if not changesets:
1039 1039 raise util.Abort(_("export requires at least one changeset"))
1040 1040 revs = cmdutil.revrange(repo, changesets)
1041 1041 if len(revs) > 1:
1042 1042 ui.note(_('exporting patches:\n'))
1043 1043 else:
1044 1044 ui.note(_('exporting patch:\n'))
1045 1045 patch.export(repo, revs, template=opts['output'],
1046 1046 switch_parent=opts['switch_parent'],
1047 1047 opts=patch.diffopts(ui, opts))
1048 1048
1049 1049 def grep(ui, repo, pattern, *pats, **opts):
1050 1050 """search for a pattern in specified files and revisions
1051 1051
1052 1052 Search revisions of files for a regular expression.
1053 1053
1054 1054 This command behaves differently than Unix grep. It only accepts
1055 1055 Python/Perl regexps. It searches repository history, not the
1056 1056 working directory. It always prints the revision number in which
1057 1057 a match appears.
1058 1058
1059 1059 By default, grep only prints output for the first revision of a
1060 1060 file in which it finds a match. To get it to print every revision
1061 1061 that contains a change in match status ("-" for a match that
1062 1062 becomes a non-match, or "+" for a non-match that becomes a match),
1063 1063 use the --all flag.
1064 1064 """
1065 1065 reflags = 0
1066 1066 if opts['ignore_case']:
1067 1067 reflags |= re.I
1068 1068 regexp = re.compile(pattern, reflags)
1069 1069 sep, eol = ':', '\n'
1070 1070 if opts['print0']:
1071 1071 sep = eol = '\0'
1072 1072
1073 1073 fcache = {}
1074 1074 def getfile(fn):
1075 1075 if fn not in fcache:
1076 1076 fcache[fn] = repo.file(fn)
1077 1077 return fcache[fn]
1078 1078
1079 1079 def matchlines(body):
1080 1080 begin = 0
1081 1081 linenum = 0
1082 1082 while True:
1083 1083 match = regexp.search(body, begin)
1084 1084 if not match:
1085 1085 break
1086 1086 mstart, mend = match.span()
1087 1087 linenum += body.count('\n', begin, mstart) + 1
1088 1088 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1089 1089 lend = body.find('\n', mend)
1090 1090 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1091 1091 begin = lend + 1
1092 1092
1093 1093 class linestate(object):
1094 1094 def __init__(self, line, linenum, colstart, colend):
1095 1095 self.line = line
1096 1096 self.linenum = linenum
1097 1097 self.colstart = colstart
1098 1098 self.colend = colend
1099 1099
1100 1100 def __eq__(self, other):
1101 1101 return self.line == other.line
1102 1102
1103 1103 matches = {}
1104 1104 copies = {}
1105 1105 def grepbody(fn, rev, body):
1106 1106 matches[rev].setdefault(fn, [])
1107 1107 m = matches[rev][fn]
1108 1108 for lnum, cstart, cend, line in matchlines(body):
1109 1109 s = linestate(line, lnum, cstart, cend)
1110 1110 m.append(s)
1111 1111
1112 1112 def difflinestates(a, b):
1113 1113 sm = difflib.SequenceMatcher(None, a, b)
1114 1114 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1115 1115 if tag == 'insert':
1116 1116 for i in xrange(blo, bhi):
1117 1117 yield ('+', b[i])
1118 1118 elif tag == 'delete':
1119 1119 for i in xrange(alo, ahi):
1120 1120 yield ('-', a[i])
1121 1121 elif tag == 'replace':
1122 1122 for i in xrange(alo, ahi):
1123 1123 yield ('-', a[i])
1124 1124 for i in xrange(blo, bhi):
1125 1125 yield ('+', b[i])
1126 1126
1127 1127 prev = {}
1128 1128 def display(fn, rev, states, prevstates):
1129 1129 found = False
1130 1130 filerevmatches = {}
1131 1131 r = prev.get(fn, -1)
1132 1132 if opts['all']:
1133 1133 iter = difflinestates(states, prevstates)
1134 1134 else:
1135 1135 iter = [('', l) for l in prevstates]
1136 1136 for change, l in iter:
1137 1137 cols = [fn, str(r)]
1138 1138 if opts['line_number']:
1139 1139 cols.append(str(l.linenum))
1140 1140 if opts['all']:
1141 1141 cols.append(change)
1142 1142 if opts['user']:
1143 1143 cols.append(ui.shortuser(get(r)[1]))
1144 1144 if opts['files_with_matches']:
1145 1145 c = (fn, r)
1146 1146 if c in filerevmatches:
1147 1147 continue
1148 1148 filerevmatches[c] = 1
1149 1149 else:
1150 1150 cols.append(l.line)
1151 1151 ui.write(sep.join(cols), eol)
1152 1152 found = True
1153 1153 return found
1154 1154
1155 1155 fstate = {}
1156 1156 skip = {}
1157 1157 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1158 1158 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1159 1159 found = False
1160 1160 follow = opts.get('follow')
1161 1161 for st, rev, fns in changeiter:
1162 1162 if st == 'window':
1163 1163 matches.clear()
1164 1164 elif st == 'add':
1165 1165 mf = repo.changectx(rev).manifest()
1166 1166 matches[rev] = {}
1167 1167 for fn in fns:
1168 1168 if fn in skip:
1169 1169 continue
1170 1170 fstate.setdefault(fn, {})
1171 1171 try:
1172 1172 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1173 1173 if follow:
1174 1174 copied = getfile(fn).renamed(mf[fn])
1175 1175 if copied:
1176 1176 copies.setdefault(rev, {})[fn] = copied[0]
1177 1177 except KeyError:
1178 1178 pass
1179 1179 elif st == 'iter':
1180 1180 states = matches[rev].items()
1181 1181 states.sort()
1182 1182 for fn, m in states:
1183 1183 copy = copies.get(rev, {}).get(fn)
1184 1184 if fn in skip:
1185 1185 if copy:
1186 1186 skip[copy] = True
1187 1187 continue
1188 1188 if fn in prev or fstate[fn]:
1189 1189 r = display(fn, rev, m, fstate[fn])
1190 1190 found = found or r
1191 1191 if r and not opts['all']:
1192 1192 skip[fn] = True
1193 1193 if copy:
1194 1194 skip[copy] = True
1195 1195 fstate[fn] = m
1196 1196 if copy:
1197 1197 fstate[copy] = m
1198 1198 prev[fn] = rev
1199 1199
1200 1200 fstate = fstate.items()
1201 1201 fstate.sort()
1202 1202 for fn, state in fstate:
1203 1203 if fn in skip:
1204 1204 continue
1205 1205 if fn not in copies.get(prev[fn], {}):
1206 1206 found = display(fn, rev, {}, state) or found
1207 1207 return (not found and 1) or 0
1208 1208
1209 def heads(ui, repo, **opts):
1210 """show current repository heads
1211
1212 Show all repository head changesets.
1213
1214 Repository "heads" are changesets that don't have children
1209 def heads(ui, repo, *branchrevs, **opts):
1210 """show current repository heads or show branch heads
1211
1212 With no arguments, show all repository head changesets.
1213
1214 If branch or revisions names are given this will show the heads of
1215 the specified branches or the branches those revisions are tagged
1216 with.
1217
1218 Repository "heads" are changesets that don't have child
1215 1219 changesets. They are where development generally takes place and
1216 1220 are the usual targets for update and merge operations.
1221
1222 Branch heads are changesets that have a given branch tag, but have
1223 no child changesets with that tag. They are usually where
1224 development on the given branch takes place.
1217 1225 """
1218 1226 if opts['rev']:
1219 heads = repo.heads(repo.lookup(opts['rev']))
1227 start = repo.lookup(opts['rev'])
1228 else:
1229 start = None
1230 if not branchrevs:
1231 # Assume we're looking repo-wide heads if no revs were specified.
1232 heads = repo.heads(start)
1220 1233 else:
1221 heads = repo.heads()
1234 heads = []
1235 visitedset = set()
1236 displayer = cmdutil.show_changeset(ui, repo, opts)
1237 for branchrev in branchrevs:
1238 branch = repo.changectx(branchrev).branch()
1239 if branch in visitedset:
1240 continue
1241 visitedset.add(branch)
1242 bheads = repo.branchheads(branch, start)
1243 if not bheads:
1244 if branch != branchrev:
1245 ui.warn(_("no changes on branch %s containing %s are "
1246 "reachable from %s\n")
1247 % (branch, branchrev, opts['rev']))
1248 else:
1249 ui.warn(_("no changes on branch %s are reachable from %s\n")
1250 % (branch, opts['rev']))
1251 heads.extend(bheads)
1252 if not heads:
1253 return 1
1222 1254 displayer = cmdutil.show_changeset(ui, repo, opts)
1223 1255 for n in heads:
1224 1256 displayer.show(changenode=n)
1225 1257
1226 1258 def help_(ui, name=None, with_version=False):
1227 1259 """show help for a command, extension, or list of commands
1228 1260
1229 1261 With no arguments, print a list of commands and short help.
1230 1262
1231 1263 Given a command name, print help for that command.
1232 1264
1233 1265 Given an extension name, print help for that extension, and the
1234 1266 commands it provides."""
1235 1267 option_lists = []
1236 1268
1237 1269 def addglobalopts(aliases):
1238 1270 if ui.verbose:
1239 1271 option_lists.append((_("global options:"), globalopts))
1240 1272 if name == 'shortlist':
1241 1273 option_lists.append((_('use "hg help" for the full list '
1242 1274 'of commands'), ()))
1243 1275 else:
1244 1276 if name == 'shortlist':
1245 1277 msg = _('use "hg help" for the full list of commands '
1246 1278 'or "hg -v" for details')
1247 1279 elif aliases:
1248 1280 msg = _('use "hg -v help%s" to show aliases and '
1249 1281 'global options') % (name and " " + name or "")
1250 1282 else:
1251 1283 msg = _('use "hg -v help %s" to show global options') % name
1252 1284 option_lists.append((msg, ()))
1253 1285
1254 1286 def helpcmd(name):
1255 1287 if with_version:
1256 1288 version_(ui)
1257 1289 ui.write('\n')
1258 1290 aliases, i = cmdutil.findcmd(ui, name)
1259 1291 # synopsis
1260 1292 ui.write("%s\n\n" % i[2])
1261 1293
1262 1294 # description
1263 1295 doc = i[0].__doc__
1264 1296 if not doc:
1265 1297 doc = _("(No help text available)")
1266 1298 if ui.quiet:
1267 1299 doc = doc.splitlines(0)[0]
1268 1300 ui.write("%s\n" % doc.rstrip())
1269 1301
1270 1302 if not ui.quiet:
1271 1303 # aliases
1272 1304 if len(aliases) > 1:
1273 1305 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1274 1306
1275 1307 # options
1276 1308 if i[1]:
1277 1309 option_lists.append((_("options:\n"), i[1]))
1278 1310
1279 1311 addglobalopts(False)
1280 1312
1281 1313 def helplist(select=None):
1282 1314 h = {}
1283 1315 cmds = {}
1284 1316 for c, e in table.items():
1285 1317 f = c.split("|", 1)[0]
1286 1318 if select and not select(f):
1287 1319 continue
1288 1320 if name == "shortlist" and not f.startswith("^"):
1289 1321 continue
1290 1322 f = f.lstrip("^")
1291 1323 if not ui.debugflag and f.startswith("debug"):
1292 1324 continue
1293 1325 doc = e[0].__doc__
1294 1326 if not doc:
1295 1327 doc = _("(No help text available)")
1296 1328 h[f] = doc.splitlines(0)[0].rstrip()
1297 1329 cmds[f] = c.lstrip("^")
1298 1330
1299 1331 fns = h.keys()
1300 1332 fns.sort()
1301 1333 m = max(map(len, fns))
1302 1334 for f in fns:
1303 1335 if ui.verbose:
1304 1336 commands = cmds[f].replace("|",", ")
1305 1337 ui.write(" %s:\n %s\n"%(commands, h[f]))
1306 1338 else:
1307 1339 ui.write(' %-*s %s\n' % (m, f, h[f]))
1308 1340
1309 1341 if not ui.quiet:
1310 1342 addglobalopts(True)
1311 1343
1312 1344 def helptopic(name):
1313 1345 v = None
1314 1346 for i in help.helptable:
1315 1347 l = i.split('|')
1316 1348 if name in l:
1317 1349 v = i
1318 1350 header = l[-1]
1319 1351 if not v:
1320 1352 raise cmdutil.UnknownCommand(name)
1321 1353
1322 1354 # description
1323 1355 doc = help.helptable[v]
1324 1356 if not doc:
1325 1357 doc = _("(No help text available)")
1326 1358 if callable(doc):
1327 1359 doc = doc()
1328 1360
1329 1361 ui.write("%s\n" % header)
1330 1362 ui.write("%s\n" % doc.rstrip())
1331 1363
1332 1364 def helpext(name):
1333 1365 try:
1334 1366 mod = extensions.find(name)
1335 1367 except KeyError:
1336 1368 raise cmdutil.UnknownCommand(name)
1337 1369
1338 1370 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1339 1371 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1340 1372 for d in doc[1:]:
1341 1373 ui.write(d, '\n')
1342 1374
1343 1375 ui.status('\n')
1344 1376
1345 1377 try:
1346 1378 ct = mod.cmdtable
1347 1379 except AttributeError:
1348 1380 ui.status(_('no commands defined\n'))
1349 1381 return
1350 1382
1351 1383 ui.status(_('list of commands:\n\n'))
1352 1384 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1353 1385 helplist(modcmds.has_key)
1354 1386
1355 1387 if name and name != 'shortlist':
1356 1388 i = None
1357 1389 for f in (helpcmd, helptopic, helpext):
1358 1390 try:
1359 1391 f(name)
1360 1392 i = None
1361 1393 break
1362 1394 except cmdutil.UnknownCommand, inst:
1363 1395 i = inst
1364 1396 if i:
1365 1397 raise i
1366 1398
1367 1399 else:
1368 1400 # program name
1369 1401 if ui.verbose or with_version:
1370 1402 version_(ui)
1371 1403 else:
1372 1404 ui.status(_("Mercurial Distributed SCM\n"))
1373 1405 ui.status('\n')
1374 1406
1375 1407 # list of commands
1376 1408 if name == "shortlist":
1377 1409 ui.status(_('basic commands:\n\n'))
1378 1410 else:
1379 1411 ui.status(_('list of commands:\n\n'))
1380 1412
1381 1413 helplist()
1382 1414
1383 1415 # list all option lists
1384 1416 opt_output = []
1385 1417 for title, options in option_lists:
1386 1418 opt_output.append(("\n%s" % title, None))
1387 1419 for shortopt, longopt, default, desc in options:
1388 1420 if "DEPRECATED" in desc and not ui.verbose: continue
1389 1421 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1390 1422 longopt and " --%s" % longopt),
1391 1423 "%s%s" % (desc,
1392 1424 default
1393 1425 and _(" (default: %s)") % default
1394 1426 or "")))
1395 1427
1396 1428 if opt_output:
1397 1429 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1398 1430 for first, second in opt_output:
1399 1431 if second:
1400 1432 ui.write(" %-*s %s\n" % (opts_len, first, second))
1401 1433 else:
1402 1434 ui.write("%s\n" % first)
1403 1435
1404 1436 def identify(ui, repo):
1405 1437 """print information about the working copy
1406 1438
1407 1439 Print a short summary of the current state of the repo.
1408 1440
1409 1441 This summary identifies the repository state using one or two parent
1410 1442 hash identifiers, followed by a "+" if there are uncommitted changes
1411 1443 in the working directory, followed by a list of tags for this revision.
1412 1444 """
1413 1445 parents = [p for p in repo.dirstate.parents() if p != nullid]
1414 1446 if not parents:
1415 1447 ui.write(_("unknown\n"))
1416 1448 return
1417 1449
1418 1450 hexfunc = ui.debugflag and hex or short
1419 1451 modified, added, removed, deleted = repo.status()[:4]
1420 1452 output = ["%s%s" %
1421 1453 ('+'.join([hexfunc(parent) for parent in parents]),
1422 1454 (modified or added or removed or deleted) and "+" or "")]
1423 1455
1424 1456 if not ui.quiet:
1425 1457
1426 1458 branch = util.tolocal(repo.workingctx().branch())
1427 1459 if branch != 'default':
1428 1460 output.append("(%s)" % branch)
1429 1461
1430 1462 # multiple tags for a single parent separated by '/'
1431 1463 parenttags = ['/'.join(tags)
1432 1464 for tags in map(repo.nodetags, parents) if tags]
1433 1465 # tags for multiple parents separated by ' + '
1434 1466 if parenttags:
1435 1467 output.append(' + '.join(parenttags))
1436 1468
1437 1469 ui.write("%s\n" % ' '.join(output))
1438 1470
1439 1471 def import_(ui, repo, patch1, *patches, **opts):
1440 1472 """import an ordered set of patches
1441 1473
1442 1474 Import a list of patches and commit them individually.
1443 1475
1444 1476 If there are outstanding changes in the working directory, import
1445 1477 will abort unless given the -f flag.
1446 1478
1447 1479 You can import a patch straight from a mail message. Even patches
1448 1480 as attachments work (body part must be type text/plain or
1449 1481 text/x-patch to be used). From and Subject headers of email
1450 1482 message are used as default committer and commit message. All
1451 1483 text/plain body parts before first diff are added to commit
1452 1484 message.
1453 1485
1454 1486 If the imported patch was generated by hg export, user and description
1455 1487 from patch override values from message headers and body. Values
1456 1488 given on command line with -m and -u override these.
1457 1489
1458 1490 If --exact is specified, import will set the working directory
1459 1491 to the parent of each patch before applying it, and will abort
1460 1492 if the resulting changeset has a different ID than the one
1461 1493 recorded in the patch. This may happen due to character set
1462 1494 problems or other deficiencies in the text patch format.
1463 1495
1464 1496 To read a patch from standard input, use patch name "-".
1465 1497 """
1466 1498 patches = (patch1,) + patches
1467 1499
1468 1500 if opts.get('exact') or not opts['force']:
1469 1501 cmdutil.bail_if_changed(repo)
1470 1502
1471 1503 d = opts["base"]
1472 1504 strip = opts["strip"]
1473 1505
1474 1506 wlock = repo.wlock()
1475 1507 lock = repo.lock()
1476 1508
1477 1509 for p in patches:
1478 1510 pf = os.path.join(d, p)
1479 1511
1480 1512 if pf == '-':
1481 1513 ui.status(_("applying patch from stdin\n"))
1482 1514 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
1483 1515 else:
1484 1516 ui.status(_("applying %s\n") % p)
1485 1517 tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, file(pf, 'rb'))
1486 1518
1487 1519 if tmpname is None:
1488 1520 raise util.Abort(_('no diffs found'))
1489 1521
1490 1522 try:
1491 1523 cmdline_message = cmdutil.logmessage(opts)
1492 1524 if cmdline_message:
1493 1525 # pickup the cmdline msg
1494 1526 message = cmdline_message
1495 1527 elif message:
1496 1528 # pickup the patch msg
1497 1529 message = message.strip()
1498 1530 else:
1499 1531 # launch the editor
1500 1532 message = None
1501 1533 ui.debug(_('message:\n%s\n') % message)
1502 1534
1503 1535 wp = repo.workingctx().parents()
1504 1536 if opts.get('exact'):
1505 1537 if not nodeid or not p1:
1506 1538 raise util.Abort(_('not a mercurial patch'))
1507 1539 p1 = repo.lookup(p1)
1508 1540 p2 = repo.lookup(p2 or hex(nullid))
1509 1541
1510 1542 if p1 != wp[0].node():
1511 1543 hg.clean(repo, p1, wlock=wlock)
1512 1544 repo.dirstate.setparents(p1, p2)
1513 1545 elif p2:
1514 1546 try:
1515 1547 p1 = repo.lookup(p1)
1516 1548 p2 = repo.lookup(p2)
1517 1549 if p1 == wp[0].node():
1518 1550 repo.dirstate.setparents(p1, p2)
1519 1551 except hg.RepoError:
1520 1552 pass
1521 1553 if opts.get('exact') or opts.get('import_branch'):
1522 1554 repo.dirstate.setbranch(branch or 'default')
1523 1555
1524 1556 files = {}
1525 1557 try:
1526 1558 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1527 1559 files=files)
1528 1560 finally:
1529 1561 files = patch.updatedir(ui, repo, files, wlock=wlock)
1530 1562 n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1531 1563 if opts.get('exact'):
1532 1564 if hex(n) != nodeid:
1533 1565 repo.rollback(wlock=wlock, lock=lock)
1534 1566 raise util.Abort(_('patch is damaged or loses information'))
1535 1567 finally:
1536 1568 os.unlink(tmpname)
1537 1569
1538 1570 def incoming(ui, repo, source="default", **opts):
1539 1571 """show new changesets found in source
1540 1572
1541 1573 Show new changesets found in the specified path/URL or the default
1542 1574 pull location. These are the changesets that would be pulled if a pull
1543 1575 was requested.
1544 1576
1545 1577 For remote repository, using --bundle avoids downloading the changesets
1546 1578 twice if the incoming is followed by a pull.
1547 1579
1548 1580 See pull for valid source format details.
1549 1581 """
1550 1582 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
1551 1583 cmdutil.setremoteconfig(ui, opts)
1552 1584
1553 1585 other = hg.repository(ui, source)
1554 1586 ui.status(_('comparing with %s\n') % source)
1555 1587 if revs:
1556 1588 if 'lookup' in other.capabilities:
1557 1589 revs = [other.lookup(rev) for rev in revs]
1558 1590 else:
1559 1591 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1560 1592 raise util.Abort(error)
1561 1593 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1562 1594 if not incoming:
1563 1595 try:
1564 1596 os.unlink(opts["bundle"])
1565 1597 except:
1566 1598 pass
1567 1599 ui.status(_("no changes found\n"))
1568 1600 return 1
1569 1601
1570 1602 cleanup = None
1571 1603 try:
1572 1604 fname = opts["bundle"]
1573 1605 if fname or not other.local():
1574 1606 # create a bundle (uncompressed if other repo is not local)
1575 1607 if revs is None:
1576 1608 cg = other.changegroup(incoming, "incoming")
1577 1609 else:
1578 1610 if 'changegroupsubset' not in other.capabilities:
1579 1611 raise util.Abort(_("Partial incoming cannot be done because other repository doesn't support changegroupsubset."))
1580 1612 cg = other.changegroupsubset(incoming, revs, 'incoming')
1581 1613 bundletype = other.local() and "HG10BZ" or "HG10UN"
1582 1614 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1583 1615 # keep written bundle?
1584 1616 if opts["bundle"]:
1585 1617 cleanup = None
1586 1618 if not other.local():
1587 1619 # use the created uncompressed bundlerepo
1588 1620 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1589 1621
1590 1622 o = other.changelog.nodesbetween(incoming, revs)[0]
1591 1623 if opts['newest_first']:
1592 1624 o.reverse()
1593 1625 displayer = cmdutil.show_changeset(ui, other, opts)
1594 1626 for n in o:
1595 1627 parents = [p for p in other.changelog.parents(n) if p != nullid]
1596 1628 if opts['no_merges'] and len(parents) == 2:
1597 1629 continue
1598 1630 displayer.show(changenode=n)
1599 1631 finally:
1600 1632 if hasattr(other, 'close'):
1601 1633 other.close()
1602 1634 if cleanup:
1603 1635 os.unlink(cleanup)
1604 1636
1605 1637 def init(ui, dest=".", **opts):
1606 1638 """create a new repository in the given directory
1607 1639
1608 1640 Initialize a new repository in the given directory. If the given
1609 1641 directory does not exist, it is created.
1610 1642
1611 1643 If no directory is given, the current directory is used.
1612 1644
1613 1645 It is possible to specify an ssh:// URL as the destination.
1614 1646 Look at the help text for the pull command for important details
1615 1647 about ssh:// URLs.
1616 1648 """
1617 1649 cmdutil.setremoteconfig(ui, opts)
1618 1650 hg.repository(ui, dest, create=1)
1619 1651
1620 1652 def locate(ui, repo, *pats, **opts):
1621 1653 """locate files matching specific patterns
1622 1654
1623 1655 Print all files under Mercurial control whose names match the
1624 1656 given patterns.
1625 1657
1626 1658 This command searches the entire repository by default. To search
1627 1659 just the current directory and its subdirectories, use "--include .".
1628 1660
1629 1661 If no patterns are given to match, this command prints all file
1630 1662 names.
1631 1663
1632 1664 If you want to feed the output of this command into the "xargs"
1633 1665 command, use the "-0" option to both this command and "xargs".
1634 1666 This will avoid the problem of "xargs" treating single filenames
1635 1667 that contain white space as multiple filenames.
1636 1668 """
1637 1669 end = opts['print0'] and '\0' or '\n'
1638 1670 rev = opts['rev']
1639 1671 if rev:
1640 1672 node = repo.lookup(rev)
1641 1673 else:
1642 1674 node = None
1643 1675
1644 1676 ret = 1
1645 1677 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1646 1678 badmatch=util.always,
1647 1679 default='relglob'):
1648 1680 if src == 'b':
1649 1681 continue
1650 1682 if not node and repo.dirstate.state(abs) == '?':
1651 1683 continue
1652 1684 if opts['fullpath']:
1653 1685 ui.write(os.path.join(repo.root, abs), end)
1654 1686 else:
1655 1687 ui.write(((pats and rel) or abs), end)
1656 1688 ret = 0
1657 1689
1658 1690 return ret
1659 1691
1660 1692 def log(ui, repo, *pats, **opts):
1661 1693 """show revision history of entire repository or files
1662 1694
1663 1695 Print the revision history of the specified files or the entire
1664 1696 project.
1665 1697
1666 1698 File history is shown without following rename or copy history of
1667 1699 files. Use -f/--follow with a file name to follow history across
1668 1700 renames and copies. --follow without a file name will only show
1669 1701 ancestors or descendants of the starting revision. --follow-first
1670 1702 only follows the first parent of merge revisions.
1671 1703
1672 1704 If no revision range is specified, the default is tip:0 unless
1673 1705 --follow is set, in which case the working directory parent is
1674 1706 used as the starting revision.
1675 1707
1676 1708 By default this command outputs: changeset id and hash, tags,
1677 1709 non-trivial parents, user, date and time, and a summary for each
1678 1710 commit. When the -v/--verbose switch is used, the list of changed
1679 1711 files and full commit message is shown.
1680 1712
1681 1713 NOTE: log -p may generate unexpected diff output for merge
1682 1714 changesets, as it will compare the merge changeset against its
1683 1715 first parent only. Also, the files: list will only reflect files
1684 1716 that are different from BOTH parents.
1685 1717
1686 1718 """
1687 1719
1688 1720 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1689 1721 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1690 1722
1691 1723 if opts['limit']:
1692 1724 try:
1693 1725 limit = int(opts['limit'])
1694 1726 except ValueError:
1695 1727 raise util.Abort(_('limit must be a positive integer'))
1696 1728 if limit <= 0: raise util.Abort(_('limit must be positive'))
1697 1729 else:
1698 1730 limit = sys.maxint
1699 1731 count = 0
1700 1732
1701 1733 if opts['copies'] and opts['rev']:
1702 1734 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1703 1735 else:
1704 1736 endrev = repo.changelog.count()
1705 1737 rcache = {}
1706 1738 ncache = {}
1707 1739 dcache = []
1708 1740 def getrenamed(fn, rev, man):
1709 1741 '''looks up all renames for a file (up to endrev) the first
1710 1742 time the file is given. It indexes on the changerev and only
1711 1743 parses the manifest if linkrev != changerev.
1712 1744 Returns rename info for fn at changerev rev.'''
1713 1745 if fn not in rcache:
1714 1746 rcache[fn] = {}
1715 1747 ncache[fn] = {}
1716 1748 fl = repo.file(fn)
1717 1749 for i in xrange(fl.count()):
1718 1750 node = fl.node(i)
1719 1751 lr = fl.linkrev(node)
1720 1752 renamed = fl.renamed(node)
1721 1753 rcache[fn][lr] = renamed
1722 1754 if renamed:
1723 1755 ncache[fn][node] = renamed
1724 1756 if lr >= endrev:
1725 1757 break
1726 1758 if rev in rcache[fn]:
1727 1759 return rcache[fn][rev]
1728 1760 mr = repo.manifest.rev(man)
1729 1761 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1730 1762 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1731 1763 if not dcache or dcache[0] != man:
1732 1764 dcache[:] = [man, repo.manifest.readdelta(man)]
1733 1765 if fn in dcache[1]:
1734 1766 return ncache[fn].get(dcache[1][fn])
1735 1767 return None
1736 1768
1737 1769 df = False
1738 1770 if opts["date"]:
1739 1771 df = util.matchdate(opts["date"])
1740 1772
1741 1773 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1742 1774 for st, rev, fns in changeiter:
1743 1775 if st == 'add':
1744 1776 changenode = repo.changelog.node(rev)
1745 1777 parents = [p for p in repo.changelog.parentrevs(rev)
1746 1778 if p != nullrev]
1747 1779 if opts['no_merges'] and len(parents) == 2:
1748 1780 continue
1749 1781 if opts['only_merges'] and len(parents) != 2:
1750 1782 continue
1751 1783
1752 1784 if df:
1753 1785 changes = get(rev)
1754 1786 if not df(changes[2][0]):
1755 1787 continue
1756 1788
1757 1789 if opts['keyword']:
1758 1790 changes = get(rev)
1759 1791 miss = 0
1760 1792 for k in [kw.lower() for kw in opts['keyword']]:
1761 1793 if not (k in changes[1].lower() or
1762 1794 k in changes[4].lower() or
1763 1795 k in " ".join(changes[3]).lower()):
1764 1796 miss = 1
1765 1797 break
1766 1798 if miss:
1767 1799 continue
1768 1800
1769 1801 copies = []
1770 1802 if opts.get('copies') and rev:
1771 1803 mf = get(rev)[0]
1772 1804 for fn in get(rev)[3]:
1773 1805 rename = getrenamed(fn, rev, mf)
1774 1806 if rename:
1775 1807 copies.append((fn, rename[0]))
1776 1808 displayer.show(rev, changenode, copies=copies)
1777 1809 elif st == 'iter':
1778 1810 if count == limit: break
1779 1811 if displayer.flush(rev):
1780 1812 count += 1
1781 1813
1782 1814 def manifest(ui, repo, rev=None):
1783 1815 """output the current or given revision of the project manifest
1784 1816
1785 1817 Print a list of version controlled files for the given revision.
1786 1818 If no revision is given, the parent of the working directory is used,
1787 1819 or tip if no revision is checked out.
1788 1820
1789 1821 The manifest is the list of files being version controlled. If no revision
1790 1822 is given then the first parent of the working directory is used.
1791 1823
1792 1824 With -v flag, print file permissions. With --debug flag, print
1793 1825 file revision hashes.
1794 1826 """
1795 1827
1796 1828 m = repo.changectx(rev).manifest()
1797 1829 files = m.keys()
1798 1830 files.sort()
1799 1831
1800 1832 for f in files:
1801 1833 if ui.debugflag:
1802 1834 ui.write("%40s " % hex(m[f]))
1803 1835 if ui.verbose:
1804 1836 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1805 1837 ui.write("%s\n" % f)
1806 1838
1807 1839 def merge(ui, repo, node=None, force=None, rev=None):
1808 1840 """merge working directory with another revision
1809 1841
1810 1842 Merge the contents of the current working directory and the
1811 1843 requested revision. Files that changed between either parent are
1812 1844 marked as changed for the next commit and a commit must be
1813 1845 performed before any further updates are allowed.
1814 1846
1815 1847 If no revision is specified, the working directory's parent is a
1816 1848 head revision, and the repository contains exactly one other head,
1817 1849 the other head is merged with by default. Otherwise, an explicit
1818 1850 revision to merge with must be provided.
1819 1851 """
1820 1852
1821 1853 if rev and node:
1822 1854 raise util.Abort(_("please specify just one revision"))
1823 1855
1824 1856 if not node:
1825 1857 node = rev
1826 1858
1827 1859 if not node:
1828 1860 heads = repo.heads()
1829 1861 if len(heads) > 2:
1830 1862 raise util.Abort(_('repo has %d heads - '
1831 1863 'please merge with an explicit rev') %
1832 1864 len(heads))
1833 1865 if len(heads) == 1:
1834 1866 raise util.Abort(_('there is nothing to merge - '
1835 1867 'use "hg update" instead'))
1836 1868 parent = repo.dirstate.parents()[0]
1837 1869 if parent not in heads:
1838 1870 raise util.Abort(_('working dir not at a head rev - '
1839 1871 'use "hg update" or merge with an explicit rev'))
1840 1872 node = parent == heads[0] and heads[-1] or heads[0]
1841 1873 return hg.merge(repo, node, force=force)
1842 1874
1843 1875 def outgoing(ui, repo, dest=None, **opts):
1844 1876 """show changesets not found in destination
1845 1877
1846 1878 Show changesets not found in the specified destination repository or
1847 1879 the default push location. These are the changesets that would be pushed
1848 1880 if a push was requested.
1849 1881
1850 1882 See pull for valid destination format details.
1851 1883 """
1852 1884 dest, revs = cmdutil.parseurl(
1853 1885 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1854 1886 cmdutil.setremoteconfig(ui, opts)
1855 1887 if revs:
1856 1888 revs = [repo.lookup(rev) for rev in revs]
1857 1889
1858 1890 other = hg.repository(ui, dest)
1859 1891 ui.status(_('comparing with %s\n') % dest)
1860 1892 o = repo.findoutgoing(other, force=opts['force'])
1861 1893 if not o:
1862 1894 ui.status(_("no changes found\n"))
1863 1895 return 1
1864 1896 o = repo.changelog.nodesbetween(o, revs)[0]
1865 1897 if opts['newest_first']:
1866 1898 o.reverse()
1867 1899 displayer = cmdutil.show_changeset(ui, repo, opts)
1868 1900 for n in o:
1869 1901 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1870 1902 if opts['no_merges'] and len(parents) == 2:
1871 1903 continue
1872 1904 displayer.show(changenode=n)
1873 1905
1874 1906 def parents(ui, repo, file_=None, **opts):
1875 1907 """show the parents of the working dir or revision
1876 1908
1877 1909 Print the working directory's parent revisions. If a
1878 1910 revision is given via --rev, the parent of that revision
1879 1911 will be printed. If a file argument is given, revision in
1880 1912 which the file was last changed (before the working directory
1881 1913 revision or the argument to --rev if given) is printed.
1882 1914 """
1883 1915 rev = opts.get('rev')
1884 1916 if file_:
1885 1917 ctx = repo.filectx(file_, changeid=rev)
1886 1918 elif rev:
1887 1919 ctx = repo.changectx(rev)
1888 1920 else:
1889 1921 ctx = repo.workingctx()
1890 1922 p = [cp.node() for cp in ctx.parents()]
1891 1923
1892 1924 displayer = cmdutil.show_changeset(ui, repo, opts)
1893 1925 for n in p:
1894 1926 if n != nullid:
1895 1927 displayer.show(changenode=n)
1896 1928
1897 1929 def paths(ui, repo, search=None):
1898 1930 """show definition of symbolic path names
1899 1931
1900 1932 Show definition of symbolic path name NAME. If no name is given, show
1901 1933 definition of available names.
1902 1934
1903 1935 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1904 1936 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1905 1937 """
1906 1938 if search:
1907 1939 for name, path in ui.configitems("paths"):
1908 1940 if name == search:
1909 1941 ui.write("%s\n" % path)
1910 1942 return
1911 1943 ui.warn(_("not found!\n"))
1912 1944 return 1
1913 1945 else:
1914 1946 for name, path in ui.configitems("paths"):
1915 1947 ui.write("%s = %s\n" % (name, path))
1916 1948
1917 1949 def postincoming(ui, repo, modheads, optupdate):
1918 1950 if modheads == 0:
1919 1951 return
1920 1952 if optupdate:
1921 1953 if modheads == 1:
1922 1954 return hg.update(repo, repo.changelog.tip()) # update
1923 1955 else:
1924 1956 ui.status(_("not updating, since new heads added\n"))
1925 1957 if modheads > 1:
1926 1958 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1927 1959 else:
1928 1960 ui.status(_("(run 'hg update' to get a working copy)\n"))
1929 1961
1930 1962 def pull(ui, repo, source="default", **opts):
1931 1963 """pull changes from the specified source
1932 1964
1933 1965 Pull changes from a remote repository to a local one.
1934 1966
1935 1967 This finds all changes from the repository at the specified path
1936 1968 or URL and adds them to the local repository. By default, this
1937 1969 does not update the copy of the project in the working directory.
1938 1970
1939 1971 Valid URLs are of the form:
1940 1972
1941 1973 local/filesystem/path (or file://local/filesystem/path)
1942 1974 http://[user@]host[:port]/[path]
1943 1975 https://[user@]host[:port]/[path]
1944 1976 ssh://[user@]host[:port]/[path]
1945 1977 static-http://host[:port]/[path]
1946 1978
1947 1979 Paths in the local filesystem can either point to Mercurial
1948 1980 repositories or to bundle files (as created by 'hg bundle' or
1949 1981 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1950 1982 allows access to a Mercurial repository where you simply use a web
1951 1983 server to publish the .hg directory as static content.
1952 1984
1953 1985 An optional identifier after # indicates a particular branch, tag,
1954 1986 or changeset to pull.
1955 1987
1956 1988 Some notes about using SSH with Mercurial:
1957 1989 - SSH requires an accessible shell account on the destination machine
1958 1990 and a copy of hg in the remote path or specified with as remotecmd.
1959 1991 - path is relative to the remote user's home directory by default.
1960 1992 Use an extra slash at the start of a path to specify an absolute path:
1961 1993 ssh://example.com//tmp/repository
1962 1994 - Mercurial doesn't use its own compression via SSH; the right thing
1963 1995 to do is to configure it in your ~/.ssh/config, e.g.:
1964 1996 Host *.mylocalnetwork.example.com
1965 1997 Compression no
1966 1998 Host *
1967 1999 Compression yes
1968 2000 Alternatively specify "ssh -C" as your ssh command in your hgrc or
1969 2001 with the --ssh command line option.
1970 2002 """
1971 2003 source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
1972 2004 cmdutil.setremoteconfig(ui, opts)
1973 2005
1974 2006 other = hg.repository(ui, source)
1975 2007 ui.status(_('pulling from %s\n') % (source))
1976 2008 if revs:
1977 2009 if 'lookup' in other.capabilities:
1978 2010 revs = [other.lookup(rev) for rev in revs]
1979 2011 else:
1980 2012 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1981 2013 raise util.Abort(error)
1982 2014
1983 2015 modheads = repo.pull(other, heads=revs, force=opts['force'])
1984 2016 return postincoming(ui, repo, modheads, opts['update'])
1985 2017
1986 2018 def push(ui, repo, dest=None, **opts):
1987 2019 """push changes to the specified destination
1988 2020
1989 2021 Push changes from the local repository to the given destination.
1990 2022
1991 2023 This is the symmetrical operation for pull. It helps to move
1992 2024 changes from the current repository to a different one. If the
1993 2025 destination is local this is identical to a pull in that directory
1994 2026 from the current one.
1995 2027
1996 2028 By default, push will refuse to run if it detects the result would
1997 2029 increase the number of remote heads. This generally indicates the
1998 2030 the client has forgotten to sync and merge before pushing.
1999 2031
2000 2032 Valid URLs are of the form:
2001 2033
2002 2034 local/filesystem/path (or file://local/filesystem/path)
2003 2035 ssh://[user@]host[:port]/[path]
2004 2036 http://[user@]host[:port]/[path]
2005 2037 https://[user@]host[:port]/[path]
2006 2038
2007 2039 An optional identifier after # indicates a particular branch, tag,
2008 2040 or changeset to push.
2009 2041
2010 2042 Look at the help text for the pull command for important details
2011 2043 about ssh:// URLs.
2012 2044
2013 2045 Pushing to http:// and https:// URLs is only possible, if this
2014 2046 feature is explicitly enabled on the remote Mercurial server.
2015 2047 """
2016 2048 dest, revs = cmdutil.parseurl(
2017 2049 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2018 2050 cmdutil.setremoteconfig(ui, opts)
2019 2051
2020 2052 other = hg.repository(ui, dest)
2021 2053 ui.status('pushing to %s\n' % (dest))
2022 2054 if revs:
2023 2055 revs = [repo.lookup(rev) for rev in revs]
2024 2056 r = repo.push(other, opts['force'], revs=revs)
2025 2057 return r == 0
2026 2058
2027 2059 def rawcommit(ui, repo, *pats, **opts):
2028 2060 """raw commit interface (DEPRECATED)
2029 2061
2030 2062 (DEPRECATED)
2031 2063 Lowlevel commit, for use in helper scripts.
2032 2064
2033 2065 This command is not intended to be used by normal users, as it is
2034 2066 primarily useful for importing from other SCMs.
2035 2067
2036 2068 This command is now deprecated and will be removed in a future
2037 2069 release, please use debugsetparents and commit instead.
2038 2070 """
2039 2071
2040 2072 ui.warn(_("(the rawcommit command is deprecated)\n"))
2041 2073
2042 2074 message = cmdutil.logmessage(opts)
2043 2075
2044 2076 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2045 2077 if opts['files']:
2046 2078 files += open(opts['files']).read().splitlines()
2047 2079
2048 2080 parents = [repo.lookup(p) for p in opts['parent']]
2049 2081
2050 2082 try:
2051 2083 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2052 2084 except ValueError, inst:
2053 2085 raise util.Abort(str(inst))
2054 2086
2055 2087 def recover(ui, repo):
2056 2088 """roll back an interrupted transaction
2057 2089
2058 2090 Recover from an interrupted commit or pull.
2059 2091
2060 2092 This command tries to fix the repository status after an interrupted
2061 2093 operation. It should only be necessary when Mercurial suggests it.
2062 2094 """
2063 2095 if repo.recover():
2064 2096 return hg.verify(repo)
2065 2097 return 1
2066 2098
2067 2099 def remove(ui, repo, *pats, **opts):
2068 2100 """remove the specified files on the next commit
2069 2101
2070 2102 Schedule the indicated files for removal from the repository.
2071 2103
2072 2104 This only removes files from the current branch, not from the
2073 2105 entire project history. If the files still exist in the working
2074 2106 directory, they will be deleted from it. If invoked with --after,
2075 2107 files are marked as removed, but not actually unlinked unless --force
2076 2108 is also given. Without exact file names, --after will only mark
2077 2109 files as removed if they are no longer in the working directory.
2078 2110
2079 2111 This command schedules the files to be removed at the next commit.
2080 2112 To undo a remove before that, see hg revert.
2081 2113
2082 2114 Modified files and added files are not removed by default. To
2083 2115 remove them, use the -f/--force option.
2084 2116 """
2085 2117 names = []
2086 2118 if not opts['after'] and not pats:
2087 2119 raise util.Abort(_('no files specified'))
2088 2120 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2089 2121 exact = dict.fromkeys(files)
2090 2122 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2091 2123 modified, added, removed, deleted, unknown = mardu
2092 2124 remove, forget = [], []
2093 2125 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2094 2126 reason = None
2095 2127 if abs in modified and not opts['force']:
2096 2128 reason = _('is modified (use -f to force removal)')
2097 2129 elif abs in added:
2098 2130 if opts['force']:
2099 2131 forget.append(abs)
2100 2132 continue
2101 2133 reason = _('has been marked for add (use -f to force removal)')
2102 2134 elif repo.dirstate.state(abs) == '?':
2103 2135 reason = _('is not managed')
2104 2136 elif opts['after'] and not exact and abs not in deleted:
2105 2137 continue
2106 2138 elif abs in removed:
2107 2139 continue
2108 2140 if reason:
2109 2141 if exact:
2110 2142 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2111 2143 else:
2112 2144 if ui.verbose or not exact:
2113 2145 ui.status(_('removing %s\n') % rel)
2114 2146 remove.append(abs)
2115 2147 repo.forget(forget)
2116 2148 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2117 2149
2118 2150 def rename(ui, repo, *pats, **opts):
2119 2151 """rename files; equivalent of copy + remove
2120 2152
2121 2153 Mark dest as copies of sources; mark sources for deletion. If
2122 2154 dest is a directory, copies are put in that directory. If dest is
2123 2155 a file, there can only be one source.
2124 2156
2125 2157 By default, this command copies the contents of files as they
2126 2158 stand in the working directory. If invoked with --after, the
2127 2159 operation is recorded, but no copying is performed.
2128 2160
2129 2161 This command takes effect in the next commit. To undo a rename
2130 2162 before that, see hg revert.
2131 2163 """
2132 2164 wlock = repo.wlock(0)
2133 2165 errs, copied = docopy(ui, repo, pats, opts, wlock)
2134 2166 names = []
2135 2167 for abs, rel, exact in copied:
2136 2168 if ui.verbose or not exact:
2137 2169 ui.status(_('removing %s\n') % rel)
2138 2170 names.append(abs)
2139 2171 if not opts.get('dry_run'):
2140 2172 repo.remove(names, True, wlock=wlock)
2141 2173 return errs
2142 2174
2143 2175 def revert(ui, repo, *pats, **opts):
2144 2176 """revert files or dirs to their states as of some revision
2145 2177
2146 2178 With no revision specified, revert the named files or directories
2147 2179 to the contents they had in the parent of the working directory.
2148 2180 This restores the contents of the affected files to an unmodified
2149 2181 state and unschedules adds, removes, copies, and renames. If the
2150 2182 working directory has two parents, you must explicitly specify the
2151 2183 revision to revert to.
2152 2184
2153 2185 Modified files are saved with a .orig suffix before reverting.
2154 2186 To disable these backups, use --no-backup.
2155 2187
2156 2188 Using the -r option, revert the given files or directories to their
2157 2189 contents as of a specific revision. This can be helpful to "roll
2158 2190 back" some or all of a change that should not have been committed.
2159 2191
2160 2192 Revert modifies the working directory. It does not commit any
2161 2193 changes, or change the parent of the working directory. If you
2162 2194 revert to a revision other than the parent of the working
2163 2195 directory, the reverted files will thus appear modified
2164 2196 afterwards.
2165 2197
2166 2198 If a file has been deleted, it is recreated. If the executable
2167 2199 mode of a file was changed, it is reset.
2168 2200
2169 2201 If names are given, all files matching the names are reverted.
2170 2202
2171 2203 If no arguments are given, no files are reverted.
2172 2204 """
2173 2205
2174 2206 if opts["date"]:
2175 2207 if opts["rev"]:
2176 2208 raise util.Abort(_("you can't specify a revision and a date"))
2177 2209 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2178 2210
2179 2211 if not pats and not opts['all']:
2180 2212 raise util.Abort(_('no files or directories specified; '
2181 2213 'use --all to revert the whole repo'))
2182 2214
2183 2215 parent, p2 = repo.dirstate.parents()
2184 2216 if not opts['rev'] and p2 != nullid:
2185 2217 raise util.Abort(_('uncommitted merge - please provide a '
2186 2218 'specific revision'))
2187 2219 ctx = repo.changectx(opts['rev'])
2188 2220 node = ctx.node()
2189 2221 mf = ctx.manifest()
2190 2222 if node == parent:
2191 2223 pmf = mf
2192 2224 else:
2193 2225 pmf = None
2194 2226
2195 2227 wlock = repo.wlock()
2196 2228
2197 2229 # need all matching names in dirstate and manifest of target rev,
2198 2230 # so have to walk both. do not print errors if files exist in one
2199 2231 # but not other.
2200 2232
2201 2233 names = {}
2202 2234 target_only = {}
2203 2235
2204 2236 # walk dirstate.
2205 2237
2206 2238 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2207 2239 badmatch=mf.has_key):
2208 2240 names[abs] = (rel, exact)
2209 2241 if src == 'b':
2210 2242 target_only[abs] = True
2211 2243
2212 2244 # walk target manifest.
2213 2245
2214 2246 def badmatch(path):
2215 2247 if path in names:
2216 2248 return True
2217 2249 path_ = path + '/'
2218 2250 for f in names:
2219 2251 if f.startswith(path_):
2220 2252 return True
2221 2253 return False
2222 2254
2223 2255 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2224 2256 badmatch=badmatch):
2225 2257 if abs in names or src == 'b':
2226 2258 continue
2227 2259 names[abs] = (rel, exact)
2228 2260 target_only[abs] = True
2229 2261
2230 2262 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2231 2263 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2232 2264
2233 2265 revert = ([], _('reverting %s\n'))
2234 2266 add = ([], _('adding %s\n'))
2235 2267 remove = ([], _('removing %s\n'))
2236 2268 forget = ([], _('forgetting %s\n'))
2237 2269 undelete = ([], _('undeleting %s\n'))
2238 2270 update = {}
2239 2271
2240 2272 disptable = (
2241 2273 # dispatch table:
2242 2274 # file state
2243 2275 # action if in target manifest
2244 2276 # action if not in target manifest
2245 2277 # make backup if in target manifest
2246 2278 # make backup if not in target manifest
2247 2279 (modified, revert, remove, True, True),
2248 2280 (added, revert, forget, True, False),
2249 2281 (removed, undelete, None, False, False),
2250 2282 (deleted, revert, remove, False, False),
2251 2283 (unknown, add, None, True, False),
2252 2284 (target_only, add, None, False, False),
2253 2285 )
2254 2286
2255 2287 entries = names.items()
2256 2288 entries.sort()
2257 2289
2258 2290 for abs, (rel, exact) in entries:
2259 2291 mfentry = mf.get(abs)
2260 2292 target = repo.wjoin(abs)
2261 2293 def handle(xlist, dobackup):
2262 2294 xlist[0].append(abs)
2263 2295 update[abs] = 1
2264 2296 if dobackup and not opts['no_backup'] and util.lexists(target):
2265 2297 bakname = "%s.orig" % rel
2266 2298 ui.note(_('saving current version of %s as %s\n') %
2267 2299 (rel, bakname))
2268 2300 if not opts.get('dry_run'):
2269 2301 util.copyfile(target, bakname)
2270 2302 if ui.verbose or not exact:
2271 2303 ui.status(xlist[1] % rel)
2272 2304 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2273 2305 if abs not in table: continue
2274 2306 # file has changed in dirstate
2275 2307 if mfentry:
2276 2308 handle(hitlist, backuphit)
2277 2309 elif misslist is not None:
2278 2310 handle(misslist, backupmiss)
2279 2311 else:
2280 2312 if exact: ui.warn(_('file not managed: %s\n') % rel)
2281 2313 break
2282 2314 else:
2283 2315 # file has not changed in dirstate
2284 2316 if node == parent:
2285 2317 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2286 2318 continue
2287 2319 if pmf is None:
2288 2320 # only need parent manifest in this unlikely case,
2289 2321 # so do not read by default
2290 2322 pmf = repo.changectx(parent).manifest()
2291 2323 if abs in pmf:
2292 2324 if mfentry:
2293 2325 # if version of file is same in parent and target
2294 2326 # manifests, do nothing
2295 2327 if pmf[abs] != mfentry:
2296 2328 handle(revert, False)
2297 2329 else:
2298 2330 handle(remove, False)
2299 2331
2300 2332 if not opts.get('dry_run'):
2301 2333 repo.dirstate.forget(forget[0])
2302 2334 r = hg.revert(repo, node, update.has_key, wlock)
2303 2335 repo.dirstate.update(add[0], 'a')
2304 2336 repo.dirstate.update(undelete[0], 'n')
2305 2337 repo.dirstate.update(remove[0], 'r')
2306 2338 return r
2307 2339
2308 2340 def rollback(ui, repo):
2309 2341 """roll back the last transaction in this repository
2310 2342
2311 2343 Roll back the last transaction in this repository, restoring the
2312 2344 project to its state prior to the transaction.
2313 2345
2314 2346 Transactions are used to encapsulate the effects of all commands
2315 2347 that create new changesets or propagate existing changesets into a
2316 2348 repository. For example, the following commands are transactional,
2317 2349 and their effects can be rolled back:
2318 2350
2319 2351 commit
2320 2352 import
2321 2353 pull
2322 2354 push (with this repository as destination)
2323 2355 unbundle
2324 2356
2325 2357 This command should be used with care. There is only one level of
2326 2358 rollback, and there is no way to undo a rollback.
2327 2359
2328 2360 This command is not intended for use on public repositories. Once
2329 2361 changes are visible for pull by other users, rolling a transaction
2330 2362 back locally is ineffective (someone else may already have pulled
2331 2363 the changes). Furthermore, a race is possible with readers of the
2332 2364 repository; for example an in-progress pull from the repository
2333 2365 may fail if a rollback is performed.
2334 2366 """
2335 2367 repo.rollback()
2336 2368
2337 2369 def root(ui, repo):
2338 2370 """print the root (top) of the current working dir
2339 2371
2340 2372 Print the root directory of the current repository.
2341 2373 """
2342 2374 ui.write(repo.root + "\n")
2343 2375
2344 2376 def serve(ui, repo, **opts):
2345 2377 """export the repository via HTTP
2346 2378
2347 2379 Start a local HTTP repository browser and pull server.
2348 2380
2349 2381 By default, the server logs accesses to stdout and errors to
2350 2382 stderr. Use the "-A" and "-E" options to log to files.
2351 2383 """
2352 2384
2353 2385 if opts["stdio"]:
2354 2386 if repo is None:
2355 2387 raise hg.RepoError(_("There is no Mercurial repository here"
2356 2388 " (.hg not found)"))
2357 2389 s = sshserver.sshserver(ui, repo)
2358 2390 s.serve_forever()
2359 2391
2360 2392 parentui = ui.parentui or ui
2361 2393 optlist = ("name templates style address port ipv6"
2362 2394 " accesslog errorlog webdir_conf")
2363 2395 for o in optlist.split():
2364 2396 if opts[o]:
2365 2397 parentui.setconfig("web", o, str(opts[o]))
2366 2398
2367 2399 if repo is None and not ui.config("web", "webdir_conf"):
2368 2400 raise hg.RepoError(_("There is no Mercurial repository here"
2369 2401 " (.hg not found)"))
2370 2402
2371 2403 class service:
2372 2404 def init(self):
2373 2405 try:
2374 2406 self.httpd = hgweb.server.create_server(parentui, repo)
2375 2407 except socket.error, inst:
2376 2408 raise util.Abort(_('cannot start server: ') + inst.args[1])
2377 2409
2378 2410 if not ui.verbose: return
2379 2411
2380 2412 if self.httpd.port != 80:
2381 2413 ui.status(_('listening at http://%s:%d/\n') %
2382 2414 (self.httpd.addr, self.httpd.port))
2383 2415 else:
2384 2416 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2385 2417
2386 2418 def run(self):
2387 2419 self.httpd.serve_forever()
2388 2420
2389 2421 service = service()
2390 2422
2391 2423 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2392 2424
2393 2425 def status(ui, repo, *pats, **opts):
2394 2426 """show changed files in the working directory
2395 2427
2396 2428 Show status of files in the repository. If names are given, only
2397 2429 files that match are shown. Files that are clean or ignored, are
2398 2430 not listed unless -c (clean), -i (ignored) or -A is given.
2399 2431
2400 2432 NOTE: status may appear to disagree with diff if permissions have
2401 2433 changed or a merge has occurred. The standard diff format does not
2402 2434 report permission changes and diff only reports changes relative
2403 2435 to one merge parent.
2404 2436
2405 2437 If one revision is given, it is used as the base revision.
2406 2438 If two revisions are given, the difference between them is shown.
2407 2439
2408 2440 The codes used to show the status of files are:
2409 2441 M = modified
2410 2442 A = added
2411 2443 R = removed
2412 2444 C = clean
2413 2445 ! = deleted, but still tracked
2414 2446 ? = not tracked
2415 2447 I = ignored (not shown by default)
2416 2448 = the previous added file was copied from here
2417 2449 """
2418 2450
2419 2451 all = opts['all']
2420 2452 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2421 2453
2422 2454 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2423 2455 cwd = (pats and repo.getcwd()) or ''
2424 2456 modified, added, removed, deleted, unknown, ignored, clean = [
2425 2457 n for n in repo.status(node1=node1, node2=node2, files=files,
2426 2458 match=matchfn,
2427 2459 list_ignored=all or opts['ignored'],
2428 2460 list_clean=all or opts['clean'])]
2429 2461
2430 2462 changetypes = (('modified', 'M', modified),
2431 2463 ('added', 'A', added),
2432 2464 ('removed', 'R', removed),
2433 2465 ('deleted', '!', deleted),
2434 2466 ('unknown', '?', unknown),
2435 2467 ('ignored', 'I', ignored))
2436 2468
2437 2469 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2438 2470
2439 2471 end = opts['print0'] and '\0' or '\n'
2440 2472
2441 2473 for opt, char, changes in ([ct for ct in explicit_changetypes
2442 2474 if all or opts[ct[0]]]
2443 2475 or changetypes):
2444 2476 if opts['no_status']:
2445 2477 format = "%%s%s" % end
2446 2478 else:
2447 2479 format = "%s %%s%s" % (char, end)
2448 2480
2449 2481 for f in changes:
2450 2482 ui.write(format % repo.pathto(f, cwd))
2451 2483 if ((all or opts.get('copies')) and not opts.get('no_status')):
2452 2484 copied = repo.dirstate.copied(f)
2453 2485 if copied:
2454 2486 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2455 2487
2456 2488 def tag(ui, repo, name, rev_=None, **opts):
2457 2489 """add a tag for the current or given revision
2458 2490
2459 2491 Name a particular revision using <name>.
2460 2492
2461 2493 Tags are used to name particular revisions of the repository and are
2462 2494 very useful to compare different revision, to go back to significant
2463 2495 earlier versions or to mark branch points as releases, etc.
2464 2496
2465 2497 If no revision is given, the parent of the working directory is used,
2466 2498 or tip if no revision is checked out.
2467 2499
2468 2500 To facilitate version control, distribution, and merging of tags,
2469 2501 they are stored as a file named ".hgtags" which is managed
2470 2502 similarly to other project files and can be hand-edited if
2471 2503 necessary. The file '.hg/localtags' is used for local tags (not
2472 2504 shared among repositories).
2473 2505 """
2474 2506 if name in ['tip', '.', 'null']:
2475 2507 raise util.Abort(_("the name '%s' is reserved") % name)
2476 2508 if rev_ is not None:
2477 2509 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2478 2510 "please use 'hg tag [-r REV] NAME' instead\n"))
2479 2511 if opts['rev']:
2480 2512 raise util.Abort(_("use only one form to specify the revision"))
2481 2513 if opts['rev'] and opts['remove']:
2482 2514 raise util.Abort(_("--rev and --remove are incompatible"))
2483 2515 if opts['rev']:
2484 2516 rev_ = opts['rev']
2485 2517 message = opts['message']
2486 2518 if opts['remove']:
2487 2519 rev_ = nullid
2488 2520 if not message:
2489 2521 message = _('Removed tag %s') % name
2490 2522 elif name in repo.tags() and not opts['force']:
2491 2523 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2492 2524 % name)
2493 2525 if not rev_ and repo.dirstate.parents()[1] != nullid:
2494 2526 raise util.Abort(_('uncommitted merge - please provide a '
2495 2527 'specific revision'))
2496 2528 r = repo.changectx(rev_).node()
2497 2529
2498 2530 if not message:
2499 2531 message = _('Added tag %s for changeset %s') % (name, short(r))
2500 2532
2501 2533 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2502 2534
2503 2535 def tags(ui, repo):
2504 2536 """list repository tags
2505 2537
2506 2538 List the repository tags.
2507 2539
2508 2540 This lists both regular and local tags.
2509 2541 """
2510 2542
2511 2543 l = repo.tagslist()
2512 2544 l.reverse()
2513 2545 hexfunc = ui.debugflag and hex or short
2514 2546 for t, n in l:
2515 2547 try:
2516 2548 hn = hexfunc(n)
2517 2549 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2518 2550 except revlog.LookupError:
2519 2551 r = " ?:%s" % hn
2520 2552 if ui.quiet:
2521 2553 ui.write("%s\n" % t)
2522 2554 else:
2523 2555 spaces = " " * (30 - util.locallen(t))
2524 2556 ui.write("%s%s %s\n" % (t, spaces, r))
2525 2557
2526 2558 def tip(ui, repo, **opts):
2527 2559 """show the tip revision
2528 2560
2529 2561 Show the tip revision.
2530 2562 """
2531 2563 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2532 2564
2533 2565 def unbundle(ui, repo, fname, **opts):
2534 2566 """apply a changegroup file
2535 2567
2536 2568 Apply a compressed changegroup file generated by the bundle
2537 2569 command.
2538 2570 """
2539 2571 if os.path.exists(fname):
2540 2572 f = open(fname, "rb")
2541 2573 else:
2542 2574 f = urllib.urlopen(fname)
2543 2575 gen = changegroup.readbundle(f, fname)
2544 2576 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2545 2577 return postincoming(ui, repo, modheads, opts['update'])
2546 2578
2547 2579 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2548 2580 """update working directory
2549 2581
2550 2582 Update the working directory to the specified revision, or the
2551 2583 tip of the current branch if none is specified.
2552 2584
2553 2585 If there are no outstanding changes in the working directory and
2554 2586 there is a linear relationship between the current version and the
2555 2587 requested version, the result is the requested version.
2556 2588
2557 2589 To merge the working directory with another revision, use the
2558 2590 merge command.
2559 2591
2560 2592 By default, update will refuse to run if doing so would require
2561 2593 discarding local changes.
2562 2594 """
2563 2595 if rev and node:
2564 2596 raise util.Abort(_("please specify just one revision"))
2565 2597
2566 2598 if not rev:
2567 2599 rev = node
2568 2600
2569 2601 if date:
2570 2602 if rev:
2571 2603 raise util.Abort(_("you can't specify a revision and a date"))
2572 2604 rev = cmdutil.finddate(ui, repo, date)
2573 2605
2574 2606 if clean:
2575 2607 return hg.clean(repo, rev)
2576 2608 else:
2577 2609 return hg.update(repo, rev)
2578 2610
2579 2611 def verify(ui, repo):
2580 2612 """verify the integrity of the repository
2581 2613
2582 2614 Verify the integrity of the current repository.
2583 2615
2584 2616 This will perform an extensive check of the repository's
2585 2617 integrity, validating the hashes and checksums of each entry in
2586 2618 the changelog, manifest, and tracked files, as well as the
2587 2619 integrity of their crosslinks and indices.
2588 2620 """
2589 2621 return hg.verify(repo)
2590 2622
2591 2623 def version_(ui):
2592 2624 """output version and copyright information"""
2593 2625 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2594 2626 % version.get_version())
2595 2627 ui.status(_(
2596 2628 "\nCopyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n"
2597 2629 "This is free software; see the source for copying conditions. "
2598 2630 "There is NO\nwarranty; "
2599 2631 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2600 2632 ))
2601 2633
2602 2634 # Command options and aliases are listed here, alphabetically
2603 2635
2604 2636 globalopts = [
2605 2637 ('R', 'repository', '',
2606 2638 _('repository root directory or symbolic path name')),
2607 2639 ('', 'cwd', '', _('change working directory')),
2608 2640 ('y', 'noninteractive', None,
2609 2641 _('do not prompt, assume \'yes\' for any required answers')),
2610 2642 ('q', 'quiet', None, _('suppress output')),
2611 2643 ('v', 'verbose', None, _('enable additional output')),
2612 2644 ('', 'config', [], _('set/override config option')),
2613 2645 ('', 'debug', None, _('enable debugging output')),
2614 2646 ('', 'debugger', None, _('start debugger')),
2615 2647 ('', 'encoding', util._encoding, _('set the charset encoding')),
2616 2648 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2617 2649 ('', 'lsprof', None, _('print improved command execution profile')),
2618 2650 ('', 'traceback', None, _('print traceback on exception')),
2619 2651 ('', 'time', None, _('time how long the command takes')),
2620 2652 ('', 'profile', None, _('print command execution profile')),
2621 2653 ('', 'version', None, _('output version information and exit')),
2622 2654 ('h', 'help', None, _('display help and exit')),
2623 2655 ]
2624 2656
2625 2657 dryrunopts = [('n', 'dry-run', None,
2626 2658 _('do not perform actions, just print output'))]
2627 2659
2628 2660 remoteopts = [
2629 2661 ('e', 'ssh', '', _('specify ssh command to use')),
2630 2662 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2631 2663 ]
2632 2664
2633 2665 walkopts = [
2634 2666 ('I', 'include', [], _('include names matching the given patterns')),
2635 2667 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2636 2668 ]
2637 2669
2638 2670 commitopts = [
2639 2671 ('m', 'message', '', _('use <text> as commit message')),
2640 2672 ('l', 'logfile', '', _('read commit message from <file>')),
2641 2673 ]
2642 2674
2643 2675 table = {
2644 2676 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2645 2677 "addremove":
2646 2678 (addremove,
2647 2679 [('s', 'similarity', '',
2648 2680 _('guess renamed files by similarity (0<=s<=100)')),
2649 2681 ] + walkopts + dryrunopts,
2650 2682 _('hg addremove [OPTION]... [FILE]...')),
2651 2683 "^annotate":
2652 2684 (annotate,
2653 2685 [('r', 'rev', '', _('annotate the specified revision')),
2654 2686 ('f', 'follow', None, _('follow file copies and renames')),
2655 2687 ('a', 'text', None, _('treat all files as text')),
2656 2688 ('u', 'user', None, _('list the author')),
2657 2689 ('d', 'date', None, _('list the date')),
2658 2690 ('n', 'number', None, _('list the revision number (default)')),
2659 2691 ('c', 'changeset', None, _('list the changeset')),
2660 2692 ] + walkopts,
2661 2693 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] FILE...')),
2662 2694 "archive":
2663 2695 (archive,
2664 2696 [('', 'no-decode', None, _('do not pass files through decoders')),
2665 2697 ('p', 'prefix', '', _('directory prefix for files in archive')),
2666 2698 ('r', 'rev', '', _('revision to distribute')),
2667 2699 ('t', 'type', '', _('type of distribution to create')),
2668 2700 ] + walkopts,
2669 2701 _('hg archive [OPTION]... DEST')),
2670 2702 "backout":
2671 2703 (backout,
2672 2704 [('', 'merge', None,
2673 2705 _('merge with old dirstate parent after backout')),
2674 2706 ('d', 'date', '', _('record datecode as commit date')),
2675 2707 ('', 'parent', '', _('parent to choose when backing out merge')),
2676 2708 ('u', 'user', '', _('record user as committer')),
2677 2709 ('r', 'rev', '', _('revision to backout')),
2678 2710 ] + walkopts + commitopts,
2679 2711 _('hg backout [OPTION]... [-r] REV')),
2680 2712 "branch": (branch,
2681 2713 [('f', 'force', None,
2682 2714 _('set branch name even if it shadows an existing branch'))],
2683 2715 _('hg branch [NAME]')),
2684 2716 "branches": (branches, [], _('hg branches')),
2685 2717 "bundle":
2686 2718 (bundle,
2687 2719 [('f', 'force', None,
2688 2720 _('run even when remote repository is unrelated')),
2689 2721 ('r', 'rev', [],
2690 2722 _('a changeset you would like to bundle')),
2691 2723 ('', 'base', [],
2692 2724 _('a base changeset to specify instead of a destination')),
2693 2725 ] + remoteopts,
2694 2726 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2695 2727 "cat":
2696 2728 (cat,
2697 2729 [('o', 'output', '', _('print output to file with formatted name')),
2698 2730 ('r', 'rev', '', _('print the given revision')),
2699 2731 ] + walkopts,
2700 2732 _('hg cat [OPTION]... FILE...')),
2701 2733 "^clone":
2702 2734 (clone,
2703 2735 [('U', 'noupdate', None, _('do not update the new working directory')),
2704 2736 ('r', 'rev', [],
2705 2737 _('a changeset you would like to have after cloning')),
2706 2738 ('', 'pull', None, _('use pull protocol to copy metadata')),
2707 2739 ('', 'uncompressed', None,
2708 2740 _('use uncompressed transfer (fast over LAN)')),
2709 2741 ] + remoteopts,
2710 2742 _('hg clone [OPTION]... SOURCE [DEST]')),
2711 2743 "^commit|ci":
2712 2744 (commit,
2713 2745 [('A', 'addremove', None,
2714 2746 _('mark new/missing files as added/removed before committing')),
2715 2747 ('d', 'date', '', _('record datecode as commit date')),
2716 2748 ('u', 'user', '', _('record user as commiter')),
2717 2749 ] + walkopts + commitopts,
2718 2750 _('hg commit [OPTION]... [FILE]...')),
2719 2751 "copy|cp":
2720 2752 (copy,
2721 2753 [('A', 'after', None, _('record a copy that has already occurred')),
2722 2754 ('f', 'force', None,
2723 2755 _('forcibly copy over an existing managed file')),
2724 2756 ] + walkopts + dryrunopts,
2725 2757 _('hg copy [OPTION]... [SOURCE]... DEST')),
2726 2758 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2727 2759 "debugcomplete":
2728 2760 (debugcomplete,
2729 2761 [('o', 'options', None, _('show the command options'))],
2730 2762 _('debugcomplete [-o] CMD')),
2731 2763 "debuginstall": (debuginstall, [], _('debuginstall')),
2732 2764 "debugrebuildstate":
2733 2765 (debugrebuildstate,
2734 2766 [('r', 'rev', '', _('revision to rebuild to'))],
2735 2767 _('debugrebuildstate [-r REV] [REV]')),
2736 2768 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2737 2769 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2738 2770 "debugstate": (debugstate, [], _('debugstate')),
2739 2771 "debugdate":
2740 2772 (debugdate,
2741 2773 [('e', 'extended', None, _('try extended date formats'))],
2742 2774 _('debugdate [-e] DATE [RANGE]')),
2743 2775 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2744 2776 "debugindex": (debugindex, [], _('debugindex FILE')),
2745 2777 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2746 2778 "debugrename": (debugrename,
2747 2779 [('r', 'rev', '', _('revision to debug'))],
2748 2780 _('debugrename [-r REV] FILE')),
2749 2781 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2750 2782 "^diff":
2751 2783 (diff,
2752 2784 [('r', 'rev', [], _('revision')),
2753 2785 ('a', 'text', None, _('treat all files as text')),
2754 2786 ('p', 'show-function', None,
2755 2787 _('show which function each change is in')),
2756 2788 ('g', 'git', None, _('use git extended diff format')),
2757 2789 ('', 'nodates', None, _("don't include dates in diff headers")),
2758 2790 ('w', 'ignore-all-space', None,
2759 2791 _('ignore white space when comparing lines')),
2760 2792 ('b', 'ignore-space-change', None,
2761 2793 _('ignore changes in the amount of white space')),
2762 2794 ('B', 'ignore-blank-lines', None,
2763 2795 _('ignore changes whose lines are all blank')),
2764 2796 ] + walkopts,
2765 2797 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2766 2798 "^export":
2767 2799 (export,
2768 2800 [('o', 'output', '', _('print output to file with formatted name')),
2769 2801 ('a', 'text', None, _('treat all files as text')),
2770 2802 ('g', 'git', None, _('use git extended diff format')),
2771 2803 ('', 'nodates', None, _("don't include dates in diff headers")),
2772 2804 ('', 'switch-parent', None, _('diff against the second parent'))],
2773 2805 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2774 2806 "grep":
2775 2807 (grep,
2776 2808 [('0', 'print0', None, _('end fields with NUL')),
2777 2809 ('', 'all', None, _('print all revisions that match')),
2778 2810 ('f', 'follow', None,
2779 2811 _('follow changeset history, or file history across copies and renames')),
2780 2812 ('i', 'ignore-case', None, _('ignore case when matching')),
2781 2813 ('l', 'files-with-matches', None,
2782 2814 _('print only filenames and revs that match')),
2783 2815 ('n', 'line-number', None, _('print matching line numbers')),
2784 2816 ('r', 'rev', [], _('search in given revision range')),
2785 2817 ('u', 'user', None, _('print user who committed change')),
2786 2818 ] + walkopts,
2787 2819 _('hg grep [OPTION]... PATTERN [FILE]...')),
2788 2820 "heads":
2789 2821 (heads,
2790 2822 [('', 'style', '', _('display using template map file')),
2791 2823 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2792 2824 ('', 'template', '', _('display with template'))],
2793 _('hg heads [-r REV]')),
2825 _('hg heads [-r REV] [REV]...')),
2794 2826 "help": (help_, [], _('hg help [COMMAND]')),
2795 2827 "identify|id": (identify, [], _('hg identify')),
2796 2828 "import|patch":
2797 2829 (import_,
2798 2830 [('p', 'strip', 1,
2799 2831 _('directory strip option for patch. This has the same\n'
2800 2832 'meaning as the corresponding patch option')),
2801 2833 ('b', 'base', '', _('base path')),
2802 2834 ('f', 'force', None,
2803 2835 _('skip check for outstanding uncommitted changes')),
2804 2836 ('', 'exact', None,
2805 2837 _('apply patch to the nodes from which it was generated')),
2806 2838 ('', 'import-branch', None,
2807 2839 _('Use any branch information in patch (implied by --exact)'))] + commitopts,
2808 2840 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2809 2841 "incoming|in": (incoming,
2810 2842 [('M', 'no-merges', None, _('do not show merges')),
2811 2843 ('f', 'force', None,
2812 2844 _('run even when remote repository is unrelated')),
2813 2845 ('', 'style', '', _('display using template map file')),
2814 2846 ('n', 'newest-first', None, _('show newest record first')),
2815 2847 ('', 'bundle', '', _('file to store the bundles into')),
2816 2848 ('p', 'patch', None, _('show patch')),
2817 2849 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2818 2850 ('', 'template', '', _('display with template')),
2819 2851 ] + remoteopts,
2820 2852 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2821 2853 ' [--bundle FILENAME] [SOURCE]')),
2822 2854 "^init":
2823 2855 (init,
2824 2856 remoteopts,
2825 2857 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2826 2858 "locate":
2827 2859 (locate,
2828 2860 [('r', 'rev', '', _('search the repository as it stood at rev')),
2829 2861 ('0', 'print0', None,
2830 2862 _('end filenames with NUL, for use with xargs')),
2831 2863 ('f', 'fullpath', None,
2832 2864 _('print complete paths from the filesystem root')),
2833 2865 ] + walkopts,
2834 2866 _('hg locate [OPTION]... [PATTERN]...')),
2835 2867 "^log|history":
2836 2868 (log,
2837 2869 [('f', 'follow', None,
2838 2870 _('follow changeset history, or file history across copies and renames')),
2839 2871 ('', 'follow-first', None,
2840 2872 _('only follow the first parent of merge changesets')),
2841 2873 ('d', 'date', '', _('show revs matching date spec')),
2842 2874 ('C', 'copies', None, _('show copied files')),
2843 2875 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
2844 2876 ('l', 'limit', '', _('limit number of changes displayed')),
2845 2877 ('r', 'rev', [], _('show the specified revision or range')),
2846 2878 ('', 'removed', None, _('include revs where files were removed')),
2847 2879 ('M', 'no-merges', None, _('do not show merges')),
2848 2880 ('', 'style', '', _('display using template map file')),
2849 2881 ('m', 'only-merges', None, _('show only merges')),
2850 2882 ('p', 'patch', None, _('show patch')),
2851 2883 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
2852 2884 ('', 'template', '', _('display with template')),
2853 2885 ] + walkopts,
2854 2886 _('hg log [OPTION]... [FILE]')),
2855 2887 "manifest": (manifest, [], _('hg manifest [REV]')),
2856 2888 "^merge":
2857 2889 (merge,
2858 2890 [('f', 'force', None, _('force a merge with outstanding changes')),
2859 2891 ('r', 'rev', '', _('revision to merge')),
2860 2892 ],
2861 2893 _('hg merge [-f] [[-r] REV]')),
2862 2894 "outgoing|out": (outgoing,
2863 2895 [('M', 'no-merges', None, _('do not show merges')),
2864 2896 ('f', 'force', None,
2865 2897 _('run even when remote repository is unrelated')),
2866 2898 ('p', 'patch', None, _('show patch')),
2867 2899 ('', 'style', '', _('display using template map file')),
2868 2900 ('r', 'rev', [], _('a specific revision you would like to push')),
2869 2901 ('n', 'newest-first', None, _('show newest record first')),
2870 2902 ('', 'template', '', _('display with template')),
2871 2903 ] + remoteopts,
2872 2904 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
2873 2905 "^parents":
2874 2906 (parents,
2875 2907 [('r', 'rev', '', _('show parents from the specified rev')),
2876 2908 ('', 'style', '', _('display using template map file')),
2877 2909 ('', 'template', '', _('display with template'))],
2878 2910 _('hg parents [-r REV] [FILE]')),
2879 2911 "paths": (paths, [], _('hg paths [NAME]')),
2880 2912 "^pull":
2881 2913 (pull,
2882 2914 [('u', 'update', None,
2883 2915 _('update to new tip if changesets were pulled')),
2884 2916 ('f', 'force', None,
2885 2917 _('run even when remote repository is unrelated')),
2886 2918 ('r', 'rev', [],
2887 2919 _('a specific revision up to which you would like to pull')),
2888 2920 ] + remoteopts,
2889 2921 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
2890 2922 "^push":
2891 2923 (push,
2892 2924 [('f', 'force', None, _('force push')),
2893 2925 ('r', 'rev', [], _('a specific revision you would like to push')),
2894 2926 ] + remoteopts,
2895 2927 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
2896 2928 "debugrawcommit|rawcommit":
2897 2929 (rawcommit,
2898 2930 [('p', 'parent', [], _('parent')),
2899 2931 ('d', 'date', '', _('date code')),
2900 2932 ('u', 'user', '', _('user')),
2901 2933 ('F', 'files', '', _('file list'))
2902 2934 ] + commitopts,
2903 2935 _('hg debugrawcommit [OPTION]... [FILE]...')),
2904 2936 "recover": (recover, [], _('hg recover')),
2905 2937 "^remove|rm":
2906 2938 (remove,
2907 2939 [('A', 'after', None, _('record remove that has already occurred')),
2908 2940 ('f', 'force', None, _('remove file even if modified')),
2909 2941 ] + walkopts,
2910 2942 _('hg remove [OPTION]... FILE...')),
2911 2943 "rename|mv":
2912 2944 (rename,
2913 2945 [('A', 'after', None, _('record a rename that has already occurred')),
2914 2946 ('f', 'force', None,
2915 2947 _('forcibly copy over an existing managed file')),
2916 2948 ] + walkopts + dryrunopts,
2917 2949 _('hg rename [OPTION]... SOURCE... DEST')),
2918 2950 "^revert":
2919 2951 (revert,
2920 2952 [('a', 'all', None, _('revert all changes when no arguments given')),
2921 2953 ('d', 'date', '', _('tipmost revision matching date')),
2922 2954 ('r', 'rev', '', _('revision to revert to')),
2923 2955 ('', 'no-backup', None, _('do not save backup copies of files')),
2924 2956 ] + walkopts + dryrunopts,
2925 2957 _('hg revert [OPTION]... [-r REV] [NAME]...')),
2926 2958 "rollback": (rollback, [], _('hg rollback')),
2927 2959 "root": (root, [], _('hg root')),
2928 2960 "showconfig|debugconfig":
2929 2961 (showconfig,
2930 2962 [('u', 'untrusted', None, _('show untrusted configuration options'))],
2931 2963 _('showconfig [-u] [NAME]...')),
2932 2964 "^serve":
2933 2965 (serve,
2934 2966 [('A', 'accesslog', '', _('name of access log file to write to')),
2935 2967 ('d', 'daemon', None, _('run server in background')),
2936 2968 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
2937 2969 ('E', 'errorlog', '', _('name of error log file to write to')),
2938 2970 ('p', 'port', 0, _('port to use (default: 8000)')),
2939 2971 ('a', 'address', '', _('address to use')),
2940 2972 ('n', 'name', '',
2941 2973 _('name to show in web pages (default: working dir)')),
2942 2974 ('', 'webdir-conf', '', _('name of the webdir config file'
2943 2975 ' (serve more than one repo)')),
2944 2976 ('', 'pid-file', '', _('name of file to write process ID to')),
2945 2977 ('', 'stdio', None, _('for remote clients')),
2946 2978 ('t', 'templates', '', _('web templates to use')),
2947 2979 ('', 'style', '', _('template style to use')),
2948 2980 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2949 2981 _('hg serve [OPTION]...')),
2950 2982 "^status|st":
2951 2983 (status,
2952 2984 [('A', 'all', None, _('show status of all files')),
2953 2985 ('m', 'modified', None, _('show only modified files')),
2954 2986 ('a', 'added', None, _('show only added files')),
2955 2987 ('r', 'removed', None, _('show only removed files')),
2956 2988 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2957 2989 ('c', 'clean', None, _('show only files without changes')),
2958 2990 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2959 2991 ('i', 'ignored', None, _('show only ignored files')),
2960 2992 ('n', 'no-status', None, _('hide status prefix')),
2961 2993 ('C', 'copies', None, _('show source of copied files')),
2962 2994 ('0', 'print0', None,
2963 2995 _('end filenames with NUL, for use with xargs')),
2964 2996 ('', 'rev', [], _('show difference from revision')),
2965 2997 ] + walkopts,
2966 2998 _('hg status [OPTION]... [FILE]...')),
2967 2999 "tag":
2968 3000 (tag,
2969 3001 [('f', 'force', None, _('replace existing tag')),
2970 3002 ('l', 'local', None, _('make the tag local')),
2971 3003 ('m', 'message', '', _('message for tag commit log entry')),
2972 3004 ('d', 'date', '', _('record datecode as commit date')),
2973 3005 ('u', 'user', '', _('record user as commiter')),
2974 3006 ('r', 'rev', '', _('revision to tag')),
2975 3007 ('', 'remove', None, _('remove a tag'))],
2976 3008 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
2977 3009 "tags": (tags, [], _('hg tags')),
2978 3010 "tip":
2979 3011 (tip,
2980 3012 [('', 'style', '', _('display using template map file')),
2981 3013 ('p', 'patch', None, _('show patch')),
2982 3014 ('', 'template', '', _('display with template'))],
2983 3015 _('hg tip [-p]')),
2984 3016 "unbundle":
2985 3017 (unbundle,
2986 3018 [('u', 'update', None,
2987 3019 _('update to new tip if changesets were unbundled'))],
2988 3020 _('hg unbundle [-u] FILE')),
2989 3021 "^update|up|checkout|co":
2990 3022 (update,
2991 3023 [('C', 'clean', None, _('overwrite locally modified files')),
2992 3024 ('d', 'date', '', _('tipmost revision matching date')),
2993 3025 ('r', 'rev', '', _('revision'))],
2994 3026 _('hg update [-C] [-d DATE] [[-r] REV]')),
2995 3027 "verify": (verify, [], _('hg verify')),
2996 3028 "version": (version_, [], _('hg version')),
2997 3029 }
2998 3030
2999 3031 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3000 3032 " debugindex debugindexdot debugdate debuginstall")
3001 3033 optionalrepo = ("paths serve showconfig")
3002 3034
3003 3035 def dispatch(args):
3004 3036 try:
3005 3037 u = ui.ui(traceback='--traceback' in args)
3006 3038 except util.Abort, inst:
3007 3039 sys.stderr.write(_("abort: %s\n") % inst)
3008 3040 return -1
3009 3041 return cmdutil.runcatch(u, args)
3010 3042
3011 3043 def run():
3012 3044 sys.exit(dispatch(sys.argv[1:]))
@@ -1,1883 +1,1929 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import repo, changegroup
11 11 import changelog, dirstate, filelog, manifest, context
12 12 import re, lock, transaction, tempfile, stat, mdiff, errno, ui
13 13 import os, revlog, time, util, extensions, hook
14 14
15 15 class localrepository(repo.repository):
16 16 capabilities = ('lookup', 'changegroupsubset')
17 17 supported = ('revlogv1', 'store')
18 18
19 19 def __del__(self):
20 20 self.transhandle = None
21 21 def __init__(self, parentui, path=None, create=0):
22 22 repo.repository.__init__(self)
23 23 self.path = path
24 24 self.root = os.path.realpath(path)
25 25 self.path = os.path.join(self.root, ".hg")
26 26 self.origroot = path
27 27 self.opener = util.opener(self.path)
28 28 self.wopener = util.opener(self.root)
29 29
30 30 if not os.path.isdir(self.path):
31 31 if create:
32 32 if not os.path.exists(path):
33 33 os.mkdir(path)
34 34 os.mkdir(self.path)
35 35 requirements = ["revlogv1"]
36 36 if parentui.configbool('format', 'usestore', True):
37 37 os.mkdir(os.path.join(self.path, "store"))
38 38 requirements.append("store")
39 39 # create an invalid changelog
40 40 self.opener("00changelog.i", "a").write(
41 41 '\0\0\0\2' # represents revlogv2
42 42 ' dummy changelog to prevent using the old repo layout'
43 43 )
44 44 reqfile = self.opener("requires", "w")
45 45 for r in requirements:
46 46 reqfile.write("%s\n" % r)
47 47 reqfile.close()
48 48 else:
49 49 raise repo.RepoError(_("repository %s not found") % path)
50 50 elif create:
51 51 raise repo.RepoError(_("repository %s already exists") % path)
52 52 else:
53 53 # find requirements
54 54 try:
55 55 requirements = self.opener("requires").read().splitlines()
56 56 except IOError, inst:
57 57 if inst.errno != errno.ENOENT:
58 58 raise
59 59 requirements = []
60 60 # check them
61 61 for r in requirements:
62 62 if r not in self.supported:
63 63 raise repo.RepoError(_("requirement '%s' not supported") % r)
64 64
65 65 # setup store
66 66 if "store" in requirements:
67 67 self.encodefn = util.encodefilename
68 68 self.decodefn = util.decodefilename
69 69 self.spath = os.path.join(self.path, "store")
70 70 else:
71 71 self.encodefn = lambda x: x
72 72 self.decodefn = lambda x: x
73 73 self.spath = self.path
74 74 self.sopener = util.encodedopener(util.opener(self.spath), self.encodefn)
75 75
76 76 self.ui = ui.ui(parentui=parentui)
77 77 try:
78 78 self.ui.readconfig(self.join("hgrc"), self.root)
79 79 extensions.loadall(self.ui)
80 80 except IOError:
81 81 pass
82 82
83 83 self.tagscache = None
84 84 self.branchcache = None
85 85 self.nodetagscache = None
86 86 self.filterpats = {}
87 87 self.transhandle = None
88 88
89 89 def __getattr__(self, name):
90 90 if name == 'changelog':
91 91 self.changelog = changelog.changelog(self.sopener)
92 92 self.sopener.defversion = self.changelog.version
93 93 return self.changelog
94 94 if name == 'manifest':
95 95 self.changelog
96 96 self.manifest = manifest.manifest(self.sopener)
97 97 return self.manifest
98 98 if name == 'dirstate':
99 99 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
100 100 return self.dirstate
101 101 else:
102 102 raise AttributeError, name
103 103
104 104 def url(self):
105 105 return 'file:' + self.root
106 106
107 107 def hook(self, name, throw=False, **args):
108 108 return hook.hook(self.ui, self, name, throw, **args)
109 109
110 110 tag_disallowed = ':\r\n'
111 111
112 112 def _tag(self, name, node, message, local, user, date, parent=None):
113 113 use_dirstate = parent is None
114 114
115 115 for c in self.tag_disallowed:
116 116 if c in name:
117 117 raise util.Abort(_('%r cannot be used in a tag name') % c)
118 118
119 119 self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
120 120
121 121 if local:
122 122 # local tags are stored in the current charset
123 123 self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
124 124 self.hook('tag', node=hex(node), tag=name, local=local)
125 125 return
126 126
127 127 # committed tags are stored in UTF-8
128 128 line = '%s %s\n' % (hex(node), util.fromlocal(name))
129 129 if use_dirstate:
130 130 self.wfile('.hgtags', 'ab').write(line)
131 131 else:
132 132 ntags = self.filectx('.hgtags', parent).data()
133 133 self.wfile('.hgtags', 'ab').write(ntags + line)
134 134 if use_dirstate and self.dirstate.state('.hgtags') == '?':
135 135 self.add(['.hgtags'])
136 136
137 137 tagnode = self.commit(['.hgtags'], message, user, date, p1=parent)
138 138
139 139 self.hook('tag', node=hex(node), tag=name, local=local)
140 140
141 141 return tagnode
142 142
143 143 def tag(self, name, node, message, local, user, date):
144 144 '''tag a revision with a symbolic name.
145 145
146 146 if local is True, the tag is stored in a per-repository file.
147 147 otherwise, it is stored in the .hgtags file, and a new
148 148 changeset is committed with the change.
149 149
150 150 keyword arguments:
151 151
152 152 local: whether to store tag in non-version-controlled file
153 153 (default False)
154 154
155 155 message: commit message to use if committing
156 156
157 157 user: name of user to use if committing
158 158
159 159 date: date tuple to use if committing'''
160 160
161 161 for x in self.status()[:5]:
162 162 if '.hgtags' in x:
163 163 raise util.Abort(_('working copy of .hgtags is changed '
164 164 '(please commit .hgtags manually)'))
165 165
166 166
167 167 self._tag(name, node, message, local, user, date)
168 168
169 169 def tags(self):
170 170 '''return a mapping of tag to node'''
171 171 if self.tagscache:
172 172 return self.tagscache
173 173
174 174 globaltags = {}
175 175
176 176 def readtags(lines, fn):
177 177 filetags = {}
178 178 count = 0
179 179
180 180 def warn(msg):
181 181 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
182 182
183 183 for l in lines:
184 184 count += 1
185 185 if not l:
186 186 continue
187 187 s = l.split(" ", 1)
188 188 if len(s) != 2:
189 189 warn(_("cannot parse entry"))
190 190 continue
191 191 node, key = s
192 192 key = util.tolocal(key.strip()) # stored in UTF-8
193 193 try:
194 194 bin_n = bin(node)
195 195 except TypeError:
196 196 warn(_("node '%s' is not well formed") % node)
197 197 continue
198 198 if bin_n not in self.changelog.nodemap:
199 199 warn(_("tag '%s' refers to unknown node") % key)
200 200 continue
201 201
202 202 h = []
203 203 if key in filetags:
204 204 n, h = filetags[key]
205 205 h.append(n)
206 206 filetags[key] = (bin_n, h)
207 207
208 208 for k, nh in filetags.items():
209 209 if k not in globaltags:
210 210 globaltags[k] = nh
211 211 continue
212 212 # we prefer the global tag if:
213 213 # it supercedes us OR
214 214 # mutual supercedes and it has a higher rank
215 215 # otherwise we win because we're tip-most
216 216 an, ah = nh
217 217 bn, bh = globaltags[k]
218 218 if (bn != an and an in bh and
219 219 (bn not in ah or len(bh) > len(ah))):
220 220 an = bn
221 221 ah.extend([n for n in bh if n not in ah])
222 222 globaltags[k] = an, ah
223 223
224 224 # read the tags file from each head, ending with the tip
225 225 f = None
226 226 for rev, node, fnode in self._hgtagsnodes():
227 227 f = (f and f.filectx(fnode) or
228 228 self.filectx('.hgtags', fileid=fnode))
229 229 readtags(f.data().splitlines(), f)
230 230
231 231 try:
232 232 data = util.fromlocal(self.opener("localtags").read())
233 233 # localtags are stored in the local character set
234 234 # while the internal tag table is stored in UTF-8
235 235 readtags(data.splitlines(), "localtags")
236 236 except IOError:
237 237 pass
238 238
239 239 self.tagscache = {}
240 240 for k,nh in globaltags.items():
241 241 n = nh[0]
242 242 if n != nullid:
243 243 self.tagscache[k] = n
244 244 self.tagscache['tip'] = self.changelog.tip()
245 245
246 246 return self.tagscache
247 247
248 248 def _hgtagsnodes(self):
249 249 heads = self.heads()
250 250 heads.reverse()
251 251 last = {}
252 252 ret = []
253 253 for node in heads:
254 254 c = self.changectx(node)
255 255 rev = c.rev()
256 256 try:
257 257 fnode = c.filenode('.hgtags')
258 258 except revlog.LookupError:
259 259 continue
260 260 ret.append((rev, node, fnode))
261 261 if fnode in last:
262 262 ret[last[fnode]] = None
263 263 last[fnode] = len(ret) - 1
264 264 return [item for item in ret if item]
265 265
266 266 def tagslist(self):
267 267 '''return a list of tags ordered by revision'''
268 268 l = []
269 269 for t, n in self.tags().items():
270 270 try:
271 271 r = self.changelog.rev(n)
272 272 except:
273 273 r = -2 # sort to the beginning of the list if unknown
274 274 l.append((r, t, n))
275 275 l.sort()
276 276 return [(t, n) for r, t, n in l]
277 277
278 278 def nodetags(self, node):
279 279 '''return the tags associated with a node'''
280 280 if not self.nodetagscache:
281 281 self.nodetagscache = {}
282 282 for t, n in self.tags().items():
283 283 self.nodetagscache.setdefault(n, []).append(t)
284 284 return self.nodetagscache.get(node, [])
285 285
286 286 def _branchtags(self):
287 287 partial, last, lrev = self._readbranchcache()
288 288
289 289 tiprev = self.changelog.count() - 1
290 290 if lrev != tiprev:
291 291 self._updatebranchcache(partial, lrev+1, tiprev+1)
292 292 self._writebranchcache(partial, self.changelog.tip(), tiprev)
293 293
294 294 return partial
295 295
296 296 def branchtags(self):
297 297 if self.branchcache is not None:
298 298 return self.branchcache
299 299
300 300 self.branchcache = {} # avoid recursion in changectx
301 301 partial = self._branchtags()
302 302
303 303 # the branch cache is stored on disk as UTF-8, but in the local
304 304 # charset internally
305 305 for k, v in partial.items():
306 306 self.branchcache[util.tolocal(k)] = v
307 307 return self.branchcache
308 308
309 309 def _readbranchcache(self):
310 310 partial = {}
311 311 try:
312 312 f = self.opener("branch.cache")
313 313 lines = f.read().split('\n')
314 314 f.close()
315 315 except (IOError, OSError):
316 316 return {}, nullid, nullrev
317 317
318 318 try:
319 319 last, lrev = lines.pop(0).split(" ", 1)
320 320 last, lrev = bin(last), int(lrev)
321 321 if not (lrev < self.changelog.count() and
322 322 self.changelog.node(lrev) == last): # sanity check
323 323 # invalidate the cache
324 324 raise ValueError('Invalid branch cache: unknown tip')
325 325 for l in lines:
326 326 if not l: continue
327 327 node, label = l.split(" ", 1)
328 328 partial[label.strip()] = bin(node)
329 329 except (KeyboardInterrupt, util.SignalInterrupt):
330 330 raise
331 331 except Exception, inst:
332 332 if self.ui.debugflag:
333 333 self.ui.warn(str(inst), '\n')
334 334 partial, last, lrev = {}, nullid, nullrev
335 335 return partial, last, lrev
336 336
337 337 def _writebranchcache(self, branches, tip, tiprev):
338 338 try:
339 339 f = self.opener("branch.cache", "w", atomictemp=True)
340 340 f.write("%s %s\n" % (hex(tip), tiprev))
341 341 for label, node in branches.iteritems():
342 342 f.write("%s %s\n" % (hex(node), label))
343 343 f.rename()
344 344 except (IOError, OSError):
345 345 pass
346 346
347 347 def _updatebranchcache(self, partial, start, end):
348 348 for r in xrange(start, end):
349 349 c = self.changectx(r)
350 350 b = c.branch()
351 351 partial[b] = c.node()
352 352
353 353 def lookup(self, key):
354 354 if key == '.':
355 355 key, second = self.dirstate.parents()
356 356 if key == nullid:
357 357 raise repo.RepoError(_("no revision checked out"))
358 358 if second != nullid:
359 359 self.ui.warn(_("warning: working directory has two parents, "
360 360 "tag '.' uses the first\n"))
361 361 elif key == 'null':
362 362 return nullid
363 363 n = self.changelog._match(key)
364 364 if n:
365 365 return n
366 366 if key in self.tags():
367 367 return self.tags()[key]
368 368 if key in self.branchtags():
369 369 return self.branchtags()[key]
370 370 n = self.changelog._partialmatch(key)
371 371 if n:
372 372 return n
373 373 raise repo.RepoError(_("unknown revision '%s'") % key)
374 374
375 375 def dev(self):
376 376 return os.lstat(self.path).st_dev
377 377
378 378 def local(self):
379 379 return True
380 380
381 381 def join(self, f):
382 382 return os.path.join(self.path, f)
383 383
384 384 def sjoin(self, f):
385 385 f = self.encodefn(f)
386 386 return os.path.join(self.spath, f)
387 387
388 388 def wjoin(self, f):
389 389 return os.path.join(self.root, f)
390 390
391 391 def file(self, f):
392 392 if f[0] == '/':
393 393 f = f[1:]
394 394 return filelog.filelog(self.sopener, f)
395 395
396 396 def changectx(self, changeid=None):
397 397 return context.changectx(self, changeid)
398 398
399 399 def workingctx(self):
400 400 return context.workingctx(self)
401 401
402 402 def parents(self, changeid=None):
403 403 '''
404 404 get list of changectxs for parents of changeid or working directory
405 405 '''
406 406 if changeid is None:
407 407 pl = self.dirstate.parents()
408 408 else:
409 409 n = self.changelog.lookup(changeid)
410 410 pl = self.changelog.parents(n)
411 411 if pl[1] == nullid:
412 412 return [self.changectx(pl[0])]
413 413 return [self.changectx(pl[0]), self.changectx(pl[1])]
414 414
415 415 def filectx(self, path, changeid=None, fileid=None):
416 416 """changeid can be a changeset revision, node, or tag.
417 417 fileid can be a file revision or node."""
418 418 return context.filectx(self, path, changeid, fileid)
419 419
420 420 def getcwd(self):
421 421 return self.dirstate.getcwd()
422 422
423 423 def pathto(self, f, cwd=None):
424 424 return self.dirstate.pathto(f, cwd)
425 425
426 426 def wfile(self, f, mode='r'):
427 427 return self.wopener(f, mode)
428 428
429 429 def _link(self, f):
430 430 return os.path.islink(self.wjoin(f))
431 431
432 432 def _filter(self, filter, filename, data):
433 433 if filter not in self.filterpats:
434 434 l = []
435 435 for pat, cmd in self.ui.configitems(filter):
436 436 mf = util.matcher(self.root, "", [pat], [], [])[1]
437 437 l.append((mf, cmd))
438 438 self.filterpats[filter] = l
439 439
440 440 for mf, cmd in self.filterpats[filter]:
441 441 if mf(filename):
442 442 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
443 443 data = util.filter(data, cmd)
444 444 break
445 445
446 446 return data
447 447
448 448 def wread(self, filename):
449 449 if self._link(filename):
450 450 data = os.readlink(self.wjoin(filename))
451 451 else:
452 452 data = self.wopener(filename, 'r').read()
453 453 return self._filter("encode", filename, data)
454 454
455 455 def wwrite(self, filename, data, flags):
456 456 data = self._filter("decode", filename, data)
457 457 if "l" in flags:
458 458 f = self.wjoin(filename)
459 459 try:
460 460 os.unlink(f)
461 461 except OSError:
462 462 pass
463 463 d = os.path.dirname(f)
464 464 if not os.path.exists(d):
465 465 os.makedirs(d)
466 466 os.symlink(data, f)
467 467 else:
468 468 try:
469 469 if self._link(filename):
470 470 os.unlink(self.wjoin(filename))
471 471 except OSError:
472 472 pass
473 473 self.wopener(filename, 'w').write(data)
474 474 util.set_exec(self.wjoin(filename), "x" in flags)
475 475
476 476 def wwritedata(self, filename, data):
477 477 return self._filter("decode", filename, data)
478 478
479 479 def transaction(self):
480 480 tr = self.transhandle
481 481 if tr != None and tr.running():
482 482 return tr.nest()
483 483
484 484 # save dirstate for rollback
485 485 try:
486 486 ds = self.opener("dirstate").read()
487 487 except IOError:
488 488 ds = ""
489 489 self.opener("journal.dirstate", "w").write(ds)
490 490
491 491 renames = [(self.sjoin("journal"), self.sjoin("undo")),
492 492 (self.join("journal.dirstate"), self.join("undo.dirstate"))]
493 493 tr = transaction.transaction(self.ui.warn, self.sopener,
494 494 self.sjoin("journal"),
495 495 aftertrans(renames))
496 496 self.transhandle = tr
497 497 return tr
498 498
499 499 def recover(self):
500 500 l = self.lock()
501 501 if os.path.exists(self.sjoin("journal")):
502 502 self.ui.status(_("rolling back interrupted transaction\n"))
503 503 transaction.rollback(self.sopener, self.sjoin("journal"))
504 504 self.invalidate()
505 505 return True
506 506 else:
507 507 self.ui.warn(_("no interrupted transaction available\n"))
508 508 return False
509 509
510 510 def rollback(self, wlock=None, lock=None):
511 511 if not wlock:
512 512 wlock = self.wlock()
513 513 if not lock:
514 514 lock = self.lock()
515 515 if os.path.exists(self.sjoin("undo")):
516 516 self.ui.status(_("rolling back last transaction\n"))
517 517 transaction.rollback(self.sopener, self.sjoin("undo"))
518 518 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
519 519 self.invalidate()
520 520 self.dirstate.invalidate()
521 521 else:
522 522 self.ui.warn(_("no rollback information available\n"))
523 523
524 524 def invalidate(self):
525 525 for a in "changelog manifest".split():
526 526 if hasattr(self, a):
527 527 self.__delattr__(a)
528 528 self.tagscache = None
529 529 self.nodetagscache = None
530 530
531 531 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
532 532 desc=None):
533 533 try:
534 534 l = lock.lock(lockname, 0, releasefn, desc=desc)
535 535 except lock.LockHeld, inst:
536 536 if not wait:
537 537 raise
538 538 self.ui.warn(_("waiting for lock on %s held by %r\n") %
539 539 (desc, inst.locker))
540 540 # default to 600 seconds timeout
541 541 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
542 542 releasefn, desc=desc)
543 543 if acquirefn:
544 544 acquirefn()
545 545 return l
546 546
547 547 def lock(self, wait=1):
548 548 return self.do_lock(self.sjoin("lock"), wait,
549 549 acquirefn=self.invalidate,
550 550 desc=_('repository %s') % self.origroot)
551 551
552 552 def wlock(self, wait=1):
553 553 return self.do_lock(self.join("wlock"), wait, self.dirstate.write,
554 554 self.dirstate.invalidate,
555 555 desc=_('working directory of %s') % self.origroot)
556 556
557 557 def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
558 558 """
559 559 commit an individual file as part of a larger transaction
560 560 """
561 561
562 562 t = self.wread(fn)
563 563 fl = self.file(fn)
564 564 fp1 = manifest1.get(fn, nullid)
565 565 fp2 = manifest2.get(fn, nullid)
566 566
567 567 meta = {}
568 568 cp = self.dirstate.copied(fn)
569 569 if cp:
570 570 # Mark the new revision of this file as a copy of another
571 571 # file. This copy data will effectively act as a parent
572 572 # of this new revision. If this is a merge, the first
573 573 # parent will be the nullid (meaning "look up the copy data")
574 574 # and the second one will be the other parent. For example:
575 575 #
576 576 # 0 --- 1 --- 3 rev1 changes file foo
577 577 # \ / rev2 renames foo to bar and changes it
578 578 # \- 2 -/ rev3 should have bar with all changes and
579 579 # should record that bar descends from
580 580 # bar in rev2 and foo in rev1
581 581 #
582 582 # this allows this merge to succeed:
583 583 #
584 584 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
585 585 # \ / merging rev3 and rev4 should use bar@rev2
586 586 # \- 2 --- 4 as the merge base
587 587 #
588 588 meta["copy"] = cp
589 589 if not manifest2: # not a branch merge
590 590 meta["copyrev"] = hex(manifest1.get(cp, nullid))
591 591 fp2 = nullid
592 592 elif fp2 != nullid: # copied on remote side
593 593 meta["copyrev"] = hex(manifest1.get(cp, nullid))
594 594 elif fp1 != nullid: # copied on local side, reversed
595 595 meta["copyrev"] = hex(manifest2.get(cp))
596 596 fp2 = fp1
597 597 else: # directory rename
598 598 meta["copyrev"] = hex(manifest1.get(cp, nullid))
599 599 self.ui.debug(_(" %s: copy %s:%s\n") %
600 600 (fn, cp, meta["copyrev"]))
601 601 fp1 = nullid
602 602 elif fp2 != nullid:
603 603 # is one parent an ancestor of the other?
604 604 fpa = fl.ancestor(fp1, fp2)
605 605 if fpa == fp1:
606 606 fp1, fp2 = fp2, nullid
607 607 elif fpa == fp2:
608 608 fp2 = nullid
609 609
610 610 # is the file unmodified from the parent? report existing entry
611 611 if fp2 == nullid and not fl.cmp(fp1, t):
612 612 return fp1
613 613
614 614 changelist.append(fn)
615 615 return fl.add(t, meta, transaction, linkrev, fp1, fp2)
616 616
617 617 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None, extra={}):
618 618 if p1 is None:
619 619 p1, p2 = self.dirstate.parents()
620 620 return self.commit(files=files, text=text, user=user, date=date,
621 621 p1=p1, p2=p2, wlock=wlock, extra=extra)
622 622
623 623 def commit(self, files=None, text="", user=None, date=None,
624 624 match=util.always, force=False, lock=None, wlock=None,
625 625 force_editor=False, p1=None, p2=None, extra={}):
626 626
627 627 commit = []
628 628 remove = []
629 629 changed = []
630 630 use_dirstate = (p1 is None) # not rawcommit
631 631 extra = extra.copy()
632 632
633 633 if use_dirstate:
634 634 if files:
635 635 for f in files:
636 636 s = self.dirstate.state(f)
637 637 if s in 'nmai':
638 638 commit.append(f)
639 639 elif s == 'r':
640 640 remove.append(f)
641 641 else:
642 642 self.ui.warn(_("%s not tracked!\n") % f)
643 643 else:
644 644 changes = self.status(match=match)[:5]
645 645 modified, added, removed, deleted, unknown = changes
646 646 commit = modified + added
647 647 remove = removed
648 648 else:
649 649 commit = files
650 650
651 651 if use_dirstate:
652 652 p1, p2 = self.dirstate.parents()
653 653 update_dirstate = True
654 654 else:
655 655 p1, p2 = p1, p2 or nullid
656 656 update_dirstate = (self.dirstate.parents()[0] == p1)
657 657
658 658 c1 = self.changelog.read(p1)
659 659 c2 = self.changelog.read(p2)
660 660 m1 = self.manifest.read(c1[0]).copy()
661 661 m2 = self.manifest.read(c2[0])
662 662
663 663 if use_dirstate:
664 664 branchname = self.workingctx().branch()
665 665 try:
666 666 branchname = branchname.decode('UTF-8').encode('UTF-8')
667 667 except UnicodeDecodeError:
668 668 raise util.Abort(_('branch name not in UTF-8!'))
669 669 else:
670 670 branchname = ""
671 671
672 672 if use_dirstate:
673 673 oldname = c1[5].get("branch") # stored in UTF-8
674 674 if (not commit and not remove and not force and p2 == nullid
675 675 and branchname == oldname):
676 676 self.ui.status(_("nothing changed\n"))
677 677 return None
678 678
679 679 xp1 = hex(p1)
680 680 if p2 == nullid: xp2 = ''
681 681 else: xp2 = hex(p2)
682 682
683 683 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
684 684
685 685 if not wlock:
686 686 wlock = self.wlock()
687 687 if not lock:
688 688 lock = self.lock()
689 689 tr = self.transaction()
690 690
691 691 # check in files
692 692 new = {}
693 693 linkrev = self.changelog.count()
694 694 commit.sort()
695 695 is_exec = util.execfunc(self.root, m1.execf)
696 696 is_link = util.linkfunc(self.root, m1.linkf)
697 697 for f in commit:
698 698 self.ui.note(f + "\n")
699 699 try:
700 700 new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
701 701 new_exec = is_exec(f)
702 702 new_link = is_link(f)
703 703 if not changed or changed[-1] != f:
704 704 # mention the file in the changelog if some flag changed,
705 705 # even if there was no content change.
706 706 old_exec = m1.execf(f)
707 707 old_link = m1.linkf(f)
708 708 if old_exec != new_exec or old_link != new_link:
709 709 changed.append(f)
710 710 m1.set(f, new_exec, new_link)
711 711 except (OSError, IOError):
712 712 if use_dirstate:
713 713 self.ui.warn(_("trouble committing %s!\n") % f)
714 714 raise
715 715 else:
716 716 remove.append(f)
717 717
718 718 # update manifest
719 719 m1.update(new)
720 720 remove.sort()
721 721 removed = []
722 722
723 723 for f in remove:
724 724 if f in m1:
725 725 del m1[f]
726 726 removed.append(f)
727 727 elif f in m2:
728 728 removed.append(f)
729 729 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed))
730 730
731 731 # add changeset
732 732 new = new.keys()
733 733 new.sort()
734 734
735 735 user = user or self.ui.username()
736 736 if not text or force_editor:
737 737 edittext = []
738 738 if text:
739 739 edittext.append(text)
740 740 edittext.append("")
741 741 edittext.append("HG: user: %s" % user)
742 742 if p2 != nullid:
743 743 edittext.append("HG: branch merge")
744 744 if branchname:
745 745 edittext.append("HG: branch %s" % util.tolocal(branchname))
746 746 edittext.extend(["HG: changed %s" % f for f in changed])
747 747 edittext.extend(["HG: removed %s" % f for f in removed])
748 748 if not changed and not remove:
749 749 edittext.append("HG: no files changed")
750 750 edittext.append("")
751 751 # run editor in the repository root
752 752 olddir = os.getcwd()
753 753 os.chdir(self.root)
754 754 text = self.ui.edit("\n".join(edittext), user)
755 755 os.chdir(olddir)
756 756
757 757 lines = [line.rstrip() for line in text.rstrip().splitlines()]
758 758 while lines and not lines[0]:
759 759 del lines[0]
760 760 if not lines:
761 761 return None
762 762 text = '\n'.join(lines)
763 763 if branchname:
764 764 extra["branch"] = branchname
765 765 n = self.changelog.add(mn, changed + removed, text, tr, p1, p2,
766 766 user, date, extra)
767 767 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
768 768 parent2=xp2)
769 769 tr.close()
770 770
771 771 if self.branchcache and "branch" in extra:
772 772 self.branchcache[util.tolocal(extra["branch"])] = n
773 773
774 774 if use_dirstate or update_dirstate:
775 775 self.dirstate.setparents(n)
776 776 if use_dirstate:
777 777 self.dirstate.update(new, "n")
778 778 self.dirstate.forget(removed)
779 779
780 780 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
781 781 return n
782 782
783 783 def walk(self, node=None, files=[], match=util.always, badmatch=None):
784 784 '''
785 785 walk recursively through the directory tree or a given
786 786 changeset, finding all files matched by the match
787 787 function
788 788
789 789 results are yielded in a tuple (src, filename), where src
790 790 is one of:
791 791 'f' the file was found in the directory tree
792 792 'm' the file was only in the dirstate and not in the tree
793 793 'b' file was not found and matched badmatch
794 794 '''
795 795
796 796 if node:
797 797 fdict = dict.fromkeys(files)
798 798 # for dirstate.walk, files=['.'] means "walk the whole tree".
799 799 # follow that here, too
800 800 fdict.pop('.', None)
801 801 mdict = self.manifest.read(self.changelog.read(node)[0])
802 802 mfiles = mdict.keys()
803 803 mfiles.sort()
804 804 for fn in mfiles:
805 805 for ffn in fdict:
806 806 # match if the file is the exact name or a directory
807 807 if ffn == fn or fn.startswith("%s/" % ffn):
808 808 del fdict[ffn]
809 809 break
810 810 if match(fn):
811 811 yield 'm', fn
812 812 ffiles = fdict.keys()
813 813 ffiles.sort()
814 814 for fn in ffiles:
815 815 if badmatch and badmatch(fn):
816 816 if match(fn):
817 817 yield 'b', fn
818 818 else:
819 819 self.ui.warn(_('%s: No such file in rev %s\n')
820 820 % (self.pathto(fn), short(node)))
821 821 else:
822 822 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
823 823 yield src, fn
824 824
825 825 def status(self, node1=None, node2=None, files=[], match=util.always,
826 826 wlock=None, list_ignored=False, list_clean=False):
827 827 """return status of files between two nodes or node and working directory
828 828
829 829 If node1 is None, use the first dirstate parent instead.
830 830 If node2 is None, compare node1 with working directory.
831 831 """
832 832
833 833 def fcmp(fn, getnode):
834 834 t1 = self.wread(fn)
835 835 return self.file(fn).cmp(getnode(fn), t1)
836 836
837 837 def mfmatches(node):
838 838 change = self.changelog.read(node)
839 839 mf = self.manifest.read(change[0]).copy()
840 840 for fn in mf.keys():
841 841 if not match(fn):
842 842 del mf[fn]
843 843 return mf
844 844
845 845 modified, added, removed, deleted, unknown = [], [], [], [], []
846 846 ignored, clean = [], []
847 847
848 848 compareworking = False
849 849 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
850 850 compareworking = True
851 851
852 852 if not compareworking:
853 853 # read the manifest from node1 before the manifest from node2,
854 854 # so that we'll hit the manifest cache if we're going through
855 855 # all the revisions in parent->child order.
856 856 mf1 = mfmatches(node1)
857 857
858 858 mywlock = False
859 859
860 860 # are we comparing the working directory?
861 861 if not node2:
862 862 (lookup, modified, added, removed, deleted, unknown,
863 863 ignored, clean) = self.dirstate.status(files, match,
864 864 list_ignored, list_clean)
865 865
866 866 # are we comparing working dir against its parent?
867 867 if compareworking:
868 868 if lookup:
869 869 # do a full compare of any files that might have changed
870 870 mnode = self.changelog.read(self.dirstate.parents()[0])[0]
871 871 getnode = lambda fn: (self.manifest.find(mnode, fn)[0] or
872 872 nullid)
873 873 for f in lookup:
874 874 if fcmp(f, getnode):
875 875 modified.append(f)
876 876 else:
877 877 if list_clean:
878 878 clean.append(f)
879 879 if not wlock and not mywlock:
880 880 mywlock = True
881 881 try:
882 882 wlock = self.wlock(wait=0)
883 883 except lock.LockException:
884 884 pass
885 885 if wlock:
886 886 self.dirstate.update([f], "n")
887 887 else:
888 888 # we are comparing working dir against non-parent
889 889 # generate a pseudo-manifest for the working dir
890 890 # XXX: create it in dirstate.py ?
891 891 mf2 = mfmatches(self.dirstate.parents()[0])
892 892 is_exec = util.execfunc(self.root, mf2.execf)
893 893 is_link = util.linkfunc(self.root, mf2.linkf)
894 894 for f in lookup + modified + added:
895 895 mf2[f] = ""
896 896 mf2.set(f, is_exec(f), is_link(f))
897 897 for f in removed:
898 898 if f in mf2:
899 899 del mf2[f]
900 900
901 901 if mywlock and wlock:
902 902 wlock.release()
903 903 else:
904 904 # we are comparing two revisions
905 905 mf2 = mfmatches(node2)
906 906
907 907 if not compareworking:
908 908 # flush lists from dirstate before comparing manifests
909 909 modified, added, clean = [], [], []
910 910
911 911 # make sure to sort the files so we talk to the disk in a
912 912 # reasonable order
913 913 mf2keys = mf2.keys()
914 914 mf2keys.sort()
915 915 getnode = lambda fn: mf1.get(fn, nullid)
916 916 for fn in mf2keys:
917 917 if mf1.has_key(fn):
918 918 if (mf1.flags(fn) != mf2.flags(fn) or
919 919 (mf1[fn] != mf2[fn] and
920 920 (mf2[fn] != "" or fcmp(fn, getnode)))):
921 921 modified.append(fn)
922 922 elif list_clean:
923 923 clean.append(fn)
924 924 del mf1[fn]
925 925 else:
926 926 added.append(fn)
927 927
928 928 removed = mf1.keys()
929 929
930 930 # sort and return results:
931 931 for l in modified, added, removed, deleted, unknown, ignored, clean:
932 932 l.sort()
933 933 return (modified, added, removed, deleted, unknown, ignored, clean)
934 934
935 935 def add(self, list, wlock=None):
936 936 if not wlock:
937 937 wlock = self.wlock()
938 938 for f in list:
939 939 p = self.wjoin(f)
940 940 try:
941 941 st = os.lstat(p)
942 942 except:
943 943 self.ui.warn(_("%s does not exist!\n") % f)
944 944 continue
945 945 if st.st_size > 10000000:
946 946 self.ui.warn(_("%s: files over 10MB may cause memory and"
947 947 " performance problems\n"
948 948 "(use 'hg revert %s' to unadd the file)\n")
949 949 % (f, f))
950 950 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
951 951 self.ui.warn(_("%s not added: only files and symlinks "
952 952 "supported currently\n") % f)
953 953 elif self.dirstate.state(f) in 'an':
954 954 self.ui.warn(_("%s already tracked!\n") % f)
955 955 else:
956 956 self.dirstate.update([f], "a")
957 957
958 958 def forget(self, list, wlock=None):
959 959 if not wlock:
960 960 wlock = self.wlock()
961 961 for f in list:
962 962 if self.dirstate.state(f) not in 'ai':
963 963 self.ui.warn(_("%s not added!\n") % f)
964 964 else:
965 965 self.dirstate.forget([f])
966 966
967 967 def remove(self, list, unlink=False, wlock=None):
968 968 if unlink:
969 969 for f in list:
970 970 try:
971 971 util.unlink(self.wjoin(f))
972 972 except OSError, inst:
973 973 if inst.errno != errno.ENOENT:
974 974 raise
975 975 if not wlock:
976 976 wlock = self.wlock()
977 977 for f in list:
978 978 if unlink and os.path.exists(self.wjoin(f)):
979 979 self.ui.warn(_("%s still exists!\n") % f)
980 980 elif self.dirstate.state(f) == 'a':
981 981 self.dirstate.forget([f])
982 982 elif f not in self.dirstate:
983 983 self.ui.warn(_("%s not tracked!\n") % f)
984 984 else:
985 985 self.dirstate.update([f], "r")
986 986
987 987 def undelete(self, list, wlock=None):
988 988 p = self.dirstate.parents()[0]
989 989 mn = self.changelog.read(p)[0]
990 990 m = self.manifest.read(mn)
991 991 if not wlock:
992 992 wlock = self.wlock()
993 993 for f in list:
994 994 if self.dirstate.state(f) not in "r":
995 995 self.ui.warn("%s not removed!\n" % f)
996 996 else:
997 997 t = self.file(f).read(m[f])
998 998 self.wwrite(f, t, m.flags(f))
999 999 self.dirstate.update([f], "n")
1000 1000
1001 1001 def copy(self, source, dest, wlock=None):
1002 1002 p = self.wjoin(dest)
1003 1003 if not (os.path.exists(p) or os.path.islink(p)):
1004 1004 self.ui.warn(_("%s does not exist!\n") % dest)
1005 1005 elif not (os.path.isfile(p) or os.path.islink(p)):
1006 1006 self.ui.warn(_("copy failed: %s is not a file or a "
1007 1007 "symbolic link\n") % dest)
1008 1008 else:
1009 1009 if not wlock:
1010 1010 wlock = self.wlock()
1011 1011 if self.dirstate.state(dest) == '?':
1012 1012 self.dirstate.update([dest], "a")
1013 1013 self.dirstate.copy(source, dest)
1014 1014
1015 1015 def heads(self, start=None):
1016 1016 heads = self.changelog.heads(start)
1017 1017 # sort the output in rev descending order
1018 1018 heads = [(-self.changelog.rev(h), h) for h in heads]
1019 1019 heads.sort()
1020 1020 return [n for (r, n) in heads]
1021 1021
1022 def branchheads(self, branch, start=None):
1023 branches = self.branchtags()
1024 if branch not in branches:
1025 return []
1026 # The basic algorithm is this:
1027 #
1028 # Start from the branch tip since there are no later revisions that can
1029 # possibly be in this branch, and the tip is a guaranteed head.
1030 #
1031 # Remember the tip's parents as the first ancestors, since these by
1032 # definition are not heads.
1033 #
1034 # Step backwards from the brach tip through all the revisions. We are
1035 # guaranteed by the rules of Mercurial that we will now be visiting the
1036 # nodes in reverse topological order (children before parents).
1037 #
1038 # If a revision is one of the ancestors of a head then we can toss it
1039 # out of the ancestors set (we've already found it and won't be
1040 # visiting it again) and put its parents in the ancestors set.
1041 #
1042 # Otherwise, if a revision is in the branch it's another head, since it
1043 # wasn't in the ancestor list of an existing head. So add it to the
1044 # head list, and add its parents to the ancestor list.
1045 #
1046 # If it is not in the branch ignore it.
1047 #
1048 # Once we have a list of heads, use nodesbetween to filter out all the
1049 # heads that cannot be reached from startrev. There may be a more
1050 # efficient way to do this as part of the previous algorithm.
1051
1052 set = util.set
1053 heads = [self.changelog.rev(branches[branch])]
1054 # Don't care if ancestors contains nullrev or not.
1055 ancestors = set(self.changelog.parentrevs(heads[0]))
1056 for rev in xrange(heads[0] - 1, nullrev, -1):
1057 if rev in ancestors:
1058 ancestors.update(self.changelog.parentrevs(rev))
1059 ancestors.remove(rev)
1060 elif self.changectx(rev).branch() == branch:
1061 heads.append(rev)
1062 ancestors.update(self.changelog.parentrevs(rev))
1063 heads = [self.changelog.node(rev) for rev in heads]
1064 if start is not None:
1065 heads = self.changelog.nodesbetween([start], heads)[2]
1066 return heads
1067
1022 1068 def branches(self, nodes):
1023 1069 if not nodes:
1024 1070 nodes = [self.changelog.tip()]
1025 1071 b = []
1026 1072 for n in nodes:
1027 1073 t = n
1028 1074 while 1:
1029 1075 p = self.changelog.parents(n)
1030 1076 if p[1] != nullid or p[0] == nullid:
1031 1077 b.append((t, n, p[0], p[1]))
1032 1078 break
1033 1079 n = p[0]
1034 1080 return b
1035 1081
1036 1082 def between(self, pairs):
1037 1083 r = []
1038 1084
1039 1085 for top, bottom in pairs:
1040 1086 n, l, i = top, [], 0
1041 1087 f = 1
1042 1088
1043 1089 while n != bottom:
1044 1090 p = self.changelog.parents(n)[0]
1045 1091 if i == f:
1046 1092 l.append(n)
1047 1093 f = f * 2
1048 1094 n = p
1049 1095 i += 1
1050 1096
1051 1097 r.append(l)
1052 1098
1053 1099 return r
1054 1100
1055 1101 def findincoming(self, remote, base=None, heads=None, force=False):
1056 1102 """Return list of roots of the subsets of missing nodes from remote
1057 1103
1058 1104 If base dict is specified, assume that these nodes and their parents
1059 1105 exist on the remote side and that no child of a node of base exists
1060 1106 in both remote and self.
1061 1107 Furthermore base will be updated to include the nodes that exists
1062 1108 in self and remote but no children exists in self and remote.
1063 1109 If a list of heads is specified, return only nodes which are heads
1064 1110 or ancestors of these heads.
1065 1111
1066 1112 All the ancestors of base are in self and in remote.
1067 1113 All the descendants of the list returned are missing in self.
1068 1114 (and so we know that the rest of the nodes are missing in remote, see
1069 1115 outgoing)
1070 1116 """
1071 1117 m = self.changelog.nodemap
1072 1118 search = []
1073 1119 fetch = {}
1074 1120 seen = {}
1075 1121 seenbranch = {}
1076 1122 if base == None:
1077 1123 base = {}
1078 1124
1079 1125 if not heads:
1080 1126 heads = remote.heads()
1081 1127
1082 1128 if self.changelog.tip() == nullid:
1083 1129 base[nullid] = 1
1084 1130 if heads != [nullid]:
1085 1131 return [nullid]
1086 1132 return []
1087 1133
1088 1134 # assume we're closer to the tip than the root
1089 1135 # and start by examining the heads
1090 1136 self.ui.status(_("searching for changes\n"))
1091 1137
1092 1138 unknown = []
1093 1139 for h in heads:
1094 1140 if h not in m:
1095 1141 unknown.append(h)
1096 1142 else:
1097 1143 base[h] = 1
1098 1144
1099 1145 if not unknown:
1100 1146 return []
1101 1147
1102 1148 req = dict.fromkeys(unknown)
1103 1149 reqcnt = 0
1104 1150
1105 1151 # search through remote branches
1106 1152 # a 'branch' here is a linear segment of history, with four parts:
1107 1153 # head, root, first parent, second parent
1108 1154 # (a branch always has two parents (or none) by definition)
1109 1155 unknown = remote.branches(unknown)
1110 1156 while unknown:
1111 1157 r = []
1112 1158 while unknown:
1113 1159 n = unknown.pop(0)
1114 1160 if n[0] in seen:
1115 1161 continue
1116 1162
1117 1163 self.ui.debug(_("examining %s:%s\n")
1118 1164 % (short(n[0]), short(n[1])))
1119 1165 if n[0] == nullid: # found the end of the branch
1120 1166 pass
1121 1167 elif n in seenbranch:
1122 1168 self.ui.debug(_("branch already found\n"))
1123 1169 continue
1124 1170 elif n[1] and n[1] in m: # do we know the base?
1125 1171 self.ui.debug(_("found incomplete branch %s:%s\n")
1126 1172 % (short(n[0]), short(n[1])))
1127 1173 search.append(n) # schedule branch range for scanning
1128 1174 seenbranch[n] = 1
1129 1175 else:
1130 1176 if n[1] not in seen and n[1] not in fetch:
1131 1177 if n[2] in m and n[3] in m:
1132 1178 self.ui.debug(_("found new changeset %s\n") %
1133 1179 short(n[1]))
1134 1180 fetch[n[1]] = 1 # earliest unknown
1135 1181 for p in n[2:4]:
1136 1182 if p in m:
1137 1183 base[p] = 1 # latest known
1138 1184
1139 1185 for p in n[2:4]:
1140 1186 if p not in req and p not in m:
1141 1187 r.append(p)
1142 1188 req[p] = 1
1143 1189 seen[n[0]] = 1
1144 1190
1145 1191 if r:
1146 1192 reqcnt += 1
1147 1193 self.ui.debug(_("request %d: %s\n") %
1148 1194 (reqcnt, " ".join(map(short, r))))
1149 1195 for p in xrange(0, len(r), 10):
1150 1196 for b in remote.branches(r[p:p+10]):
1151 1197 self.ui.debug(_("received %s:%s\n") %
1152 1198 (short(b[0]), short(b[1])))
1153 1199 unknown.append(b)
1154 1200
1155 1201 # do binary search on the branches we found
1156 1202 while search:
1157 1203 n = search.pop(0)
1158 1204 reqcnt += 1
1159 1205 l = remote.between([(n[0], n[1])])[0]
1160 1206 l.append(n[1])
1161 1207 p = n[0]
1162 1208 f = 1
1163 1209 for i in l:
1164 1210 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1165 1211 if i in m:
1166 1212 if f <= 2:
1167 1213 self.ui.debug(_("found new branch changeset %s\n") %
1168 1214 short(p))
1169 1215 fetch[p] = 1
1170 1216 base[i] = 1
1171 1217 else:
1172 1218 self.ui.debug(_("narrowed branch search to %s:%s\n")
1173 1219 % (short(p), short(i)))
1174 1220 search.append((p, i))
1175 1221 break
1176 1222 p, f = i, f * 2
1177 1223
1178 1224 # sanity check our fetch list
1179 1225 for f in fetch.keys():
1180 1226 if f in m:
1181 1227 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1182 1228
1183 1229 if base.keys() == [nullid]:
1184 1230 if force:
1185 1231 self.ui.warn(_("warning: repository is unrelated\n"))
1186 1232 else:
1187 1233 raise util.Abort(_("repository is unrelated"))
1188 1234
1189 1235 self.ui.debug(_("found new changesets starting at ") +
1190 1236 " ".join([short(f) for f in fetch]) + "\n")
1191 1237
1192 1238 self.ui.debug(_("%d total queries\n") % reqcnt)
1193 1239
1194 1240 return fetch.keys()
1195 1241
1196 1242 def findoutgoing(self, remote, base=None, heads=None, force=False):
1197 1243 """Return list of nodes that are roots of subsets not in remote
1198 1244
1199 1245 If base dict is specified, assume that these nodes and their parents
1200 1246 exist on the remote side.
1201 1247 If a list of heads is specified, return only nodes which are heads
1202 1248 or ancestors of these heads, and return a second element which
1203 1249 contains all remote heads which get new children.
1204 1250 """
1205 1251 if base == None:
1206 1252 base = {}
1207 1253 self.findincoming(remote, base, heads, force=force)
1208 1254
1209 1255 self.ui.debug(_("common changesets up to ")
1210 1256 + " ".join(map(short, base.keys())) + "\n")
1211 1257
1212 1258 remain = dict.fromkeys(self.changelog.nodemap)
1213 1259
1214 1260 # prune everything remote has from the tree
1215 1261 del remain[nullid]
1216 1262 remove = base.keys()
1217 1263 while remove:
1218 1264 n = remove.pop(0)
1219 1265 if n in remain:
1220 1266 del remain[n]
1221 1267 for p in self.changelog.parents(n):
1222 1268 remove.append(p)
1223 1269
1224 1270 # find every node whose parents have been pruned
1225 1271 subset = []
1226 1272 # find every remote head that will get new children
1227 1273 updated_heads = {}
1228 1274 for n in remain:
1229 1275 p1, p2 = self.changelog.parents(n)
1230 1276 if p1 not in remain and p2 not in remain:
1231 1277 subset.append(n)
1232 1278 if heads:
1233 1279 if p1 in heads:
1234 1280 updated_heads[p1] = True
1235 1281 if p2 in heads:
1236 1282 updated_heads[p2] = True
1237 1283
1238 1284 # this is the set of all roots we have to push
1239 1285 if heads:
1240 1286 return subset, updated_heads.keys()
1241 1287 else:
1242 1288 return subset
1243 1289
1244 1290 def pull(self, remote, heads=None, force=False, lock=None):
1245 1291 mylock = False
1246 1292 if not lock:
1247 1293 lock = self.lock()
1248 1294 mylock = True
1249 1295
1250 1296 try:
1251 1297 fetch = self.findincoming(remote, force=force)
1252 1298 if fetch == [nullid]:
1253 1299 self.ui.status(_("requesting all changes\n"))
1254 1300
1255 1301 if not fetch:
1256 1302 self.ui.status(_("no changes found\n"))
1257 1303 return 0
1258 1304
1259 1305 if heads is None:
1260 1306 cg = remote.changegroup(fetch, 'pull')
1261 1307 else:
1262 1308 if 'changegroupsubset' not in remote.capabilities:
1263 1309 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1264 1310 cg = remote.changegroupsubset(fetch, heads, 'pull')
1265 1311 return self.addchangegroup(cg, 'pull', remote.url())
1266 1312 finally:
1267 1313 if mylock:
1268 1314 lock.release()
1269 1315
1270 1316 def push(self, remote, force=False, revs=None):
1271 1317 # there are two ways to push to remote repo:
1272 1318 #
1273 1319 # addchangegroup assumes local user can lock remote
1274 1320 # repo (local filesystem, old ssh servers).
1275 1321 #
1276 1322 # unbundle assumes local user cannot lock remote repo (new ssh
1277 1323 # servers, http servers).
1278 1324
1279 1325 if remote.capable('unbundle'):
1280 1326 return self.push_unbundle(remote, force, revs)
1281 1327 return self.push_addchangegroup(remote, force, revs)
1282 1328
1283 1329 def prepush(self, remote, force, revs):
1284 1330 base = {}
1285 1331 remote_heads = remote.heads()
1286 1332 inc = self.findincoming(remote, base, remote_heads, force=force)
1287 1333
1288 1334 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1289 1335 if revs is not None:
1290 1336 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1291 1337 else:
1292 1338 bases, heads = update, self.changelog.heads()
1293 1339
1294 1340 if not bases:
1295 1341 self.ui.status(_("no changes found\n"))
1296 1342 return None, 1
1297 1343 elif not force:
1298 1344 # check if we're creating new remote heads
1299 1345 # to be a remote head after push, node must be either
1300 1346 # - unknown locally
1301 1347 # - a local outgoing head descended from update
1302 1348 # - a remote head that's known locally and not
1303 1349 # ancestral to an outgoing head
1304 1350
1305 1351 warn = 0
1306 1352
1307 1353 if remote_heads == [nullid]:
1308 1354 warn = 0
1309 1355 elif not revs and len(heads) > len(remote_heads):
1310 1356 warn = 1
1311 1357 else:
1312 1358 newheads = list(heads)
1313 1359 for r in remote_heads:
1314 1360 if r in self.changelog.nodemap:
1315 1361 desc = self.changelog.heads(r, heads)
1316 1362 l = [h for h in heads if h in desc]
1317 1363 if not l:
1318 1364 newheads.append(r)
1319 1365 else:
1320 1366 newheads.append(r)
1321 1367 if len(newheads) > len(remote_heads):
1322 1368 warn = 1
1323 1369
1324 1370 if warn:
1325 1371 self.ui.warn(_("abort: push creates new remote branches!\n"))
1326 1372 self.ui.status(_("(did you forget to merge?"
1327 1373 " use push -f to force)\n"))
1328 1374 return None, 1
1329 1375 elif inc:
1330 1376 self.ui.warn(_("note: unsynced remote changes!\n"))
1331 1377
1332 1378
1333 1379 if revs is None:
1334 1380 cg = self.changegroup(update, 'push')
1335 1381 else:
1336 1382 cg = self.changegroupsubset(update, revs, 'push')
1337 1383 return cg, remote_heads
1338 1384
1339 1385 def push_addchangegroup(self, remote, force, revs):
1340 1386 lock = remote.lock()
1341 1387
1342 1388 ret = self.prepush(remote, force, revs)
1343 1389 if ret[0] is not None:
1344 1390 cg, remote_heads = ret
1345 1391 return remote.addchangegroup(cg, 'push', self.url())
1346 1392 return ret[1]
1347 1393
1348 1394 def push_unbundle(self, remote, force, revs):
1349 1395 # local repo finds heads on server, finds out what revs it
1350 1396 # must push. once revs transferred, if server finds it has
1351 1397 # different heads (someone else won commit/push race), server
1352 1398 # aborts.
1353 1399
1354 1400 ret = self.prepush(remote, force, revs)
1355 1401 if ret[0] is not None:
1356 1402 cg, remote_heads = ret
1357 1403 if force: remote_heads = ['force']
1358 1404 return remote.unbundle(cg, remote_heads, 'push')
1359 1405 return ret[1]
1360 1406
1361 1407 def changegroupinfo(self, nodes):
1362 1408 self.ui.note(_("%d changesets found\n") % len(nodes))
1363 1409 if self.ui.debugflag:
1364 1410 self.ui.debug(_("List of changesets:\n"))
1365 1411 for node in nodes:
1366 1412 self.ui.debug("%s\n" % hex(node))
1367 1413
1368 1414 def changegroupsubset(self, bases, heads, source):
1369 1415 """This function generates a changegroup consisting of all the nodes
1370 1416 that are descendents of any of the bases, and ancestors of any of
1371 1417 the heads.
1372 1418
1373 1419 It is fairly complex as determining which filenodes and which
1374 1420 manifest nodes need to be included for the changeset to be complete
1375 1421 is non-trivial.
1376 1422
1377 1423 Another wrinkle is doing the reverse, figuring out which changeset in
1378 1424 the changegroup a particular filenode or manifestnode belongs to."""
1379 1425
1380 1426 self.hook('preoutgoing', throw=True, source=source)
1381 1427
1382 1428 # Set up some initial variables
1383 1429 # Make it easy to refer to self.changelog
1384 1430 cl = self.changelog
1385 1431 # msng is short for missing - compute the list of changesets in this
1386 1432 # changegroup.
1387 1433 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1388 1434 self.changegroupinfo(msng_cl_lst)
1389 1435 # Some bases may turn out to be superfluous, and some heads may be
1390 1436 # too. nodesbetween will return the minimal set of bases and heads
1391 1437 # necessary to re-create the changegroup.
1392 1438
1393 1439 # Known heads are the list of heads that it is assumed the recipient
1394 1440 # of this changegroup will know about.
1395 1441 knownheads = {}
1396 1442 # We assume that all parents of bases are known heads.
1397 1443 for n in bases:
1398 1444 for p in cl.parents(n):
1399 1445 if p != nullid:
1400 1446 knownheads[p] = 1
1401 1447 knownheads = knownheads.keys()
1402 1448 if knownheads:
1403 1449 # Now that we know what heads are known, we can compute which
1404 1450 # changesets are known. The recipient must know about all
1405 1451 # changesets required to reach the known heads from the null
1406 1452 # changeset.
1407 1453 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1408 1454 junk = None
1409 1455 # Transform the list into an ersatz set.
1410 1456 has_cl_set = dict.fromkeys(has_cl_set)
1411 1457 else:
1412 1458 # If there were no known heads, the recipient cannot be assumed to
1413 1459 # know about any changesets.
1414 1460 has_cl_set = {}
1415 1461
1416 1462 # Make it easy to refer to self.manifest
1417 1463 mnfst = self.manifest
1418 1464 # We don't know which manifests are missing yet
1419 1465 msng_mnfst_set = {}
1420 1466 # Nor do we know which filenodes are missing.
1421 1467 msng_filenode_set = {}
1422 1468
1423 1469 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1424 1470 junk = None
1425 1471
1426 1472 # A changeset always belongs to itself, so the changenode lookup
1427 1473 # function for a changenode is identity.
1428 1474 def identity(x):
1429 1475 return x
1430 1476
1431 1477 # A function generating function. Sets up an environment for the
1432 1478 # inner function.
1433 1479 def cmp_by_rev_func(revlog):
1434 1480 # Compare two nodes by their revision number in the environment's
1435 1481 # revision history. Since the revision number both represents the
1436 1482 # most efficient order to read the nodes in, and represents a
1437 1483 # topological sorting of the nodes, this function is often useful.
1438 1484 def cmp_by_rev(a, b):
1439 1485 return cmp(revlog.rev(a), revlog.rev(b))
1440 1486 return cmp_by_rev
1441 1487
1442 1488 # If we determine that a particular file or manifest node must be a
1443 1489 # node that the recipient of the changegroup will already have, we can
1444 1490 # also assume the recipient will have all the parents. This function
1445 1491 # prunes them from the set of missing nodes.
1446 1492 def prune_parents(revlog, hasset, msngset):
1447 1493 haslst = hasset.keys()
1448 1494 haslst.sort(cmp_by_rev_func(revlog))
1449 1495 for node in haslst:
1450 1496 parentlst = [p for p in revlog.parents(node) if p != nullid]
1451 1497 while parentlst:
1452 1498 n = parentlst.pop()
1453 1499 if n not in hasset:
1454 1500 hasset[n] = 1
1455 1501 p = [p for p in revlog.parents(n) if p != nullid]
1456 1502 parentlst.extend(p)
1457 1503 for n in hasset:
1458 1504 msngset.pop(n, None)
1459 1505
1460 1506 # This is a function generating function used to set up an environment
1461 1507 # for the inner function to execute in.
1462 1508 def manifest_and_file_collector(changedfileset):
1463 1509 # This is an information gathering function that gathers
1464 1510 # information from each changeset node that goes out as part of
1465 1511 # the changegroup. The information gathered is a list of which
1466 1512 # manifest nodes are potentially required (the recipient may
1467 1513 # already have them) and total list of all files which were
1468 1514 # changed in any changeset in the changegroup.
1469 1515 #
1470 1516 # We also remember the first changenode we saw any manifest
1471 1517 # referenced by so we can later determine which changenode 'owns'
1472 1518 # the manifest.
1473 1519 def collect_manifests_and_files(clnode):
1474 1520 c = cl.read(clnode)
1475 1521 for f in c[3]:
1476 1522 # This is to make sure we only have one instance of each
1477 1523 # filename string for each filename.
1478 1524 changedfileset.setdefault(f, f)
1479 1525 msng_mnfst_set.setdefault(c[0], clnode)
1480 1526 return collect_manifests_and_files
1481 1527
1482 1528 # Figure out which manifest nodes (of the ones we think might be part
1483 1529 # of the changegroup) the recipient must know about and remove them
1484 1530 # from the changegroup.
1485 1531 def prune_manifests():
1486 1532 has_mnfst_set = {}
1487 1533 for n in msng_mnfst_set:
1488 1534 # If a 'missing' manifest thinks it belongs to a changenode
1489 1535 # the recipient is assumed to have, obviously the recipient
1490 1536 # must have that manifest.
1491 1537 linknode = cl.node(mnfst.linkrev(n))
1492 1538 if linknode in has_cl_set:
1493 1539 has_mnfst_set[n] = 1
1494 1540 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1495 1541
1496 1542 # Use the information collected in collect_manifests_and_files to say
1497 1543 # which changenode any manifestnode belongs to.
1498 1544 def lookup_manifest_link(mnfstnode):
1499 1545 return msng_mnfst_set[mnfstnode]
1500 1546
1501 1547 # A function generating function that sets up the initial environment
1502 1548 # the inner function.
1503 1549 def filenode_collector(changedfiles):
1504 1550 next_rev = [0]
1505 1551 # This gathers information from each manifestnode included in the
1506 1552 # changegroup about which filenodes the manifest node references
1507 1553 # so we can include those in the changegroup too.
1508 1554 #
1509 1555 # It also remembers which changenode each filenode belongs to. It
1510 1556 # does this by assuming the a filenode belongs to the changenode
1511 1557 # the first manifest that references it belongs to.
1512 1558 def collect_msng_filenodes(mnfstnode):
1513 1559 r = mnfst.rev(mnfstnode)
1514 1560 if r == next_rev[0]:
1515 1561 # If the last rev we looked at was the one just previous,
1516 1562 # we only need to see a diff.
1517 1563 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1518 1564 # For each line in the delta
1519 1565 for dline in delta.splitlines():
1520 1566 # get the filename and filenode for that line
1521 1567 f, fnode = dline.split('\0')
1522 1568 fnode = bin(fnode[:40])
1523 1569 f = changedfiles.get(f, None)
1524 1570 # And if the file is in the list of files we care
1525 1571 # about.
1526 1572 if f is not None:
1527 1573 # Get the changenode this manifest belongs to
1528 1574 clnode = msng_mnfst_set[mnfstnode]
1529 1575 # Create the set of filenodes for the file if
1530 1576 # there isn't one already.
1531 1577 ndset = msng_filenode_set.setdefault(f, {})
1532 1578 # And set the filenode's changelog node to the
1533 1579 # manifest's if it hasn't been set already.
1534 1580 ndset.setdefault(fnode, clnode)
1535 1581 else:
1536 1582 # Otherwise we need a full manifest.
1537 1583 m = mnfst.read(mnfstnode)
1538 1584 # For every file in we care about.
1539 1585 for f in changedfiles:
1540 1586 fnode = m.get(f, None)
1541 1587 # If it's in the manifest
1542 1588 if fnode is not None:
1543 1589 # See comments above.
1544 1590 clnode = msng_mnfst_set[mnfstnode]
1545 1591 ndset = msng_filenode_set.setdefault(f, {})
1546 1592 ndset.setdefault(fnode, clnode)
1547 1593 # Remember the revision we hope to see next.
1548 1594 next_rev[0] = r + 1
1549 1595 return collect_msng_filenodes
1550 1596
1551 1597 # We have a list of filenodes we think we need for a file, lets remove
1552 1598 # all those we now the recipient must have.
1553 1599 def prune_filenodes(f, filerevlog):
1554 1600 msngset = msng_filenode_set[f]
1555 1601 hasset = {}
1556 1602 # If a 'missing' filenode thinks it belongs to a changenode we
1557 1603 # assume the recipient must have, then the recipient must have
1558 1604 # that filenode.
1559 1605 for n in msngset:
1560 1606 clnode = cl.node(filerevlog.linkrev(n))
1561 1607 if clnode in has_cl_set:
1562 1608 hasset[n] = 1
1563 1609 prune_parents(filerevlog, hasset, msngset)
1564 1610
1565 1611 # A function generator function that sets up the a context for the
1566 1612 # inner function.
1567 1613 def lookup_filenode_link_func(fname):
1568 1614 msngset = msng_filenode_set[fname]
1569 1615 # Lookup the changenode the filenode belongs to.
1570 1616 def lookup_filenode_link(fnode):
1571 1617 return msngset[fnode]
1572 1618 return lookup_filenode_link
1573 1619
1574 1620 # Now that we have all theses utility functions to help out and
1575 1621 # logically divide up the task, generate the group.
1576 1622 def gengroup():
1577 1623 # The set of changed files starts empty.
1578 1624 changedfiles = {}
1579 1625 # Create a changenode group generator that will call our functions
1580 1626 # back to lookup the owning changenode and collect information.
1581 1627 group = cl.group(msng_cl_lst, identity,
1582 1628 manifest_and_file_collector(changedfiles))
1583 1629 for chnk in group:
1584 1630 yield chnk
1585 1631
1586 1632 # The list of manifests has been collected by the generator
1587 1633 # calling our functions back.
1588 1634 prune_manifests()
1589 1635 msng_mnfst_lst = msng_mnfst_set.keys()
1590 1636 # Sort the manifestnodes by revision number.
1591 1637 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1592 1638 # Create a generator for the manifestnodes that calls our lookup
1593 1639 # and data collection functions back.
1594 1640 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1595 1641 filenode_collector(changedfiles))
1596 1642 for chnk in group:
1597 1643 yield chnk
1598 1644
1599 1645 # These are no longer needed, dereference and toss the memory for
1600 1646 # them.
1601 1647 msng_mnfst_lst = None
1602 1648 msng_mnfst_set.clear()
1603 1649
1604 1650 changedfiles = changedfiles.keys()
1605 1651 changedfiles.sort()
1606 1652 # Go through all our files in order sorted by name.
1607 1653 for fname in changedfiles:
1608 1654 filerevlog = self.file(fname)
1609 1655 # Toss out the filenodes that the recipient isn't really
1610 1656 # missing.
1611 1657 if msng_filenode_set.has_key(fname):
1612 1658 prune_filenodes(fname, filerevlog)
1613 1659 msng_filenode_lst = msng_filenode_set[fname].keys()
1614 1660 else:
1615 1661 msng_filenode_lst = []
1616 1662 # If any filenodes are left, generate the group for them,
1617 1663 # otherwise don't bother.
1618 1664 if len(msng_filenode_lst) > 0:
1619 1665 yield changegroup.genchunk(fname)
1620 1666 # Sort the filenodes by their revision #
1621 1667 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1622 1668 # Create a group generator and only pass in a changenode
1623 1669 # lookup function as we need to collect no information
1624 1670 # from filenodes.
1625 1671 group = filerevlog.group(msng_filenode_lst,
1626 1672 lookup_filenode_link_func(fname))
1627 1673 for chnk in group:
1628 1674 yield chnk
1629 1675 if msng_filenode_set.has_key(fname):
1630 1676 # Don't need this anymore, toss it to free memory.
1631 1677 del msng_filenode_set[fname]
1632 1678 # Signal that no more groups are left.
1633 1679 yield changegroup.closechunk()
1634 1680
1635 1681 if msng_cl_lst:
1636 1682 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1637 1683
1638 1684 return util.chunkbuffer(gengroup())
1639 1685
1640 1686 def changegroup(self, basenodes, source):
1641 1687 """Generate a changegroup of all nodes that we have that a recipient
1642 1688 doesn't.
1643 1689
1644 1690 This is much easier than the previous function as we can assume that
1645 1691 the recipient has any changenode we aren't sending them."""
1646 1692
1647 1693 self.hook('preoutgoing', throw=True, source=source)
1648 1694
1649 1695 cl = self.changelog
1650 1696 nodes = cl.nodesbetween(basenodes, None)[0]
1651 1697 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1652 1698 self.changegroupinfo(nodes)
1653 1699
1654 1700 def identity(x):
1655 1701 return x
1656 1702
1657 1703 def gennodelst(revlog):
1658 1704 for r in xrange(0, revlog.count()):
1659 1705 n = revlog.node(r)
1660 1706 if revlog.linkrev(n) in revset:
1661 1707 yield n
1662 1708
1663 1709 def changed_file_collector(changedfileset):
1664 1710 def collect_changed_files(clnode):
1665 1711 c = cl.read(clnode)
1666 1712 for fname in c[3]:
1667 1713 changedfileset[fname] = 1
1668 1714 return collect_changed_files
1669 1715
1670 1716 def lookuprevlink_func(revlog):
1671 1717 def lookuprevlink(n):
1672 1718 return cl.node(revlog.linkrev(n))
1673 1719 return lookuprevlink
1674 1720
1675 1721 def gengroup():
1676 1722 # construct a list of all changed files
1677 1723 changedfiles = {}
1678 1724
1679 1725 for chnk in cl.group(nodes, identity,
1680 1726 changed_file_collector(changedfiles)):
1681 1727 yield chnk
1682 1728 changedfiles = changedfiles.keys()
1683 1729 changedfiles.sort()
1684 1730
1685 1731 mnfst = self.manifest
1686 1732 nodeiter = gennodelst(mnfst)
1687 1733 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1688 1734 yield chnk
1689 1735
1690 1736 for fname in changedfiles:
1691 1737 filerevlog = self.file(fname)
1692 1738 nodeiter = gennodelst(filerevlog)
1693 1739 nodeiter = list(nodeiter)
1694 1740 if nodeiter:
1695 1741 yield changegroup.genchunk(fname)
1696 1742 lookup = lookuprevlink_func(filerevlog)
1697 1743 for chnk in filerevlog.group(nodeiter, lookup):
1698 1744 yield chnk
1699 1745
1700 1746 yield changegroup.closechunk()
1701 1747
1702 1748 if nodes:
1703 1749 self.hook('outgoing', node=hex(nodes[0]), source=source)
1704 1750
1705 1751 return util.chunkbuffer(gengroup())
1706 1752
1707 1753 def addchangegroup(self, source, srctype, url):
1708 1754 """add changegroup to repo.
1709 1755
1710 1756 return values:
1711 1757 - nothing changed or no source: 0
1712 1758 - more heads than before: 1+added heads (2..n)
1713 1759 - less heads than before: -1-removed heads (-2..-n)
1714 1760 - number of heads stays the same: 1
1715 1761 """
1716 1762 def csmap(x):
1717 1763 self.ui.debug(_("add changeset %s\n") % short(x))
1718 1764 return cl.count()
1719 1765
1720 1766 def revmap(x):
1721 1767 return cl.rev(x)
1722 1768
1723 1769 if not source:
1724 1770 return 0
1725 1771
1726 1772 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1727 1773
1728 1774 changesets = files = revisions = 0
1729 1775
1730 1776 tr = self.transaction()
1731 1777
1732 1778 # write changelog data to temp files so concurrent readers will not see
1733 1779 # inconsistent view
1734 1780 cl = self.changelog
1735 1781 cl.delayupdate()
1736 1782 oldheads = len(cl.heads())
1737 1783
1738 1784 # pull off the changeset group
1739 1785 self.ui.status(_("adding changesets\n"))
1740 1786 cor = cl.count() - 1
1741 1787 chunkiter = changegroup.chunkiter(source)
1742 1788 if cl.addgroup(chunkiter, csmap, tr, 1) is None:
1743 1789 raise util.Abort(_("received changelog group is empty"))
1744 1790 cnr = cl.count() - 1
1745 1791 changesets = cnr - cor
1746 1792
1747 1793 # pull off the manifest group
1748 1794 self.ui.status(_("adding manifests\n"))
1749 1795 chunkiter = changegroup.chunkiter(source)
1750 1796 # no need to check for empty manifest group here:
1751 1797 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1752 1798 # no new manifest will be created and the manifest group will
1753 1799 # be empty during the pull
1754 1800 self.manifest.addgroup(chunkiter, revmap, tr)
1755 1801
1756 1802 # process the files
1757 1803 self.ui.status(_("adding file changes\n"))
1758 1804 while 1:
1759 1805 f = changegroup.getchunk(source)
1760 1806 if not f:
1761 1807 break
1762 1808 self.ui.debug(_("adding %s revisions\n") % f)
1763 1809 fl = self.file(f)
1764 1810 o = fl.count()
1765 1811 chunkiter = changegroup.chunkiter(source)
1766 1812 if fl.addgroup(chunkiter, revmap, tr) is None:
1767 1813 raise util.Abort(_("received file revlog group is empty"))
1768 1814 revisions += fl.count() - o
1769 1815 files += 1
1770 1816
1771 1817 # make changelog see real files again
1772 1818 cl.finalize(tr)
1773 1819
1774 1820 newheads = len(self.changelog.heads())
1775 1821 heads = ""
1776 1822 if oldheads and newheads != oldheads:
1777 1823 heads = _(" (%+d heads)") % (newheads - oldheads)
1778 1824
1779 1825 self.ui.status(_("added %d changesets"
1780 1826 " with %d changes to %d files%s\n")
1781 1827 % (changesets, revisions, files, heads))
1782 1828
1783 1829 if changesets > 0:
1784 1830 self.hook('pretxnchangegroup', throw=True,
1785 1831 node=hex(self.changelog.node(cor+1)), source=srctype,
1786 1832 url=url)
1787 1833
1788 1834 tr.close()
1789 1835
1790 1836 if changesets > 0:
1791 1837 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1792 1838 source=srctype, url=url)
1793 1839
1794 1840 for i in xrange(cor + 1, cnr + 1):
1795 1841 self.hook("incoming", node=hex(self.changelog.node(i)),
1796 1842 source=srctype, url=url)
1797 1843
1798 1844 # never return 0 here:
1799 1845 if newheads < oldheads:
1800 1846 return newheads - oldheads - 1
1801 1847 else:
1802 1848 return newheads - oldheads + 1
1803 1849
1804 1850
1805 1851 def stream_in(self, remote):
1806 1852 fp = remote.stream_out()
1807 1853 l = fp.readline()
1808 1854 try:
1809 1855 resp = int(l)
1810 1856 except ValueError:
1811 1857 raise util.UnexpectedOutput(
1812 1858 _('Unexpected response from remote server:'), l)
1813 1859 if resp == 1:
1814 1860 raise util.Abort(_('operation forbidden by server'))
1815 1861 elif resp == 2:
1816 1862 raise util.Abort(_('locking the remote repository failed'))
1817 1863 elif resp != 0:
1818 1864 raise util.Abort(_('the server sent an unknown error code'))
1819 1865 self.ui.status(_('streaming all changes\n'))
1820 1866 l = fp.readline()
1821 1867 try:
1822 1868 total_files, total_bytes = map(int, l.split(' ', 1))
1823 1869 except ValueError, TypeError:
1824 1870 raise util.UnexpectedOutput(
1825 1871 _('Unexpected response from remote server:'), l)
1826 1872 self.ui.status(_('%d files to transfer, %s of data\n') %
1827 1873 (total_files, util.bytecount(total_bytes)))
1828 1874 start = time.time()
1829 1875 for i in xrange(total_files):
1830 1876 # XXX doesn't support '\n' or '\r' in filenames
1831 1877 l = fp.readline()
1832 1878 try:
1833 1879 name, size = l.split('\0', 1)
1834 1880 size = int(size)
1835 1881 except ValueError, TypeError:
1836 1882 raise util.UnexpectedOutput(
1837 1883 _('Unexpected response from remote server:'), l)
1838 1884 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1839 1885 ofp = self.sopener(name, 'w')
1840 1886 for chunk in util.filechunkiter(fp, limit=size):
1841 1887 ofp.write(chunk)
1842 1888 ofp.close()
1843 1889 elapsed = time.time() - start
1844 1890 if elapsed <= 0:
1845 1891 elapsed = 0.001
1846 1892 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1847 1893 (util.bytecount(total_bytes), elapsed,
1848 1894 util.bytecount(total_bytes / elapsed)))
1849 1895 self.invalidate()
1850 1896 return len(self.heads()) + 1
1851 1897
1852 1898 def clone(self, remote, heads=[], stream=False):
1853 1899 '''clone remote repository.
1854 1900
1855 1901 keyword arguments:
1856 1902 heads: list of revs to clone (forces use of pull)
1857 1903 stream: use streaming clone if possible'''
1858 1904
1859 1905 # now, all clients that can request uncompressed clones can
1860 1906 # read repo formats supported by all servers that can serve
1861 1907 # them.
1862 1908
1863 1909 # if revlog format changes, client will have to check version
1864 1910 # and format flags on "stream" capability, and use
1865 1911 # uncompressed only if compatible.
1866 1912
1867 1913 if stream and not heads and remote.capable('stream'):
1868 1914 return self.stream_in(remote)
1869 1915 return self.pull(remote, heads)
1870 1916
1871 1917 # used to avoid circular references so destructors work
1872 1918 def aftertrans(files):
1873 1919 renamefiles = [tuple(t) for t in files]
1874 1920 def a():
1875 1921 for src, dest in renamefiles:
1876 1922 util.rename(src, dest)
1877 1923 return a
1878 1924
1879 1925 def instance(ui, path, create):
1880 1926 return localrepository(ui, util.drop_scheme('file', path), create)
1881 1927
1882 1928 def islocal(path):
1883 1929 return True
General Comments 0
You need to be logged in to leave comments. Login now