git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Enumerating reflog entries in a wrong order
@ 2013-03-08 21:53 Junio C Hamano
  2013-03-08 21:53 ` [PATCH 1/3] for_each_reflog_ent(): extract a helper to process a single entry Junio C Hamano
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Junio C Hamano @ 2013-03-08 21:53 UTC (permalink / raw)
  To: git

The for_each_reflog_ent() function yields reflog entries from the
oldest to newer, and is inefficient when we know what we are looking
for are near the newest end (e.g. "find the nth newest reflog entry
that matches 'checkout: moving from X to Y'").  To optimize for the
common case, we introduced for_each_recent_reflog_ent() to scan only
the newest part (i.e. tail) of the reflog file, but it is difficult
to use this function correctly.

Just bite the bullet and stop working around the API that reads the
file in a wrong order.  The new for_each_reflog_ent_reverse()
function gives us reflog entries from the newest to older.

Junio C Hamano (3):
  for_each_reflog_ent(): extract a helper to process a single entry
  for_each_recent_reflog_ent(): simplify opening of a reflog file
  reflog: add for_each_reflog_ent_reverse() API

 refs.c      | 161 +++++++++++++++++++++++++++++++++++++++++++-----------------
 refs.h      |   2 +-
 sha1_name.c |  48 +++++++-----------
 3 files changed, 134 insertions(+), 77 deletions(-)

-- 
1.8.2-rc3-189-g94c4d42

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

* [PATCH 1/3] for_each_reflog_ent(): extract a helper to process a single entry
  2013-03-08 21:53 [PATCH 0/3] Enumerating reflog entries in a wrong order Junio C Hamano
@ 2013-03-08 21:53 ` Junio C Hamano
  2013-03-08 21:53 ` [PATCH 2/3] for_each_recent_reflog_ent(): simplify opening of a reflog file Junio C Hamano
  2013-03-08 21:53 ` [PATCH 3/3] reflog: add for_each_reflog_ent_reverse() API Junio C Hamano
  2 siblings, 0 replies; 4+ messages in thread
From: Junio C Hamano @ 2013-03-08 21:53 UTC (permalink / raw)
  To: git

Split the logic that takes a single line of reflog entry in a strbuf
parses it and calls the callback function out of the loop into a
separate helper function.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 refs.c | 59 ++++++++++++++++++++++++++++++-----------------------------
 1 file changed, 30 insertions(+), 29 deletions(-)

diff --git a/refs.c b/refs.c
index da74a2b..9f702a7 100644
--- a/refs.c
+++ b/refs.c
@@ -2290,6 +2290,34 @@ int read_ref_at(const char *refname, unsigned long at_time, int cnt,
 	return 1;
 }
 
+static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
+{
+	unsigned char osha1[20], nsha1[20];
+	char *email_end, *message;
+	unsigned long timestamp;
+	int tz;
+
+	/* old SP new SP name <email> SP time TAB msg LF */
+	if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' ||
+	    get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' ||
+	    get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' ||
+	    !(email_end = strchr(sb->buf + 82, '>')) ||
+	    email_end[1] != ' ' ||
+	    !(timestamp = strtoul(email_end + 2, &message, 10)) ||
+	    !message || message[0] != ' ' ||
+	    (message[1] != '+' && message[1] != '-') ||
+	    !isdigit(message[2]) || !isdigit(message[3]) ||
+	    !isdigit(message[4]) || !isdigit(message[5]))
+		return 0; /* corrupt? */
+	email_end[1] = '\0';
+	tz = strtol(message + 1, NULL, 10);
+	if (message[6] != '\t')
+		message += 6;
+	else
+		message += 7;
+	return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data);
+}
+
 int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
 {
 	const char *logfile;
@@ -2314,35 +2342,8 @@ int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long
 		}
 	}
 
