git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/7] Fix some problems with reflog expiration
@ 2015-03-02  9:29 Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 1/7] write_ref_sha1(): remove check for lock == NULL Michael Haggerty
                   ` (7 more replies)
  0 siblings, 8 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

This is v2 of the patch series. Thanks to Eric Sunshine, Stefan
Beller, Peff, and Junio for their comments about v1 [1].

The two main changes since v1:

* Don't change the locking policy for changing symbolic references.
  Even though the existing policy is partly broken, the change
  proposed in v1 would have introduced an incompatibility with older
  versions of Git and with libgit2 and possibly other Git clients. I
  will try to implement the transition plan described in [2], but I
  will do it in a separate patch series.

* I rewrote the "git reflog" documentation more completely, following
  Junio's suggestion.

Other changes since v1:

* Rebased onto a more recent version of upstream master (because
  master now includes mh/reflog-expire and sb/atomic-push). The rebase
  was conflict-free.

* Tweaked some commit messages and added some Reviewed-by lines.

* Appended a patch by Stefan Beller to delete the "force_write" member
  of "struct ref_lock".

[1] http://thread.gmane.org/gmane.comp.version-control.git/263552
[2] http://article.gmane.org/gmane.comp.version-control.git/263811

Michael Haggerty (6):
  write_ref_sha1(): remove check for lock == NULL
  write_ref_sha1(): Move write elision test to callers
  lock_ref_sha1_basic(): do not set force_write for missing references
  reflog: improve and update documentation
  reflog_expire(): ignore --updateref for symbolic references
  reflog_expire(): never update a reference to null_sha1

Stefan Beller (1):
  struct ref_lock: delete the force_write member

 Documentation/git-reflog.txt | 139 +++++++++++++++++++++++++------------------
 builtin/reflog.c             |   4 +-
 refs.c                       |  63 +++++++++++---------
 3 files changed, 117 insertions(+), 89 deletions(-)

-- 
2.1.4

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 1/7] write_ref_sha1(): remove check for lock == NULL
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
@ 2015-03-02  9:29 ` Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 2/7] write_ref_sha1(): Move write elision test to callers Michael Haggerty
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

None of the callers pass NULL to this function, and there doesn't seem
to be any usefulness to allowing them to do so.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Reviewed-by: Stefan Beller <sbeller@google.com>
---
 refs.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/refs.c b/refs.c
index ab2f2a9..e82d503 100644
--- a/refs.c
+++ b/refs.c
@@ -3079,10 +3079,6 @@ static int write_ref_sha1(struct ref_lock *lock,
 	static char term = '\n';
 	struct object *o;
 
-	if (!lock) {
-		errno = EINVAL;
-		return -1;
-	}
 	if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
 		unlock_ref(lock);
 		return 0;
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 2/7] write_ref_sha1(): Move write elision test to callers
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 1/7] write_ref_sha1(): remove check for lock == NULL Michael Haggerty
@ 2015-03-02  9:29 ` Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 3/7] lock_ref_sha1_basic(): do not set force_write for missing references Michael Haggerty
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

write_ref_sha1() previously skipped the write if the reference already
had the desired value, unless lock->force_write was set. Instead,
perform that test at the callers.

Two of the callers (in rename_ref()) unconditionally set force_write
just before calling write_ref_sha1(), so they don't need the extra
check at all. Nor do they need to set force_write anymore.

The last caller, in ref_transaction_commit(), still needs the test.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 refs.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/refs.c b/refs.c
index e82d503..dd30bfa 100644
--- a/refs.c
+++ b/refs.c
@@ -2877,7 +2877,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
 		error("unable to lock %s for update", newrefname);
 		goto rollback;
 	}
-	lock->force_write = 1;
 	hashcpy(lock->old_sha1, orig_sha1);
 	if (write_ref_sha1(lock, orig_sha1, logmsg)) {
 		error("unable to write current sha1 into %s", newrefname);
@@ -2893,7 +2892,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
 		goto rollbacklog;
 	}
 
-	lock->force_write = 1;
 	flag = log_all_ref_updates;
 	log_all_ref_updates = 0;
 	if (write_ref_sha1(lock, orig_sha1, NULL))
@@ -3079,10 +3077,6 @@ static int write_ref_sha1(struct ref_lock *lock,
 	static char term = '\n';
 	struct object *o;
 
-	if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
-		unlock_ref(lock);
-		return 0;
-	}
 	o = parse_object(sha1);
 	if (!o) {
 		error("Trying to write ref %s with nonexistent object %s",
@@ -3796,15 +3790,21 @@ int ref_transaction_commit(struct ref_transaction *transaction,
 		struct ref_update *update = updates[i];
 
 		if (!is_null_sha1(update->new_sha1)) {
-			if (write_ref_sha1(update->lock, update->new_sha1,
-					   update->msg)) {
+			if (!update->lock->force_write &&
+			    !hashcmp(update->lock->old_sha1, update->new_sha1)) {
+				unlock_ref(update->lock);
+				update->lock = NULL;
+			} else if (write_ref_sha1(update->lock, update->new_sha1,
+						  update->msg)) {
 				update->lock = NULL; /* freed by write_ref_sha1 */
 				strbuf_addf(err, "Cannot update the ref '%s'.",
 					    update->refname);
 				ret = TRANSACTION_GENERIC_ERROR;
 				goto cleanup;
+			} else {
+				/* freed by write_ref_sha1(): */
+				update->lock = NULL;
 			}
-			update->lock = NULL; /* freed by write_ref_sha1 */
 		}
 	}
 
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 3/7] lock_ref_sha1_basic(): do not set force_write for missing references
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 1/7] write_ref_sha1(): remove check for lock == NULL Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 2/7] write_ref_sha1(): Move write elision test to callers Michael Haggerty
@ 2015-03-02  9:29 ` Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 4/7] struct ref_lock: delete the force_write member Michael Haggerty
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

