1#include "cache.h"
2#include "builtin.h"
3#include "commit.h"
4#include "refs.h"
5#include "dir.h"
6#include <time.h>
7
8struct expire_reflog_cb {
9 FILE *newlog;
10 const char *ref;
11 struct commit *ref_commit;
12 unsigned long expire_total;
13 unsigned long expire_unreachable;
14};
15
16static int keep_entry(struct commit **it, unsigned char *sha1)
17{
18 *it = NULL;
19 if (is_null_sha1(sha1))
20 return 1;
21 *it = lookup_commit_reference_gently(sha1, 1);
22 return (*it != NULL);
23}
24
25static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
26 char *data, void *cb_data)
27{
28 struct expire_reflog_cb *cb = cb_data;
29 unsigned long timestamp;
30 char *cp, *ep;
31 struct commit *old, *new;
32
33 cp = strchr(data, '>');
34 if (!cp || *++cp != ' ')
35 goto prune;
36 timestamp = strtoul(cp, &ep, 10);
37 if (*ep != ' ')
38 goto prune;
39 if (timestamp < cb->expire_total)
40 goto prune;
41
42 if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))
43 goto prune;
44
45 if ((timestamp < cb->expire_unreachable) &&
46 ((old && !in_merge_bases(old, cb->ref_commit)) ||
47 (new && !in_merge_bases(new, cb->ref_commit))))
48 goto prune;
49
50 if (cb->newlog)
51 fprintf(cb->newlog, "%s %s %s",
52 sha1_to_hex(osha1), sha1_to_hex(nsha1), data);
53 return 0;
54 prune:
55 if (!cb->newlog)
56 fprintf(stderr, "would prune %s", data);
57 return 0;
58}
59
60struct cmd_reflog_expire_cb {
61 int dry_run;
62 unsigned long expire_total;
63 unsigned long expire_unreachable;
64};
65
66static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
67{
68 struct cmd_reflog_expire_cb *cmd = cb_data;
69 struct expire_reflog_cb cb;
70 struct ref_lock *lock;
71 char *newlog_path = NULL;
72 int status = 0;
73
74 if (strncmp(ref, "refs/", 5))
75 return error("not a ref '%s'", ref);
76
77 memset(&cb, 0, sizeof(cb));
78 /* we take the lock for the ref itself to prevent it from
79 * getting updated.
80 */
81 lock = lock_ref_sha1(ref + 5, sha1);
82 if (!lock)
83 return error("cannot lock ref '%s'", ref);
84 if (!file_exists(lock->log_file))
85 goto finish;
86 if (!cmd->dry_run) {
87 newlog_path = xstrdup(git_path("logs/%s.lock", ref));
88 cb.newlog = fopen(newlog_path, "w");
89 }
90
91 cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
92 if (!cb.ref_commit) {
93 status = error("ref '%s' does not point at a commit", ref);
94 goto finish;
95 }
96 cb.ref = ref;
97 cb.expire_total = cmd->expire_total;
98 cb.expire_unreachable = cmd->expire_unreachable;
99 for_each_reflog_ent(ref, expire_reflog_ent, &cb);
100 finish:
101 if (cb.newlog) {
102 if (fclose(cb.newlog))
103 status |= error("%s: %s", strerror(errno),
104 newlog_path);
105 if (rename(newlog_path, lock->log_file)) {
106 status |= error("cannot rename %s to %s",
107 newlog_path, lock->log_file);
108 unlink(newlog_path);
109 }
110 }
111 free(newlog_path);
112 unlock_ref(lock);
113 return status;
114}
115
116static const char reflog_expire_usage[] =
117"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
118
119static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
120{
121 struct cmd_reflog_expire_cb cb;
122 unsigned long now = time(NULL);
123 int i, status, do_all;
124
125 save_commit_buffer = 0;
126 do_all = status = 0;
127 memset(&cb, 0, sizeof(cb));
128 cb.expire_total = now - 90 * 24 * 3600;
129 cb.expire_unreachable = now - 30 * 24 * 3600;
130
131 for (i = 1; i < argc; i++) {
132 const char *arg = argv[i];
133 if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
134 cb.dry_run = 1;
135 else if (!strncmp(arg, "--expire=", 9))
136 cb.expire_total = approxidate(arg + 9);
137 else if (!strncmp(arg, "--expire-unreachable=", 21))
138 cb.expire_unreachable = approxidate(arg + 21);
139 else if (!strcmp(arg, "--all"))
140 do_all = 1;
141 else if (!strcmp(arg, "--")) {
142 i++;
143 break;
144 }
145 else if (arg[0] == '-')
146 usage(reflog_expire_usage);
147 else
148 break;
149 }
150 if (do_all)
151 status |= for_each_ref(expire_reflog, &cb);
152 while (i < argc) {
153 const char *ref = argv[i++];
154 unsigned char sha1[20];
155 if (!resolve_ref(ref, sha1, 1, NULL)) {
156 status |= error("%s points nowhere!", ref);
157 continue;
158 }
159 status |= expire_reflog(ref, sha1, 0, &cb);
160 }
161 return status;
162}
163
164static const char reflog_usage[] =
165"git-reflog (expire | ...)";
166
167int cmd_reflog(int argc, const char **argv, const char *prefix)
168{
169 if (argc < 2)
170 usage(reflog_usage);
171 else if (!strcmp(argv[1], "expire"))
172 return cmd_reflog_expire(argc - 1, argv + 1, prefix);
173 else
174 usage(reflog_usage);
175}