| #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; | |
| } |