Skip to content

Commit 82b7e65

Browse files
committed
Merge branch 'mh/expire-updateref-fixes'
Various issues around "reflog expire", e.g. using --updateref when expiring a reflog for a symbolic reference, have been corrected and/or made saner. * mh/expire-updateref-fixes: reflog_expire(): never update a reference to null_sha1 reflog_expire(): ignore --updateref for symbolic references reflog: improve and update documentation struct ref_lock: delete the force_write member lock_ref_sha1_basic(): do not set force_write for missing references write_ref_sha1(): move write elision test to callers write_ref_sha1(): remove check for lock == NULL
2 parents 2d659f7 + 423c688 commit 82b7e65

File tree

3 files changed

+126
-92
lines changed

3 files changed

+126
-92
lines changed

Documentation/git-reflog.txt

Lines changed: 86 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,85 +17,113 @@ The command takes various subcommands, and different options
1717
depending on the subcommand:
1818

1919
[verse]
20-
'git reflog expire' [--dry-run] [--stale-fix] [--verbose]
21-
[--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...
22-
'git reflog delete' ref@\{specifier\}...
2320
'git reflog' ['show'] [log-options] [<ref>]
21+
'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
22+
[--rewrite] [--updateref] [--stale-fix]
23+
[--dry-run] [--verbose] [--all | <refs>...]
24+
'git reflog delete' [--rewrite] [--updateref]
25+
[--dry-run] [--verbose] ref@\{specifier\}...
26+
27+
Reference logs, or "reflogs", record when the tips of branches and
28+
other references were updated in the local repository. Reflogs are
29+
useful in various Git commands, to specify the old value of a
30+
reference. For example, `HEAD@{2}` means "where HEAD used to be two
31+
moves ago", `master@{one.week.ago}` means "where master used to point
32+
to one week ago in this local repository", and so on. See
33+
linkgit:gitrevisions[7] for more details.
34+
35+
This command manages the information recorded in the reflogs.
36+
37+
The "show" subcommand (which is also the default, in the absence of
38+
any subcommands) shows the log of the reference provided in the
39+
command-line (or `HEAD`, by default). The reflog covers all recent
40+
actions, and in addition the `HEAD` reflog records branch switching.
41+
`git reflog show` is an alias for `git log -g --abbrev-commit
42+
--pretty=oneline`; see linkgit:git-log[1] for more information.
43+
44+
The "expire" subcommand prunes older reflog entries. Entries older
45+
than `expire` time, or entries older than `expire-unreachable` time
46+
and not reachable from the current tip, are removed from the reflog.
47+
This is typically not used directly by end users -- instead, see
48+
linkgit:git-gc[1].
49+
50+
The "delete" subcommand deletes single entries from the reflog. Its
51+
argument must be an _exact_ entry (e.g. "`git reflog delete
52+
master@{2}`"). This subcommand is also typically not used directly by
53+
end users.
2454

25-
Reflog is a mechanism to record when the tip of branches are
26-
updated. This command is to manage the information recorded in it.
2755

28-
The subcommand "expire" is used to prune older reflog entries.
29-
Entries older than `expire` time, or entries older than
30-
`expire-unreachable` time and not reachable from the current
31-
tip, are removed from the reflog. This is typically not used
32-
directly by the end users -- instead, see linkgit:git-gc[1].
33-
34-
The subcommand "show" (which is also the default, in the absence of any
35-
subcommands) will take all the normal log options, and show the log of
36-
the reference provided in the command-line (or `HEAD`, by default).
37-
The reflog will cover all recent actions (HEAD reflog records branch switching
38-
as well). It is an alias for `git log -g --abbrev-commit --pretty=oneline`;
39-
see linkgit:git-log[1].
56+
OPTIONS
57+
-------
4058

41-
The reflog is useful in various Git commands, to specify the old value
42-
of a reference. For example, `HEAD@{2}` means "where HEAD used to be
43-
two moves ago", `master@{one.week.ago}` means "where master used to
44-
point to one week ago", and so on. See linkgit:gitrevisions[7] for
45-
more details.
59+
Options for `show`
60+
~~~~~~~~~~~~~~~~~~
4661

47-
To delete single entries from the reflog, use the subcommand "delete"
48-
and specify the _exact_ entry (e.g. "`git reflog delete master@{2}`").
62+
`git reflog show` accepts any of the options accepted by `git log`.
4963

5064

51-
OPTIONS
52-
-------
65+
Options for `expire`
66+
~~~~~~~~~~~~~~~~~~~~
5367

54-
--stale-fix::
55-
This revamps the logic -- the definition of "broken commit"
56-
becomes: a commit that is not reachable from any of the refs and
57-
there is a missing object among the commit, tree, or blob
58-
objects reachable from it that is not reachable from any of the
59-
refs.
60-
+
61-
This computation involves traversing all the reachable objects, i.e. it
62-
has the same cost as 'git prune'. Fortunately, once this is run, we
63-
should not have to ever worry about missing objects, because the current
64-
prune and pack-objects know about reflogs and protect objects referred by
65-
them.
68+
--all::
69+
Process the reflogs of all references.
6670

6771
--expire=<time>::
68-
Entries older than this time are pruned. Without the
69-
option it is taken from configuration `gc.reflogExpire`,
70-
which in turn defaults to 90 days. --expire=all prunes
71-
entries regardless of their age; --expire=never turns off
72-
pruning of reachable entries (but see --expire-unreachable).
72+
Prune entries older than the specified time. If this option is
73+
not specified, the expiration time is taken from the
74+
configuration setting `gc.reflogExpire`, which in turn
75+
defaults to 90 days. `--expire=all` prunes entries regardless
76+
of their age; `--expire=never` turns off pruning of reachable
77+
entries (but see `--expire-unreachable`).
7378

7479
--expire-unreachable=<time>::
75-
Entries older than this time and not reachable from
76-
the current tip of the branch are pruned. Without the
77-
option it is taken from configuration
78-
`gc.reflogExpireUnreachable`, which in turn defaults to
79-
30 days. --expire-unreachable=all prunes unreachable
80-
entries regardless of their age; --expire-unreachable=never
80+
Prune entries older than `<time>` that are not reachable from
81+
the current tip of the branch. If this option is not
82+
specified, the expiration time is taken from the configuration
83+
setting `gc.reflogExpireUnreachable`, which in turn defaults
84+
to 30 days. `--expire-unreachable=all` prunes unreachable
85+
entries regardless of their age; `--expire-unreachable=never`
8186
turns off early pruning of unreachable entries (but see
82-
--expire).
83-
84-
--all::
85-
Instead of listing <refs> explicitly, prune all refs.
87+
`--expire`).
8688

8789
--updateref::
88-
Update the ref with the sha1 of the top reflog entry (i.e.
89-
<ref>@\{0\}) after expiring or deleting.
90+
Update the reference to the value of the top reflog entry (i.e.
91+
<ref>@\{0\}) if the previous top entry was pruned. (This
92+
option is ignored for symbolic references.)
9093

9194
--rewrite::
92-
While expiring or deleting, adjust each reflog entry to ensure
93-
that the `old` sha1 field points to the `new` sha1 field of the
94-
previous entry.
95+
If a reflog entry's predecessor is pruned, adjust its "old"
96+
SHA-1 to be equal to the "new" SHA-1 field of the entry that
97+
now precedes it.
98+
99+
--stale-fix::
100+
Prune any reflog entries that point to "broken commits". A
101+
broken commit is a commit that is not reachable from any of
102+
the reference tips and that refers, directly or indirectly, to
103+
a missing commit, tree, or blob object.
104+
+
105+
This computation involves traversing all the reachable objects, i.e. it
106+
has the same cost as 'git prune'. It is primarily intended to fix
107+
corruption caused by garbage collecting using older versions of Git,
108+
which didn't protect objects referred to by reflogs.
109+
110+
-n::
111+
--dry-run::
112+
Do not actually prune any entries; just show what would have
113+
been pruned.
95114

96115
--verbose::
97116
Print extra information on screen.
98117

118+
119+
Options for `delete`
120+
~~~~~~~~~~~~~~~~~~~~
121+
122+
`git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`,
123+
`--dry-run`, and `--verbose`, with the same meanings as when they are
124+
used with `expire`.
125+
126+
99127
GIT
100128
---
101129
Part of the linkgit:git[1] suite

builtin/reflog.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@
88
#include "revision.h"
99
#include "reachable.h"
1010

11-
/*
12-
* reflog expire
13-
*/
14-
11+
/* NEEDSWORK: switch to using parse_options */
1512
static const char reflog_expire_usage[] =
16-
"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
13+
"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
1714
static const char reflog_delete_usage[] =
18-
"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
15+
"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
1916

2017
static unsigned long default_reflog_expire;
2118
static unsigned long default_reflog_expire_unreachable;

refs.c

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ struct ref_lock {
1212
struct lock_file *lk;
1313
unsigned char old_sha1[20];
1414
int lock_fd;
15-
int force_write;
1615
};
1716

1817
/*
@@ -2277,7 +2276,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
22772276
int type, lflags;
22782277
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
22792278
int resolve_flags = 0;
2280-
int missing = 0;
22812279
int attempts_remaining = 3;
22822280

22832281
lock = xcalloc(1, sizeof(struct ref_lock));
@@ -2316,13 +2314,13 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
23162314
orig_refname, strerror(errno));
23172315
goto error_return;
23182316
}
2319-
missing = is_null_sha1(lock->old_sha1);
2320-
/* When the ref did not exist and we are creating it,
2321-
* make sure there is no existing ref that is packed
2322-
* whose name begins with our refname, nor a ref whose
2323-
* name is a proper prefix of our refname.
2317+
/*
2318+
* If the ref did not exist and we are creating it, make sure
2319+
* there is no existing packed ref whose name begins with our
2320+
* refname, nor a packed ref whose name is a proper prefix of
2321+
* our refname.
23242322
*/
2325-
if (missing &&
2323+
if (is_null_sha1(lock->old_sha1) &&
23262324
!is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {
23272325
last_errno = ENOTDIR;
23282326
goto error_return;
@@ -2338,10 +2336,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
23382336
lock->ref_name = xstrdup(refname);
23392337
lock->orig_ref_name = xstrdup(orig_refname);
23402338
ref_file = git_path("%s", refname);
2341-
if (missing)
2342-
lock->force_write = 1;
2343-
if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
2344-
lock->force_write = 1;
23452339

23462340
retry:
23472341
switch (safe_create_leading_directories(ref_file)) {
@@ -2897,7 +2891,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
28972891
error("unable to lock %s for update", newrefname);
28982892
goto rollback;
28992893
}
2900-
lock->force_write = 1;
29012894
hashcpy(lock->old_sha1, orig_sha1);
29022895
if (write_ref_sha1(lock, orig_sha1, logmsg)) {
29032896
error("unable to write current sha1 into %s", newrefname);
@@ -2913,7 +2906,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
29132906
goto rollbacklog;
29142907
}
29152908

2916-
lock->force_write = 1;
29172909
flag = log_all_ref_updates;
29182910
log_all_ref_updates = 0;
29192911
if (write_ref_sha1(lock, orig_sha1, NULL))
@@ -3099,14 +3091,6 @@ static int write_ref_sha1(struct ref_lock *lock,
30993091
static char term = '\n';
31003092
struct object *o;
31013093

3102-
if (!lock) {
3103-
errno = EINVAL;
3104-
return -1;
3105-
}
3106-
if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
3107-
unlock_ref(lock);
3108-
return 0;
3109-
}
31103094
o = parse_object(sha1);
31113095
if (!o) {
31123096
error("Trying to write ref %s with nonexistent object %s",
@@ -3851,15 +3835,28 @@ int ref_transaction_commit(struct ref_transaction *transaction,
38513835
int flags = update->flags;
38523836

38533837
if ((flags & REF_HAVE_NEW) && !is_null_sha1(update->new_sha1)) {
3854-
if (write_ref_sha1(update->lock, update->new_sha1,
3855-
update->msg)) {
3838+
int overwriting_symref = ((update->type & REF_ISSYMREF) &&
3839+
(update->flags & REF_NODEREF));
3840+
3841+
if (!overwriting_symref
3842+
&& !hashcmp(update->lock->old_sha1, update->new_sha1)) {
3843+
/*
3844+
* The reference already has the desired
3845+
* value, so we don't need to write it.
3846+
*/
3847+
unlock_ref(update->lock);
3848+
update->lock = NULL;
3849+
} else if (write_ref_sha1(update->lock, update->new_sha1,
3850+
update->msg)) {
38563851
update->lock = NULL; /* freed by write_ref_sha1 */
38573852
strbuf_addf(err, "Cannot update the ref '%s'.",
38583853
update->refname);
38593854
ret = TRANSACTION_GENERIC_ERROR;
38603855
goto cleanup;
3856+
} else {
3857+
/* freed by write_ref_sha1(): */
3858+
update->lock = NULL;
38613859
}
3862-
update->lock = NULL; /* freed by write_ref_sha1 */
38633860
}
38643861
}
38653862

@@ -4083,6 +4080,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
40834080
struct ref_lock *lock;
40844081
char *log_file;
40854082
int status = 0;
4083+
int type;
40864084

40874085
memset(&cb, 0, sizeof(cb));
40884086
cb.flags = flags;
@@ -4094,7 +4092,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
40944092
* reference itself, plus we might need to update the
40954093
* reference if --updateref was specified:
40964094
*/
4097-
lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, NULL);
4095+
lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, &type);
40984096
if (!lock)
40994097
return error("cannot lock ref '%s'", refname);
41004098
if (!reflog_exists(refname)) {
@@ -4131,10 +4129,21 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
41314129
(*cleanup_fn)(cb.policy_cb);
41324130

41334131
if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) {
4132+
/*
4133+
* It doesn't make sense to adjust a reference pointed
4134+
* to by a symbolic ref based on expiring entries in
4135+
* the symbolic reference's reflog. Nor can we update
4136+
* a reference if there are no remaining reflog
4137+
* entries.
4138+
*/
4139+
int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) &&
4140+
!(type & REF_ISSYMREF) &&
4141+
!is_null_sha1(cb.last_kept_sha1);
4142+
41344143
if (close_lock_file(&reflog_lock)) {
41354144
status |= error("couldn't write %s: %s", log_file,
41364145
strerror(errno));
4137-
} else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) &&
4146+
} else if (update &&
41384147
(write_in_full(lock->lock_fd,
41394148
sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
41404149
write_str_in_full(lock->lock_fd, "\n") != 1 ||
@@ -4145,7 +4154,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
41454154
} else if (commit_lock_file(&reflog_lock)) {
41464155
status |= error("unable to commit reflog '%s' (%s)",
41474156
log_file, strerror(errno));
4148-
} else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) && commit_ref(lock)) {
4157+
} else if (update && commit_ref(lock)) {
41494158
status |= error("couldn't set %s", lock->ref_name);
41504159
}
41514160
}

0 commit comments

Comments
 (0)