* jc/boundary:
  git-bundle: prevent overwriting existing bundles
  git-bundle: die if a given ref is not included in bundle
  git-bundle: handle thin packs in subcommand "unbundle"
  git-bundle: Make thin packs
  git-bundle: avoid packing objects which are in the prerequisites
  bundle: fix wrong check of read_header()'s return value & add tests
  revision --boundary: fix uncounted case.
  revision --boundary: fix stupid typo
  git-bundle: make verify a bit more chatty.
  revision traversal: SHOWN means shown
  git-bundle: various fixups
  revision traversal: retire BOUNDARY_SHOW
  revision walker: Fix --boundary when limited
This commit is contained in:
Junio C Hamano 2007-03-11 23:02:52 -07:00
Родитель f43cd49fb8 2e578f9a4f
Коммит 2422f1ca3b
4 изменённых файлов: 243 добавлений и 197 удалений

Просмотреть файл

@ -160,7 +160,28 @@ static int fork_with_pipe(const char **argv, int *in, int *out)
return pid;
}
static int verify_bundle(struct bundle_header *header)
static int list_refs(struct ref_list *r, int argc, const char **argv)
{
int i;
for (i = 0; i < r->nr; i++) {
if (argc > 1) {
int j;
for (j = 1; j < argc; j++)
if (!strcmp(r->list[i].name, argv[j]))
break;
if (j == argc)
continue;
}
printf("%s %s\n", sha1_to_hex(r->list[i].sha1),
r->list[i].name);
}
return 0;
}
#define PREREQ_MARK (1u<<16)
static int verify_bundle(struct bundle_header *header, int verbose)
{
/*
* Do fast check, then if any prereqs are missing then go line by line
@ -179,7 +200,7 @@ static int verify_bundle(struct bundle_header *header)
struct ref_list_entry *e = p->list + i;
struct object *o = parse_object(e->sha1);
if (o) {
o->flags |= BOUNDARY_SHOW;
o->flags |= PREREQ_MARK;
add_pending_object(&revs, o, e->name);
continue;
}
@ -187,7 +208,7 @@ static int verify_bundle(struct bundle_header *header)
error(message);
error("%s %s", sha1_to_hex(e->sha1), e->name);
}
if (revs.pending.nr == 0)
if (revs.pending.nr != p->nr)
return ret;
req_nr = revs.pending.nr;
setup_revisions(2, argv, &revs, NULL);
@ -202,7 +223,7 @@ static int verify_bundle(struct bundle_header *header)
i = req_nr;
while (i && (commit = get_revision(&revs)))
if (commit->object.flags & BOUNDARY_SHOW)
if (commit->object.flags & PREREQ_MARK)
i--;
for (i = 0; i < req_nr; i++)
@ -216,56 +237,24 @@ static int verify_bundle(struct bundle_header *header)
for (i = 0; i < refs.nr; i++)
clear_commit_marks((struct commit *)refs.objects[i].item, -1);
if (verbose) {
struct ref_list *r;
r = &header->references;
printf("The bundle contains %d ref%s\n",
r->nr, (1 < r->nr) ? "s" : "");
list_refs(r, 0, NULL);
r = &header->prerequisites;
printf("The bundle requires these %d ref%s\n",
r->nr, (1 < r->nr) ? "s" : "");
list_refs(r, 0, NULL);
}
return ret;
}
static int list_heads(struct bundle_header *header, int argc, const char **argv)
{
int i;
struct ref_list *r = &header->references;
for (i = 0; i < r->nr; i++) {
if (argc > 1) {
int j;
for (j = 1; j < argc; j++)
if (!strcmp(r->list[i].name, argv[j]))
break;
if (j == argc)
continue;
}
printf("%s %s\n", sha1_to_hex(r->list[i].sha1),
r->list[i].name);
}
return 0;
}
static void show_commit(struct commit *commit)
{
write_or_die(1, sha1_to_hex(commit->object.sha1), 40);
write_or_die(1, "\n", 1);
if (commit->parents) {
free_commit_list(commit->parents);
commit->parents = NULL;
}
}
static void show_object(struct object_array_entry *p)
{
/* An object with name "foo\n0000000..." can be used to
* confuse downstream git-pack-objects very badly.
*/
const char *ep = strchr(p->name, '\n');
int len = ep ? ep - p->name : strlen(p->name);
write_or_die(1, sha1_to_hex(p->item->sha1), 40);
write_or_die(1, " ", 1);
if (len)
write_or_die(1, p->name, len);
write_or_die(1, "\n", 1);
}
static void show_edge(struct commit *commit)
{
; /* nothing to do */
return list_refs(&header->references, argc, argv);
}
static int create_bundle(struct bundle_header *header, const char *path,
@ -273,19 +262,23 @@ static int create_bundle(struct bundle_header *header, const char *path,
{
int bundle_fd = -1;
const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
const char **argv_pack = xmalloc(4 * sizeof(const char *));
const char **argv_pack = xmalloc(5 * sizeof(const char *));
int pid, in, out, i, status;
char buffer[1024];
struct rev_info revs;
bundle_fd = (!strcmp(path, "-") ? 1 :
open(path, O_CREAT | O_WRONLY, 0666));
open(path, O_CREAT | O_EXCL | O_WRONLY, 0666));
if (bundle_fd < 0)
return error("Could not write to '%s'", path);
return error("Could not create '%s': %s", path, strerror(errno));
/* write signature */
write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature));
/* init revs to list objects for pack-objects later */
save_commit_buffer = 0;
init_revisions(&revs, NULL);
/* write prerequisites */
memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *));
argv_boundary[0] = "rev-list";
@ -296,9 +289,20 @@ static int create_bundle(struct bundle_header *header, const char *path,
pid = fork_with_pipe(argv_boundary, NULL, &out);
if (pid < 0)
return -1;
while ((i = read_string(out, buffer, sizeof(buffer))) > 0)
if (buffer[0] == '-')
while ((i = read_string(out, buffer, sizeof(buffer))) > 0) {
unsigned char sha1[20];
if (buffer[0] == '-') {
write_or_die(bundle_fd, buffer, i);
if (!get_sha1_hex(buffer + 1, sha1)) {
struct object *object = parse_object(sha1);
object->flags |= UNINTERESTING;
add_pending_object(&revs, object, buffer);
}
} else if (!get_sha1_hex(buffer, sha1)) {
struct object *object = parse_object(sha1);
object->flags |= SHOWN;
}
}
while ((i = waitpid(pid, &status, 0)) < 0)
if (errno != EINTR)
return error("rev-list died");
@ -306,27 +310,32 @@ static int create_bundle(struct bundle_header *header, const char *path,
return error("rev-list died %d", WEXITSTATUS(status));
/* write references */
save_commit_buffer = 0;
init_revisions(&revs, NULL);
revs.tag_objects = 1;
revs.tree_objects = 1;
revs.blob_objects = 1;
argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1)
return error("unrecognized argument: %s'", argv[1]);
for (i = 0; i < revs.pending.nr; i++) {
struct object_array_entry *e = revs.pending.objects + i;
if (!(e->item->flags & UNINTERESTING)) {
unsigned char sha1[20];
char *ref;
if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
continue;
write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40);
write_or_die(bundle_fd, " ", 1);
write_or_die(bundle_fd, ref, strlen(ref));
write_or_die(bundle_fd, "\n", 1);
free(ref);
}
unsigned char sha1[20];
char *ref;
if (e->item->flags & UNINTERESTING)
continue;
if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
continue;
/*
* Make sure the refs we wrote out is correct; --max-count and
* other limiting options could have prevented all the tips
* from getting output.
*/
if (!(e->item->flags & SHOWN))
die("ref '%s' is excluded by the rev-list options",
e->name);
write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40);
write_or_die(bundle_fd, " ", 1);
write_or_die(bundle_fd, ref, strlen(ref));
write_or_die(bundle_fd, "\n", 1);
free(ref);
}
/* end header */
@ -336,36 +345,42 @@ static int create_bundle(struct bundle_header *header, const char *path,
argv_pack[0] = "pack-objects";
argv_pack[1] = "--all-progress";
argv_pack[2] = "--stdout";
argv_pack[3] = NULL;
argv_pack[3] = "--thin";
argv_pack[4] = NULL;
in = -1;
out = bundle_fd;
pid = fork_with_pipe(argv_pack, &in, &out);
if (pid < 0)
return error("Could not spawn pack-objects");
close(1);
dup2(in, 1);
for (i = 0; i < revs.pending.nr; i++) {
struct object *object = revs.pending.objects[i].item;
if (object->flags & UNINTERESTING)
write(in, "^", 1);
write(in, sha1_to_hex(object->sha1), 40);
write(in, "\n", 1);
}
close(in);
prepare_revision_walk(&revs);
mark_edges_uninteresting(revs.commits, &revs, show_edge);
traverse_commit_list(&revs, show_commit, show_object);
close(1);
while (waitpid(pid, &status, 0) < 0)
if (errno != EINTR)
return -1;
if (!WIFEXITED(status) || WEXITSTATUS(status))
return error ("pack-objects died");
return 0;
return status;
}
static int unbundle(struct bundle_header *header, int bundle_fd,
int argc, const char **argv)
{
const char *argv_index_pack[] = {"index-pack", "--stdin", NULL};
const char *argv_index_pack[] = {"index-pack",
"--fix-thin", "--stdin", NULL};
int pid, status, dev_null;
if (verify_bundle(header))
if (verify_bundle(header, 0))
return -1;
dev_null = open("/dev/null", O_WRONLY);
if (dev_null < 0)
return error("Could not open /dev/null");
pid = fork_with_pipe(argv_index_pack, &bundle_fd, &dev_null);
if (pid < 0)
return error("Could not spawn index-pack");
@ -402,12 +417,12 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
memset(&header, 0, sizeof(header));
if (strcmp(cmd, "create") &&
!(bundle_fd = read_header(bundle_file, &header)))
(bundle_fd = read_header(bundle_file, &header)) < 0)
return 1;
if (!strcmp(cmd, "verify")) {
close(bundle_fd);
if (verify_bundle(&header))
if (verify_bundle(&header, 1))
return 1;
fprintf(stderr, "%s is okay\n", bundle_file);
return 0;
@ -427,4 +442,3 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
} else
usage(bundle_usage);
}

