##// END OF EJS Templates
subrepo: bytes/str cleanups on Git support...
Augie Fackler -
r41521:d4df6897 default draft
parent child Browse files
Show More
@@ -1,749 +1,750 b''
1 1 test-abort-checkin.t
2 2 test-absorb-edit-lines.t
3 3 test-absorb-filefixupstate.py
4 4 test-absorb-phase.t
5 5 test-absorb-rename.t
6 6 test-absorb-strip.t
7 7 test-absorb.t
8 8 test-add.t
9 9 test-addremove-similar.t
10 10 test-addremove.t
11 11 test-alias.t
12 12 test-amend-subrepo.t
13 13 test-amend.t
14 14 test-ancestor.py
15 15 test-annotate.py
16 16 test-annotate.t
17 17 test-arbitraryfilectx.t
18 18 test-archive-symlinks.t
19 19 test-archive.t
20 20 test-atomictempfile.py
21 21 test-audit-path.t
22 22 test-audit-subrepo.t
23 23 test-automv.t
24 24 test-backout.t
25 25 test-backwards-remove.t
26 26 test-bad-extension.t
27 27 test-bad-pull.t
28 28 test-basic.t
29 29 test-batching.py
30 30 test-bdiff.py
31 31 test-bheads.t
32 32 test-bisect.t
33 33 test-bisect2.t
34 34 test-bisect3.t
35 35 test-blackbox.t
36 36 test-bookflow.t
37 37 test-bookmarks-current.t
38 38 test-bookmarks-merge.t
39 39 test-bookmarks-pushpull.t
40 40 test-bookmarks-rebase.t
41 41 test-bookmarks-strip.t
42 42 test-bookmarks.t
43 43 test-branch-change.t
44 44 test-branch-option.t
45 45 test-branch-tag-confict.t
46 46 test-branches.t
47 47 test-bugzilla.t
48 48 test-bundle-phases.t
49 49 test-bundle-r.t
50 50 test-bundle-type.t
51 51 test-bundle-vs-outgoing.t
52 52 test-bundle.t
53 53 test-bundle2-exchange.t
54 54 test-bundle2-format.t
55 55 test-bundle2-multiple-changegroups.t
56 56 test-bundle2-pushback.t
57 57 test-bundle2-remote-changegroup.t
58 58 test-cache-abuse.t
59 59 test-cappedreader.py
60 60 test-casecollision-merge.t
61 61 test-casecollision.t
62 62 test-casefolding.t
63 63 test-cat.t
64 64 test-cbor.py
65 65 test-censor.t
66 66 test-changelog-exec.t
67 67 test-check-code.t
68 68 test-check-commit.t
69 69 test-check-config.py
70 70 test-check-config.t
71 71 test-check-execute.t
72 72 test-check-help.t
73 73 test-check-interfaces.py
74 74 test-check-module-imports.t
75 75 test-check-py3-compat.t
76 76 test-check-pyflakes.t
77 77 test-check-pylint.t
78 78 test-check-shbang.t
79 79 test-children.t
80 80 test-churn.t
81 81 test-clone-cgi.t
82 82 test-clone-pull-corruption.t
83 83 test-clone-r.t
84 84 test-clone-uncompressed.t
85 85 test-clone-update-order.t
86 86 test-clone.t
87 87 test-clonebundles.t
88 88 test-close-head.t
89 89 test-commandserver.t
90 90 test-commit-amend.t
91 91 test-commit-interactive.t
92 92 test-commit-multiple.t
93 93 test-commit-unresolved.t
94 94 test-commit.t
95 95 test-committer.t
96 96 test-completion.t
97 97 test-config-env.py
98 98 test-config.t
99 99 test-conflict.t
100 100 test-confused-revert.t
101 101 test-context-metadata.t
102 102 test-context.py
103 103 test-contrib-check-code.t
104 104 test-contrib-check-commit.t
105 105 test-contrib-dumprevlog.t
106 106 test-contrib-perf.t
107 107 test-contrib-relnotes.t
108 108 test-contrib-testparseutil.t
109 109 test-contrib.t
110 110 test-convert-authormap.t
111 111 test-convert-clonebranches.t
112 112 test-convert-cvs-branch.t
113 113 test-convert-cvs-detectmerge.t
114 114 test-convert-cvs-synthetic.t
115 115 test-convert-cvs.t
116 116 test-convert-cvsnt-mergepoints.t
117 117 test-convert-datesort.t
118 118 test-convert-filemap.t
119 119 test-convert-git.t
120 120 test-convert-hg-sink.t
121 121 test-convert-hg-source.t
122 122 test-convert-hg-startrev.t
123 123 test-convert-splicemap.t
124 124 test-convert-svn-sink.t
125 125 test-convert-tagsbranch-topology.t
126 126 test-convert.t
127 127 test-copy-move-merge.t
128 128 test-copy.t
129 129 test-copytrace-heuristics.t
130 130 test-custom-filters.t
131 131 test-debugbuilddag.t
132 132 test-debugbundle.t
133 133 test-debugcommands.t
134 134 test-debugextensions.t
135 135 test-debugindexdot.t
136 136 test-debugrename.t
137 137 test-default-push.t
138 138 test-diff-antipatience.t
139 139 test-diff-binary-file.t
140 140 test-diff-change.t
141 141 test-diff-color.t
142 142 test-diff-copy-depth.t
143 143 test-diff-hashes.t
144 144 test-diff-ignore-whitespace.t
145 145 test-diff-indent-heuristic.t
146 146 test-diff-issue2761.t
147 147 test-diff-newlines.t
148 148 test-diff-reverse.t
149 149 test-diff-subdir.t
150 150 test-diff-unified.t
151 151 test-diff-upgrade.t
152 152 test-diffdir.t
153 153 test-diffstat.t
154 154 test-directaccess.t
155 155 test-dirstate-backup.t
156 156 test-dirstate-nonnormalset.t
157 157 test-dirstate-race.t
158 158 test-dirstate.t
159 159 test-dispatch.py
160 160 test-dispatch.t
161 161 test-doctest.py
162 162 test-double-merge.t
163 163 test-drawdag.t
164 164 test-duplicateoptions.py
165 165 test-editor-filename.t
166 166 test-empty-dir.t
167 167 test-empty-file.t
168 168 test-empty-group.t
169 169 test-empty.t
170 170 test-encode.t
171 171 test-encoding-align.t
172 172 test-encoding-func.py
173 173 test-encoding-textwrap.t
174 174 test-encoding.t
175 175 test-eol-add.t
176 176 test-eol-clone.t
177 177 test-eol-hook.t
178 178 test-eol-patch.t
179 179 test-eol-tag.t
180 180 test-eol-update.t
181 181 test-eol.t
182 182 test-eolfilename.t
183 183 test-excessive-merge.t
184 184 test-exchange-obsmarkers-case-A1.t
185 185 test-exchange-obsmarkers-case-A2.t
186 186 test-exchange-obsmarkers-case-A3.t
187 187 test-exchange-obsmarkers-case-A4.t
188 188 test-exchange-obsmarkers-case-A5.t
189 189 test-exchange-obsmarkers-case-A6.t
190 190 test-exchange-obsmarkers-case-A7.t
191 191 test-exchange-obsmarkers-case-B1.t
192 192 test-exchange-obsmarkers-case-B2.t
193 193 test-exchange-obsmarkers-case-B3.t
194 194 test-exchange-obsmarkers-case-B4.t
195 195 test-exchange-obsmarkers-case-B5.t
196 196 test-exchange-obsmarkers-case-B6.t
197 197 test-exchange-obsmarkers-case-B7.t
198 198 test-exchange-obsmarkers-case-C1.t
199 199 test-exchange-obsmarkers-case-C2.t
200 200 test-exchange-obsmarkers-case-C3.t
201 201 test-exchange-obsmarkers-case-C4.t
202 202 test-exchange-obsmarkers-case-D1.t
203 203 test-exchange-obsmarkers-case-D2.t
204 204 test-exchange-obsmarkers-case-D3.t
205 205 test-exchange-obsmarkers-case-D4.t
206 206 test-execute-bit.t
207 207 test-export.t
208 208 test-extdata.t
209 209 test-extdiff.t
210 210 test-extension-timing.t
211 211 test-extensions-afterloaded.t
212 212 test-extensions-wrapfunction.py
213 213 test-extra-filelog-entry.t
214 214 test-fastannotate-corrupt.t
215 215 test-fastannotate-diffopts.t
216 216 test-fastannotate-hg.t
217 217 test-fastannotate-perfhack.t
218 218 test-fastannotate-protocol.t
219 219 test-fastannotate-renames.t
220 220 test-fastannotate-revmap.py
221 221 test-fastannotate.t
222 222 test-fetch.t
223 223 test-filebranch.t
224 224 test-filecache.py
225 225 test-filelog.py
226 226 test-fileset-generated.t
227 227 test-fileset.t
228 228 test-fix-topology.t
229 229 test-fix.t
230 230 test-flags.t
231 231 test-fncache.t
232 232 test-gendoc-da.t
233 233 test-gendoc-de.t
234 234 test-gendoc-el.t
235 235 test-gendoc-fr.t
236 236 test-gendoc-it.t
237 237 test-gendoc-ja.t
238 238 test-gendoc-pt_BR.t
239 239 test-gendoc-ro.t
240 240 test-gendoc-ru.t
241 241 test-gendoc-sv.t
242 242 test-gendoc-zh_CN.t
243 243 test-gendoc-zh_TW.t
244 244 test-gendoc.t
245 245 test-generaldelta.t
246 246 test-getbundle.t
247 247 test-git-export.t
248 248 test-githelp.t
249 249 test-globalopts.t
250 250 test-glog-beautifygraph.t
251 251 test-glog-topological.t
252 252 test-glog.t
253 253 test-gpg.t
254 254 test-graft.t
255 255 test-grep.t
256 256 test-hardlinks.t
257 257 test-help-hide.t
258 258 test-help.t
259 259 test-hg-parseurl.py
260 260 test-hghave.t
261 261 test-hgignore.t
262 262 test-hgk.t
263 263 test-hgrc.t
264 264 test-hgweb-annotate-whitespace.t
265 265 test-hgweb-auth.py
266 266 test-hgweb-bundle.t
267 267 test-hgweb-commands.t
268 268 test-hgweb-csp.t
269 269 test-hgweb-descend-empties.t
270 270 test-hgweb-diffs.t
271 271 test-hgweb-empty.t
272 272 test-hgweb-filelog.t
273 273 test-hgweb-no-path-info.t
274 274 test-hgweb-no-request-uri.t
275 275 test-hgweb-non-interactive.t
276 276 test-hgweb-raw.t
277 277 test-hgweb-removed.t
278 278 test-hgweb-symrev.t
279 279 test-hgweb.t
280 280 test-hgwebdir-paths.py
281 281 test-hgwebdir.t
282 282 test-hgwebdirsym.t
283 283 test-histedit-arguments.t
284 284 test-histedit-base.t
285 285 test-histedit-bookmark-motion.t
286 286 test-histedit-commute.t
287 287 test-histedit-drop.t
288 288 test-histedit-edit.t
289 289 test-histedit-fold-non-commute.t
290 290 test-histedit-fold.t
291 291 test-histedit-no-backup.t
292 292 test-histedit-no-change.t
293 293 test-histedit-non-commute-abort.t
294 294 test-histedit-non-commute.t
295 295 test-histedit-obsolete.t
296 296 test-histedit-outgoing.t
297 297 test-histedit-templates.t
298 298 test-http-api-httpv2.t
299 299 test-http-api.t
300 300 test-http-bad-server.t
301 301 test-http-branchmap.t
302 302 test-http-bundle1.t
303 303 test-http-clone-r.t
304 304 test-http-permissions.t
305 305 test-http-protocol.t
306 306 test-http.t
307 307 test-hybridencode.py
308 308 test-i18n.t
309 309 test-identify.t
310 310 test-impexp-branch.t
311 311 test-import-bypass.t
312 312 test-import-context.t
313 313 test-import-eol.t
314 314 test-import-merge.t
315 315 test-import-unknown.t
316 316 test-import.t
317 317 test-imports-checker.t
318 318 test-incoming-outgoing.t
319 319 test-infinitepush-bundlestore.t
320 320 test-infinitepush-ci.t
321 321 test-infinitepush.t
322 322 test-inherit-mode.t
323 323 test-init.t
324 324 test-install.t
325 325 test-issue1089.t
326 326 test-issue1102.t
327 327 test-issue1175.t
328 328 test-issue1306.t
329 329 test-issue1438.t
330 330 test-issue1502.t
331 331 test-issue1802.t
332 332 test-issue1877.t
333 333 test-issue1993.t
334 334 test-issue2137.t
335 335 test-issue3084.t
336 336 test-issue4074.t
337 337 test-issue522.t
338 338 test-issue586.t
339 339 test-issue5979.t
340 340 test-issue612.t
341 341 test-issue619.t
342 342 test-issue660.t
343 343 test-issue672.t
344 344 test-issue842.t
345 345 test-journal-exists.t
346 346 test-journal-share.t
347 347 test-journal.t
348 348 test-keyword.t
349 349 test-known.t
350 350 test-largefiles-cache.t
351 351 test-largefiles-misc.t
352 352 test-largefiles-small-disk.t
353 353 test-largefiles-update.t
354 354 test-largefiles-wireproto.t
355 355 test-largefiles.t
356 356 test-lfconvert.t
357 357 test-lfs-bundle.t
358 358 test-lfs-largefiles.t
359 359 test-lfs-pointer.py
360 360 test-lfs-test-server.t
361 361 test-lfs.t
362 362 test-linelog.py
363 363 test-linerange.py
364 364 test-locate.t
365 365 test-lock-badness.t
366 366 test-lock.py
367 367 test-log-exthook.t
368 368 test-log-linerange.t
369 369 test-log.t
370 370 test-logexchange.t
371 371 test-logtoprocess.t
372 372 test-lrucachedict.py
373 373 test-mactext.t
374 374 test-mailmap.t
375 375 test-manifest-merging.t
376 376 test-manifest.py
377 377 test-manifest.t
378 378 test-match.py
379 379 test-mdiff.py
380 380 test-merge-changedelete.t
381 381 test-merge-closedheads.t
382 382 test-merge-commit.t
383 383 test-merge-criss-cross.t
384 384 test-merge-default.t
385 385 test-merge-force.t
386 386 test-merge-halt.t
387 387 test-merge-internal-tools-pattern.t
388 388 test-merge-local.t
389 389 test-merge-no-file-change.t
390 390 test-merge-remove.t
391 391 test-merge-revert.t
392 392 test-merge-revert2.t
393 393 test-merge-subrepos.t
394 394 test-merge-symlinks.t
395 395 test-merge-tools.t
396 396 test-merge-types.t
397 397 test-merge1.t
398 398 test-merge10.t
399 399 test-merge2.t
400 400 test-merge4.t
401 401 test-merge5.t
402 402 test-merge6.t
403 403 test-merge7.t
404 404 test-merge8.t
405 405 test-merge9.t
406 406 test-minifileset.py
407 407 test-minirst.py
408 408 test-missing-capability.t
409 409 test-mq-eol.t
410 410 test-mq-git.t
411 411 test-mq-guards.t
412 412 test-mq-header-date.t
413 413 test-mq-header-from.t
414 414 test-mq-merge.t
415 415 test-mq-missingfiles.t
416 416 test-mq-pull-from-bundle.t
417 417 test-mq-qclone-http.t
418 418 test-mq-qdelete.t
419 419 test-mq-qdiff.t
420 420 test-mq-qfold.t
421 421 test-mq-qgoto.t
422 422 test-mq-qimport-fail-cleanup.t
423 423 test-mq-qimport.t
424 424 test-mq-qnew.t
425 425 test-mq-qpush-exact.t
426 426 test-mq-qpush-fail.t
427 427 test-mq-qqueue.t
428 428 test-mq-qrefresh-interactive.t
429 429 test-mq-qrefresh-replace-log-message.t
430 430 test-mq-qrefresh.t
431 431 test-mq-qrename.t
432 432 test-mq-qsave.t
433 433 test-mq-safety.t
434 434 test-mq-subrepo.t
435 435 test-mq-symlinks.t
436 436 test-mq.t
437 437 test-mv-cp-st-diff.t
438 438 test-narrow-acl.t
439 439 test-narrow-archive.t
440 440 test-narrow-clone-no-ellipsis.t
441 441 test-narrow-clone-non-narrow-server.t
442 442 test-narrow-clone-nonlinear.t
443 443 test-narrow-clone-stream.t
444 444 test-narrow-clone.t
445 445 test-narrow-commit.t
446 446 test-narrow-copies.t
447 447 test-narrow-debugcommands.t
448 448 test-narrow-debugrebuilddirstate.t
449 449 test-narrow-exchange-merges.t
450 450 test-narrow-exchange.t
451 451 test-narrow-expanddirstate.t
452 452 test-narrow-merge.t
453 453 test-narrow-patch.t
454 454 test-narrow-patterns.t
455 455 test-narrow-pull.t
456 456 test-narrow-rebase.t
457 457 test-narrow-shallow-merges.t
458 458 test-narrow-shallow.t
459 459 test-narrow-share.t
460 460 test-narrow-sparse.t
461 461 test-narrow-strip.t
462 462 test-narrow-trackedcmd.t
463 463 test-narrow-update.t
464 464 test-narrow-widen-no-ellipsis.t
465 465 test-narrow-widen.t
466 466 test-narrow.t
467 467 test-nested-repo.t
468 468 test-newbranch.t
469 469 test-newcgi.t
470 470 test-newercgi.t
471 471 test-nointerrupt.t
472 472 test-notify-changegroup.t
473 473 test-obshistory.t
474 474 test-obsmarker-template.t
475 475 test-obsmarkers-effectflag.t
476 476 test-obsolete-bounds-checking.t
477 477 test-obsolete-bundle-strip.t
478 478 test-obsolete-changeset-exchange.t
479 479 test-obsolete-checkheads.t
480 480 test-obsolete-distributed.t
481 481 test-obsolete-divergent.t
482 482 test-obsolete-tag-cache.t
483 483 test-obsolete.t
484 484 test-oldcgi.t
485 485 test-origbackup-conflict.t
486 486 test-pager-legacy.t
487 487 test-pager.t
488 488 test-parents.t
489 489 test-parse-date.t
490 490 test-parseindex.t
491 491 test-parseindex2.py
492 492 test-patch-offset.t
493 493 test-patch.t
494 494 test-patchbomb-bookmark.t
495 495 test-patchbomb-tls.t
496 496 test-patchbomb.t
497 497 test-pathconflicts-basic.t
498 498 test-pathconflicts-merge.t
499 499 test-pathconflicts-update.t
500 500 test-pathencode.py
501 501 test-pending.t
502 502 test-permissions.t
503 503 test-phases-exchange.t
504 504 test-phases.t
505 505 test-profile.t
506 506 test-progress.t
507 507 test-propertycache.py
508 508 test-pull-branch.t
509 509 test-pull-bundle.t
510 510 test-pull-http.t
511 511 test-pull-permission.t
512 512 test-pull-pull-corruption.t
513 513 test-pull-r.t
514 514 test-pull-update.t
515 515 test-pull.t
516 516 test-purge.t
517 517 test-push-cgi.t
518 518 test-push-checkheads-partial-C1.t
519 519 test-push-checkheads-partial-C2.t
520 520 test-push-checkheads-partial-C3.t
521 521 test-push-checkheads-partial-C4.t
522 522 test-push-checkheads-pruned-B1.t
523 523 test-push-checkheads-pruned-B2.t
524 524 test-push-checkheads-pruned-B3.t
525 525 test-push-checkheads-pruned-B4.t
526 526 test-push-checkheads-pruned-B5.t
527 527 test-push-checkheads-pruned-B6.t
528 528 test-push-checkheads-pruned-B7.t
529 529 test-push-checkheads-pruned-B8.t
530 530 test-push-checkheads-superceed-A1.t
531 531 test-push-checkheads-superceed-A2.t
532 532 test-push-checkheads-superceed-A3.t
533 533 test-push-checkheads-superceed-A4.t
534 534 test-push-checkheads-superceed-A5.t
535 535 test-push-checkheads-superceed-A6.t
536 536 test-push-checkheads-superceed-A7.t
537 537 test-push-checkheads-superceed-A8.t
538 538 test-push-checkheads-unpushed-D1.t
539 539 test-push-checkheads-unpushed-D2.t
540 540 test-push-checkheads-unpushed-D3.t
541 541 test-push-checkheads-unpushed-D4.t
542 542 test-push-checkheads-unpushed-D5.t
543 543 test-push-checkheads-unpushed-D6.t
544 544 test-push-checkheads-unpushed-D7.t
545 545 test-push-http.t
546 546 test-push-race.t
547 547 test-push-warn.t
548 548 test-push.t
549 549 test-pushvars.t
550 550 test-qrecord.t
551 551 test-rebase-abort.t
552 552 test-rebase-backup.t
553 553 test-rebase-base-flag.t
554 554 test-rebase-bookmarks.t
555 555 test-rebase-brute-force.t
556 556 test-rebase-cache.t
557 557 test-rebase-check-restore.t
558 558 test-rebase-collapse.t
559 559 test-rebase-conflicts.t
560 560 test-rebase-dest.t
561 561 test-rebase-detach.t
562 562 test-rebase-emptycommit.t
563 563 test-rebase-inmemory.t
564 564 test-rebase-interruptions.t
565 565 test-rebase-issue-noparam-single-rev.t
566 566 test-rebase-legacy.t
567 567 test-rebase-mq-skip.t
568 568 test-rebase-mq.t
569 569 test-rebase-named-branches.t
570 570 test-rebase-newancestor.t
571 571 test-rebase-obsolete.t
572 572 test-rebase-parameters.t
573 573 test-rebase-partial.t
574 574 test-rebase-pull.t
575 575 test-rebase-rename.t
576 576 test-rebase-scenario-global.t
577 577 test-rebase-templates.t
578 578 test-rebase-transaction.t
579 579 test-rebuildstate.t
580 580 test-record.t
581 581 test-releasenotes-formatting.t
582 582 test-releasenotes-merging.t
583 583 test-releasenotes-parsing.t
584 584 test-relink.t
585 585 test-remotefilelog-bad-configs.t
586 586 test-remotefilelog-bgprefetch.t
587 587 test-remotefilelog-blame.t
588 588 test-remotefilelog-bundle2.t
589 589 test-remotefilelog-bundles.t
590 590 test-remotefilelog-cacheprocess.t
591 591 test-remotefilelog-clone-tree.t
592 592 test-remotefilelog-clone.t
593 593 test-remotefilelog-datapack.py
594 594 test-remotefilelog-gcrepack.t
595 595 test-remotefilelog-histpack.py
596 596 test-remotefilelog-http.t
597 597 test-remotefilelog-keepset.t
598 598 test-remotefilelog-local.t
599 599 test-remotefilelog-log.t
600 600 test-remotefilelog-partial-shallow.t
601 601 test-remotefilelog-permissions.t
602 602 test-remotefilelog-permisssions.t
603 603 test-remotefilelog-prefetch.t
604 604 test-remotefilelog-pull-noshallow.t
605 605 test-remotefilelog-share.t
606 606 test-remotefilelog-sparse.t
607 607 test-remotefilelog-tags.t
608 608 test-remotefilelog-wireproto.t
609 609 test-remove.t
610 610 test-removeemptydirs.t
611 611 test-rename-after-merge.t
612 612 test-rename-dir-merge.t
613 613 test-rename-merge1.t
614 614 test-rename-merge2.t
615 615 test-rename.t
616 616 test-repair-strip.t
617 617 test-repo-compengines.t
618 618 test-requires.t
619 619 test-resolve.t
620 620 test-revert-flags.t
621 621 test-revert-interactive.t
622 622 test-revert-unknown.t
623 623 test-revert.t
624 624 test-revisions.t
625 625 test-revlog-ancestry.py
626 626 test-revlog-group-emptyiter.t
627 627 test-revlog-mmapindex.t
628 628 test-revlog-packentry.t
629 629 test-revlog-raw.py
630 630 test-revlog-v2.t
631 631 test-revlog.t
632 632 test-revset-dirstate-parents.t
633 633 test-revset-legacy-lookup.t
634 634 test-revset-outgoing.t
635 635 test-revset2.t
636 636 test-rollback.t
637 637 test-run-tests.py
638 638 test-run-tests.t
639 639 test-rust-ancestor.py
640 640 test-schemes.t
641 641 test-serve.t
642 642 test-setdiscovery.t
643 643 test-share.t
644 644 test-shelve.t
645 645 test-shelve2.t
646 646 test-show-stack.t
647 647 test-show-work.t
648 648 test-show.t
649 649 test-simple-update.t
650 650 test-simplekeyvaluefile.py
651 651 test-simplemerge.py
652 652 test-single-head.t
653 653 test-sparse-clear.t
654 654 test-sparse-clone.t
655 655 test-sparse-import.t
656 656 test-sparse-merges.t
657 657 test-sparse-profiles.t
658 658 test-sparse-requirement.t
659 659 test-sparse-verbose-json.t
660 660 test-sparse.t
661 661 test-split.t
662 662 test-ssh-bundle1.t
663 663 test-ssh-clone-r.t
664 664 test-ssh-proto-unbundle.t
665 665 test-ssh-proto.t
666 666 test-ssh-repoerror.t
667 667 test-ssh.t
668 668 test-sshserver.py
669 669 test-stack.t
670 670 test-static-http.t
671 671 test-status-color.t
672 672 test-status-inprocess.py
673 673 test-status-rev.t
674 674 test-status-terse.t
675 675 test-status.t
676 676 test-storage.py
677 677 test-stream-bundle-v2.t
678 678 test-strict.t
679 679 test-strip-cross.t
680 680 test-strip.t
681 681 test-subrepo-deep-nested-change.t
682 test-subrepo-git.t
682 683 test-subrepo-missing.t
683 684 test-subrepo-paths.t
684 685 test-subrepo-recursion.t
685 686 test-subrepo-relative-path.t
686 687 test-subrepo.t
687 688 test-symlink-os-yes-fs-no.py
688 689 test-symlink-placeholder.t
689 690 test-symlinks.t
690 691 test-tag.t
691 692 test-tags.t
692 693 test-template-basic.t
693 694 test-template-functions.t
694 695 test-template-keywords.t
695 696 test-template-map.t
696 697 test-tools.t
697 698 test-transplant.t
698 699 test-treediscovery-legacy.t
699 700 test-treediscovery.t
700 701 test-treemanifest.t
701 702 test-ui-color.py
702 703 test-ui-config.py
703 704 test-ui-verbosity.py
704 705 test-unamend.t
705 706 test-unbundlehash.t
706 707 test-uncommit.t
707 708 test-unified-test.t
708 709 test-unionrepo.t
709 710 test-unrelated-pull.t
710 711 test-up-local-change.t
711 712 test-update-atomic.t
712 713 test-update-branches.t
713 714 test-update-dest.t
714 715 test-update-issue1456.t
715 716 test-update-names.t
716 717 test-update-reverse.t
717 718 test-upgrade-repo.t
718 719 test-url-download.t
719 720 test-url-rev.t
720 721 test-url.py
721 722 test-username-newline.t
722 723 test-util.py
723 724 test-verify.t
724 725 test-walk.t
725 726 test-walkrepo.py
726 727 test-websub.t
727 728 test-win32text.t
728 729 test-wireproto-caching.t
729 730 test-wireproto-clientreactor.py
730 731 test-wireproto-command-branchmap.t
731 732 test-wireproto-command-capabilities.t
732 733 test-wireproto-command-changesetdata.t
733 734 test-wireproto-command-filedata.t
734 735 test-wireproto-command-filesdata.t
735 736 test-wireproto-command-heads.t
736 737 test-wireproto-command-known.t
737 738 test-wireproto-command-listkeys.t
738 739 test-wireproto-command-lookup.t
739 740 test-wireproto-command-manifestdata.t
740 741 test-wireproto-command-pushkey.t
741 742 test-wireproto-command-rawstorefiledata.t
742 743 test-wireproto-content-redirects.t
743 744 test-wireproto-exchangev2.t
744 745 test-wireproto-framing.py
745 746 test-wireproto-serverreactor.py
746 747 test-wireproto.py
747 748 test-wireproto.t
748 749 test-wsgirequest.py
749 750 test-xdg.t
@@ -1,1834 +1,1835 b''
1 1 # subrepo.py - sub-repository classes and factory
2 2 #
3 3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import copy
11 11 import errno
12 12 import hashlib
13 13 import os
14 14 import posixpath
15 15 import re
16 16 import stat
17 17 import subprocess
18 18 import sys
19 19 import tarfile
20 20 import xml.dom.minidom
21 21
22 22 from .i18n import _
23 23 from . import (
24 24 cmdutil,
25 25 encoding,
26 26 error,
27 27 exchange,
28 28 logcmdutil,
29 29 match as matchmod,
30 30 node,
31 31 pathutil,
32 32 phases,
33 33 pycompat,
34 34 scmutil,
35 35 subrepoutil,
36 36 util,
37 37 vfs as vfsmod,
38 38 )
39 39 from .utils import (
40 40 dateutil,
41 41 procutil,
42 42 stringutil,
43 43 )
44 44
45 45 hg = None
46 46 reporelpath = subrepoutil.reporelpath
47 47 subrelpath = subrepoutil.subrelpath
48 48 _abssource = subrepoutil._abssource
49 49 propertycache = util.propertycache
50 50
51 51 def _expandedabspath(path):
52 52 '''
53 53 get a path or url and if it is a path expand it and return an absolute path
54 54 '''
55 55 expandedpath = util.urllocalpath(util.expandpath(path))
56 56 u = util.url(expandedpath)
57 57 if not u.scheme:
58 58 path = util.normpath(os.path.abspath(u.path))
59 59 return path
60 60
61 61 def _getstorehashcachename(remotepath):
62 62 '''get a unique filename for the store hash cache of a remote repository'''
63 63 return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
64 64
65 65 class SubrepoAbort(error.Abort):
66 66 """Exception class used to avoid handling a subrepo error more than once"""
67 67 def __init__(self, *args, **kw):
68 68 self.subrepo = kw.pop(r'subrepo', None)
69 69 self.cause = kw.pop(r'cause', None)
70 70 error.Abort.__init__(self, *args, **kw)
71 71
72 72 def annotatesubrepoerror(func):
73 73 def decoratedmethod(self, *args, **kargs):
74 74 try:
75 75 res = func(self, *args, **kargs)
76 76 except SubrepoAbort as ex:
77 77 # This exception has already been handled
78 78 raise ex
79 79 except error.Abort as ex:
80 80 subrepo = subrelpath(self)
81 81 errormsg = (stringutil.forcebytestr(ex) + ' '
82 82 + _('(in subrepository "%s")') % subrepo)
83 83 # avoid handling this exception by raising a SubrepoAbort exception
84 84 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
85 85 cause=sys.exc_info())
86 86 return res
87 87 return decoratedmethod
88 88
89 89 def _updateprompt(ui, sub, dirty, local, remote):
90 90 if dirty:
91 91 msg = (_(' subrepository sources for %s differ\n'
92 92 'use (l)ocal source (%s) or (r)emote source (%s)?'
93 93 '$$ &Local $$ &Remote')
94 94 % (subrelpath(sub), local, remote))
95 95 else:
96 96 msg = (_(' subrepository sources for %s differ (in checked out '
97 97 'version)\n'
98 98 'use (l)ocal source (%s) or (r)emote source (%s)?'
99 99 '$$ &Local $$ &Remote')
100 100 % (subrelpath(sub), local, remote))
101 101 return ui.promptchoice(msg, 0)
102 102
103 103 def _sanitize(ui, vfs, ignore):
104 104 for dirname, dirs, names in vfs.walk():
105 105 for i, d in enumerate(dirs):
106 106 if d.lower() == ignore:
107 107 del dirs[i]
108 108 break
109 109 if vfs.basename(dirname).lower() != '.hg':
110 110 continue
111 111 for f in names:
112 112 if f.lower() == 'hgrc':
113 113 ui.warn(_("warning: removing potentially hostile 'hgrc' "
114 114 "in '%s'\n") % vfs.join(dirname))
115 115 vfs.unlink(vfs.reljoin(dirname, f))
116 116
117 117 def _auditsubrepopath(repo, path):
118 118 # auditor doesn't check if the path itself is a symlink
119 119 pathutil.pathauditor(repo.root)(path)
120 120 if repo.wvfs.islink(path):
121 121 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
122 122
123 123 SUBREPO_ALLOWED_DEFAULTS = {
124 124 'hg': True,
125 125 'git': False,
126 126 'svn': False,
127 127 }
128 128
129 129 def _checktype(ui, kind):
130 130 # subrepos.allowed is a master kill switch. If disabled, subrepos are
131 131 # disabled period.
132 132 if not ui.configbool('subrepos', 'allowed', True):
133 133 raise error.Abort(_('subrepos not enabled'),
134 134 hint=_("see 'hg help config.subrepos' for details"))
135 135
136 136 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
137 137 if not ui.configbool('subrepos', '%s:allowed' % kind, default):
138 138 raise error.Abort(_('%s subrepos not allowed') % kind,
139 139 hint=_("see 'hg help config.subrepos' for details"))
140 140
141 141 if kind not in types:
142 142 raise error.Abort(_('unknown subrepo type %s') % kind)
143 143
144 144 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
145 145 """return instance of the right subrepo class for subrepo in path"""
146 146 # subrepo inherently violates our import layering rules
147 147 # because it wants to make repo objects from deep inside the stack
148 148 # so we manually delay the circular imports to not break
149 149 # scripts that don't use our demand-loading
150 150 global hg
151 151 from . import hg as h
152 152 hg = h
153 153
154 154 repo = ctx.repo()
155 155 _auditsubrepopath(repo, path)
156 156 state = ctx.substate[path]
157 157 _checktype(repo.ui, state[2])
158 158 if allowwdir:
159 159 state = (state[0], ctx.subrev(path), state[2])
160 160 return types[state[2]](ctx, path, state[:2], allowcreate)
161 161
162 162 def nullsubrepo(ctx, path, pctx):
163 163 """return an empty subrepo in pctx for the extant subrepo in ctx"""
164 164 # subrepo inherently violates our import layering rules
165 165 # because it wants to make repo objects from deep inside the stack
166 166 # so we manually delay the circular imports to not break
167 167 # scripts that don't use our demand-loading
168 168 global hg
169 169 from . import hg as h
170 170 hg = h
171 171
172 172 repo = ctx.repo()
173 173 _auditsubrepopath(repo, path)
174 174 state = ctx.substate[path]
175 175 _checktype(repo.ui, state[2])
176 176 subrev = ''
177 177 if state[2] == 'hg':
178 178 subrev = "0" * 40
179 179 return types[state[2]](pctx, path, (state[0], subrev), True)
180 180
181 181 # subrepo classes need to implement the following abstract class:
182 182
183 183 class abstractsubrepo(object):
184 184
185 185 def __init__(self, ctx, path):
186 186 """Initialize abstractsubrepo part
187 187
188 188 ``ctx`` is the context referring this subrepository in the
189 189 parent repository.
190 190
191 191 ``path`` is the path to this subrepository as seen from
192 192 innermost repository.
193 193 """
194 194 self.ui = ctx.repo().ui
195 195 self._ctx = ctx
196 196 self._path = path
197 197
198 198 def addwebdirpath(self, serverpath, webconf):
199 199 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
200 200
201 201 ``serverpath`` is the path component of the URL for this repo.
202 202
203 203 ``webconf`` is the dictionary of hgwebdir entries.
204 204 """
205 205 pass
206 206
207 207 def storeclean(self, path):
208 208 """
209 209 returns true if the repository has not changed since it was last
210 210 cloned from or pushed to a given repository.
211 211 """
212 212 return False
213 213
214 214 def dirty(self, ignoreupdate=False, missing=False):
215 215 """returns true if the dirstate of the subrepo is dirty or does not
216 216 match current stored state. If ignoreupdate is true, only check
217 217 whether the subrepo has uncommitted changes in its dirstate. If missing
218 218 is true, check for deleted files.
219 219 """
220 220 raise NotImplementedError
221 221
222 222 def dirtyreason(self, ignoreupdate=False, missing=False):
223 223 """return reason string if it is ``dirty()``
224 224
225 225 Returned string should have enough information for the message
226 226 of exception.
227 227
228 228 This returns None, otherwise.
229 229 """
230 230 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
231 231 return _('uncommitted changes in subrepository "%s"'
232 232 ) % subrelpath(self)
233 233
234 234 def bailifchanged(self, ignoreupdate=False, hint=None):
235 235 """raise Abort if subrepository is ``dirty()``
236 236 """
237 237 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
238 238 missing=True)
239 239 if dirtyreason:
240 240 raise error.Abort(dirtyreason, hint=hint)
241 241
242 242 def basestate(self):
243 243 """current working directory base state, disregarding .hgsubstate
244 244 state and working directory modifications"""
245 245 raise NotImplementedError
246 246
247 247 def checknested(self, path):
248 248 """check if path is a subrepository within this repository"""
249 249 return False
250 250
251 251 def commit(self, text, user, date):
252 252 """commit the current changes to the subrepo with the given
253 253 log message. Use given user and date if possible. Return the
254 254 new state of the subrepo.
255 255 """
256 256 raise NotImplementedError
257 257
258 258 def phase(self, state):
259 259 """returns phase of specified state in the subrepository.
260 260 """
261 261 return phases.public
262 262
263 263 def remove(self):
264 264 """remove the subrepo
265 265
266 266 (should verify the dirstate is not dirty first)
267 267 """
268 268 raise NotImplementedError
269 269
270 270 def get(self, state, overwrite=False):
271 271 """run whatever commands are needed to put the subrepo into
272 272 this state
273 273 """
274 274 raise NotImplementedError
275 275
276 276 def merge(self, state):
277 277 """merge currently-saved state with the new state."""
278 278 raise NotImplementedError
279 279
280 280 def push(self, opts):
281 281 """perform whatever action is analogous to 'hg push'
282 282
283 283 This may be a no-op on some systems.
284 284 """
285 285 raise NotImplementedError
286 286
287 287 def add(self, ui, match, prefix, explicitonly, **opts):
288 288 return []
289 289
290 290 def addremove(self, matcher, prefix, opts):
291 291 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
292 292 return 1
293 293
294 294 def cat(self, match, fm, fntemplate, prefix, **opts):
295 295 return 1
296 296
297 297 def status(self, rev2, **opts):
298 298 return scmutil.status([], [], [], [], [], [], [])
299 299
300 300 def diff(self, ui, diffopts, node2, match, prefix, **opts):
301 301 pass
302 302
303 303 def outgoing(self, ui, dest, opts):
304 304 return 1
305 305
306 306 def incoming(self, ui, source, opts):
307 307 return 1
308 308
309 309 def files(self):
310 310 """return filename iterator"""
311 311 raise NotImplementedError
312 312
313 313 def filedata(self, name, decode):
314 314 """return file data, optionally passed through repo decoders"""
315 315 raise NotImplementedError
316 316
317 317 def fileflags(self, name):
318 318 """return file flags"""
319 319 return ''
320 320
321 321 def matchfileset(self, expr, badfn=None):
322 322 """Resolve the fileset expression for this repo"""
323 323 return matchmod.nevermatcher(self.wvfs.base, '', badfn=badfn)
324 324
325 325 def printfiles(self, ui, m, fm, fmt, subrepos):
326 326 """handle the files command for this subrepo"""
327 327 return 1
328 328
329 329 def archive(self, archiver, prefix, match=None, decode=True):
330 330 if match is not None:
331 331 files = [f for f in self.files() if match(f)]
332 332 else:
333 333 files = self.files()
334 334 total = len(files)
335 335 relpath = subrelpath(self)
336 336 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
337 337 unit=_('files'), total=total)
338 338 progress.update(0)
339 339 for name in files:
340 340 flags = self.fileflags(name)
341 341 mode = 'x' in flags and 0o755 or 0o644
342 342 symlink = 'l' in flags
343 343 archiver.addfile(prefix + self._path + '/' + name,
344 344 mode, symlink, self.filedata(name, decode))
345 345 progress.increment()
346 346 progress.complete()
347 347 return total
348 348
349 349 def walk(self, match):
350 350 '''
351 351 walk recursively through the directory tree, finding all files
352 352 matched by the match function
353 353 '''
354 354
355 355 def forget(self, match, prefix, dryrun, interactive):
356 356 return ([], [])
357 357
358 358 def removefiles(self, matcher, prefix, after, force, subrepos,
359 359 dryrun, warnings):
360 360 """remove the matched files from the subrepository and the filesystem,
361 361 possibly by force and/or after the file has been removed from the
362 362 filesystem. Return 0 on success, 1 on any warning.
363 363 """
364 364 warnings.append(_("warning: removefiles not implemented (%s)")
365 365 % self._path)
366 366 return 1
367 367
368 368 def revert(self, substate, *pats, **opts):
369 369 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
370 370 % (substate[0], substate[2]))
371 371 return []
372 372
373 373 def shortid(self, revid):
374 374 return revid
375 375
376 376 def unshare(self):
377 377 '''
378 378 convert this repository from shared to normal storage.
379 379 '''
380 380
381 381 def verify(self):
382 382 '''verify the integrity of the repository. Return 0 on success or
383 383 warning, 1 on any error.
384 384 '''
385 385 return 0
386 386
387 387 @propertycache
388 388 def wvfs(self):
389 389 """return vfs to access the working directory of this subrepository
390 390 """
391 391 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
392 392
393 393 @propertycache
394 394 def _relpath(self):
395 395 """return path to this subrepository as seen from outermost repository
396 396 """
397 397 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
398 398
399 399 class hgsubrepo(abstractsubrepo):
400 400 def __init__(self, ctx, path, state, allowcreate):
401 401 super(hgsubrepo, self).__init__(ctx, path)
402 402 self._state = state
403 403 r = ctx.repo()
404 404 root = r.wjoin(path)
405 405 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
406 406 self._repo = hg.repository(r.baseui, root, create=create)
407 407
408 408 # Propagate the parent's --hidden option
409 409 if r is r.unfiltered():
410 410 self._repo = self._repo.unfiltered()
411 411
412 412 self.ui = self._repo.ui
413 413 for s, k in [('ui', 'commitsubrepos')]:
414 414 v = r.ui.config(s, k)
415 415 if v:
416 416 self.ui.setconfig(s, k, v, 'subrepo')
417 417 # internal config: ui._usedassubrepo
418 418 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
419 419 self._initrepo(r, state[0], create)
420 420
421 421 @annotatesubrepoerror
422 422 def addwebdirpath(self, serverpath, webconf):
423 423 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
424 424
425 425 def storeclean(self, path):
426 426 with self._repo.lock():
427 427 return self._storeclean(path)
428 428
429 429 def _storeclean(self, path):
430 430 clean = True
431 431 itercache = self._calcstorehash(path)
432 432 for filehash in self._readstorehashcache(path):
433 433 if filehash != next(itercache, None):
434 434 clean = False
435 435 break
436 436 if clean:
437 437 # if not empty:
438 438 # the cached and current pull states have a different size
439 439 clean = next(itercache, None) is None
440 440 return clean
441 441
442 442 def _calcstorehash(self, remotepath):
443 443 '''calculate a unique "store hash"
444 444
445 445 This method is used to to detect when there are changes that may
446 446 require a push to a given remote path.'''
447 447 # sort the files that will be hashed in increasing (likely) file size
448 448 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
449 449 yield '# %s\n' % _expandedabspath(remotepath)
450 450 vfs = self._repo.vfs
451 451 for relname in filelist:
452 452 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
453 453 yield '%s = %s\n' % (relname, filehash)
454 454
455 455 @propertycache
456 456 def _cachestorehashvfs(self):
457 457 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
458 458
459 459 def _readstorehashcache(self, remotepath):
460 460 '''read the store hash cache for a given remote repository'''
461 461 cachefile = _getstorehashcachename(remotepath)
462 462 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
463 463
464 464 def _cachestorehash(self, remotepath):
465 465 '''cache the current store hash
466 466
467 467 Each remote repo requires its own store hash cache, because a subrepo
468 468 store may be "clean" versus a given remote repo, but not versus another
469 469 '''
470 470 cachefile = _getstorehashcachename(remotepath)
471 471 with self._repo.lock():
472 472 storehash = list(self._calcstorehash(remotepath))
473 473 vfs = self._cachestorehashvfs
474 474 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
475 475
476 476 def _getctx(self):
477 477 '''fetch the context for this subrepo revision, possibly a workingctx
478 478 '''
479 479 if self._ctx.rev() is None:
480 480 return self._repo[None] # workingctx if parent is workingctx
481 481 else:
482 482 rev = self._state[1]
483 483 return self._repo[rev]
484 484
485 485 @annotatesubrepoerror
486 486 def _initrepo(self, parentrepo, source, create):
487 487 self._repo._subparent = parentrepo
488 488 self._repo._subsource = source
489 489
490 490 if create:
491 491 lines = ['[paths]\n']
492 492
493 493 def addpathconfig(key, value):
494 494 if value:
495 495 lines.append('%s = %s\n' % (key, value))
496 496 self.ui.setconfig('paths', key, value, 'subrepo')
497 497
498 498 defpath = _abssource(self._repo, abort=False)
499 499 defpushpath = _abssource(self._repo, True, abort=False)
500 500 addpathconfig('default', defpath)
501 501 if defpath != defpushpath:
502 502 addpathconfig('default-push', defpushpath)
503 503
504 504 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
505 505
506 506 @annotatesubrepoerror
507 507 def add(self, ui, match, prefix, explicitonly, **opts):
508 508 return cmdutil.add(ui, self._repo, match,
509 509 self.wvfs.reljoin(prefix, self._path),
510 510 explicitonly, **opts)
511 511
512 512 @annotatesubrepoerror
513 513 def addremove(self, m, prefix, opts):
514 514 # In the same way as sub directories are processed, once in a subrepo,
515 515 # always entry any of its subrepos. Don't corrupt the options that will
516 516 # be used to process sibling subrepos however.
517 517 opts = copy.copy(opts)
518 518 opts['subrepos'] = True
519 519 return scmutil.addremove(self._repo, m,
520 520 self.wvfs.reljoin(prefix, self._path), opts)
521 521
522 522 @annotatesubrepoerror
523 523 def cat(self, match, fm, fntemplate, prefix, **opts):
524 524 rev = self._state[1]
525 525 ctx = self._repo[rev]
526 526 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
527 527 prefix, **opts)
528 528
529 529 @annotatesubrepoerror
530 530 def status(self, rev2, **opts):
531 531 try:
532 532 rev1 = self._state[1]
533 533 ctx1 = self._repo[rev1]
534 534 ctx2 = self._repo[rev2]
535 535 return self._repo.status(ctx1, ctx2, **opts)
536 536 except error.RepoLookupError as inst:
537 537 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
538 538 % (inst, subrelpath(self)))
539 539 return scmutil.status([], [], [], [], [], [], [])
540 540
541 541 @annotatesubrepoerror
542 542 def diff(self, ui, diffopts, node2, match, prefix, **opts):
543 543 try:
544 544 node1 = node.bin(self._state[1])
545 545 # We currently expect node2 to come from substate and be
546 546 # in hex format
547 547 if node2 is not None:
548 548 node2 = node.bin(node2)
549 549 logcmdutil.diffordiffstat(ui, self._repo, diffopts,
550 550 node1, node2, match,
551 551 prefix=posixpath.join(prefix, self._path),
552 552 listsubrepos=True, **opts)
553 553 except error.RepoLookupError as inst:
554 554 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
555 555 % (inst, subrelpath(self)))
556 556
557 557 @annotatesubrepoerror
558 558 def archive(self, archiver, prefix, match=None, decode=True):
559 559 self._get(self._state + ('hg',))
560 560 files = self.files()
561 561 if match:
562 562 files = [f for f in files if match(f)]
563 563 rev = self._state[1]
564 564 ctx = self._repo[rev]
565 565 scmutil.prefetchfiles(self._repo, [ctx.rev()],
566 566 scmutil.matchfiles(self._repo, files))
567 567 total = abstractsubrepo.archive(self, archiver, prefix, match)
568 568 for subpath in ctx.substate:
569 569 s = subrepo(ctx, subpath, True)
570 570 submatch = matchmod.subdirmatcher(subpath, match)
571 571 total += s.archive(archiver, prefix + self._path + '/', submatch,
572 572 decode)
573 573 return total
574 574
575 575 @annotatesubrepoerror
576 576 def dirty(self, ignoreupdate=False, missing=False):
577 577 r = self._state[1]
578 578 if r == '' and not ignoreupdate: # no state recorded
579 579 return True
580 580 w = self._repo[None]
581 581 if r != w.p1().hex() and not ignoreupdate:
582 582 # different version checked out
583 583 return True
584 584 return w.dirty(missing=missing) # working directory changed
585 585
586 586 def basestate(self):
587 587 return self._repo['.'].hex()
588 588
589 589 def checknested(self, path):
590 590 return self._repo._checknested(self._repo.wjoin(path))
591 591
592 592 @annotatesubrepoerror
593 593 def commit(self, text, user, date):
594 594 # don't bother committing in the subrepo if it's only been
595 595 # updated
596 596 if not self.dirty(True):
597 597 return self._repo['.'].hex()
598 598 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
599 599 n = self._repo.commit(text, user, date)
600 600 if not n:
601 601 return self._repo['.'].hex() # different version checked out
602 602 return node.hex(n)
603 603
604 604 @annotatesubrepoerror
605 605 def phase(self, state):
606 606 return self._repo[state or '.'].phase()
607 607
608 608 @annotatesubrepoerror
609 609 def remove(self):
610 610 # we can't fully delete the repository as it may contain
611 611 # local-only history
612 612 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
613 613 hg.clean(self._repo, node.nullid, False)
614 614
615 615 def _get(self, state):
616 616 source, revision, kind = state
617 617 parentrepo = self._repo._subparent
618 618
619 619 if revision in self._repo.unfiltered():
620 620 # Allow shared subrepos tracked at null to setup the sharedpath
621 621 if len(self._repo) != 0 or not parentrepo.shared():
622 622 return True
623 623 self._repo._subsource = source
624 624 srcurl = _abssource(self._repo)
625 625
626 626 # Defer creating the peer until after the status message is logged, in
627 627 # case there are network problems.
628 628 getpeer = lambda: hg.peer(self._repo, {}, srcurl)
629 629
630 630 if len(self._repo) == 0:
631 631 # use self._repo.vfs instead of self.wvfs to remove .hg only
632 632 self._repo.vfs.rmtree()
633 633
634 634 # A remote subrepo could be shared if there is a local copy
635 635 # relative to the parent's share source. But clone pooling doesn't
636 636 # assemble the repos in a tree, so that can't be consistently done.
637 637 # A simpler option is for the user to configure clone pooling, and
638 638 # work with that.
639 639 if parentrepo.shared() and hg.islocal(srcurl):
640 640 self.ui.status(_('sharing subrepo %s from %s\n')
641 641 % (subrelpath(self), srcurl))
642 642 shared = hg.share(self._repo._subparent.baseui,
643 643 getpeer(), self._repo.root,
644 644 update=False, bookmarks=False)
645 645 self._repo = shared.local()
646 646 else:
647 647 # TODO: find a common place for this and this code in the
648 648 # share.py wrap of the clone command.
649 649 if parentrepo.shared():
650 650 pool = self.ui.config('share', 'pool')
651 651 if pool:
652 652 pool = util.expandpath(pool)
653 653
654 654 shareopts = {
655 655 'pool': pool,
656 656 'mode': self.ui.config('share', 'poolnaming'),
657 657 }
658 658 else:
659 659 shareopts = {}
660 660
661 661 self.ui.status(_('cloning subrepo %s from %s\n')
662 662 % (subrelpath(self), util.hidepassword(srcurl)))
663 663 other, cloned = hg.clone(self._repo._subparent.baseui, {},
664 664 getpeer(), self._repo.root,
665 665 update=False, shareopts=shareopts)
666 666 self._repo = cloned.local()
667 667 self._initrepo(parentrepo, source, create=True)
668 668 self._cachestorehash(srcurl)
669 669 else:
670 670 self.ui.status(_('pulling subrepo %s from %s\n')
671 671 % (subrelpath(self), util.hidepassword(srcurl)))
672 672 cleansub = self.storeclean(srcurl)
673 673 exchange.pull(self._repo, getpeer())
674 674 if cleansub:
675 675 # keep the repo clean after pull
676 676 self._cachestorehash(srcurl)
677 677 return False
678 678
679 679 @annotatesubrepoerror
680 680 def get(self, state, overwrite=False):
681 681 inrepo = self._get(state)
682 682 source, revision, kind = state
683 683 repo = self._repo
684 684 repo.ui.debug("getting subrepo %s\n" % self._path)
685 685 if inrepo:
686 686 urepo = repo.unfiltered()
687 687 ctx = urepo[revision]
688 688 if ctx.hidden():
689 689 urepo.ui.warn(
690 690 _('revision %s in subrepository "%s" is hidden\n') \
691 691 % (revision[0:12], self._path))
692 692 repo = urepo
693 693 hg.updaterepo(repo, revision, overwrite)
694 694
695 695 @annotatesubrepoerror
696 696 def merge(self, state):
697 697 self._get(state)
698 698 cur = self._repo['.']
699 699 dst = self._repo[state[1]]
700 700 anc = dst.ancestor(cur)
701 701
702 702 def mergefunc():
703 703 if anc == cur and dst.branch() == cur.branch():
704 704 self.ui.debug('updating subrepository "%s"\n'
705 705 % subrelpath(self))
706 706 hg.update(self._repo, state[1])
707 707 elif anc == dst:
708 708 self.ui.debug('skipping subrepository "%s"\n'
709 709 % subrelpath(self))
710 710 else:
711 711 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
712 712 hg.merge(self._repo, state[1], remind=False)
713 713
714 714 wctx = self._repo[None]
715 715 if self.dirty():
716 716 if anc != dst:
717 717 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
718 718 mergefunc()
719 719 else:
720 720 mergefunc()
721 721 else:
722 722 mergefunc()
723 723
724 724 @annotatesubrepoerror
725 725 def push(self, opts):
726 726 force = opts.get('force')
727 727 newbranch = opts.get('new_branch')
728 728 ssh = opts.get('ssh')
729 729
730 730 # push subrepos depth-first for coherent ordering
731 731 c = self._repo['.']
732 732 subs = c.substate # only repos that are committed
733 733 for s in sorted(subs):
734 734 if c.sub(s).push(opts) == 0:
735 735 return False
736 736
737 737 dsturl = _abssource(self._repo, True)
738 738 if not force:
739 739 if self.storeclean(dsturl):
740 740 self.ui.status(
741 741 _('no changes made to subrepo %s since last push to %s\n')
742 742 % (subrelpath(self), util.hidepassword(dsturl)))
743 743 return None
744 744 self.ui.status(_('pushing subrepo %s to %s\n') %
745 745 (subrelpath(self), util.hidepassword(dsturl)))
746 746 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
747 747 res = exchange.push(self._repo, other, force, newbranch=newbranch)
748 748
749 749 # the repo is now clean
750 750 self._cachestorehash(dsturl)
751 751 return res.cgresult
752 752
753 753 @annotatesubrepoerror
754 754 def outgoing(self, ui, dest, opts):
755 755 if 'rev' in opts or 'branch' in opts:
756 756 opts = copy.copy(opts)
757 757 opts.pop('rev', None)
758 758 opts.pop('branch', None)
759 759 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
760 760
761 761 @annotatesubrepoerror
762 762 def incoming(self, ui, source, opts):
763 763 if 'rev' in opts or 'branch' in opts:
764 764 opts = copy.copy(opts)
765 765 opts.pop('rev', None)
766 766 opts.pop('branch', None)
767 767 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
768 768
769 769 @annotatesubrepoerror
770 770 def files(self):
771 771 rev = self._state[1]
772 772 ctx = self._repo[rev]
773 773 return ctx.manifest().keys()
774 774
775 775 def filedata(self, name, decode):
776 776 rev = self._state[1]
777 777 data = self._repo[rev][name].data()
778 778 if decode:
779 779 data = self._repo.wwritedata(name, data)
780 780 return data
781 781
782 782 def fileflags(self, name):
783 783 rev = self._state[1]
784 784 ctx = self._repo[rev]
785 785 return ctx.flags(name)
786 786
787 787 @annotatesubrepoerror
788 788 def printfiles(self, ui, m, fm, fmt, subrepos):
789 789 # If the parent context is a workingctx, use the workingctx here for
790 790 # consistency.
791 791 if self._ctx.rev() is None:
792 792 ctx = self._repo[None]
793 793 else:
794 794 rev = self._state[1]
795 795 ctx = self._repo[rev]
796 796 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
797 797
798 798 @annotatesubrepoerror
799 799 def matchfileset(self, expr, badfn=None):
800 800 repo = self._repo
801 801 if self._ctx.rev() is None:
802 802 ctx = repo[None]
803 803 else:
804 804 rev = self._state[1]
805 805 ctx = repo[rev]
806 806
807 807 matchers = [ctx.matchfileset(expr, badfn=badfn)]
808 808
809 809 for subpath in ctx.substate:
810 810 sub = ctx.sub(subpath)
811 811
812 812 try:
813 813 sm = sub.matchfileset(expr, badfn=badfn)
814 814 pm = matchmod.prefixdirmatcher(repo.root, repo.getcwd(),
815 815 subpath, sm, badfn=badfn)
816 816 matchers.append(pm)
817 817 except error.LookupError:
818 818 self.ui.status(_("skipping missing subrepository: %s\n")
819 819 % self.wvfs.reljoin(reporelpath(self), subpath))
820 820 if len(matchers) == 1:
821 821 return matchers[0]
822 822 return matchmod.unionmatcher(matchers)
823 823
824 824 def walk(self, match):
825 825 ctx = self._repo[None]
826 826 return ctx.walk(match)
827 827
828 828 @annotatesubrepoerror
829 829 def forget(self, match, prefix, dryrun, interactive):
830 830 return cmdutil.forget(self.ui, self._repo, match,
831 831 self.wvfs.reljoin(prefix, self._path),
832 832 True, dryrun=dryrun, interactive=interactive)
833 833
834 834 @annotatesubrepoerror
835 835 def removefiles(self, matcher, prefix, after, force, subrepos,
836 836 dryrun, warnings):
837 837 return cmdutil.remove(self.ui, self._repo, matcher,
838 838 self.wvfs.reljoin(prefix, self._path),
839 839 after, force, subrepos, dryrun)
840 840
841 841 @annotatesubrepoerror
842 842 def revert(self, substate, *pats, **opts):
843 843 # reverting a subrepo is a 2 step process:
844 844 # 1. if the no_backup is not set, revert all modified
845 845 # files inside the subrepo
846 846 # 2. update the subrepo to the revision specified in
847 847 # the corresponding substate dictionary
848 848 self.ui.status(_('reverting subrepo %s\n') % substate[0])
849 849 if not opts.get(r'no_backup'):
850 850 # Revert all files on the subrepo, creating backups
851 851 # Note that this will not recursively revert subrepos
852 852 # We could do it if there was a set:subrepos() predicate
853 853 opts = opts.copy()
854 854 opts[r'date'] = None
855 855 opts[r'rev'] = substate[1]
856 856
857 857 self.filerevert(*pats, **opts)
858 858
859 859 # Update the repo to the revision specified in the given substate
860 860 if not opts.get(r'dry_run'):
861 861 self.get(substate, overwrite=True)
862 862
863 863 def filerevert(self, *pats, **opts):
864 864 ctx = self._repo[opts[r'rev']]
865 865 parents = self._repo.dirstate.parents()
866 866 if opts.get(r'all'):
867 867 pats = ['set:modified()']
868 868 else:
869 869 pats = []
870 870 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
871 871
872 872 def shortid(self, revid):
873 873 return revid[:12]
874 874
875 875 @annotatesubrepoerror
876 876 def unshare(self):
877 877 # subrepo inherently violates our import layering rules
878 878 # because it wants to make repo objects from deep inside the stack
879 879 # so we manually delay the circular imports to not break
880 880 # scripts that don't use our demand-loading
881 881 global hg
882 882 from . import hg as h
883 883 hg = h
884 884
885 885 # Nothing prevents a user from sharing in a repo, and then making that a
886 886 # subrepo. Alternately, the previous unshare attempt may have failed
887 887 # part way through. So recurse whether or not this layer is shared.
888 888 if self._repo.shared():
889 889 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
890 890
891 891 hg.unshare(self.ui, self._repo)
892 892
893 893 def verify(self):
894 894 try:
895 895 rev = self._state[1]
896 896 ctx = self._repo.unfiltered()[rev]
897 897 if ctx.hidden():
898 898 # Since hidden revisions aren't pushed/pulled, it seems worth an
899 899 # explicit warning.
900 900 ui = self._repo.ui
901 901 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
902 902 (self._relpath, node.short(self._ctx.node())))
903 903 return 0
904 904 except error.RepoLookupError:
905 905 # A missing subrepo revision may be a case of needing to pull it, so
906 906 # don't treat this as an error.
907 907 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
908 908 (self._relpath, node.short(self._ctx.node())))
909 909 return 0
910 910
911 911 @propertycache
912 912 def wvfs(self):
913 913 """return own wvfs for efficiency and consistency
914 914 """
915 915 return self._repo.wvfs
916 916
917 917 @propertycache
918 918 def _relpath(self):
919 919 """return path to this subrepository as seen from outermost repository
920 920 """
921 921 # Keep consistent dir separators by avoiding vfs.join(self._path)
922 922 return reporelpath(self._repo)
923 923
924 924 class svnsubrepo(abstractsubrepo):
925 925 def __init__(self, ctx, path, state, allowcreate):
926 926 super(svnsubrepo, self).__init__(ctx, path)
927 927 self._state = state
928 928 self._exe = procutil.findexe('svn')
929 929 if not self._exe:
930 930 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
931 931 % self._path)
932 932
933 933 def _svncommand(self, commands, filename='', failok=False):
934 934 cmd = [self._exe]
935 935 extrakw = {}
936 936 if not self.ui.interactive():
937 937 # Making stdin be a pipe should prevent svn from behaving
938 938 # interactively even if we can't pass --non-interactive.
939 939 extrakw[r'stdin'] = subprocess.PIPE
940 940 # Starting in svn 1.5 --non-interactive is a global flag
941 941 # instead of being per-command, but we need to support 1.4 so
942 942 # we have to be intelligent about what commands take
943 943 # --non-interactive.
944 944 if commands[0] in ('update', 'checkout', 'commit'):
945 945 cmd.append('--non-interactive')
946 946 cmd.extend(commands)
947 947 if filename is not None:
948 948 path = self.wvfs.reljoin(self._ctx.repo().origroot,
949 949 self._path, filename)
950 950 cmd.append(path)
951 951 env = dict(encoding.environ)
952 952 # Avoid localized output, preserve current locale for everything else.
953 953 lc_all = env.get('LC_ALL')
954 954 if lc_all:
955 955 env['LANG'] = lc_all
956 956 del env['LC_ALL']
957 957 env['LC_MESSAGES'] = 'C'
958 958 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
959 959 bufsize=-1, close_fds=procutil.closefds,
960 960 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
961 961 universal_newlines=True,
962 962 env=procutil.tonativeenv(env), **extrakw)
963 963 stdout, stderr = p.communicate()
964 964 stderr = stderr.strip()
965 965 if not failok:
966 966 if p.returncode:
967 967 raise error.Abort(stderr or 'exited with code %d'
968 968 % p.returncode)
969 969 if stderr:
970 970 self.ui.warn(stderr + '\n')
971 971 return stdout, stderr
972 972
973 973 @propertycache
974 974 def _svnversion(self):
975 975 output, err = self._svncommand(['--version', '--quiet'], filename=None)
976 976 m = re.search(br'^(\d+)\.(\d+)', output)
977 977 if not m:
978 978 raise error.Abort(_('cannot retrieve svn tool version'))
979 979 return (int(m.group(1)), int(m.group(2)))
980 980
981 981 def _svnmissing(self):
982 982 return not self.wvfs.exists('.svn')
983 983
984 984 def _wcrevs(self):
985 985 # Get the working directory revision as well as the last
986 986 # commit revision so we can compare the subrepo state with
987 987 # both. We used to store the working directory one.
988 988 output, err = self._svncommand(['info', '--xml'])
989 989 doc = xml.dom.minidom.parseString(output)
990 990 entries = doc.getElementsByTagName('entry')
991 991 lastrev, rev = '0', '0'
992 992 if entries:
993 993 rev = str(entries[0].getAttribute('revision')) or '0'
994 994 commits = entries[0].getElementsByTagName('commit')
995 995 if commits:
996 996 lastrev = str(commits[0].getAttribute('revision')) or '0'
997 997 return (lastrev, rev)
998 998
999 999 def _wcrev(self):
1000 1000 return self._wcrevs()[0]
1001 1001
1002 1002 def _wcchanged(self):
1003 1003 """Return (changes, extchanges, missing) where changes is True
1004 1004 if the working directory was changed, extchanges is
1005 1005 True if any of these changes concern an external entry and missing
1006 1006 is True if any change is a missing entry.
1007 1007 """
1008 1008 output, err = self._svncommand(['status', '--xml'])
1009 1009 externals, changes, missing = [], [], []
1010 1010 doc = xml.dom.minidom.parseString(output)
1011 1011 for e in doc.getElementsByTagName('entry'):
1012 1012 s = e.getElementsByTagName('wc-status')
1013 1013 if not s:
1014 1014 continue
1015 1015 item = s[0].getAttribute('item')
1016 1016 props = s[0].getAttribute('props')
1017 1017 path = e.getAttribute('path')
1018 1018 if item == 'external':
1019 1019 externals.append(path)
1020 1020 elif item == 'missing':
1021 1021 missing.append(path)
1022 1022 if (item not in ('', 'normal', 'unversioned', 'external')
1023 1023 or props not in ('', 'none', 'normal')):
1024 1024 changes.append(path)
1025 1025 for path in changes:
1026 1026 for ext in externals:
1027 1027 if path == ext or path.startswith(ext + pycompat.ossep):
1028 1028 return True, True, bool(missing)
1029 1029 return bool(changes), False, bool(missing)
1030 1030
1031 1031 @annotatesubrepoerror
1032 1032 def dirty(self, ignoreupdate=False, missing=False):
1033 1033 if self._svnmissing():
1034 1034 return self._state[1] != ''
1035 1035 wcchanged = self._wcchanged()
1036 1036 changed = wcchanged[0] or (missing and wcchanged[2])
1037 1037 if not changed:
1038 1038 if self._state[1] in self._wcrevs() or ignoreupdate:
1039 1039 return False
1040 1040 return True
1041 1041
1042 1042 def basestate(self):
1043 1043 lastrev, rev = self._wcrevs()
1044 1044 if lastrev != rev:
1045 1045 # Last committed rev is not the same than rev. We would
1046 1046 # like to take lastrev but we do not know if the subrepo
1047 1047 # URL exists at lastrev. Test it and fallback to rev it
1048 1048 # is not there.
1049 1049 try:
1050 1050 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1051 1051 return lastrev
1052 1052 except error.Abort:
1053 1053 pass
1054 1054 return rev
1055 1055
1056 1056 @annotatesubrepoerror
1057 1057 def commit(self, text, user, date):
1058 1058 # user and date are out of our hands since svn is centralized
1059 1059 changed, extchanged, missing = self._wcchanged()
1060 1060 if not changed:
1061 1061 return self.basestate()
1062 1062 if extchanged:
1063 1063 # Do not try to commit externals
1064 1064 raise error.Abort(_('cannot commit svn externals'))
1065 1065 if missing:
1066 1066 # svn can commit with missing entries but aborting like hg
1067 1067 # seems a better approach.
1068 1068 raise error.Abort(_('cannot commit missing svn entries'))
1069 1069 commitinfo, err = self._svncommand(['commit', '-m', text])
1070 1070 self.ui.status(commitinfo)
1071 1071 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1072 1072 if not newrev:
1073 1073 if not commitinfo.strip():
1074 1074 # Sometimes, our definition of "changed" differs from
1075 1075 # svn one. For instance, svn ignores missing files
1076 1076 # when committing. If there are only missing files, no
1077 1077 # commit is made, no output and no error code.
1078 1078 raise error.Abort(_('failed to commit svn changes'))
1079 1079 raise error.Abort(commitinfo.splitlines()[-1])
1080 1080 newrev = newrev.groups()[0]
1081 1081 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1082 1082 return newrev
1083 1083
1084 1084 @annotatesubrepoerror
1085 1085 def remove(self):
1086 1086 if self.dirty():
1087 1087 self.ui.warn(_('not removing repo %s because '
1088 1088 'it has changes.\n') % self._path)
1089 1089 return
1090 1090 self.ui.note(_('removing subrepo %s\n') % self._path)
1091 1091
1092 1092 self.wvfs.rmtree(forcibly=True)
1093 1093 try:
1094 1094 pwvfs = self._ctx.repo().wvfs
1095 1095 pwvfs.removedirs(pwvfs.dirname(self._path))
1096 1096 except OSError:
1097 1097 pass
1098 1098
1099 1099 @annotatesubrepoerror
1100 1100 def get(self, state, overwrite=False):
1101 1101 if overwrite:
1102 1102 self._svncommand(['revert', '--recursive'])
1103 1103 args = ['checkout']
1104 1104 if self._svnversion >= (1, 5):
1105 1105 args.append('--force')
1106 1106 # The revision must be specified at the end of the URL to properly
1107 1107 # update to a directory which has since been deleted and recreated.
1108 1108 args.append('%s@%s' % (state[0], state[1]))
1109 1109
1110 1110 # SEC: check that the ssh url is safe
1111 1111 util.checksafessh(state[0])
1112 1112
1113 1113 status, err = self._svncommand(args, failok=True)
1114 1114 _sanitize(self.ui, self.wvfs, '.svn')
1115 1115 if not re.search('Checked out revision [0-9]+.', status):
1116 1116 if ('is already a working copy for a different URL' in err
1117 1117 and (self._wcchanged()[:2] == (False, False))):
1118 1118 # obstructed but clean working copy, so just blow it away.
1119 1119 self.remove()
1120 1120 self.get(state, overwrite=False)
1121 1121 return
1122 1122 raise error.Abort((status or err).splitlines()[-1])
1123 1123 self.ui.status(status)
1124 1124
1125 1125 @annotatesubrepoerror
1126 1126 def merge(self, state):
1127 1127 old = self._state[1]
1128 1128 new = state[1]
1129 1129 wcrev = self._wcrev()
1130 1130 if new != wcrev:
1131 1131 dirty = old == wcrev or self._wcchanged()[0]
1132 1132 if _updateprompt(self.ui, self, dirty, wcrev, new):
1133 1133 self.get(state, False)
1134 1134
1135 1135 def push(self, opts):
1136 1136 # push is a no-op for SVN
1137 1137 return True
1138 1138
1139 1139 @annotatesubrepoerror
1140 1140 def files(self):
1141 1141 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1142 1142 doc = xml.dom.minidom.parseString(output)
1143 1143 paths = []
1144 1144 for e in doc.getElementsByTagName('entry'):
1145 1145 kind = pycompat.bytestr(e.getAttribute('kind'))
1146 1146 if kind != 'file':
1147 1147 continue
1148 1148 name = ''.join(c.data for c
1149 1149 in e.getElementsByTagName('name')[0].childNodes
1150 1150 if c.nodeType == c.TEXT_NODE)
1151 1151 paths.append(name.encode('utf-8'))
1152 1152 return paths
1153 1153
1154 1154 def filedata(self, name, decode):
1155 1155 return self._svncommand(['cat'], name)[0]
1156 1156
1157 1157
1158 1158 class gitsubrepo(abstractsubrepo):
1159 1159 def __init__(self, ctx, path, state, allowcreate):
1160 1160 super(gitsubrepo, self).__init__(ctx, path)
1161 1161 self._state = state
1162 1162 self._abspath = ctx.repo().wjoin(path)
1163 1163 self._subparent = ctx.repo()
1164 1164 self._ensuregit()
1165 1165
1166 1166 def _ensuregit(self):
1167 1167 try:
1168 1168 self._gitexecutable = 'git'
1169 1169 out, err = self._gitnodir(['--version'])
1170 1170 except OSError as e:
1171 1171 genericerror = _("error executing git for subrepo '%s': %s")
1172 1172 notfoundhint = _("check git is installed and in your PATH")
1173 1173 if e.errno != errno.ENOENT:
1174 1174 raise error.Abort(genericerror % (
1175 1175 self._path, encoding.strtolocal(e.strerror)))
1176 1176 elif pycompat.iswindows:
1177 1177 try:
1178 1178 self._gitexecutable = 'git.cmd'
1179 1179 out, err = self._gitnodir(['--version'])
1180 1180 except OSError as e2:
1181 1181 if e2.errno == errno.ENOENT:
1182 1182 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1183 1183 " for subrepo '%s'") % self._path,
1184 1184 hint=notfoundhint)
1185 1185 else:
1186 1186 raise error.Abort(genericerror % (self._path,
1187 1187 encoding.strtolocal(e2.strerror)))
1188 1188 else:
1189 1189 raise error.Abort(_("couldn't find git for subrepo '%s'")
1190 1190 % self._path, hint=notfoundhint)
1191 1191 versionstatus = self._checkversion(out)
1192 1192 if versionstatus == 'unknown':
1193 1193 self.ui.warn(_('cannot retrieve git version\n'))
1194 1194 elif versionstatus == 'abort':
1195 1195 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1196 1196 elif versionstatus == 'warning':
1197 1197 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1198 1198
1199 1199 @staticmethod
1200 1200 def _gitversion(out):
1201 1201 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1202 1202 if m:
1203 1203 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1204 1204
1205 1205 m = re.search(br'^git version (\d+)\.(\d+)', out)
1206 1206 if m:
1207 1207 return (int(m.group(1)), int(m.group(2)), 0)
1208 1208
1209 1209 return -1
1210 1210
1211 1211 @staticmethod
1212 1212 def _checkversion(out):
1213 1213 '''ensure git version is new enough
1214 1214
1215 1215 >>> _checkversion = gitsubrepo._checkversion
1216 1216 >>> _checkversion(b'git version 1.6.0')
1217 1217 'ok'
1218 1218 >>> _checkversion(b'git version 1.8.5')
1219 1219 'ok'
1220 1220 >>> _checkversion(b'git version 1.4.0')
1221 1221 'abort'
1222 1222 >>> _checkversion(b'git version 1.5.0')
1223 1223 'warning'
1224 1224 >>> _checkversion(b'git version 1.9-rc0')
1225 1225 'ok'
1226 1226 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1227 1227 'ok'
1228 1228 >>> _checkversion(b'git version 1.9.0.GIT')
1229 1229 'ok'
1230 1230 >>> _checkversion(b'git version 12345')
1231 1231 'unknown'
1232 1232 >>> _checkversion(b'no')
1233 1233 'unknown'
1234 1234 '''
1235 1235 version = gitsubrepo._gitversion(out)
1236 1236 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1237 1237 # despite the docstring comment. For now, error on 1.4.0, warn on
1238 1238 # 1.5.0 but attempt to continue.
1239 1239 if version == -1:
1240 1240 return 'unknown'
1241 1241 if version < (1, 5, 0):
1242 1242 return 'abort'
1243 1243 elif version < (1, 6, 0):
1244 1244 return 'warning'
1245 1245 return 'ok'
1246 1246
1247 1247 def _gitcommand(self, commands, env=None, stream=False):
1248 1248 return self._gitdir(commands, env=env, stream=stream)[0]
1249 1249
1250 1250 def _gitdir(self, commands, env=None, stream=False):
1251 1251 return self._gitnodir(commands, env=env, stream=stream,
1252 1252 cwd=self._abspath)
1253 1253
1254 1254 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1255 1255 """Calls the git command
1256 1256
1257 1257 The methods tries to call the git command. versions prior to 1.6.0
1258 1258 are not supported and very probably fail.
1259 1259 """
1260 1260 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1261 1261 if env is None:
1262 1262 env = encoding.environ.copy()
1263 1263 # disable localization for Git output (issue5176)
1264 1264 env['LC_ALL'] = 'C'
1265 1265 # fix for Git CVE-2015-7545
1266 1266 if 'GIT_ALLOW_PROTOCOL' not in env:
1267 1267 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1268 1268 # unless ui.quiet is set, print git's stderr,
1269 1269 # which is mostly progress and useful info
1270 1270 errpipe = None
1271 1271 if self.ui.quiet:
1272 1272 errpipe = open(os.devnull, 'w')
1273 1273 if self.ui._colormode and len(commands) and commands[0] == "diff":
1274 1274 # insert the argument in the front,
1275 1275 # the end of git diff arguments is used for paths
1276 1276 commands.insert(1, '--color')
1277 1277 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr,
1278 1278 [self._gitexecutable] + commands),
1279 1279 bufsize=-1,
1280 1280 cwd=pycompat.rapply(procutil.tonativestr, cwd),
1281 1281 env=procutil.tonativeenv(env),
1282 1282 close_fds=procutil.closefds,
1283 1283 stdout=subprocess.PIPE, stderr=errpipe)
1284 1284 if stream:
1285 1285 return p.stdout, None
1286 1286
1287 retdata = p.stdout.read().strip()
1287 retdata = pycompat.fsencode(p.stdout.read().strip())
1288 1288 # wait for the child to exit to avoid race condition.
1289 1289 p.wait()
1290 1290
1291 1291 if p.returncode != 0 and p.returncode != 1:
1292 1292 # there are certain error codes that are ok
1293 1293 command = commands[0]
1294 1294 if command in ('cat-file', 'symbolic-ref'):
1295 1295 return retdata, p.returncode
1296 1296 # for all others, abort
1297 1297 raise error.Abort(_('git %s error %d in %s') %
1298 1298 (command, p.returncode, self._relpath))
1299 1299
1300 1300 return retdata, p.returncode
1301 1301
1302 1302 def _gitmissing(self):
1303 1303 return not self.wvfs.exists('.git')
1304 1304
1305 1305 def _gitstate(self):
1306 1306 return self._gitcommand(['rev-parse', 'HEAD'])
1307 1307
1308 1308 def _gitcurrentbranch(self):
1309 1309 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1310 1310 if err:
1311 1311 current = None
1312 1312 return current
1313 1313
1314 1314 def _gitremote(self, remote):
1315 1315 out = self._gitcommand(['remote', 'show', '-n', remote])
1316 1316 line = out.split('\n')[1]
1317 1317 i = line.index('URL: ') + len('URL: ')
1318 1318 return line[i:]
1319 1319
1320 1320 def _githavelocally(self, revision):
1321 1321 out, code = self._gitdir(['cat-file', '-e', revision])
1322 1322 return code == 0
1323 1323
1324 1324 def _gitisancestor(self, r1, r2):
1325 1325 base = self._gitcommand(['merge-base', r1, r2])
1326 1326 return base == r1
1327 1327
1328 1328 def _gitisbare(self):
1329 1329 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1330 1330
1331 1331 def _gitupdatestat(self):
1332 1332 """This must be run before git diff-index.
1333 1333 diff-index only looks at changes to file stat;
1334 1334 this command looks at file contents and updates the stat."""
1335 1335 self._gitcommand(['update-index', '-q', '--refresh'])
1336 1336
1337 1337 def _gitbranchmap(self):
1338 1338 '''returns 2 things:
1339 1339 a map from git branch to revision
1340 1340 a map from revision to branches'''
1341 1341 branch2rev = {}
1342 1342 rev2branch = {}
1343 1343
1344 1344 out = self._gitcommand(['for-each-ref', '--format',
1345 1345 '%(objectname) %(refname)'])
1346 1346 for line in out.split('\n'):
1347 1347 revision, ref = line.split(' ')
1348 1348 if (not ref.startswith('refs/heads/') and
1349 1349 not ref.startswith('refs/remotes/')):
1350 1350 continue
1351 1351 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1352 1352 continue # ignore remote/HEAD redirects
1353 1353 branch2rev[ref] = revision
1354 1354 rev2branch.setdefault(revision, []).append(ref)
1355 1355 return branch2rev, rev2branch
1356 1356
1357 1357 def _gittracking(self, branches):
1358 1358 'return map of remote branch to local tracking branch'
1359 1359 # assumes no more than one local tracking branch for each remote
1360 1360 tracking = {}
1361 1361 for b in branches:
1362 1362 if b.startswith('refs/remotes/'):
1363 1363 continue
1364 1364 bname = b.split('/', 2)[2]
1365 1365 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1366 1366 if remote:
1367 1367 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1368 1368 tracking['refs/remotes/%s/%s' %
1369 1369 (remote, ref.split('/', 2)[2])] = b
1370 1370 return tracking
1371 1371
1372 1372 def _abssource(self, source):
1373 1373 if '://' not in source:
1374 1374 # recognize the scp syntax as an absolute source
1375 1375 colon = source.find(':')
1376 1376 if colon != -1 and '/' not in source[:colon]:
1377 1377 return source
1378 1378 self._subsource = source
1379 1379 return _abssource(self)
1380 1380
1381 1381 def _fetch(self, source, revision):
1382 1382 if self._gitmissing():
1383 1383 # SEC: check for safe ssh url
1384 1384 util.checksafessh(source)
1385 1385
1386 1386 source = self._abssource(source)
1387 1387 self.ui.status(_('cloning subrepo %s from %s\n') %
1388 1388 (self._relpath, source))
1389 1389 self._gitnodir(['clone', source, self._abspath])
1390 1390 if self._githavelocally(revision):
1391 1391 return
1392 1392 self.ui.status(_('pulling subrepo %s from %s\n') %
1393 1393 (self._relpath, self._gitremote('origin')))
1394 1394 # try only origin: the originally cloned repo
1395 1395 self._gitcommand(['fetch'])
1396 1396 if not self._githavelocally(revision):
1397 1397 raise error.Abort(_('revision %s does not exist in subrepository '
1398 1398 '"%s"\n') % (revision, self._relpath))
1399 1399
1400 1400 @annotatesubrepoerror
1401 1401 def dirty(self, ignoreupdate=False, missing=False):
1402 1402 if self._gitmissing():
1403 1403 return self._state[1] != ''
1404 1404 if self._gitisbare():
1405 1405 return True
1406 1406 if not ignoreupdate and self._state[1] != self._gitstate():
1407 1407 # different version checked out
1408 1408 return True
1409 1409 # check for staged changes or modified files; ignore untracked files
1410 1410 self._gitupdatestat()
1411 1411 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1412 1412 return code == 1
1413 1413
1414 1414 def basestate(self):
1415 1415 return self._gitstate()
1416 1416
1417 1417 @annotatesubrepoerror
1418 1418 def get(self, state, overwrite=False):
1419 1419 source, revision, kind = state
1420 1420 if not revision:
1421 1421 self.remove()
1422 1422 return
1423 1423 self._fetch(source, revision)
1424 1424 # if the repo was set to be bare, unbare it
1425 1425 if self._gitisbare():
1426 1426 self._gitcommand(['config', 'core.bare', 'false'])
1427 1427 if self._gitstate() == revision:
1428 1428 self._gitcommand(['reset', '--hard', 'HEAD'])
1429 1429 return
1430 1430 elif self._gitstate() == revision:
1431 1431 if overwrite:
1432 1432 # first reset the index to unmark new files for commit, because
1433 1433 # reset --hard will otherwise throw away files added for commit,
1434 1434 # not just unmark them.
1435 1435 self._gitcommand(['reset', 'HEAD'])
1436 1436 self._gitcommand(['reset', '--hard', 'HEAD'])
1437 1437 return
1438 1438 branch2rev, rev2branch = self._gitbranchmap()
1439 1439
1440 1440 def checkout(args):
1441 1441 cmd = ['checkout']
1442 1442 if overwrite:
1443 1443 # first reset the index to unmark new files for commit, because
1444 1444 # the -f option will otherwise throw away files added for
1445 1445 # commit, not just unmark them.
1446 1446 self._gitcommand(['reset', 'HEAD'])
1447 1447 cmd.append('-f')
1448 1448 self._gitcommand(cmd + args)
1449 1449 _sanitize(self.ui, self.wvfs, '.git')
1450 1450
1451 1451 def rawcheckout():
1452 1452 # no branch to checkout, check it out with no branch
1453 1453 self.ui.warn(_('checking out detached HEAD in '
1454 1454 'subrepository "%s"\n') % self._relpath)
1455 1455 self.ui.warn(_('check out a git branch if you intend '
1456 1456 'to make changes\n'))
1457 1457 checkout(['-q', revision])
1458 1458
1459 1459 if revision not in rev2branch:
1460 1460 rawcheckout()
1461 1461 return
1462 1462 branches = rev2branch[revision]
1463 1463 firstlocalbranch = None
1464 1464 for b in branches:
1465 1465 if b == 'refs/heads/master':
1466 1466 # master trumps all other branches
1467 1467 checkout(['refs/heads/master'])
1468 1468 return
1469 1469 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1470 1470 firstlocalbranch = b
1471 1471 if firstlocalbranch:
1472 1472 checkout([firstlocalbranch])
1473 1473 return
1474 1474
1475 1475 tracking = self._gittracking(branch2rev.keys())
1476 1476 # choose a remote branch already tracked if possible
1477 1477 remote = branches[0]
1478 1478 if remote not in tracking:
1479 1479 for b in branches:
1480 1480 if b in tracking:
1481 1481 remote = b
1482 1482 break
1483 1483
1484 1484 if remote not in tracking:
1485 1485 # create a new local tracking branch
1486 1486 local = remote.split('/', 3)[3]
1487 1487 checkout(['-b', local, remote])
1488 1488 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1489 1489 # When updating to a tracked remote branch,
1490 1490 # if the local tracking branch is downstream of it,
1491 1491 # a normal `git pull` would have performed a "fast-forward merge"
1492 1492 # which is equivalent to updating the local branch to the remote.
1493 1493 # Since we are only looking at branching at update, we need to
1494 1494 # detect this situation and perform this action lazily.
1495 1495 if tracking[remote] != self._gitcurrentbranch():
1496 1496 checkout([tracking[remote]])
1497 1497 self._gitcommand(['merge', '--ff', remote])
1498 1498 _sanitize(self.ui, self.wvfs, '.git')
1499 1499 else:
1500 1500 # a real merge would be required, just checkout the revision
1501 1501 rawcheckout()
1502 1502
1503 1503 @annotatesubrepoerror
1504 1504 def commit(self, text, user, date):
1505 1505 if self._gitmissing():
1506 1506 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1507 1507 cmd = ['commit', '-a', '-m', text]
1508 1508 env = encoding.environ.copy()
1509 1509 if user:
1510 1510 cmd += ['--author', user]
1511 1511 if date:
1512 1512 # git's date parser silently ignores when seconds < 1e9
1513 1513 # convert to ISO8601
1514 1514 env['GIT_AUTHOR_DATE'] = dateutil.datestr(date,
1515 1515 '%Y-%m-%dT%H:%M:%S %1%2')
1516 1516 self._gitcommand(cmd, env=env)
1517 1517 # make sure commit works otherwise HEAD might not exist under certain
1518 1518 # circumstances
1519 1519 return self._gitstate()
1520 1520
1521 1521 @annotatesubrepoerror
1522 1522 def merge(self, state):
1523 1523 source, revision, kind = state
1524 1524 self._fetch(source, revision)
1525 1525 base = self._gitcommand(['merge-base', revision, self._state[1]])
1526 1526 self._gitupdatestat()
1527 1527 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1528 1528
1529 1529 def mergefunc():
1530 1530 if base == revision:
1531 1531 self.get(state) # fast forward merge
1532 1532 elif base != self._state[1]:
1533 1533 self._gitcommand(['merge', '--no-commit', revision])
1534 1534 _sanitize(self.ui, self.wvfs, '.git')
1535 1535
1536 1536 if self.dirty():
1537 1537 if self._gitstate() != revision:
1538 1538 dirty = self._gitstate() == self._state[1] or code != 0
1539 1539 if _updateprompt(self.ui, self, dirty,
1540 1540 self._state[1][:7], revision[:7]):
1541 1541 mergefunc()
1542 1542 else:
1543 1543 mergefunc()
1544 1544
1545 1545 @annotatesubrepoerror
1546 1546 def push(self, opts):
1547 1547 force = opts.get('force')
1548 1548
1549 1549 if not self._state[1]:
1550 1550 return True
1551 1551 if self._gitmissing():
1552 1552 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1553 1553 # if a branch in origin contains the revision, nothing to do
1554 1554 branch2rev, rev2branch = self._gitbranchmap()
1555 1555 if self._state[1] in rev2branch:
1556 1556 for b in rev2branch[self._state[1]]:
1557 1557 if b.startswith('refs/remotes/origin/'):
1558 1558 return True
1559 1559 for b, revision in branch2rev.iteritems():
1560 1560 if b.startswith('refs/remotes/origin/'):
1561 1561 if self._gitisancestor(self._state[1], revision):
1562 1562 return True
1563 1563 # otherwise, try to push the currently checked out branch
1564 1564 cmd = ['push']
1565 1565 if force:
1566 1566 cmd.append('--force')
1567 1567
1568 1568 current = self._gitcurrentbranch()
1569 1569 if current:
1570 1570 # determine if the current branch is even useful
1571 1571 if not self._gitisancestor(self._state[1], current):
1572 1572 self.ui.warn(_('unrelated git branch checked out '
1573 1573 'in subrepository "%s"\n') % self._relpath)
1574 1574 return False
1575 1575 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1576 1576 (current.split('/', 2)[2], self._relpath))
1577 1577 ret = self._gitdir(cmd + ['origin', current])
1578 1578 return ret[1] == 0
1579 1579 else:
1580 1580 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1581 1581 'cannot push revision %s\n') %
1582 1582 (self._relpath, self._state[1]))
1583 1583 return False
1584 1584
1585 1585 @annotatesubrepoerror
1586 1586 def add(self, ui, match, prefix, explicitonly, **opts):
1587 1587 if self._gitmissing():
1588 1588 return []
1589 1589
1590 1590 s = self.status(None, unknown=True, clean=True)
1591 1591
1592 1592 tracked = set()
1593 1593 # dirstates 'amn' warn, 'r' is added again
1594 1594 for l in (s.modified, s.added, s.deleted, s.clean):
1595 1595 tracked.update(l)
1596 1596
1597 1597 # Unknown files not of interest will be rejected by the matcher
1598 1598 files = s.unknown
1599 1599 files.extend(match.files())
1600 1600
1601 1601 rejected = []
1602 1602
1603 1603 files = [f for f in sorted(set(files)) if match(f)]
1604 1604 for f in files:
1605 1605 exact = match.exact(f)
1606 1606 command = ["add"]
1607 1607 if exact:
1608 1608 command.append("-f") #should be added, even if ignored
1609 1609 if ui.verbose or not exact:
1610 1610 ui.status(_('adding %s\n') % match.rel(f))
1611 1611
1612 1612 if f in tracked: # hg prints 'adding' even if already tracked
1613 1613 if exact:
1614 1614 rejected.append(f)
1615 1615 continue
1616 1616 if not opts.get(r'dry_run'):
1617 1617 self._gitcommand(command + [f])
1618 1618
1619 1619 for f in rejected:
1620 1620 ui.warn(_("%s already tracked!\n") % match.abs(f))
1621 1621
1622 1622 return rejected
1623 1623
1624 1624 @annotatesubrepoerror
1625 1625 def remove(self):
1626 1626 if self._gitmissing():
1627 1627 return
1628 1628 if self.dirty():
1629 1629 self.ui.warn(_('not removing repo %s because '
1630 1630 'it has changes.\n') % self._relpath)
1631 1631 return
1632 1632 # we can't fully delete the repository as it may contain
1633 1633 # local-only history
1634 1634 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1635 1635 self._gitcommand(['config', 'core.bare', 'true'])
1636 1636 for f, kind in self.wvfs.readdir():
1637 1637 if f == '.git':
1638 1638 continue
1639 1639 if kind == stat.S_IFDIR:
1640 1640 self.wvfs.rmtree(f)
1641 1641 else:
1642 1642 self.wvfs.unlink(f)
1643 1643
1644 1644 def archive(self, archiver, prefix, match=None, decode=True):
1645 1645 total = 0
1646 1646 source, revision = self._state
1647 1647 if not revision:
1648 1648 return total
1649 1649 self._fetch(source, revision)
1650 1650
1651 1651 # Parse git's native archive command.
1652 1652 # This should be much faster than manually traversing the trees
1653 1653 # and objects with many subprocess calls.
1654 1654 tarstream = self._gitcommand(['archive', revision], stream=True)
1655 1655 tar = tarfile.open(fileobj=tarstream, mode=r'r|')
1656 1656 relpath = subrelpath(self)
1657 1657 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
1658 1658 unit=_('files'))
1659 1659 progress.update(0)
1660 1660 for info in tar:
1661 1661 if info.isdir():
1662 1662 continue
1663 if match and not match(info.name):
1663 bname = pycompat.fsencode(info.name)
1664 if match and not match(bname):
1664 1665 continue
1665 1666 if info.issym():
1666 1667 data = info.linkname
1667 1668 else:
1668 1669 data = tar.extractfile(info).read()
1669 archiver.addfile(prefix + self._path + '/' + info.name,
1670 archiver.addfile(prefix + self._path + '/' + bname,
1670 1671 info.mode, info.issym(), data)
1671 1672 total += 1
1672 1673 progress.increment()
1673 1674 progress.complete()
1674 1675 return total
1675 1676
1676 1677
1677 1678 @annotatesubrepoerror
1678 1679 def cat(self, match, fm, fntemplate, prefix, **opts):
1679 1680 rev = self._state[1]
1680 1681 if match.anypats():
1681 1682 return 1 #No support for include/exclude yet
1682 1683
1683 1684 if not match.files():
1684 1685 return 1
1685 1686
1686 1687 # TODO: add support for non-plain formatter (see cmdutil.cat())
1687 1688 for f in match.files():
1688 1689 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1689 1690 fp = cmdutil.makefileobj(self._ctx, fntemplate,
1690 1691 pathname=self.wvfs.reljoin(prefix, f))
1691 1692 fp.write(output)
1692 1693 fp.close()
1693 1694 return 0
1694 1695
1695 1696
1696 1697 @annotatesubrepoerror
1697 1698 def status(self, rev2, **opts):
1698 1699 rev1 = self._state[1]
1699 1700 if self._gitmissing() or not rev1:
1700 1701 # if the repo is missing, return no results
1701 1702 return scmutil.status([], [], [], [], [], [], [])
1702 1703 modified, added, removed = [], [], []
1703 1704 self._gitupdatestat()
1704 1705 if rev2:
1705 1706 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1706 1707 else:
1707 1708 command = ['diff-index', '--no-renames', rev1]
1708 1709 out = self._gitcommand(command)
1709 1710 for line in out.split('\n'):
1710 1711 tab = line.find('\t')
1711 1712 if tab == -1:
1712 1713 continue
1713 1714 status, f = line[tab - 1:tab], line[tab + 1:]
1714 1715 if status == 'M':
1715 1716 modified.append(f)
1716 1717 elif status == 'A':
1717 1718 added.append(f)
1718 1719 elif status == 'D':
1719 1720 removed.append(f)
1720 1721
1721 1722 deleted, unknown, ignored, clean = [], [], [], []
1722 1723
1723 1724 command = ['status', '--porcelain', '-z']
1724 1725 if opts.get(r'unknown'):
1725 1726 command += ['--untracked-files=all']
1726 1727 if opts.get(r'ignored'):
1727 1728 command += ['--ignored']
1728 1729 out = self._gitcommand(command)
1729 1730
1730 1731 changedfiles = set()
1731 1732 changedfiles.update(modified)
1732 1733 changedfiles.update(added)
1733 1734 changedfiles.update(removed)
1734 1735 for line in out.split('\0'):
1735 1736 if not line:
1736 1737 continue
1737 1738 st = line[0:2]
1738 1739 #moves and copies show 2 files on one line
1739 1740 if line.find('\0') >= 0:
1740 1741 filename1, filename2 = line[3:].split('\0')
1741 1742 else:
1742 1743 filename1 = line[3:]
1743 1744 filename2 = None
1744 1745
1745 1746 changedfiles.add(filename1)
1746 1747 if filename2:
1747 1748 changedfiles.add(filename2)
1748 1749
1749 1750 if st == '??':
1750 1751 unknown.append(filename1)
1751 1752 elif st == '!!':
1752 1753 ignored.append(filename1)
1753 1754
1754 1755 if opts.get(r'clean'):
1755 1756 out = self._gitcommand(['ls-files'])
1756 1757 for f in out.split('\n'):
1757 1758 if not f in changedfiles:
1758 1759 clean.append(f)
1759 1760
1760 1761 return scmutil.status(modified, added, removed, deleted,
1761 1762 unknown, ignored, clean)
1762 1763
1763 1764 @annotatesubrepoerror
1764 1765 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1765 1766 node1 = self._state[1]
1766 1767 cmd = ['diff', '--no-renames']
1767 1768 if opts[r'stat']:
1768 1769 cmd.append('--stat')
1769 1770 else:
1770 1771 # for Git, this also implies '-p'
1771 1772 cmd.append('-U%d' % diffopts.context)
1772 1773
1773 1774 gitprefix = self.wvfs.reljoin(prefix, self._path)
1774 1775
1775 1776 if diffopts.noprefix:
1776 1777 cmd.extend(['--src-prefix=%s/' % gitprefix,
1777 1778 '--dst-prefix=%s/' % gitprefix])
1778 1779 else:
1779 1780 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1780 1781 '--dst-prefix=b/%s/' % gitprefix])
1781 1782
1782 1783 if diffopts.ignorews:
1783 1784 cmd.append('--ignore-all-space')
1784 1785 if diffopts.ignorewsamount:
1785 1786 cmd.append('--ignore-space-change')
1786 1787 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1787 1788 and diffopts.ignoreblanklines:
1788 1789 cmd.append('--ignore-blank-lines')
1789 1790
1790 1791 cmd.append(node1)
1791 1792 if node2:
1792 1793 cmd.append(node2)
1793 1794
1794 1795 output = ""
1795 1796 if match.always():
1796 1797 output += self._gitcommand(cmd) + '\n'
1797 1798 else:
1798 1799 st = self.status(node2)[:3]
1799 1800 files = [f for sublist in st for f in sublist]
1800 1801 for f in files:
1801 1802 if match(f):
1802 1803 output += self._gitcommand(cmd + ['--', f]) + '\n'
1803 1804
1804 1805 if output.strip():
1805 1806 ui.write(output)
1806 1807
1807 1808 @annotatesubrepoerror
1808 1809 def revert(self, substate, *pats, **opts):
1809 1810 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1810 1811 if not opts.get(r'no_backup'):
1811 1812 status = self.status(None)
1812 1813 names = status.modified
1813 1814 origvfs = scmutil.getorigvfs(self.ui, self._subparent)
1814 1815 if origvfs is None:
1815 1816 origvfs = self.wvfs
1816 1817 for name in names:
1817 1818 bakname = scmutil.origpath(self.ui, self._subparent, name)
1818 1819 self.ui.note(_('saving current version of %s as %s\n') %
1819 1820 (name, bakname))
1820 1821 name = self.wvfs.join(name)
1821 1822 origvfs.rename(name, bakname)
1822 1823
1823 1824 if not opts.get(r'dry_run'):
1824 1825 self.get(substate, overwrite=True)
1825 1826 return []
1826 1827
1827 1828 def shortid(self, revid):
1828 1829 return revid[:7]
1829 1830
1830 1831 types = {
1831 1832 'hg': hgsubrepo,
1832 1833 'svn': svnsubrepo,
1833 1834 'git': gitsubrepo,
1834 1835 }
General Comments 0
You need to be logged in to leave comments. Login now