##// END OF EJS Templates
lfs: explicitly add the Content-Length header when uploading blobs, for py3...
Matt Harbison -
r41485:1bc01490 default
parent child Browse files
Show More
@@ -1,739 +1,740
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-hg-sink.t
120 120 test-convert-hg-source.t
121 121 test-convert-hg-startrev.t
122 122 test-convert-splicemap.t
123 123 test-convert-svn-sink.t
124 124 test-convert-tagsbranch-topology.t
125 125 test-copy-move-merge.t
126 126 test-copy.t
127 127 test-copytrace-heuristics.t
128 128 test-custom-filters.t
129 129 test-debugbuilddag.t
130 130 test-debugbundle.t
131 131 test-debugcommands.t
132 132 test-debugextensions.t
133 133 test-debugindexdot.t
134 134 test-debugrename.t
135 135 test-default-push.t
136 136 test-diff-antipatience.t
137 137 test-diff-binary-file.t
138 138 test-diff-change.t
139 139 test-diff-color.t
140 140 test-diff-copy-depth.t
141 141 test-diff-hashes.t
142 142 test-diff-ignore-whitespace.t
143 143 test-diff-indent-heuristic.t
144 144 test-diff-issue2761.t
145 145 test-diff-newlines.t
146 146 test-diff-reverse.t
147 147 test-diff-subdir.t
148 148 test-diff-unified.t
149 149 test-diff-upgrade.t
150 150 test-diffdir.t
151 151 test-diffstat.t
152 152 test-directaccess.t
153 153 test-dirstate-backup.t
154 154 test-dirstate-nonnormalset.t
155 155 test-dirstate-race.t
156 156 test-dirstate.t
157 157 test-dispatch.py
158 158 test-doctest.py
159 159 test-double-merge.t
160 160 test-drawdag.t
161 161 test-duplicateoptions.py
162 162 test-editor-filename.t
163 163 test-empty-dir.t
164 164 test-empty-file.t
165 165 test-empty-group.t
166 166 test-empty.t
167 167 test-encode.t
168 168 test-encoding-align.t
169 169 test-encoding-func.py
170 170 test-encoding-textwrap.t
171 171 test-encoding.t
172 172 test-eol-add.t
173 173 test-eol-clone.t
174 174 test-eol-hook.t
175 175 test-eol-patch.t
176 176 test-eol-tag.t
177 177 test-eol-update.t
178 178 test-eol.t
179 179 test-eolfilename.t
180 180 test-excessive-merge.t
181 181 test-exchange-obsmarkers-case-A1.t
182 182 test-exchange-obsmarkers-case-A2.t
183 183 test-exchange-obsmarkers-case-A3.t
184 184 test-exchange-obsmarkers-case-A4.t
185 185 test-exchange-obsmarkers-case-A5.t
186 186 test-exchange-obsmarkers-case-A6.t
187 187 test-exchange-obsmarkers-case-A7.t
188 188 test-exchange-obsmarkers-case-B1.t
189 189 test-exchange-obsmarkers-case-B2.t
190 190 test-exchange-obsmarkers-case-B3.t
191 191 test-exchange-obsmarkers-case-B4.t
192 192 test-exchange-obsmarkers-case-B5.t
193 193 test-exchange-obsmarkers-case-B6.t
194 194 test-exchange-obsmarkers-case-B7.t
195 195 test-exchange-obsmarkers-case-C1.t
196 196 test-exchange-obsmarkers-case-C2.t
197 197 test-exchange-obsmarkers-case-C3.t
198 198 test-exchange-obsmarkers-case-C4.t
199 199 test-exchange-obsmarkers-case-D1.t
200 200 test-exchange-obsmarkers-case-D2.t
201 201 test-exchange-obsmarkers-case-D3.t
202 202 test-exchange-obsmarkers-case-D4.t
203 203 test-execute-bit.t
204 204 test-export.t
205 205 test-extdata.t
206 206 test-extdiff.t
207 207 test-extension-timing.t
208 208 test-extensions-afterloaded.t
209 209 test-extensions-wrapfunction.py
210 210 test-extra-filelog-entry.t
211 211 test-fastannotate-corrupt.t
212 212 test-fastannotate-diffopts.t
213 213 test-fastannotate-hg.t
214 214 test-fastannotate-perfhack.t
215 215 test-fastannotate-protocol.t
216 216 test-fastannotate-renames.t
217 217 test-fastannotate-revmap.py
218 218 test-fastannotate.t
219 219 test-fetch.t
220 220 test-filebranch.t
221 221 test-filecache.py
222 222 test-filelog.py
223 223 test-fileset-generated.t
224 224 test-fileset.t
225 225 test-fix-topology.t
226 226 test-fix.t
227 227 test-flags.t
228 228 test-fncache.t
229 229 test-gendoc-da.t
230 230 test-gendoc-de.t
231 231 test-gendoc-el.t
232 232 test-gendoc-fr.t
233 233 test-gendoc-it.t
234 234 test-gendoc-ja.t
235 235 test-gendoc-pt_BR.t
236 236 test-gendoc-ro.t
237 237 test-gendoc-ru.t
238 238 test-gendoc-sv.t
239 239 test-gendoc-zh_CN.t
240 240 test-gendoc-zh_TW.t
241 241 test-gendoc.t
242 242 test-generaldelta.t
243 243 test-getbundle.t
244 244 test-git-export.t
245 245 test-githelp.t
246 246 test-globalopts.t
247 247 test-glog-beautifygraph.t
248 248 test-glog-topological.t
249 249 test-glog.t
250 250 test-gpg.t
251 251 test-graft.t
252 252 test-grep.t
253 253 test-hardlinks.t
254 254 test-help-hide.t
255 255 test-help.t
256 256 test-hg-parseurl.py
257 257 test-hghave.t
258 258 test-hgignore.t
259 259 test-hgk.t
260 260 test-hgrc.t
261 261 test-hgweb-annotate-whitespace.t
262 262 test-hgweb-bundle.t
263 263 test-hgweb-commands.t
264 264 test-hgweb-csp.t
265 265 test-hgweb-descend-empties.t
266 266 test-hgweb-diffs.t
267 267 test-hgweb-empty.t
268 268 test-hgweb-filelog.t
269 269 test-hgweb-no-path-info.t
270 270 test-hgweb-no-request-uri.t
271 271 test-hgweb-non-interactive.t
272 272 test-hgweb-raw.t
273 273 test-hgweb-removed.t
274 274 test-hgweb-symrev.t
275 275 test-hgweb.t
276 276 test-hgwebdir-paths.py
277 277 test-hgwebdir.t
278 278 test-hgwebdirsym.t
279 279 test-histedit-arguments.t
280 280 test-histedit-base.t
281 281 test-histedit-bookmark-motion.t
282 282 test-histedit-commute.t
283 283 test-histedit-drop.t
284 284 test-histedit-edit.t
285 285 test-histedit-fold-non-commute.t
286 286 test-histedit-fold.t
287 287 test-histedit-no-backup.t
288 288 test-histedit-no-change.t
289 289 test-histedit-non-commute-abort.t
290 290 test-histedit-non-commute.t
291 291 test-histedit-obsolete.t
292 292 test-histedit-outgoing.t
293 293 test-histedit-templates.t
294 294 test-http-api-httpv2.t
295 295 test-http-api.t
296 296 test-http-branchmap.t
297 297 test-http-bundle1.t
298 298 test-http-clone-r.t
299 299 test-http-permissions.t
300 300 test-http-protocol.t
301 301 test-http.t
302 302 test-hybridencode.py
303 303 test-i18n.t
304 304 test-identify.t
305 305 test-impexp-branch.t
306 306 test-import-bypass.t
307 307 test-import-context.t
308 308 test-import-eol.t
309 309 test-import-merge.t
310 310 test-import-unknown.t
311 311 test-import.t
312 312 test-imports-checker.t
313 313 test-incoming-outgoing.t
314 314 test-infinitepush-bundlestore.t
315 315 test-infinitepush-ci.t
316 316 test-infinitepush.t
317 317 test-inherit-mode.t
318 318 test-init.t
319 319 test-install.t
320 320 test-issue1089.t
321 321 test-issue1102.t
322 322 test-issue1175.t
323 323 test-issue1306.t
324 324 test-issue1438.t
325 325 test-issue1502.t
326 326 test-issue1802.t
327 327 test-issue1877.t
328 328 test-issue1993.t
329 329 test-issue2137.t
330 330 test-issue3084.t
331 331 test-issue4074.t
332 332 test-issue522.t
333 333 test-issue586.t
334 334 test-issue5979.t
335 335 test-issue612.t
336 336 test-issue619.t
337 337 test-issue660.t
338 338 test-issue672.t
339 339 test-issue842.t
340 340 test-journal-exists.t
341 341 test-journal-share.t
342 342 test-journal.t
343 343 test-keyword.t
344 344 test-known.t
345 345 test-largefiles-cache.t
346 346 test-largefiles-misc.t
347 347 test-largefiles-small-disk.t
348 348 test-largefiles-update.t
349 349 test-largefiles-wireproto.t
350 350 test-largefiles.t
351 351 test-lfconvert.t
352 352 test-lfs-bundle.t
353 353 test-lfs-largefiles.t
354 354 test-lfs-pointer.py
355 test-lfs-test-server.t
355 356 test-lfs.t
356 357 test-linelog.py
357 358 test-linerange.py
358 359 test-locate.t
359 360 test-lock-badness.t
360 361 test-log-exthook.t
361 362 test-log-linerange.t
362 363 test-log.t
363 364 test-logexchange.t
364 365 test-logtoprocess.t
365 366 test-lrucachedict.py
366 367 test-mactext.t
367 368 test-mailmap.t
368 369 test-manifest-merging.t
369 370 test-manifest.py
370 371 test-manifest.t
371 372 test-match.py
372 373 test-mdiff.py
373 374 test-merge-changedelete.t
374 375 test-merge-closedheads.t
375 376 test-merge-commit.t
376 377 test-merge-criss-cross.t
377 378 test-merge-default.t
378 379 test-merge-force.t
379 380 test-merge-halt.t
380 381 test-merge-internal-tools-pattern.t
381 382 test-merge-local.t
382 383 test-merge-no-file-change.t
383 384 test-merge-remove.t
384 385 test-merge-revert.t
385 386 test-merge-revert2.t
386 387 test-merge-subrepos.t
387 388 test-merge-symlinks.t
388 389 test-merge-tools.t
389 390 test-merge-types.t
390 391 test-merge1.t
391 392 test-merge10.t
392 393 test-merge2.t
393 394 test-merge4.t
394 395 test-merge5.t
395 396 test-merge6.t
396 397 test-merge7.t
397 398 test-merge8.t
398 399 test-merge9.t
399 400 test-minifileset.py
400 401 test-minirst.py
401 402 test-missing-capability.t
402 403 test-mq-eol.t
403 404 test-mq-git.t
404 405 test-mq-guards.t
405 406 test-mq-header-date.t
406 407 test-mq-header-from.t
407 408 test-mq-merge.t
408 409 test-mq-missingfiles.t
409 410 test-mq-pull-from-bundle.t
410 411 test-mq-qclone-http.t
411 412 test-mq-qdelete.t
412 413 test-mq-qdiff.t
413 414 test-mq-qfold.t
414 415 test-mq-qgoto.t
415 416 test-mq-qimport-fail-cleanup.t
416 417 test-mq-qimport.t
417 418 test-mq-qnew.t
418 419 test-mq-qpush-exact.t
419 420 test-mq-qpush-fail.t
420 421 test-mq-qqueue.t
421 422 test-mq-qrefresh-interactive.t
422 423 test-mq-qrefresh-replace-log-message.t
423 424 test-mq-qrefresh.t
424 425 test-mq-qrename.t
425 426 test-mq-qsave.t
426 427 test-mq-safety.t
427 428 test-mq-subrepo.t
428 429 test-mq-symlinks.t
429 430 test-mq.t
430 431 test-mv-cp-st-diff.t
431 432 test-narrow-acl.t
432 433 test-narrow-archive.t
433 434 test-narrow-clone-no-ellipsis.t
434 435 test-narrow-clone-non-narrow-server.t
435 436 test-narrow-clone-nonlinear.t
436 437 test-narrow-clone-stream.t
437 438 test-narrow-clone.t
438 439 test-narrow-commit.t
439 440 test-narrow-copies.t
440 441 test-narrow-debugcommands.t
441 442 test-narrow-debugrebuilddirstate.t
442 443 test-narrow-exchange-merges.t
443 444 test-narrow-exchange.t
444 445 test-narrow-expanddirstate.t
445 446 test-narrow-merge.t
446 447 test-narrow-patch.t
447 448 test-narrow-patterns.t
448 449 test-narrow-pull.t
449 450 test-narrow-rebase.t
450 451 test-narrow-shallow-merges.t
451 452 test-narrow-shallow.t
452 453 test-narrow-share.t
453 454 test-narrow-sparse.t
454 455 test-narrow-strip.t
455 456 test-narrow-trackedcmd.t
456 457 test-narrow-update.t
457 458 test-narrow-widen-no-ellipsis.t
458 459 test-narrow-widen.t
459 460 test-narrow.t
460 461 test-nested-repo.t
461 462 test-newbranch.t
462 463 test-newcgi.t
463 464 test-newercgi.t
464 465 test-nointerrupt.t
465 466 test-notify-changegroup.t
466 467 test-obshistory.t
467 468 test-obsmarker-template.t
468 469 test-obsmarkers-effectflag.t
469 470 test-obsolete-bounds-checking.t
470 471 test-obsolete-bundle-strip.t
471 472 test-obsolete-changeset-exchange.t
472 473 test-obsolete-checkheads.t
473 474 test-obsolete-distributed.t
474 475 test-obsolete-divergent.t
475 476 test-obsolete-tag-cache.t
476 477 test-obsolete.t
477 478 test-oldcgi.t
478 479 test-origbackup-conflict.t
479 480 test-pager-legacy.t
480 481 test-pager.t
481 482 test-parents.t
482 483 test-parse-date.t
483 484 test-parseindex.t
484 485 test-parseindex2.py
485 486 test-patch-offset.t
486 487 test-patch.t
487 488 test-patchbomb-bookmark.t
488 489 test-patchbomb-tls.t
489 490 test-patchbomb.t
490 491 test-pathconflicts-basic.t
491 492 test-pathconflicts-merge.t
492 493 test-pathconflicts-update.t
493 494 test-pathencode.py
494 495 test-pending.t
495 496 test-permissions.t
496 497 test-phases-exchange.t
497 498 test-phases.t
498 499 test-profile.t
499 500 test-progress.t
500 501 test-propertycache.py
501 502 test-pull-branch.t
502 503 test-pull-http.t
503 504 test-pull-permission.t
504 505 test-pull-pull-corruption.t
505 506 test-pull-r.t
506 507 test-pull-update.t
507 508 test-pull.t
508 509 test-purge.t
509 510 test-push-cgi.t
510 511 test-push-checkheads-partial-C1.t
511 512 test-push-checkheads-partial-C2.t
512 513 test-push-checkheads-partial-C3.t
513 514 test-push-checkheads-partial-C4.t
514 515 test-push-checkheads-pruned-B1.t
515 516 test-push-checkheads-pruned-B2.t
516 517 test-push-checkheads-pruned-B3.t
517 518 test-push-checkheads-pruned-B4.t
518 519 test-push-checkheads-pruned-B5.t
519 520 test-push-checkheads-pruned-B6.t
520 521 test-push-checkheads-pruned-B7.t
521 522 test-push-checkheads-pruned-B8.t
522 523 test-push-checkheads-superceed-A1.t
523 524 test-push-checkheads-superceed-A2.t
524 525 test-push-checkheads-superceed-A3.t
525 526 test-push-checkheads-superceed-A4.t
526 527 test-push-checkheads-superceed-A5.t
527 528 test-push-checkheads-superceed-A6.t
528 529 test-push-checkheads-superceed-A7.t
529 530 test-push-checkheads-superceed-A8.t
530 531 test-push-checkheads-unpushed-D1.t
531 532 test-push-checkheads-unpushed-D2.t
532 533 test-push-checkheads-unpushed-D3.t
533 534 test-push-checkheads-unpushed-D4.t
534 535 test-push-checkheads-unpushed-D5.t
535 536 test-push-checkheads-unpushed-D6.t
536 537 test-push-checkheads-unpushed-D7.t
537 538 test-push-http.t
538 539 test-push-race.t
539 540 test-push-warn.t
540 541 test-push.t
541 542 test-pushvars.t
542 543 test-qrecord.t
543 544 test-rebase-abort.t
544 545 test-rebase-backup.t
545 546 test-rebase-base-flag.t
546 547 test-rebase-bookmarks.t
547 548 test-rebase-brute-force.t
548 549 test-rebase-cache.t
549 550 test-rebase-check-restore.t
550 551 test-rebase-collapse.t
551 552 test-rebase-conflicts.t
552 553 test-rebase-dest.t
553 554 test-rebase-detach.t
554 555 test-rebase-emptycommit.t
555 556 test-rebase-inmemory.t
556 557 test-rebase-interruptions.t
557 558 test-rebase-issue-noparam-single-rev.t
558 559 test-rebase-legacy.t
559 560 test-rebase-mq-skip.t
560 561 test-rebase-mq.t
561 562 test-rebase-named-branches.t
562 563 test-rebase-newancestor.t
563 564 test-rebase-obsolete.t
564 565 test-rebase-parameters.t
565 566 test-rebase-partial.t
566 567 test-rebase-pull.t
567 568 test-rebase-rename.t
568 569 test-rebase-scenario-global.t
569 570 test-rebase-templates.t
570 571 test-rebase-transaction.t
571 572 test-rebuildstate.t
572 573 test-record.t
573 574 test-releasenotes-formatting.t
574 575 test-releasenotes-merging.t
575 576 test-releasenotes-parsing.t
576 577 test-relink.t
577 578 test-remotefilelog-bad-configs.t
578 579 test-remotefilelog-bgprefetch.t
579 580 test-remotefilelog-blame.t
580 581 test-remotefilelog-bundle2.t
581 582 test-remotefilelog-bundles.t
582 583 test-remotefilelog-cacheprocess.t
583 584 test-remotefilelog-clone-tree.t
584 585 test-remotefilelog-clone.t
585 586 test-remotefilelog-gcrepack.t
586 587 test-remotefilelog-histpack.py
587 588 test-remotefilelog-http.t
588 589 test-remotefilelog-keepset.t
589 590 test-remotefilelog-local.t
590 591 test-remotefilelog-log.t
591 592 test-remotefilelog-partial-shallow.t
592 593 test-remotefilelog-permissions.t
593 594 test-remotefilelog-permisssions.t
594 595 test-remotefilelog-prefetch.t
595 596 test-remotefilelog-pull-noshallow.t
596 597 test-remotefilelog-share.t
597 598 test-remotefilelog-sparse.t
598 599 test-remotefilelog-tags.t
599 600 test-remotefilelog-wireproto.t
600 601 test-remove.t
601 602 test-removeemptydirs.t
602 603 test-rename-after-merge.t
603 604 test-rename-dir-merge.t
604 605 test-rename-merge1.t
605 606 test-rename-merge2.t
606 607 test-rename.t
607 608 test-repair-strip.t
608 609 test-repo-compengines.t
609 610 test-requires.t
610 611 test-resolve.t
611 612 test-revert-flags.t
612 613 test-revert-interactive.t
613 614 test-revert-unknown.t
614 615 test-revert.t
615 616 test-revisions.t
616 617 test-revlog-ancestry.py
617 618 test-revlog-group-emptyiter.t
618 619 test-revlog-mmapindex.t
619 620 test-revlog-packentry.t
620 621 test-revlog-raw.py
621 622 test-revlog-v2.t
622 623 test-revlog.t
623 624 test-revset-dirstate-parents.t
624 625 test-revset-legacy-lookup.t
625 626 test-revset-outgoing.t
626 627 test-rollback.t
627 628 test-run-tests.py
628 629 test-run-tests.t
629 630 test-rust-ancestor.py
630 631 test-schemes.t
631 632 test-serve.t
632 633 test-setdiscovery.t
633 634 test-share.t
634 635 test-shelve.t
635 636 test-shelve2.t
636 637 test-show-stack.t
637 638 test-show-work.t
638 639 test-show.t
639 640 test-simple-update.t
640 641 test-simplekeyvaluefile.py
641 642 test-simplemerge.py
642 643 test-single-head.t
643 644 test-sparse-clear.t
644 645 test-sparse-clone.t
645 646 test-sparse-import.t
646 647 test-sparse-merges.t
647 648 test-sparse-profiles.t
648 649 test-sparse-requirement.t
649 650 test-sparse-verbose-json.t
650 651 test-sparse.t
651 652 test-split.t
652 653 test-ssh-bundle1.t
653 654 test-ssh-clone-r.t
654 655 test-ssh-proto-unbundle.t
655 656 test-ssh-proto.t
656 657 test-ssh-repoerror.t
657 658 test-ssh.t
658 659 test-sshserver.py
659 660 test-stack.t
660 661 test-static-http.t
661 662 test-status-color.t
662 663 test-status-inprocess.py
663 664 test-status-rev.t
664 665 test-status-terse.t
665 666 test-status.t
666 667 test-storage.py
667 668 test-stream-bundle-v2.t
668 669 test-strict.t
669 670 test-strip-cross.t
670 671 test-strip.t
671 672 test-subrepo-deep-nested-change.t
672 673 test-subrepo-missing.t
673 674 test-subrepo-paths.t
674 675 test-subrepo-recursion.t
675 676 test-subrepo-relative-path.t
676 677 test-subrepo.t
677 678 test-symlink-os-yes-fs-no.py
678 679 test-symlink-placeholder.t
679 680 test-symlinks.t
680 681 test-tag.t
681 682 test-tags.t
682 683 test-template-basic.t
683 684 test-template-functions.t
684 685 test-template-keywords.t
685 686 test-template-map.t
686 687 test-tools.t
687 688 test-transplant.t
688 689 test-treediscovery-legacy.t
689 690 test-treediscovery.t
690 691 test-treemanifest.t
691 692 test-ui-color.py
692 693 test-ui-config.py
693 694 test-ui-verbosity.py
694 695 test-unamend.t
695 696 test-unbundlehash.t
696 697 test-uncommit.t
697 698 test-unified-test.t
698 699 test-unionrepo.t
699 700 test-unrelated-pull.t
700 701 test-up-local-change.t
701 702 test-update-atomic.t
702 703 test-update-branches.t
703 704 test-update-dest.t
704 705 test-update-issue1456.t
705 706 test-update-names.t
706 707 test-update-reverse.t
707 708 test-upgrade-repo.t
708 709 test-url-download.t
709 710 test-url-rev.t
710 711 test-url.py
711 712 test-username-newline.t
712 713 test-util.py
713 714 test-verify.t
714 715 test-walk.t
715 716 test-walkrepo.py
716 717 test-websub.t
717 718 test-win32text.t
718 719 test-wireproto-caching.t
719 720 test-wireproto-clientreactor.py
720 721 test-wireproto-command-branchmap.t
721 722 test-wireproto-command-capabilities.t
722 723 test-wireproto-command-changesetdata.t
723 724 test-wireproto-command-filedata.t
724 725 test-wireproto-command-filesdata.t
725 726 test-wireproto-command-heads.t
726 727 test-wireproto-command-known.t
727 728 test-wireproto-command-listkeys.t
728 729 test-wireproto-command-lookup.t
729 730 test-wireproto-command-manifestdata.t
730 731 test-wireproto-command-pushkey.t
731 732 test-wireproto-command-rawstorefiledata.t
732 733 test-wireproto-content-redirects.t
733 734 test-wireproto-exchangev2.t
734 735 test-wireproto-framing.py
735 736 test-wireproto-serverreactor.py
736 737 test-wireproto.py
737 738 test-wireproto.t
738 739 test-wsgirequest.py
739 740 test-xdg.t
@@ -1,658 +1,659
1 1 # blobstore.py - local and remote (speaking Git-LFS protocol) blob storages
2 2 #
3 3 # Copyright 2017 Facebook, Inc.
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 contextlib
11 11 import errno
12 12 import hashlib
13 13 import json
14 14 import os
15 15 import re
16 16 import socket
17 17
18 18 from mercurial.i18n import _
19 19
20 20 from mercurial import (
21 21 encoding,
22 22 error,
23 23 node,
24 24 pathutil,
25 25 pycompat,
26 26 url as urlmod,
27 27 util,
28 28 vfs as vfsmod,
29 29 worker,
30 30 )
31 31
32 32 from mercurial.utils import (
33 33 stringutil,
34 34 )
35 35
36 36 from ..largefiles import lfutil
37 37
38 38 # 64 bytes for SHA256
39 39 _lfsre = re.compile(br'\A[a-f0-9]{64}\Z')
40 40
41 41 class lfsvfs(vfsmod.vfs):
42 42 def join(self, path):
43 43 """split the path at first two characters, like: XX/XXXXX..."""
44 44 if not _lfsre.match(path):
45 45 raise error.ProgrammingError(b'unexpected lfs path: %s' % path)
46 46 return super(lfsvfs, self).join(path[0:2], path[2:])
47 47
48 48 def walk(self, path=None, onerror=None):
49 49 """Yield (dirpath, [], oids) tuple for blobs under path
50 50
51 51 Oids only exist in the root of this vfs, so dirpath is always ''.
52 52 """
53 53 root = os.path.normpath(self.base)
54 54 # when dirpath == root, dirpath[prefixlen:] becomes empty
55 55 # because len(dirpath) < prefixlen.
56 56 prefixlen = len(pathutil.normasprefix(root))
57 57 oids = []
58 58
59 59 for dirpath, dirs, files in os.walk(self.reljoin(self.base, path
60 60 or b''),
61 61 onerror=onerror):
62 62 dirpath = dirpath[prefixlen:]
63 63
64 64 # Silently skip unexpected files and directories
65 65 if len(dirpath) == 2:
66 66 oids.extend([dirpath + f for f in files
67 67 if _lfsre.match(dirpath + f)])
68 68
69 69 yield ('', [], oids)
70 70
71 71 class nullvfs(lfsvfs):
72 72 def __init__(self):
73 73 pass
74 74
75 75 def exists(self, oid):
76 76 return False
77 77
78 78 def read(self, oid):
79 79 # store.read() calls into here if the blob doesn't exist in its
80 80 # self.vfs. Raise the same error as a normal vfs when asked to read a
81 81 # file that doesn't exist. The only difference is the full file path
82 82 # isn't available in the error.
83 83 raise IOError(errno.ENOENT,
84 84 pycompat.sysstr(b'%s: No such file or directory' % oid))
85 85
86 86 def walk(self, path=None, onerror=None):
87 87 return (b'', [], [])
88 88
89 89 def write(self, oid, data):
90 90 pass
91 91
92 92 class filewithprogress(object):
93 93 """a file-like object that supports __len__ and read.
94 94
95 95 Useful to provide progress information for how many bytes are read.
96 96 """
97 97
98 98 def __init__(self, fp, callback):
99 99 self._fp = fp
100 100 self._callback = callback # func(readsize)
101 101 fp.seek(0, os.SEEK_END)
102 102 self._len = fp.tell()
103 103 fp.seek(0)
104 104
105 105 def __len__(self):
106 106 return self._len
107 107
108 108 def read(self, size):
109 109 if self._fp is None:
110 110 return b''
111 111 data = self._fp.read(size)
112 112 if data:
113 113 if self._callback:
114 114 self._callback(len(data))
115 115 else:
116 116 self._fp.close()
117 117 self._fp = None
118 118 return data
119 119
120 120 class local(object):
121 121 """Local blobstore for large file contents.
122 122
123 123 This blobstore is used both as a cache and as a staging area for large blobs
124 124 to be uploaded to the remote blobstore.
125 125 """
126 126
127 127 def __init__(self, repo):
128 128 fullpath = repo.svfs.join(b'lfs/objects')
129 129 self.vfs = lfsvfs(fullpath)
130 130
131 131 if repo.ui.configbool(b'experimental', b'lfs.disableusercache'):
132 132 self.cachevfs = nullvfs()
133 133 else:
134 134 usercache = lfutil._usercachedir(repo.ui, b'lfs')
135 135 self.cachevfs = lfsvfs(usercache)
136 136 self.ui = repo.ui
137 137
138 138 def open(self, oid):
139 139 """Open a read-only file descriptor to the named blob, in either the
140 140 usercache or the local store."""
141 141 # The usercache is the most likely place to hold the file. Commit will
142 142 # write to both it and the local store, as will anything that downloads
143 143 # the blobs. However, things like clone without an update won't
144 144 # populate the local store. For an init + push of a local clone,
145 145 # the usercache is the only place it _could_ be. If not present, the
146 146 # missing file msg here will indicate the local repo, not the usercache.
147 147 if self.cachevfs.exists(oid):
148 148 return self.cachevfs(oid, b'rb')
149 149
150 150 return self.vfs(oid, b'rb')
151 151
152 152 def download(self, oid, src):
153 153 """Read the blob from the remote source in chunks, verify the content,
154 154 and write to this local blobstore."""
155 155 sha256 = hashlib.sha256()
156 156
157 157 with self.vfs(oid, b'wb', atomictemp=True) as fp:
158 158 for chunk in util.filechunkiter(src, size=1048576):
159 159 fp.write(chunk)
160 160 sha256.update(chunk)
161 161
162 162 realoid = node.hex(sha256.digest())
163 163 if realoid != oid:
164 164 raise LfsCorruptionError(_(b'corrupt remote lfs object: %s')
165 165 % oid)
166 166
167 167 self._linktousercache(oid)
168 168
169 169 def write(self, oid, data):
170 170 """Write blob to local blobstore.
171 171
172 172 This should only be called from the filelog during a commit or similar.
173 173 As such, there is no need to verify the data. Imports from a remote
174 174 store must use ``download()`` instead."""
175 175 with self.vfs(oid, b'wb', atomictemp=True) as fp:
176 176 fp.write(data)
177 177
178 178 self._linktousercache(oid)
179 179
180 180 def linkfromusercache(self, oid):
181 181 """Link blobs found in the user cache into this store.
182 182
183 183 The server module needs to do this when it lets the client know not to
184 184 upload the blob, to ensure it is always available in this store.
185 185 Normally this is done implicitly when the client reads or writes the
186 186 blob, but that doesn't happen when the server tells the client that it
187 187 already has the blob.
188 188 """
189 189 if (not isinstance(self.cachevfs, nullvfs)
190 190 and not self.vfs.exists(oid)):
191 191 self.ui.note(_(b'lfs: found %s in the usercache\n') % oid)
192 192 lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
193 193
194 194 def _linktousercache(self, oid):
195 195 # XXX: should we verify the content of the cache, and hardlink back to
196 196 # the local store on success, but truncate, write and link on failure?
197 197 if (not self.cachevfs.exists(oid)
198 198 and not isinstance(self.cachevfs, nullvfs)):
199 199 self.ui.note(_(b'lfs: adding %s to the usercache\n') % oid)
200 200 lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid))
201 201
202 202 def read(self, oid, verify=True):
203 203 """Read blob from local blobstore."""
204 204 if not self.vfs.exists(oid):
205 205 blob = self._read(self.cachevfs, oid, verify)
206 206
207 207 # Even if revlog will verify the content, it needs to be verified
208 208 # now before making the hardlink to avoid propagating corrupt blobs.
209 209 # Don't abort if corruption is detected, because `hg verify` will
210 210 # give more useful info about the corruption- simply don't add the
211 211 # hardlink.
212 212 if verify or node.hex(hashlib.sha256(blob).digest()) == oid:
213 213 self.ui.note(_(b'lfs: found %s in the usercache\n') % oid)
214 214 lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
215 215 else:
216 216 self.ui.note(_(b'lfs: found %s in the local lfs store\n') % oid)
217 217 blob = self._read(self.vfs, oid, verify)
218 218 return blob
219 219
220 220 def _read(self, vfs, oid, verify):
221 221 """Read blob (after verifying) from the given store"""
222 222 blob = vfs.read(oid)
223 223 if verify:
224 224 _verify(oid, blob)
225 225 return blob
226 226
227 227 def verify(self, oid):
228 228 """Indicate whether or not the hash of the underlying file matches its
229 229 name."""
230 230 sha256 = hashlib.sha256()
231 231
232 232 with self.open(oid) as fp:
233 233 for chunk in util.filechunkiter(fp, size=1048576):
234 234 sha256.update(chunk)
235 235
236 236 return oid == node.hex(sha256.digest())
237 237
238 238 def has(self, oid):
239 239 """Returns True if the local blobstore contains the requested blob,
240 240 False otherwise."""
241 241 return self.cachevfs.exists(oid) or self.vfs.exists(oid)
242 242
243 243 def _urlerrorreason(urlerror):
244 244 '''Create a friendly message for the given URLError to be used in an
245 245 LfsRemoteError message.
246 246 '''
247 247 inst = urlerror
248 248
249 249 if isinstance(urlerror.reason, Exception):
250 250 inst = urlerror.reason
251 251
252 252 if util.safehasattr(inst, 'reason'):
253 253 try: # usually it is in the form (errno, strerror)
254 254 reason = inst.reason.args[1]
255 255 except (AttributeError, IndexError):
256 256 # it might be anything, for example a string
257 257 reason = inst.reason
258 258 if isinstance(reason, pycompat.unicode):
259 259 # SSLError of Python 2.7.9 contains a unicode
260 260 reason = encoding.unitolocal(reason)
261 261 return reason
262 262 elif getattr(inst, "strerror", None):
263 263 return encoding.strtolocal(inst.strerror)
264 264 else:
265 265 return stringutil.forcebytestr(urlerror)
266 266
267 267 class _gitlfsremote(object):
268 268
269 269 def __init__(self, repo, url):
270 270 ui = repo.ui
271 271 self.ui = ui
272 272 baseurl, authinfo = url.authinfo()
273 273 self.baseurl = baseurl.rstrip(b'/')
274 274 useragent = repo.ui.config(b'experimental', b'lfs.user-agent')
275 275 if not useragent:
276 276 useragent = b'git-lfs/2.3.4 (Mercurial %s)' % util.version()
277 277 self.urlopener = urlmod.opener(ui, authinfo, useragent)
278 278 self.retry = ui.configint(b'lfs', b'retry')
279 279
280 280 def writebatch(self, pointers, fromstore):
281 281 """Batch upload from local to remote blobstore."""
282 282 self._batch(_deduplicate(pointers), fromstore, b'upload')
283 283
284 284 def readbatch(self, pointers, tostore):
285 285 """Batch download from remote to local blostore."""
286 286 self._batch(_deduplicate(pointers), tostore, b'download')
287 287
288 288 def _batchrequest(self, pointers, action):
289 289 """Get metadata about objects pointed by pointers for given action
290 290
291 291 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]}
292 292 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
293 293 """
294 294 objects = [{r'oid': pycompat.strurl(p.oid()),
295 295 r'size': p.size()} for p in pointers]
296 296 requestdata = pycompat.bytesurl(json.dumps({
297 297 r'objects': objects,
298 298 r'operation': pycompat.strurl(action),
299 299 }))
300 300 url = b'%s/objects/batch' % self.baseurl
301 301 batchreq = util.urlreq.request(pycompat.strurl(url), data=requestdata)
302 302 batchreq.add_header(r'Accept', r'application/vnd.git-lfs+json')
303 303 batchreq.add_header(r'Content-Type', r'application/vnd.git-lfs+json')
304 304 try:
305 305 with contextlib.closing(self.urlopener.open(batchreq)) as rsp:
306 306 rawjson = rsp.read()
307 307 except util.urlerr.httperror as ex:
308 308 hints = {
309 309 400: _(b'check that lfs serving is enabled on %s and "%s" is '
310 310 b'supported') % (self.baseurl, action),
311 311 404: _(b'the "lfs.url" config may be used to override %s')
312 312 % self.baseurl,
313 313 }
314 314 hint = hints.get(ex.code, _(b'api=%s, action=%s') % (url, action))
315 315 raise LfsRemoteError(
316 316 _(b'LFS HTTP error: %s') % stringutil.forcebytestr(ex),
317 317 hint=hint)
318 318 except util.urlerr.urlerror as ex:
319 319 hint = (_(b'the "lfs.url" config may be used to override %s')
320 320 % self.baseurl)
321 321 raise LfsRemoteError(_(b'LFS error: %s') % _urlerrorreason(ex),
322 322 hint=hint)
323 323 try:
324 324 response = json.loads(rawjson)
325 325 except ValueError:
326 326 raise LfsRemoteError(_(b'LFS server returns invalid JSON: %s')
327 327 % rawjson.encode("utf-8"))
328 328
329 329 if self.ui.debugflag:
330 330 self.ui.debug(b'Status: %d\n' % rsp.status)
331 331 # lfs-test-server and hg serve return headers in different order
332 332 headers = pycompat.bytestr(rsp.info()).strip()
333 333 self.ui.debug(b'%s\n'
334 334 % b'\n'.join(sorted(headers.splitlines())))
335 335
336 336 if r'objects' in response:
337 337 response[r'objects'] = sorted(response[r'objects'],
338 338 key=lambda p: p[r'oid'])
339 339 self.ui.debug(b'%s\n'
340 340 % pycompat.bytesurl(
341 341 json.dumps(response, indent=2,
342 342 separators=(r'', r': '),
343 343 sort_keys=True)))
344 344
345 345 def encodestr(x):
346 346 if isinstance(x, pycompat.unicode):
347 347 return x.encode(u'utf-8')
348 348 return x
349 349
350 350 return pycompat.rapply(encodestr, response)
351 351
352 352 def _checkforservererror(self, pointers, responses, action):
353 353 """Scans errors from objects
354 354
355 355 Raises LfsRemoteError if any objects have an error"""
356 356 for response in responses:
357 357 # The server should return 404 when objects cannot be found. Some
358 358 # server implementation (ex. lfs-test-server) does not set "error"
359 359 # but just removes "download" from "actions". Treat that case
360 360 # as the same as 404 error.
361 361 if b'error' not in response:
362 362 if (action == b'download'
363 363 and action not in response.get(b'actions', [])):
364 364 code = 404
365 365 else:
366 366 continue
367 367 else:
368 368 # An error dict without a code doesn't make much sense, so
369 369 # treat as a server error.
370 370 code = response.get(b'error').get(b'code', 500)
371 371
372 372 ptrmap = {p.oid(): p for p in pointers}
373 373 p = ptrmap.get(response[b'oid'], None)
374 374 if p:
375 375 filename = getattr(p, 'filename', b'unknown')
376 376 errors = {
377 377 404: b'The object does not exist',
378 378 410: b'The object was removed by the owner',
379 379 422: b'Validation error',
380 380 500: b'Internal server error',
381 381 }
382 382 msg = errors.get(code, b'status code %d' % code)
383 383 raise LfsRemoteError(_(b'LFS server error for "%s": %s')
384 384 % (filename, msg))
385 385 else:
386 386 raise LfsRemoteError(
387 387 _(b'LFS server error. Unsolicited response for oid %s')
388 388 % response[b'oid'])
389 389
390 390 def _extractobjects(self, response, pointers, action):
391 391 """extract objects from response of the batch API
392 392
393 393 response: parsed JSON object returned by batch API
394 394 return response['objects'] filtered by action
395 395 raise if any object has an error
396 396 """
397 397 # Scan errors from objects - fail early
398 398 objects = response.get(b'objects', [])
399 399 self._checkforservererror(pointers, objects, action)
400 400
401 401 # Filter objects with given action. Practically, this skips uploading
402 402 # objects which exist in the server.
403 403 filteredobjects = [o for o in objects
404 404 if action in o.get(b'actions', [])]
405 405
406 406 return filteredobjects
407 407
408 408 def _basictransfer(self, obj, action, localstore):
409 409 """Download or upload a single object using basic transfer protocol
410 410
411 411 obj: dict, an object description returned by batch API
412 412 action: string, one of ['upload', 'download']
413 413 localstore: blobstore.local
414 414
415 415 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/\
416 416 basic-transfers.md
417 417 """
418 418 oid = obj[b'oid']
419 419 href = obj[b'actions'][action].get(b'href')
420 420 headers = obj[b'actions'][action].get(b'header', {}).items()
421 421
422 422 request = util.urlreq.request(pycompat.strurl(href))
423 423 if action == b'upload':
424 424 # If uploading blobs, read data from local blobstore.
425 425 if not localstore.verify(oid):
426 426 raise error.Abort(_(b'detected corrupt lfs object: %s') % oid,
427 427 hint=_(b'run hg verify'))
428 428 request.data = filewithprogress(localstore.open(oid), None)
429 429 request.get_method = lambda: r'PUT'
430 430 request.add_header(r'Content-Type', r'application/octet-stream')
431 request.add_header(r'Content-Length', len(request.data))
431 432
432 433 for k, v in headers:
433 434 request.add_header(pycompat.strurl(k), pycompat.strurl(v))
434 435
435 436 response = b''
436 437 try:
437 438 with contextlib.closing(self.urlopener.open(request)) as req:
438 439 ui = self.ui # Shorten debug lines
439 440 if self.ui.debugflag:
440 441 ui.debug(b'Status: %d\n' % req.status)
441 442 # lfs-test-server and hg serve return headers in different
442 443 # order
443 444 headers = pycompat.bytestr(req.info()).strip()
444 445 ui.debug(b'%s\n'
445 446 % b'\n'.join(sorted(headers.splitlines())))
446 447
447 448 if action == b'download':
448 449 # If downloading blobs, store downloaded data to local
449 450 # blobstore
450 451 localstore.download(oid, req)
451 452 else:
452 453 while True:
453 454 data = req.read(1048576)
454 455 if not data:
455 456 break
456 457 response += data
457 458 if response:
458 459 ui.debug(b'lfs %s response: %s' % (action, response))
459 460 except util.urlerr.httperror as ex:
460 461 if self.ui.debugflag:
461 462 self.ui.debug(b'%s: %s\n' % (oid, ex.read())) # XXX: also bytes?
462 463 raise LfsRemoteError(_(b'LFS HTTP error: %s (oid=%s, action=%s)')
463 464 % (stringutil.forcebytestr(ex), oid, action))
464 465 except util.urlerr.urlerror as ex:
465 466 hint = (_(b'attempted connection to %s')
466 467 % pycompat.bytesurl(util.urllibcompat.getfullurl(request)))
467 468 raise LfsRemoteError(_(b'LFS error: %s') % _urlerrorreason(ex),
468 469 hint=hint)
469 470
470 471 def _batch(self, pointers, localstore, action):
471 472 if action not in [b'upload', b'download']:
472 473 raise error.ProgrammingError(b'invalid Git-LFS action: %s' % action)
473 474
474 475 response = self._batchrequest(pointers, action)
475 476 objects = self._extractobjects(response, pointers, action)
476 477 total = sum(x.get(b'size', 0) for x in objects)
477 478 sizes = {}
478 479 for obj in objects:
479 480 sizes[obj.get(b'oid')] = obj.get(b'size', 0)
480 481 topic = {b'upload': _(b'lfs uploading'),
481 482 b'download': _(b'lfs downloading')}[action]
482 483 if len(objects) > 1:
483 484 self.ui.note(_(b'lfs: need to transfer %d objects (%s)\n')
484 485 % (len(objects), util.bytecount(total)))
485 486
486 487 def transfer(chunk):
487 488 for obj in chunk:
488 489 objsize = obj.get(b'size', 0)
489 490 if self.ui.verbose:
490 491 if action == b'download':
491 492 msg = _(b'lfs: downloading %s (%s)\n')
492 493 elif action == b'upload':
493 494 msg = _(b'lfs: uploading %s (%s)\n')
494 495 self.ui.note(msg % (obj.get(b'oid'),
495 496 util.bytecount(objsize)))
496 497 retry = self.retry
497 498 while True:
498 499 try:
499 500 self._basictransfer(obj, action, localstore)
500 501 yield 1, obj.get(b'oid')
501 502 break
502 503 except socket.error as ex:
503 504 if retry > 0:
504 505 self.ui.note(
505 506 _(b'lfs: failed: %r (remaining retry %d)\n')
506 507 % (stringutil.forcebytestr(ex), retry))
507 508 retry -= 1
508 509 continue
509 510 raise
510 511
511 512 # Until https multiplexing gets sorted out
512 513 if self.ui.configbool(b'experimental', b'lfs.worker-enable'):
513 514 oids = worker.worker(self.ui, 0.1, transfer, (),
514 515 sorted(objects, key=lambda o: o.get(b'oid')))
515 516 else:
516 517 oids = transfer(sorted(objects, key=lambda o: o.get(b'oid')))
517 518
518 519 with self.ui.makeprogress(topic, total=total) as progress:
519 520 progress.update(0)
520 521 processed = 0
521 522 blobs = 0
522 523 for _one, oid in oids:
523 524 processed += sizes[oid]
524 525 blobs += 1
525 526 progress.update(processed)
526 527 self.ui.note(_(b'lfs: processed: %s\n') % oid)
527 528
528 529 if blobs > 0:
529 530 if action == b'upload':
530 531 self.ui.status(_(b'lfs: uploaded %d files (%s)\n')
531 532 % (blobs, util.bytecount(processed)))
532 533 elif action == b'download':
533 534 self.ui.status(_(b'lfs: downloaded %d files (%s)\n')
534 535 % (blobs, util.bytecount(processed)))
535 536
536 537 def __del__(self):
537 538 # copied from mercurial/httppeer.py
538 539 urlopener = getattr(self, 'urlopener', None)
539 540 if urlopener:
540 541 for h in urlopener.handlers:
541 542 h.close()
542 543 getattr(h, "close_all", lambda : None)()
543 544
544 545 class _dummyremote(object):
545 546 """Dummy store storing blobs to temp directory."""
546 547
547 548 def __init__(self, repo, url):
548 549 fullpath = repo.vfs.join(b'lfs', url.path)
549 550 self.vfs = lfsvfs(fullpath)
550 551
551 552 def writebatch(self, pointers, fromstore):
552 553 for p in _deduplicate(pointers):
553 554 content = fromstore.read(p.oid(), verify=True)
554 555 with self.vfs(p.oid(), b'wb', atomictemp=True) as fp:
555 556 fp.write(content)
556 557
557 558 def readbatch(self, pointers, tostore):
558 559 for p in _deduplicate(pointers):
559 560 with self.vfs(p.oid(), b'rb') as fp:
560 561 tostore.download(p.oid(), fp)
561 562
562 563 class _nullremote(object):
563 564 """Null store storing blobs to /dev/null."""
564 565
565 566 def __init__(self, repo, url):
566 567 pass
567 568
568 569 def writebatch(self, pointers, fromstore):
569 570 pass
570 571
571 572 def readbatch(self, pointers, tostore):
572 573 pass
573 574
574 575 class _promptremote(object):
575 576 """Prompt user to set lfs.url when accessed."""
576 577
577 578 def __init__(self, repo, url):
578 579 pass
579 580
580 581 def writebatch(self, pointers, fromstore, ui=None):
581 582 self._prompt()
582 583
583 584 def readbatch(self, pointers, tostore, ui=None):
584 585 self._prompt()
585 586
586 587 def _prompt(self):
587 588 raise error.Abort(_(b'lfs.url needs to be configured'))
588 589
589 590 _storemap = {
590 591 b'https': _gitlfsremote,
591 592 b'http': _gitlfsremote,
592 593 b'file': _dummyremote,
593 594 b'null': _nullremote,
594 595 None: _promptremote,
595 596 }
596 597
597 598 def _deduplicate(pointers):
598 599 """Remove any duplicate oids that exist in the list"""
599 600 reduced = util.sortdict()
600 601 for p in pointers:
601 602 reduced[p.oid()] = p
602 603 return reduced.values()
603 604
604 605 def _verify(oid, content):
605 606 realoid = node.hex(hashlib.sha256(content).digest())
606 607 if realoid != oid:
607 608 raise LfsCorruptionError(_(b'detected corrupt lfs object: %s') % oid,
608 609 hint=_(b'run hg verify'))
609 610
610 611 def remote(repo, remote=None):
611 612 """remotestore factory. return a store in _storemap depending on config
612 613
613 614 If ``lfs.url`` is specified, use that remote endpoint. Otherwise, try to
614 615 infer the endpoint, based on the remote repository using the same path
615 616 adjustments as git. As an extension, 'http' is supported as well so that
616 617 ``hg serve`` works out of the box.
617 618
618 619 https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md
619 620 """
620 621 lfsurl = repo.ui.config(b'lfs', b'url')
621 622 url = util.url(lfsurl or '')
622 623 if lfsurl is None:
623 624 if remote:
624 625 path = remote
625 626 elif util.safehasattr(repo, '_subtoppath'):
626 627 # The pull command sets this during the optional update phase, which
627 628 # tells exactly where the pull originated, whether 'paths.default'
628 629 # or explicit.
629 630 path = repo._subtoppath
630 631 else:
631 632 # TODO: investigate 'paths.remote:lfsurl' style path customization,
632 633 # and fall back to inferring from 'paths.remote' if unspecified.
633 634 path = repo.ui.config(b'paths', b'default') or b''
634 635
635 636 defaulturl = util.url(path)
636 637
637 638 # TODO: support local paths as well.
638 639 # TODO: consider the ssh -> https transformation that git applies
639 640 if defaulturl.scheme in (b'http', b'https'):
640 641 if defaulturl.path and defaulturl.path[:-1] != b'/':
641 642 defaulturl.path += b'/'
642 643 defaulturl.path = (defaulturl.path or b'') + b'.git/info/lfs'
643 644
644 645 url = util.url(bytes(defaulturl))
645 646 repo.ui.note(_(b'lfs: assuming remote store: %s\n') % url)
646 647
647 648 scheme = url.scheme
648 649 if scheme not in _storemap:
649 650 raise error.Abort(_(b'lfs: unknown url scheme: %s') % scheme)
650 651 return _storemap[scheme](repo, url)
651 652
652 653 class LfsRemoteError(error.StorageError):
653 654 pass
654 655
655 656 class LfsCorruptionError(error.Abort):
656 657 """Raised when a corrupt blob is detected, aborting an operation
657 658
658 659 It exists to allow specialized handling on the server side."""
General Comments 0
You need to be logged in to leave comments. Login now