If a reference is missing, its SHA-1 will be null_sha1, which can't
possibly match a new value that ref_transaction_commit() is trying to
update it to. So there is no need to set force_write in this scenario.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 refs.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/refs.c b/refs.c
index dd30bfa..3ed9ea6 100644
--- a/refs.c
+++ b/refs.c
@@ -2258,7 +2258,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 	int type, lflags;
 	int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
 	int resolve_flags = 0;
-	int missing = 0;
 	int attempts_remaining = 3;
 
 	lock = xcalloc(1, sizeof(struct ref_lock));
@@ -2297,13 +2296,13 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 			orig_refname, strerror(errno));
 		goto error_return;
 	}
-	missing = is_null_sha1(lock->old_sha1);
-	/* When the ref did not exist and we are creating it,
-	 * make sure there is no existing ref that is packed
-	 * whose name begins with our refname, nor a ref whose
-	 * name is a proper prefix of our refname.
+	/*
+	 * If the ref did not exist and we are creating it, make sure
+	 * there is no existing packed ref whose name begins with our
+	 * refname, nor a packed ref whose name is a proper prefix of
+	 * our refname.
 	 */
-	if (missing &&
+	if (is_null_sha1(lock->old_sha1) &&
 	     !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {
 		last_errno = ENOTDIR;
 		goto error_return;
@@ -2319,8 +2318,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 	lock->ref_name = xstrdup(refname);
 	lock->orig_ref_name = xstrdup(orig_refname);
 	ref_file = git_path("%s", refname);
-	if (missing)
-		lock->force_write = 1;
 	if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
 		lock->force_write = 1;
 
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 4/7] struct ref_lock: delete the force_write member
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
                   ` (2 preceding siblings ...)
  2015-03-02  9:29 ` [PATCH v2 3/7] lock_ref_sha1_basic(): do not set force_write for missing references Michael Haggerty
@ 2015-03-02  9:29 ` Michael Haggerty
  2015-03-02 21:44   ` Junio C Hamano
  2015-03-02  9:29 ` [PATCH v2 5/7] reflog: improve and update documentation Michael Haggerty
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

From: Stefan Beller <sbeller@google.com>

Instead, compute the value when it is needed.

Signed-off-by: Stefan Beller <sbeller@google.com>
Edited-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 refs.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/refs.c b/refs.c
index 3ed9ea6..f2e9883 100644
--- a/refs.c
+++ b/refs.c
@@ -12,7 +12,6 @@ struct ref_lock {
 	struct lock_file *lk;
 	unsigned char old_sha1[20];
 	int lock_fd;
-	int force_write;
 };
 
 /*
@@ -2318,8 +2317,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 	lock->ref_name = xstrdup(refname);
 	lock->orig_ref_name = xstrdup(orig_refname);
 	ref_file = git_path("%s", refname);
-	if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
-		lock->force_write = 1;
 
  retry:
 	switch (safe_create_leading_directories(ref_file)) {
@@ -3787,8 +3784,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,
 		struct ref_update *update = updates[i];
 
 		if (!is_null_sha1(update->new_sha1)) {
-			if (!update->lock->force_write &&
-			    !hashcmp(update->lock->old_sha1, update->new_sha1)) {
+			if (!((update->type & REF_ISSYMREF)
+			      && (update->flags & REF_NODEREF))
+			    && !hashcmp(update->lock->old_sha1, update->new_sha1)) {
+				/*
+				 * The reference already has the desired
+				 * value, so we don't need to write it.
+				 */
 				unlock_ref(update->lock);
 				update->lock = NULL;
 			} else if (write_ref_sha1(update->lock, update->new_sha1,
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 5/7] reflog: improve and update documentation
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
                   ` (3 preceding siblings ...)
  2015-03-02  9:29 ` [PATCH v2 4/7] struct ref_lock: delete the force_write member Michael Haggerty
@ 2015-03-02  9:29 ` Michael Haggerty
  2015-03-02 22:04   ` Junio C Hamano
  2015-03-02  9:29 ` [PATCH v2 6/7] reflog_expire(): ignore --updateref for symbolic references Michael Haggerty
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

Revamp the "git reflog" usage documentation in the manpage and the
command help to match the current reality and improve its clarity:

* Add documentation for some options that had been left out.

* Group the subcommands and options more logically and move more
  common subcommands/options higher.

* Improve some explanations.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 Documentation/git-reflog.txt | 138 +++++++++++++++++++++++++------------------
 builtin/reflog.c             |   4 +-
 2 files changed, 81 insertions(+), 61 deletions(-)

diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index 70791b9..3af9422 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -17,81 +17,101 @@ The command takes various subcommands, and different options
 depending on the subcommand:
 
 [verse]
-'git reflog expire' [--dry-run] [--stale-fix] [--verbose]
-	[--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...
-'git reflog delete' ref@\{specifier\}...
 'git reflog' ['show'] [log-options] [<ref>]
+'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
+	[--rewrite] [--updateref] [--stale-fix]
+	[--dry-run] [--verbose] [--all | <refs>...]
+'git reflog delete' [--rewrite] [--updateref]
+	[--dry-run] [--verbose] ref@\{specifier\}...
+
+Reference logs, or "reflogs", record when the tips of branches and
+other references were updated in the local repository. Reflogs are
+useful in various Git commands, to specify the old value of a
+reference. For example, `HEAD@{2}` means "where HEAD used to be two
+moves ago", `master@{one.week.ago}` means "where master used to point
+to one week ago", and so on. See linkgit:gitrevisions[7] for more
+details.
+
+This command manages the information recorded in the reflogs.
+
+The "show" subcommand (which is also the default, in the absence of
+any subcommands) shows the log of the reference provided in the
+command-line (or `HEAD`, by default). The reflog covers all recent
+actions, and in addition the `HEAD` reflog records branch switching.
+`git reflog show` is an alias for `git log -g --abbrev-commit
+--pretty=oneline`; see linkgit:git-log[1] for more information.
+
+The "expire" subcommand prunes older reflog entries. Entries older
+than `expire` time, or entries older than `expire-unreachable` time
+and not reachable from the current tip, are removed from the reflog.
+This is typically not used directly by end users -- instead, see
+linkgit:git-gc[1].
+
+The "delete" subcommand deletes single entries from the reflog. Its
+argument must be an _exact_ entry (e.g. "`git reflog delete
+master@{2}`").
 
-Reflog is a mechanism to record when the tip of branches are
-updated.  This command is to manage the information recorded in it.
 
-The subcommand "expire" is used to prune older reflog entries.
-Entries older than `expire` time, or entries older than
-`expire-unreachable` time and not reachable from the current
-tip, are removed from the reflog.  This is typically not used
-directly by the end users -- instead, see linkgit:git-gc[1].
-
-The subcommand "show" (which is also the default, in the absence of any
-subcommands) will take all the normal log options, and show the log of
-the reference provided in the command-line (or `HEAD`, by default).
-The reflog will cover all recent actions (HEAD reflog records branch switching
-as well).  It is an alias for `git log -g --abbrev-commit --pretty=oneline`;
-see linkgit:git-log[1].
+OPTIONS
+-------
 
-The reflog is useful in various Git commands, to specify the old value
-of a reference. For example, `HEAD@{2}` means "where HEAD used to be
-two moves ago", `master@{one.week.ago}` means "where master used to
-point to one week ago", and so on. See linkgit:gitrevisions[7] for
-more details.
+Options for `show`
+~~~~~~~~~~~~~~~~~~
 
-To delete single entries from the reflog, use the subcommand "delete"
-and specify the _exact_ entry (e.g. "`git reflog delete master@{2}`").
+`git reflog show` accepts any of the options accepted by `git log`.
 
 
-OPTIONS
--------
+Options for `expire` and/or `delete`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
---stale-fix::
-	This revamps the logic -- the definition of "broken commit"
-	becomes: a commit that is not reachable from any of the refs and
-	there is a missing object among the commit, tree, or blob
-	objects reachable from it that is not reachable from any of the
-	refs.
-+
-This computation involves traversing all the reachable objects, i.e. it
-has the same cost as 'git prune'.  Fortunately, once this is run, we
-should not have to ever worry about missing objects, because the current
-prune and pack-objects know about reflogs and protect objects referred by
-them.
+--all::
+	(For the `expire` command only.) Process the reflogs of all
+	references.
 
 --expire=<time>::
-	Entries older than this time are pruned.  Without the
-	option it is taken from configuration `gc.reflogExpire`,
-	which in turn defaults to 90 days.  --expire=all prunes
-	entries regardless of their age; --expire=never turns off
-	pruning of reachable entries (but see --expire-unreachable).
+	(For the `expire` command only.) Prune entries older than the
+	specified time. If this option is not specified, the
+	expiration time is taken from the configuration setting
+	`gc.reflogExpire`, which in turn defaults to 90 days.
+	`--expire=all` prunes entries regardless of their age;
+	`--expire=never` turns off pruning of reachable entries (but
+	see `--expire-unreachable`).
 
 --expire-unreachable=<time>::
-	Entries older than this time and not reachable from
-	the current tip of the branch are pruned.  Without the
-	option it is taken from configuration
-	`gc.reflogExpireUnreachable`, which in turn defaults to
-	30 days.  --expire-unreachable=all prunes unreachable
-	entries regardless of their age; --expire-unreachable=never
-	turns off early pruning of unreachable entries (but see
-	--expire).
-
---all::
-	Instead of listing <refs> explicitly, prune all refs.
+	(For the `expire` command only.) Prune entries older than
+	`<time>` that are not reachable from the current tip of the
+	branch. If this option is not specified, the expiration time
+	is taken from the configuration setting
+	`gc.reflogExpireUnreachable`, which in turn defaults to 30
+	days. `--expire-unreachable=all` prunes unreachable entries
+	regardless of their age; `--expire-unreachable=never` turns
+	off early pruning of unreachable entries (but see `--expire`).
 
 --updateref::
-	Update the ref with the sha1 of the top reflog entry (i.e.
-	<ref>@\{0\}) after expiring or deleting.
+	Update the reference to the value of the top reflog entry (i.e.
+	<ref>@\{0\}) if the previous top entry was pruned.
 
 --rewrite::
-	While expiring or deleting, adjust each reflog entry to ensure
-	that the `old` sha1 field points to the `new` sha1 field of the
-	previous entry.
+	While expiring or deleting, adjust each reflog entry's "old"
+	SHA-1 to be equal to the "new" SHA-1 field of the entry
+	preceding it.
+
+--stale-fix::
+	(For the `expire` command only.) Prune any reflog entries that
+	point to "broken commits". A broken commit is a commit that is
+	not reachable from any of the reference tips and that refers,
+	directly or indirectly, to a missing commit, tree, or blob
+	object.
++
+This computation involves traversing all the reachable objects, i.e. it
+has the same cost as 'git prune'.  It is primarily intended to fix
+corruption caused by garbage collecting using older versions of Git,
+which didn't protect objects referred to by reflogs.
+
+-n::
+--dry-run::
+	Do not actually prune any entries; just show what would have
+	been pruned.
 
 --verbose::
 	Print extra information on screen.
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 49c64f9..dd68a72 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -13,9 +13,9 @@
  */
 
 static const char reflog_expire_usage[] =
-"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
 static const char reflog_delete_usage[] =
-"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
+"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
 
 static unsigned long default_reflog_expire;
 static unsigned long default_reflog_expire_unreachable;
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 6/7] reflog_expire(): ignore --updateref for symbolic references
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
                   ` (4 preceding siblings ...)
  2015-03-02  9:29 ` [PATCH v2 5/7] reflog: improve and update documentation Michael Haggerty
@ 2015-03-02  9:29 ` Michael Haggerty
  2015-03-02  9:29 ` [PATCH v2 7/7] reflog_expire(): never update a reference to null_sha1 Michael Haggerty
  2015-03-02 22:09 ` [PATCH v2 0/7] Fix some problems with reflog expiration Junio C Hamano
  7 siblings, 0 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

If we are expiring reflog entries for a symbolic reference, then how
should --updateref be handled if the newest reflog entry is expired?

Option 1: Update the referred-to reference. (This is what the current
code does.) This doesn't make sense, because the referred-to reference
has its own reflog, which hasn't been rewritten.

Option 2: Update the symbolic reference itself (as in, REF_NODEREF).
This would convert the symbolic reference into a non-symbolic
reference (e.g., detaching HEAD), which is surely not what a user
would expect.

Option 3: Error out. This is plausible, but it would make the
following usage impossible:

    git reflog expire ... --updateref --all

Option 4: Ignore --updateref for symbolic references.

We choose to implement option 4.

Note: another problem in this code will be fixed in a moment.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 Documentation/git-reflog.txt |  3 ++-
 refs.c                       | 15 ++++++++++++---
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index 3af9422..d49db7c 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -89,7 +89,8 @@ Options for `expire` and/or `delete`
 
 --updateref::
 	Update the reference to the value of the top reflog entry (i.e.
-	<ref>@\{0\}) if the previous top entry was pruned.
+	<ref>@\{0\}) if the previous top entry was pruned.  (This
+	option is ignored for symbolic references.)
 
 --rewrite::
 	While expiring or deleting, adjust each reflog entry's "old"
diff --git a/refs.c b/refs.c
index f2e9883..bd47d23 100644
--- a/refs.c
+++ b/refs.c
@@ -4026,6 +4026,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 	struct ref_lock *lock;
 	char *log_file;
 	int status = 0;
+	int type;
 
 	memset(&cb, 0, sizeof(cb));
 	cb.flags = flags;
@@ -4037,7 +4038,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 	 * reference itself, plus we might need to update the
 	 * reference if --updateref was specified:
 	 */
-	lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, NULL);
+	lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, &type);
 	if (!lock)
 		return error("cannot lock ref '%s'", refname);
 	if (!reflog_exists(refname)) {
@@ -4074,10 +4075,18 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 	(*cleanup_fn)(cb.policy_cb);
 
 	if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) {
+		/*
+		 * It doesn't make sense to adjust a reference pointed
+		 * to by a symbolic ref based on expiring entries in
+		 * the symbolic reference's reflog.
+		 */
+		int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) &&
+			!(type & REF_ISSYMREF);
+
 		if (close_lock_file(&reflog_lock)) {
 			status |= error("couldn't write %s: %s", log_file,
 					strerror(errno));
-		} else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) &&
+		} else if (update &&
 			(write_in_full(lock->lock_fd,
 				sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
 			 write_str_in_full(lock->lock_fd, "\n") != 1 ||
@@ -4088,7 +4097,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 		} else if (commit_lock_file(&reflog_lock)) {
 			status |= error("unable to commit reflog '%s' (%s)",
 					log_file, strerror(errno));
-		} else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) && commit_ref(lock)) {
+		} else if (update && commit_ref(lock)) {
 			status |= error("couldn't set %s", lock->ref_name);
 		}
 	}
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 7/7] reflog_expire(): never update a reference to null_sha1
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
                   ` (5 preceding siblings ...)
  2015-03-02  9:29 ` [PATCH v2 6/7] reflog_expire(): ignore --updateref for symbolic references Michael Haggerty
