Move traversal of reachable objects into a separate library.
[git/git.git] / builtin-reflog.c
CommitLineData
4264dc15
JH
1#include "cache.h"
2#include "builtin.h"
3#include "commit.h"
4#include "refs.h"
5#include "dir.h"
8d8b9f62 6#include "tree-walk.h"
4264dc15 7
4aec56d1
JH
8static unsigned long default_reflog_expire;
9static unsigned long default_reflog_expire_unreachable;
10
4264dc15
JH
11struct expire_reflog_cb {
12 FILE *newlog;
13 const char *ref;
14 struct commit *ref_commit;
15 unsigned long expire_total;
16 unsigned long expire_unreachable;
17};
18
8d8b9f62
JH
19static int tree_is_complete(const unsigned char *sha1)
20{
21 struct tree_desc desc;
22 void *buf;
23 char type[20];
24
25 buf = read_sha1_file(sha1, type, &desc.size);
26 if (!buf)
27 return 0;
28 desc.buf = buf;
29 while (desc.size) {
30 const unsigned char *elem;
31 const char *name;
32 unsigned mode;
33
34 elem = tree_entry_extract(&desc, &name, &mode);
35 if (!has_sha1_file(elem) ||
36 (S_ISDIR(mode) && !tree_is_complete(elem))) {
37 free(buf);
38 return 0;
39 }
40 update_tree_entry(&desc);
41 }
42 free(buf);
43 return 1;
44}
45
4264dc15
JH
46static int keep_entry(struct commit **it, unsigned char *sha1)
47{
8d8b9f62
JH
48 struct commit *commit;
49
4264dc15
JH
50 *it = NULL;
51 if (is_null_sha1(sha1))
52 return 1;
8d8b9f62
JH
53 commit = lookup_commit_reference_gently(sha1, 1);
54 if (!commit)
55 return 0;
56
57 /* Make sure everything in this commit exists. */
58 parse_object(commit->object.sha1);
59 if (!tree_is_complete(commit->tree->object.sha1))
60 return 0;
61 *it = commit;
62 return 1;
4264dc15
JH
63}
64
65static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
66 char *data, void *cb_data)
67{
68 struct expire_reflog_cb *cb = cb_data;
69 unsigned long timestamp;
70 char *cp, *ep;
71 struct commit *old, *new;
72
73 cp = strchr(data, '>');
74 if (!cp || *++cp != ' ')
75 goto prune;
76 timestamp = strtoul(cp, &ep, 10);
77 if (*ep != ' ')
78 goto prune;
79 if (timestamp < cb->expire_total)
80 goto prune;
81
82 if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))
83 goto prune;
84
85 if ((timestamp < cb->expire_unreachable) &&
58748293
JH
86 (!cb->ref_commit ||
87 (old && !in_merge_bases(old, cb->ref_commit)) ||
4264dc15
JH
88 (new && !in_merge_bases(new, cb->ref_commit))))
89 goto prune;
90
91 if (cb->newlog)
92 fprintf(cb->newlog, "%s %s %s",
93 sha1_to_hex(osha1), sha1_to_hex(nsha1), data);
94 return 0;
95 prune:
96 if (!cb->newlog)
97 fprintf(stderr, "would prune %s", data);
98 return 0;
99}
100
101struct cmd_reflog_expire_cb {
102 int dry_run;
103 unsigned long expire_total;
104 unsigned long expire_unreachable;
105};
106
107static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
108{
109 struct cmd_reflog_expire_cb *cmd = cb_data;
110 struct expire_reflog_cb cb;
111 struct ref_lock *lock;
112 char *newlog_path = NULL;
113 int status = 0;
114
115 if (strncmp(ref, "refs/", 5))
116 return error("not a ref '%s'", ref);
117
118 memset(&cb, 0, sizeof(cb));
119 /* we take the lock for the ref itself to prevent it from
120 * getting updated.
121 */
122 lock = lock_ref_sha1(ref + 5, sha1);
123 if (!lock)
124 return error("cannot lock ref '%s'", ref);
125 if (!file_exists(lock->log_file))
126 goto finish;
127 if (!cmd->dry_run) {
128 newlog_path = xstrdup(git_path("logs/%s.lock", ref));
129 cb.newlog = fopen(newlog_path, "w");
130 }
131
132 cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
58748293
JH
133 if (!cb.ref_commit)
134 fprintf(stderr,
135 "warning: ref '%s' does not point at a commit\n", ref);
4264dc15
JH
136 cb.ref = ref;
137 cb.expire_total = cmd->expire_total;
138 cb.expire_unreachable = cmd->expire_unreachable;
139 for_each_reflog_ent(ref, expire_reflog_ent, &cb);
140 finish:
141 if (cb.newlog) {
142 if (fclose(cb.newlog))
143 status |= error("%s: %s", strerror(errno),
144 newlog_path);
145 if (rename(newlog_path, lock->log_file)) {
146 status |= error("cannot rename %s to %s",
147 newlog_path, lock->log_file);
148 unlink(newlog_path);
149 }
150 }
151 free(newlog_path);
152 unlock_ref(lock);
153 return status;
154}
155
4aec56d1
JH
156static int reflog_expire_config(const char *var, const char *value)
157{
158 if (!strcmp(var, "gc.reflogexpire"))
159 default_reflog_expire = approxidate(value);
160 else if (!strcmp(var, "gc.reflogexpireunreachable"))
161 default_reflog_expire_unreachable = approxidate(value);
162 else
163 return git_default_config(var, value);
164 return 0;
165}
166
4264dc15
JH
167static const char reflog_expire_usage[] =
168"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
169
170static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
171{
172 struct cmd_reflog_expire_cb cb;
173 unsigned long now = time(NULL);
174 int i, status, do_all;
175
4aec56d1
JH
176 git_config(reflog_expire_config);
177
4264dc15
JH
178 save_commit_buffer = 0;
179 do_all = status = 0;
180 memset(&cb, 0, sizeof(cb));
4aec56d1
JH
181
182 if (!default_reflog_expire_unreachable)
183 default_reflog_expire_unreachable = now - 30 * 24 * 3600;
184 if (!default_reflog_expire)
185 default_reflog_expire = now - 90 * 24 * 3600;
186 cb.expire_total = default_reflog_expire;
187 cb.expire_unreachable = default_reflog_expire_unreachable;
4264dc15
JH
188
189 for (i = 1; i < argc; i++) {
190 const char *arg = argv[i];
191 if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
192 cb.dry_run = 1;
193 else if (!strncmp(arg, "--expire=", 9))
194 cb.expire_total = approxidate(arg + 9);
195 else if (!strncmp(arg, "--expire-unreachable=", 21))
196 cb.expire_unreachable = approxidate(arg + 21);
197 else if (!strcmp(arg, "--all"))
198 do_all = 1;
199 else if (!strcmp(arg, "--")) {
200 i++;
201 break;
202 }
203 else if (arg[0] == '-')
204 usage(reflog_expire_usage);
205 else
206 break;
207 }
208 if (do_all)
209 status |= for_each_ref(expire_reflog, &cb);
210 while (i < argc) {
211 const char *ref = argv[i++];
212 unsigned char sha1[20];
213 if (!resolve_ref(ref, sha1, 1, NULL)) {
214 status |= error("%s points nowhere!", ref);
215 continue;
216 }
217 status |= expire_reflog(ref, sha1, 0, &cb);
218 }
219 return status;
220}
221
222static const char reflog_usage[] =
223"git-reflog (expire | ...)";
224
225int cmd_reflog(int argc, const char **argv, const char *prefix)
226{
227 if (argc < 2)
228 usage(reflog_usage);
229 else if (!strcmp(argv[1], "expire"))
230 return cmd_reflog_expire(argc - 1, argv + 1, prefix);
231 else
232 usage(reflog_usage);
233}