Re: Tracking branch history

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Shawn Pearce <spearce@xxxxxxxxxxx> wrote:
> Junio C Hamano <junkio@xxxxxxx> wrote:
> > Shawn Pearce <spearce@xxxxxxxxxxx> writes:
> > 
> > > This is all well and good but its sort of useless without the diffcore
> > > being able to lookup what SHA1 was valid on a given branch at a given
> > > point in time.  :-)
> > >
> > > I'm thinking about extending the 'extended SHA1' syntax to accept
> > > a date (or date expression) as a suffix:
> > >
> > > 	HEAD@'2 hours ago'
> > > 	HEAD@'2006-04-20'
> > > 	HEAD@'2006-04-20 14:12'
> > >
> > > etc... This would be merged into get_sha1 (sha1_name.c) so its
> > > usable pretty much anywhere.  Does this seem reasonable?  If so
> > > I'll work up a patch for it.

This is a preliminary patch for this syntax.  I haven't handled the
absolute date parsing yet; I was hoping to use the same syntax
accepted by GIT_AUTHOR_DATE/GIT_COMMITTER_DATE but looking at the
code in date.c it wasn't going to be easily reused.  I'll work
on it more tomorrow, right now I have to go do my day job to pay
the rent.  :-)

Hmm... A quick look at date.c indicates I should be able to clean up
this parse_date_spec function quite a bit by using code from date.c.
I'll look at it more later.

-- >8 -
Support 'master@2 hours ago' syntax

Extended sha1 expressions may now include date specifications
which indicate a point in time within the local repository's
history.  If the ref indicated to the left of '@' has a log in
$GIT_DIR/logs/<ref> then the value of the ref at the time indicated
by the specification is obtained from the ref's log.

---

 Documentation/git-rev-parse.txt |    6 ++
 refs.c                          |   52 ++++++++++++++
 refs.h                          |    3 +
 sha1_name.c                     |  145 ++++++++++++++++++++++++++++++++++-----
 4 files changed, 189 insertions(+), 17 deletions(-)

1f16364fd8cadb6cdeb0b14a6f5439f02b578924
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index ab896fc..df308c3 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -124,6 +124,12 @@ syntax.
   happen to have both heads/master and tags/master, you can
   explicitly say 'heads/master' to tell git which one you mean.
 
+* A suffix '@' followed by a date specification such as 'yesterday'
+  (24 hours ago) or '1 month 2 weeks 3 days 1 hour 1 second ago'
+  to specify the value of the ref at a prior point in time.
+  This suffix may only be used immediately following a ref name
+  and the ref must have an existing log ($GIT_DIR/logs/<ref>).
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
diff --git a/refs.c b/refs.c
index a50ea8f..da009ac 100644
--- a/refs.c
+++ b/refs.c
@@ -440,3 +440,55 @@ int log_ref_update(const char *ref, cons
 	close(logfd);
 	return 0;
 }
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+	const char *logfile, *logdata, *rec, *c;
+	char *tz_c;
+	int logfd, tz;
+	struct stat st;
+	unsigned long date;
+	unsigned char oldsha1[20];
+
+	logfile = git_path("logs/%s", ref);
+	logfd = open(logfile, O_RDONLY, 0);
+	if (logfd < 0)
+		die("Unable to read log %s: %s", logfile, strerror(errno));
+	fstat(logfd, &st);
+	if (!st.st_size)
+		die("Log %s is empty.", logfile);
+	logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+	close(logfd);
+
+	rec = logdata + st.st_size;
+	while (logdata < rec) {
+		if (logdata < rec && *(rec-1) == '\n')
+			rec--;
+		while (logdata < rec && *(rec-1) != '\n')
+			rec--;
+		c = rec;
+		while (*c++ != '>')
+			/* nada */;
+		date = strtoul(c, NULL, 10);
+		if (date <= at_time) {
+			if (get_sha1_hex(rec, oldsha1))
+				die("Log %s is corrupt.", logfile);
+			if (get_sha1_hex(rec + 41, sha1))
+				die("Log %s is corrupt.", logfile);
+			munmap((void*)logdata, st.st_size);
+			return 0;
+		}
+	}
+
+	c = logdata;
+	while (*c++ != '>')
+		/* nada */;
+	date = strtoul(c, &tz_c, 10);
+	tz = strtoul(tz_c, NULL, 10);
+	if (get_sha1_hex(logdata, sha1))
+		die("Log %s is corrupt.", logfile);
+	munmap((void*)logdata, st.st_size);
+	fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+		logfile, show_rfc2822_date(date, tz));
+	return 0;
+}
diff --git a/refs.h b/refs.h
index de3cb92..4831cdb 100644
--- a/refs.h
+++ b/refs.h
@@ -31,4 +31,7 @@ extern int check_ref_format(const char *
 /** If logging is enabled logs the change made to the ref. **/
 extern int log_ref_update(const char *ref, const unsigned char *currsha1, const unsigned char *newsha1, const char *msg);
 
+/** Reads log for the value of ref during at_time. **/
+extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
+
 #endif /* REFS_H */
diff --git a/sha1_name.c b/sha1_name.c
index dc68355..5f33aea 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -4,6 +4,7 @@ #include "commit.h"
 #include "tree.h"
 #include "blob.h"
 #include "tree-walk.h"
+#include "refs.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -234,6 +235,98 @@ static int ambiguous_path(const char *pa
 	return slash;
 }
 