-	while (!strbuf_getwholeline(&sb, logfp, '\n')) {
-		unsigned char osha1[20], nsha1[20];
-		char *email_end, *message;
-		unsigned long timestamp;
-		int tz;
-
-		/* old SP new SP name <email> SP time TAB msg LF */
-		if (sb.len < 83 || sb.buf[sb.len - 1] != '\n' ||
-		    get_sha1_hex(sb.buf, osha1) || sb.buf[40] != ' ' ||
-		    get_sha1_hex(sb.buf + 41, nsha1) || sb.buf[81] != ' ' ||
-		    !(email_end = strchr(sb.buf + 82, '>')) ||
-		    email_end[1] != ' ' ||
-		    !(timestamp = strtoul(email_end + 2, &message, 10)) ||
-		    !message || message[0] != ' ' ||
-		    (message[1] != '+' && message[1] != '-') ||
-		    !isdigit(message[2]) || !isdigit(message[3]) ||
-		    !isdigit(message[4]) || !isdigit(message[5]))
-			continue; /* corrupt? */
-		email_end[1] = '\0';
-		tz = strtol(message + 1, NULL, 10);
-		if (message[6] != '\t')
-			message += 6;
-		else
-			message += 7;
-		ret = fn(osha1, nsha1, sb.buf + 82, timestamp, tz, message,
-			 cb_data);
-		if (ret)
-			break;
-	}
+	while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
+		ret = show_one_reflog_ent(&sb, fn, cb_data);
 	fclose(logfp);
 	strbuf_release(&sb);
 	return ret;
-- 
1.8.2-rc3-189-g94c4d42

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

* [PATCH 2/3] for_each_recent_reflog_ent(): simplify opening of a reflog file
  2013-03-08 21:53 [PATCH 0/3] Enumerating reflog entries in a wrong order Junio C Hamano
  2013-03-08 21:53 ` [PATCH 1/3] for_each_reflog_ent(): extract a helper to process a single entry Junio C Hamano
@ 2013-03-08 21:53 ` Junio C Hamano
  2013-03-08 21:53 ` [PATCH 3/3] reflog: add for_each_reflog_ent_reverse() API Junio C Hamano
  2 siblings, 0 replies; 4+ messages in thread
From: Junio C Hamano @ 2013-03-08 21:53 UTC (permalink / raw)
  To: git

There is no reason to use a temporary variable logfile.
---
 refs.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/refs.c b/refs.c
index 9f702a7..e302521 100644
--- a/refs.c
+++ b/refs.c
@@ -2320,13 +2320,11 @@ static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *c
 
 int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
 {
-	const char *logfile;
 	FILE *logfp;
 	struct strbuf sb = STRBUF_INIT;
 	int ret = 0;
 
-	logfile = git_path("logs/%s", refname);
-	logfp = fopen(logfile, "r");
+	logfp = fopen(git_path("logs/%s", refname), "r");
 	if (!logfp)
 		return -1;
 
-- 
1.8.2-rc3-189-g94c4d42

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

* [PATCH 3/3] reflog: add for_each_reflog_ent_reverse() API
  2013-03-08 21:53 [PATCH 0/3] Enumerating reflog entries in a wrong order Junio C Hamano
  2013-03-08 21:53 ` [PATCH 1/3] for_each_reflog_ent(): extract a helper to process a single entry Junio C Hamano
  2013-03-08 21:53 ` [PATCH 2/3] for_each_recent_reflog_ent(): simplify opening of a reflog file Junio C Hamano
@ 2013-03-08 21:53 ` Junio C Hamano
  2 siblings, 0 replies; 4+ messages in thread
From: Junio C Hamano @ 2013-03-08 21:53 UTC (permalink / raw)
  To: git

"git checkout -" is a short-hand for "git checkout @{-1}" and the
"@{nth}" notation for a negative number is to find nth previous
checkout in the reflog of the HEAD to determine the name of the
branch the user was on.  We would want to find the nth most recent
reflog entry that matches "checkout: moving from X to Y" for this.

Unfortunately, reflog is implemented as an append-only file, and the
API to iterate over its entries, for_each_reflog_ent(), reads the
file in order, giving the entries from oldest to newer.  For the
purpose of finding nth most recent one, this API makes us to record
the last n entries in a rotating buffer and give the result out only
after we read everything.  To optimize for a common case of finding
the nth most recent one for a small value of n, we also have a side
API for_each_recent_reflog_ent() that starts reading near the end of
the file, but it still reads the entries in the "wrong" order.  The
implementation of understanding @{-1} uses this interface.

