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