{"thread":{"id":"4181","subject":"[RFC 5/5] Support 'master@2 hours ago' syntax","startedAt":"2006-05-17T09:56:09Z","lastAt":"2006-05-17T22:32:46Z","messageCount":7,"participants":["Shawn Pearce","Junio C Hamano","Linus Torvalds"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"20127","messageId":"20060517095609.GF28529@spearce.org","threadId":"4181","inReplyTo":null,"subject":"[RFC 5/5] Support 'master@2 hours ago' syntax","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-17T09:56:09Z","receivedAt":"2006-05-17T09:56:09Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Extended sha1 expressions may now include date specifications\nwhich indicate a point in time within the local repository's\nhistory.  If the ref indicated to the left of '@' has a log in\n$GIT_DIR/logs/<ref> then the value of the ref at the time indicated\nby the specification is obtained from the ref's log.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n\n---\n\n Documentation/git-rev-parse.txt |    6 ++++\n refs.c                          |   53 +++++++++++++++++++++++++++++++++++\n refs.h                          |    3 ++\n sha1_name.c                     |   59 ++++++++++++++++++++++++++++-----------\n 4 files changed, 104 insertions(+), 17 deletions(-)\n\nefffa32e4896acd7978767b6a856fc7421060040\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex ab896fc..df308c3 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -124,6 +124,12 @@ syntax.\n   happen to have both heads/master and tags/master, you can\n   explicitly say 'heads/master' to tell git which one you mean.\n \n+* A suffix '@' followed by a date specification such as 'yesterday'\n+  (24 hours ago) or '1 month 2 weeks 3 days 1 hour 1 second ago'\n+  to specify the value of the ref at a prior point in time.\n+  This suffix may only be used immediately following a ref name\n+  and the ref must have an existing log ($GIT_DIR/logs/<ref>).\n+\n * A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\n   'rev{caret}'\ndiff --git a/refs.c b/refs.c\nindex 4be75a5..4c99e37 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -429,3 +429,56 @@ int write_ref_sha1(struct ref_lock *lock\n \tunlock_ref(lock);\n \treturn 0;\n }\n+\n+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)\n+{\n+\tconst char *logfile, *logdata, *logend, *rec, *c;\n+\tchar *tz_c;\n+\tint logfd, tz;\n+\tstruct stat st;\n+\tunsigned long date;\n+\n+\tlogfile = git_path(\"logs/%s\", ref);\n+\tlogfd = open(logfile, O_RDONLY, 0);\n+\tif (logfd < 0)\n+\t\tdie(\"Unable to read log %s: %s\", logfile, strerror(errno));\n+\tfstat(logfd, &st);\n+\tif (!st.st_size)\n+\t\tdie(\"Log %s is empty.\", logfile);\n+\tlogdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);\n+\tclose(logfd);\n+\n+\trec = logend = logdata + st.st_size;\n+\twhile (logdata < rec) {\n+\t\tif (logdata < rec && *(rec-1) == '\\n')\n+\t\t\trec--;\n+\t\twhile (logdata < rec && *(rec-1) != '\\n')\n+\t\t\trec--;\n+\t\tc = rec;\n+\t\twhile (c < logend && *c != '>' && *c != '\\n')\n+\t\t\tc++;\n+\t\tif (c == logend || *c == '\\n')\n+\t\t\tdie(\"Log %s is corrupt.\", logfile);\n+\t\tdate = strtoul(c, NULL, 10);\n+\t\tif (date <= at_time) {\n+\t\t\tif (get_sha1_hex(rec + 41, sha1))\n+\t\t\t\tdie(\"Log %s is corrupt.\", logfile);\n+\t\t\tmunmap((void*)logdata, st.st_size);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tc = logdata;\n+\twhile (c < logend && *c != '>' && *c != '\\n')\n+\t\tc++;\n+\tif (c == logend || *c == '\\n')\n+\t\tdie(\"Log %s is corrupt.\", logfile);\n+\tdate = strtoul(c, &tz_c, 10);\n+\ttz = strtoul(tz_c, NULL, 10);\n+\tif (get_sha1_hex(logdata, sha1))\n+\t\tdie(\"Log %s is corrupt.\", logfile);\n+\tmunmap((void*)logdata, st.st_size);\n+\tfprintf(stderr, \"warning: Log %s only goes back to %s.\\n\",\n+\t\tlogfile, show_rfc2822_date(date, tz));\n+\treturn 0;\n+}\ndiff --git a/refs.h b/refs.h\nindex 43831e9..2c854de 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -34,6 +34,9 @@ extern void unlock_ref (struct ref_lock \n /** Writes sha1 into the ref specified by the lock. **/\n extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);\n \n+/** Reads log for the value of ref during at_time. **/\n+extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);\n+\n /** Returns 0 if target has the right format for a ref. **/\n extern int check_ref_format(const char *target);\n \ndiff --git a/sha1_name.c b/sha1_name.c\nindex dc68355..3ac3ab4 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -4,6 +4,7 @@ #include \"commit.h\"\n #include \"tree.h\"\n #include \"blob.h\"\n #include \"tree-walk.h\"\n+#include \"refs.h\"\n \n static int find_short_object_filename(int len, const char *name, unsigned char *sha1)\n {\n@@ -245,36 +246,60 @@ static int get_sha1_basic(const char *st\n \t\t\"refs/remotes/%.*s/HEAD\",\n \t\tNULL\n \t};\n-\tconst char **p;\n-\tconst char *warning = \"warning: refname '%.*s' is ambiguous.\\n\";\n-\tchar *pathname;\n-\tint already_found = 0;\n+\tstatic const char *warning = \"warning: refname '%.*s' is ambiguous.\\n\";\n+\tconst char **p, *pathname;\n+\tchar *real_path = NULL;\n+\tint refs_found = 0, at_mark;\n+\tunsigned long at_time = (unsigned long)-1;\n \tunsigned char *this_result;\n \tunsigned char sha1_from_ref[20];\n \n \tif (len == 40 && !get_sha1_hex(str, sha1))\n \t\treturn 0;\n \n+\t/* At a given period of time? \"@2 hours ago\" */\n+\tfor (at_mark = 1; at_mark < len; at_mark++) {\n+\t\tif (str[at_mark] == '@') {\n+\t\t\tint date_len = len - at_mark - 1;\n+\t\t\tchar *date_spec = xmalloc(date_len + 1);\n+\t\t\tstrncpy(date_spec, str + at_mark + 1, date_len);\n+\t\t\tdate_spec[date_len] = 0;\n+\t\t\tat_time = approxidate(date_spec);\n+\t\t\tfree(date_spec);\n+\t\t\tlen = at_mark;\n+\t\t}\n+\t}\n+\n \t/* Accept only unambiguous ref paths. */\n \tif (ambiguous_path(str, len))\n \t\treturn -1;\n \n \tfor (p = fmt; *p; p++) {\n-\t\tthis_result = already_found ? sha1_from_ref : sha1;\n-\t\tpathname = git_path(*p, len, str);\n-\t\tif (!read_ref(pathname, this_result)) {\n-\t\t\tif (warn_ambiguous_refs) {\n-\t\t\t\tif (already_found)\n-\t\t\t\t\tfprintf(stderr, warning, len, str);\n-\t\t\t\talready_found++;\n-\t\t\t}\n-\t\t\telse\n-\t\t\t\treturn 0;\n+\t\tthis_result = refs_found ? sha1_from_ref : sha1;\n+\t\tpathname = resolve_ref(git_path(*p, len, str), this_result, 1);\n+\t\tif (pathname) {\n+\t\t\tif (!refs_found++)\n+\t\t\t\treal_path = strdup(pathname);\n+\t\t\tif (!warn_ambiguous_refs)\n+\t\t\t\tbreak;\n \t\t}\n \t}\n-\tif (already_found)\n-\t\treturn 0;\n-\treturn -1;\n+\n+\tif (!refs_found)\n+\t\treturn -1;\n+\n+\tif (warn_ambiguous_refs && refs_found > 1)\n+\t\tfprintf(stderr, warning, len, str);\n+\n+\tif (at_time != (unsigned long)-1) {\n+\t\tread_ref_at(\n+\t\t\treal_path + strlen(git_path(\".\")) - 1,\n+\t\t\tat_time,\n+\t\t\tsha1);\n+\t}\n+\n+\tfree(real_path);\n+\treturn 0;\n }\n \n static int get_sha1_1(const char *name, int len, unsigned char *sha1);\n-- \n1.3.2.g7278\n"},{"id":"20133","messageId":"7vbqtwhpum.fsf@assigned-by-dhcp.cox.net","threadId":"4181","inReplyTo":"20060517095609.GF28529@spearce.org","subject":"Re: [RFC 5/5] Support 'master@2 hours ago' syntax","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-05-17T11:07:13Z","receivedAt":"2006-05-17T11:07:13Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Shawn Pearce <spearce@spearce.org> writes:\n\n> Extended sha1 expressions may now include date specifications\n> which indicate a point in time within the local repository's\n> history.  If the ref indicated to the left of '@' has a log in\n> $GIT_DIR/logs/<ref> then the value of the ref at the time indicated\n> by the specification is obtained from the ref's log.\n\nThis does not allow '2006-05-17 00:00:00' as the timespec, and\nthe documentation carefully avoids giving that example, but I\nthink it is better to spell that limitation out.\n\n> +* A suffix '@' followed by a date specification such as 'yesterday'\n> +  (24 hours ago) or '1 month 2 weeks 3 days 1 hour 1 second ago'\n> +  to specify the value of the ref at a prior point in time.\n> +  This suffix may only be used immediately following a ref name\n> +  and the ref must have an existing log ($GIT_DIR/logs/<ref>).\n\n\n+\tfprintf(stderr, \"warning: Log %s only goes back to %s.\\n\",\n+\t\tlogfile, show_rfc2822_date(date, tz));\n+\treturn 0;\n\nI am not sure about this part.  If the oldest log entry was 3\nhours ago, the second oldest 2 hours ago, we can tell during\nthat one hour period the ref was at that point.  If the user\nasked \"ref as of four hours ago\", and if the oldest log entry\nhad old SHA1 that is not 0{40} (because the log was not enabled\nbefore that record), it might make more sense to give that back.\n\nAlso I wonder how much complexity would we suffer and how much\nefficiency would we gain if we binary search the logdata (the\ncommitter info is variable length, so you would need to resync\nin each step).\n"},{"id":"20147","messageId":"Pine.LNX.4.64.0605170745270.10823@g5.osdl.org","threadId":"4181","inReplyTo":"7vbqtwhpum.fsf@assigned-by-dhcp.cox.net","subject":"Re: [RFC 5/5] Support 'master@2 hours ago' syntax","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-05-17T14:46:51Z","receivedAt":"2006-05-17T14:46:51Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Wed, 17 May 2006, Junio C Hamano wrote:\n> \n> This does not allow '2006-05-17 00:00:00' as the timespec, and\n> the documentation carefully avoids giving that example, but I\n> think it is better to spell that limitation out.\n\nIt doesn't? The \"approxidate()\" function should handle any reasonable date \nspecifier, and the above is certainly more than reasonable.\n\nWhy doesn't approxidate handle it?\n\n\t\tLinus\n"},{"id":"20162","messageId":"7v7j4kec3h.fsf@assigned-by-dhcp.cox.net","threadId":"4181","inReplyTo":"Pine.LNX.4.64.0605170745270.10823@g5.osdl.org","subject":"Re: [RFC 5/5] Support 'master@2 hours ago' syntax","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-05-17T18:32:34Z","receivedAt":"2006-05-17T18:32:34Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@osdl.org> writes:\n\n> On Wed, 17 May 2006, Junio C Hamano wrote:\n>> \n>> This does not allow '2006-05-17 00:00:00' as the timespec, and\n>> the documentation carefully avoids giving that example, but I\n>> think it is better to spell that limitation out.\n>\n> It doesn't? The \"approxidate()\" function should handle any reasonable date \n> specifier, and the above is certainly more than reasonable.\n>\n> Why doesn't approxidate handle it?\n\nThe way I read the code is that get_sha1() would first do its\nmagic at the first colon and feeds get_sha1_1() with prefix up\nto the first colon.  This gets passed down to get_sha1_basic()\nand what approxidate() is fed is the suffix of that prefix. It\nends up seeing stuff between '@' and ':'.  I.e.\n\n\t\"master@2006-05-17 00:00:00:cache.h\"\n\nwould ask for \"00:00:cache.h\" file in the \"master\" branch as of\ntimestamp \"2006-05-17 00\".\n"},{"id":"20187","messageId":"20060517213908.GB30313@spearce.org","threadId":"4181","inReplyTo":"7v7j4kec3h.fsf@assigned-by-dhcp.cox.net","subject":"Re: [RFC 5/5] Support 'master@2 hours ago' syntax","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-17T21:39:08Z","receivedAt":"2006-05-17T21:39:08Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <junkio@cox.net> wrote:\n> Linus Torvalds <torvalds@osdl.org> writes:\n> \n> > On Wed, 17 May 2006, Junio C Hamano wrote:\n> >> \n> >> This does not allow '2006-05-17 00:00:00' as the timespec, and\n> >> the documentation carefully avoids giving that example, but I\n> >> think it is better to spell that limitation out.\n> >\n> > It doesn't? The \"approxidate()\" function should handle any reasonable date \n> > specifier, and the above is certainly more than reasonable.\n> >\n> > Why doesn't approxidate handle it?\n> \n> The way I read the code is that get_sha1() would first do its\n> magic at the first colon and feeds get_sha1_1() with prefix up\n> to the first colon.  This gets passed down to get_sha1_basic()\n> and what approxidate() is fed is the suffix of that prefix. It\n> ends up seeing stuff between '@' and ':'.  I.e.\n> \n> \t\"master@2006-05-17 00:00:00:cache.h\"\n> \n> would ask for \"00:00:cache.h\" file in the \"master\" branch as of\n> timestamp \"2006-05-17 00\".\n\nGood catch.  I'll see if I can deal with it later; probably early\ntomorrow morning before work.  It may just come down to documenting\nthis particular case as ambiguous and sure to parse the way you\ndid not mean it to.  :-)\n\nI tested a bunch of other date formats but not the basic ISO.  Argh.\nI'll send a test case soon for the expression parsing here, to be\nsure we pull stuff from the log as expected as well as parse the\nexpression in a consistent way between releases.  :-)\n\n-- \nShawn.\n"},{"id":"20189","messageId":"20060517220613.GC30313@spearce.org","threadId":"4181","inReplyTo":"7vbqtwhpum.fsf@assigned-by-dhcp.cox.net","subject":"Re: [RFC 5/5] Support 'master@2 hours ago' syntax","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-17T22:06:13Z","receivedAt":"2006-05-17T22:06:13Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <junkio@cox.net> wrote:\n> Shawn Pearce <spearce@spearce.org> writes:\n> \n> +\tfprintf(stderr, \"warning: Log %s only goes back to %s.\\n\",\n> +\t\tlogfile, show_rfc2822_date(date, tz));\n> +\treturn 0;\n> \n> I am not sure about this part.  If the oldest log entry was 3\n> hours ago, the second oldest 2 hours ago, we can tell during\n> that one hour period the ref was at that point.  If the user\n> asked \"ref as of four hours ago\", and if the oldest log entry\n> had old SHA1 that is not 0{40} (because the log was not enabled\n> before that record), it might make more sense to give that back.\n\nIf I understand my own code here what I'm doing is walking back\nin the log file, realizing I fell off the first line of it, then\nloading the old ref from the first line.  This is the oldest ref\nI can find so I return it as a valid ref to the caller but I'm\nprinting out this warning to tell the user that the oldest point\nin time I found in the log is effectively the update date as I have\nno idea when that old sha1 first became the value of the ref.\n\nSo I think I'm doing what you are expecting here.  The log will start\nwith the value in the ref at the time the log started, not 0{40}\nand that value is what gets returned when we have this warning\ncome out..  That's the best anyone can expect...\n\n> Also I wonder how much complexity would we suffer and how much\n> efficiency would we gain if we binary search the logdata (the\n> committer info is variable length, so you would need to resync\n> in each step).\n\nI thought about doing this but did not think it would be worth the\neffort (either developer to code or CPU to execute) at this point\nin time.  I don't think users will be pulling refs from the log very\noften and if they are they will probably be pulling from recent time,\nnot very far back.  Thus starting at the end and walking back is\nprobably \"good enough\".\n\nBut if it proves to be too slow in practice I'm sure I can come up\nwith a faster way to walk through the log.  :-)\n\n-- \nShawn.\n"},{"id":"20190","messageId":"20060517223246.GD30313@spearce.org","threadId":"4181","inReplyTo":"20060517220613.GC30313@spearce.org","subject":"Re: [RFC 5/5] Support 'master@2 hours ago' syntax","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-17T22:32:46Z","receivedAt":"2006-05-17T22:32:46Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Shawn Pearce <spearce@spearce.org> wrote:\n> Junio C Hamano <junkio@cox.net> wrote:\n> >\n> > Also I wonder how much complexity would we suffer and how much\n> > efficiency would we gain if we binary search the logdata (the\n> > committer info is variable length, so you would need to resync\n> > in each step).\n> \n> I thought about doing this but did not think it would be worth the\n> effort (either developer to code or CPU to execute) at this point\n> in time.  I don't think users will be pulling refs from the log very\n> often and if they are they will probably be pulling from recent time,\n> not very far back.  Thus starting at the end and walking back is\n> probably \"good enough\".\n> \n> But if it proves to be too slow in practice I'm sure I can come up\n> with a faster way to walk through the log.  :-)\n\nI just ran a test on my PowerBook: walking a 10,000 line log file and\nextracting the very oldest commit along.  Each hit on git-rev-parse\nseems to took about 100 ms.  Hardly worth worrying about for casual\nuse.  Further git-rev-parse is taking 73 ms just to run '--verify\nHEAD' so an extra 30 ms to read the 10k log is pretty much nothing.\n\n  [spearce@pb15 trash]$ wc -l .git/logs/refs/heads/master \n     10000 .git/logs/refs/heads/master\n  [spearce@pb15 trash]$ head -n 1 .git/logs/refs/heads/master \n  b943559a305bdd6bdee2cef6e5df2413c3d30a00 0000000000000000000000000000000000000000 A U Thor <example@example.com> 1136091600 -0500\n  [spearce@pb15 trash]$ perl -e 'print scalar(localtime shift),\"\\n\"' 1136091600\n  Sun Jan  1 00:00:00 2006\n  [spearce@pb15 trash]$ time ../../git-rev-parse --verify HEAD@'300 days'\n  warning: Log .git/logs/refs/heads/master only goes back to Thu, 1 Jan 1970 00:00:00 +0000.\n  b943559a305bdd6bdee2cef6e5df2413c3d30a00\n  \n  real    0m0.112s\n  user    0m0.029s\n  sys     0m0.023s\n  [spearce@pb15 trash]$ time ../../git-rev-parse --verify HEAD@'300 days'\n  warning: Log .git/logs/refs/heads/master only goes back to Thu, 1 Jan 1970 00:00:00 +0000.\n  b943559a305bdd6bdee2cef6e5df2413c3d30a00\n  \n  real    0m0.105s\n  user    0m0.029s\n  sys     0m0.023s\n\n-- \nShawn.\n"}]}