This all becomes unnecessary if we had an API to let us iterate over
reflog entries in the reverse order, from the newest to older.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 refs.c      | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++----------
 refs.h      |   2 +-
 sha1_name.c |  48 +++++++++++-----------------
 3 files changed, 105 insertions(+), 47 deletions(-)

diff --git a/refs.c b/refs.c
index e302521..8e24060 100644
--- a/refs.c
+++ b/refs.c
@@ -2318,30 +2318,89 @@ static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *c
 	return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data);
 }
 
-int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
+static char *find_beginning_of_line(char *bob, char *scan)
+{
+	while (bob < scan && *(--scan) != '\n')
+		; /* keep scanning backwards */
+	/*
+	 * Return either beginning of the buffer, or LF at the end of
+	 * the previous line.
+	 */
+	return scan;
+}
+
+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data)
 {
-	FILE *logfp;
 	struct strbuf sb = STRBUF_INIT;
-	int ret = 0;
+	FILE *logfp;
+	long pos;
+	int ret = 0, at_tail = 1;
 
 	logfp = fopen(git_path("logs/%s", refname), "r");
 	if (!logfp)
 		return -1;
 
-	if (ofs) {
-		struct stat statbuf;
-		if (fstat(fileno(logfp), &statbuf) ||
-		    statbuf.st_size < ofs ||
-		    fseek(logfp, -ofs, SEEK_END) ||
-		    strbuf_getwholeline(&sb, logfp, '\n')) {
-			fclose(logfp);
-			strbuf_release(&sb);
-			return -1;
+	/* Jump to the end */
+	if (fseek(logfp, 0, SEEK_END) < 0)
+		return error("cannot seek back reflog for %s: %s",
+			     refname, strerror(errno));
+	pos = ftell(logfp);
+	while (!ret && 0 < pos) {
+		int cnt;
+		size_t nread;
+		char buf[BUFSIZ];
+		char *endp, *scanp;
+
+		/* Fill next block from the end */
+		cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos;
+		if (fseek(logfp, pos - cnt, SEEK_SET))
+			return error("cannot seek back reflog for %s: %s",
+				     refname, strerror(errno));
+		nread = fread(buf, cnt, 1, logfp);
+		if (nread < 0)
+			return error("cannot read %d bytes from reflog for %s: %s",
+				     cnt, refname, strerror(errno));
+		pos -= cnt;
+
+		scanp = endp = buf + cnt;
+		if (at_tail && scanp[-1] == '\n')
+			/* Looking at the final LF at the end of the file */
+			scanp--;
+		at_tail = 0;
+
+		while (buf < scanp) {
+			/*
+			 * terminating LF of the previous line, or the beginning
+			 * of the buffer.
+			 */
+			char *bp;
+
+			bp = find_beginning_of_line(buf, scanp);
+
+			if (*bp != '\n') {
+				strbuf_splice(&sb, 0, 0, buf, endp - buf);
+				if (pos)
+					break; /* need to fill another block */
+				scanp = buf - 1; /* leave loop */
+			} else {
+				/*
+				 * (bp + 1) thru endp is the beginning of the
+				 * current line we have in sb
+				 */
+				strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));
+				scanp = bp;
+				endp = bp + 1;
+			}
+			ret = show_one_reflog_ent(&sb, fn, cb_data);
+			strbuf_reset(&sb);
+			if (ret)
+				break;
 		}
-	}
 
-	while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
+	}
+	if (!ret && sb.len)
 		ret = show_one_reflog_ent(&sb, fn, cb_data);
+
 	fclose(logfp);
 	strbuf_release(&sb);
 	return ret;
@@ -2349,9 +2408,20 @@ int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long
 
 int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
 {
-	return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
-}
+	FILE *logfp;
+	struct strbuf sb = STRBUF_INIT;
+	int ret = 0;
+
+	logfp = fopen(git_path("logs/%s", refname), "r");
+	if (!logfp)
+		return -1;
 
