Code

t/: Replace diff [-u|-U0] with test_cmp to allow compilation with old diff
[git.git] / builtin-reflog.c
1 #include "cache.h"
2 #include "builtin.h"
3 #include "commit.h"
4 #include "refs.h"
5 #include "dir.h"
6 #include "tree-walk.h"
7 #include "diff.h"
8 #include "revision.h"
9 #include "reachable.h"
11 /*
12  * reflog expire
13  */
15 static const char reflog_expire_usage[] =
16 "git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
17 static const char reflog_delete_usage[] =
18 "git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
20 static unsigned long default_reflog_expire;
21 static unsigned long default_reflog_expire_unreachable;
23 struct cmd_reflog_expire_cb {
24         struct rev_info revs;
25         int dry_run;
26         int stalefix;
27         int rewrite;
28         int updateref;
29         int verbose;
30         unsigned long expire_total;
31         unsigned long expire_unreachable;
32         int recno;
33 };
35 struct expire_reflog_cb {
36         FILE *newlog;
37         const char *ref;
38         struct commit *ref_commit;
39         struct cmd_reflog_expire_cb *cmd;
40         unsigned char last_kept_sha1[20];
41 };
43 struct collected_reflog {
44         unsigned char sha1[20];
45         char reflog[FLEX_ARRAY];
46 };
47 struct collect_reflog_cb {
48         struct collected_reflog **e;
49         int alloc;
50         int nr;
51 };
53 #define INCOMPLETE      (1u<<10)
54 #define STUDYING        (1u<<11)
56 static int tree_is_complete(const unsigned char *sha1)
57 {
58         struct tree_desc desc;
59         struct name_entry entry;
60         int complete;
61         struct tree *tree;
63         tree = lookup_tree(sha1);
64         if (!tree)
65                 return 0;
66         if (tree->object.flags & SEEN)
67                 return 1;
68         if (tree->object.flags & INCOMPLETE)
69                 return 0;
71         if (!tree->buffer) {
72                 enum object_type type;
73                 unsigned long size;
74                 void *data = read_sha1_file(sha1, &type, &size);
75                 if (!data) {
76                         tree->object.flags |= INCOMPLETE;
77                         return 0;
78                 }
79                 tree->buffer = data;
80                 tree->size = size;
81         }
82         init_tree_desc(&desc, tree->buffer, tree->size);
83         complete = 1;
84         while (tree_entry(&desc, &entry)) {
85                 if (!has_sha1_file(entry.sha1) ||
86                     (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
87                         tree->object.flags |= INCOMPLETE;
88                         complete = 0;
89                 }
90         }
91         free(tree->buffer);
92         tree->buffer = NULL;
94         if (complete)
95                 tree->object.flags |= SEEN;
96         return complete;
97 }
99 static int commit_is_complete(struct commit *commit)
101         struct object_array study;
102         struct object_array found;
103         int is_incomplete = 0;
104         int i;
106         /* early return */
107         if (commit->object.flags & SEEN)
108                 return 1;
109         if (commit->object.flags & INCOMPLETE)
110                 return 0;
111         /*
112          * Find all commits that are reachable and are not marked as
113          * SEEN.  Then make sure the trees and blobs contained are
114          * complete.  After that, mark these commits also as SEEN.
115          * If some of the objects that are needed to complete this
116          * commit are missing, mark this commit as INCOMPLETE.
117          */
118         memset(&study, 0, sizeof(study));
119         memset(&found, 0, sizeof(found));
120         add_object_array(&commit->object, NULL, &study);
121         add_object_array(&commit->object, NULL, &found);
122         commit->object.flags |= STUDYING;
123         while (study.nr) {
124                 struct commit *c;
125                 struct commit_list *parent;
127                 c = (struct commit *)study.objects[--study.nr].item;
128                 if (!c->object.parsed && !parse_object(c->object.sha1))
129                         c->object.flags |= INCOMPLETE;
131                 if (c->object.flags & INCOMPLETE) {
132                         is_incomplete = 1;
133                         break;
134                 }
135                 else if (c->object.flags & SEEN)
136                         continue;
137                 for (parent = c->parents; parent; parent = parent->next) {
138                         struct commit *p = parent->item;
139                         if (p->object.flags & STUDYING)
140                                 continue;
141                         p->object.flags |= STUDYING;
142                         add_object_array(&p->object, NULL, &study);
143                         add_object_array(&p->object, NULL, &found);
144                 }
145         }
146         if (!is_incomplete) {
147                 /*
148                  * make sure all commits in "found" array have all the
149                  * necessary objects.
150                  */
151                 for (i = 0; i < found.nr; i++) {
152                         struct commit *c =
153                                 (struct commit *)found.objects[i].item;
154                         if (!tree_is_complete(c->tree->object.sha1)) {
155                                 is_incomplete = 1;
156                                 c->object.flags |= INCOMPLETE;
157                         }
158                 }
159                 if (!is_incomplete) {
160                         /* mark all found commits as complete, iow SEEN */
161                         for (i = 0; i < found.nr; i++)
162                                 found.objects[i].item->flags |= SEEN;
163                 }
164         }
165         /* clear flags from the objects we traversed */
166         for (i = 0; i < found.nr; i++)
167                 found.objects[i].item->flags &= ~STUDYING;
168         if (is_incomplete)
169                 commit->object.flags |= INCOMPLETE;
170         else {
171                 /*
172                  * If we come here, we have (1) traversed the ancestry chain
173                  * from the "commit" until we reach SEEN commits (which are
174                  * known to be complete), and (2) made sure that the commits
175                  * encountered during the above traversal refer to trees that
176                  * are complete.  Which means that we know *all* the commits
177                  * we have seen during this process are complete.
178                  */
179                 for (i = 0; i < found.nr; i++)
180                         found.objects[i].item->flags |= SEEN;
181         }
182         /* free object arrays */
183         free(study.objects);
184         free(found.objects);
185         return !is_incomplete;
188 static int keep_entry(struct commit **it, unsigned char *sha1)
190         struct commit *commit;
192         if (is_null_sha1(sha1))
193                 return 1;
194         commit = lookup_commit_reference_gently(sha1, 1);
195         if (!commit)
196                 return 0;
198         /*
199          * Make sure everything in this commit exists.
200          *
201          * We have walked all the objects reachable from the refs
202          * and cache earlier.  The commits reachable by this commit
203          * must meet SEEN commits -- and then we should mark them as
204          * SEEN as well.
205          */
206         if (!commit_is_complete(commit))
207                 return 0;
208         *it = commit;
209         return 1;
212 static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
213                 const char *email, unsigned long timestamp, int tz,
214                 const char *message, void *cb_data)
216         struct expire_reflog_cb *cb = cb_data;
217         struct commit *old, *new;
219         if (timestamp < cb->cmd->expire_total)
220                 goto prune;
222         if (cb->cmd->rewrite)
223                 osha1 = cb->last_kept_sha1;
225         old = new = NULL;
226         if (cb->cmd->stalefix &&
227             (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
228                 goto prune;
230         if (timestamp < cb->cmd->expire_unreachable) {
231                 if (!cb->ref_commit)
232                         goto prune;
233                 if (!old && !is_null_sha1(osha1))
234                         old = lookup_commit_reference_gently(osha1, 1);
235                 if (!new && !is_null_sha1(nsha1))
236                         new = lookup_commit_reference_gently(nsha1, 1);
237                 if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) ||
238                     (new && !in_merge_bases(new, &cb->ref_commit, 1)))
239                         goto prune;
240         }
242         if (cb->cmd->recno && --(cb->cmd->recno) == 0)
243                 goto prune;
245         if (cb->newlog) {
246                 char sign = (tz < 0) ? '-' : '+';
247                 int zone = (tz < 0) ? (-tz) : tz;
248                 fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
249                         sha1_to_hex(osha1), sha1_to_hex(nsha1),
250                         email, timestamp, sign, zone,
251                         message);
252                 hashcpy(cb->last_kept_sha1, nsha1);
253         }
254         if (cb->cmd->verbose)
255                 printf("keep %s", message);
256         return 0;
257  prune:
258         if (!cb->newlog || cb->cmd->verbose)
259                 printf("%sprune %s", cb->newlog ? "" : "would ", message);
260         return 0;
263 static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
265         struct cmd_reflog_expire_cb *cmd = cb_data;
266         struct expire_reflog_cb cb;
267         struct ref_lock *lock;
268         char *log_file, *newlog_path = NULL;
269         int status = 0;
271         memset(&cb, 0, sizeof(cb));
273         /*
274          * we take the lock for the ref itself to prevent it from
275          * getting updated.
276          */
277         lock = lock_any_ref_for_update(ref, sha1, 0);
278         if (!lock)
279                 return error("cannot lock ref '%s'", ref);
280         log_file = xstrdup(git_path("logs/%s", ref));
281         if (!file_exists(log_file))
282                 goto finish;
283         if (!cmd->dry_run) {
284                 newlog_path = xstrdup(git_path("logs/%s.lock", ref));
285                 cb.newlog = fopen(newlog_path, "w");
286         }
288         cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
289         cb.ref = ref;
290         cb.cmd = cmd;
291         for_each_reflog_ent(ref, expire_reflog_ent, &cb);
292  finish:
293         if (cb.newlog) {
294                 if (fclose(cb.newlog)) {
295                         status |= error("%s: %s", strerror(errno),
296                                         newlog_path);
297                         unlink(newlog_path);
298                 } else if (cmd->updateref &&
299                         (write_in_full(lock->lock_fd,
300                                 sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
301                          write_in_full(lock->lock_fd, "\n", 1) != 1 ||
302                          close_ref(lock) < 0)) {
303                         status |= error("Couldn't write %s",
304                                 lock->lk->filename);
305                         unlink(newlog_path);
306                 } else if (rename(newlog_path, log_file)) {
307                         status |= error("cannot rename %s to %s",
308                                         newlog_path, log_file);
309                         unlink(newlog_path);
310                 } else if (cmd->updateref && commit_ref(lock)) {
311                         status |= error("Couldn't set %s", lock->ref_name);
312                 } else {
313                         adjust_shared_perm(log_file);
314                 }
315         }
316         free(newlog_path);
317         free(log_file);
318         unlock_ref(lock);
319         return status;
322 static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
324         struct collected_reflog *e;
325         struct collect_reflog_cb *cb = cb_data;
326         size_t namelen = strlen(ref);
328         e = xmalloc(sizeof(*e) + namelen + 1);
329         hashcpy(e->sha1, sha1);
330         memcpy(e->reflog, ref, namelen + 1);
331         ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
332         cb->e[cb->nr++] = e;
333         return 0;
336 static struct reflog_expire_cfg {
337         struct reflog_expire_cfg *next;
338         unsigned long expire_total;
339         unsigned long expire_unreachable;
340         size_t len;
341         char pattern[FLEX_ARRAY];
342 } *reflog_expire_cfg, **reflog_expire_cfg_tail;
344 static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
346         struct reflog_expire_cfg *ent;
348         if (!reflog_expire_cfg_tail)
349                 reflog_expire_cfg_tail = &reflog_expire_cfg;
351         for (ent = reflog_expire_cfg; ent; ent = ent->next)
352                 if (ent->len == len &&
353                     !memcmp(ent->pattern, pattern, len))
354                         return ent;
356         ent = xcalloc(1, (sizeof(*ent) + len));
357         memcpy(ent->pattern, pattern, len);
358         ent->len = len;
359         *reflog_expire_cfg_tail = ent;
360         reflog_expire_cfg_tail = &(ent->next);
361         return ent;
364 static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)
366         if (!value)
367                 return config_error_nonbool(var);
368         if (!strcmp(value, "never") || !strcmp(value, "false")) {
369                 *expire = 0;
370                 return 0;
371         }
372         *expire = approxidate(value);
373         return 0;
376 /* expiry timer slot */
377 #define EXPIRE_TOTAL   01
378 #define EXPIRE_UNREACH 02
380 static int reflog_expire_config(const char *var, const char *value, void *cb)
382         const char *lastdot = strrchr(var, '.');
383         unsigned long expire;
384         int slot;
385         struct reflog_expire_cfg *ent;
387         if (!lastdot || prefixcmp(var, "gc."))
388                 return git_default_config(var, value, cb);
390         if (!strcmp(lastdot, ".reflogexpire")) {
391                 slot = EXPIRE_TOTAL;
392                 if (parse_expire_cfg_value(var, value, &expire))
393                         return -1;
394         } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
395                 slot = EXPIRE_UNREACH;
396                 if (parse_expire_cfg_value(var, value, &expire))
397                         return -1;
398         } else
399                 return git_default_config(var, value, cb);
401         if (lastdot == var + 2) {
402                 switch (slot) {
403                 case EXPIRE_TOTAL:
404                         default_reflog_expire = expire;
405                         break;
406                 case EXPIRE_UNREACH:
407                         default_reflog_expire_unreachable = expire;
408                         break;
409                 }
410                 return 0;
411         }
413         ent = find_cfg_ent(var + 3, lastdot - (var+3));
414         if (!ent)
415                 return -1;
416         switch (slot) {
417         case EXPIRE_TOTAL:
418                 ent->expire_total = expire;
419                 break;
420         case EXPIRE_UNREACH:
421                 ent->expire_unreachable = expire;
422                 break;
423         }
424         return 0;
427 static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
429         struct reflog_expire_cfg *ent;
431         if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
432                 return; /* both given explicitly -- nothing to tweak */
434         for (ent = reflog_expire_cfg; ent; ent = ent->next) {
435                 if (!fnmatch(ent->pattern, ref, 0)) {
436                         if (!(slot & EXPIRE_TOTAL))
437                                 cb->expire_total = ent->expire_total;
438                         if (!(slot & EXPIRE_UNREACH))
439                                 cb->expire_unreachable = ent->expire_unreachable;
440                         return;
441                 }
442         }
444         /*
445          * If unconfigured, make stash never expire
446          */
447         if (!strcmp(ref, "refs/stash")) {
448                 if (!(slot & EXPIRE_TOTAL))
449                         cb->expire_total = 0;
450                 if (!(slot & EXPIRE_UNREACH))
451                         cb->expire_unreachable = 0;
452                 return;
453         }
455         /* Nothing matched -- use the default value */
456         if (!(slot & EXPIRE_TOTAL))
457                 cb->expire_total = default_reflog_expire;
458         if (!(slot & EXPIRE_UNREACH))
459                 cb->expire_unreachable = default_reflog_expire_unreachable;
462 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
464         struct cmd_reflog_expire_cb cb;
465         unsigned long now = time(NULL);
466         int i, status, do_all;
467         int explicit_expiry = 0;
469         git_config(reflog_expire_config, NULL);
471         save_commit_buffer = 0;
472         do_all = status = 0;
473         memset(&cb, 0, sizeof(cb));
475         if (!default_reflog_expire_unreachable)
476                 default_reflog_expire_unreachable = now - 30 * 24 * 3600;
477         if (!default_reflog_expire)
478                 default_reflog_expire = now - 90 * 24 * 3600;
479         cb.expire_total = default_reflog_expire;
480         cb.expire_unreachable = default_reflog_expire_unreachable;
482         for (i = 1; i < argc; i++) {
483                 const char *arg = argv[i];
484                 if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
485                         cb.dry_run = 1;
486                 else if (!prefixcmp(arg, "--expire=")) {
487                         cb.expire_total = approxidate(arg + 9);
488                         explicit_expiry |= EXPIRE_TOTAL;
489                 }
490                 else if (!prefixcmp(arg, "--expire-unreachable=")) {
491                         cb.expire_unreachable = approxidate(arg + 21);
492                         explicit_expiry |= EXPIRE_UNREACH;
493                 }
494                 else if (!strcmp(arg, "--stale-fix"))
495                         cb.stalefix = 1;
496                 else if (!strcmp(arg, "--rewrite"))
497                         cb.rewrite = 1;
498                 else if (!strcmp(arg, "--updateref"))
499                         cb.updateref = 1;
500                 else if (!strcmp(arg, "--all"))
501                         do_all = 1;
502                 else if (!strcmp(arg, "--verbose"))
503                         cb.verbose = 1;
504                 else if (!strcmp(arg, "--")) {
505                         i++;
506                         break;
507                 }
508                 else if (arg[0] == '-')
509                         usage(reflog_expire_usage);
510                 else
511                         break;
512         }
514         /*
515          * We can trust the commits and objects reachable from refs
516          * even in older repository.  We cannot trust what's reachable
517          * from reflog if the repository was pruned with older git.
518          */
519         if (cb.stalefix) {
520                 init_revisions(&cb.revs, prefix);
521                 if (cb.verbose)
522                         printf("Marking reachable objects...");
523                 mark_reachable_objects(&cb.revs, 0);
524                 if (cb.verbose)
525                         putchar('\n');
526         }
528         if (do_all) {
529                 struct collect_reflog_cb collected;
530                 int i;
532                 memset(&collected, 0, sizeof(collected));
533                 for_each_reflog(collect_reflog, &collected);
534                 for (i = 0; i < collected.nr; i++) {
535                         struct collected_reflog *e = collected.e[i];
536                         set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
537                         status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
538                         free(e);
539                 }
540                 free(collected.e);
541         }
543         while (i < argc) {
544                 const char *ref = argv[i++];
545                 unsigned char sha1[20];
546                 if (!resolve_ref(ref, sha1, 1, NULL)) {
547                         status |= error("%s points nowhere!", ref);
548                         continue;
549                 }
550                 set_reflog_expiry_param(&cb, explicit_expiry, ref);
551                 status |= expire_reflog(ref, sha1, 0, &cb);
552         }
553         return status;
556 static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
557                 const char *email, unsigned long timestamp, int tz,
558                 const char *message, void *cb_data)
560         struct cmd_reflog_expire_cb *cb = cb_data;
561         if (!cb->expire_total || timestamp < cb->expire_total)
562                 cb->recno++;
563         return 0;
566 static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
568         struct cmd_reflog_expire_cb cb;
569         int i, status = 0;
571         memset(&cb, 0, sizeof(cb));
573         for (i = 1; i < argc; i++) {
574                 const char *arg = argv[i];
575                 if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
576                         cb.dry_run = 1;
577                 else if (!strcmp(arg, "--rewrite"))
578                         cb.rewrite = 1;
579                 else if (!strcmp(arg, "--updateref"))
580                         cb.updateref = 1;
581                 else if (!strcmp(arg, "--verbose"))
582                         cb.verbose = 1;
583                 else if (!strcmp(arg, "--")) {
584                         i++;
585                         break;
586                 }
587                 else if (arg[0] == '-')
588                         usage(reflog_delete_usage);
589                 else
590                         break;
591         }
593         if (argc - i < 1)
594                 return error("Nothing to delete?");
596         for ( ; i < argc; i++) {
597                 const char *spec = strstr(argv[i], "@{");
598                 unsigned char sha1[20];
599                 char *ep, *ref;
600                 int recno;
602                 if (!spec) {
603                         status |= error("Not a reflog: %s", argv[i]);
604                         continue;
605                 }
607                 if (!dwim_ref(argv[i], spec - argv[i], sha1, &ref)) {
608                         status |= error("%s points nowhere!", argv[i]);
609                         continue;
610                 }
612                 recno = strtoul(spec + 2, &ep, 10);
613                 if (*ep == '}') {
614                         cb.recno = -recno;
615                         for_each_reflog_ent(ref, count_reflog_ent, &cb);
616                 } else {
617                         cb.expire_total = approxidate(spec + 2);
618                         for_each_reflog_ent(ref, count_reflog_ent, &cb);
619                         cb.expire_total = 0;
620                 }
622                 status |= expire_reflog(ref, sha1, 0, &cb);
623                 free(ref);
624         }
625         return status;
628 /*
629  * main "reflog"
630  */
632 static const char reflog_usage[] =
633 "git reflog (expire | ...)";
635 int cmd_reflog(int argc, const char **argv, const char *prefix)
637         /* With no command, we default to showing it. */
638         if (argc < 2 || *argv[1] == '-')
639                 return cmd_log_reflog(argc, argv, prefix);
641         if (!strcmp(argv[1], "show"))
642                 return cmd_log_reflog(argc - 1, argv + 1, prefix);
644         if (!strcmp(argv[1], "expire"))
645                 return cmd_reflog_expire(argc - 1, argv + 1, prefix);
647         if (!strcmp(argv[1], "delete"))
648                 return cmd_reflog_delete(argc - 1, argv + 1, prefix);
650         /* Not a recognized reflog command..*/
651         usage(reflog_usage);