@ 2015-03-02  9:29 ` Michael Haggerty
  2015-03-02 22:09 ` [PATCH v2 0/7] Fix some problems with reflog expiration Junio C Hamano
  7 siblings, 0 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-02  9:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git, Michael Haggerty

Currently, if --updateref is specified and the very last reflog entry
is expired or deleted, the reference's value is set to 0{40}. This is
an invalid state of the repository, and breaks, for example, "git
fsck" and "git for-each-ref".

The only place we use --updateref in our own code is when dropping
stash entries. In that code, the very next step is to check if the
reflog has been made empty, and if so, delete the "refs/stash"
reference entirely. Thus that code path ultimately leaves the
repository in a valid state.

But we don't want to the repository in an invalid state even
temporarily, and we don't want to leave an invalid state if other
callers of "git reflog expire|delete --updateref" don't think to do
the extra cleanup step.

So, if "git reflog expire|delete" leaves no more entries in the
reflog, just leave the reference unchanged.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 refs.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/refs.c b/refs.c
index bd47d23..f6b04d9 100644
--- a/refs.c
+++ b/refs.c
@@ -4078,10 +4078,13 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 		/*
 		 * It doesn't make sense to adjust a reference pointed
 		 * to by a symbolic ref based on expiring entries in
-		 * the symbolic reference's reflog.
+		 * the symbolic reference's reflog. Nor can we update
+		 * a reference if there are no remaining reflog
+		 * entries.
 		 */
 		int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) &&
