Skip to content

Commit 4264dc1

Browse files
author
Junio C Hamano
committed
git reflog expire
This prepares a place to collect reflog management subcommands, and implements "expire" action. $ git reflog expire --dry-run \ --expire=4.weeks \ --expire-unreachable=1.week \ refs/heads/master The expiration uses two timestamps: --expire and --expire-unreachable. Entries older than expire time (defaults to 90 days), and entries older than expire-unreachable time (defaults to 30 days) and records a commit that has been rewound and made unreachable from the current tip of the ref are removed from the reflog. The parameter handling is still rough, but I think the core logic for expiration is already sound. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2ecd2bb commit 4264dc1

File tree

4 files changed

+178
-0
lines changed

4 files changed

+178
-0
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ BUILTIN_OBJS = \
288288
builtin-prune-packed.o \
289289
builtin-push.o \
290290
builtin-read-tree.o \
291+
builtin-reflog.o \
291292
builtin-repo-config.o \
292293
builtin-rev-list.o \
293294
builtin-rev-parse.o \

builtin-reflog.c

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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+
8+
struct 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+
16+
static 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+
25+
static 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+
60+
struct cmd_reflog_expire_cb {
61+
int dry_run;
62+
unsigned long expire_total;
63+
unsigned long expire_unreachable;
64+
};
65+
66+
static 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+
116+
static const char reflog_expire_usage[] =
117+
"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
118+
119+
static 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+
164+
static const char reflog_usage[] =
165+
"git-reflog (expire | ...)";
166+
167+
int 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+
}

builtin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
5151
extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
5252
extern int cmd_push(int argc, const char **argv, const char *prefix);
5353
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
54+
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
5455
extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
5556
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
5657
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);

git.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
244244
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
245245
{ "push", cmd_push, RUN_SETUP },
246246
{ "read-tree", cmd_read_tree, RUN_SETUP },
247+
{ "reflog", cmd_reflog, RUN_SETUP },
247248
{ "repo-config", cmd_repo_config },
248249
{ "rev-list", cmd_rev_list, RUN_SETUP },
249250
{ "rev-parse", cmd_rev_parse, RUN_SETUP },

0 commit comments

Comments
 (0)