diff --git a/builtin/reflog.c b/builtin/reflog.c index 2d85d260ca..49c64f96d8 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -22,18 +22,13 @@ static unsigned long default_reflog_expire_unreachable; struct cmd_reflog_expire_cb { struct rev_info revs; - int dry_run; int stalefix; - int rewrite; - int updateref; - int verbose; unsigned long expire_total; unsigned long expire_unreachable; int recno; }; -struct expire_reflog_cb { - FILE *newlog; +struct expire_reflog_policy_cb { enum { UE_NORMAL, UE_ALWAYS, @@ -41,14 +36,16 @@ struct expire_reflog_cb { } unreachable_expire_kind; struct commit_list *mark_list; unsigned long mark_limit; - struct cmd_reflog_expire_cb *cmd; - unsigned char last_kept_sha1[20]; + struct cmd_reflog_expire_cb cmd; + struct commit *tip_commit; + struct commit_list *tips; }; struct collected_reflog { unsigned char sha1[20]; char reflog[FLEX_ARRAY]; }; + struct collect_reflog_cb { struct collected_reflog **e; int alloc; @@ -220,7 +217,7 @@ static int keep_entry(struct commit **it, unsigned char *sha1) * the expire_limit and queue them back, so that the caller can call * us again to restart the traversal with longer expire_limit. */ -static void mark_reachable(struct expire_reflog_cb *cb) +static void mark_reachable(struct expire_reflog_policy_cb *cb) { struct commit *commit; struct commit_list *pending; @@ -259,7 +256,7 @@ static void mark_reachable(struct expire_reflog_cb *cb) cb->mark_list = leftover; } -static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, unsigned char *sha1) { /* * We may or may not have the commit yet - if not, look it @@ -288,55 +285,39 @@ static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsig return !(commit->object.flags & REACHABLE); } -static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, - const char *email, unsigned long timestamp, int tz, - const char *message, void *cb_data) +/* + * Return true iff the specified reflog entry should be expired. + */ +static int should_expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) { - struct expire_reflog_cb *cb = cb_data; + struct expire_reflog_policy_cb *cb = cb_data; struct commit *old, *new; - if (timestamp < cb->cmd->expire_total) - goto prune; - - if (cb->cmd->rewrite) - osha1 = cb->last_kept_sha1; + if (timestamp < cb->cmd.expire_total) + return 1; old = new = NULL; - if (cb->cmd->stalefix && + if (cb->cmd.stalefix && (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))) - goto prune; + return 1; - if (timestamp < cb->cmd->expire_unreachable) { + if (timestamp < cb->cmd.expire_unreachable) { if (cb->unreachable_expire_kind == UE_ALWAYS) - goto prune; + return 1; if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) - goto prune; + return 1; } - if (cb->cmd->recno && --(cb->cmd->recno) == 0) - goto prune; + if (cb->cmd.recno && --(cb->cmd.recno) == 0) + return 1; - if (cb->newlog) { - char sign = (tz < 0) ? '-' : '+'; - int zone = (tz < 0) ? (-tz) : tz; - fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s", - sha1_to_hex(osha1), sha1_to_hex(nsha1), - email, timestamp, sign, zone, - message); - hashcpy(cb->last_kept_sha1, nsha1); - } - if (cb->cmd->verbose) - printf("keep %s", message); - return 0; - prune: - if (!cb->newlog) - printf("would prune %s", message); - else if (cb->cmd->verbose) - printf("prune %s", message); return 0; } -static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +static int push_tip_to_list(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) { struct commit_list **list = cb_data; struct commit *tip_commit; @@ -349,104 +330,56 @@ static int push_tip_to_list(const char *refname, const unsigned char *sha1, int return 0; } -static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) +static void reflog_expiry_prepare(const char *refname, + const unsigned char *sha1, + void *cb_data) { - struct cmd_reflog_expire_cb *cmd = cb_data; - struct expire_reflog_cb cb; - struct ref_lock *lock; - char *log_file, *newlog_path = NULL; - struct commit *tip_commit; - struct commit_list *tips; - int status = 0; + struct expire_reflog_policy_cb *cb = cb_data; - memset(&cb, 0, sizeof(cb)); - - /* - * we take the lock for the ref itself to prevent it from - * getting updated. - */ - lock = lock_any_ref_for_update(ref, sha1, 0, NULL); - if (!lock) - return error("cannot lock ref '%s'", ref); - log_file = git_pathdup("logs/%s", ref); - if (!reflog_exists(ref)) - goto finish; - if (!cmd->dry_run) { - newlog_path = git_pathdup("logs/%s.lock", ref); - cb.newlog = fopen(newlog_path, "w"); - } - - cb.cmd = cmd; - - if (!cmd->expire_unreachable || !strcmp(ref, "HEAD")) { - tip_commit = NULL; - cb.unreachable_expire_kind = UE_HEAD; + if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) { + cb->tip_commit = NULL; + cb->unreachable_expire_kind = UE_HEAD; } else { - tip_commit = lookup_commit_reference_gently(sha1, 1); - if (!tip_commit) - cb.unreachable_expire_kind = UE_ALWAYS; + cb->tip_commit = lookup_commit_reference_gently(sha1, 1); + if (!cb->tip_commit) + cb->unreachable_expire_kind = UE_ALWAYS; else - cb.unreachable_expire_kind = UE_NORMAL; + cb->unreachable_expire_kind = UE_NORMAL; } - if (cmd->expire_unreachable <= cmd->expire_total) - cb.unreachable_expire_kind = UE_ALWAYS; + if (cb->cmd.expire_unreachable <= cb->cmd.expire_total) + cb->unreachable_expire_kind = UE_ALWAYS; - cb.mark_list = NULL; - tips = NULL; - if (cb.unreachable_expire_kind != UE_ALWAYS) { - if (cb.unreachable_expire_kind == UE_HEAD) { + cb->mark_list = NULL; + cb->tips = NULL; + if (cb->unreachable_expire_kind != UE_ALWAYS) { + if (cb->unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; - for_each_ref(push_tip_to_list, &tips); - for (elem = tips; elem; elem = elem->next) - commit_list_insert(elem->item, &cb.mark_list); + for_each_ref(push_tip_to_list, &cb->tips); + for (elem = cb->tips; elem; elem = elem->next) + commit_list_insert(elem->item, &cb->mark_list); } else { - commit_list_insert(tip_commit, &cb.mark_list); + commit_list_insert(cb->tip_commit, &cb->mark_list); } - cb.mark_limit = cmd->expire_total; - mark_reachable(&cb); + cb->mark_limit = cb->cmd.expire_total; + mark_reachable(cb); } +} - for_each_reflog_ent(ref, expire_reflog_ent, &cb); +static void reflog_expiry_cleanup(void *cb_data) +{ + struct expire_reflog_policy_cb *cb = cb_data; - if (cb.unreachable_expire_kind != UE_ALWAYS) { - if (cb.unreachable_expire_kind == UE_HEAD) { + if (cb->unreachable_expire_kind != UE_ALWAYS) { + if (cb->unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; - for (elem = tips; elem; elem = elem->next) + for (elem = cb->tips; elem; elem = elem->next) clear_commit_marks(elem->item, REACHABLE); - free_commit_list(tips); + free_commit_list(cb->tips); } else { - clear_commit_marks(tip_commit, REACHABLE); + clear_commit_marks(cb->tip_commit, REACHABLE); } } - finish: - if (cb.newlog) { - if (fclose(cb.newlog)) { - status |= error("%s: %s", strerror(errno), - newlog_path); - unlink(newlog_path); - } else if (cmd->updateref && - (write_in_full(lock->lock_fd, - sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_str_in_full(lock->lock_fd, "\n") != 1 || - close_ref(lock) < 0)) { - status |= error("Couldn't write %s", - lock->lk->filename.buf); - unlink(newlog_path); - } else if (rename(newlog_path, log_file)) { - status |= error("cannot rename %s to %s", - newlog_path, log_file); - unlink(newlog_path); - } else if (cmd->updateref && commit_ref(lock)) { - status |= error("Couldn't set %s", lock->ref_name); - } else { - adjust_shared_perm(log_file); - } - } - free(newlog_path); - free(log_file); - unlock_ref(lock); - return status; } static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) @@ -590,10 +523,11 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { - struct cmd_reflog_expire_cb cb; + struct expire_reflog_policy_cb cb; unsigned long now = time(NULL); int i, status, do_all; int explicit_expiry = 0; + unsigned int flags = 0; default_reflog_expire_unreachable = now - 30 * 24 * 3600; default_reflog_expire = now - 90 * 24 * 3600; @@ -603,33 +537,33 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) do_all = status = 0; memset(&cb, 0, sizeof(cb)); - cb.expire_total = default_reflog_expire; - cb.expire_unreachable = default_reflog_expire_unreachable; + cb.cmd.expire_total = default_reflog_expire; + cb.cmd.expire_unreachable = default_reflog_expire_unreachable; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - cb.dry_run = 1; + flags |= EXPIRE_REFLOGS_DRY_RUN; else if (starts_with(arg, "--expire=")) { - if (parse_expiry_date(arg + 9, &cb.expire_total)) + if (parse_expiry_date(arg + 9, &cb.cmd.expire_total)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } else if (starts_with(arg, "--expire-unreachable=")) { - if (parse_expiry_date(arg + 21, &cb.expire_unreachable)) + if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } else if (!strcmp(arg, "--stale-fix")) - cb.stalefix = 1; + cb.cmd.stalefix = 1; else if (!strcmp(arg, "--rewrite")) - cb.rewrite = 1; + flags |= EXPIRE_REFLOGS_REWRITE; else if (!strcmp(arg, "--updateref")) - cb.updateref = 1; + flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--all")) do_all = 1; else if (!strcmp(arg, "--verbose")) - cb.verbose = 1; + flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { i++; break; @@ -645,12 +579,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) * even in older repository. We cannot trust what's reachable * from reflog if the repository was pruned with older git. */ - if (cb.stalefix) { - init_revisions(&cb.revs, prefix); - if (cb.verbose) + if (cb.cmd.stalefix) { + init_revisions(&cb.cmd.revs, prefix); + if (flags & EXPIRE_REFLOGS_VERBOSE) printf("Marking reachable objects..."); - mark_reachable_objects(&cb.revs, 0, 0, NULL); - if (cb.verbose) + mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); + if (flags & EXPIRE_REFLOGS_VERBOSE) putchar('\n'); } @@ -662,8 +596,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for_each_reflog(collect_reflog, &collected); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; - set_reflog_expiry_param(&cb, explicit_expiry, e->reflog); - status |= expire_reflog(e->reflog, e->sha1, 0, &cb); + set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); + status |= reflog_expire(e->reflog, e->sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); free(e); } free(collected.e); @@ -676,8 +614,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) status |= error("%s points nowhere!", argv[i]); continue; } - set_reflog_expiry_param(&cb, explicit_expiry, ref); - status |= expire_reflog(ref, sha1, 0, &cb); + set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); + status |= reflog_expire(ref, sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); } return status; } @@ -686,29 +628,30 @@ static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - struct cmd_reflog_expire_cb *cb = cb_data; - if (!cb->expire_total || timestamp < cb->expire_total) - cb->recno++; + struct expire_reflog_policy_cb *cb = cb_data; + if (!cb->cmd.expire_total || timestamp < cb->cmd.expire_total) + cb->cmd.recno++; return 0; } static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) { - struct cmd_reflog_expire_cb cb; + struct expire_reflog_policy_cb cb; int i, status = 0; + unsigned int flags = 0; memset(&cb, 0, sizeof(cb)); for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - cb.dry_run = 1; + flags |= EXPIRE_REFLOGS_DRY_RUN; else if (!strcmp(arg, "--rewrite")) - cb.rewrite = 1; + flags |= EXPIRE_REFLOGS_REWRITE; else if (!strcmp(arg, "--updateref")) - cb.updateref = 1; + flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--verbose")) - cb.verbose = 1; + flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { i++; break; @@ -740,15 +683,19 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) recno = strtoul(spec + 2, &ep, 10); if (*ep == '}') { - cb.recno = -recno; + cb.cmd.recno = -recno; for_each_reflog_ent(ref, count_reflog_ent, &cb); } else { - cb.expire_total = approxidate(spec + 2); + cb.cmd.expire_total = approxidate(spec + 2); for_each_reflog_ent(ref, count_reflog_ent, &cb); - cb.expire_total = 0; + cb.cmd.expire_total = 0; } - status |= expire_reflog(ref, sha1, 0, &cb); + status |= reflog_expire(ref, sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); free(ref); } return status; diff --git a/refs.c b/refs.c index ed3b2cb405..c5fa70947f 100644 --- a/refs.c +++ b/refs.c @@ -6,6 +6,15 @@ #include "dir.h" #include "string-list.h" +struct ref_lock { + char *ref_name; + char *orig_ref_name; + struct lock_file *lk; + unsigned char old_sha1[20]; + int lock_fd; + int force_write; +}; + /* * How to handle various characters in refnames: * 0: An acceptable character for refs @@ -2094,6 +2103,16 @@ int refname_match(const char *abbrev_name, const char *full_name) return 0; } +static void unlock_ref(struct ref_lock *lock) +{ + /* Do not free lock->lk -- atexit() still looks at them */ + if (lock->lk) + rollback_lock_file(lock->lk); + free(lock->ref_name); + free(lock->orig_ref_name); + free(lock); +} + /* This function should make sure errno is meaningful on error */ static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) @@ -2346,13 +2365,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, return NULL; } -struct ref_lock *lock_any_ref_for_update(const char *refname, - const unsigned char *old_sha1, - int flags, int *type_p) -{ - return lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p); -} - /* * Write an entry to the packed-refs file for the specified refname. * If peeled is non-NULL, write it as the entry's peeled value. @@ -2901,7 +2913,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms return 1; } -int close_ref(struct ref_lock *lock) +static int close_ref(struct ref_lock *lock) { if (close_lock_file(lock->lk)) return -1; @@ -2909,7 +2921,7 @@ int close_ref(struct ref_lock *lock) return 0; } -int commit_ref(struct ref_lock *lock) +static int commit_ref(struct ref_lock *lock) { if (commit_lock_file(lock->lk)) return -1; @@ -2917,16 +2929,6 @@ int commit_ref(struct ref_lock *lock) return 0; } -void unlock_ref(struct ref_lock *lock) -{ - /* Do not free lock->lk -- atexit() still looks at them */ - if (lock->lk) - rollback_lock_file(lock->lk); - free(lock->ref_name); - free(lock->orig_ref_name); - free(lock); -} - /* * copy the reflog message msg to buf, which has been allocated sufficiently * large, while cleaning up the whitespaces. Especially, convert LF to space, @@ -3003,15 +3005,37 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) return 0; } +static int log_ref_write_fd(int fd, const unsigned char *old_sha1, + const unsigned char *new_sha1, + const char *committer, const char *msg) +{ + int msglen, written; + unsigned maxlen, len; + char *logrec; + + msglen = msg ? strlen(msg) : 0; + maxlen = strlen(committer) + msglen + 100; + logrec = xmalloc(maxlen); + len = sprintf(logrec, "%s %s %s\n", + sha1_to_hex(old_sha1), + sha1_to_hex(new_sha1), + committer); + if (msglen) + len += copy_msg(logrec + len - 1, msg) - 1; + + written = len <= maxlen ? write_in_full(fd, logrec, len) : -1; + free(logrec); + if (written != len) + return -1; + + return 0; +} + static int log_ref_write(const char *refname, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *msg) { - int logfd, result, written, oflags = O_APPEND | O_WRONLY; - unsigned maxlen, len; - int msglen; + int logfd, result, oflags = O_APPEND | O_WRONLY; char log_file[PATH_MAX]; - char *logrec; - const char *committer; if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); @@ -3023,19 +3047,9 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1, logfd = open(log_file, oflags); if (logfd < 0) return 0; - msglen = msg ? strlen(msg) : 0; - committer = git_committer_info(0); - maxlen = strlen(committer) + msglen + 100; - logrec = xmalloc(maxlen); - len = sprintf(logrec, "%s %s %s\n", - sha1_to_hex(old_sha1), - sha1_to_hex(new_sha1), - committer); - if (msglen) - len += copy_msg(logrec + len - 1, msg) - 1; - written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1; - free(logrec); - if (written != len) { + result = log_ref_write_fd(logfd, old_sha1, new_sha1, + git_committer_info(0), msg); + if (result) { int save_errno = errno; close(logfd); error("Unable to append to %s", log_file); @@ -3661,31 +3675,8 @@ int ref_transaction_create(struct ref_transaction *transaction, int flags, const char *msg, struct strbuf *err) { - struct ref_update *update; - - assert(err); - - if (transaction->state != REF_TRANSACTION_OPEN) - die("BUG: create called for transaction that is not open"); - - if (!new_sha1 || is_null_sha1(new_sha1)) - die("BUG: create ref with null new_sha1"); - - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - strbuf_addf(err, "refusing to create ref with bad name %s", - refname); - return -1; - } - - update = add_update(transaction, refname); - - hashcpy(update->new_sha1, new_sha1); - hashclr(update->old_sha1); - update->flags = flags; - update->have_old = 1; - if (msg) - update->msg = xstrdup(msg); - return 0; + return ref_transaction_update(transaction, refname, new_sha1, + null_sha1, flags, 1, msg, err); } int ref_transaction_delete(struct ref_transaction *transaction, @@ -3694,26 +3685,8 @@ int ref_transaction_delete(struct ref_transaction *transaction, int flags, int have_old, const char *msg, struct strbuf *err) { - struct ref_update *update; - - assert(err); - - if (transaction->state != REF_TRANSACTION_OPEN) - die("BUG: delete called for transaction that is not open"); - - if (have_old && !old_sha1) - die("BUG: have_old is true but old_sha1 is NULL"); - - update = add_update(transaction, refname); - update->flags = flags; - update->have_old = have_old; - if (have_old) { - assert(!is_null_sha1(old_sha1)); - hashcpy(update->old_sha1, old_sha1); - } - if (msg) - update->msg = xstrdup(msg); - return 0; + return ref_transaction_update(transaction, refname, null_sha1, + old_sha1, flags, have_old, msg, err); } int update_ref(const char *action, const char *refname, @@ -4009,3 +3982,129 @@ int ref_is_hidden(const char *refname) } return 0; } + +struct expire_reflog_cb { + unsigned int flags; + reflog_expiry_should_prune_fn *should_prune_fn; + void *policy_cb; + FILE *newlog; + unsigned char last_kept_sha1[20]; +}; + +static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct expire_reflog_cb *cb = cb_data; + struct expire_reflog_policy_cb *policy_cb = cb->policy_cb; + + if (cb->flags & EXPIRE_REFLOGS_REWRITE) + osha1 = cb->last_kept_sha1; + + if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz, + message, policy_cb)) { + if (!cb->newlog) + printf("would prune %s", message); + else if (cb->flags & EXPIRE_REFLOGS_VERBOSE) + printf("prune %s", message); + } else { + if (cb->newlog) { + fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s", + sha1_to_hex(osha1), sha1_to_hex(nsha1), + email, timestamp, tz, message); + hashcpy(cb->last_kept_sha1, nsha1); + } + if (cb->flags & EXPIRE_REFLOGS_VERBOSE) + printf("keep %s", message); + } + return 0; +} + +int reflog_expire(const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) +{ + static struct lock_file reflog_lock; + struct expire_reflog_cb cb; + struct ref_lock *lock; + char *log_file; + int status = 0; + + memset(&cb, 0, sizeof(cb)); + cb.flags = flags; + cb.policy_cb = policy_cb_data; + cb.should_prune_fn = should_prune_fn; + + /* + * The reflog file is locked by holding the lock on the + * reference itself, plus we might need to update the + * reference if --updateref was specified: + */ + lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, NULL); + if (!lock) + return error("cannot lock ref '%s'", refname); + if (!reflog_exists(refname)) { + unlock_ref(lock); + return 0; + } + + log_file = git_pathdup("logs/%s", refname); + if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + /* + * Even though holding $GIT_DIR/logs/$reflog.lock has + * no locking implications, we use the lock_file + * machinery here anyway because it does a lot of the + * work we need, including cleaning up if the program + * exits unexpectedly. + */ + if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) { + struct strbuf err = STRBUF_INIT; + unable_to_lock_message(log_file, errno, &err); + error("%s", err.buf); + strbuf_release(&err); + goto failure; + } + cb.newlog = fdopen_lock_file(&reflog_lock, "w"); + if (!cb.newlog) { + error("cannot fdopen %s (%s)", + reflog_lock.filename.buf, strerror(errno)); + goto failure; + } + } + + (*prepare_fn)(refname, sha1, cb.policy_cb); + for_each_reflog_ent(refname, expire_reflog_ent, &cb); + (*cleanup_fn)(cb.policy_cb); + + if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + if (close_lock_file(&reflog_lock)) { + status |= error("couldn't write %s: %s", log_file, + strerror(errno)); + } else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) && + (write_in_full(lock->lock_fd, + sha1_to_hex(cb.last_kept_sha1), 40) != 40 || + write_str_in_full(lock->lock_fd, "\n") != 1 || + close_ref(lock) < 0)) { + status |= error("couldn't write %s", + lock->lk->filename.buf); + rollback_lock_file(&reflog_lock); + } else if (commit_lock_file(&reflog_lock)) { + status |= error("unable to commit reflog '%s' (%s)", + log_file, strerror(errno)); + } else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) && commit_ref(lock)) { + status |= error("couldn't set %s", lock->ref_name); + } + } + free(log_file); + unlock_ref(lock); + return status; + + failure: + rollback_lock_file(&reflog_lock); + free(log_file); + unlock_ref(lock); + return -1; +} diff --git a/refs.h b/refs.h index 405c6572f1..afa3c4decd 100644 --- a/refs.h +++ b/refs.h @@ -1,15 +1,6 @@ #ifndef REFS_H #define REFS_H -struct ref_lock { - char *ref_name; - char *orig_ref_name; - struct lock_file *lk; - unsigned char old_sha1[20]; - int lock_fd; - int force_write; -}; - /* * A ref_transaction represents a collection of ref updates * that should succeed or fail together. @@ -189,8 +180,7 @@ extern int is_branch(const char *refname); extern int peel_ref(const char *refname, unsigned char *sha1); /* - * Flags controlling lock_any_ref_for_update(), ref_transaction_update(), - * ref_transaction_create(), etc. + * Flags controlling ref_transaction_update(), ref_transaction_create(), etc. * REF_NODEREF: act on the ref directly, instead of dereferencing * symbolic references. * REF_DELETING: tolerate broken refs @@ -199,21 +189,6 @@ extern int peel_ref(const char *refname, unsigned char *sha1); */ #define REF_NODEREF 0x01 #define REF_DELETING 0x02 -/* - * This function sets errno to something meaningful on failure. - */ -extern struct ref_lock *lock_any_ref_for_update(const char *refname, - const unsigned char *old_sha1, - int flags, int *type_p); - -/** Close the file descriptor owned by a lock and return the status */ -extern int close_ref(struct ref_lock *lock); - -/** Close and commit the ref locked by the lock */ -extern int commit_ref(struct ref_lock *lock); - -/** Release any lock taken but not written. **/ -extern void unlock_ref(struct ref_lock *lock); /* * Setup reflog before using. Set errno to something meaningful on failure. @@ -291,7 +266,7 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err); /* * Add a reference update to transaction. new_sha1 is the value that - * the reference should have after the update, or zeros if it should + * the reference should have after the update, or null_sha1 if it should * be deleted. If have_old is true, then old_sha1 holds the value * that the reference should have had before the update, or zeros if * it must not have existed beforehand. @@ -361,4 +336,50 @@ int update_ref(const char *action, const char *refname, extern int parse_hide_refs_config(const char *var, const char *value, const char *); extern int ref_is_hidden(const char *); +enum expire_reflog_flags { + EXPIRE_REFLOGS_DRY_RUN = 1 << 0, + EXPIRE_REFLOGS_UPDATE_REF = 1 << 1, + EXPIRE_REFLOGS_VERBOSE = 1 << 2, + EXPIRE_REFLOGS_REWRITE = 1 << 3 +}; + +/* + * The following interface is used for reflog expiration. The caller + * calls reflog_expire(), supplying it with three callback functions, + * of the following types. The callback functions define the + * expiration policy that is desired. + * + * reflog_expiry_prepare_fn -- Called once after the reference is + * locked. + * + * reflog_expiry_should_prune_fn -- Called once for each entry in the + * existing reflog. It should return true iff that entry should be + * pruned. + * + * reflog_expiry_cleanup_fn -- Called once before the reference is + * unlocked again. + */ +typedef void reflog_expiry_prepare_fn(const char *refname, + const unsigned char *sha1, + void *cb_data); +typedef int reflog_expiry_should_prune_fn(unsigned char *osha1, + unsigned char *nsha1, + const char *email, + unsigned long timestamp, int tz, + const char *message, void *cb_data); +typedef void reflog_expiry_cleanup_fn(void *cb_data); + +/* + * Expire reflog entries for the specified reference. sha1 is the old + * value of the reference. flags is a combination of the constants in + * enum expire_reflog_flags. The three function pointers are described + * above. On success, return zero. + */ +extern int reflog_expire(const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data); + #endif /* REFS_H */