From: Stefan Beller <sbeller@google.com>
To: ronniesahlberg@gmail.com, mhagger@alum.mit.edu,
jrnieder@gmail.com, gitster@pobox.com
Cc: git@vger.kernel.org, Stefan Beller <sbeller@google.com>
Subject: [PATCH 06/13] refs.c: add a transaction function to truncate or append a reflog entry
Date: Thu, 4 Dec 2014 00:29:16 -0800 [thread overview]
Message-ID: <1417681763-32334-7-git-send-email-sbeller@google.com> (raw)
In-Reply-To: <1417681763-32334-1-git-send-email-sbeller@google.com>
This patch introduces two transaction functions for dealing with reflog
changes. The first function transaction_truncate_reflog can be used,
when a rebuilding of the reflog is desired, e.g. on reflog expire.
The transaction_update_reflog function can be used to amend a line to the
reflog.
We cannot handle reflogs the same way we handle refs because
of naming conflicts in the file system.
If renaming a ref from foo/foo to foo or the other way round,
we need to be careful as we need the basic foo/ from being a
directory to be a file or vice versa. When handling the refs
this can be solved easily by first recording all we want into
the packed refs file and then deleting all the loose refs. By
doing it that way, we always have a valid state on disk.
We don't have an equivalent of a packed refs file when dealing
with reflog updates, but the API for updating the refs turned
out to be conveniant, so let's introduce an intermediate file
outside the $GIT_DIR/logs/refs/heads/ directory helping us
avoiding this naming conflict for the reflogs. The intermediate
lock file, in which we build up the new reflog will live under
$GIT_DIR/logs/lock/refs/heads/ so the files
$GIT_DIR/logs/lock/refs/heads/foo.lock and
$GIT_DIR/logs/lock/refs/heads/foo/foo.lock
do not collide.
When using transaction_update_reflog, only write to the reflog iff
msg is non-NULL.
During one transaction we allow to make multiple reflog updates to the
same ref. This means we only need to lock the reflog once, during the first
update that touches the reflog, and that all further updates can just write the
reflog entry since the reflog is already locked.
This allows us to write code such as:
t = transaction_begin()
transaction_truncate_reflog(t, "foo");
loop-over-something...
if (want_reflog_entry(...))
transaction_reflog_update(t, "foo", 0, <message>);
transaction_commit(t)
where we first truncate the reflog and then build the new content one line
at a time.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Notes:
This is a complete rewrite of this patch, lots of design ideas
have been born in fruitful discussions with Jonathan.
Personally I am not yet happy about the file copying in
transaction_update_reflog.
refs.c | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
refs.h | 20 +++++++++++
2 files changed, 146 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 58de60c..c411af9 100644
--- a/refs.c
+++ b/refs.c
@@ -3557,6 +3557,12 @@ struct transaction {
struct ref_update **ref_updates;
size_t alloc;
size_t nr;
+
+ /*
+ * Sorted list of reflogs to be committed,
+ * the util points to the lock_file.
+ */
+ struct string_list reflog_updates;
enum transaction_state state;
};
@@ -3564,7 +3570,10 @@ struct transaction *transaction_begin(struct strbuf *err)
{
assert(err);
- return xcalloc(1, sizeof(struct transaction));
+ struct transaction *ret = xcalloc(1, sizeof(struct transaction));
+ ret->reflog_updates.strdup_strings = 1;
+
+ return ret;
}
void transaction_free(struct transaction *transaction)
@@ -3629,6 +3638,113 @@ int transaction_update_ref(struct transaction *transaction,
return 0;
}
+int transaction_truncate_reflog(struct transaction *transaction,
+ const char *refname,
+ struct strbuf *err)
+{
+ struct lock_file *lock;
+ struct string_list_item *item;
+
+ if (transaction->state != TRANSACTION_OPEN)
+ die("BUG: update_reflog called for transaction that is not open");
+
+ item = string_list_insert(&transaction->reflog_updates, refname);
+ if (item->util) {
+ lock = item->util;
+ if (lseek(lock->fd, 0, SEEK_SET) < 0 ||
+ ftruncate(lock->fd, 0)) {
+ strbuf_addf(err, "cannot truncate reflog '%s': %s",
+ refname, strerror(errno));
+ goto failure;
+ }
+
+ } else {
+ char *path = git_path("logs/locks/%s", refname);
+ item->util = xcalloc(1, sizeof(struct lock_file));
+ lock = item->util;
+ if (safe_create_leading_directories(path)) {
+ strbuf_addf(err, "could not create leading directories of '%s': %s",
+ path, strerror(errno));
+ goto failure;
+ }
+
+ if (hold_lock_file_for_update(lock, path, 0) < 0) {
+ unable_to_lock_message(path, errno, err);
+ goto failure;
+ }
+ /* The file is empty, no need to truncate. */
+ }
+ return 0;
+failure:
+ transaction->state = TRANSACTION_CLOSED;
+ return -1;
+}
+
+
+int transaction_update_reflog(struct transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ const unsigned char *old_sha1,
+ const char *email,
+ unsigned long timestamp, int tz,
+ const char *msg,
+ struct strbuf *err)
+{
+ struct lock_file *lock;
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list_item *item;
+
+ if (transaction->state != TRANSACTION_OPEN)
+ die("BUG: update_reflog called for transaction that is not open");
+
+ item = string_list_insert(&transaction->reflog_updates, refname);
+ if (!item->util) {
+ char buf[1024];
+ int infd, nread;
+ char *path = git_path("logs/locks/%s", refname);
+ item->util = xcalloc(1, sizeof(struct lock_file));
+ lock = item->util;
+ if (safe_create_leading_directories(path)) {
+ strbuf_addf(err, "could not create leading directories of '%s': %s",
+ path, strerror(errno));
+ goto failure;
+ }
+
+ if (hold_lock_file_for_update(lock, path, 0) < 0) {
+ unable_to_lock_message(path, errno, err);
+ goto failure;
+ }
+
+ /* copy existing reflog over */
+ infd = open(git_path("logs/%s", refname), O_RDONLY);
+ while ((nread = read(infd, buf, 1024)) > 0)
+ write_in_full(lock->fd, buf, nread);
+ if (nread < 0) {
+ strbuf_addf(err, "could not read reflog '%s': %s", refname, strerror(errno));
+ goto failure;
+ }
+ close(infd);
+ }
+ lock = item->util;
+
+ if (email)
+ strbuf_addf(&buf, "%s %lu %+05d", email, timestamp, tz);
+
+ if (msg &&
+ log_ref_write_fd(lock->fd, old_sha1, new_sha1, buf.buf, msg)) {
+ strbuf_addf(err, "Could not write to reflog: %s. %s",
+ refname, strerror(errno));
+ goto failure;
+ }
+ strbuf_release(&buf);
+
+ return 0;
+failure:
+ strbuf_release(&buf);
+ transaction->state = TRANSACTION_CLOSED;
+ return -1;
+}
+
int transaction_create_ref(struct transaction *transaction,
const char *refname,
const unsigned char *new_sha1,
@@ -3713,13 +3829,14 @@ int transaction_commit(struct transaction *transaction,
const char **delnames;
int n = transaction->nr;
struct ref_update **updates = transaction->ref_updates;
+ struct string_list_item *item;
assert(err);
if (transaction->state != TRANSACTION_OPEN)
die("BUG: commit called for transaction that is not open");
- if (!n) {
+ if (!n && !transaction->reflog_updates.nr) {
transaction->state = TRANSACTION_CLOSED;
return 0;
}
@@ -3796,6 +3913,13 @@ int transaction_commit(struct transaction *transaction,
}
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
+
+ /* Commit all reflog updates*/
+ for_each_string_list_item(item, &transaction->reflog_updates) {
+ struct lock_file *lock = item->util;
+ commit_lock_file_to(lock, git_path("logs/%s", item->string));
+ }
+
clear_loose_ref_cache(&ref_cache);
cleanup:
diff --git a/refs.h b/refs.h
index 556adfd..1afc72c 100644
--- a/refs.h
+++ b/refs.h
@@ -328,6 +328,26 @@ int transaction_delete_ref(struct transaction *transaction,
struct strbuf *err);
/*
+ * Truncate the reflog for the given refname.
+ */
+int transaction_truncate_reflog(struct transaction *transaction,
+ const char *refname,
+ struct strbuf *err);
+
+/*
+ * Append a reflog entry for refname. If msg is NULL no update will be
+ * written to the log.
+ */
+int transaction_update_reflog(struct transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ const unsigned char *old_sha1,
+ const char *email,
+ unsigned long timestamp, int tz,
+ const char *msg,
+ struct strbuf *err);
+
+/*
* Commit all of the changes that have been queued in transaction, as
* atomically as possible.
*
--
2.2.0
next prev parent reply other threads:[~2014-12-04 8:30 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-12-04 8:29 [PATCHv3 00/13] the refs-transactions-reflog series Stefan Beller
2014-12-04 8:29 ` [PATCH 01/13] refs.c: make ref_transaction_create a wrapper for ref_transaction_update Stefan Beller
2014-12-04 8:29 ` [PATCH 02/13] refs.c: make ref_transaction_delete " Stefan Beller
2014-12-04 8:29 ` [PATCH 03/13] refs.c: add a function to append a reflog entry to a fd Stefan Beller
2014-12-04 8:29 ` [PATCH 04/13] refs.c: rename the transaction functions Stefan Beller
2014-12-04 8:29 ` [PATCH 05/13] refs.c: rename transaction.updates to transaction.ref_updates Stefan Beller
2014-12-04 8:29 ` Stefan Beller [this message]
2014-12-04 8:29 ` [PATCH 07/13] reflog.c: use a reflog transaction when writing during expire Stefan Beller
2014-12-04 8:29 ` [PATCH 08/13] refs.c: rename log_ref_setup to create_reflog Stefan Beller
2014-12-04 8:29 ` [PATCH 09/13] refs.c: remove unlock_ref/close_ref/commit_ref from the refs api Stefan Beller
2014-12-04 8:29 ` [PATCH 10/13] refs.c: remove lock_any_ref_for_update Stefan Beller
2014-12-04 8:29 ` [PATCH 11/13] refs.c: don't expose the internal struct ref_lock in the header file Stefan Beller
2014-12-04 8:29 ` [PATCH 12/13] refs.c: use a bit for ref_update have_old Stefan Beller
2014-12-04 16:10 ` Torsten Bögershausen
2014-12-04 17:00 ` Andreas Schwab
2014-12-04 8:29 ` [PATCH 13/13] refs.c: allow deleting refs with a broken sha1 Stefan Beller
2014-12-04 17:10 ` [PATCHv3 00/13] the refs-transactions-reflog series Michael Haggerty
2014-12-04 17:53 ` Jonathan Nieder
2014-12-04 18:14 ` Jonathan Nieder
2014-12-04 18:32 ` Stefan Beller
2014-12-04 21:13 ` Michael Haggerty
2014-12-04 18:37 ` Junio C Hamano
2014-12-04 18:41 ` Junio C Hamano
2014-12-04 18:49 ` Stefan Beller
2014-12-04 19:27 ` Jonathan Nieder
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1417681763-32334-7-git-send-email-sbeller@google.com \
--to=sbeller@google.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=jrnieder@gmail.com \
--cc=mhagger@alum.mit.edu \
--cc=ronniesahlberg@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).