| #include "refs.h" |
| #include "cache.h" |
| #include "rev-cache.h" |
| |
| struct rev_cache **rev_cache; |
| int nr_revs, alloc_revs; |
| |
| static struct rev_list_elem *rle_free; |
| |
| #define BATCH_SIZE 512 |
| |
| int find_rev_cache(const unsigned char *sha1) |
| { |
| int lo = 0, hi = nr_revs; |
| while (lo < hi) { |
| int mi = (lo + hi) / 2; |
| struct rev_cache *ri = rev_cache[mi]; |
| int cmp = memcmp(sha1, ri->sha1, 20); |
| if (!cmp) |
| return mi; |
| if (cmp < 0) |
| hi = mi; |
| else |
| lo = mi + 1; |
| } |
| return -lo - 1; |
| } |
| |
| static struct rev_list_elem *alloc_list_elem(void) |
| { |
| struct rev_list_elem *rle; |
| if (!rle_free) { |
| int i; |
| |
| rle = xmalloc(sizeof(*rle) * BATCH_SIZE); |
| for (i = 0; i < BATCH_SIZE - 1; i++) { |
| rle[i].ri = NULL; |
| rle[i].next = &rle[i + 1]; |
| } |
| rle[BATCH_SIZE - 1].ri = NULL; |
| rle[BATCH_SIZE - 1].next = NULL; |
| rle_free = rle; |
| } |
| rle = rle_free; |
| rle_free = rle->next; |
| return rle; |
| } |
| |
| static struct rev_cache *create_rev_cache(const unsigned char *sha1) |
| { |
| struct rev_cache *ri; |
| int pos = find_rev_cache(sha1); |
| |
| if (0 <= pos) |
| return rev_cache[pos]; |
| pos = -pos - 1; |
| if (alloc_revs <= ++nr_revs) { |
| alloc_revs = alloc_nr(alloc_revs); |
| rev_cache = xrealloc(rev_cache, sizeof(ri) * alloc_revs); |
| } |
| if (pos < nr_revs) |
| memmove(rev_cache + pos + 1, rev_cache + pos, |
| (nr_revs - pos - 1) * sizeof(ri)); |
| ri = xcalloc(1, sizeof(*ri)); |
| memcpy(ri->sha1, sha1, 20); |
| rev_cache[pos] = ri; |
| return ri; |
| } |
| |
| static unsigned char last_sha1[20]; |
| |
| static void write_one_rev_cache(FILE *rev_cache_file, struct rev_cache *ri) |
| { |
| unsigned char flag; |
| struct rev_list_elem *rle; |
| |
| if (ri->written) |
| return; |
| |
| if (ri->parsed) { |
| /* We use last_sha1 compression only for the first parent; |
| * otherwise the resulting rev-cache would lose the parent |
| * order information. |
| */ |
| if (ri->parents && |
| !memcmp(ri->parents->ri->sha1, last_sha1, 20)) |
| flag = (ri->num_parents - 1) | 0x80; |
| else |
| flag = ri->num_parents; |
| |
| fwrite(ri->sha1, 20, 1, rev_cache_file); |
| fwrite(&flag, 1, 1, rev_cache_file); |
| for (rle = ri->parents; rle; rle = rle->next) { |
| if (flag & 0x80 && rle == ri->parents) |
| continue; |
| fwrite(rle->ri->sha1, 20, 1, rev_cache_file); |
| } |
| memcpy(last_sha1, ri->sha1, 20); |
| ri->written = 1; |
| } |
| /* recursively write children depth first */ |
| for (rle = ri->children; rle; rle = rle->next) |
| write_one_rev_cache(rev_cache_file, rle->ri); |
| } |
| |
| void write_rev_cache(const char *newpath, const char *oldpath) |
| { |
| /* write the following commit ancestry information in |
| * $GIT_DIR/info/rev-cache. |
| * |
| * The format is: |
| * 20-byte SHA1 (commit ID) |
| * 1-byte flag: |
| * - bit 0-6 records "number of parent commit SHA1s to |
| * follow" (i.e. up to 127 children can be listed). |
| * - when the bit 7 is on, then "the entry immediately |
| * before this entry is one of the parents of this |
| * commit". |
| * N x 20-byte SHA1 (parent commit IDs) |
| */ |
| FILE *rev_cache_file; |
| int i; |
| struct rev_cache *ri; |
| |
| if (!strcmp(newpath, oldpath)) { |
| /* If we are doing it in place */ |
| rev_cache_file = fopen(newpath, "a"); |
| } |
| else { |
| char buf[8096]; |
| size_t sz; |
| FILE *oldfp = fopen(oldpath, "r"); |
| rev_cache_file = fopen(newpath, "w"); |
| if (oldfp) { |
| while (1) { |
| sz = fread(buf, 1, sizeof(buf), oldfp); |
| if (sz == 0) |
| break; |
| fwrite(buf, 1, sz, rev_cache_file); |
| } |
| fclose(oldfp); |
| } |
| } |
| |
| memset(last_sha1, 0, 20); |
| |
| /* Go through available rev_cache structures, starting from |
| * parentless ones first, so that we would get most out of |
| * last_sha1 optimization by the depth first behaviour of |
| * write_one_rev_cache(). |
| */ |
| for (i = 0; i < nr_revs; i++) { |
| ri = rev_cache[i]; |
| if (ri->num_parents) |
| continue; |
| write_one_rev_cache(rev_cache_file, ri); |
| } |
| /* Then the rest */ |
| for (i = 0; i < nr_revs; i++) { |
| ri = rev_cache[i]; |
| write_one_rev_cache(rev_cache_file, ri); |
| } |
| fclose(rev_cache_file); |
| } |
| |
| static void add_parent(struct rev_cache *child, |
| const unsigned char *parent_sha1) |
| { |
| struct rev_cache *parent = create_rev_cache(parent_sha1); |
| struct rev_list_elem *e = alloc_list_elem(); |
| |
| /* Keep the parent list ordered in the same way the commit |
| * object records them. |
| */ |
| e->ri = parent; |
| e->next = NULL; |
| if (!child->parents_tail) |
| child->parents = e; |
| else |
| child->parents_tail->next = e; |
| child->parents_tail = e; |
| child->num_parents++; |
| |
| /* There is no inherent order of the children so we just |
| * LIFO them together. |
| */ |
| e = alloc_list_elem(); |
| e->next = parent->children; |
| parent->children = e; |
| e->ri = child; |
| parent->num_children++; |
| } |
| |
| int read_rev_cache(const char *path, FILE *dumpfile, int dry_run) |
| { |
| unsigned char *map; |
| int fd; |
| struct stat st; |
| unsigned long ofs, len; |
| struct rev_cache *ri = NULL; |
| |
| fd = open(path, O_RDONLY); |
| if (fd < 0) { |
| if (dry_run) |
| return error("cannot open %s", path); |
| if (errno == ENOENT) |
| return 0; |
| return -1; |
| } |
| if (fstat(fd, &st)) { |
| close(fd); |
| return -1; |
| } |
| map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
| close(fd); |
| if (map == MAP_FAILED) |
| return -1; |
| |
| memset(last_sha1, 0, 20); |
| ofs = 0; |
| len = st.st_size; |
| while (ofs < len) { |
| unsigned char sha1[20]; |
| int flag, cnt, i; |
| if (len < ofs + 21) |
| die("rev-cache too short"); |
| memcpy(sha1, map + ofs, 20); |
| flag = map[ofs + 20]; |
| ofs += 21; |
| cnt = (flag & 0x7f) + ((flag & 0x80) != 0); |
| if (len < ofs + (flag & 0x7f) * 20) |
| die("rev-cache too short to have %d more parents", |
| (flag & 0x7f)); |
| if (dumpfile) |
| fprintf(dumpfile, "%s", sha1_to_hex(sha1)); |
| if (!dry_run) { |
| ri = create_rev_cache(sha1); |
| if (!ri) |
| die("cannot create rev-cache for %s", |
| sha1_to_hex(sha1)); |
| ri->written = ri->parsed = 1; |
| } |
| i = 0; |
| if (flag & 0x80) { |
| if (!dry_run) |
| add_parent(ri, last_sha1); |
| if (dumpfile) |
| fprintf(dumpfile, " %s", |
| sha1_to_hex(last_sha1)); |
| i++; |
| } |
| while (i++ < cnt) { |
| if (!dry_run) |
| add_parent(ri, map + ofs); |
| if (dumpfile) |
| fprintf(dumpfile, " %s", |
| sha1_to_hex(last_sha1)); |
| ofs += 20; |
| } |
| if (dumpfile) |
| fprintf(dumpfile, "\n"); |
| memcpy(last_sha1, sha1, 20); |
| } |
| if (ofs != len) |
| die("rev-cache truncated?"); |
| munmap(map, len); |
| return 0; |
| } |
| |
| int record_rev_cache(const unsigned char *sha1, FILE *dumpfile) |
| { |
| unsigned char parent[20]; |
| char type[20]; |
| unsigned long size, ofs; |
| unsigned int cnt, i; |
| void *buf; |
| struct rev_cache *ri; |
| |
| buf = read_sha1_file(sha1, type, &size); |
| if (!buf) |
| return error("%s: not found", sha1_to_hex(sha1)); |
| if (strcmp(type, "commit")) { |
| free(buf); |
| return error("%s: not a commit but a %s", |
| sha1_to_hex(sha1), type); |
| } |
| ri = create_rev_cache(sha1); |
| if (ri->parsed) |
| return 0; |
| if (dumpfile) |
| fprintf(dumpfile, "commit %s\n", sha1_to_hex(sha1)); |
| |
| cnt = 0; |
| ofs = 46; /* "tree " + hex-sha1 + "\n" */ |
| while (!memcmp(buf + ofs, "parent ", 7) && |
| !get_sha1_hex(buf + ofs + 7, parent)) { |
| ofs += 48; |
| cnt++; |
| } |
| if (cnt * 48 + 46 != ofs) { |
| free(buf); |
| die("internal error in record_rev_cache"); |
| } |
| |
| ri = create_rev_cache(sha1); |
| ri->parsed = 1; |
| |
| for (i = 0; i < cnt; i++) { |
| unsigned char parent_sha1[20]; |
| |
| ofs = 46 + i * 48 + 7; |
| get_sha1_hex(buf + ofs, parent_sha1); |
| add_parent(ri, parent_sha1); |
| record_rev_cache(parent_sha1, dumpfile); |
| } |
| free(buf); |
| return 0; |
| } |