summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-rev-parse.txt6
-rw-r--r--refs.c53
-rw-r--r--refs.h3
-rw-r--r--sha1_name.c59
4 files changed, 104 insertions, 17 deletions
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index ab896fcf83..df308c3f5f 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 4be75a59aa..4c99e3729f 100644
--- a/refs.c
+++ b/refs.c
@@ -429,3 +429,56 @@ int write_ref_sha1(struct ref_lock *lock,
unlock_ref(lock);
return 0;
}
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+ const char *logfile, *logdata, *logend, *rec, *c;
+ char *tz_c;
+ int logfd, tz;
+ struct stat st;
+ unsigned long date;
+
+ 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 = logend = 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 < logend && *c != '>' && *c != '\n')
+ c++;
+ if (c == logend || *c == '\n')
+ die("Log %s is corrupt.", logfile);
+ date = strtoul(c, NULL, 10);
+ if (date <= at_time) {
+ 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 < logend && *c != '>' && *c != '\n')
+ c++;
+ if (c == logend || *c == '\n')
+ die("Log %s is corrupt.", logfile);
+ 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 43831e9be7..2c854de3fe 100644
--- a/refs.h
+++ b/refs.h
@@ -34,6 +34,9 @@ extern void unlock_ref (struct ref_lock *lock);
/** Writes sha1 into the ref specified by the lock. **/
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, 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);
+
/** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target);
diff --git a/sha1_name.c b/sha1_name.c
index dc6835520c..3ac3ab4f58 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -4,6 +4,7 @@
#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)
{
@@ -245,36 +246,60 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
"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] == '@') {
+ int date_len = len - at_mark - 1;
+ char *date_spec = xmalloc(date_len + 1);
+ strncpy(date_spec, str + at_mark + 1, date_len);
+ date_spec[date_len] = 0;
+ at_time = approxidate(date_spec);
+ free(date_spec);
+ 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);