+	while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
+		ret = show_one_reflog_ent(&sb, fn, cb_data);
+	fclose(logfp);
+	strbuf_release(&sb);
+	return ret;
+}
 /*
  * Call fn for each reflog in the namespace indicated by name.  name
  * must be empty or end with '/'.  Name will be used as a scratch
diff --git a/refs.h b/refs.h
index d6c2fe2..a62b9db 100644
--- a/refs.h
+++ b/refs.h
@@ -103,7 +103,7 @@ extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
 int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
-int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data);
+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
diff --git a/sha1_name.c b/sha1_name.c
index 95003c7..635cd13 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -856,8 +856,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
 }
 
 struct grab_nth_branch_switch_cbdata {
-	long cnt, alloc;
-	struct strbuf *buf;
+	int remaining;
+	struct strbuf buf;
 };
 
 static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
@@ -867,7 +867,6 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 	struct grab_nth_branch_switch_cbdata *cb = cb_data;
 	const char *match = NULL, *target = NULL;
 	size_t len;
-	int nth;
 
 	if (!prefixcmp(message, "checkout: moving from ")) {
 		match = message + strlen("checkout: moving from ");
@@ -876,11 +875,12 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 
 	if (!match || !target)
 		return 0;
-
-	len = target - match;
-	nth = cb->cnt++ % cb->alloc;
-	strbuf_reset(&cb->buf[nth]);
-	strbuf_add(&cb->buf[nth], match, len);
+	if (--(cb->remaining) == 0) {
+		len = target - match;
+		strbuf_reset(&cb->buf);
+		strbuf_add(&cb->buf, match, len);
+		return 1; /* we are done */
+	}
 	return 0;
 }
 
@@ -891,7 +891,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
 {
 	long nth;
-	int i, retval;
+	int retval;
 	struct grab_nth_branch_switch_cbdata cb;
 	const char *brace;
 	char *num_end;
@@ -901,34 +901,22 @@ static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
 	brace = strchr(name, '}');
 	if (!brace)
 		return -1;
-	nth = strtol(name+3, &num_end, 10);
+	nth = strtol(name + 3, &num_end, 10);
 	if (num_end != brace)
 		return -1;
 	if (nth <= 0)
 		return -1;
-	cb.alloc = nth;
-	cb.buf = xmalloc(nth * sizeof(struct strbuf));
-	for (i = 0; i < nth; i++)
-		strbuf_init(&cb.buf[i], 20);
-	cb.cnt = 0;
+	cb.remaining = nth;
+	strbuf_init(&cb.buf, 20);
+
 	retval = 0;
-	for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
-	if (cb.cnt < nth) {
-		cb.cnt = 0;
-		for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+	if (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) {
+		strbuf_reset(buf);
+		strbuf_add(buf, cb.buf.buf, cb.buf.len);
+		retval = brace - name + 1;
 	}
-	if (cb.cnt < nth)
-		goto release_return;
-	i = cb.cnt % nth;
-	strbuf_reset(buf);
-	strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
-	retval = brace-name+1;
-
-release_return:
-	for (i = 0; i < nth; i++)
-		strbuf_release(&cb.buf[i]);
-	free(cb.buf);
 
+	strbuf_release(&cb.buf);
 	return retval;
 }
 
-- 
1.8.2-rc3-189-g94c4d42

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

end of thread, other threads:[~2013-03-08 21:54 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-03-08 21:53 [PATCH 0/3] Enumerating reflog entries in a wrong order Junio C Hamano
2013-03-08 21:53 ` [PATCH 1/3] for_each_reflog_ent(): extract a helper to process a single entry Junio C Hamano
2013-03-08 21:53 ` [PATCH 2/3] for_each_recent_reflog_ent(): simplify opening of a reflog file Junio C Hamano
2013-03-08 21:53 ` [PATCH 3/3] reflog: add for_each_reflog_ent_reverse() API 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).