git
/
reflog.c
444 строки · 11.4 Кб
1#define USE_THE_REPOSITORY_VARIABLE2
3#include "git-compat-util.h"4#include "gettext.h"5#include "object-store-ll.h"6#include "reflog.h"7#include "refs.h"8#include "revision.h"9#include "tree.h"10#include "tree-walk.h"11
12/* Remember to update object flag allocation in object.h */
13#define INCOMPLETE (1u<<10)14#define STUDYING (1u<<11)15#define REACHABLE (1u<<12)16
17static int tree_is_complete(const struct object_id *oid)18{
19struct tree_desc desc;20struct name_entry entry;21int complete;22struct tree *tree;23
24tree = lookup_tree(the_repository, oid);25if (!tree)26return 0;27if (tree->object.flags & SEEN)28return 1;29if (tree->object.flags & INCOMPLETE)30return 0;31
32if (!tree->buffer) {33enum object_type type;34unsigned long size;35void *data = repo_read_object_file(the_repository, oid, &type,36&size);37if (!data) {38tree->object.flags |= INCOMPLETE;39return 0;40}41tree->buffer = data;42tree->size = size;43}44init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);45complete = 1;46while (tree_entry(&desc, &entry)) {47if (!repo_has_object_file(the_repository, &entry.oid) ||48(S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {49tree->object.flags |= INCOMPLETE;50complete = 0;51}52}53free_tree_buffer(tree);54
55if (complete)56tree->object.flags |= SEEN;57return complete;58}
59
60static int commit_is_complete(struct commit *commit)61{
62struct object_array study;63struct object_array found;64int is_incomplete = 0;65int i;66
67/* early return */68if (commit->object.flags & SEEN)69return 1;70if (commit->object.flags & INCOMPLETE)71return 0;72/*73* Find all commits that are reachable and are not marked as
74* SEEN. Then make sure the trees and blobs contained are
75* complete. After that, mark these commits also as SEEN.
76* If some of the objects that are needed to complete this
77* commit are missing, mark this commit as INCOMPLETE.
78*/
79memset(&study, 0, sizeof(study));80memset(&found, 0, sizeof(found));81add_object_array(&commit->object, NULL, &study);82add_object_array(&commit->object, NULL, &found);83commit->object.flags |= STUDYING;84while (study.nr) {85struct commit *c;86struct commit_list *parent;87
88c = (struct commit *)object_array_pop(&study);89if (!c->object.parsed && !parse_object(the_repository, &c->object.oid))90c->object.flags |= INCOMPLETE;91
92if (c->object.flags & INCOMPLETE) {93is_incomplete = 1;94break;95}96else if (c->object.flags & SEEN)97continue;98for (parent = c->parents; parent; parent = parent->next) {99struct commit *p = parent->item;100if (p->object.flags & STUDYING)101continue;102p->object.flags |= STUDYING;103add_object_array(&p->object, NULL, &study);104add_object_array(&p->object, NULL, &found);105}106}107if (!is_incomplete) {108/*109* make sure all commits in "found" array have all the
110* necessary objects.
111*/
112for (i = 0; i < found.nr; i++) {113struct commit *c =114(struct commit *)found.objects[i].item;115if (!tree_is_complete(get_commit_tree_oid(c))) {116is_incomplete = 1;117c->object.flags |= INCOMPLETE;118}119}120if (!is_incomplete) {121/* mark all found commits as complete, iow SEEN */122for (i = 0; i < found.nr; i++)123found.objects[i].item->flags |= SEEN;124}125}126/* clear flags from the objects we traversed */127for (i = 0; i < found.nr; i++)128found.objects[i].item->flags &= ~STUDYING;129if (is_incomplete)130commit->object.flags |= INCOMPLETE;131else {132/*133* If we come here, we have (1) traversed the ancestry chain
134* from the "commit" until we reach SEEN commits (which are
135* known to be complete), and (2) made sure that the commits
136* encountered during the above traversal refer to trees that
137* are complete. Which means that we know *all* the commits
138* we have seen during this process are complete.
139*/
140for (i = 0; i < found.nr; i++)141found.objects[i].item->flags |= SEEN;142}143/* free object arrays */144object_array_clear(&study);145object_array_clear(&found);146return !is_incomplete;147}
148
149static int keep_entry(struct commit **it, struct object_id *oid)150{
151struct commit *commit;152
153if (is_null_oid(oid))154return 1;155commit = lookup_commit_reference_gently(the_repository, oid, 1);156if (!commit)157return 0;158
159/*160* Make sure everything in this commit exists.
161*
162* We have walked all the objects reachable from the refs
163* and cache earlier. The commits reachable by this commit
164* must meet SEEN commits -- and then we should mark them as
165* SEEN as well.
166*/
167if (!commit_is_complete(commit))168return 0;169*it = commit;170return 1;171}
172
173/*
174* Starting from commits in the cb->mark_list, mark commits that are
175* reachable from them. Stop the traversal at commits older than
176* the expire_limit and queue them back, so that the caller can call
177* us again to restart the traversal with longer expire_limit.
178*/
179static void mark_reachable(struct expire_reflog_policy_cb *cb)180{
181struct commit_list *pending;182timestamp_t expire_limit = cb->mark_limit;183struct commit_list *leftover = NULL;184
185for (pending = cb->mark_list; pending; pending = pending->next)186pending->item->object.flags &= ~REACHABLE;187
188pending = cb->mark_list;189while (pending) {190struct commit_list *parent;191struct commit *commit = pop_commit(&pending);192if (commit->object.flags & REACHABLE)193continue;194if (repo_parse_commit(the_repository, commit))195continue;196commit->object.flags |= REACHABLE;197if (commit->date < expire_limit) {198commit_list_insert(commit, &leftover);199continue;200}201parent = commit->parents;202while (parent) {203commit = parent->item;204parent = parent->next;205if (commit->object.flags & REACHABLE)206continue;207commit_list_insert(commit, &pending);208}209}210cb->mark_list = leftover;211}
212
213static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)214{
215/*216* We may or may not have the commit yet - if not, look it
217* up using the supplied sha1.
218*/
219if (!commit) {220if (is_null_oid(oid))221return 0;222
223commit = lookup_commit_reference_gently(the_repository, oid,2241);225
226/* Not a commit -- keep it */227if (!commit)228return 0;229}230
231/* Reachable from the current ref? Don't prune. */232if (commit->object.flags & REACHABLE)233return 0;234
235if (cb->mark_list && cb->mark_limit) {236cb->mark_limit = 0; /* dig down to the root */237mark_reachable(cb);238}239
240return !(commit->object.flags & REACHABLE);241}
242
243/*
244* Return true iff the specified reflog entry should be expired.
245*/
246int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,247const char *email UNUSED,248timestamp_t timestamp, int tz UNUSED,249const char *message UNUSED, void *cb_data)250{
251struct expire_reflog_policy_cb *cb = cb_data;252struct commit *old_commit, *new_commit;253
254if (timestamp < cb->cmd.expire_total)255return 1;256
257old_commit = new_commit = NULL;258if (cb->cmd.stalefix &&259(!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))260return 1;261
262if (timestamp < cb->cmd.expire_unreachable) {263switch (cb->unreachable_expire_kind) {264case UE_ALWAYS:265return 1;266case UE_NORMAL:267case UE_HEAD:268if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))269return 1;270break;271}272}273
274if (cb->cmd.recno && --(cb->cmd.recno) == 0)275return 1;276
277return 0;278}
279
280int should_expire_reflog_ent_verbose(struct object_id *ooid,281struct object_id *noid,282const char *email,283timestamp_t timestamp, int tz,284const char *message, void *cb_data)285{
286struct expire_reflog_policy_cb *cb = cb_data;287int expire;288
289expire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,290message, cb);291
292if (!expire)293printf("keep %s", message);294else if (cb->dry_run)295printf("would prune %s", message);296else297printf("prune %s", message);298
299return expire;300}
301
302static int push_tip_to_list(const char *refname UNUSED,303const char *referent UNUSED,304const struct object_id *oid,305int flags, void *cb_data)306{
307struct commit_list **list = cb_data;308struct commit *tip_commit;309if (flags & REF_ISSYMREF)310return 0;311tip_commit = lookup_commit_reference_gently(the_repository, oid, 1);312if (!tip_commit)313return 0;314commit_list_insert(tip_commit, list);315return 0;316}
317
318static int is_head(const char *refname)319{
320const char *stripped_refname;321parse_worktree_ref(refname, NULL, NULL, &stripped_refname);322return !strcmp(stripped_refname, "HEAD");323}
324
325void reflog_expiry_prepare(const char *refname,326const struct object_id *oid,327void *cb_data)328{
329struct expire_reflog_policy_cb *cb = cb_data;330struct commit_list *elem;331struct commit *commit = NULL;332
333if (!cb->cmd.expire_unreachable || is_head(refname)) {334cb->unreachable_expire_kind = UE_HEAD;335} else {336commit = lookup_commit_reference_gently(the_repository,337oid, 1);338if (commit && is_null_oid(&commit->object.oid))339commit = NULL;340cb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;341}342
343if (cb->cmd.expire_unreachable <= cb->cmd.expire_total)344cb->unreachable_expire_kind = UE_ALWAYS;345
346switch (cb->unreachable_expire_kind) {347case UE_ALWAYS:348return;349case UE_HEAD:350refs_for_each_ref(get_main_ref_store(the_repository),351push_tip_to_list, &cb->tips);352for (elem = cb->tips; elem; elem = elem->next)353commit_list_insert(elem->item, &cb->mark_list);354break;355case UE_NORMAL:356commit_list_insert(commit, &cb->mark_list);357/* For reflog_expiry_cleanup() below */358cb->tip_commit = commit;359}360cb->mark_limit = cb->cmd.expire_total;361mark_reachable(cb);362}
363
364void reflog_expiry_cleanup(void *cb_data)365{
366struct expire_reflog_policy_cb *cb = cb_data;367struct commit_list *elem;368
369switch (cb->unreachable_expire_kind) {370case UE_ALWAYS:371return;372case UE_HEAD:373for (elem = cb->tips; elem; elem = elem->next)374clear_commit_marks(elem->item, REACHABLE);375free_commit_list(cb->tips);376break;377case UE_NORMAL:378clear_commit_marks(cb->tip_commit, REACHABLE);379break;380}381for (elem = cb->mark_list; elem; elem = elem->next)382clear_commit_marks(elem->item, REACHABLE);383free_commit_list(cb->mark_list);384}
385
386int count_reflog_ent(struct object_id *ooid UNUSED,387struct object_id *noid UNUSED,388const char *email UNUSED,389timestamp_t timestamp, int tz UNUSED,390const char *message UNUSED, void *cb_data)391{
392struct cmd_reflog_expire_cb *cb = cb_data;393if (!cb->expire_total || timestamp < cb->expire_total)394cb->recno++;395return 0;396}
397
398int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose)399{
400struct cmd_reflog_expire_cb cmd = { 0 };401int status = 0;402reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;403const char *spec = strstr(rev, "@{");404char *ep, *ref;405int recno;406struct expire_reflog_policy_cb cb = {407.dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),408};409
410if (verbose)411should_prune_fn = should_expire_reflog_ent_verbose;412
413if (!spec)414return error(_("not a reflog: %s"), rev);415
416if (!repo_dwim_log(the_repository, rev, spec - rev, NULL, &ref)) {417status |= error(_("no reflog for '%s'"), rev);418goto cleanup;419}420
421recno = strtoul(spec + 2, &ep, 10);422if (*ep == '}') {423cmd.recno = -recno;424refs_for_each_reflog_ent(get_main_ref_store(the_repository),425ref, count_reflog_ent, &cmd);426} else {427cmd.expire_total = approxidate(spec + 2);428refs_for_each_reflog_ent(get_main_ref_store(the_repository),429ref, count_reflog_ent, &cmd);430cmd.expire_total = 0;431}432
433cb.cmd = cmd;434status |= refs_reflog_expire(get_main_ref_store(the_repository), ref,435flags,436reflog_expiry_prepare,437should_prune_fn,438reflog_expiry_cleanup,439&cb);440
441cleanup:442free(ref);443return status;444}
445