From 2ff81662c2fb2679f841c09a28bd80c0a63011ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 01:18:16 -0800 Subject: [PATCH 01/10] add for_each_reflog_ent() iterator Signed-off-by: Junio C Hamano --- refs.c | 25 +++++++++++++++++++++++++ refs.h | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/refs.c b/refs.c index a101ff3bf8..b67b1f011f 100644 --- a/refs.c +++ b/refs.c @@ -1091,3 +1091,28 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * logfile, show_rfc2822_date(date, tz)); return 0; } + +void for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +{ + const char *logfile; + FILE *logfp; + char buf[1024]; + + logfile = git_path("logs/%s", ref); + logfp = fopen(logfile, "r"); + if (!logfp) + return; + while (fgets(buf, sizeof(buf), logfp)) { + unsigned char osha1[20], nsha1[20]; + int len; + + /* old SP new SP name SP time TAB msg LF */ + len = strlen(buf); + if (len < 83 || buf[len-1] != '\n' || + get_sha1_hex(buf, osha1) || buf[40] != ' ' || + get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ') + continue; /* corrupt? */ + fn(osha1, nsha1, buf+82, cb_data); + } + fclose(logfp); +} diff --git a/refs.h b/refs.h index 51aab1e6bf..de43cc768a 100644 --- a/refs.h +++ b/refs.h @@ -44,6 +44,10 @@ extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, cons /** Reads log for the value of ref during at_time. **/ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1); +/* iterate over reflog entries */ +typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, char *, void *); +void for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data); + /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); From 55dd55263b6d27aa8daa77bd69f5ea990b66c7a1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 01:36:16 -0800 Subject: [PATCH 02/10] Protect commits recorded in reflog from pruning. This teaches fsck-objects and prune to protect objects referred to by reflog entries. Signed-off-by: Junio C Hamano --- builtin-prune.c | 16 ++++++++++++++++ fsck-objects.c | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/builtin-prune.c b/builtin-prune.c index 8591d28b8e..00a53b3647 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -181,12 +181,28 @@ static void walk_commit_list(struct rev_info *revs) } } +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; } diff --git a/fsck-objects.c b/fsck-objects.c index 409aea02b4..1cc3b399bc 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -399,6 +399,25 @@ static void fsck_dir(int i, char *path) static int default_refs; +static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data) +{ + struct object *obj; + + if (!is_null_sha1(osha1)) { + obj = lookup_object(osha1); + if (obj) { + obj->used = 1; + mark_reachable(obj, REACHABLE); + } + } + obj = lookup_object(nsha1); + if (obj) { + obj->used = 1; + mark_reachable(obj, REACHABLE); + } + return 0; +} + static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -416,6 +435,9 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f default_refs++; obj->used = 1; mark_reachable(obj, REACHABLE); + + for_each_reflog_ent(refname, fsck_handle_reflog_ent, NULL); + return 0; } From 63049292e083faf80e033eba4fa43efdbac3acad Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 17:25:28 -0800 Subject: [PATCH 03/10] Teach git-repack to preserve objects referred to by reflog entries. This adds a new option --reflog to pack-objects and revision machinery; do not bother documenting it for now, since this is only useful for local repacking. When the option is passed, objects reachable from reflog entries are marked as interesting while computing the set of objects to pack. Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 3 ++- git-repack.sh | 2 +- revision.c | 56 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 807be8c3f8..9e15beb3ba 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -17,7 +17,7 @@ static const char pack_usage[] = "\ git-pack-objects [{ -q | --progress | --all-progress }] \n\ [--local] [--incremental] [--window=N] [--depth=N] \n\ [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\ - [--revs [--unpacked | --all]*] [--stdout | base-name] \n\ + [--revs [--unpacked | --all]*] [--reflog] [--stdout | base-name] \n\ [commits = newlist; } -static int all_flags; -static struct rev_info *all_revs; +struct all_refs_cb { + int all_flags; + struct rev_info *all_revs; + const char *name_for_errormsg; +}; static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - struct object *object = get_reference(all_revs, path, sha1, all_flags); - add_pending_object(all_revs, object, ""); + struct all_refs_cb *cb = cb_data; + struct object *object = get_reference(cb->all_revs, path, sha1, + cb->all_flags); + add_pending_object(cb->all_revs, object, ""); return 0; } static void handle_all(struct rev_info *revs, unsigned flags) { - all_revs = revs; - all_flags = flags; - for_each_ref(handle_one_ref, NULL); + struct all_refs_cb cb; + cb.all_revs = revs; + cb.all_flags = flags; + for_each_ref(handle_one_ref, &cb); +} + +static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *detail, void *cb_data) +{ + struct all_refs_cb *cb = cb_data; + struct object *object; + + if (!is_null_sha1(osha1)) { + object = get_reference(cb->all_revs, cb->name_for_errormsg, + osha1, cb->all_flags); + add_pending_object(cb->all_revs, object, ""); + } + object = get_reference(cb->all_revs, cb->name_for_errormsg, + nsha1, cb->all_flags); + add_pending_object(cb->all_revs, object, ""); + return 0; +} + +static int handle_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + struct all_refs_cb *cb = cb_data; + cb->name_for_errormsg = path; + for_each_reflog_ent(path, handle_one_reflog_ent, cb_data); + return 0; +} + +static void handle_reflog(struct rev_info *revs, unsigned flags) +{ + struct all_refs_cb cb; + cb.all_revs = revs; + cb.all_flags = flags; + for_each_ref(handle_one_reflog, &cb); } static int add_parents_only(struct rev_info *revs, const char *arg, int flags) @@ -805,6 +843,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch handle_all(revs, flags); continue; } + if (!strcmp(arg, "--reflog")) { + handle_reflog(revs, flags); + continue; + } if (!strcmp(arg, "--not")) { flags ^= UNINTERESTING; continue; From e29cb53a8b6aa1256221207b14a1c8ef72f69d9f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 22:07:45 -0800 Subject: [PATCH 04/10] reflog: fix warning message. When ref@{N} is specified on a ref that has only M entries (M < N), instead of saying the initial timestamp the reflog has, warn that there is only M entries. Signed-off-by: Junio C Hamano --- refs.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/refs.c b/refs.c index b67b1f011f..8b2a3c1378 100644 --- a/refs.c +++ b/refs.c @@ -1013,7 +1013,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; - int logfd, tz; + int logfd, tz, reccnt = 0; struct stat st; unsigned long date; unsigned char logged_sha1[20]; @@ -1031,6 +1031,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * lastrec = NULL; rec = logend = logdata + st.st_size; while (logdata < rec) { + reccnt++; if (logdata < rec && *(rec-1) == '\n') rec--; lastgt = NULL; @@ -1087,8 +1088,12 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * if (get_sha1_hex(logdata, sha1)) die("Log %s is corrupt.", logfile); munmap((void*)logdata, st.st_size); - fprintf(stderr, "warning: Log %s only goes back to %s.\n", - logfile, show_rfc2822_date(date, tz)); + if (at_time) + fprintf(stderr, "warning: Log %s only goes back to %s.\n", + logfile, show_rfc2822_date(date, tz)); + else + fprintf(stderr, "warning: Log %s only has %d entries.\n", + logfile, reccnt); return 0; } @@ -1116,3 +1121,4 @@ void for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) } fclose(logfp); } + From 2ecd2bbcbe5335c1d9209b6ce28513e4e9d3491b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Dec 2006 00:14:04 -0800 Subject: [PATCH 05/10] Move in_merge_bases() to commit.c This reasonably useful function was hidden inside builtin-branch.c --- builtin-branch.c | 21 +-------------------- commit.c | 17 +++++++++++++++++ commit.h | 1 + 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index 903d5cf056..745ee04d6e 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -74,25 +74,6 @@ const char *branch_get_color(enum color_branch ix) return ""; } -static int in_merge_bases(const unsigned char *sha1, - struct commit *rev1, - struct commit *rev2) -{ - struct commit_list *bases, *b; - int ret = 0; - - bases = get_merge_bases(rev1, rev2, 1); - for (b = bases; b; b = b->next) { - if (!hashcmp(sha1, b->item->object.sha1)) { - ret = 1; - break; - } - } - - free_commit_list(bases); - return ret; -} - static int delete_branches(int argc, const char **argv, int force, int kinds) { struct commit *rev, *head_rev = head_rev; @@ -153,7 +134,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) */ if (!force && - !in_merge_bases(sha1, rev, head_rev)) { + !in_merge_bases(rev, head_rev)) { error("The branch '%s' is not a strict subset of " "your current HEAD.\n" "If you are sure you want to delete it, " diff --git a/commit.c b/commit.c index 289ef65eb1..3167ce62ac 100644 --- a/commit.c +++ b/commit.c @@ -1009,3 +1009,20 @@ struct commit_list *get_merge_bases(struct commit *one, free(rslt); return result; } + +int in_merge_bases(struct commit *rev1, struct commit *rev2) +{ + struct commit_list *bases, *b; + int ret = 0; + + bases = get_merge_bases(rev1, rev2, 1); + for (b = bases; b; b = b->next) { + if (!hashcmp(rev1->object.sha1, b->item->object.sha1)) { + ret = 1; + break; + } + } + + free_commit_list(bases); + return ret; +} diff --git a/commit.h b/commit.h index fc13de9780..10eea9f26f 100644 --- a/commit.h +++ b/commit.h @@ -107,4 +107,5 @@ int read_graft_file(const char *graft_file); extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); +int in_merge_bases(struct commit *rev1, struct commit *rev2); #endif /* COMMIT_H */ From 4264dc15e198bf9e9a2bb4ee897dd8e3eaabca47 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Dec 2006 00:23:12 -0800 Subject: [PATCH 06/10] git reflog expire This prepares a place to collect reflog management subcommands, and implements "expire" action. $ git reflog expire --dry-run \ --expire=4.weeks \ --expire-unreachable=1.week \ refs/heads/master The expiration uses two timestamps: --expire and --expire-unreachable. Entries older than expire time (defaults to 90 days), and entries older than expire-unreachable time (defaults to 30 days) and records a commit that has been rewound and made unreachable from the current tip of the ref are removed from the reflog. The parameter handling is still rough, but I think the core logic for expiration is already sound. Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-reflog.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + 4 files changed, 178 insertions(+) create mode 100644 builtin-reflog.c diff --git a/Makefile b/Makefile index 76511045a8..d4d8590b6e 100644 --- a/Makefile +++ b/Makefile @@ -288,6 +288,7 @@ BUILTIN_OBJS = \ builtin-prune-packed.o \ builtin-push.o \ builtin-read-tree.o \ + builtin-reflog.o \ builtin-repo-config.o \ builtin-rev-list.o \ builtin-rev-parse.o \ diff --git a/builtin-reflog.c b/builtin-reflog.c new file mode 100644 index 0000000000..d4f73535c4 --- /dev/null +++ b/builtin-reflog.c @@ -0,0 +1,175 @@ +#include "cache.h" +#include "builtin.h" +#include "commit.h" +#include "refs.h" +#include "dir.h" +#include + +struct expire_reflog_cb { + FILE *newlog; + const char *ref; + struct commit *ref_commit; + unsigned long expire_total; + unsigned long expire_unreachable; +}; + +static int keep_entry(struct commit **it, unsigned char *sha1) +{ + *it = NULL; + if (is_null_sha1(sha1)) + return 1; + *it = lookup_commit_reference_gently(sha1, 1); + return (*it != NULL); +} + +static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + char *data, void *cb_data) +{ + struct expire_reflog_cb *cb = cb_data; + unsigned long timestamp; + char *cp, *ep; + struct commit *old, *new; + + cp = strchr(data, '>'); + if (!cp || *++cp != ' ') + goto prune; + timestamp = strtoul(cp, &ep, 10); + if (*ep != ' ') + goto prune; + if (timestamp < cb->expire_total) + goto prune; + + if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)) + goto prune; + + if ((timestamp < cb->expire_unreachable) && + ((old && !in_merge_bases(old, cb->ref_commit)) || + (new && !in_merge_bases(new, cb->ref_commit)))) + goto prune; + + if (cb->newlog) + fprintf(cb->newlog, "%s %s %s", + sha1_to_hex(osha1), sha1_to_hex(nsha1), data); + return 0; + prune: + if (!cb->newlog) + fprintf(stderr, "would prune %s", data); + 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) +{ + struct cmd_reflog_expire_cb *cmd = cb_data; + struct expire_reflog_cb cb; + struct ref_lock *lock; + char *newlog_path = NULL; + int status = 0; + + if (strncmp(ref, "refs/", 5)) + return error("not a ref '%s'", ref); + + memset(&cb, 0, sizeof(cb)); + /* we take the lock for the ref itself to prevent it from + * getting updated. + */ + lock = lock_ref_sha1(ref + 5, sha1); + if (!lock) + return error("cannot lock ref '%s'", ref); + if (!file_exists(lock->log_file)) + goto finish; + if (!cmd->dry_run) { + newlog_path = xstrdup(git_path("logs/%s.lock", ref)); + cb.newlog = fopen(newlog_path, "w"); + } + + cb.ref_commit = lookup_commit_reference_gently(sha1, 1); + if (!cb.ref_commit) { + status = error("ref '%s' does not point at a commit", ref); + goto finish; + } + cb.ref = ref; + cb.expire_total = cmd->expire_total; + cb.expire_unreachable = cmd->expire_unreachable; + for_each_reflog_ent(ref, expire_reflog_ent, &cb); + finish: + if (cb.newlog) { + if (fclose(cb.newlog)) + status |= error("%s: %s", strerror(errno), + newlog_path); + if (rename(newlog_path, lock->log_file)) { + status |= error("cannot rename %s to %s", + newlog_path, lock->log_file); + unlink(newlog_path); + } + } + free(newlog_path); + unlock_ref(lock); + return status; +} + +static const char reflog_expire_usage[] = +"git-reflog expire [--dry-run] [--expire=