{"thread":{"id":"25326","subject":"RFC: checkout/temporary branch switch restoring modification times","startedAt":"2010-10-04T04:42:00Z","lastAt":"2010-10-04T14:20:52Z","messageCount":4,"participants":["Justin Frankel","Ævar Arnfjörð Bjarmason","Jonathan Nieder"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"152458","messageId":"4CA95B18.5090008@cockos.com","threadId":"25326","inReplyTo":null,"subject":"RFC: checkout/temporary branch switch restoring modification times","fromName":"Justin Frankel","fromEmail":"justin@cockos.com","sentAt":"2010-10-04T04:42:00Z","receivedAt":"2010-10-04T04:42:00Z","isPatch":false,"sender":{"key":"justin@cockos.com","avatar":"https://avatars.githubusercontent.com/u/5803984?v=4"},"body":"Hello all,\n\nI often find myself wanting to switch temporarily to another branch, and\nthen back to my original branch, but what I find is that my build tools\nsuch as MSVC or Xcode) notice the file modification times changed on\nthe changed files, and then want to rebuild, even though the contents of\nthe files are the same.\n\nI've implemented a perl script (tested on OSX and msysGit), which I've\nnamed \"git-cop\" (so I can use via \"git cop\") to checkout,\npreserving file modification times.\n\nThe use case is:\n\ngit cop master\n; build\ngit cop some-branch-that-affects-lots-of-files\n; edit some things, commit\ngit cop master\n; build (fast, nothing changed)\n\nIf you use \"checkout\" instead of \"cop\", the final build command might\ntake longer, since the modification times on files may have changed.\n\nTo summarize the implementation of the script:\n\n1) Get a list of what files will change\n2) Read timestamps for changed files, updating .git/timestamp-list.txt\nwhich is effectively an associative array of timestamps, indexed\nby \"blobhash:filename\".\n3) Call git checkout\n4) Update file modification times with their new timestamps (read from\n.git/timestamp-list.txt, using newblobhash:filename).\n\nThis will get slower as that text file grows, but you can periodically\nremove .git/timestamp-list.txt (any time you're not in the middle of a\ntemporary branch switch), and it will be regenerated.\n\nAs this is a request for comments, I'd be curious if anybody has\nthoughts on the best way to integrate this type of functionality into\ngit, or indeed if anybody else thinks it is worth including (I do find\nit saves me a great deal of time, but it is easy enough to add to my git\ninstalls, too).\n\nAdditionally, is there a better place to store repository-local\ninformation, information which isn't terribly important and shouldn't be\ncopied in case of clones, etc? I figured .git/timestamp-list.txt isn't\nthe best place, but where would that be?\n\nCheers,\n\nJustin\n\n\n\n---------------- begin git-cop -----------------\n#!/usr/bin/perl\n\nmy ($destbranch,$allargs) = (\"\", \"\");\n\nforeach (@ARGV) {\n\t$allargs .= \" $_\";\n\t# last non-option parameter would be considered the new branch\n\t$destbranch = $_ if ($_ !~ /^-/);\n}\n\n$destbranch eq \"\" and die \"Usage: git-cop [checkout options] <branch>\\n\";\n\nmy $modf = join('',`git diff --name-only HEAD..$destbranch`);\n$modf =~ s/ /\\\\ /g;\n$modf =~ s/\\n/ /g;\n\nlength($modf)>0 or exit(system(\"git checkout$allargs\"));\n\nchomp(my $pwd = `git rev-parse --show-toplevel`);\nmy $idxfile = \"${pwd}/.git/timestamp-list.txt\";\nchdir($pwd);\n\nmy %times;\nif (open(my $F, \"<\", $idxfile)) {\n\t# read cached timestamps\n\twhile (<$F>) {\n\t\tchomp;\n\t\tmy ($val, $nm) = split(/\\t/, $_,2);\n\t\t$nm eq \"\" or $val eq \"\" or $times{$nm} = $val;\n\t}\n\tclose($F);\n}\n\nmy $idxfile_updcnt = 0;\nforeach (`git ls-tree HEAD $modf`) {\n\tchomp;\n\tmy ($perm, $type, $hash, $fn) = split(\" \",$_,4);\n\tif ($type eq \"blob\") {\n\t\t# add/update timestamp in cache if needed\n\t\t$hash .= \":$fn\";\n\t\tmy $mt = int ((stat(\"${pwd}/$fn\"))[9]);\n\t\tif ($mt && $mt != (int $times{$hash})) {\n\t\t\t$times{$hash} = $mt;\n\t\t\t$idxfile_updcnt++;\n\t\t}\n\t}\n}\n\nif ($idxfile_updcnt != 0) {\n\tprint \"Saved/updated timestamps for $idxfile_updcnt files\\n\";\n\tif (open(my $F, \">\", $idxfile)) {\n\t\tprint $F \"$ts\\t$nm\\n\" while (($nm,$ts) = each(%times));\n\t\tclose($F);\n\t}\n}\n\nmy $rv = system(\"git checkout$allargs\");\n\n!$rv or exit($rv);\n\n# restore timestamps from cache\nmy $update_cnt = 0;\nforeach (`git ls-tree HEAD $modf`) {\n\tchomp;\n\tmy ($perm, $type, $hash, $fn) = split(\" \",$_,4);\n\tif ($type eq \"blob\") {\n\t\t$hash .= \":$fn\";\n\t\tmy $tsreq = int $times{$hash};\n\t\tif ($tsreq &&\n  \t\t    $tsreq != (int ((stat(\"${pwd}/$fn\"))[9]))) {\n\t\t\tutime(undef,$tsreq,\"${pwd}/$fn\");\n\t\t\t$update_cnt++;\n\t\t}\n\t}\n}\n$update_cnt < 1 or\n\tprint \"Updated file timestamps for $update_cnt files\\n\";\n\nexit (0);\n\n---------------- end git-cop -----------------\n"},{"id":"152459","messageId":"AANLkTinXbwtv=6TMeu0nA=jV4u7zGXE_56791ky_=93b@mail.gmail.com","threadId":"25326","inReplyTo":"4CA95B18.5090008@cockos.com","subject":"Re: RFC: checkout/temporary branch switch restoring modification times","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-10-04T05:00:32Z","receivedAt":"2010-10-04T05:00:32Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Mon, Oct 4, 2010 at 04:42, Justin Frankel <justin@cockos.com> wrote:\n> I often find myself wanting to switch temporarily to another branch, and\n> then back to my original branch, but what I find is that my build tools\n> such as MSVC or Xcode) notice the file modification times changed on\n> the changed files, and then want to rebuild, even though the contents of\n> the files are the same.\n>\n> I've implemented a perl script (tested on OSX and msysGit), which I've\n> named \"git-cop\" (so I can use via \"git cop\") to checkout,\n> preserving file modification times.\n\nMaybe something like this is a worthwhile feature, although perhaps it\nwould be better to give each file a timestamp corresponding to when it\nwas last modified in Git? That would break in many cases, but a scheme\nlike this inherently does.\n\nHave you tried to use ccache? It should reduce your build times by a\nlot, so perhaps if you use it you won't need this anymore.\n"},{"id":"152460","messageId":"20101004051148.GG24884@burratino","threadId":"25326","inReplyTo":"4CA95B18.5090008@cockos.com","subject":"Re: RFC: checkout/temporary branch switch restoring modification times","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2010-10-04T05:11:48Z","receivedAt":"2010-10-04T05:11:48Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nJustin Frankel wrote:\n\n> git cop master\n> ; build\n> git cop some-branch-that-affects-lots-of-files\n> ; edit some things, commit\n> git cop master\n> ; build (fast, nothing changed)\n\nInteresting.  I guess the intended use is that you only ever build\non the master branch?\n\nHave you ever tried the contrib/workdir/git-new-workdir script?\nI find it fits the use case well for me:\n\n git clone $repo\n cd repo\n make\n # oh, shoot! I need to try something out real quick.\n cd ..\n git new-workdir repo repo2 origin/master\n cd repo2\n git am patch-to-test\n make\n # okay, back to what I was doing...\n cd ../repo\n\nMaybe it could be helpful for you, too?\n\nLimitations:\n\n - requires a file system with support for symbolic links\n   (I think Pierre Habouzit and Junio discussed changing\n   that);\n\n - workdirs share refs.  If you update master in one\n   workdir and another workdir also has master checked\n   out, the new changes will appear as staged changes.\n\n - workdirs do not share HEAD.  \"git gc\" from one\n   workdir can completely trash another if it has a\n   detached HEAD pointing to a commit that is not part\n   of any local or remote branch.\n"},{"id":"152533","messageId":"4CA9E2C4.2040500@cockos.com","threadId":"25326","inReplyTo":"20101004051148.GG24884@burratino","subject":"Re: RFC: checkout/temporary branch switch restoring modification times","fromName":"Justin Frankel","fromEmail":"justin@cockos.com","sentAt":"2010-10-04T14:20:52Z","receivedAt":"2010-10-04T14:20:52Z","isPatch":false,"sender":{"key":"justin@cockos.com","avatar":"https://avatars.githubusercontent.com/u/5803984?v=4"},"body":"Jonathan Nieder wrote:\n> Hi,\n> \n> Justin Frankel wrote:\n> \n>> git cop master\n>> ; build\n>> git cop some-branch-that-affects-lots-of-files\n>> ; edit some things, commit\n>> git cop master\n>> ; build (fast, nothing changed)\n> \n> Interesting.  I guess the intended use is that you only ever build\n> on the master branch?\n> \n\nThe idea is that you're often building on a particular branch, but want \nto switch to another branch temporarily to either do a quick edit or to \nbrowse some code.\n\n> Have you ever tried the contrib/workdir/git-new-workdir script?\n> I find it fits the use case well for me:\n> \n>  git clone $repo\n>  cd repo\n>  make\n>  # oh, shoot! I need to try something out real quick.\n>  cd ..\n>  git new-workdir repo repo2 origin/master\n>  cd repo2\n>  git am patch-to-test\n>  make\n>  # okay, back to what I was doing...\n>  cd ../repo\n> \n> Maybe it could be helpful for you, too?\n> \n> Limitations:\n> \n>  - requires a file system with support for symbolic links\n>    (I think Pierre Habouzit and Junio discussed changing\n>    that);\n> \n>  - workdirs share refs.  If you update master in one\n>    workdir and another workdir also has master checked\n>    out, the new changes will appear as staged changes.\n> \n>  - workdirs do not share HEAD.  \"git gc\" from one\n>    workdir can completely trash another if it has a\n>    detached HEAD pointing to a commit that is not part\n>    of any local or remote branch.\n\n\nAhh, that would be great.  Unfortunately my filesystem often doesn't \nsupport symlinks...\n\n-Justin\n"}]}