+static unsigned long parse_date_spec(const char *str, int len)
+{
+	long delta;
+	time_t now;
+
+	time(&now);
+	if (len == 9 && !strncasecmp("yesterday", str, 9))
+		return now - 24 * 60 * 60;
+	if (len > 4 && !strncasecmp(" ago", str + (len - 4), 4)) {
+		len -= 4;
+		while (len) {
+			if (len > 2 && !strncasecmp("a ", str, 2)) {
+				delta = 1;
+				len -= 2;
+				str += 2;
+			}
+			else if (len > 3 && !strncasecmp("an ", str, 3)) {
+				delta = 1;
+				len -= 2;
+				str += 2;
+			} else {
+				delta = 0;
+				while (len && isdigit(*str)) {
+					if (delta)
+						delta *= 10;
+					delta += *str++ - '0';
+					len--;
+				}
+				if (!delta)
+					return (time_t)-1;
+				while (len && isspace(*str)) {
+					str++;
+					len--;
+				}
+			}
+
+			if (len >= 5 && !strncasecmp("month", str, 5)) {
+				len -= 5;
+				str += 5;
+				now -= 30 * 24 * 60 * 60 * delta;
+			}
+			else if (len >= 4 && !strncasecmp("week", str, 4)) {
+				len -= 4;
+				str += 4;
+				now -= 7 * 24 * 60 * 60 * delta;
+			}
+			else if (len >= 3 && !strncasecmp("day", str, 3)) {
+				len -= 3;
+				str += 3;
+				now -= 24 * 60 * 60 * delta;
+			}
+			else if (len >= 4 && !strncasecmp("hour", str, 4)) {
+				len -= 4;
+				str += 4;
+				now -= 60 * 60 * delta;
+			}
+			else if (len >= 6 && !strncasecmp("minute", str, 6)) {
+				len -= 6;
+				str += 6;
+				now -= 60 * delta;
+			}
+			else if (len >= 3 && !strncasecmp("min", str, 3)) {
+				len -= 3;
+				str += 3;
+				now -= 60 * delta;
+			}
+			else if (len >= 6 && !strncasecmp("second", str, 6)) {
+				len -= 6;
+				str += 6;
+				now -= delta;
+			}
+			else if (len >= 3 && !strncasecmp("sec", str, 3)) {
+				len -= 3;
+				str += 3;
+				now -= delta;
+			}
+
+			if (len && *str == 's') {
+				len -= 1;
+				str += 1;
+			}
+
+			while (len && isspace(*str)) {
+				str++;
+				len--;
+			}
+		}
+		return now;
+	}
+	return (time_t)-1;
+}
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
 	static const char *fmt[] = {
@@ -245,36 +338,54 @@ static int get_sha1_basic(const char *st
 		"refs/remotes/%.*s/HEAD",
 		NULL
 	};
-	const char **p;
-	const char *warning = "warning: refname '%.*s' is ambiguous.\n";
-	char *pathname;
-	int already_found = 0;
+	static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+	const char **p, *pathname;
+	char *real_path = NULL;
+	int refs_found = 0, at_mark;
+	unsigned long at_time = (unsigned long)-1;
 	unsigned char *this_result;
 	unsigned char sha1_from_ref[20];
 
 	if (len == 40 && !get_sha1_hex(str, sha1))
 		return 0;
 
+	/* At a given period of time? "@2 hours ago" */
+	for (at_mark = 1; at_mark < len; at_mark++) {
+		if (str[at_mark] == '@') {
+			at_time = parse_date_spec(str + at_mark + 1, len - at_mark - 1);
+			if (at_time == (unsigned long)-1)
+				die("Invalid date spec after @ in '%.*s'", len, str);
+			len = at_mark;
+		}
+	}
+
 	/* Accept only unambiguous ref paths. */
 	if (ambiguous_path(str, len))
 		return -1;
 
 	for (p = fmt; *p; p++) {
-		this_result = already_found ? sha1_from_ref : sha1;
-		pathname = git_path(*p, len, str);
-		if (!read_ref(pathname, this_result)) {
-			if (warn_ambiguous_refs) {
-				if (already_found)
-					fprintf(stderr, warning, len, str);
-				already_found++;
-			}
-			else
-				return 0;
+		this_result = refs_found ? sha1_from_ref : sha1;
+		pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
+		if (pathname) {
+			if (!refs_found++)
+				real_path = strdup(pathname);
+			if (!warn_ambiguous_refs)
+				break;
 		}
 	}
-	if (already_found)
-		return 0;
-	return -1;
+
+	if (!refs_found)
+		return -1;
+
+	if (warn_ambiguous_refs && refs_found > 1)
+		fprintf(stderr, warning, len, str);
+
+	if (at_time != (unsigned long)-1) {
+		read_ref_at(real_path + strlen(git_path(".")) - 1, at_time, sha1);
+	}
+
+	free(real_path);
+	return 0;
 }
 
 static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-- 
1.3.2.g7278

-
: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]