From 2ff81662c2fb2679f841c09a28bd80c0a63011ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 01:18:16 -0800 Subject: add for_each_reflog_ent() iterator Signed-off-by: Junio C Hamano diff --git a/refs.c b/refs.c index a101ff3..b67b1f0 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 51aab1e..de43cc7 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); -- cgit v0.10.2-6-g49f6 From 55dd55263b6d27aa8daa77bd69f5ea990b66c7a1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 01:36:16 -0800 Subject: 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 diff --git a/builtin-prune.c b/builtin-prune.c index 8591d28..00a53b3 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 409aea0..1cc3b39 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; } -- cgit v0.10.2-6-g49f6 From 63049292e083faf80e033eba4fa43efdbac3acad Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 17:25:28 -0800 Subject: 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 diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 807be8c..9e15beb 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; -- cgit v0.10.2-6-g49f6 From e29cb53a8b6aa1256221207b14a1c8ef72f69d9f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Dec 2006 22:07:45 -0800 Subject: 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 diff --git a/refs.c b/refs.c index b67b1f0..8b2a3c1 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); } + -- cgit v0.10.2-6-g49f6 From 2ecd2bbcbe5335c1d9209b6ce28513e4e9d3491b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Dec 2006 00:14:04 -0800 Subject: Move in_merge_bases() to commit.c This reasonably useful function was hidden inside builtin-branch.c diff --git a/builtin-branch.c b/builtin-branch.c index 903d5cf..745ee04 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 289ef65..3167ce6 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 fc13de9..10eea9f 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 */ -- cgit v0.10.2-6-g49f6 From 4264dc15e198bf9e9a2bb4ee897dd8e3eaabca47 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Dec 2006 00:23:12 -0800 Subject: 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 diff --git a/Makefile b/Makefile index 7651104..d4d8590 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 0000000..d4f7353 --- /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=