[PATCH] Add update-server-info.
The git-update-server-info command prepares informational files
to help clients discover the contents of a repository, and pull
from it via a dumb transport protocols. Currently, the
following files are produced.
- The $repo/info/refs file lists the name of heads and tags
available in the $repo/refs/ directory, along with their
SHA1. This can be used by git-ls-remote command running on
the client side.
- The $repo/info/rev-cache file describes the commit ancestry
reachable from references in the $repo/refs/ directory. This
file is in an append-only binary format to make the server
side friendly to rsync mirroring scheme, and can be read by
git-show-rev-cache command.
- The $repo/objects/info/pack file lists the name of the packs
available, the interdependencies among them, and the head
commits and tags contained in them. Along with the other two
files, this is designed to help clients to make smart pull
decisions.
The git-receive-pack command is changed to invoke it at the end,
so just after a push to a public repository finishes via "git
push", the server info is automatically updated.
In addition, building of the rev-cache file can be done by a
standalone git-build-rev-cache command separately.
Signed-off-by: Junio C Hamano <junkio@cox.net>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-07-24 02:54:41 +02:00
|
|
|
#include "refs.h"
|
|
|
|
#include "cache.h"
|
|
|
|
#include "rev-cache.h"
|
|
|
|
|
|
|
|
struct rev_cache **rev_cache;
|
|
|
|
int nr_revs, alloc_revs;
|
|
|
|
|
|
|
|
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);
|
2005-07-29 16:49:14 +02:00
|
|
|
if (map == MAP_FAILED)
|
|
|
|
return -1;
|
[PATCH] Add update-server-info.
The git-update-server-info command prepares informational files
to help clients discover the contents of a repository, and pull
from it via a dumb transport protocols. Currently, the
following files are produced.
- The $repo/info/refs file lists the name of heads and tags
available in the $repo/refs/ directory, along with their
SHA1. This can be used by git-ls-remote command running on
the client side.
- The $repo/info/rev-cache file describes the commit ancestry
reachable from references in the $repo/refs/ directory. This
file is in an append-only binary format to make the server
side friendly to rsync mirroring scheme, and can be read by
git-show-rev-cache command.
- The $repo/objects/info/pack file lists the name of the packs
available, the interdependencies among them, and the head
commits and tags contained in them. Along with the other two
files, this is designed to help clients to make smart pull
decisions.
The git-receive-pack command is changed to invoke it at the end,
so just after a push to a public repository finishes via "git
push", the server info is automatically updated.
In addition, building of the rev-cache file can be done by a
standalone git-build-rev-cache command separately.
Signed-off-by: Junio C Hamano <junkio@cox.net>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-07-24 02:54:41 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|