git/http-pull.c
barkalow@iabervon.org 182005b954 [PATCH] Support downloading packs by HTTP (whitespace fixed)
This adds support to http-pull for finding the list of pack files
available on the server, downloading the index files for those pack
files, and downloading pack files when they contain needed objects not
available individually. It retains the index files even if the pack
files were not needed, but downloads the list of pack files once per
run if an object is not found separately.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-07-31 23:30:59 -07:00

368 lines
7.7 KiB
C

#include "cache.h"
#include "commit.h"
#include "pull.h"
#include <curl/curl.h>
#include <curl/easy.h>
#if LIBCURL_VERSION_NUM < 0x070704
#define curl_global_cleanup() do { /* nothing */ } while(0)
#endif
#if LIBCURL_VERSION_NUM < 0x070800
#define curl_global_init(a) do { /* nothing */ } while(0)
#endif
static CURL *curl;
static char *base;
static SHA_CTX c;
static z_stream stream;
static int local;
static int zret;
static int curl_ssl_verify;
struct buffer
{
size_t posn;
size_t size;
void *buffer;
};
static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
struct buffer *buffer)
{
size_t size = eltsize * nmemb;
if (size > buffer->size - buffer->posn)
size = buffer->size - buffer->posn;
memcpy(buffer->buffer + buffer->posn, ptr, size);
buffer->posn += size;
return size;
}
static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
void *data)
{
unsigned char expn[4096];
size_t size = eltsize * nmemb;
int posn = 0;
do {
ssize_t retval = write(local, ptr + posn, size - posn);
if (retval < 0)
return posn;
posn += retval;
} while (posn < size);
stream.avail_in = size;
stream.next_in = ptr;
do {
stream.next_out = expn;
stream.avail_out = sizeof(expn);
zret = inflate(&stream, Z_SYNC_FLUSH);
SHA1_Update(&c, expn, sizeof(expn) - stream.avail_out);
} while (stream.avail_in && zret == Z_OK);
return size;
}
static int got_indices = 0;
static struct packed_git *packs = NULL;
static int fetch_index(unsigned char *sha1)
{
char *filename;
char *url;
FILE *indexfile;
if (has_pack_index(sha1))
return 0;
if (get_verbosely)
fprintf(stderr, "Getting index for pack %s\n",
sha1_to_hex(sha1));
url = xmalloc(strlen(base) + 64);
sprintf(url, "%s/objects/pack/pack-%s.idx",
base, sha1_to_hex(sha1));
filename = sha1_pack_index_name(sha1);
indexfile = fopen(filename, "w");
if (!indexfile)
return error("Unable to open local file %s for pack index",
filename);
curl_easy_setopt(curl, CURLOPT_FILE, indexfile);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_URL, url);
if (curl_easy_perform(curl)) {
fclose(indexfile);
return error("Unable to get pack index %s", url);
}
fclose(indexfile);
return 0;
}
static int setup_index(unsigned char *sha1)
{
struct packed_git *new_pack;
if (has_pack_file(sha1))
return 0; // don't list this as something we can get
if (fetch_index(sha1))
return -1;
new_pack = parse_pack_index(sha1);
new_pack->next = packs;
packs = new_pack;
return 0;
}
static int fetch_indices(void)
{
unsigned char sha1[20];
char *url;
struct buffer buffer;
char *data;
int i = 0;
if (got_indices)
return 0;
data = xmalloc(4096);
buffer.size = 4096;
buffer.posn = 0;
buffer.buffer = data;
if (get_verbosely)
fprintf(stderr, "Getting pack list\n");
url = xmalloc(strlen(base) + 21);
sprintf(url, "%s/objects/info/packs", base);
curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
curl_easy_setopt(curl, CURLOPT_URL, url);
if (curl_easy_perform(curl)) {
return error("Unable to get pack index %s", url);
}
do {
switch (data[i]) {
case 'P':
i++;
if (i + 52 < buffer.posn &&
!strncmp(data + i, " pack-", 6) &&
!strncmp(data + i + 46, ".pack\n", 6)) {
get_sha1_hex(data + i + 6, sha1);
setup_index(sha1);
i += 51;
break;
}
default:
while (data[i] != '\n')
i++;
}
i++;
} while (i < buffer.posn);
got_indices = 1;
return 0;
}
static int fetch_pack(unsigned char *sha1)
{
char *url;
struct packed_git *target;
struct packed_git **lst;
FILE *packfile;
char *filename;
if (fetch_indices())
return -1;
target = find_sha1_pack(sha1, packs);
if (!target)
return error("Couldn't get %s: not separate or in any pack",
sha1_to_hex(sha1));
if (get_verbosely) {
fprintf(stderr, "Getting pack %s\n",
sha1_to_hex(target->sha1));
fprintf(stderr, " which contains %s\n",
sha1_to_hex(sha1));
}
url = xmalloc(strlen(base) + 65);
sprintf(url, "%s/objects/pack/pack-%s.pack",
base, sha1_to_hex(target->sha1));
filename = sha1_pack_name(target->sha1);
packfile = fopen(filename, "w");
if (!packfile)
return error("Unable to open local file %s for pack",
filename);
curl_easy_setopt(curl, CURLOPT_FILE, packfile);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_URL, url);
if (curl_easy_perform(curl)) {
fclose(packfile);
return error("Unable to get pack file %s", url);
}
fclose(packfile);
lst = &packs;
while (*lst != target)
lst = &((*lst)->next);
*lst = (*lst)->next;
install_packed_git(target);
return 0;
}
int fetch(unsigned char *sha1)
{
char *hex = sha1_to_hex(sha1);
char *filename = sha1_file_name(sha1);
unsigned char real_sha1[20];
char *url;
char *posn;
local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (local < 0)
return error("Couldn't open local object %s\n", filename);
memset(&stream, 0, sizeof(stream));
inflateInit(&stream);
SHA1_Init(&c);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
curl_easy_setopt(curl, CURLOPT_FILE, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
url = xmalloc(strlen(base) + 50);
strcpy(url, base);
posn = url + strlen(base);
strcpy(posn, "objects/");
posn += 8;
memcpy(posn, hex, 2);
posn += 2;
*(posn++) = '/';
strcpy(posn, hex + 2);
curl_easy_setopt(curl, CURLOPT_URL, url);
if (curl_easy_perform(curl)) {
unlink(filename);
if (fetch_pack(sha1))
return error("Tried %s", url);
return 0;
}
close(local);
inflateEnd(&stream);
SHA1_Final(real_sha1, &c);
if (zret != Z_STREAM_END) {
unlink(filename);
return error("File %s (%s) corrupt\n", hex, url);
}
if (memcmp(sha1, real_sha1, 20)) {
unlink(filename);
return error("File %s has bad hash\n", hex);
}
pull_say("got %s\n", hex);
return 0;
}
int fetch_ref(char *ref, unsigned char *sha1)
{
char *url, *posn;
char hex[42];
struct buffer buffer;
buffer.size = 41;
buffer.posn = 0;
buffer.buffer = hex;
hex[41] = '\0';
curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
url = xmalloc(strlen(base) + 6 + strlen(ref));
strcpy(url, base);
posn = url + strlen(base);
strcpy(posn, "refs/");
posn += 5;
strcpy(posn, ref);
curl_easy_setopt(curl, CURLOPT_URL, url);
if (curl_easy_perform(curl))
return error("Couldn't get %s for %s\n", url, ref);
hex[40] = '\0';
get_sha1_hex(hex, sha1);
return 0;
}
int main(int argc, char **argv)
{
char *commit_id;
char *url;
int arg = 1;
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't') {
get_tree = 1;
} else if (argv[arg][1] == 'c') {
get_history = 1;
} else if (argv[arg][1] == 'a') {
get_all = 1;
get_tree = 1;
get_history = 1;
} else if (argv[arg][1] == 'v') {
get_verbosely = 1;
} else if (argv[arg][1] == 'w') {
write_ref = argv[arg + 1];
arg++;
}
arg++;
}
if (argc < arg + 2) {
usage("git-http-pull [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
return 1;
}
commit_id = argv[arg];
url = argv[arg + 1];
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
curl_ssl_verify = gitenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
#if LIBCURL_VERSION_NUM >= 0x070907
curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
#endif
base = url;
if (pull(commit_id))
return 1;
curl_global_cleanup();
return 0;
}