You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
318 lines
7.0 KiB
318 lines
7.0 KiB
#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; |
|
}
|
|
|