{"thread":{"id":"4124","subject":"Tracking branch history","startedAt":"2006-05-12T23:18:37Z","lastAt":"2006-05-15T09:53:07Z","messageCount":21,"participants":["Daniel Barkalow","Linus Torvalds","Shawn Pearce","Junio C Hamano","Elrond"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"19866","messageId":"Pine.LNX.4.64.0605121838490.6713@iabervon.org","threadId":"4124","inReplyTo":null,"subject":"Tracking branch history","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2006-05-12T23:18:37Z","receivedAt":"2006-05-12T23:18:37Z","isPatch":false,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"One feature that might make git more intuitive to people is if we were to \nadditionally track the history of what commit was the head of each branch \nover time. This is only vaguely related to the history of the content, but \nit's well-defined and sometimes significant.\n\nE.g., if you know that two weeks ago, what you had worked, but it doesn't \nwork now, you can use git-bisect to figure out what happened, but first \nyou have to figure out what commit it was that you were using two weeks \nago. Two weeks ago, we had that information, but we didn't keep it.\n\nIt would probably also be useful for showing changelogs in a \nlocally-useful order. If you merge in a tree that's been in separate \ndevelopment for a long time, the commits in that tree will be interleaved \nin commit date with the commits you did locally. You tend to want to \nattribute all of the changes that happened in the merge to the time of the \nmerge, but that commit object isn't going to tell you anything useful, \nbecause it may have been done by the other tree (and you fast-forwarded to \nthe merge). In fact, you may want to attribute the changes to the \nfast-forward, which can't recorded in the content history, because nothing \nhappened to the content. On the other hand, if we were to also record the \nbranch history, we could give output which shows changes in the order they \nreached the local tree (then ordered by the commit tree), just by having \nit do:\n\n<time now>\n\ngit log <head-as-of-before>..<head-of-of-now>\n\n<time before>\n\ngit log <head-as-of-before-that>..<head-as-of-before>\n\n<time before-that>\n\nand so forth.\n\n\t-Daniel\n*This .sig left intentionally blank*\n"},{"id":"19867","messageId":"Pine.LNX.4.64.0605121640210.3866@g5.osdl.org","threadId":"4124","inReplyTo":"Pine.LNX.4.64.0605121838490.6713@iabervon.org","subject":"Re: Tracking branch history","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-05-12T23:45:42Z","receivedAt":"2006-05-12T23:45:42Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Fri, 12 May 2006, Daniel Barkalow wrote:\n>\n> One feature that might make git more intuitive to people is if we were to \n> additionally track the history of what commit was the head of each branch \n> over time. This is only vaguely related to the history of the content, but \n> it's well-defined and sometimes significant.\n> \n> E.g., if you know that two weeks ago, what you had worked, but it doesn't \n> work now, you can use git-bisect to figure out what happened, but first \n> you have to figure out what commit it was that you were using two weeks \n> ago. Two weeks ago, we had that information, but we didn't keep it.\n\nNote that this is possible, but it must be done literally as a separate \nhistory from the commit history. \n\nIOW, a good (?) way to do it is to literally have a commit hook that \nbasically just does\n\n\techo $new >> .git/$branch-commit-history\n\npossibly together with a datestamp thing (ie it could be something like\n\"echo $new \"$USER\" $(date)\" rather than just the commit SHA1).\n\nMake sure that not just \"git commit\", but anything else that changes the \nbranch (notably, \"git fetch\" and a fast-forward merge as a result of an \nexplicit merge or a \"git pull\") would also do this same thing.\n\nBut realize that this is really purely a per-repository logging thing, and \nnot really bound to the actual git history any way.\n\n\t\t\tLinus\n"},{"id":"19868","messageId":"Pine.LNX.4.64.0605121656350.3866@g5.osdl.org","threadId":"4124","inReplyTo":"Pine.LNX.4.64.0605121640210.3866@g5.osdl.org","subject":"Re: Tracking branch history","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-05-13T00:03:53Z","receivedAt":"2006-05-13T00:03:53Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Fri, 12 May 2006, Linus Torvalds wrote:\n> \n> IOW, a good (?) way to do it is to literally have a commit hook that \n> basically just does\n> \n> \techo $new >> .git/$branch-commit-history\n> \n> possibly together with a datestamp thing (ie it could be something like\n> \"echo $new \"$USER\" $(date)\" rather than just the commit SHA1).\n\nBtw, the real problem with this is how to use it.\n\nThe only really valid use I see is to use it for date-based things, ie if \ngiven a date, look up the most recent commit ID that is older than the \ndate in question. No other op seems to really make sense, but that one \ndoes.\n\nNow, the one other operation that is semantically sensible is to use the \nlist of commits to figure out a \"path\" through the commit space. However, \nthat path won't actually even be well-defined (a fast-forward pull/merge \ncan and often /will/ update the history in a way where it's impossible to \nselect one particular path to the previous commit listed in the commit \nlog).\n\nThe other thing that makes the \"path\" thing hard is that it's just \nfundamentally a pretty hard thing to calculate, even when it would result \nin one unambiguous path. I _believe_ that it comes close to what \"git \nbisect\" does, and that the bisect algorithm could probably be used to \nalways create _a_ path between each commit (is just pick successive \nhalf-way-points - the commit list _should_ always have a direct dominance \nrelationship, but the bisection algorithm should do something half-way \nsane even if you \"jump about\" by \"git reset\" or something).\n\nIt might be interesting to see if it's somethign that can be done \nreasonably efficiently.\n\n\t\t\tLinus\n"},{"id":"19869","messageId":"20060513034051.GA21586@spearce.org","threadId":"4124","inReplyTo":"Pine.LNX.4.64.0605121640210.3866@g5.osdl.org","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-13T03:40:51Z","receivedAt":"2006-05-13T03:40:51Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Linus Torvalds <torvalds@osdl.org> wrote:\n> \n> On Fri, 12 May 2006, Daniel Barkalow wrote:\n> >\n> > One feature that might make git more intuitive to people is if we were to \n> > additionally track the history of what commit was the head of each branch \n> > over time. This is only vaguely related to the history of the content, but \n> > it's well-defined and sometimes significant.\n> > \n> > E.g., if you know that two weeks ago, what you had worked, but it doesn't \n> > work now, you can use git-bisect to figure out what happened, but first \n> > you have to figure out what commit it was that you were using two weeks \n> > ago. Two weeks ago, we had that information, but we didn't keep it.\n> \n> Note that this is possible, but it must be done literally as a separate \n> history from the commit history. \n> \n> IOW, a good (?) way to do it is to literally have a commit hook that \n> basically just does\n> \n> \techo $new >> .git/$branch-commit-history\n> \n> possibly together with a datestamp thing (ie it could be something like\n> \"echo $new \"$USER\" $(date)\" rather than just the commit SHA1).\n\nWhy not intergrate this into git-update-ref.  Almost every tool which\ndeals with a GIT repository (aside from my pure-Java Eclipse plugin\nwhich is still a major work-in-process) performs ref changes through\ngit-udpate-ref.  So just have it append the ref's history to a file:\n\n\t.git/log/refs/heads/$branch\n\nwhere the history records are stored as:\n\n\t40 byte commit-ish SHA1\n\t<SP>\n\t<committer>\n\t<LF>\n\ne.g.:\n\n\tcbb6d91d95e523c2b6a6b52577c4be28d18ace83 Shawn O. Pearce <spearce@spearce.org> 1137039378 -0500\n\tae8c74e96a1e02bbfb7f1a9669b77d6f8ee6c3cf Shawn O. Pearce <spearce@spearce.org> 1136921470 -0500\n\nOf course a major issue here is locking the log file during the ref\nupdate, but it looks like it might just be safe to append the entry\nto the log file right after the re_verify and before the rename.\n\nI wouldn't have git-update-ref create the log file. I'd would only\nhave it append if the log already exists.\n\nHmm, this actually looks like it would be really easy.  Maybe I'll\nhack up an RFC patch this evening after dinner.\n\n-- \nShawn.\n"},{"id":"19870","messageId":"Pine.LNX.4.64.0605122358490.6713@iabervon.org","threadId":"4124","inReplyTo":"Pine.LNX.4.64.0605121656350.3866@g5.osdl.org","subject":"Re: Tracking branch history","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2006-05-13T04:27:00Z","receivedAt":"2006-05-13T04:27:00Z","isPatch":false,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"On Fri, 12 May 2006, Linus Torvalds wrote:\n\n> Btw, the real problem with this is how to use it.\n> \n> The only really valid use I see is to use it for date-based things, ie if \n> given a date, look up the most recent commit ID that is older than the \n> date in question. No other op seems to really make sense, but that one \n> does.\n> \n> Now, the one other operation that is semantically sensible is to use the \n> list of commits to figure out a \"path\" through the commit space. However, \n> that path won't actually even be well-defined (a fast-forward pull/merge \n> can and often /will/ update the history in a way where it's impossible to \n> select one particular path to the previous commit listed in the commit \n> log).\n\nI think that jumping around with reset is necessary to make this actually \ncomplicated; a fast-forward only happens if the new value descends from \nthe old value, and a merge obviously descends from the old value. Sure, \nthe non-linear history added by a merge will still be non-linear, but \nfrom the local user's point of view, it was all added in bulk by the \nmerge.\n\nI think the program creating the history should note the tricky cases, \nwhere the new value doesn't descend from the old value, which should be \neasy to identify. I'm not sure what should actually be done to report a \nreset in a changelog, either. The section of the log just before the reset \nis clearly a false start of some sort, and you probably want to do \nsomething special to list the commits which don't actually lead to the \ncurrent state, but you probably want to report them, in case the reason \nyou'd looking through this is that there was some benefit to a version \nthat you ended up discarding, and you want to revisit that attempt.\n\nI think in the always-forward case, there's a useful optimization to be \nhad by having the rev-list-equivalent actually binning commits by the \nearliest points that descend from them, so you don't trace the local \nbranch back to where other branches forked off over and over. But it seems \nto me otherwise pretty simple.\n\n\t-Daniel\n*This .sig left intentionally blank*\n"},{"id":"19871","messageId":"Pine.LNX.4.64.0605122136100.3866@g5.osdl.org","threadId":"4124","inReplyTo":"20060513034051.GA21586@spearce.org","subject":"Re: Tracking branch history","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-05-13T04:38:12Z","receivedAt":"2006-05-13T04:38:12Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Fri, 12 May 2006, Shawn Pearce wrote:\n> \n> Why not intergrate this into git-update-ref.  Almost every tool which\n> deals with a GIT repository (aside from my pure-Java Eclipse plugin\n> which is still a major work-in-process) performs ref changes through\n> git-udpate-ref.  So just have it append the ref's history to a file:\n> \n> \t.git/log/refs/heads/$branch\n> \n> where the history records are stored as:\n> \n> \t40 byte commit-ish SHA1\n> \t<SP>\n> \t<committer>\n> \t<LF>\n\nSure. Except it's not really \"committer\", in the ordinary sense (there's \nno \"commit\" for a fast-forward). But yes, re-using that format (with date \nand all) makes sense.\n\n> Of course a major issue here is locking the log file during the ref\n> update, but it looks like it might just be safe to append the entry\n> to the log file right after the re_verify and before the rename.\n\nI'd suggest just opening it with O_APPEND, and doing the update with a \nsingle write() system call. Let the OS do the locking for you. \n\n\t\tLinus\n"},{"id":"19872","messageId":"7vody2v7yr.fsf@assigned-by-dhcp.cox.net","threadId":"4124","inReplyTo":"20060513034051.GA21586@spearce.org","subject":"Re: Tracking branch history","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-05-13T04:56:12Z","receivedAt":"2006-05-13T04:56:12Z","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> git-udpate-ref.  So just have it append the ref's history to a file:\n>\n> \t.git/log/refs/heads/$branch\n>\n> where the history records are stored as:\n>\n> \t40 byte commit-ish SHA1\n> \t<SP>\n> \t<committer>\n> \t<LF>\n>\n> e.g.:\n>\n> \tcbb6d91d95e523c2b6a6b52577c4be28d18ace83 Shawn O. Pearce <spearce@spearce.org> 1137039378 -0500\n> \tae8c74e96a1e02bbfb7f1a9669b77d6f8ee6c3cf Shawn O. Pearce <spearce@spearce.org> 1136921470 -0500\n>\n\nBecause the question we often would want to ask is \"two days ago\nmy tip worked but today it does not\", recording the timestamp\nmakes sense, but I do not know what the point is for the name\nand e-mail.  If it is in your local repository (i.e. the program\nthat updates the tip ref is not receive-pack which is invoked by\nyour pushing into a remote repo), it will always be you.  And in\nthe receive-pack case, the information is not available to begin\nwith (you may have a UNIX UID but that is about it).\n"},{"id":"19874","messageId":"20060513071753.GA21998@spearce.org","threadId":"4124","inReplyTo":"7vody2v7yr.fsf@assigned-by-dhcp.cox.net","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-13T07:17:53Z","receivedAt":"2006-05-13T07:17:53Z","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> > git-udpate-ref.  So just have it append the ref's history to a file:\n> >\n> > \t.git/log/refs/heads/$branch\n> >\n> > where the history records are stored as:\n> >\n> > \t40 byte commit-ish SHA1\n> > \t<SP>\n> > \t<committer>\n> > \t<LF>\n> >\n> > e.g.:\n> >\n> > \tcbb6d91d95e523c2b6a6b52577c4be28d18ace83 Shawn O. Pearce <spearce@spearce.org> 1137039378 -0500\n> > \tae8c74e96a1e02bbfb7f1a9669b77d6f8ee6c3cf Shawn O. Pearce <spearce@spearce.org> 1136921470 -0500\n> >\n> \n> Because the question we often would want to ask is \"two days ago\n> my tip worked but today it does not\", recording the timestamp\n> makes sense, but I do not know what the point is for the name\n> and e-mail.  If it is in your local repository (i.e. the program\n> that updates the tip ref is not receive-pack which is invoked by\n> your pushing into a remote repo), it will always be you.  And in\n> the receive-pack case, the information is not available to begin\n> with (you may have a UNIX UID but that is about it).\n\nAgreed.  Prototype patch below.\n\nWhile writing this I discovered at least two chunks of GIT which\ndon't use git-update-ref: fetch.c and upload-pack.c.  fetch.c uses\nthe APIs in refs.c but upload-pack.c doesn't.  I spent a couple of\nhours trying to convert update-ref.c to use the APIs in refs.c so\nI could just put the logging change there, but that turned out to\nbe more difficult than expected for a simple prototype so it all\nwent out the window.\n\n-- >-\nLog ref updates to logs/refs/<refname>\n\nIf .git/logs/refs/<refname> exists then append a line to it whenever\ngit-update-ref <refname> is executed.  Each log line contains the\nfollowing information:\n\n  40 byte tree-ish SHA1\n  <SP>\n  date/time\n  <LF>\n\nwhere date/time is the current date, time and timezone in the\nstandard GIT date format.  If the caller is unable to append to\nthe log file and the log file exists then git-update-ref will fail\nwithout updating <refname>.\n\n---\n\n Documentation/git-update-ref.txt |   15 ++++++++++++++\n update-ref.c                     |   41 +++++++++++++++++++++++++++++++++++---\n 2 files changed, 53 insertions(+), 3 deletions(-)\n\n8f1ccd3b0eda9d54eca37af178113c91174e81ca\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 475237f..d314786 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -49,6 +49,21 @@ for reading but not for writing (so we'l\n ref symlink to some other tree, if you have copied a whole\n archive by creating a symlink tree).\n \n+Logging Updates\n+---------------\n+If \"$GIT_DIR/logs/<ref>\" (possibly dereferencing symbolic refs)\n+exists then `git-update-ref` will append a line to the log file\n+describing the change in ref value.  Log lines are formatted as:\n+\n+    . sha1 SP date LF\n++\n+Where \"sha1\" is the 40 character hexadecimal value of <newvalue>\n+and \"date\" is the current date/time and timezone in the standard\n+GIT date format.\n+\n+An update will fail (without changing <ref>) if the log file\n+exists but the current user is unable to append to the file.\n+\n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>.\ndiff --git a/update-ref.c b/update-ref.c\nindex fd48742..bffe5f9 100644\n--- a/update-ref.c\n+++ b/update-ref.c\n@@ -20,9 +20,9 @@ int main(int argc, char **argv)\n {\n \tchar *hex;\n \tconst char *refname, *value, *oldval, *path;\n-\tchar *lockpath;\n+\tchar *lockpath, *logpath;\n \tunsigned char sha1[20], oldsha1[20], currsha1[20];\n-\tint fd, written;\n+\tint fd, logfd, written, pfxlen;\n \n \tsetup_git_directory();\n \tgit_config(git_default_config);\n@@ -38,7 +38,9 @@ int main(int argc, char **argv)\n \tif (oldval && get_sha1(oldval, oldsha1))\n \t\tdie(\"%s: not a valid old SHA1\", oldval);\n \n-\tpath = resolve_ref(git_path(\"%s\", refname), currsha1, !!oldval);\n+\tpath = git_path(\"%s\", refname);\n+\tpfxlen = strlen(path) - strlen(refname);\n+\tpath = resolve_ref(path, currsha1, !!oldval);\n \tif (!path)\n \t\tdie(\"No such ref: %s\", refname);\n \n@@ -50,6 +52,17 @@ int main(int argc, char **argv)\n \t\t\texit(0);\n \t}\n \tpath = strdup(path);\n+\n+\t/*\n+\t * If logging is required make sure we can append to the log.\n+\t */\n+\tlogpath = strdup(git_path(\"logs/%s\", path + pfxlen));\n+\tlogfd = open(logpath, O_APPEND | O_WRONLY, 0);\n+\tif (logfd < 0 && errno != ENOENT)\n+\t\tdie(\"Unable to append to log %s\", logpath);\n+\tif (logfd >= 0)\n+\t\tsetup_ident();\n+\n \tlockpath = mkpath(\"%s.lock\", path);\n \tif (safe_create_leading_directories(lockpath) < 0)\n \t\tdie(\"Unable to create all of %s\", lockpath);\n@@ -75,6 +88,28 @@ int main(int argc, char **argv)\n \t}\n \n \t/*\n+\t * Write to the log, if it was opened.\n+\t */\n+\tif (logfd >= 0) {\n+\t\tchar now[50];\n+\t\tchar logrec[100];\n+\t\tunsigned len;\n+\n+\t\tdatestamp(now, sizeof(now));\n+\t\tlen = snprintf(logrec, sizeof(logrec), \"%s %s\\n\", sha1_to_hex(sha1), now);\n+\t\tif (len >= sizeof(logrec)) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Internal error formatting log record.\");\n+\t\t}\n+\t\twritten = write(logfd, logrec, len);\n+\t\tif (written != len) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Unable to append to %s\", logpath);\n+\t\t}\n+\t\tclose(logfd);\n+\t}\n+\n+\t/*\n \t * Finally, replace the old ref with the new one\n \t */\n \tif (rename(lockpath, path) < 0) {\n-- \n1.3.2.g7278\n"},{"id":"19875","messageId":"20060513074328.GB21998@spearce.org","threadId":"4124","inReplyTo":"20060513071753.GA21998@spearce.org","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-13T07:43:28Z","receivedAt":"2006-05-13T07:43:28Z","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> > Shawn Pearce <spearce@spearce.org> writes:\n> > \n> > > git-udpate-ref.  So just have it append the ref's history to a file:\n> > >\n> > > \t.git/log/refs/heads/$branch\n> > >\n> > > where the history records are stored as:\n> > >\n> > > \t40 byte commit-ish SHA1\n> > > \t<SP>\n> > > \t<committer>\n> > > \t<LF>\n> > >\n> > > e.g.:\n> > >\n> > > \tcbb6d91d95e523c2b6a6b52577c4be28d18ace83 Shawn O. Pearce <spearce@spearce.org> 1137039378 -0500\n> > > \tae8c74e96a1e02bbfb7f1a9669b77d6f8ee6c3cf Shawn O. Pearce <spearce@spearce.org> 1136921470 -0500\n> > >\n> > \n> > Because the question we often would want to ask is \"two days ago\n> > my tip worked but today it does not\", recording the timestamp\n> > makes sense, but I do not know what the point is for the name\n> > and e-mail.  If it is in your local repository (i.e. the program\n> > that updates the tip ref is not receive-pack which is invoked by\n> > your pushing into a remote repo), it will always be you.  And in\n> > the receive-pack case, the information is not available to begin\n> > with (you may have a UNIX UID but that is about it).\n\nForget my last patch.  This one automatically creates the log file\nby looking for a config value of 'core.logRefUpdates=true'.\n\n--> -\nLog ref updates to logs/refs/<ref>\n\nIf config parameter core.logRefUpdates is true then append a line\nto .git/logs/refs/<ref> whenever git-update-ref <ref> is executed.\nEach log line contains the following information:\n\n  40 byte tree-ish SHA1\n  <SP>\n  date/time\n  <LF>\n\nwhere date/time is the current date, time and timezone in the\nstandard GIT date format.  If the caller is unable to append to\nthe log file then git-update-ref will fail without updating <ref>.\n\n---\n\n Documentation/config.txt         |    7 ++++++\n Documentation/git-update-ref.txt |   17 +++++++++++++++\n cache.h                          |    1 +\n config.c                         |    5 ++++\n environment.c                    |    1 +\n update-ref.c                     |   43 +++++++++++++++++++++++++++++++++++---\n 6 files changed, 71 insertions(+), 3 deletions(-)\n\ncac86f2df9a52d94cb03038e267934c67f04122b\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex d1a4bec..f06695c 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -70,6 +70,13 @@ core.preferSymlinkRefs::\n \tThis is sometimes needed to work with old scripts that\n \texpect HEAD to be a symbolic link.\n \n+core.logRefUpdates::\n+\tIf true, `git-update-ref` will append a line to\n+\t\"$GIT_DIR/logs/<ref>\" listing the new SHA1 and the date/time\n+\tof the update.\tThis information can be used to determine\n+\twhat commit was the tip of a branch \"2 days ago\".  This value\n+\tis false by default (no logging).\n+\n core.repositoryFormatVersion::\n \tInternal variable identifying the repository format and layout\n \tversion.\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 475237f..8c46263 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -49,6 +49,23 @@ for reading but not for writing (so we'l\n ref symlink to some other tree, if you have copied a whole\n archive by creating a symlink tree).\n \n+Logging Updates\n+---------------\n+If config parameter \"core.logRefUpdates\" is true then\n+`git-update-ref` will append a line to the log file\n+\"$GIT_DIR/logs/<ref>\" (dereferencing all symbolic refs before\n+creating the log name) describing the change in ref value.  Log lines\n+are formatted as:\n+\n+    . sha1 SP date LF\n++\n+Where \"sha1\" is the 40 character hexadecimal value of <newvalue>\n+and \"date\" is the current date/time and timezone in the standard\n+GIT date format.\n+\n+An update will fail (without changing <ref>) if the current user is\n+unable to create a new log file or append to the existing log file.\n+\n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>.\ndiff --git a/cache.h b/cache.h\nindex b1300cd..887ce20 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -171,6 +171,7 @@ extern void rollback_index_file(struct c\n extern int trust_executable_bit;\n extern int assume_unchanged;\n extern int prefer_symlink_refs;\n+extern int log_ref_updates;\n extern int warn_ambiguous_refs;\n extern int diff_rename_limit_default;\n extern int shared_repository;\ndiff --git a/config.c b/config.c\nindex 0f518c9..f8a814e 100644\n--- a/config.c\n+++ b/config.c\n@@ -232,6 +232,11 @@ int git_default_config(const char *var, \n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"core.logrefupdates\")) {\n+\t\tlog_ref_updates = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"core.warnambiguousrefs\")) {\n \t\twarn_ambiguous_refs = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/environment.c b/environment.c\nindex 444c99e..437266e 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];\n int trust_executable_bit = 1;\n int assume_unchanged = 0;\n int prefer_symlink_refs = 0;\n+int log_ref_updates = 0;\n int warn_ambiguous_refs = 1;\n int repository_format_version = 0;\n char git_commit_encoding[MAX_ENCODING_LENGTH] = \"utf-8\";\ndiff --git a/update-ref.c b/update-ref.c\nindex fd48742..c231760 100644\n--- a/update-ref.c\n+++ b/update-ref.c\n@@ -22,7 +22,7 @@ int main(int argc, char **argv)\n \tconst char *refname, *value, *oldval, *path;\n \tchar *lockpath;\n \tunsigned char sha1[20], oldsha1[20], currsha1[20];\n-\tint fd, written;\n+\tint fd, written, pfxlen;\n \n \tsetup_git_directory();\n \tgit_config(git_default_config);\n@@ -38,7 +38,9 @@ int main(int argc, char **argv)\n \tif (oldval && get_sha1(oldval, oldsha1))\n \t\tdie(\"%s: not a valid old SHA1\", oldval);\n \n-\tpath = resolve_ref(git_path(\"%s\", refname), currsha1, !!oldval);\n+\tpath = git_path(\"%s\", refname);\n+\tpfxlen = strlen(path) - strlen(refname);\n+\tpath = resolve_ref(path, currsha1, !!oldval);\n \tif (!path)\n \t\tdie(\"No such ref: %s\", refname);\n \n@@ -50,7 +52,7 @@ int main(int argc, char **argv)\n \t\t\texit(0);\n \t}\n \tpath = strdup(path);\n-\tlockpath = mkpath(\"%s.lock\", path);\n+\tlockpath = strdup(mkpath(\"%s.lock\", path));\n \tif (safe_create_leading_directories(lockpath) < 0)\n \t\tdie(\"Unable to create all of %s\", lockpath);\n \n@@ -75,6 +77,41 @@ int main(int argc, char **argv)\n \t}\n \n \t/*\n+\t * Write to the log if logging of ref updates is enabled\n+\t */\n+\tif (log_ref_updates) {\n+\t\tchar *logpath;\n+\t\tchar now[50];\n+\t\tchar logrec[100];\n+\t\tunsigned len;\n+\t\tint logfd;\n+\n+\t\tdatestamp(now, sizeof(now));\n+\t\tlen = snprintf(logrec, sizeof(logrec), \"%s %s\\n\", sha1_to_hex(sha1), now);\n+\t\tif (len >= sizeof(logrec)) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Internal error formatting log record.\");\n+\t\t}\n+\n+\t\tlogpath = git_path(\"logs/%s\", path + pfxlen);\n+\t\tif (safe_create_leading_directories(logpath) < 0) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Unable to create all of %s\", logpath);\n+\t\t}\n+\t\tlogfd = open(logpath, O_CREAT | O_APPEND | O_WRONLY, 0666);\n+\t\tif (logfd < 0) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Unable to append to log %s\", logpath);\n+\t\t}\n+\t\twritten = write(logfd, logrec, len);\n+\t\tif (written != len) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Unable to append to %s\", logpath);\n+\t\t}\n+\t\tclose(logfd);\n+\t}\n+\n+\t/*\n \t * Finally, replace the old ref with the new one\n \t */\n \tif (rename(lockpath, path) < 0) {\n-- \n1.3.2.g7278\n"},{"id":"19881","messageId":"loom.20060513T140528-554@post.gmane.org","threadId":"4124","inReplyTo":"Pine.LNX.4.64.0605121838490.6713@iabervon.org","subject":"Re: Tracking branch history","fromName":"Elrond","fromEmail":"elrond+kernel.org@samba-tng.org","sentAt":"2006-05-13T12:53:14Z","receivedAt":"2006-05-13T12:53:14Z","isPatch":false,"sender":{"key":"elrond+kernel.org@samba-tng.org","avatar":null},"body":"Daniel Barkalow <barkalow <at> iabervon.org> writes:\n\n> \n> One feature that might make git more intuitive to people is if we were to \n> additionally track the history of what commit was the head of each branch \n> over time. This is only vaguely related to the history of the content, but \n> it's well-defined and sometimes significant.\n> \n> E.g., if you know that two weeks ago, what you had worked, but it doesn't \n> work now, you can use git-bisect to figure out what happened, but first \n> you have to figure out what commit it was that you were using two weeks \n> ago. Two weeks ago, we had that information, but we didn't keep it.\n\nOn a related issue:\n\nLooking at a commit:\n   commit id-commit\n   parent id-1\n   parent id-2\n   parent id-3\n\n       Merge branch 'branch-2', 'branch-3'\n\nOne can tell the name of the branches for id-2 and id-3 (branch-2, 3),\nbut one can't tell the name of id-1.\n\nAt the time, those branches were not yet merged, this information was\navailable easily, even remotely via git-clone.\n"},{"id":"19882","messageId":"Pine.LNX.4.64.0605130809580.3866@g5.osdl.org","threadId":"4124","inReplyTo":"20060513071753.GA21998@spearce.org","subject":"Re: Tracking branch history","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-05-13T15:11:19Z","receivedAt":"2006-05-13T15:11:19Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Sat, 13 May 2006, Shawn Pearce wrote:\n>\n> +\n> +\t/*\n> +\t * If logging is required make sure we can append to the log.\n> +\t */\n> +\tlogpath = strdup(git_path(\"logs/%s\", path + pfxlen));\n\nI don't think you need the strdup().\n\nI also think you might as well just let the logging silently fail, but \nhey, that's up to you.\n\n\t\tLinus\n"},{"id":"19884","messageId":"Pine.LNX.4.64.0605130815550.3866@g5.osdl.org","threadId":"4124","inReplyTo":"20060513074328.GB21998@spearce.org","subject":"Re: Tracking branch history","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-05-13T15:20:05Z","receivedAt":"2006-05-13T15:20:05Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Sat, 13 May 2006, Shawn Pearce wrote:\n> \n> Forget my last patch.  This one automatically creates the log file\n> by looking for a config value of 'core.logRefUpdates=true'.\n\nLooks better.\n\nI actually disagree with Junio - I think the logging the name made sense. \nIf you have a shared repo with multiple people pushing to the same branch, \nit's interesting to see who does the pushing.\n\nI also think it might be good to save the oldsha1 value. Yes, it _should_ \nalways be the previous sha1 logged, but it's interesting to see in case it \nisn't (ie the ref was updated some other way), and it's also interesting \nfor the first entry after logging has been enabled.\n\nHmm?\n\n\t\tLinue\n"},{"id":"19885","messageId":"7vbqu1vps3.fsf@assigned-by-dhcp.cox.net","threadId":"4124","inReplyTo":"Pine.LNX.4.64.0605130815550.3866@g5.osdl.org","subject":"Re: Tracking branch history","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-05-13T16:43:40Z","receivedAt":"2006-05-13T16:43:40Z","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> I actually disagree with Junio - I think the logging the name made sense. \n> If you have a shared repo with multiple people pushing to the same branch, \n> it's interesting to see who does the pushing.\n\nYes, I agree recording that information is interesting.  \n\nMy comment was about how it is recorded.  In the local case\nGIT_COMMITTER_IDENT is what you want as Shawn did in his\noriginal proposal, but remote \"pushing into shared repository\"\ncase I do not think it is available in general.\n\nWell, at least that was what I was thinking when I made that\ncomment.  However,\n\nIf the pushers arrange to have appropriate environment variables\nwhile receive-pack does its work (.git/config in the target\nrepository is not an appropriate place to get user.* settings\nfrom in a shared setting), what you say makes perfect sense.\n\nHaving the usual enviornment available would be handy to make\nthe hooks on the receiving end to do useful things anyway, so I\nretract that suggestion.\n\n> I also think it might be good to save the oldsha1 value. Yes, it _should_ \n> always be the previous sha1 logged, but it's interesting to see in case it \n> isn't (ie the ref was updated some other way), and it's also interesting \n> for the first entry after logging has been enabled.\n>\n> \t\tLinue\n\nYes, and also what user-level command was used to cause update\nthe ref; was it a merge from remote, own commit, rewind/rebase?\n\n\tJunia\n\n;-)\n"},{"id":"19888","messageId":"20060513181816.GA12475@spearce.org","threadId":"4124","inReplyTo":"7vbqu1vps3.fsf@assigned-by-dhcp.cox.net","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-13T18:18:16Z","receivedAt":"2006-05-13T18:18:16Z","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> > I actually disagree with Junio - I think the logging the name made sense. \n> > If you have a shared repo with multiple people pushing to the same branch, \n> > it's interesting to see who does the pushing.\n> \n> Yes, I agree recording that information is interesting.  \n> \n> My comment was about how it is recorded.  In the local case\n> GIT_COMMITTER_IDENT is what you want as Shawn did in his\n> original proposal, but remote \"pushing into shared repository\"\n> case I do not think it is available in general.\n> \n> Well, at least that was what I was thinking when I made that\n> comment.  However,\n> \n> If the pushers arrange to have appropriate environment variables\n> while receive-pack does its work (.git/config in the target\n> repository is not an appropriate place to get user.* settings\n> from in a shared setting), what you say makes perfect sense.\n> \n> Having the usual enviornment available would be handy to make\n> the hooks on the receiving end to do useful things anyway, so I\n> retract that suggestion.\n> \n> > I also think it might be good to save the oldsha1 value. Yes, it _should_ \n> > always be the previous sha1 logged, but it's interesting to see in case it \n> > isn't (ie the ref was updated some other way), and it's also interesting \n> > for the first entry after logging has been enabled.\n> >\n> > \t\tLinue\n> \n> Yes, and also what user-level command was used to cause update\n> the ref; was it a merge from remote, own commit, rewind/rebase?\n> \n> \tJunia\n> \n> ;-)\n> \n\nNew version of the patch below; this patch superceeds the prior two.\nThe format now includes the old sha1 and committer ident, and does\nnot write a log record if the update-ref is pointless.\n\nHmm, why does update-ref do a pointless update?\n\nNow the problem with this patch is it doesn't append to the log\nin the case of upload-pack or fetch; nor does it record what user\nlevel command caused the update.\n\n  [spearce@pb15 git]$ grep update-ref *.sh *.perl *.py | wc -l\n      20\n\nHmm.  That's easy enough to fix.  Anyone up for a '-m foo' comment\nswitch to update-ref from the higher-level tools such that foo\nappears as part of the log line?\n\nI'll be offline for a few hours but can work on this some more\nlater tonight.\n\n--> -\nLog ref updates to logs/refs/<ref>\n\nIf config parameter core.logRefUpdates is true then append a line\nto .git/logs/refs/<ref> whenever git-update-ref <ref> is executed.\nEach log line contains the following information:\n\n  oldsha1 <SP> newsha1 <SP> committer <LF>\n\nwhere committer is the current user, date, time and timezone in\nthe standard GIT ident format.  If the caller is unable to append\nto the log file then git-update-ref will fail without updating <ref>.\n\n---\n\n Documentation/config.txt         |    7 ++++++\n Documentation/git-update-ref.txt |   19 +++++++++++++++\n cache.h                          |    1 +\n config.c                         |    5 ++++\n environment.c                    |    1 +\n update-ref.c                     |   48 ++++++++++++++++++++++++++++++++++++--\n 6 files changed, 78 insertions(+), 3 deletions(-)\n\n898e33d9c2def73296697d65dff0676d96adb5d8\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex d1a4bec..f06695c 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -70,6 +70,13 @@ core.preferSymlinkRefs::\n \tThis is sometimes needed to work with old scripts that\n \texpect HEAD to be a symbolic link.\n \n+core.logRefUpdates::\n+\tIf true, `git-update-ref` will append a line to\n+\t\"$GIT_DIR/logs/<ref>\" listing the new SHA1 and the date/time\n+\tof the update.\tThis information can be used to determine\n+\twhat commit was the tip of a branch \"2 days ago\".  This value\n+\tis false by default (no logging).\n+\n core.repositoryFormatVersion::\n \tInternal variable identifying the repository format and layout\n \tversion.\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 475237f..edd39d3 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -49,6 +49,25 @@ for reading but not for writing (so we'l\n ref symlink to some other tree, if you have copied a whole\n archive by creating a symlink tree).\n \n+Logging Updates\n+---------------\n+If config parameter \"core.logRefUpdates\" is true then\n+`git-update-ref` will append a line to the log file\n+\"$GIT_DIR/logs/<ref>\" (dereferencing all symbolic refs before\n+creating the log name) describing the change in ref value.  Log lines\n+are formatted as:\n+\n+    . oldsha1 SP newsha1 SP committer LF\n++\n+Where \"oldsha1\" is the 40 character hexadecimal value previously\n+stored in <ref>, \"newsha1\" is the 40 character hexadecimal value of\n+<newvalue> and \"committer\" is the committer's name, email address\n+and date in the standard GIT committer ident format.\n+\n+An update will fail (without changing <ref>) if the current user is\n+unable to create a new log file, append to the existing log file\n+or does not have committer information available.\n+\n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>.\ndiff --git a/cache.h b/cache.h\nindex b1300cd..887ce20 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -171,6 +171,7 @@ extern void rollback_index_file(struct c\n extern int trust_executable_bit;\n extern int assume_unchanged;\n extern int prefer_symlink_refs;\n+extern int log_ref_updates;\n extern int warn_ambiguous_refs;\n extern int diff_rename_limit_default;\n extern int shared_repository;\ndiff --git a/config.c b/config.c\nindex 0f518c9..f8a814e 100644\n--- a/config.c\n+++ b/config.c\n@@ -232,6 +232,11 @@ int git_default_config(const char *var, \n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"core.logrefupdates\")) {\n+\t\tlog_ref_updates = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"core.warnambiguousrefs\")) {\n \t\twarn_ambiguous_refs = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/environment.c b/environment.c\nindex 444c99e..437266e 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];\n int trust_executable_bit = 1;\n int assume_unchanged = 0;\n int prefer_symlink_refs = 0;\n+int log_ref_updates = 0;\n int warn_ambiguous_refs = 1;\n int repository_format_version = 0;\n char git_commit_encoding[MAX_ENCODING_LENGTH] = \"utf-8\";\ndiff --git a/update-ref.c b/update-ref.c\nindex fd48742..64fc3db 100644\n--- a/update-ref.c\n+++ b/update-ref.c\n@@ -22,7 +22,7 @@ int main(int argc, char **argv)\n \tconst char *refname, *value, *oldval, *path;\n \tchar *lockpath;\n \tunsigned char sha1[20], oldsha1[20], currsha1[20];\n-\tint fd, written;\n+\tint fd, written, pfxlen;\n \n \tsetup_git_directory();\n \tgit_config(git_default_config);\n@@ -38,7 +38,9 @@ int main(int argc, char **argv)\n \tif (oldval && get_sha1(oldval, oldsha1))\n \t\tdie(\"%s: not a valid old SHA1\", oldval);\n \n-\tpath = resolve_ref(git_path(\"%s\", refname), currsha1, !!oldval);\n+\tpath = git_path(\"%s\", refname);\n+\tpfxlen = strlen(path) - strlen(refname);\n+\tpath = resolve_ref(path, currsha1, !!oldval);\n \tif (!path)\n \t\tdie(\"No such ref: %s\", refname);\n \n@@ -50,7 +52,7 @@ int main(int argc, char **argv)\n \t\t\texit(0);\n \t}\n \tpath = strdup(path);\n-\tlockpath = mkpath(\"%s.lock\", path);\n+\tlockpath = strdup(mkpath(\"%s.lock\", path));\n \tif (safe_create_leading_directories(lockpath) < 0)\n \t\tdie(\"Unable to create all of %s\", lockpath);\n \n@@ -75,6 +77,46 @@ int main(int argc, char **argv)\n \t}\n \n \t/*\n+\t * Write to the log if logging of ref updates is enabled\n+\t */\n+\tif (log_ref_updates && memcmp(currsha1, sha1, 20)) {\n+\t\tchar *logrec, *logpath;\n+\t\tconst char *comitter;\n+\t\tunsigned maxlen, len;\n+\t\tint logfd;\n+\n+\t\tsetup_ident();\n+\t\tcomitter = git_committer_info(1);\n+\t\tmaxlen = strlen(comitter) + 2*40 + 4;\n+\t\tlogrec = xmalloc(maxlen);\n+\t\tlen = snprintf(logrec, maxlen, \"%s %s %s\\n\",\n+\t\t\tsha1_to_hex(currsha1),\n+\t\t\tsha1_to_hex(sha1),\n+\t\t\tcomitter);\n+\t\tif (len >= maxlen) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Internal error formatting log record.\");\n+\t\t}\n+\n+\t\tlogpath = git_path(\"logs/%s\", path + pfxlen);\n+\t\tif (safe_create_leading_directories(logpath) < 0) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Unable to create all of %s\", logpath);\n+\t\t}\n+\t\tlogfd = open(logpath, O_CREAT | O_APPEND | O_WRONLY, 0666);\n+\t\tif (logfd < 0) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Unable to append to %s\", logpath);\n+\t\t}\n+\t\twritten = write(logfd, logrec, len);\n+\t\tif (written != len) {\n+\t\t\tunlink(lockpath);\n+\t\t\tdie(\"Unable to append to %s\", logpath);\n+\t\t}\n+\t\tclose(logfd);\n+\t}\n+\n+\t/*\n \t * Finally, replace the old ref with the new one\n \t */\n \tif (rename(lockpath, path) < 0) {\n-- \n1.3.2.g7278\n"},{"id":"19935","messageId":"7vk68o19n4.fsf@assigned-by-dhcp.cox.net","threadId":"4124","inReplyTo":"20060513181816.GA12475@spearce.org","subject":"Re: Tracking branch history","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-05-14T23:14:55Z","receivedAt":"2006-05-14T23:14:55Z","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> Log ref updates to logs/refs/<ref>\n>\n> If config parameter core.logRefUpdates is true then append a line\n> to .git/logs/refs/<ref> whenever git-update-ref <ref> is executed.\n\nI cannot decide if a parameter makes more sense, or just making\nthe existence of such a file a cue is better.  For example, I do\nnot much care about when I updated each of my topic branch head,\nwhile I do care about master, next, and pu branches.  A global\nparameter would make this black-or-white choice, while opening\nthe log without O_CREAT and write things out only when the log\nfile exists might make things as easy and controllable.\n\nI could \"touch\" the ones I care about to prime the process.\n\nHmm?\n"},{"id":"19936","messageId":"7vfyjc19jt.fsf@assigned-by-dhcp.cox.net","threadId":"4124","inReplyTo":"loom.20060513T140528-554@post.gmane.org","subject":"Re: Tracking branch history","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-05-14T23:16:54Z","receivedAt":"2006-05-14T23:16:54Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elrond <elrond+kernel.org@samba-tng.org> writes:\n\n> Daniel Barkalow <barkalow <at> iabervon.org> writes:\n>\n>> \n>> One feature that might make git more intuitive to people is if we were to \n>> additionally track the history of what commit was the head of each branch \n>> over time. This is only vaguely related to the history of the content, but \n>> it's well-defined and sometimes significant.\n>> \n>> E.g., if you know that two weeks ago, what you had worked, but it doesn't \n>> work now, you can use git-bisect to figure out what happened, but first \n>> you have to figure out what commit it was that you were using two weeks \n>> ago. Two weeks ago, we had that information, but we didn't keep it.\n>\n> On a related issue:\n>\n> Looking at a commit:\n>    commit id-commit\n>    parent id-1\n>    parent id-2\n>    parent id-3\n>\n>        Merge branch 'branch-2', 'branch-3'\n>\n> One can tell the name of the branches for id-2 and id-3 (branch-2, 3),\n> but one can't tell the name of id-1.\n\nThat's deliberate.  If you are merging into a branch other than\n\"master\", the message would say:\n\n        commit ea892b27b15fbc46a3bb3ad2ddce737dc6590ae5\n        Merge: 7278a29... 8d48ad6...\n        Author: Junio C Hamano <junkio@cox.net>\n        Date:   Sat May 13 18:49:54 2006 -0700\n\n            Merge branch 'lt/config' into next\n\n            * lt/config:\n              git config syntax updates\n              Another config file parsing fix.\n              checkout: use --aggressive when running a 3-way merge (-m).\n              Fix git-pack-objects for 64-bit platforms\n              fix diff-delta bad memory access\n\nThe point is to keep the punch line as short and meaningful for\nthe most common case.\n"},{"id":"19958","messageId":"20060515031511.GA27505@spearce.org","threadId":"4124","inReplyTo":"7vk68o19n4.fsf@assigned-by-dhcp.cox.net","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-15T03:15:11Z","receivedAt":"2006-05-15T03:15:11Z","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> > Log ref updates to logs/refs/<ref>\n> >\n> > If config parameter core.logRefUpdates is true then append a line\n> > to .git/logs/refs/<ref> whenever git-update-ref <ref> is executed.\n> \n> I cannot decide if a parameter makes more sense, or just making\n> the existence of such a file a cue is better.  For example, I do\n> not much care about when I updated each of my topic branch head,\n> while I do care about master, next, and pu branches.  A global\n> parameter would make this black-or-white choice, while opening\n> the log without O_CREAT and write things out only when the log\n> file exists might make things as easy and controllable.\n> \n> I could \"touch\" the ones I care about to prime the process.\n\nI'm as torn as you are on this.  My `pg' would have some serious\nissues with update-ref and the logRefUpdates config being true as it\nuses a large number of refs to track the patch stack.  StGIT might\nalso be in a somewhat similiar position, but I think it uses slightly\nless refs.  Opening without O_CREAT and letting the user touch the\nlog file into existance would fix this problem for most users.\n\nBut I can also see the case where the user has most branches logged\nthen infrequently creates a new branch but expects that branch\nto also be logged.  What happens when they later find out its not\nlogging but they were counting on that log data to be there?\n\nPerhaps a mix of the two solutions is ideal.  Use O_CREAT if\nlogRefUpdates is true and otherwise log only if the file already\nexists.  This is now in the patch below.\n\nI have also added an optional '-m <reason>' flag to update-ref,\nfor use in e.g. git-commit.  I have also moved the logging code to\nrefs.c where it could be used by fetch or upload-pack, but those\nchanges are not yet included in this patch.\n\n--> -\nLog ref updates to logs/refs/<ref>\n\nIf config parameter core.logAllRefUpdates is true or the log\nfile already exists then append a line to \".git/logs/refs/<ref>\"\nwhenever git-update-ref <ref> is executed.  Each log line contains\nthe following information:\n\n  oldsha1 <SP> newsha1 <SP> committer <LF>\n\nwhere committer is the current user, date, time and timezone in\nthe standard GIT ident format.  If the caller is unable to append\nto the log file then git-update-ref will fail without updating <ref>.\n\nAn optional message may be included in the log line with the -m flag.\n\n---\n\n Documentation/config.txt         |    8 ++++++\n Documentation/git-update-ref.txt |   28 +++++++++++++++++++-\n cache.h                          |    1 +\n config.c                         |    5 ++++\n environment.c                    |    1 +\n refs.c                           |   50 ++++++++++++++++++++++++++++++++++++\n refs.h                           |    3 ++\n update-ref.c                     |   53 ++++++++++++++++++++++++++++++++------\n 8 files changed, 139 insertions(+), 10 deletions(-)\n\nbbac000fbc779c7434d66b10639eaa5ea218f62e\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex d1a4bec..e178ee2 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -70,6 +70,14 @@ core.preferSymlinkRefs::\n \tThis is sometimes needed to work with old scripts that\n \texpect HEAD to be a symbolic link.\n \n+core.logAllRefUpdates::\n+\tIf true, `git-update-ref` will append a line to\n+\t\"$GIT_DIR/logs/<ref>\" listing the new SHA1 and the date/time\n+\tof the update.\tIf the file does not exist it will be\n+\tcreated automatically.\tThis information can be used to\n+\tdetermine what commit was the tip of a branch \"2 days ago\".\n+\tThis value is false by default (no logging).\n+\n core.repositoryFormatVersion::\n \tInternal variable identifying the repository format and layout\n \tversion.\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 475237f..dfbd886 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -7,7 +7,7 @@ git-update-ref - update the object name \n \n SYNOPSIS\n --------\n-'git-update-ref' <ref> <newvalue> [<oldvalue>]\n+'git-update-ref' <ref> <newvalue> [<oldvalue>] [-m <reason>]\n \n DESCRIPTION\n -----------\n@@ -49,6 +49,32 @@ for reading but not for writing (so we'l\n ref symlink to some other tree, if you have copied a whole\n archive by creating a symlink tree).\n \n+Logging Updates\n+---------------\n+If config parameter \"core.logAllRefUpdates\" is true or the file\n+\"$GIT_DIR/logs/<ref>\" exists then `git-update-ref` will append\n+a line to the log file \"$GIT_DIR/logs/<ref>\" (dereferencing all\n+symbolic refs before creating the log name) describing the change\n+in ref value.  Log lines are formatted as:\n+\n+    . oldsha1 SP newsha1 SP committer LF\n++\n+Where \"oldsha1\" is the 40 character hexadecimal value previously\n+stored in <ref>, \"newsha1\" is the 40 character hexadecimal value of\n+<newvalue> and \"committer\" is the committer's name, email address\n+and date in the standard GIT committer ident format.\n+\n+Optionally with -m:\n+\n+    . oldsha1 SP newsha1 SP committer TAB message LF\n++\n+Where all fields are as described above and \"message\" is the\n+value supplied to the -m option.\n+\n+An update will fail (without changing <ref>) if the current user is\n+unable to create a new log file, append to the existing log file\n+or does not have committer information available.\n+\n Author\n ------\n Written by Linus Torvalds <torvalds@osdl.org>.\ndiff --git a/cache.h b/cache.h\nindex b1300cd..82adbba 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -171,6 +171,7 @@ extern void rollback_index_file(struct c\n extern int trust_executable_bit;\n extern int assume_unchanged;\n extern int prefer_symlink_refs;\n+extern int log_all_ref_updates;\n extern int warn_ambiguous_refs;\n extern int diff_rename_limit_default;\n extern int shared_repository;\ndiff --git a/config.c b/config.c\nindex 0f518c9..fa6db4b 100644\n--- a/config.c\n+++ b/config.c\n@@ -232,6 +232,11 @@ int git_default_config(const char *var, \n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"core.logallrefupdates\")) {\n+\t\tlog_all_ref_updates = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"core.warnambiguousrefs\")) {\n \t\twarn_ambiguous_refs = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/environment.c b/environment.c\nindex 444c99e..2e79eab 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];\n int trust_executable_bit = 1;\n int assume_unchanged = 0;\n int prefer_symlink_refs = 0;\n+int log_all_ref_updates = 0;\n int warn_ambiguous_refs = 1;\n int repository_format_version = 0;\n char git_commit_encoding[MAX_ENCODING_LENGTH] = \"utf-8\";\ndiff --git a/refs.c b/refs.c\nindex 275b914..691a8ba 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -375,3 +375,53 @@ int write_ref_sha1_unlocked(const char *\n \tfree(lock_filename);\n \treturn retval;\n }\n+\n+int log_ref_update(const char *ref, const unsigned char *currsha1, const unsigned char *newsha1, const char *msg)\n+{\n+\tchar *logpath, *logrec;\n+\tconst char *comitter;\n+\tunsigned maxlen, len;\n+\tint oflags, logfd, written;\n+\n+\toflags = O_APPEND | O_WRONLY;\n+\tlogpath = git_path(\"logs/%s\", ref);\n+\tif (log_all_ref_updates) {\n+\t\tif (safe_create_leading_directories(logpath) < 0)\n+\t\t\treturn error(\"Unable to create all of %s\", logpath);\n+\t\toflags |= O_CREAT;\n+\t}\n+\n+\tlogfd = open(logpath, oflags, 0666);\n+\tif (logfd < 0) {\n+\t\tif (!log_all_ref_updates && errno == ENOENT)\n+\t\t\treturn 0;\n+\t\treturn error(\"Unable to append to %s: %s\", logpath, strerror(errno));\n+\t}\n+\n+\tsetup_ident();\n+\tcomitter = git_committer_info(1);\n+\tif (msg) {\n+\t\tmaxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;\n+\t\tlogrec = xmalloc(maxlen);\n+\t\tlen = snprintf(logrec, maxlen, \"%s %s %s\\t%s\\n\",\n+\t\t\tsha1_to_hex(currsha1),\n+\t\t\tsha1_to_hex(newsha1),\n+\t\t\tcomitter,\n+\t\t\tmsg);\n+\t} else {\n+\t\tmaxlen = strlen(comitter) + 2*40 + 4;\n+\t\tlogrec = xmalloc(maxlen);\n+\t\tlen = snprintf(logrec, maxlen, \"%s %s %s\\n\",\n+\t\t\tsha1_to_hex(currsha1),\n+\t\t\tsha1_to_hex(newsha1),\n+\t\t\tcomitter);\n+\t}\n+\tif (len >= maxlen)\n+\t\treturn error(\"Internal error formatting log record.\");\n+\twritten = write(logfd, logrec, len);\n+\tif (written != len)\n+\t\treturn error(\"Unable to append to %s\", logpath);\n+\tfree(logrec);\n+\tclose(logfd);\n+\treturn 0;\n+}\ndiff --git a/refs.h b/refs.h\nindex 2625596..9345942 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -25,4 +25,7 @@ extern int write_ref_sha1_unlocked(const\n /** Returns 0 if target has the right format for a ref. **/\n extern int check_ref_format(const char *target);\n \n+/** If logging is enabled logs the change made to the ref. **/\n+extern int log_ref_update(const char *ref, const unsigned char *currsha1, const unsigned char *newsha1, const char *msg);\n+\n #endif /* REFS_H */\ndiff --git a/update-ref.c b/update-ref.c\nindex fd48742..95940dd 100644\n--- a/update-ref.c\n+++ b/update-ref.c\n@@ -1,7 +1,8 @@\n #include \"cache.h\"\n #include \"refs.h\"\n \n-static const char git_update_ref_usage[] = \"git-update-ref <refname> <value> [<oldval>]\";\n+static const char git_update_ref_usage[] =\n+\"git-update-ref <refname> <value> [<oldval>] [-m <reason>]\";\n \n static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)\n {\n@@ -19,26 +20,50 @@ static int re_verify(const char *path, u\n int main(int argc, char **argv)\n {\n \tchar *hex;\n-\tconst char *refname, *value, *oldval, *path;\n+\tconst char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL, *path;\n \tchar *lockpath;\n \tunsigned char sha1[20], oldsha1[20], currsha1[20];\n-\tint fd, written;\n+\tint i, fd, written, pfxlen;\n \n \tsetup_git_directory();\n \tgit_config(git_default_config);\n-\tif (argc < 3 || argc > 4)\n+\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(\"-m\", argv[i])) {\n+\t\t\tif (i+1 >= argc)\n+\t\t\t\tusage(git_update_ref_usage);\n+\t\t\tmsg = argv[++i];\n+\t\t\tif (!*msg)\n+\t\t\t\tdie(\"Refusing to perform update with empty message.\");\n+\t\t\tif (strchr(msg, '\\n'))\n+\t\t\t\tdie(\"Refusing to perform update with \\\\n in message.\");\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!refname) {\n+\t\t\trefname = argv[i];\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!value) {\n+\t\t\tvalue = argv[i];\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!oldval) {\n+\t\t\toldval = argv[i];\n+\t\t\tcontinue;\n+\t\t}\n+\t}\n+\tif (!refname || !value)\n \t\tusage(git_update_ref_usage);\n \n-\trefname = argv[1];\n-\tvalue = argv[2];\n-\toldval = argv[3];\n \tif (get_sha1(value, sha1))\n \t\tdie(\"%s: not a valid SHA1\", value);\n \tmemset(oldsha1, 0, 20);\n \tif (oldval && get_sha1(oldval, oldsha1))\n \t\tdie(\"%s: not a valid old SHA1\", oldval);\n \n-\tpath = resolve_ref(git_path(\"%s\", refname), currsha1, !!oldval);\n+\tpath = git_path(\"%s\", refname);\n+\tpfxlen = strlen(path) - strlen(refname);\n+\tpath = resolve_ref(path, currsha1, !!oldval);\n \tif (!path)\n \t\tdie(\"No such ref: %s\", refname);\n \n@@ -49,8 +74,13 @@ int main(int argc, char **argv)\n \t\tif (!memcmp(oldsha1, sha1, 20))\n \t\t\texit(0);\n \t}\n+\n+\t/* No change in ref.  Don't do anything. */\n+\tif (!memcmp(currsha1, sha1, 20))\n+\t\texit(0);\n+\n \tpath = strdup(path);\n-\tlockpath = mkpath(\"%s.lock\", path);\n+\tlockpath = strdup(mkpath(\"%s.lock\", path));\n \tif (safe_create_leading_directories(lockpath) < 0)\n \t\tdie(\"Unable to create all of %s\", lockpath);\n \n@@ -74,6 +104,11 @@ int main(int argc, char **argv)\n \t\tdie(\"Ref lock failed\");\n \t}\n \n+\tif (log_ref_update(path + pfxlen, currsha1, sha1, msg) < 0) {\n+\t\tunlink(lockpath);\n+\t\tdie(\"Logging ref update failed, update aborted.\");\n+\t}\n+\n \t/*\n \t * Finally, replace the old ref with the new one\n \t */\n-- \n1.3.2.g7278\n"},{"id":"19968","messageId":"20060515055830.GC28068@spearce.org","threadId":"4124","inReplyTo":"20060515031511.GA27505@spearce.org","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-15T05:58:30Z","receivedAt":"2006-05-15T05:58:30Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Shawn Pearce <spearce@spearce.org> wrote:\n> Log ref updates to logs/refs/<ref>\n> \n> If config parameter core.logAllRefUpdates is true or the log\n> file already exists then append a line to \".git/logs/refs/<ref>\"\n> whenever git-update-ref <ref> is executed.  Each log line contains\n> the following information:\n> \n>   oldsha1 <SP> newsha1 <SP> committer <LF>\n> \n> where committer is the current user, date, time and timezone in\n> the standard GIT ident format.  If the caller is unable to append\n> to the log file then git-update-ref will fail without updating <ref>.\n> \n> An optional message may be included in the log line with the -m flag.\n\nThis is all well and good but its sort of useless without the diffcore\nbeing able to lookup what SHA1 was valid on a given branch at a given\npoint in time.  :-)\n\nI'm thinking about extending the 'extended SHA1' syntax to accept\na date (or date expression) as a suffix:\n\n\tHEAD@'2 hours ago'\n\tHEAD@'2006-04-20'\n\tHEAD@'2006-04-20 14:12'\n\netc... This would be merged into get_sha1 (sha1_name.c) so its\nusable pretty much anywhere.  Does this seem reasonable?  If so\nI'll work up a patch for it.\n\n-- \nShawn.\n"},{"id":"19972","messageId":"7vac9jx0nq.fsf@assigned-by-dhcp.cox.net","threadId":"4124","inReplyTo":"20060515055830.GC28068@spearce.org","subject":"Re: Tracking branch history","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-05-15T06:27:53Z","receivedAt":"2006-05-15T06:27:53Z","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> This is all well and good but its sort of useless without the diffcore\n> being able to lookup what SHA1 was valid on a given branch at a given\n> point in time.  :-)\n>\n> I'm thinking about extending the 'extended SHA1' syntax to accept\n> a date (or date expression) as a suffix:\n>\n> \tHEAD@'2 hours ago'\n> \tHEAD@'2006-04-20'\n> \tHEAD@'2006-04-20 14:12'\n>\n> etc... This would be merged into get_sha1 (sha1_name.c) so its\n> usable pretty much anywhere.  Does this seem reasonable?  If so\n> I'll work up a patch for it.\n\nHEAD?\n\nAre you going to hook into symbolic-ref as well to track branch\nswitching?\n\nSince there is no reverse pointer to tell which symbolic\nreference is pointing at branch heads,and there are symbolic\nreferences like refs/remotes/origin/HEAD that point at\nrefs/remotes/origin/master, detecting that such and such\nsymbolic refs are pointing at a branch that is advanced by a\ncall to update-ref and update the log for the symbolic refs that\npoint at it becomes rather expensive.\nSo probably you would need a separate log format that tracks\nwhich concrete ref a symbolic ref was pointing at at any given\ntime and use that to keep track of them.\n\nI personally doubt it is worth the trouble.  I switch branches\nbetween master, next and the topics all the time, and never is\ninterested in which branch I happened to be on 30 minutes ago.\n\nThe time-warp format would make sense for individual branches,\nlike refs/heads/master, though.\n\nsha1_name.c and sha1_file.c were supposed to be real core, but\nget_sha1() is looking more and more Porcelainish these days, and\nI do not have much problem with being able to say \"tip of this\nbranch, two hours ago\".\n\nI am not sure about the syntax though.  We would want to be able\nto say \"start from the commit that was at the tip of 'master'\nbranch two days ago, grab its tree and look at arch/sparc64\ndirectory\", so things like\n\n            \"master@2006-05-14 14:12\"\n            \"master@2006-05-14 14:12^{tree}\"\n            \"master@two days ago:arch/sparc64\"\n\nwould need to be supported.\n"},{"id":"19973","messageId":"20060515063849.GA28337@spearce.org","threadId":"4124","inReplyTo":"7vac9jx0nq.fsf@assigned-by-dhcp.cox.net","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-15T06:38:49Z","receivedAt":"2006-05-15T06:38:49Z","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> > This is all well and good but its sort of useless without the diffcore\n> > being able to lookup what SHA1 was valid on a given branch at a given\n> > point in time.  :-)\n> >\n> > I'm thinking about extending the 'extended SHA1' syntax to accept\n> > a date (or date expression) as a suffix:\n> >\n> > \tHEAD@'2 hours ago'\n> > \tHEAD@'2006-04-20'\n> > \tHEAD@'2006-04-20 14:12'\n> >\n> > etc... This would be merged into get_sha1 (sha1_name.c) so its\n> > usable pretty much anywhere.  Does this seem reasonable?  If so\n> > I'll work up a patch for it.\n> \n> HEAD?\n> \n> Are you going to hook into symbolic-ref as well to track branch\n> switching?\n\nI hadn't planned on it.  I was going to resolve the symref HEAD\ndown to the real ref (e.g. refs/heads/sp/ref-log) and then do the\ndate range searching on the real branch.  I didn't think it was\ninteresting to track what HEAD is.  But I think it would be very\ncommon for the user to use HEAD rather than their actual branch\nref names when forming an expression.\n \n[snip]\n> The time-warp format would make sense for individual branches,\n> like refs/heads/master, though.\n> \n> sha1_name.c and sha1_file.c were supposed to be real core, but\n> get_sha1() is looking more and more Porcelainish these days, and\n> I do not have much problem with being able to say \"tip of this\n> branch, two hours ago\".\n> \n> I am not sure about the syntax though.  We would want to be able\n> to say \"start from the commit that was at the tip of 'master'\n> branch two days ago, grab its tree and look at arch/sparc64\n> directory\", so things like\n> \n>             \"master@2006-05-14 14:12\"\n>             \"master@2006-05-14 14:12^{tree}\"\n>             \"master@two days ago:arch/sparc64\"\n> \n> would need to be supported.\n\nYea, I realize that.  I'm currently looking at get_sha1_1 and how\nI can put the date resolution in before the ^, ~ and :.  :-)\n\n-- \nShawn.\n"},{"id":"19985","messageId":"20060515095307.GB28337@spearce.org","threadId":"4124","inReplyTo":"20060515063849.GA28337@spearce.org","subject":"Re: Tracking branch history","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-05-15T09:53:07Z","receivedAt":"2006-05-15T09:53:07Z","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> > Shawn Pearce <spearce@spearce.org> writes:\n> > \n> > > This is all well and good but its sort of useless without the diffcore\n> > > being able to lookup what SHA1 was valid on a given branch at a given\n> > > point in time.  :-)\n> > >\n> > > I'm thinking about extending the 'extended SHA1' syntax to accept\n> > > a date (or date expression) as a suffix:\n> > >\n> > > \tHEAD@'2 hours ago'\n> > > \tHEAD@'2006-04-20'\n> > > \tHEAD@'2006-04-20 14:12'\n> > >\n> > > etc... This would be merged into get_sha1 (sha1_name.c) so its\n> > > usable pretty much anywhere.  Does this seem reasonable?  If so\n> > > I'll work up a patch for it.\n\nThis is a preliminary patch for this syntax.  I haven't handled the\nabsolute date parsing yet; I was hoping to use the same syntax\naccepted by GIT_AUTHOR_DATE/GIT_COMMITTER_DATE but looking at the\ncode in date.c it wasn't going to be easily reused.  I'll work\non it more tomorrow, right now I have to go do my day job to pay\nthe rent.  :-)\n\nHmm... A quick look at date.c indicates I should be able to clean up\nthis parse_date_spec function quite a bit by using code from date.c.\nI'll look at it more later.\n\n-- >8 -\nSupport 'master@2 hours ago' syntax\n\nExtended 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\n---\n\n Documentation/git-rev-parse.txt |    6 ++\n refs.c                          |   52 ++++++++++++++\n refs.h                          |    3 +\n sha1_name.c                     |  145 ++++++++++++++++++++++++++++++++++-----\n 4 files changed, 189 insertions(+), 17 deletions(-)\n\n1f16364fd8cadb6cdeb0b14a6f5439f02b578924\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 a50ea8f..da009ac 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -440,3 +440,55 @@ int log_ref_update(const char *ref, cons\n \tclose(logfd);\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, *rec, *c;\n+\tchar *tz_c;\n+\tint logfd, tz;\n+\tstruct stat st;\n+\tunsigned long date;\n+\tunsigned char oldsha1[20];\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 = 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++ != '>')\n+\t\t\t/* nada */;\n+\t\tdate = strtoul(c, NULL, 10);\n+\t\tif (date <= at_time) {\n+\t\t\tif (get_sha1_hex(rec, oldsha1))\n+\t\t\t\tdie(\"Log %s is corrupt.\", logfile);\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++ != '>')\n+\t\t/* nada */;\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 de3cb92..4831cdb 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -31,4 +31,7 @@ extern int check_ref_format(const char *\n /** If logging is enabled logs the change made to the ref. **/\n extern int log_ref_update(const char *ref, const unsigned char *currsha1, const unsigned char *newsha1, 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 #endif /* REFS_H */\ndiff --git a/sha1_name.c b/sha1_name.c\nindex dc68355..5f33aea 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@@ -234,6 +235,98 @@ static int ambiguous_path(const char *pa\n \treturn slash;\n }\n \n+static unsigned long parse_date_spec(const char *str, int len)\n+{\n+\tlong delta;\n+\ttime_t now;\n+\n+\ttime(&now);\n+\tif (len == 9 && !strncasecmp(\"yesterday\", str, 9))\n+\t\treturn now - 24 * 60 * 60;\n+\tif (len > 4 && !strncasecmp(\" ago\", str + (len - 4), 4)) {\n+\t\tlen -= 4;\n+\t\twhile (len) {\n+\t\t\tif (len > 2 && !strncasecmp(\"a \", str, 2)) {\n+\t\t\t\tdelta = 1;\n+\t\t\t\tlen -= 2;\n+\t\t\t\tstr += 2;\n+\t\t\t}\n+\t\t\telse if (len > 3 && !strncasecmp(\"an \", str, 3)) {\n+\t\t\t\tdelta = 1;\n+\t\t\t\tlen -= 2;\n+\t\t\t\tstr += 2;\n+\t\t\t} else {\n+\t\t\t\tdelta = 0;\n+\t\t\t\twhile (len && isdigit(*str)) {\n+\t\t\t\t\tif (delta)\n+\t\t\t\t\t\tdelta *= 10;\n+\t\t\t\t\tdelta += *str++ - '0';\n+\t\t\t\t\tlen--;\n+\t\t\t\t}\n+\t\t\t\tif (!delta)\n+\t\t\t\t\treturn (time_t)-1;\n+\t\t\t\twhile (len && isspace(*str)) {\n+\t\t\t\t\tstr++;\n+\t\t\t\t\tlen--;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (len >= 5 && !strncasecmp(\"month\", str, 5)) {\n+\t\t\t\tlen -= 5;\n+\t\t\t\tstr += 5;\n+\t\t\t\tnow -= 30 * 24 * 60 * 60 * delta;\n+\t\t\t}\n+\t\t\telse if (len >= 4 && !strncasecmp(\"week\", str, 4)) {\n+\t\t\t\tlen -= 4;\n+\t\t\t\tstr += 4;\n+\t\t\t\tnow -= 7 * 24 * 60 * 60 * delta;\n+\t\t\t}\n+\t\t\telse if (len >= 3 && !strncasecmp(\"day\", str, 3)) {\n+\t\t\t\tlen -= 3;\n+\t\t\t\tstr += 3;\n+\t\t\t\tnow -= 24 * 60 * 60 * delta;\n+\t\t\t}\n+\t\t\telse if (len >= 4 && !strncasecmp(\"hour\", str, 4)) {\n+\t\t\t\tlen -= 4;\n+\t\t\t\tstr += 4;\n+\t\t\t\tnow -= 60 * 60 * delta;\n+\t\t\t}\n+\t\t\telse if (len >= 6 && !strncasecmp(\"minute\", str, 6)) {\n+\t\t\t\tlen -= 6;\n+\t\t\t\tstr += 6;\n+\t\t\t\tnow -= 60 * delta;\n+\t\t\t}\n+\t\t\telse if (len >= 3 && !strncasecmp(\"min\", str, 3)) {\n+\t\t\t\tlen -= 3;\n+\t\t\t\tstr += 3;\n+\t\t\t\tnow -= 60 * delta;\n+\t\t\t}\n+\t\t\telse if (len >= 6 && !strncasecmp(\"second\", str, 6)) {\n+\t\t\t\tlen -= 6;\n+\t\t\t\tstr += 6;\n+\t\t\t\tnow -= delta;\n+\t\t\t}\n+\t\t\telse if (len >= 3 && !strncasecmp(\"sec\", str, 3)) {\n+\t\t\t\tlen -= 3;\n+\t\t\t\tstr += 3;\n+\t\t\t\tnow -= delta;\n+\t\t\t}\n+\n+\t\t\tif (len && *str == 's') {\n+\t\t\t\tlen -= 1;\n+\t\t\t\tstr += 1;\n+\t\t\t}\n+\n+\t\t\twhile (len && isspace(*str)) {\n+\t\t\t\tstr++;\n+\t\t\t\tlen--;\n+\t\t\t}\n+\t\t}\n+\t\treturn now;\n+\t}\n+\treturn (time_t)-1;\n+}\n+\n static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n {\n \tstatic const char *fmt[] = {\n@@ -245,36 +338,54 @@ 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\tat_time = parse_date_spec(str + at_mark + 1, len - at_mark - 1);\n+\t\t\tif (at_time == (unsigned long)-1)\n+\t\t\t\tdie(\"Invalid date spec after @ in '%.*s'\", len, str);\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(real_path + strlen(git_path(\".\")) - 1, at_time, sha1);\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"}]}