X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=sha1_file.c;h=5ec5598d7d6cd56ebb40b21c497c0ae3db1dca57;hb=7a868a84143c0bad2dce3fd0337320524436da27;hp=e6fdaa217a8ef7d2ac58e705d72bb7b34fd49282;hpb=6b0c3121061df6b323f430700e8077b324b5e9dd;p=git.git diff --git a/sha1_file.c b/sha1_file.c index e6fdaa21..5ec5598d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -6,8 +6,11 @@ * This handles basic git sha1 object files - packing, unpacking, * creation etc. */ +#include +#include #include "cache.h" #include "delta.h" +#include "pack.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -58,7 +61,7 @@ static int get_sha1_file(const char *path, unsigned char *result) return get_sha1_hex(buffer, result); } -static char *git_dir, *git_object_dir, *git_index_file; +static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir; static void setup_git_env(void) { git_dir = gitenv(GIT_DIR_ENVIRONMENT); @@ -69,6 +72,8 @@ static void setup_git_env(void) git_object_dir = xmalloc(strlen(git_dir) + 9); sprintf(git_object_dir, "%s/objects", git_dir); } + git_refs_dir = xmalloc(strlen(git_dir) + 6); + sprintf(git_refs_dir, "%s/refs", git_dir); git_index_file = gitenv(INDEX_ENVIRONMENT); if (!git_index_file) { git_index_file = xmalloc(strlen(git_dir) + 7); @@ -83,6 +88,13 @@ char *get_object_directory(void) return git_object_dir; } +char *get_refs_directory(void) +{ + if (!git_refs_dir) + setup_git_env(); + return git_refs_dir; +} + char *get_index_file(void) { if (!git_index_file) @@ -90,9 +102,27 @@ char *get_index_file(void) return git_index_file; } +int safe_create_leading_directories(char *path) +{ + char *pos = path; + + while (pos) { + pos = strchr(pos, '/'); + if (!pos) + break; + *pos = 0; + if (mkdir(path, 0777) < 0) + if (errno != EEXIST) { + *pos = '/'; + return -1; + } + *pos++ = '/'; + } + return 0; +} + int get_sha1(const char *str, unsigned char *sha1) { - static char pathname[PATH_MAX]; static const char *prefix[] = { "", "refs", @@ -106,11 +136,8 @@ int get_sha1(const char *str, unsigned char *sha1) if (!get_sha1_hex(str, sha1)) return 0; - if (!git_dir) - setup_git_env(); for (p = prefix; *p; p++) { - snprintf(pathname, sizeof(pathname), "%s/%s/%s", - git_dir, *p, str); + char * pathname = git_path("%s/%s", *p, str); if (!get_sha1_file(pathname, sha1)) return 0; } @@ -173,10 +200,7 @@ char *sha1_file_name(const unsigned char *sha1) return base; } -static struct alternate_object_database { - char *base; - char *name; -} *alt_odb; +struct alternate_object_database *alt_odb; /* * Prepare alternate object database registry. @@ -194,13 +218,15 @@ static struct alternate_object_database { * pointed by base fields of the array elements with one xmalloc(); * the string pool immediately follows the array. */ -static void prepare_alt_odb(void) +void prepare_alt_odb(void) { int pass, totlen, i; const char *cp, *last; char *op = NULL; const char *alt = gitenv(ALTERNATE_DB_ENVIRONMENT) ? : ""; + if (alt_odb) + return; /* The first pass counts how large an area to allocate to * hold the entire alt_odb structure, including array of * structs and path buffers for them. The second pass fills @@ -247,8 +273,7 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st) if (!stat(name, st)) return name; - if (!alt_odb) - prepare_alt_odb(); + prepare_alt_odb(); for (i = 0; (name = alt_odb[i].name) != NULL; i++) { fill_sha1_path(name, sha1); if (!stat(alt_odb[i].base, st)) @@ -257,7 +282,198 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st) return NULL; } -int check_sha1_signature(unsigned char *sha1, void *map, unsigned long size, const char *type) +#define PACK_MAX_SZ (1<<26) +static int pack_used_ctr; +static unsigned long pack_mapped; +struct packed_git *packed_git; + +static int check_packed_git_idx(const char *path, unsigned long *idx_size_, + void **idx_map_) +{ + void *idx_map; + unsigned int *index; + unsigned long idx_size; + int nr, i; + int fd = open(path, O_RDONLY); + struct stat st; + if (fd < 0) + return -1; + if (fstat(fd, &st)) { + close(fd); + return -1; + } + idx_size = st.st_size; + idx_map = mmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (idx_map == MAP_FAILED) + return -1; + + index = idx_map; + *idx_map_ = idx_map; + *idx_size_ = idx_size; + + /* check index map */ + if (idx_size < 4*256 + 20 + 20) + return error("index file too small"); + nr = 0; + for (i = 0; i < 256; i++) { + unsigned int n = ntohl(index[i]); + if (n < nr) + return error("non-monotonic index"); + nr = n; + } + + /* + * Total size: + * - 256 index entries 4 bytes each + * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + */ + if (idx_size != 4*256 + nr * 24 + 20 + 20) + return error("wrong index file size"); + + return 0; +} + +static int unuse_one_packed_git(void) +{ + struct packed_git *p, *lru = NULL; + + for (p = packed_git; p; p = p->next) { + if (p->pack_use_cnt || !p->pack_base) + continue; + if (!lru || p->pack_last_used < lru->pack_last_used) + lru = p; + } + if (!lru) + return 0; + munmap(lru->pack_base, lru->pack_size); + lru->pack_base = NULL; + return 1; +} + +void unuse_packed_git(struct packed_git *p) +{ + p->pack_use_cnt--; +} + +int use_packed_git(struct packed_git *p) +{ + if (!p->pack_base) { + int fd; + struct stat st; + void *map; + + pack_mapped += p->pack_size; + while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git()) + ; /* nothing */ + fd = open(p->pack_name, O_RDONLY); + if (fd < 0) + die("packfile %s cannot be opened", p->pack_name); + if (fstat(fd, &st)) { + close(fd); + die("packfile %s cannot be opened", p->pack_name); + } + if (st.st_size != p->pack_size) + die("packfile %s size mismatch.", p->pack_name); + map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (map == MAP_FAILED) + die("packfile %s cannot be mapped.", p->pack_name); + p->pack_base = map; + + /* Check if the pack file matches with the index file. + * this is cheap. + */ + if (memcmp((char*)(p->index_base) + p->index_size - 40, + p->pack_base + p->pack_size - 20, 20)) + die("packfile %s does not match index.", p->pack_name); + } + p->pack_last_used = pack_used_ctr++; + p->pack_use_cnt++; + return 0; +} + +struct packed_git *add_packed_git(char *path, int path_len) +{ + struct stat st; + struct packed_git *p; + unsigned long idx_size; + void *idx_map; + + if (check_packed_git_idx(path, &idx_size, &idx_map)) + return NULL; + + /* do we have a corresponding .pack file? */ + strcpy(path + path_len - 4, ".pack"); + if (stat(path, &st) || !S_ISREG(st.st_mode)) { + munmap(idx_map, idx_size); + return NULL; + } + /* ok, it looks sane as far as we can check without + * actually mapping the pack file. + */ + p = xmalloc(sizeof(*p) + path_len + 2); + strcpy(p->pack_name, path); + p->index_size = idx_size; + p->pack_size = st.st_size; + p->index_base = idx_map; + p->next = NULL; + p->pack_base = NULL; + p->pack_last_used = 0; + p->pack_use_cnt = 0; + return p; +} + +static void prepare_packed_git_one(char *objdir) +{ + char path[PATH_MAX]; + int len; + DIR *dir; + struct dirent *de; + + sprintf(path, "%s/pack", objdir); + len = strlen(path); + dir = opendir(path); + if (!dir) + return; + path[len++] = '/'; + while ((de = readdir(dir)) != NULL) { + int namelen = strlen(de->d_name); + struct packed_git *p; + + if (strcmp(de->d_name + namelen - 4, ".idx")) + continue; + + /* we have .idx. Is it a file we can map? */ + strcpy(path + len, de->d_name); + p = add_packed_git(path, len + namelen); + if (!p) + continue; + p->next = packed_git; + packed_git = p; + } + closedir(dir); +} + +void prepare_packed_git(void) +{ + int i; + static int run_once = 0; + + if (run_once++) + return; + + prepare_packed_git_one(get_object_directory()); + prepare_alt_odb(); + for (i = 0; alt_odb[i].base != NULL; i++) { + alt_odb[i].name[0] = 0; + prepare_packed_git_one(alt_odb[i].base); + } +} + +int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type) { char header[100]; unsigned char real_sha1[20]; @@ -270,7 +486,8 @@ int check_sha1_signature(unsigned char *sha1, void *map, unsigned long size, con return memcmp(sha1, real_sha1, 20) ? -1 : 0; } -void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +static void *map_sha1_file_internal(const unsigned char *sha1, + unsigned long *size) { struct stat st; void *map; @@ -278,7 +495,6 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) char *filename = find_sha1_file(sha1, &st); if (!filename) { - error("cannot map sha1 file %s", sha1_to_hex(sha1)); return NULL; } @@ -292,11 +508,12 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) break; /* Fallthrough */ case 0: - perror(filename); return NULL; } - /* If it failed once, it will probably fail again. Stop using O_NOATIME */ + /* If it failed once, it will probably fail again. + * Stop using O_NOATIME + */ sha1_file_open_flag = 0; } map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -307,64 +524,525 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) return map; } +int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size) +{ + /* Get the data stream */ + memset(stream, 0, sizeof(*stream)); + stream->next_in = map; + stream->avail_in = mapsize; + stream->next_out = buffer; + stream->avail_out = size; + + inflateInit(stream); + return inflate(stream, 0); +} + +static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size) +{ + int bytes = strlen(buffer) + 1; + unsigned char *buf = xmalloc(1+size); + + memcpy(buf, buffer + bytes, stream->total_out - bytes); + bytes = stream->total_out - bytes; + if (bytes < size) { + stream->next_out = buf + bytes; + stream->avail_out = size - bytes; + while (inflate(stream, Z_FINISH) == Z_OK) + /* nothing */; + } + buf[size] = 0; + inflateEnd(stream); + return buf; +} + +/* + * We used to just use "sscanf()", but that's actually way + * too permissive for what we want to check. So do an anal + * object header parse by hand. + */ +int parse_sha1_header(char *hdr, char *type, unsigned long *sizep) +{ + int i; + unsigned long size; + + /* + * The type can be at most ten bytes (including the + * terminating '\0' that we add), and is followed by + * a space. + */ + i = 10; + for (;;) { + char c = *hdr++; + if (c == ' ') + break; + if (!--i) + return -1; + *type++ = c; + } + *type = 0; + + /* + * The length must follow immediately, and be in canonical + * decimal format (ie "010" is not valid). + */ + size = *hdr++ - '0'; + if (size > 9) + return -1; + if (size) { + for (;;) { + unsigned long c = *hdr - '0'; + if (c > 9) + break; + hdr++; + size = size * 10 + c; + } + } + *sizep = size; + + /* + * The length must be followed by a zero byte + */ + return *hdr ? -1 : 0; +} + void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size) { - int ret, bytes; + int ret; z_stream stream; - char buffer[8192]; - unsigned char *buf; + char hdr[8192]; - /* Get the data stream */ + ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)); + if (ret < Z_OK || parse_sha1_header(hdr, type, size) < 0) + return NULL; + + return unpack_sha1_rest(&stream, hdr, *size); +} + +/* forward declaration for a mutually recursive function */ +static int packed_object_info(struct pack_entry *entry, + char *type, unsigned long *sizep); + +static int packed_delta_info(unsigned char *base_sha1, + unsigned long delta_size, + unsigned long left, + char *type, + unsigned long *sizep, + struct packed_git *p) +{ + struct pack_entry base_ent; + + if (left < 20) + die("truncated pack file"); + + /* The base entry _must_ be in the same pack */ + if (!find_pack_entry_one(base_sha1, &base_ent, p)) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_sha1)); + + /* We choose to only get the type of the base object and + * ignore potentially corrupt pack file that expects the delta + * based on a base with a wrong size. This saves tons of + * inflate() calls. + */ + + if (packed_object_info(&base_ent, type, NULL)) + die("cannot get info for delta-pack base"); + + if (sizep) { + const unsigned char *data; + unsigned char delta_head[64]; + unsigned long result_size; + z_stream stream; + int st; + + memset(&stream, 0, sizeof(stream)); + + data = stream.next_in = base_sha1 + 20; + stream.avail_in = left - 20; + stream.next_out = delta_head; + stream.avail_out = sizeof(delta_head); + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) && + stream.total_out != sizeof(delta_head)) + die("delta data unpack-initial failed"); + + /* Examine the initial part of the delta to figure out + * the result size. + */ + data = delta_head; + get_delta_hdr_size(&data); /* ignore base size */ + + /* Read the result size */ + result_size = get_delta_hdr_size(&data); + *sizep = result_size; + } + return 0; +} + +static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset, + enum object_type *type, unsigned long *sizep) +{ + unsigned shift; + unsigned char *pack, c; + unsigned long size; + + if (offset >= p->pack_size) + die("object offset outside of pack file"); + + pack = p->pack_base + offset; + c = *pack++; + offset++; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (offset >= p->pack_size) + die("object offset outside of pack file"); + c = *pack++; + offset++; + size += (c & 0x7f) << shift; + shift += 7; + } + *sizep = size; + return offset; +} + +void packed_object_info_detail(struct pack_entry *e, + char *type, + unsigned long *size, + unsigned long *store_size, + int *delta_chain_length, + unsigned char *base_sha1) +{ + struct packed_git *p = e->p; + unsigned long offset, left; + unsigned char *pack; + enum object_type kind; + + offset = unpack_object_header(p, e->offset, &kind, size); + pack = p->pack_base + offset; + left = p->pack_size - offset; + if (kind != OBJ_DELTA) + *delta_chain_length = 0; + else { + int chain_length = 0; + memcpy(base_sha1, pack, 20); + do { + struct pack_entry base_ent; + unsigned long junk; + + find_pack_entry_one(pack, &base_ent, p); + offset = unpack_object_header(p, base_ent.offset, + &kind, &junk); + pack = p->pack_base + offset; + chain_length++; + } while (kind == OBJ_DELTA); + *delta_chain_length = chain_length; + } + switch (kind) { + case OBJ_COMMIT: + strcpy(type, "commit"); + break; + case OBJ_TREE: + strcpy(type, "tree"); + break; + case OBJ_BLOB: + strcpy(type, "blob"); + break; + case OBJ_TAG: + strcpy(type, "tag"); + break; + default: + die("corrupted pack file"); + } + *store_size = 0; /* notyet */ +} + +static int packed_object_info(struct pack_entry *entry, + char *type, unsigned long *sizep) +{ + struct packed_git *p = entry->p; + unsigned long offset, size, left; + unsigned char *pack; + enum object_type kind; + int retval; + + if (use_packed_git(p)) + die("cannot map packed file"); + + offset = unpack_object_header(p, entry->offset, &kind, &size); + pack = p->pack_base + offset; + left = p->pack_size - offset; + + switch (kind) { + case OBJ_DELTA: + retval = packed_delta_info(pack, size, left, type, sizep, p); + unuse_packed_git(p); + return retval; + case OBJ_COMMIT: + strcpy(type, "commit"); + break; + case OBJ_TREE: + strcpy(type, "tree"); + break; + case OBJ_BLOB: + strcpy(type, "blob"); + break; + case OBJ_TAG: + strcpy(type, "tag"); + break; + default: + die("corrupted pack file"); + } + if (sizep) + *sizep = size; + unuse_packed_git(p); + return 0; +} + +/* forward declaration for a mutually recursive function */ +static void *unpack_entry(struct pack_entry *, char *, unsigned long *); + +static void *unpack_delta_entry(unsigned char *base_sha1, + unsigned long delta_size, + unsigned long left, + char *type, + unsigned long *sizep, + struct packed_git *p) +{ + struct pack_entry base_ent; + void *data, *delta_data, *result, *base; + unsigned long data_size, result_size, base_size; + z_stream stream; + int st; + + if (left < 20) + die("truncated pack file"); + data = base_sha1 + 20; + data_size = left - 20; + delta_data = xmalloc(delta_size); + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = data; + stream.avail_in = data_size; + stream.next_out = delta_data; + stream.avail_out = delta_size; + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) || stream.total_out != delta_size) + die("delta data unpack failed"); + + /* The base entry _must_ be in the same pack */ + if (!find_pack_entry_one(base_sha1, &base_ent, p)) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_sha1)); + base = unpack_entry_gently(&base_ent, type, &base_size); + if (!base) + die("failed to read delta-pack base object %s", + sha1_to_hex(base_sha1)); + result = patch_delta(base, base_size, + delta_data, delta_size, + &result_size); + if (!result) + die("failed to apply delta"); + free(delta_data); + free(base); + *sizep = result_size; + return result; +} + +static void *unpack_non_delta_entry(unsigned char *data, + unsigned long size, + unsigned long left) +{ + int st; + z_stream stream; + unsigned char *buffer; + + buffer = xmalloc(size + 1); + buffer[size] = 0; memset(&stream, 0, sizeof(stream)); - stream.next_in = map; - stream.avail_in = mapsize; - stream.next_out = (unsigned char *)buffer; - stream.avail_out = sizeof(buffer); + stream.next_in = data; + stream.avail_in = left; + stream.next_out = buffer; + stream.avail_out = size; inflateInit(&stream); - ret = inflate(&stream, 0); - if (ret < Z_OK) + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) || stream.total_out != size) { + free(buffer); return NULL; - if (sscanf(buffer, "%10s %lu", type, size) != 2) + } + + return buffer; +} + +static void *unpack_entry(struct pack_entry *entry, + char *type, unsigned long *sizep) +{ + struct packed_git *p = entry->p; + void *retval; + + if (use_packed_git(p)) + die("cannot map packed file"); + retval = unpack_entry_gently(entry, type, sizep); + unuse_packed_git(p); + if (!retval) + die("corrupted pack file"); + return retval; +} + +/* The caller is responsible for use_packed_git()/unuse_packed_git() pair */ +void *unpack_entry_gently(struct pack_entry *entry, + char *type, unsigned long *sizep) +{ + struct packed_git *p = entry->p; + unsigned long offset, size, left; + unsigned char *pack; + enum object_type kind; + void *retval; + + offset = unpack_object_header(p, entry->offset, &kind, &size); + pack = p->pack_base + offset; + left = p->pack_size - offset; + switch (kind) { + case OBJ_DELTA: + retval = unpack_delta_entry(pack, size, left, type, sizep, p); + return retval; + case OBJ_COMMIT: + strcpy(type, "commit"); + break; + case OBJ_TREE: + strcpy(type, "tree"); + break; + case OBJ_BLOB: + strcpy(type, "blob"); + break; + case OBJ_TAG: + strcpy(type, "tag"); + break; + default: return NULL; + } + *sizep = size; + retval = unpack_non_delta_entry(pack, size, left); + return retval; +} - bytes = strlen(buffer) + 1; - buf = xmalloc(*size); +int num_packed_objects(const struct packed_git *p) +{ + /* See check_packed_git_idx() */ + return (p->index_size - 20 - 20 - 4*256) / 24; +} - memcpy(buf, buffer + bytes, stream.total_out - bytes); - bytes = stream.total_out - bytes; - if (bytes < *size && ret == Z_OK) { - stream.next_out = buf + bytes; - stream.avail_out = *size - bytes; - while (inflate(&stream, Z_FINISH) == Z_OK) - /* nothing */; +int nth_packed_object_sha1(const struct packed_git *p, int n, + unsigned char* sha1) +{ + void *index = p->index_base + 256; + if (n < 0 || num_packed_objects(p) <= n) + return -1; + memcpy(sha1, (index + 24 * n + 4), 20); + return 0; +} + +int find_pack_entry_one(const unsigned char *sha1, + struct pack_entry *e, struct packed_git *p) +{ + unsigned int *level1_ofs = p->index_base; + int hi = ntohl(level1_ofs[*sha1]); + int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1])); + void *index = p->index_base + 256; + + do { + int mi = (lo + hi) / 2; + int cmp = memcmp(index + 24 * mi + 4, sha1, 20); + if (!cmp) { + e->offset = ntohl(*((int*)(index + 24 * mi))); + memcpy(e->sha1, sha1, 20); + e->p = p; + return 1; + } + if (cmp > 0) + hi = mi; + else + lo = mi+1; + } while (lo < hi); + return 0; +} + +static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) +{ + struct packed_git *p; + prepare_packed_git(); + + for (p = packed_git; p; p = p->next) { + if (find_pack_entry_one(sha1, e, p)) + return 1; + } + return 0; +} + +int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) +{ + int status; + unsigned long mapsize, size; + void *map; + z_stream stream; + char hdr[128]; + + map = map_sha1_file_internal(sha1, &mapsize); + if (!map) { + struct pack_entry e; + + if (!find_pack_entry(sha1, &e)) + return error("unable to find %s", sha1_to_hex(sha1)); + return packed_object_info(&e, type, sizep); + } + if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) + status = error("unable to unpack %s header", + sha1_to_hex(sha1)); + if (parse_sha1_header(hdr, type, &size) < 0) + status = error("unable to parse %s header", sha1_to_hex(sha1)); + else { + status = 0; + if (sizep) + *sizep = size; } inflateEnd(&stream); - return buf; + munmap(map, mapsize); + return status; +} + +static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size) +{ + struct pack_entry e; + + if (!find_pack_entry(sha1, &e)) { + error("cannot read sha1_file for %s", sha1_to_hex(sha1)); + return NULL; + } + return unpack_entry(&e, type, size); } void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) { unsigned long mapsize; void *map, *buf; + struct pack_entry e; - map = map_sha1_file(sha1, &mapsize); + if (find_pack_entry(sha1, &e)) + return read_packed_sha1(sha1, type, size); + map = map_sha1_file_internal(sha1, &mapsize); if (map) { buf = unpack_sha1_file(map, mapsize, type, size); munmap(map, mapsize); - if (buf && !strcmp(type, "delta")) { - void *ref = NULL, *delta = buf; - unsigned long ref_size, delta_size = *size; - buf = NULL; - if (delta_size > 20) - ref = read_sha1_file(delta, type, &ref_size); - if (ref) - buf = patch_delta(ref, ref_size, - delta+20, delta_size-20, - size); - free(delta); - free(ref); - } return buf; } return NULL; @@ -415,31 +1093,46 @@ void *read_object_with_reference(const unsigned char *sha1, } } +char *write_sha1_file_prepare(void *buf, + unsigned long len, + const char *type, + unsigned char *sha1, + unsigned char *hdr, + int *hdrlen) +{ + SHA_CTX c; + + /* Generate the header */ + *hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1; + + /* Sha1.. */ + SHA1_Init(&c); + SHA1_Update(&c, hdr, *hdrlen); + SHA1_Update(&c, buf, len); + SHA1_Final(sha1, &c); + + return sha1_file_name(sha1); +} + int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; unsigned char *compressed; z_stream stream; unsigned char sha1[20]; - SHA_CTX c; char *filename; static char tmpfile[PATH_MAX]; unsigned char hdr[50]; int fd, hdrlen, ret; - /* Generate the header */ - hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1; - - /* Sha1.. */ - SHA1_Init(&c); - SHA1_Update(&c, hdr, hdrlen); - SHA1_Update(&c, buf, len); - SHA1_Final(sha1, &c); - + /* Normally if we have it in the pack then we do not bother writing + * it out into .git/objects/??/?{38} file. + */ + filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); if (returnsha1) memcpy(returnsha1, sha1, 20); - - filename = sha1_file_name(sha1); + if (has_sha1_file(sha1)) + return 0; fd = open(filename, O_RDONLY); if (fd >= 0) { /* @@ -521,6 +1214,65 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha return 0; } +int write_sha1_to_fd(int fd, const unsigned char *sha1) +{ + ssize_t size; + unsigned long objsize; + int posn = 0; + void *buf = map_sha1_file_internal(sha1, &objsize); + z_stream stream; + if (!buf) { + unsigned char *unpacked; + unsigned long len; + char type[20]; + char hdr[50]; + int hdrlen; + // need to unpack and recompress it by itself + unpacked = read_packed_sha1(sha1, type, &len); + + hdrlen = sprintf(hdr, "%s %lu", type, len) + 1; + + /* Set it up */ + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, Z_BEST_COMPRESSION); + size = deflateBound(&stream, len + hdrlen); + buf = xmalloc(size); + + /* Compress it */ + stream.next_out = buf; + stream.avail_out = size; + + /* First header.. */ + stream.next_in = (void *)hdr; + stream.avail_in = hdrlen; + while (deflate(&stream, 0) == Z_OK) + /* nothing */; + + /* Then the data itself.. */ + stream.next_in = unpacked; + stream.avail_in = len; + while (deflate(&stream, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&stream); + + objsize = stream.total_out; + } + + do { + size = write(fd, buf + posn, objsize - posn); + if (size <= 0) { + if (!size) { + fprintf(stderr, "write closed"); + } else { + perror("write "); + } + return -1; + } + posn += size; + } while (posn < objsize); + return 0; +} + int write_sha1_from_fd(const unsigned char *sha1, int fd) { char *filename = sha1_file_name(sha1); @@ -583,17 +1335,29 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd) return 0; } +int has_sha1_pack(const unsigned char *sha1) +{ + struct pack_entry e; + return find_pack_entry(sha1, &e); +} + int has_sha1_file(const unsigned char *sha1) { struct stat st; - return !!find_sha1_file(sha1, &st); + struct pack_entry e; + + if (find_pack_entry(sha1, &e)) + return 1; + return find_sha1_file(sha1, &st) ? 1 : 0; } -int index_fd(unsigned char *sha1, int fd, struct stat *st) +int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type) { unsigned long size = st->st_size; void *buf; int ret; + unsigned char hdr[50]; + int hdrlen; buf = ""; if (size) @@ -602,7 +1366,14 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st) if ((int)(long)buf == -1) return -1; - ret = write_sha1_file(buf, size, "blob", sha1); + if (!type) + type = "blob"; + if (write_object) + ret = write_sha1_file(buf, size, type, sha1); + else { + write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen); + ret = 0; + } if (size) munmap(buf, size); return ret;