Просмотреть файл

@ -437,36 +437,6 @@ static void limit_list(struct rev_info *revs)
continue;
p = &commit_list_insert(commit, p)->next;
}
if (revs->boundary) {
/* mark the ones that are on the result list first */
for (list = newlist; list; list = list->next) {
struct commit *commit = list->item;
commit->object.flags |= TMP_MARK;
}
for (list = newlist; list; list = list->next) {
struct commit *commit = list->item;
struct object *obj = &commit->object;
struct commit_list *parent;
if (obj->flags & UNINTERESTING)
continue;
for (parent = commit->parents;
parent;
parent = parent->next) {
struct commit *pcommit = parent->item;
if (!(pcommit->object.flags & UNINTERESTING))
continue;
pcommit->object.flags |= BOUNDARY;
if (pcommit->object.flags & TMP_MARK)
continue;
pcommit->object.flags |= TMP_MARK;
p = &commit_list_insert(pcommit, p)->next;
}
}
for (list = newlist; list; list = list->next) {
struct commit *commit = list->item;
commit->object.flags &= ~TMP_MARK;
}
}
revs->commits = newlist;
}
@ -1193,17 +1163,6 @@ static void rewrite_parents(struct rev_info *revs, struct commit *commit)
}
}
static void mark_boundary_to_show(struct commit *commit)
{
struct commit_list *p = commit->parents;
while (p) {
commit = p->item;
p = p->next;
if (commit->object.flags & BOUNDARY)
commit->object.flags |= BOUNDARY_SHOW;
}
}
static int commit_match(struct commit *commit, struct rev_info *opt)
{
if (!opt->grep_filter)
@ -1235,15 +1194,9 @@ static struct commit *get_revision_1(struct rev_info *revs)
*/
if (!revs->limited) {
if (revs->max_age != -1 &&
(commit->date < revs->max_age)) {
if (revs->boundary)
commit->object.flags |=
BOUNDARY_SHOW | BOUNDARY;
else
continue;
} else
add_parents_to_list(revs, commit,
&revs->commits);
(commit->date < revs->max_age))
continue;
add_parents_to_list(revs, commit, &revs->commits);
}
if (commit->object.flags & SHOWN)
continue;
@ -1252,18 +1205,6 @@ static struct commit *get_revision_1(struct rev_info *revs)
revs->ignore_packed))
continue;
/* We want to show boundary commits only when their
* children are shown. When path-limiter is in effect,
* rewrite_parents() drops some commits from getting shown,
* and there is no point showing boundary parents that
* are not shown. After rewrite_parents() rewrites the
* parents of a commit that is shown, we mark the boundary
* parents with BOUNDARY_SHOW.
*/
if (commit->object.flags & BOUNDARY_SHOW) {
commit->object.flags |= SHOWN;
return commit;
}
if (commit->object.flags & UNINTERESTING)
continue;
if (revs->min_age != -1 && (commit->date > revs->min_age))
@ -1286,80 +1227,136 @@ static struct commit *get_revision_1(struct rev_info *revs)
if (revs->parents)
rewrite_parents(revs, commit);
}
if (revs->boundary)
mark_boundary_to_show(commit);
commit->object.flags |= SHOWN;
return commit;
} while (revs->commits);
return NULL;
}
static void gc_boundary(struct object_array *array)
{
unsigned nr = array->nr;
unsigned alloc = array->alloc;
struct object_array_entry *objects = array->objects;
if (alloc <= nr) {
unsigned i, j;
for (i = j = 0; i < nr; i++) {
if (objects[i].item->flags & SHOWN)
continue;
if (i != j)
objects[j] = objects[i];
j++;
}
for (i = j; i < nr; i++)
objects[i].item = NULL;
array->nr = j;
}
}
struct commit *get_revision(struct rev_info *revs)
{
struct commit *c = NULL;
struct commit_list *l;
if (revs->reverse) {
struct commit_list *list;
/*
* rev_info.reverse is used to note the fact that we
* want to output the list of revisions in reverse
* order. To accomplish this goal, reverse can have
* different values:
*
* 0 do nothing
* 1 reverse the list
* 2 internal use: we have already obtained and
* reversed the list, now we only need to yield
* its items.
*/
if (revs->reverse == 1) {
revs->reverse = 0;
list = NULL;
while ((c = get_revision(revs)))
commit_list_insert(c, &list);
revs->commits = list;
revs->reverse = 2;
if (revs->boundary == 2) {
unsigned i;
struct object_array *array = &revs->boundary_commits;
struct object_array_entry *objects = array->objects;
for (i = 0; i < array->nr; i++) {
c = (struct commit *)(objects[i].item);
if (!c)
continue;
if (!(c->object.flags & CHILD_SHOWN))
continue;
if (!(c->object.flags & SHOWN))
break;
}
if (!revs->commits)
if (array->nr <= i)
return NULL;
c = revs->commits->item;
list = revs->commits->next;
free(revs->commits);
revs->commits = list;
c->object.flags |= SHOWN | BOUNDARY;
return c;
}
if (0 < revs->skip_count) {
while ((c = get_revision_1(revs)) != NULL) {
if (revs->skip_count-- <= 0)
if (revs->reverse) {
int limit = -1;
if (0 <= revs->max_count) {
limit = revs->max_count;
if (0 < revs->skip_count)
limit += revs->skip_count;
}
l = NULL;
while ((c = get_revision_1(revs))) {
commit_list_insert(c, &l);
if ((0 < limit) && !--limit)
break;
}
revs->commits = l;
revs->reverse = 0;
revs->max_count = -1;
c = NULL;
}
/*
* Now pick up what they want to give us
*/
c = get_revision_1(revs);
if (c) {
while (0 < revs->skip_count) {
revs->skip_count--;
c = get_revision_1(revs);
if (!c)
break;
}
}
/* Check the max_count ... */
/*
* Check the max_count.
*/
switch (revs->max_count) {
case -1:
break;
case 0:
if (revs->boundary) {
struct commit_list *list = revs->commits;
while (list) {
list->item->object.flags |=
BOUNDARY_SHOW | BOUNDARY;
list = list->next;
}
/* all remaining commits are boundary commits */
revs->max_count = -1;
revs->limited = 1;
} else
return NULL;
c = NULL;
break;
default:
revs->max_count--;
}
if (c)
c->object.flags |= SHOWN;
if (!revs->boundary) {
return c;
return get_revision_1(revs);
}
if (!c) {
/*
* get_revision_1() runs out the commits, and
* we are done computing the boundaries.
* switch to boundary commits output mode.
*/
revs->boundary = 2;
return get_revision(revs);
}
/*
* boundary commits are the commits that are parents of the
* ones we got from get_revision_1() but they themselves are
* not returned from get_revision_1(). Before returning
* 'c', we need to mark its parents that they could be boundaries.
*/
for (l = c->parents; l; l = l->next) {
struct object *p;
p = &(l->item->object);
if (p->flags & (CHILD_SHOWN | SHOWN))
continue;
p->flags |= CHILD_SHOWN;
gc_boundary(&revs->boundary_commits);
add_object_array(p, NULL, &revs->boundary_commits);
}
return c;
}

Просмотреть файл

@ -7,7 +7,7 @@
#define SHOWN (1u<<3)
#define TMP_MARK (1u<<4) /* for isolated cases; clean after use */
#define BOUNDARY (1u<<5)
#define BOUNDARY_SHOW (1u<<6)
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
@ -21,6 +21,9 @@ struct rev_info {
struct commit_list *commits;
struct object_array pending;
/* Parents of shown commits */
struct object_array boundary_commits;
/* Basic information */
const char *prefix;
void *prune_data;
@ -40,10 +43,10 @@ struct rev_info {
edge_hint:1,
limited:1,
unpacked:1, /* see also ignore_packed below */
boundary:1,
boundary:2,
left_right:1,
parents:1,
reverse:2;
reverse:1;
/* Diff flags */
unsigned int diff:1,

Просмотреть файл

@ -90,6 +90,13 @@ test_expect_success 'create bundle 1' '
git bundle create bundle1 master^..master
'
test_expect_success 'header of bundle looks right' '
head -n 1 "$D"/bundle1 | grep "^#" &&
head -n 2 "$D"/bundle1 | grep "^-[0-9a-f]\{40\} " &&
head -n 3 "$D"/bundle1 | grep "^[0-9a-f]\{40\} " &&
head -n 4 "$D"/bundle1 | grep "^$"
'
test_expect_success 'create bundle 2' '
cd "$D" &&
git bundle create bundle2 master~2..master
@ -101,10 +108,35 @@ test_expect_failure 'unbundle 1' '
git fetch "$D/bundle1" master:master
'
test_expect_success 'bundle 1 has only 3 files ' '
cd "$D" &&
(
while read x && test -n "$x"
do
:;
done
cat
) <bundle1 >bundle.pack &&
git index-pack bundle.pack &&
verify=$(git verify-pack -v bundle.pack) &&
test 4 = $(echo "$verify" | wc -l)
'
test_expect_success 'unbundle 2' '
cd "$D/bundle" &&
git fetch ../bundle2 master:master &&
test "tip" = "$(git log -1 --pretty=oneline master | cut -b42-)"
'
test_expect_success 'bundle does not prerequisite objects' '
cd "$D" &&
touch file2 &&
git add file2 &&
git commit -m add.file2 file2 &&
git bundle create bundle3 -1 HEAD &&
sed "1,4d" < bundle3 > bundle.pack &&
git index-pack bundle.pack &&
test 4 = $(git verify-pack -v bundle.pack | wc -l)
'
test_done