-			!(type & REF_ISSYMREF);
+			!(type & REF_ISSYMREF) &&
+			!is_null_sha1(cb.last_kept_sha1);
 
 		if (close_lock_file(&reflog_lock)) {
 			status |= error("couldn't write %s: %s", log_file,
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 4/7] struct ref_lock: delete the force_write member
  2015-03-02  9:29 ` [PATCH v2 4/7] struct ref_lock: delete the force_write member Michael Haggerty
@ 2015-03-02 21:44   ` Junio C Hamano
  2015-03-03 10:50     ` Michael Haggerty
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2015-03-02 21:44 UTC (permalink / raw)
  To: Michael Haggerty
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> Instead, compute the value when it is needed.

> @@ -2318,8 +2317,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
>  	lock->ref_name = xstrdup(refname);
>  	lock->orig_ref_name = xstrdup(orig_refname);
>  	ref_file = git_path("%s", refname);
> -	if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
> -		lock->force_write = 1;
>  
>   retry:
>  	switch (safe_create_leading_directories(ref_file)) {
> @@ -3787,8 +3784,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,
>  		struct ref_update *update = updates[i];
>  
>  		if (!is_null_sha1(update->new_sha1)) {
> -			if (!update->lock->force_write &&
> -			    !hashcmp(update->lock->old_sha1, update->new_sha1)) {
> +			if (!((update->type & REF_ISSYMREF)
> +			      && (update->flags & REF_NODEREF))
> +			    && !hashcmp(update->lock->old_sha1, update->new_sha1)) {
> +				/*
> +				 * The reference already has the desired
> +				 * value, so we don't need to write it.
> +				 */
>  				unlock_ref(update->lock);
>  				update->lock = NULL;
>  			} else if (write_ref_sha1(update->lock, update->new_sha1,

The code before and after the change are equivalent.

It shouldn't be the case, but somehow I find the original slightly
easier to understand.  The before and after says the same thing,
i.e. the code used to be:

 - We say "do the write-out without questioning" when we are
   updating a symbolic ref without dereferencing.

 - Do nothing and unlock if we are not told to "do the write-out
   without questioning" and the update will be a no-op anyway.

while the code after the change says:

 + Do nothing and unlock if we are not handling "update a symbolic
   ref without dereferencing" and the update will be a no-op anyway.

Perhaps the former has the same effect as "avoid a single complex
sentence and use two short sentences instead".

The negation in the condition does not help, either.

 * If we are updating a symbolic ref without dereferencing, or if we
   are updating with a different object name, we definitely have to
   write.

would be easier to understand, perhaps?  I.e.

	if (hashcmp(update->lock->old_sha1, update->lock->new_sha1) ||
	    ((update->type & REF_ISSYMREF) && (update->flags & REF_NO_DEREF))) {
		/* do the write-out thing */
	} else {
		/* the request to update from the same to the same is a no-op */
		unlock_ref(update->lock);
                update->lock = NULL;
	}

I dunno.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 5/7] reflog: improve and update documentation
  2015-03-02  9:29 ` [PATCH v2 5/7] reflog: improve and update documentation Michael Haggerty
@ 2015-03-02 22:04   ` Junio C Hamano
  2015-03-03 11:35     ` Michael Haggerty
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2015-03-02 22:04 UTC (permalink / raw)
  To: Michael Haggerty
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> +Reference logs, or "reflogs", record when the tips of branches and
> +other references were updated in the local repository. Reflogs are
> +useful in various Git commands, to specify the old value of a
> +reference. For example, `HEAD@{2}` means "where HEAD used to be two
> +moves ago", `master@{one.week.ago}` means "where master used to point
> +to one week ago", and so on. See linkgit:gitrevisions[7] for more
> +details.

Looks very good, especially the part that mentions "in the local
repository".  It seems to be a common novice misunderstanding what
`master@{one.week.ago}` means, and it might be beneficial to be more
verbose by saying "where master used to point to one week ago in
this local repository".

> +The "expire" subcommand prunes older reflog entries. Entries older
> +than `expire` time, or entries older than `expire-unreachable` time
> +and not reachable from the current tip, are removed from the reflog.
> +This is typically not used directly by end users -- instead, see
> +linkgit:git-gc[1].
> +
> +The "delete" subcommand deletes single entries from the reflog. Its
> +argument must be an _exact_ entry (e.g. "`git reflog delete
> +master@{2}`").

Just like "expire", "delete" should be accompanied by the same
"typically not".  I do not think it is even worth mentioning that it
exists merely as an implementation detail for likgit:git-stash[1]
and for no other reason.


> +Options for `expire` and/or `delete`
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I think this started from a hope that these two share many, but
looking at the result I notice the shared ones are a tiny and
trivial minority.  It probably makes sense to retitle this section
"Options for expire" (and remove "For the expire command only"), and
add an "Options for delete" section immediately after it that looks
like:

	Options for `delete`
        ~~~~~~~~~~~~~~~~~~~~

	--updateref::
        --rewrite::
        --dry-run::
        	The `delete` command takes these options whose
                meanings are the same as those for `expire`.

> diff --git a/builtin/reflog.c b/builtin/reflog.c
> index 49c64f9..dd68a72 100644
> --- a/builtin/reflog.c
> +++ b/builtin/reflog.c
> @@ -13,9 +13,9 @@
>   */
>  
>  static const char reflog_expire_usage[] =
> -"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
> +"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
>  static const char reflog_delete_usage[] =
> -"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
> +"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
>  
>  static unsigned long default_reflog_expire;
>  static unsigned long default_reflog_expire_unreachable;

Thanks for being complete, but I sense that it may be time we
switched to parse-options here, which gives us the help string for
free.  Perhaps throw in a comment line before this hunk

	/* NEEDSWORK: switch to parse-options */

or something to leave hint for other people?

Thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 0/7] Fix some problems with reflog expiration
  2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
                   ` (6 preceding siblings ...)
  2015-03-02  9:29 ` [PATCH v2 7/7] reflog_expire(): never update a reference to null_sha1 Michael Haggerty
@ 2015-03-02 22:09 ` Junio C Hamano
  7 siblings, 0 replies; 13+ messages in thread
From: Junio C Hamano @ 2015-03-02 22:09 UTC (permalink / raw)
  To: Michael Haggerty
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> This is v2 of the patch series. Thanks to Eric Sunshine, Stefan
> Beller, Peff, and Junio for their comments about v1 [1].

Thanks for a pleasant read.  Will replace what has been queued.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 4/7] struct ref_lock: delete the force_write member
  2015-03-02 21:44   ` Junio C Hamano
@ 2015-03-03 10:50     ` Michael Haggerty
  0 siblings, 0 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-03 10:50 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git

On 03/02/2015 10:44 PM, Junio C Hamano wrote:
> Michael Haggerty <mhagger@alum.mit.edu> writes:
> 
>> Instead, compute the value when it is needed.
> 
>> @@ -2318,8 +2317,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
>>  	lock->ref_name = xstrdup(refname);
>>  	lock->orig_ref_name = xstrdup(orig_refname);
>>  	ref_file = git_path("%s", refname);
>> -	if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
>> -		lock->force_write = 1;
>>  
>>   retry:
>>  	switch (safe_create_leading_directories(ref_file)) {
>> @@ -3787,8 +3784,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,
>>  		struct ref_update *update = updates[i];
>>  
>>  		if (!is_null_sha1(update->new_sha1)) {
>> -			if (!update->lock->force_write &&
>> -			    !hashcmp(update->lock->old_sha1, update->new_sha1)) {
>> +			if (!((update->type & REF_ISSYMREF)
>> +			      && (update->flags & REF_NODEREF))
>> +			    && !hashcmp(update->lock->old_sha1, update->new_sha1)) {
>> +				/*
>> +				 * The reference already has the desired
>> +				 * value, so we don't need to write it.
>> +				 */
>>  				unlock_ref(update->lock);
>>  				update->lock = NULL;
>>  			} else if (write_ref_sha1(update->lock, update->new_sha1,
> 
> The code before and after the change are equivalent.
> 
> It shouldn't be the case, but somehow I find the original slightly
> easier to understand. [...]

I had the same feeling; thanks for confirming it. How about I introduce
a temporary variable `overwriting_symref` as an aid to the reader? I
think this makes it pretty clear:

>  		if (!is_null_sha1(update->new_sha1)) {
> -			if (!update->lock->force_write &&
> -			    !hashcmp(update->lock->old_sha1, update->new_sha1)) {
> +			int overwriting_symref = ((update->type & REF_ISSYMREF) &&
> +						  (update->flags & REF_NODEREF));
> +
> +			if (!overwriting_symref
> +			    && !hashcmp(update->lock->old_sha1, update->new_sha1)) {
> +				/*
> +				 * The reference already has the desired
> +				 * value, so we don't need to write it.
> +				 */
>  				unlock_ref(update->lock);
>  				update->lock = NULL;
>  			} else if (write_ref_sha1(update->lock, update->new_sha1,

Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 5/7] reflog: improve and update documentation
  2015-03-02 22:04   ` Junio C Hamano
@ 2015-03-03 11:35     ` Michael Haggerty
  0 siblings, 0 replies; 13+ messages in thread
From: Michael Haggerty @ 2015-03-03 11:35 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Stefan Beller, Ronnie Sahlberg, Jonathan Nieder,
	Nguyễn Thái Ngọc Duy, Eric Sunshine, Jeff King,
	git

On 03/02/2015 11:04 PM, Junio C Hamano wrote:
> Michael Haggerty <mhagger@alum.mit.edu> writes:
> 
>> +Reference logs, or "reflogs", record when the tips of branches and
>> +other references were updated in the local repository. Reflogs are
>> +useful in various Git commands, to specify the old value of a
>> +reference. For example, `HEAD@{2}` means "where HEAD used to be two
>> +moves ago", `master@{one.week.ago}` means "where master used to point
>> +to one week ago", and so on. See linkgit:gitrevisions[7] for more
>> +details.
> 
> Looks very good, especially the part that mentions "in the local
> repository".  It seems to be a common novice misunderstanding what
> `master@{one.week.ago}` means, and it might be beneficial to be more
> verbose by saying "where master used to point to one week ago in
> this local repository".

Yes, that's good. I will change it.

>> +The "expire" subcommand prunes older reflog entries. Entries older
>> +than `expire` time, or entries older than `expire-unreachable` time
>> +and not reachable from the current tip, are removed from the reflog.
>> +This is typically not used directly by end users -- instead, see
>> +linkgit:git-gc[1].
>> +
>> +The "delete" subcommand deletes single entries from the reflog. Its
>> +argument must be an _exact_ entry (e.g. "`git reflog delete
>> +master@{2}`").
> 
> Just like "expire", "delete" should be accompanied by the same
> "typically not".  I do not think it is even worth mentioning that it
> exists merely as an implementation detail for likgit:git-stash[1]
> and for no other reason.

OK, will change.

>> +Options for `expire` and/or `delete`
>> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> I think this started from a hope that these two share many, but
> looking at the result I notice the shared ones are a tiny and
> trivial minority.  It probably makes sense to retitle this section
> "Options for expire" (and remove "For the expire command only"), and
> add an "Options for delete" section immediately after it that looks
> like:
> 
> 	Options for `delete`
>         ~~~~~~~~~~~~~~~~~~~~
> 
> 	--updateref::
>         --rewrite::
>         --dry-run::
>         	The `delete` command takes these options whose
>                 meanings are the same as those for `expire`.

Either way is a little bit awkward, because these two subcommands share
4 out of 8 of their options. But since "delete" is really quite useless
to end users, I stuck its options in a separate section as you
suggested, but inline in a paragraph, where they won't bother anybody.

>> diff --git a/builtin/reflog.c b/builtin/reflog.c
>> index 49c64f9..dd68a72 100644
>> --- a/builtin/reflog.c
>> +++ b/builtin/reflog.c
>> @@ -13,9 +13,9 @@
>>   */
>>  
>>  static const char reflog_expire_usage[] =
>> -"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
>> +"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
>>  static const char reflog_delete_usage[] =
>> -"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
>> +"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
>>  
>>  static unsigned long default_reflog_expire;
>>  static unsigned long default_reflog_expire_unreachable;
> 
> Thanks for being complete, but I sense that it may be time we
> switched to parse-options here, which gives us the help string for
> free.  Perhaps throw in a comment line before this hunk
> 
> 	/* NEEDSWORK: switch to parse-options */
> 
> or something to leave hint for other people?

OK.

Thanks for your review! A reroll will be coming soon.

Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2015-03-03 11:35 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-03-02  9:29 [PATCH v2 0/7] Fix some problems with reflog expiration Michael Haggerty
2015-03-02  9:29 ` [PATCH v2 1/7] write_ref_sha1(): remove check for lock == NULL Michael Haggerty
2015-03-02  9:29 ` [PATCH v2 2/7] write_ref_sha1(): Move write elision test to callers Michael Haggerty
2015-03-02  9:29 ` [PATCH v2 3/7] lock_ref_sha1_basic(): do not set force_write for missing references Michael Haggerty
2015-03-02  9:29 ` [PATCH v2 4/7] struct ref_lock: delete the force_write member Michael Haggerty
2015-03-02 21:44   ` Junio C Hamano
2015-03-03 10:50     ` Michael Haggerty
2015-03-02  9:29 ` [PATCH v2 5/7] reflog: improve and update documentation Michael Haggerty
2015-03-02 22:04   ` Junio C Hamano
2015-03-03 11:35     ` Michael Haggerty
2015-03-02  9:29 ` [PATCH v2 6/7] reflog_expire(): ignore --updateref for symbolic references Michael Haggerty
2015-03-02  9:29 ` [PATCH v2 7/7] reflog_expire(): never update a reference to null_sha1 Michael Haggerty
2015-03-02 22:09 ` [PATCH v2 0/7] Fix some problems with reflog expiration Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).