From: Jeff King <hidden> Date: 2016-06-15 22:52:27
One possible option for resumable clones that has been discussed is
letting the server point the client by http to a static bundle
containing most of history, followed by a fetch from the actual git repo
(which should be much cheaper now that we have all of the bundled
history). This series implements "step 0" of this plan: just letting
bundles be fetched across the network in the first place.
Shawn raised some issues about using bundles for this (as opposed to
accessing the packfiles themselves); specifically, this raises the I/O
footprint of a repository that has to serve both the bundled version of
the pack and the regular packfile.
So it may be that we don't follow this plan all the way through.
However, even if we don't, fetching bundles over http is still a useful
thing to be able to do. Which makes this first step worth doing either
way.
[01/14]: t/lib-httpd: check for NO_CURL
[02/14]: http: turn off curl signals
[03/14]: http: refactor http_request function
[04/14]: http: add a public function for arbitrary-callback request
[05/14]: remote-curl: use http callback for requesting refs
[06/14]: transport: factor out bundle to ref list conversion
[07/14]: bundle: add is_bundle_buf helper
[08/14]: remote-curl: free "discovery" object
[09/14]: remote-curl: auto-detect bundles when fetching refs
[10/14]: remote-curl: try base $URL after $URL/info/refs
[11/14]: progress: allow pure-throughput progress meters
[12/14]: remote-curl: show progress for bundle downloads
[13/14]: remote-curl: resume interrupted bundle transfers
[14/14]: clone: give advice on how to resume a failed clone
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
On Thu, Nov 10, 2011 at 02:43:30AM -0500, Jeff King wrote:
[01/14]: t/lib-httpd: check for NO_CURL
[02/14]: http: turn off curl signals
[03/14]: http: refactor http_request function
[04/14]: http: add a public function for arbitrary-callback request
[05/14]: remote-curl: use http callback for requesting refs
[06/14]: transport: factor out bundle to ref list conversion
[07/14]: bundle: add is_bundle_buf helper
[08/14]: remote-curl: free "discovery" object
[09/14]: remote-curl: auto-detect bundles when fetching refs
[10/14]: remote-curl: try base $URL after $URL/info/refs
[11/14]: progress: allow pure-throughput progress meters
[12/14]: remote-curl: show progress for bundle downloads
[13/14]: remote-curl: resume interrupted bundle transfers
[14/14]: clone: give advice on how to resume a failed clone
I forgot to mention: this goes on top of mf/curl-select-fdset. It's only
in next now, but some of my http cleanups build semantically on the
cleanups in that topic.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
We already do this check individually in each of the tests
that includes lib-httpd. Let's factor it out.
There is one test (t5540) that uses lib-httpd but does not
currently do this check. But it actually has a stricter
check which is a superset (it needs all of the requirements
to have built git-http-push, one of which is not setting
NO_CURL), so adding this extra check won't hurt anything.
Signed-off-by: Jeff King <redacted>
---
t/lib-httpd.sh | 5 +++++
t/t5541-http-push.sh | 5 -----
t/t5550-http-fetch.sh | 5 -----
t/t5551-http-fetch.sh | 5 -----
t/t5561-http-backend.sh | 5 -----
5 files changed, 5 insertions(+), 20 deletions(-)
@@ -6,11 +6,6 @@test_description='test smart pushing over http via http-backend' ../test-lib.sh-iftest-n"$NO_CURL";then-skip_all='skipping test, git built without http support'-test_done-fi-ROOT_PATH="$PWD"LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'} ."$TEST_DIRECTORY"/lib-httpd.sh
@@ -3,11 +3,6 @@test_description='test dumb fetching over http via static file' ../test-lib.sh-iftest-n"$NO_CURL";then-skip_all='skipping test, git built without http support'-test_done-fi-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'} ."$TEST_DIRECTORY"/lib-httpd.sh start_httpd
@@ -3,11 +3,6 @@test_description='test smart fetching over http via http-backend' ../test-lib.sh-iftest-n"$NO_CURL";then-skip_all='skipping test, git built without http support'-test_done-fi-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'} ."$TEST_DIRECTORY"/lib-httpd.sh start_httpd
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
Curl sets and clears the handler for SIGALRM, which makes it
incompatible with git's progress code. However, we can ask
curl not to do this.
Signed-off-by: Jeff King <redacted>
---
I'm a little iffy on this one. If I understand correctly, depending on
the build and configuration, curl may not be able to timeout during DNS
lookups. But I'm not sure if it does, anyway, since we don't set any
timeouts.
An alternate plan would be to give the progress code a mode where it
gets poked by curl every second or so (curl has a PROGRESSFUNCTION
option for doing this).
http.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
This function takes a flag to indicate where the output
should go (either to a file or to a strbuf). This flag is
mostly used to set the callback function we hand to curl.
This isn't very flexible for adding new output types.
Instead, let's just let callers pass in the callback
function directly. This results in shorter, more readable,
and more flexible code.
The only other thing the flag was used for was to set a
"Range" header when we already have a partial file (by using
the results of ftell). This patch also adds an "offset"
parameter, which can be used by callers to specify this
feature separately (which is also more flexible, as non-FILE
callers can now resume partial transfers).
Signed-off-by: Jeff King <redacted>
---
http.c | 37 ++++++++++++++-----------------------
1 files changed, 14 insertions(+), 23 deletions(-)
@@ -818,19 +815,13 @@ static int http_request(const char *url, void *result, int target, int options)}else{curl_easy_setopt(slot->curl,CURLOPT_NOBODY,0);curl_easy_setopt(slot->curl,CURLOPT_FILE,result);+curl_easy_setopt(slot->curl,CURLOPT_WRITEFUNCTION,cb);+}-if(target==HTTP_REQUEST_FILE){-longposn=ftell(result);-curl_easy_setopt(slot->curl,CURLOPT_WRITEFUNCTION,-fwrite);-if(posn>0){-strbuf_addf(&buf,"Range: bytes=%ld-",posn);-headers=curl_slist_append(headers,buf.buf);-strbuf_reset(&buf);-}-}else-curl_easy_setopt(slot->curl,CURLOPT_WRITEFUNCTION,-fwrite_buffer);+if(offset>0){+strbuf_addf(&buf,"Range: bytes=%lu-",offset);+headers=curl_slist_append(headers,buf.buf);+strbuf_reset(&buf);}strbuf_addstr(&buf,"Pragma:");
@@ -881,18 +872,18 @@ static int http_request(const char *url, void *result, int target, int options)returnret;}-staticinthttp_request_reauth(constchar*url,void*result,inttarget,-intoptions)+staticinthttp_request_reauth(constchar*url,curl_write_callbackcb,+void*result,unsignedlongoffset,intoptions){-intret=http_request(url,result,target,options);+intret=http_request(url,cb,result,offset,options);if(ret!=HTTP_REAUTH)returnret;-returnhttp_request(url,result,target,options);+returnhttp_request(url,cb,result,offset,options);}inthttp_get_strbuf(constchar*url,structstrbuf*result,intoptions){-returnhttp_request_reauth(url,result,HTTP_REQUEST_STRBUF,options);+returnhttp_request_reauth(url,fwrite_buffer,result,0,options);}/*
@@ -915,7 +906,7 @@ static int http_get_file(const char *url, const char *filename, int options)gotocleanup;}-ret=http_request_reauth(url,result,HTTP_REQUEST_FILE,options);+ret=http_request_reauth(url,NULL,result,ftell(result),options);fclose(result);if((ret==HTTP_OK)&&move_temp_to_file(tmpfile.buf,filename))
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
The http_request function recently learned to take arbitrary
callbacks; let's expose this functionality to callers.
Signed-off-by: Jeff King <redacted>
---
http.c | 6 ++++++
http.h | 3 +++
2 files changed, 9 insertions(+), 0 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
This should behave identically to the current strbuf code,
but opens up room for us to do more clever things with
bundles in a future patch.
Signed-off-by: Jeff King <redacted>
---
Obviously it's way more code for the same thing, but future patches will
make the design more clear.
remote-curl.c | 22 ++++++++++++++++++++--
1 files changed, 20 insertions(+), 2 deletions(-)
@@ -112,7 +130,7 @@ static void free_discovery(struct discovery *d)}refs_url=strbuf_detach(&buffer,NULL);-http_ret=http_get_strbuf(refs_url,&buffer,HTTP_NO_CACHE);+http_ret=get_refs_from_url(refs_url,&buffer,HTTP_NO_CACHE);/* try again with "plain" url (no ? or & appended) */if(http_ret!=HTTP_OK&&http_ret!=HTTP_NOAUTH){
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
There is a data structure mismatch between what the
transport code wants (a linked list of "struct ref") and
what the bundle header provides (an array of ref names and
sha1s), so the transport code has to convert.
Let's factor out this conversion to make it useful to other
transport-ish callers (like remote-curl).
Signed-off-by: Jeff King <redacted>
---
bundle.c | 16 ++++++++++++++++
bundle.h | 2 ++
transport.c | 11 +----------
3 files changed, 19 insertions(+), 10 deletions(-)
@@ -449,3 +450,18 @@ int unbundle(struct bundle_header *header, int bundle_fd, int flags)returnerror("index-pack died");return0;}++structref*bundle_header_to_refs(conststructbundle_header*header)+{+structref*result=NULL;+inti;++for(i=0;i<header->references.nr;i++){+structref_list_entry*e=header->references.list+i;+structref*ref=alloc_ref(e->name);+hashcpy(ref->old_sha1,e->sha1);+ref->next=result;+result=ref;+}+returnresult;+}
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
This is similar to is_bundle, but checks an in-memory buffer
rather than a file. It also works on a partial buffer,
checking only the bundle signature. This means the result is
a three-way conditional: yes, no, or "we do not have enough
data yet".
Signed-off-by: Jeff King <redacted>
---
bundle.c | 14 ++++++++++++++
bundle.h | 1 +
2 files changed, 15 insertions(+), 0 deletions(-)
@@ -122,6 +122,20 @@ int is_bundle(const char *path, int quiet)return(fd>=0);}+intis_bundle_buf(constchar*s,intlen)+{+if(len>strlen(bundle_signature))+len=strlen(bundle_signature);+/* If we don't match what we already have, then definitely not. */+if(memcmp(s,bundle_signature,len))+return0;+/* If we have enough bytes, we can say yes */+if(len==strlen(bundle_signature))+return1;+/* otherwise, we can only say "maybe" */+return-1;+}+staticintlist_refs(structref_list*r,intargc,constchar**argv){inti;
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
We cache the last-used "discovery" object, which contains
the data we pulled from the remote about which refs it has,
which saves us an HTTP round-trip when somebody does
something like "list" followed by "fetch".
We don't bother free()ing it at the end of the program
because it just contains memory which will be reclaimed by
the OS. However, cleaning up explicitly will future-proof us
against later changes which will add external storage (like
temporary files).
Signed-off-by: Jeff King <redacted>
---
remote-curl.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
You can't currently fetch from a network bundle, like:
git fetch http://example.com/foo.bundle
This patch takes the first (and biggest) step towards that
working: it auto-detects when fetching refs results in a
bundle, and automatically spools the bundle to disk and
fetches from it.
There are a few important design decisions to note:
1. We auto-detect the bundle based on content, not based
on a special token in the URL (like ending in
".bundle"). This lets the server side be flexible with
its URLs (e.g., "http://example.com/bundle?repo=foo").
2. When fetching refs, we don't actually fetch $URL, but
start with $URL/info/refs, looking for smart or dumb
http. Some servers, when file "foo.bundle" exists, will
serve it to the client when "foo.bundle/info/refs" is
requested. Therefore we may be "surprised" to receive a
bundle when we thought we were just getting the list of
refs, and need to handle it appropriately.
3. We spool the bundle to disk, and then run "index-pack
--fix-thin" to create a packfile. That means we will
momentarily use twice the size of the bundle in local
disk space. Avoiding this would mean piping directly to
"index-pack --fix-thin". However, if we want to be
able to resume the transfer of the bundle after an
interruption, then we need to save the bundle's pack.
In theory a smart index-pack that was interrupted could
write out its partial results along with a count of how
many bytes it actually consumed (i.e., where to resume
next time), and then pick up where it left off when fed
the rest of the data. But index-pack isn't that smart
yet, so let's start off with spooling.
No tests yet, as apache is not one of the "surprising"
servers from (2), and our test harness is based around that
(though just with this patch, you can fetch from surprising
servers like lighttpd).
Signed-off-by: Jeff King <redacted>
---
This is really the big, interesting one.
remote-curl.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 119 insertions(+), 5 deletions(-)
@@ -86,26 +91,93 @@ static void free_discovery(struct discovery *d)if(d==last_discovery)last_discovery=NULL;free(d->buf_alloc);+if(d->bundle_fd>=0)+close(d->bundle_fd);+if(d->bundle_filename){+unlink(d->bundle_filename);+free(d->bundle_filename);+}free(d);}}structget_refs_cb_data{structstrbuf*out;++intis_bundle;+constchar*tmpname;+FILE*fh;};staticsize_tget_refs_callback(char*buf,size_tsz,size_tn,void*vdata){structget_refs_cb_data*data=vdata;-strbuf_add(data->out,buf,sz*n);+structstrbuf*out=data->out;++if(data->is_bundle>0)+returnfwrite(buf,sz,n,data->fh);++strbuf_add(out,buf,sz*n);++if(data->is_bundle==0)+returnsz*n;++data->is_bundle=is_bundle_buf(out->buf,out->len);+if(data->is_bundle>0){+data->fh=fopen(data->tmpname,"wb");+if(!data->fh)+die_errno("unable to open %s",data->tmpname);+if(fwrite(out->buf,1,out->len,data->fh)<out->len)+die_errno("unable to write to %s",data->tmpname);+}returnsz*n;}-staticintget_refs_from_url(constchar*url,structstrbuf*out,intoptions)+staticintget_refs_from_url(constchar*url,structstrbuf*out,intoptions,+constchar*tmpname,int*is_bundle){structget_refs_cb_datadata;+intret;+data.out=out;-returnhttp_get_callback(url,get_refs_callback,&data,0,options);+data.is_bundle=-1;+data.tmpname=tmpname;+data.fh=NULL;++ret=http_get_callback(url,get_refs_callback,&data,0,options);++if(data.fh){+if(fclose(data.fh))+die_errno("unable to write to %s",data.tmpname);+}++*is_bundle=data.is_bundle>0;+returnret;+}++staticconstchar*url_to_bundle_tmpfile(constchar*url)+{+structstrbufbuf=STRBUF_INIT;+intlast_was_quoted=1;+constchar*ret;++strbuf_addstr(&buf,"tmp_bundle_");+for(;*url;url++){+if(isalpha(*url)||isdigit(*url)){+strbuf_addch(&buf,*url);+last_was_quoted=0;+}+elseif(!last_was_quoted){+strbuf_addch(&buf,'_');+last_was_quoted=1;+}+}+if(last_was_quoted)+strbuf_setlen(&buf,buf.len-1);++ret=git_path("objects/%s",buf.buf);+strbuf_release(&buf);+returnret;}staticstructdiscovery*discover_refs(constchar*service)
@@ -114,11 +186,15 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options)structdiscovery*last=last_discovery;char*refs_url;inthttp_ret,is_http=0,proto_git_candidate=1;+constchar*filename;+intis_bundle;if(last&&!strcmp(service,last->service))returnlast;free_discovery(last);+filename=url_to_bundle_tmpfile(url);+strbuf_addf(&buffer,"%sinfo/refs",url);if(!prefixcmp(url,"http://")||!prefixcmp(url,"https://")){is_http=1;
@@ -130,7 +206,8 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options)}refs_url=strbuf_detach(&buffer,NULL);-http_ret=get_refs_from_url(refs_url,&buffer,HTTP_NO_CACHE);+http_ret=get_refs_from_url(refs_url,&buffer,HTTP_NO_CACHE,+filename,&is_bundle);/* try again with "plain" url (no ? or & appended) */if(http_ret!=HTTP_OK&&http_ret!=HTTP_NOAUTH){
@@ -141,7 +218,8 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options)strbuf_addf(&buffer,"%sinfo/refs",url);refs_url=strbuf_detach(&buffer,NULL);-http_ret=get_refs_from_url(refs_url,&buffer,HTTP_NO_CACHE);+http_ret=get_refs_from_url(refs_url,&buffer,HTTP_NO_CACHE,+filename,&is_bundle);}switch(http_ret){
@@ -161,6 +239,7 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options)last->service=service;last->buf_alloc=strbuf_detach(&buffer,&last->len);last->buf=last->buf_alloc;+last->bundle_fd=-1;if(is_http&&proto_git_candidate&&5<=last->len&&last->buf[4]=='#'){
@@ -190,6 +269,10 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options)last->proto_git=1;}+elseif(is_bundle){+last->bundle_filename=xstrdup(filename);+}+free(refs_url);strbuf_release(&buffer);last_discovery=last;
@@ -276,6 +359,22 @@ static int write_discovery(int in, int out, void *data)returnrefs;}+staticvoidensure_bundle_open(structdiscovery*heads)+{+if(heads->bundle_fd>=0)+return;+heads->bundle_fd=read_bundle_header(heads->bundle_filename,+&heads->bundle_header);+if(heads->bundle_fd<0)+die("could not read bundle from %s",url);+}++staticstructref*parse_bundle_refs(structdiscovery*heads)+{+ensure_bundle_open(heads);+returnbundle_header_to_refs(&heads->bundle_header);+}+staticstructref*get_refs(intfor_push){structdiscovery*heads;
@@ -287,6 +386,11 @@ static int write_discovery(int in, int out, void *data)if(heads->proto_git)returnparse_git_refs(heads);+if(heads->bundle_filename){+if(for_push)+die("cannot push into a remote bundle");+returnparse_bundle_refs(heads);+}returnparse_info_refs(heads);}
@@ -690,11 +794,21 @@ static int fetch_git(struct discovery *heads,returnerr;}+staticintfetch_bundle(structdiscovery*d,+intnr_heads,structref**to_fetch)+{+ensure_bundle_open(d);+returnunbundle(&d->bundle_header,d->bundle_fd,+options.progress?BUNDLE_VERBOSE:0);+}+staticintfetch(intnr_heads,structref**to_fetch){structdiscovery*d=discover_refs("git-upload-pack");if(d->proto_git)returnfetch_git(d,nr_heads,to_fetch);+elseif(d->bundle_filename)+returnfetch_bundle(d,nr_heads,to_fetch);elsereturnfetch_dumb(nr_heads,to_fetch);}
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
When fetching via http, we will try "$URL/info/refs" to get
the list of refs. We may get an unexpected bundle from that
transfer, and we already handle that case. But we should
also check just "$URL" to see if it's a bundle.
Signed-off-by: Jeff King <redacted>
---
And now we can actually test with apache.
remote-curl.c | 19 +++++++++++++++++++
t/t5552-http-bundle.sh | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 0 deletions(-)
create mode 100755 t/t5552-http-bundle.sh
@@ -222,6 +222,25 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options,filename,&is_bundle);}+/* try the straight URL for a bundle, but don't impact the+*errorreportingthathappensbelow.*/+if(http_ret!=HTTP_OK&&http_ret!=HTTP_NOAUTH){+structstrbuftrimmed=STRBUF_INIT;+intr;++strbuf_reset(&buffer);++strbuf_addstr(&trimmed,url);+while(trimmed.len>0&&trimmed.buf[trimmed.len-1]=='/')+strbuf_setlen(&trimmed,trimmed.len-1);++r=get_refs_from_url(trimmed.buf,&buffer,0,filename,+&is_bundle);+if(r==HTTP_OK&&is_bundle)+http_ret=r;+strbuf_release(&trimmed);+}+switch(http_ret){caseHTTP_OK:break;
@@ -0,0 +1,36 @@+#!/bin/sh++test_description='test fetching from http-accessible bundles'+../test-lib.sh++LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5552'}+."$TEST_DIRECTORY"/lib-httpd.sh+start_httpd++test_expect_success'create bundles''+test_commitone&&+gitbundlecreate"$HTTPD_DOCUMENT_ROOT_PATH/one.bundle"--all&&+test_committwo&&+gitbundlecreate"$HTTPD_DOCUMENT_ROOT_PATH/two.bundle"--all^one+'++test_expect_success'clone from bundle''+gitclone--bare$HTTPD_URL/one.bundleclone&&+echoone>expect&&+git--git-dir=clonelog-1--format=%s>actual&&+test_cmpexpectactual+'++test_expect_success'fetch from bundle''+git--git-dir=clonefetch$HTTPD_URL/two.bundlerefs/*:refs/*&&+echotwo>expect&&+git--git-dir=clonelog-1--format=%s>actual&&+test_cmpexpectactual+'++test_expect_success'cannot clone from partial bundle''+test_must_failgitclone$HTTPD_URL/two.bundle+'++stop_httpd+test_done
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
The progress code assumes we are counting something (usually
objects), even if we are measuring throughput. This works
for fetching packfiles, since they show us the object count
alongside the throughput, like:
Receiving objects: 2% (301/11968), 22.00 MiB | 10.97 MiB/s
You can also tell the progress code you don't know how many
items you have (by specifying a total of 0), and it looks
like:
Counting objects: 34957
However, if you're fetching a single large item, you want
throughput but you might not have a meaningful count. You
can say you are getting item 0 or 1 out of 1 total, but then
the percent meter is misleading:
Downloading: 0% (0/1), 22.00 MiB | 10.97 MiB/s
or
Downloading: 100% (0/1), 22.00 MiB | 10.97 MiB/s
Neither of those is accurate. You are probably somewhere
between zero and 100 percent through the operation, but you
don't know how far.
Telling it you don't know how many items is even uglier:
Downloading: 1, 22.00 MiB | 10.97 MiB/s
Instead, this patch will omit the count entirely if you are
on the zero-th item of an unknown number of items. It looks
like:
Downloading: 22.00 MiB | 10.97 MiB/s
Signed-off-by: Jeff King <redacted>
---
This was the last amount of work to massage the progress code into doing
what I wanted. It might be nicer if it could show a percentage (if we
know the total size), but there's even more surgery required for that.
progress.c | 20 ++++++++++++--------
1 files changed, 12 insertions(+), 8 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
Generally, the point of fetching from a bundle is that it's
big. Without a progress meter, git will appear to hang
during the long download.
This patch adds a throughput meter (i.e., just the bytes
transferred and the rate). In the long run, we should look
for a content-length header from the server so we can show a
total size and completion percentage. However, displaying
that properly will require some surgery to the progress
code, so let's leave it as a future enhancement.
Signed-off-by: Jeff King <redacted>
---
remote-curl.c | 18 +++++++++++++++++-
1 files changed, 17 insertions(+), 1 deletions(-)
@@ -129,6 +136,12 @@ static size_t get_refs_callback(char *buf, size_t sz, size_t n, void *vdata)die_errno("unable to open %s",data->tmpname);if(fwrite(out->buf,1,out->len,data->fh)<out->len)die_errno("unable to write to %s",data->tmpname);+if(options.progress){+data->total=out->len;+data->progress=start_progress("Downloading bundle",0);+display_progress(data->progress,0);+display_throughput(data->progress,data->total);+}}returnsz*n;}
@@ -143,6 +156,8 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options,data.is_bundle=-1;data.tmpname=tmpname;data.fh=NULL;+data.progress=NULL;+data.total=0;ret=http_get_callback(url,get_refs_callback,&data,0,options);
@@ -150,6 +165,7 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options,if(fclose(data.fh))die_errno("unable to write to %s",data.tmpname);}+stop_progress(&data.progress);*is_bundle=data.is_bundle>0;returnret;
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
If we have a bundle file from a previous fetch that matches
this URL, then we should resume the transfer where we left
off.
Signed-off-by: Jeff King <redacted>
---
The second half of the diff is hard to read because I re-indent a big
chunk. It's much easier to see what's going on with "diff -b".
remote-curl.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 53 insertions(+), 11 deletions(-)
@@ -9,6 +9,7 @@#include"sideband.h"#include"bundle.h"#include"progress.h"+#include"dir.h"staticstructremote*remote;staticconstchar*url;/* always ends with a trailing slash */
@@ -171,6 +172,32 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options,returnret;}+staticintresume_bundle(constchar*url,constchar*tmpname)+{+structget_refs_cb_datadata;+intret;++data.fh=fopen(tmpname,"ab");+if(!data.fh)+die_errno("unable to open %s",tmpname);++data.is_bundle=1;+data.total=ftell(data.fh);+if(options.progress){+data.progress=start_progress("Resuming bundle",0);+display_progress(data.progress,0);+display_throughput(data.progress,data.total);+}++ret=http_get_callback(url,get_refs_callback,&data,data.total,0);++if(fclose(data.fh))+die_errno("unable to write to %s",tmpname);+stop_progress(&data.progress);++returnret;+}+staticconstchar*url_to_bundle_tmpfile(constchar*url){structstrbufbuf=STRBUF_INIT;
@@ -210,20 +237,35 @@ static int get_refs_from_url(const char *url, struct strbuf *out, int options,free_discovery(last);filename=url_to_bundle_tmpfile(url);+if(file_exists(filename)){+structstrbuftrimmed=STRBUF_INIT;-strbuf_addf(&buffer,"%sinfo/refs",url);-if(!prefixcmp(url,"http://")||!prefixcmp(url,"https://")){-is_http=1;-if(!strchr(url,'?'))-strbuf_addch(&buffer,'?');-else-strbuf_addch(&buffer,'&');-strbuf_addf(&buffer,"service=%s",service);+strbuf_addstr(&trimmed,url);+while(trimmed.len>0&&trimmed.buf[trimmed.len-1]=='/')+strbuf_setlen(&trimmed,trimmed.len-1);+refs_url=strbuf_detach(&trimmed,NULL);++http_ret=resume_bundle(refs_url,filename);+is_bundle=1;}-refs_url=strbuf_detach(&buffer,NULL);+else+http_ret=HTTP_MISSING_TARGET;++if(http_ret!=HTTP_OK&&http_ret!=HTTP_NOAUTH){+strbuf_addf(&buffer,"%sinfo/refs",url);+if(!prefixcmp(url,"http://")||!prefixcmp(url,"https://")){+is_http=1;+if(!strchr(url,'?'))+strbuf_addch(&buffer,'?');+else+strbuf_addch(&buffer,'&');+strbuf_addf(&buffer,"service=%s",service);+}+refs_url=strbuf_detach(&buffer,NULL);-http_ret=get_refs_from_url(refs_url,&buffer,HTTP_NO_CACHE,-filename,&is_bundle);+http_ret=get_refs_from_url(refs_url,&buffer,HTTP_NO_CACHE,+filename,&is_bundle);+}/* try again with "plain" url (no ? or & appended) */if(http_ret!=HTTP_OK&&http_ret!=HTTP_NOAUTH){
From: Jeff King <hidden> Date: 2016-06-15 22:52:27
When clone fails, we usually delete the partial directory.
However, if cloning was fetching a bundle, that is resumable
and we should consider those results precious.
This patch detects when a partial bundle is present,
preserves the directory, and gives the user some advice
about how to resume.
Signed-off-by: Jeff King <redacted>
---
We could make "git clone ..." automatically resume, but I'm a little
nervous about that. I wrote a patch that did so, and it did work, but
there are a lot of little hiccups as we violate the assumption that the
directory didn't already exist (e.g., it writes multiple fetch refspec
lines to the config).
But more importantly, I really worry about destroying the safety valve
of not overwriting an existing directory. Yes, we can check to see that
it is a git directory and that it has a partially-downloaded bundle
file. But that could also describe an existing git repo with unstaged
changes. And you sure don't want to "checkout -f" over them.
So I'd rather at least start with giving the user some advice and
having them explicitly say "yeah, I do want to resume this".
builtin/clone.c | 34 ++++++++++++++++++++++++++++++++++
1 files changed, 34 insertions(+), 0 deletions(-)
From: Daniel Stenberg <hidden> Date: 2016-06-15 22:52:27
On Thu, 10 Nov 2011, Jeff King wrote:
I'm a little iffy on this one. If I understand correctly, depending on the
build and configuration, curl may not be able to timeout during DNS lookups.
But I'm not sure if it does, anyway, since we don't set any timeouts.
Right, without a timeout set libcurl won't try to timeout name resolves.
To clarify: when libcurl is built to use the standard synchronous name
resolver functions it can only abort them after a specified time by using
signals (on posix systems).
--
/ daniel.haxx.se
From: David Michael Barr <hidden> Date: 2016-06-15 22:52:28
On Thu, Nov 10, 2011 at 6:43 PM, Jeff King [off-list ref] wrote:
One possible option for resumable clones that has been discussed is
letting the server point the client by http to a static bundle
containing most of history, followed by a fetch from the actual git repo
(which should be much cheaper now that we have all of the bundled
history). This series implements "step 0" of this plan: just letting
bundles be fetched across the network in the first place.
Shawn raised some issues about using bundles for this (as opposed to
accessing the packfiles themselves); specifically, this raises the I/O
footprint of a repository that has to serve both the bundled version of
the pack and the regular packfile.
So it may be that we don't follow this plan all the way through.
However, even if we don't, fetching bundles over http is still a useful
thing to be able to do. Which makes this first step worth doing either
way.
[01/14]: t/lib-httpd: check for NO_CURL
[02/14]: http: turn off curl signals
[03/14]: http: refactor http_request function
[04/14]: http: add a public function for arbitrary-callback request
[05/14]: remote-curl: use http callback for requesting refs
[06/14]: transport: factor out bundle to ref list conversion
[07/14]: bundle: add is_bundle_buf helper
[08/14]: remote-curl: free "discovery" object
[09/14]: remote-curl: auto-detect bundles when fetching refs
[10/14]: remote-curl: try base $URL after $URL/info/refs
[11/14]: progress: allow pure-throughput progress meters
[12/14]: remote-curl: show progress for bundle downloads
[13/14]: remote-curl: resume interrupted bundle transfers
[14/14]: clone: give advice on how to resume a failed clone
-Peff
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
I just want to say thank you for doing this.--David Barr
From: Jeff King <hidden> Date: 2016-06-15 22:52:28
On Thu, Nov 10, 2011 at 09:43:40AM +0100, Daniel Stenberg wrote:
quoted
I'm a little iffy on this one. If I understand correctly, depending
on the build and configuration, curl may not be able to timeout
during DNS lookups. But I'm not sure if it does, anyway, since we
don't set any timeouts.
Right, without a timeout set libcurl won't try to timeout name resolves.
To clarify: when libcurl is built to use the standard synchronous
name resolver functions it can only abort them after a specified time
by using signals (on posix systems).
OK, that matches with my understanding. I think this patch is a fine
thing to do for us, then. If we ever do start caring about timing out on
name lookups, we can rework it.
Thanks.
-Peff
From: Tay Ray Chuan <hidden> Date: 2016-06-15 22:52:28
On Thu, Nov 10, 2011 at 3:43 PM, Jeff King [off-list ref] wrote:
One possible option for resumable clones that has been discussed is
letting the server point the client by http to a static bundle
containing most of history, followed by a fetch from the actual git repo
(which should be much cheaper now that we have all of the bundled
history). This series implements "step 0" of this plan: just letting
bundles be fetched across the network in the first place.
Shawn raised some issues about using bundles for this (as opposed to
accessing the packfiles themselves); specifically, this raises the I/O
footprint of a repository that has to serve both the bundled version of
the pack and the regular packfile.
So it may be that we don't follow this plan all the way through.
However, even if we don't, fetching bundles over http is still a useful
thing to be able to do. Which makes this first step worth doing either
way.
Jeff, this is a great series, I think the cleanups and refactors
should get integrated independently of the bundle-cloning stuff.
One thing I'm not comfortable with is the "flexibility" allowed in
bundle fetching - servers are allowed to send bundles if they see fit,
and we have to detect it when they do (if I'm reading the "surprised"
scenario in patch 9 correctly).
Perhaps we can expose bundle fetching through /objects/info/bundles?
It could possibly contain information about what bundles are available
and what revs they contain. If bundles are found, fetch them;
otherwise, go through the usual ref advertisement and other steps of
the pack protocol.
That way, we take out the "surprise" factor in the fetching protocol.
--
Cheers,
Ray Chuan
From: Jeff King <hidden> Date: 2016-06-15 22:52:28
On Sun, Nov 13, 2011 at 12:11:31AM +0800, Tay Ray Chuan wrote:
One thing I'm not comfortable with is the "flexibility" allowed in
bundle fetching - servers are allowed to send bundles if they see fit,
and we have to detect it when they do (if I'm reading the "surprised"
scenario in patch 9 correctly).
Right.
Perhaps we can expose bundle fetching through /objects/info/bundles?
But what if the server you are hitting doesn't have a git repo at all?
In the simplest case, a bundle provider should just be able to put a
file somewhere http-ccessible, without having any special directory
structure or other meta files.
Which means that we have to be prepared for the URL the user gave us to
be a bundle, not a git repo that contains bundles.
It could possibly contain information about what bundles are available
and what revs they contain. If bundles are found, fetch them;
otherwise, go through the usual ref advertisement and other steps of
the pack protocol.
This is "step 2" of my plan: hitting a git repo will provide a way of
redirecting to other, static storage. But I think it's important that
the other storage not just be a path in the existing repo, for two
reasons:
1. You might want to redirect the client off-server to a
higher-bandwidth static service like S3, or something backed by a
CDN.
2. The client might not be hitting you through http, so you can't
expect them to look at arbitrary repo files (like
objects/info/bundles). We need to provide the information over the
git protocol (my plan is to use a special ref name, like
"refs/mirrors" to encode the information).
That way, we take out the "surprise" factor in the fetching protocol.
I don't think it's that big a deal. It influenced the way that patches 9
and 10 were written (patch 9 handles "surprise" bundles when fetching
info/refs, and then patch 10 falls back to fetching $URL without
info/refs). But even if we didn't have the "surprise" case, most of the
code in patch 9 would have just ended up in patch 10. That is, the
surprise case doesn't take much code, and doesn't have a negative impact
on the non-surprise case (i.e., until we see a bundle header, the
behavior is identical, just putting the refs into a memory buffer).
-Peff