Merge branch 'jc/reflog'
* jc/reflog: reflog --fix-stale: do not check the same trees and commits repeatedly. reflog expire --fix-stale Move traversal of reachable objects into a separate library. builtin-prune: separate ref walking from reflog walking. builtin-prune: make file-scope static struct to an argument.
This commit is contained in:
commit
c326246acc
1
Makefile
1
Makefile
@ -251,6 +251,7 @@ LIB_OBJS = \
|
|||||||
interpolate.o \
|
interpolate.o \
|
||||||
lockfile.o \
|
lockfile.o \
|
||||||
object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \
|
object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \
|
||||||
|
reachable.o \
|
||||||
quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
|
quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
|
||||||
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
|
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
|
||||||
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
|
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
|
||||||
|
187
builtin-prune.c
187
builtin-prune.c
@ -1,18 +1,12 @@
|
|||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "refs.h"
|
|
||||||
#include "tag.h"
|
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
#include "tree.h"
|
|
||||||
#include "blob.h"
|
|
||||||
#include "tree-walk.h"
|
|
||||||
#include "diff.h"
|
#include "diff.h"
|
||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "cache-tree.h"
|
#include "reachable.h"
|
||||||
|
|
||||||
static const char prune_usage[] = "git-prune [-n]";
|
static const char prune_usage[] = "git-prune [-n]";
|
||||||
static int show_only;
|
static int show_only;
|
||||||
static struct rev_info revs;
|
|
||||||
|
|
||||||
static int prune_object(char *path, const char *filename, const unsigned char *sha1)
|
static int prune_object(char *path, const char *filename, const unsigned char *sha1)
|
||||||
{
|
{
|
||||||
@ -85,164 +79,10 @@ static void prune_object_dir(const char *path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void process_blob(struct blob *blob,
|
|
||||||
struct object_array *p,
|
|
||||||
struct name_path *path,
|
|
||||||
const char *name)
|
|
||||||
{
|
|
||||||
struct object *obj = &blob->object;
|
|
||||||
|
|
||||||
if (obj->flags & SEEN)
|
|
||||||
return;
|
|
||||||
obj->flags |= SEEN;
|
|
||||||
/* Nothing to do, really .. The blob lookup was the important part */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_tree(struct tree *tree,
|
|
||||||
struct object_array *p,
|
|
||||||
struct name_path *path,
|
|
||||||
const char *name)
|
|
||||||
{
|
|
||||||
struct object *obj = &tree->object;
|
|
||||||
struct tree_desc desc;
|
|
||||||
struct name_entry entry;
|
|
||||||
struct name_path me;
|
|
||||||
|
|
||||||
if (obj->flags & SEEN)
|
|
||||||
return;
|
|
||||||
obj->flags |= SEEN;
|
|
||||||
if (parse_tree(tree) < 0)
|
|
||||||
die("bad tree object %s", sha1_to_hex(obj->sha1));
|
|
||||||
name = xstrdup(name);
|
|
||||||
add_object(obj, p, path, name);
|
|
||||||
me.up = path;
|
|
||||||
me.elem = name;
|
|
||||||
me.elem_len = strlen(name);
|
|
||||||
|
|
||||||
desc.buf = tree->buffer;
|
|
||||||
desc.size = tree->size;
|
|
||||||
|
|
||||||
while (tree_entry(&desc, &entry)) {
|
|
||||||
if (S_ISDIR(entry.mode))
|
|
||||||
process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
|
|
||||||
else
|
|
||||||
process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
|
|
||||||
}
|
|
||||||
free(tree->buffer);
|
|
||||||
tree->buffer = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_tag(struct tag *tag, struct object_array *p, const char *name)
|
|
||||||
{
|
|
||||||
struct object *obj = &tag->object;
|
|
||||||
struct name_path me;
|
|
||||||
|
|
||||||
if (obj->flags & SEEN)
|
|
||||||
return;
|
|
||||||
obj->flags |= SEEN;
|
|
||||||
|
|
||||||
me.up = NULL;
|
|
||||||
me.elem = "tag:/";
|
|
||||||
me.elem_len = 5;
|
|
||||||
|
|
||||||
if (parse_tag(tag) < 0)
|
|
||||||
die("bad tag object %s", sha1_to_hex(obj->sha1));
|
|
||||||
add_object(tag->tagged, p, NULL, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void walk_commit_list(struct rev_info *revs)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
struct commit *commit;
|
|
||||||
struct object_array objects = { 0, 0, NULL };
|
|
||||||
|
|
||||||
/* Walk all commits, process their trees */
|
|
||||||
while ((commit = get_revision(revs)) != NULL)
|
|
||||||
process_tree(commit->tree, &objects, NULL, "");
|
|
||||||
|
|
||||||
/* Then walk all the pending objects, recursively processing them too */
|
|
||||||
for (i = 0; i < revs->pending.nr; i++) {
|
|
||||||
struct object_array_entry *pending = revs->pending.objects + i;
|
|
||||||
struct object *obj = pending->item;
|
|
||||||
const char *name = pending->name;
|
|
||||||
if (obj->type == OBJ_TAG) {
|
|
||||||
process_tag((struct tag *) obj, &objects, name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (obj->type == OBJ_TREE) {
|
|
||||||
process_tree((struct tree *)obj, &objects, NULL, name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (obj->type == OBJ_BLOB) {
|
|
||||||
process_blob((struct blob *)obj, &objects, NULL, name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data)
|
|
||||||
{
|
|
||||||
struct object *object;
|
|
||||||
|
|
||||||
object = parse_object(osha1);
|
|
||||||
if (object)
|
|
||||||
add_pending_object(&revs, object, "");
|
|
||||||
object = parse_object(nsha1);
|
|
||||||
if (object)
|
|
||||||
add_pending_object(&revs, object, "");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
|
|
||||||
{
|
|
||||||
struct object *object = parse_object(sha1);
|
|
||||||
if (!object)
|
|
||||||
die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
|
|
||||||
add_pending_object(&revs, object, "");
|
|
||||||
|
|
||||||
for_each_reflog_ent(path, add_one_reflog_ent, NULL);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_one_tree(const unsigned char *sha1)
|
|
||||||
{
|
|
||||||
struct tree *tree = lookup_tree(sha1);
|
|
||||||
add_pending_object(&revs, &tree->object, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_cache_tree(struct cache_tree *it)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (it->entry_count >= 0)
|
|
||||||
add_one_tree(it->sha1);
|
|
||||||
for (i = 0; i < it->subtree_nr; i++)
|
|
||||||
add_cache_tree(it->down[i]->cache_tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_cache_refs(void)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
read_cache();
|
|
||||||
for (i = 0; i < active_nr; i++) {
|
|
||||||
lookup_blob(active_cache[i]->sha1);
|
|
||||||
/*
|
|
||||||
* We could add the blobs to the pending list, but quite
|
|
||||||
* frankly, we don't care. Once we've looked them up, and
|
|
||||||
* added them as objects, we've really done everything
|
|
||||||
* there is to do for a blob
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
if (active_cache_tree)
|
|
||||||
add_cache_tree(active_cache_tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
int cmd_prune(int argc, const char **argv, const char *prefix)
|
int cmd_prune(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
struct rev_info revs;
|
||||||
|
|
||||||
for (i = 1; i < argc; i++) {
|
for (i = 1; i < argc; i++) {
|
||||||
const char *arg = argv[i];
|
const char *arg = argv[i];
|
||||||
@ -254,29 +94,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
|
|||||||
}
|
}
|
||||||
|
|
||||||
save_commit_buffer = 0;
|
save_commit_buffer = 0;
|
||||||
|
|
||||||
/*
|
|
||||||
* Set up revision parsing, and mark us as being interested
|
|
||||||
* in all object types, not just commits.
|
|
||||||
*/
|
|
||||||
init_revisions(&revs, prefix);
|
init_revisions(&revs, prefix);
|
||||||
revs.tag_objects = 1;
|
mark_reachable_objects(&revs, 1);
|
||||||
revs.blob_objects = 1;
|
|
||||||
revs.tree_objects = 1;
|
|
||||||
|
|
||||||
/* Add all external refs */
|
|
||||||
for_each_ref(add_one_ref, NULL);
|
|
||||||
|
|
||||||
/* Add all refs from the index file */
|
|
||||||
add_cache_refs();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set up the revision walk - this will move all commits
|
|
||||||
* from the pending list to the commit walking list.
|
|
||||||
*/
|
|
||||||
prepare_revision_walk(&revs);
|
|
||||||
|
|
||||||
walk_commit_list(&revs);
|
|
||||||
|
|
||||||
prune_object_dir(get_object_directory());
|
prune_object_dir(get_object_directory());
|
||||||
|
|
||||||
|
222
builtin-reflog.c
222
builtin-reflog.c
@ -4,43 +4,169 @@
|
|||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "tree-walk.h"
|
#include "tree-walk.h"
|
||||||
|
#include "diff.h"
|
||||||
|
#include "revision.h"
|
||||||
|
#include "reachable.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* reflog expire
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const char reflog_expire_usage[] =
|
||||||
|
"git-reflog expire [--verbose] [--dry-run] [--fix-stale] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
|
||||||
|
|
||||||
static unsigned long default_reflog_expire;
|
static unsigned long default_reflog_expire;
|
||||||
static unsigned long default_reflog_expire_unreachable;
|
static unsigned long default_reflog_expire_unreachable;
|
||||||
|
|
||||||
|
struct cmd_reflog_expire_cb {
|
||||||
|
struct rev_info revs;
|
||||||
|
int dry_run;
|
||||||
|
int stalefix;
|
||||||
|
int verbose;
|
||||||
|
unsigned long expire_total;
|
||||||
|
unsigned long expire_unreachable;
|
||||||
|
};
|
||||||
|
|
||||||
struct expire_reflog_cb {
|
struct expire_reflog_cb {
|
||||||
FILE *newlog;
|
FILE *newlog;
|
||||||
const char *ref;
|
const char *ref;
|
||||||
struct commit *ref_commit;
|
struct commit *ref_commit;
|
||||||
unsigned long expire_total;
|
struct cmd_reflog_expire_cb *cmd;
|
||||||
unsigned long expire_unreachable;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define INCOMPLETE (1u<<10)
|
||||||
|
#define STUDYING (1u<<11)
|
||||||
|
|
||||||
static int tree_is_complete(const unsigned char *sha1)
|
static int tree_is_complete(const unsigned char *sha1)
|
||||||
{
|
{
|
||||||
struct tree_desc desc;
|
struct tree_desc desc;
|
||||||
void *buf;
|
struct name_entry entry;
|
||||||
char type[20];
|
int complete;
|
||||||
|
struct tree *tree;
|
||||||
|
|
||||||
buf = read_sha1_file(sha1, type, &desc.size);
|
tree = lookup_tree(sha1);
|
||||||
if (!buf)
|
if (!tree)
|
||||||
|
return 0;
|
||||||
|
if (tree->object.flags & SEEN)
|
||||||
|
return 1;
|
||||||
|
if (tree->object.flags & INCOMPLETE)
|
||||||
return 0;
|
return 0;
|
||||||
desc.buf = buf;
|
|
||||||
while (desc.size) {
|
|
||||||
const unsigned char *elem;
|
|
||||||
const char *name;
|
|
||||||
unsigned mode;
|
|
||||||
|
|
||||||
elem = tree_entry_extract(&desc, &name, &mode);
|
desc.buf = tree->buffer;
|
||||||
if (!has_sha1_file(elem) ||
|
desc.size = tree->size;
|
||||||
(S_ISDIR(mode) && !tree_is_complete(elem))) {
|
if (!desc.buf) {
|
||||||
free(buf);
|
char type[20];
|
||||||
|
void *data = read_sha1_file(sha1, type, &desc.size);
|
||||||
|
if (!data) {
|
||||||
|
tree->object.flags |= INCOMPLETE;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
update_tree_entry(&desc);
|
desc.buf = data;
|
||||||
|
tree->buffer = data;
|
||||||
}
|
}
|
||||||
free(buf);
|
complete = 1;
|
||||||
return 1;
|
while (tree_entry(&desc, &entry)) {
|
||||||
|
if (!has_sha1_file(entry.sha1) ||
|
||||||
|
(S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
|
||||||
|
tree->object.flags |= INCOMPLETE;
|
||||||
|
complete = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(tree->buffer);
|
||||||
|
tree->buffer = NULL;
|
||||||
|
|
||||||
|
if (complete)
|
||||||
|
tree->object.flags |= SEEN;
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int commit_is_complete(struct commit *commit)
|
||||||
|
{
|
||||||
|
struct object_array study;
|
||||||
|
struct object_array found;
|
||||||
|
int is_incomplete = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* early return */
|
||||||
|
if (commit->object.flags & SEEN)
|
||||||
|
return 1;
|
||||||
|
if (commit->object.flags & INCOMPLETE)
|
||||||
|
return 0;
|
||||||
|
/*
|
||||||
|
* Find all commits that are reachable and are not marked as
|
||||||
|
* SEEN. Then make sure the trees and blobs contained are
|
||||||
|
* complete. After that, mark these commits also as SEEN.
|
||||||
|
* If some of the objects that are needed to complete this
|
||||||
|
* commit are missing, mark this commit as INCOMPLETE.
|
||||||
|
*/
|
||||||
|
memset(&study, 0, sizeof(study));
|
||||||
|
memset(&found, 0, sizeof(found));
|
||||||
|
add_object_array(&commit->object, NULL, &study);
|
||||||
|
add_object_array(&commit->object, NULL, &found);
|
||||||
|
commit->object.flags |= STUDYING;
|
||||||
|
while (study.nr) {
|
||||||
|
struct commit *c;
|
||||||
|
struct commit_list *parent;
|
||||||
|
|
||||||
|
c = (struct commit *)study.objects[--study.nr].item;
|
||||||
|
if (!c->object.parsed && !parse_object(c->object.sha1))
|
||||||
|
c->object.flags |= INCOMPLETE;
|
||||||
|
|
||||||
|
if (c->object.flags & INCOMPLETE) {
|
||||||
|
is_incomplete = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c->object.flags & SEEN)
|
||||||
|
continue;
|
||||||
|
for (parent = c->parents; parent; parent = parent->next) {
|
||||||
|
struct commit *p = parent->item;
|
||||||
|
if (p->object.flags & STUDYING)
|
||||||
|
continue;
|
||||||
|
p->object.flags |= STUDYING;
|
||||||
|
add_object_array(&p->object, NULL, &study);
|
||||||
|
add_object_array(&p->object, NULL, &found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_incomplete) {
|
||||||
|
/*
|
||||||
|
* make sure all commits in "found" array have all the
|
||||||
|
* necessary objects.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < found.nr; i++) {
|
||||||
|
struct commit *c =
|
||||||
|
(struct commit *)found.objects[i].item;
|
||||||
|
if (!tree_is_complete(c->tree->object.sha1)) {
|
||||||
|
is_incomplete = 1;
|
||||||
|
c->object.flags |= INCOMPLETE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_incomplete) {
|
||||||
|
/* mark all found commits as complete, iow SEEN */
|
||||||
|
for (i = 0; i < found.nr; i++)
|
||||||
|
found.objects[i].item->flags |= SEEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* clear flags from the objects we traversed */
|
||||||
|
for (i = 0; i < found.nr; i++)
|
||||||
|
found.objects[i].item->flags &= ~STUDYING;
|
||||||
|
if (is_incomplete)
|
||||||
|
commit->object.flags |= INCOMPLETE;
|
||||||
|
else {
|
||||||
|
/*
|
||||||
|
* If we come here, we have (1) traversed the ancestry chain
|
||||||
|
* from the "commit" until we reach SEEN commits (which are
|
||||||
|
* known to be complete), and (2) made sure that the commits
|
||||||
|
* encountered during the above traversal refer to trees that
|
||||||
|
* are complete. Which means that we know *all* the commits
|
||||||
|
* we have seen during this process are complete.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < found.nr; i++)
|
||||||
|
found.objects[i].item->flags |= SEEN;
|
||||||
|
}
|
||||||
|
/* free object arrays */
|
||||||
|
free(study.objects);
|
||||||
|
free(found.objects);
|
||||||
|
return !is_incomplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int keep_entry(struct commit **it, unsigned char *sha1)
|
static int keep_entry(struct commit **it, unsigned char *sha1)
|
||||||
@ -54,9 +180,15 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
|
|||||||
if (!commit)
|
if (!commit)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Make sure everything in this commit exists. */
|
/*
|
||||||
parse_object(commit->object.sha1);
|
* Make sure everything in this commit exists.
|
||||||
if (!tree_is_complete(commit->tree->object.sha1))
|
*
|
||||||
|
* We have walked all the objects reachable from the refs
|
||||||
|
* and cache earlier. The commits reachable by this commit
|
||||||
|
* must meet SEEN commits -- and then we should mark them as
|
||||||
|
* SEEN as well.
|
||||||
|
*/
|
||||||
|
if (!commit_is_complete(commit))
|
||||||
return 0;
|
return 0;
|
||||||
*it = commit;
|
*it = commit;
|
||||||
return 1;
|
return 1;
|
||||||
@ -76,13 +208,14 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
|
|||||||
timestamp = strtoul(cp, &ep, 10);
|
timestamp = strtoul(cp, &ep, 10);
|
||||||
if (*ep != ' ')
|
if (*ep != ' ')
|
||||||
goto prune;
|
goto prune;
|
||||||
if (timestamp < cb->expire_total)
|
if (timestamp < cb->cmd->expire_total)
|
||||||
goto prune;
|
goto prune;
|
||||||
|
|
||||||
if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))
|
if (cb->cmd->stalefix &&
|
||||||
|
(!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
|
||||||
goto prune;
|
goto prune;
|
||||||
|
|
||||||
if ((timestamp < cb->expire_unreachable) &&
|
if ((timestamp < cb->cmd->expire_unreachable) &&
|
||||||
(!cb->ref_commit ||
|
(!cb->ref_commit ||
|
||||||
(old && !in_merge_bases(old, cb->ref_commit)) ||
|
(old && !in_merge_bases(old, cb->ref_commit)) ||
|
||||||
(new && !in_merge_bases(new, cb->ref_commit))))
|
(new && !in_merge_bases(new, cb->ref_commit))))
|
||||||
@ -91,19 +224,15 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
|
|||||||
if (cb->newlog)
|
if (cb->newlog)
|
||||||
fprintf(cb->newlog, "%s %s %s",
|
fprintf(cb->newlog, "%s %s %s",
|
||||||
sha1_to_hex(osha1), sha1_to_hex(nsha1), data);
|
sha1_to_hex(osha1), sha1_to_hex(nsha1), data);
|
||||||
|
if (cb->cmd->verbose)
|
||||||
|
printf("keep %s", data);
|
||||||
return 0;
|
return 0;
|
||||||
prune:
|
prune:
|
||||||
if (!cb->newlog)
|
if (!cb->newlog || cb->cmd->verbose)
|
||||||
fprintf(stderr, "would prune %s", data);
|
printf("%sprune %s", cb->newlog ? "" : "would ", data);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct cmd_reflog_expire_cb {
|
|
||||||
int dry_run;
|
|
||||||
unsigned long expire_total;
|
|
||||||
unsigned long expire_unreachable;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
|
static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
|
||||||
{
|
{
|
||||||
struct cmd_reflog_expire_cb *cmd = cb_data;
|
struct cmd_reflog_expire_cb *cmd = cb_data;
|
||||||
@ -134,8 +263,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
|
|||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"warning: ref '%s' does not point at a commit\n", ref);
|
"warning: ref '%s' does not point at a commit\n", ref);
|
||||||
cb.ref = ref;
|
cb.ref = ref;
|
||||||
cb.expire_total = cmd->expire_total;
|
cb.cmd = cmd;
|
||||||
cb.expire_unreachable = cmd->expire_unreachable;
|
|
||||||
for_each_reflog_ent(ref, expire_reflog_ent, &cb);
|
for_each_reflog_ent(ref, expire_reflog_ent, &cb);
|
||||||
finish:
|
finish:
|
||||||
if (cb.newlog) {
|
if (cb.newlog) {
|
||||||
@ -164,9 +292,6 @@ static int reflog_expire_config(const char *var, const char *value)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char reflog_expire_usage[] =
|
|
||||||
"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
|
|
||||||
|
|
||||||
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
struct cmd_reflog_expire_cb cb;
|
struct cmd_reflog_expire_cb cb;
|
||||||
@ -186,6 +311,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
|||||||
cb.expire_total = default_reflog_expire;
|
cb.expire_total = default_reflog_expire;
|
||||||
cb.expire_unreachable = default_reflog_expire_unreachable;
|
cb.expire_unreachable = default_reflog_expire_unreachable;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can trust the commits and objects reachable from refs
|
||||||
|
* even in older repository. We cannot trust what's reachable
|
||||||
|
* from reflog if the repository was pruned with older git.
|
||||||
|
*/
|
||||||
|
|
||||||
for (i = 1; i < argc; i++) {
|
for (i = 1; i < argc; i++) {
|
||||||
const char *arg = argv[i];
|
const char *arg = argv[i];
|
||||||
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
|
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
|
||||||
@ -194,8 +325,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
|||||||
cb.expire_total = approxidate(arg + 9);
|
cb.expire_total = approxidate(arg + 9);
|
||||||
else if (!strncmp(arg, "--expire-unreachable=", 21))
|
else if (!strncmp(arg, "--expire-unreachable=", 21))
|
||||||
cb.expire_unreachable = approxidate(arg + 21);
|
cb.expire_unreachable = approxidate(arg + 21);
|
||||||
|
else if (!strcmp(arg, "--stale-fix"))
|
||||||
|
cb.stalefix = 1;
|
||||||
else if (!strcmp(arg, "--all"))
|
else if (!strcmp(arg, "--all"))
|
||||||
do_all = 1;
|
do_all = 1;
|
||||||
|
else if (!strcmp(arg, "--verbose"))
|
||||||
|
cb.verbose = 1;
|
||||||
else if (!strcmp(arg, "--")) {
|
else if (!strcmp(arg, "--")) {
|
||||||
i++;
|
i++;
|
||||||
break;
|
break;
|
||||||
@ -205,6 +340,15 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
|||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (cb.stalefix) {
|
||||||
|
init_revisions(&cb.revs, prefix);
|
||||||
|
if (cb.verbose)
|
||||||
|
printf("Marking reachable objects...");
|
||||||
|
mark_reachable_objects(&cb.revs, 0);
|
||||||
|
if (cb.verbose)
|
||||||
|
putchar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
if (do_all)
|
if (do_all)
|
||||||
status |= for_each_ref(expire_reflog, &cb);
|
status |= for_each_ref(expire_reflog, &cb);
|
||||||
while (i < argc) {
|
while (i < argc) {
|
||||||
@ -219,6 +363,10 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* main "reflog"
|
||||||
|
*/
|
||||||
|
|
||||||
static const char reflog_usage[] =
|
static const char reflog_usage[] =
|
||||||
"git-reflog (expire | ...)";
|
"git-reflog (expire | ...)";
|
||||||
|
|
||||||
|
199
reachable.c
Normal file
199
reachable.c
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
#include "cache.h"
|
||||||
|
#include "refs.h"
|
||||||
|
#include "tag.h"
|
||||||
|
#include "commit.h"
|
||||||
|
#include "blob.h"
|
||||||
|
#include "diff.h"
|
||||||
|
#include "revision.h"
|
||||||
|
#include "reachable.h"
|
||||||
|
#include "cache-tree.h"
|
||||||
|
|
||||||
|
static void process_blob(struct blob *blob,
|
||||||
|
struct object_array *p,
|
||||||
|
struct name_path *path,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
struct object *obj = &blob->object;
|
||||||
|
|
||||||
|
if (obj->flags & SEEN)
|
||||||
|
return;
|
||||||
|
obj->flags |= SEEN;
|
||||||
|
/* Nothing to do, really .. The blob lookup was the important part */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_tree(struct tree *tree,
|
||||||
|
struct object_array *p,
|
||||||
|
struct name_path *path,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
struct object *obj = &tree->object;
|
||||||
|
struct tree_desc desc;
|
||||||
|
struct name_entry entry;
|
||||||
|
struct name_path me;
|
||||||
|
|
||||||
|
if (obj->flags & SEEN)
|
||||||
|
return;
|
||||||
|
obj->flags |= SEEN;
|
||||||
|
if (parse_tree(tree) < 0)
|
||||||
|
die("bad tree object %s", sha1_to_hex(obj->sha1));
|
||||||
|
name = xstrdup(name);
|
||||||
|
add_object(obj, p, path, name);
|
||||||
|
me.up = path;
|
||||||
|
me.elem = name;
|
||||||
|
me.elem_len = strlen(name);
|
||||||
|
|
||||||
|
desc.buf = tree->buffer;
|
||||||
|
desc.size = tree->size;
|
||||||
|
|
||||||
|
while (tree_entry(&desc, &entry)) {
|
||||||
|
if (S_ISDIR(entry.mode))
|
||||||
|
process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
|
||||||
|
else
|
||||||
|
process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
|
||||||
|
}
|
||||||
|
free(tree->buffer);
|
||||||
|
tree->buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_tag(struct tag *tag, struct object_array *p, const char *name)
|
||||||
|
{
|
||||||
|
struct object *obj = &tag->object;
|
||||||
|
struct name_path me;
|
||||||
|
|
||||||
|
if (obj->flags & SEEN)
|
||||||
|
return;
|
||||||
|
obj->flags |= SEEN;
|
||||||
|
|
||||||
|
me.up = NULL;
|
||||||
|
me.elem = "tag:/";
|
||||||
|
me.elem_len = 5;
|
||||||
|
|
||||||
|
if (parse_tag(tag) < 0)
|
||||||
|
die("bad tag object %s", sha1_to_hex(obj->sha1));
|
||||||
|
add_object(tag->tagged, p, NULL, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk_commit_list(struct rev_info *revs)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct commit *commit;
|
||||||
|
struct object_array objects = { 0, 0, NULL };
|
||||||
|
|
||||||
|
/* Walk all commits, process their trees */
|
||||||
|
while ((commit = get_revision(revs)) != NULL)
|
||||||
|
process_tree(commit->tree, &objects, NULL, "");
|
||||||
|
|
||||||
|
/* Then walk all the pending objects, recursively processing them too */
|
||||||
|
for (i = 0; i < revs->pending.nr; i++) {
|
||||||
|
struct object_array_entry *pending = revs->pending.objects + i;
|
||||||
|
struct object *obj = pending->item;
|
||||||
|
const char *name = pending->name;
|
||||||
|
if (obj->type == OBJ_TAG) {
|
||||||
|
process_tag((struct tag *) obj, &objects, name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (obj->type == OBJ_TREE) {
|
||||||
|
process_tree((struct tree *)obj, &objects, NULL, name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (obj->type == OBJ_BLOB) {
|
||||||
|
process_blob((struct blob *)obj, &objects, NULL, name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data)
|
||||||
|
{
|
||||||
|
struct object *object;
|
||||||
|
struct rev_info *revs = (struct rev_info *)cb_data;
|
||||||
|
|
||||||
|
object = parse_object(osha1);
|
||||||
|
if (object)
|
||||||
|
add_pending_object(revs, object, "");
|
||||||
|
object = parse_object(nsha1);
|
||||||
|
if (object)
|
||||||
|
add_pending_object(revs, object, "");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
|
||||||
|
{
|
||||||
|
struct object *object = parse_object(sha1);
|
||||||
|
struct rev_info *revs = (struct rev_info *)cb_data;
|
||||||
|
|
||||||
|
if (!object)
|
||||||
|
die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
|
||||||
|
add_pending_object(revs, object, "");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data)
|
||||||
|
{
|
||||||
|
for_each_reflog_ent(path, add_one_reflog_ent, cb_data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_one_tree(const unsigned char *sha1, struct rev_info *revs)
|
||||||
|
{
|
||||||
|
struct tree *tree = lookup_tree(sha1);
|
||||||
|
add_pending_object(revs, &tree->object, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_cache_tree(struct cache_tree *it, struct rev_info *revs)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (it->entry_count >= 0)
|
||||||
|
add_one_tree(it->sha1, revs);
|
||||||
|
for (i = 0; i < it->subtree_nr; i++)
|
||||||
|
add_cache_tree(it->down[i]->cache_tree, revs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_cache_refs(struct rev_info *revs)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
read_cache();
|
||||||
|
for (i = 0; i < active_nr; i++) {
|
||||||
|
lookup_blob(active_cache[i]->sha1);
|
||||||
|
/*
|
||||||
|
* We could add the blobs to the pending list, but quite
|
||||||
|
* frankly, we don't care. Once we've looked them up, and
|
||||||
|
* added them as objects, we've really done everything
|
||||||
|
* there is to do for a blob
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
if (active_cache_tree)
|
||||||
|
add_cache_tree(active_cache_tree, revs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Set up revision parsing, and mark us as being interested
|
||||||
|
* in all object types, not just commits.
|
||||||
|
*/
|
||||||
|
revs->tag_objects = 1;
|
||||||
|
revs->blob_objects = 1;
|
||||||
|
revs->tree_objects = 1;
|
||||||
|
|
||||||
|
/* Add all refs from the index file */
|
||||||
|
add_cache_refs(revs);
|
||||||
|
|
||||||
|
/* Add all external refs */
|
||||||
|
for_each_ref(add_one_ref, revs);
|
||||||
|
|
||||||
|
/* Add all reflog info from refs */
|
||||||
|
if (mark_reflog)
|
||||||
|
for_each_ref(add_one_reflog, revs);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the revision walk - this will move all commits
|
||||||
|
* from the pending list to the commit walking list.
|
||||||
|
*/
|
||||||
|
prepare_revision_walk(revs);
|
||||||
|
walk_commit_list(revs);
|
||||||
|
}
|
6
reachable.h
Normal file
6
reachable.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#ifndef REACHEABLE_H
|
||||||
|
#define REACHEABLE_H
|
||||||
|
|
||||||
|
extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog);
|
||||||
|
|
||||||
|
#endif
|
176
t/t1410-reflog.sh
Executable file
176
t/t1410-reflog.sh
Executable file
@ -0,0 +1,176 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 Junio C Hamano
|
||||||
|
#
|
||||||
|
|
||||||
|
test_description='Test prune and reflog expiration'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
check_have () {
|
||||||
|
gaah= &&
|
||||||
|
for N in "$@"
|
||||||
|
do
|
||||||
|
eval "o=\$$N" && git cat-file -t $o || {
|
||||||
|
echo Gaah $N
|
||||||
|
gaah=$N
|
||||||
|
break
|
||||||
|
}
|
||||||
|
done &&
|
||||||
|
test -z "$gaah"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_fsck () {
|
||||||
|
output=$(git fsck-objects --full)
|
||||||
|
case "$1" in
|
||||||
|
'')
|
||||||
|
test -z "$output" ;;
|
||||||
|
*)
|
||||||
|
echo "$output" | grep "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
corrupt () {
|
||||||
|
aa=${1%??????????????????????????????????????} zz=${1#??}
|
||||||
|
mv .git/objects/$aa/$zz .git/$aa$zz
|
||||||
|
}
|
||||||
|
|
||||||
|
recover () {
|
||||||
|
aa=${1%??????????????????????????????????????} zz=${1#??}
|
||||||
|
mkdir -p .git/objects/$aa
|
||||||
|
mv .git/$aa$zz .git/objects/$aa/$zz
|
||||||
|
}
|
||||||
|
|
||||||
|
check_dont_have () {
|
||||||
|
gaah= &&
|
||||||
|
for N in "$@"
|
||||||
|
do
|
||||||
|
eval "o=\$$N"
|
||||||
|
git cat-file -t $o && {
|
||||||
|
echo Gaah $N
|
||||||
|
gaah=$N
|
||||||
|
break
|
||||||
|
}
|
||||||
|
done
|
||||||
|
test -z "$gaah"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success setup '
|
||||||
|
mkdir -p A/B &&
|
||||||
|
echo rat >C &&
|
||||||
|
echo ox >A/D &&
|
||||||
|
echo tiger >A/B/E &&
|
||||||
|
git add . &&
|
||||||
|
|
||||||
|
test_tick && git commit -m rabbit &&
|
||||||
|
H=`git rev-parse --verify HEAD` &&
|
||||||
|
A=`git rev-parse --verify HEAD:A` &&
|
||||||
|
B=`git rev-parse --verify HEAD:A/B` &&
|
||||||
|
C=`git rev-parse --verify HEAD:C` &&
|
||||||
|
D=`git rev-parse --verify HEAD:A/D` &&
|
||||||
|
E=`git rev-parse --verify HEAD:A/B/E` &&
|
||||||
|
check_fsck &&
|
||||||
|
|
||||||
|
chmod +x C &&
|
||||||
|
git add C &&
|
||||||
|
test_tick && git commit -m dragon &&
|
||||||
|
L=`git rev-parse --verify HEAD` &&
|
||||||
|
check_fsck &&
|
||||||
|
|
||||||
|
rm -f C A/B/E &&
|
||||||
|
echo snake >F &&
|
||||||
|
echo horse >A/G &&
|
||||||
|
git add F A/G &&
|
||||||
|
test_tick && git commit -a -m sheep &&
|
||||||
|
F=`git rev-parse --verify HEAD:F` &&
|
||||||
|
G=`git rev-parse --verify HEAD:A/G` &&
|
||||||
|
I=`git rev-parse --verify HEAD:A` &&
|
||||||
|
J=`git rev-parse --verify HEAD` &&
|
||||||
|
check_fsck &&
|
||||||
|
|
||||||
|
rm -f A/G &&
|
||||||
|
test_tick && git commit -a -m monkey &&
|
||||||
|
K=`git rev-parse --verify HEAD` &&
|
||||||
|
check_fsck &&
|
||||||
|
|
||||||
|
check_have A B C D E F G H I J K L &&
|
||||||
|
|
||||||
|
git prune &&
|
||||||
|
|
||||||
|
check_have A B C D E F G H I J K L &&
|
||||||
|
|
||||||
|
check_fsck &&
|
||||||
|
|
||||||
|
loglen=$(wc -l <.git/logs/refs/heads/master) &&
|
||||||
|
test $loglen = 4
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success rewind '
|
||||||
|
test_tick && git reset --hard HEAD~2 &&
|
||||||
|
test -f C &&
|
||||||
|
test -f A/B/E &&
|
||||||
|
! test -f F &&
|
||||||
|
! test -f A/G &&
|
||||||
|
|
||||||
|
check_have A B C D E F G H I J K L &&
|
||||||
|
|
||||||
|
git prune &&
|
||||||
|
|
||||||
|
check_have A B C D E F G H I J K L &&
|
||||||
|
|
||||||
|
loglen=$(wc -l <.git/logs/refs/heads/master) &&
|
||||||
|
test $loglen = 5
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'corrupt and check' '
|
||||||
|
|
||||||
|
corrupt $F &&
|
||||||
|
check_fsck "missing blob $F"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'reflog expire --dry-run should not touch reflog' '
|
||||||
|
|
||||||
|
git reflog expire --dry-run \
|
||||||
|
--expire=$(($test_tick - 10000)) \
|
||||||
|
--expire-unreachable=$(($test_tick - 10000)) \
|
||||||
|
--stale-fix \
|
||||||
|
--all &&
|
||||||
|
|
||||||
|
loglen=$(wc -l <.git/logs/refs/heads/master) &&
|
||||||
|
test $loglen = 5 &&
|
||||||
|
|
||||||
|
check_fsck "missing blob $F"
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'reflog expire' '
|
||||||
|
|
||||||
|
git reflog expire --verbose \
|
||||||
|
--expire=$(($test_tick - 10000)) \
|
||||||
|
--expire-unreachable=$(($test_tick - 10000)) \
|
||||||
|
--stale-fix \
|
||||||
|
--all &&
|
||||||
|
|
||||||
|
loglen=$(wc -l <.git/logs/refs/heads/master) &&
|
||||||
|
test $loglen = 2 &&
|
||||||
|
|
||||||
|
check_fsck "dangling commit $K"
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'prune and fsck' '
|
||||||
|
|
||||||
|
git prune &&
|
||||||
|
check_fsck &&
|
||||||
|
|
||||||
|
check_have A B C D E H L &&
|
||||||
|
check_dont_have F G I J K
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'recover and check' '
|
||||||
|
|
||||||
|
recover $F &&
|
||||||
|
check_fsck "dangling blob $F"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user