builtin-prune: separate ref walking from reflog walking.
[git/jrn.git] / builtin-reflog.c
blobd3f2f50d2bc7b4ef0f0e4801c70c5a15ff869f1f
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"
8 static unsigned long default_reflog_expire;
9 static unsigned long default_reflog_expire_unreachable;
11 struct 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;
19 static int tree_is_complete(const unsigned char *sha1)
21 struct tree_desc desc;
22 void *buf;
23 char type[20];
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;
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;
40 update_tree_entry(&desc);
42 free(buf);
43 return 1;
46 static int keep_entry(struct commit **it, unsigned char *sha1)
48 struct commit *commit;
50 *it = NULL;
51 if (is_null_sha1(sha1))
52 return 1;
53 commit = lookup_commit_reference_gently(sha1, 1);
54 if (!commit)
55 return 0;
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;
65 static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
66 char *data, void *cb_data)
68 struct expire_reflog_cb *cb = cb_data;
69 unsigned long timestamp;
70 char *cp, *ep;
71 struct commit *old, *new;
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;
82 if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))
83 goto prune;
85 if ((timestamp < cb->expire_unreachable) &&
86 (!cb->ref_commit ||
87 (old && !in_merge_bases(old, cb->ref_commit)) ||
88 (new && !in_merge_bases(new, cb->ref_commit))))
89 goto prune;
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;
101 struct cmd_reflog_expire_cb {
102 int dry_run;
103 unsigned long expire_total;
104 unsigned long expire_unreachable;
107 static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
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;
115 if (strncmp(ref, "refs/", 5))
116 return error("not a ref '%s'", ref);
118 memset(&cb, 0, sizeof(cb));
119 /* we take the lock for the ref itself to prevent it from
120 * getting updated.
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");
132 cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
133 if (!cb.ref_commit)
134 fprintf(stderr,
135 "warning: ref '%s' does not point at a commit\n", ref);
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);
151 free(newlog_path);
152 unlock_ref(lock);
153 return status;
156 static int reflog_expire_config(const char *var, const char *value)
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;
167 static const char reflog_expire_usage[] =
168 "git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
170 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
172 struct cmd_reflog_expire_cb cb;
173 unsigned long now = time(NULL);
174 int i, status, do_all;
176 git_config(reflog_expire_config);
178 save_commit_buffer = 0;
179 do_all = status = 0;
180 memset(&cb, 0, sizeof(cb));
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;
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;
203 else if (arg[0] == '-')
204 usage(reflog_expire_usage);
205 else
206 break;
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;
217 status |= expire_reflog(ref, sha1, 0, &cb);
219 return status;
222 static const char reflog_usage[] =
223 "git-reflog (expire | ...)";
225 int cmd_reflog(int argc, const char **argv, const char *prefix)
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);