{"thread":{"id":"34967","subject":"[PATCH v4] Teach git to change to a given directory using -C option","startedAt":"2013-09-19T13:18:54Z","lastAt":"2013-09-19T17:13:51Z","messageCount":2,"participants":["Nazri Ramliy","Junio C Hamano"],"isPatch":true,"patchVersion":4,"patchTotal":null},"messages":[{"id":"227883","messageId":"20130919131854.GA19790@gmail.com","threadId":"34967","inReplyTo":null,"subject":"[PATCH v4] Teach git to change to a given directory using -C option","fromName":"Nazri Ramliy","fromEmail":"ayiehere@gmail.com","sentAt":"2013-09-19T13:18:54Z","receivedAt":"2013-09-19T13:18:54Z","isPatch":true,"sender":{"key":"ayiehere@gmail.com","avatar":"https://avatars.githubusercontent.com/u/164756?v=4"},"body":"Hi,\n\nSorry it took me a while to get back on this. Reroll at the bottom ...\n\nOn Tue, Sep 10, 2013 at 12:32 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nazri Ramliy <ayiehere@gmail.com> writes:\n>\n>> Subject: git: run in a directory given with -C option\n>>\n>> This is similar in spirit to to \"make -C dir ...\" and \"tar -C dir ...\".\n>\n> The doubled-\"to to\" which I locally fixed when I queued the last one\n> (together with other rewording to make it more agreeable and easier\n> to read) somehow came back ;-) Will fix locally again.\n\nI must have mistakenly made the revision on top of my local changes\ninstead of the one in next - I didn't notice the fix. Thanks for the\nfix.\n\n>> +-C <path>::\n>> +     Run as if git was started in '<path>' instead of the current working\n>> +     directory.  When multiple '-C' options are given, each subsequent\n>\n> I think this should be `-C` to typeset it as \"typed literally\".\n>\n>> +     non-absolute `-C <path>` is interpreted relative to the preceding `-C\n>> +     <path>`.\n>> ++\n>> +This option affects options that expect path name like '--git-dir' and\n>> +'--work-tree' in that their interpretations of the path names would be\n>\n> Likewise for `--git-dir` and `--work-tree`.\n>\n>> +made relative to the working directory caused by the '-C' option. For\n>\n> and here.\n\nNow I'm noticing that you've already made the above fixes in next ;)\n\n>> diff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh\n>> new file mode 100755\n>> index 0000000..c0006da\n>> --- /dev/null\n>> +++ b/t/t0056-git-C.sh\n>> @@ -0,0 +1,82 @@\n>> +#!/bin/sh\n>> +\n>> +test_description='\"-C <path>\" option and its effects on other path-related options'\n>> +\n>> +. ./test-lib.sh\n>> +\n>> +test_expect_success '\"git -C <path>\" runs git from the directory <path>' '\n>> +     test_create_repo dir1 &&\n>> +     echo 1 >dir1/a.txt &&\n>> +     (cd dir1 && git add a.txt && git commit -m \"initial in dir1\") &&\n>\n> Curious why this does not use -C here.\n\nIt didn't use -C there because it's in the \"prepare the expected test\noutput\" stage and we want that to succeed, whether -C works or not - we\nhaven't reached the part where we are actually testing the -C option,\nwhich is right after that:\n\n>> +     echo \"initial in dir1\" >expected &&\n>> +     git -C dir1 log --format=%s >actual &&\n>> +     test_cmp expected actual\n>> +'\n>> +\n>> +test_expect_success 'Multiple -C options: \"-C dir1 -C dir2\" is equivalent to \"-C dir1/dir2\"' '\n>> +     test_create_repo dir1/dir2 &&\n>> +     echo 1 >dir1/dir2/a.txt &&\n>> +     git -C dir1/dir2 add a.txt &&\n>\n> Because \"a.txt\" exists in both dir1 and dir1/dir2, this has less\n> chance of catching a bug (if somebody breaks the feature to run it\n> in dir1 not dir1/dir2, \"add\" will happily say \"Oh, I found something\n> to add\", instead of saying \"Huh? there is no such path\".\n>\n> If you used b.txt instead, you would catch such a breakage.\n>\n> Remember, tests are not about demonstrating how cool the new feature\n> is and/or how well it works in an expected setting.  Imagine ways\n> other people can break your spiffy new feature in later patches, and\n> design tests that are more likely to catch them.\n>\n> The same comment applies throughout the remainder of this script.\n\nNoted, but I my imagination is limited at the moment so I haven't come\nup with new tests to that effect yet ;)\n\n>> +     echo \"initial in dir1/dir2\" >expected &&\n>> +     git -C dir1/dir2 commit -m \"initial in dir1/dir2\" &&\n>\n> to reduce possibilities of breaking this test in the future due to\n> typos (e.g. somebody may want to say \"initial commit in dir1/dir2\"),\n> doing this may be a better idea:\n>\n>         msg=\"initial in dir1/dir2\" &&\n>         echo \"$msg\" >expected &&\n>         git -C dir1/dir2 commit -m \"$msg\" &&\n>\n> The same comment applies to the previous one.\n\nFixed.\n\nnazri\n-- >8 --\nSubject: git: run in a directory given with -C option\n\nThis is similar in spirit to \"make -C dir ...\" and \"tar -C dir ...\".\n\nIt takes more keypresses to invoke git command in a different\ndirectory without leaving the current directory:\n\n    1. (cd ~/foo && git status)\n       git --git-dir=~/foo/.git --work-dir=~/foo status\n       GIT_DIR=~/foo/.git GIT_WORK_TREE=~/foo git status\n    2. (cd ../..; git grep foo)\n    3. for d in d1 d2 d3; do (cd $d && git svn rebase); done\n\nThe methods shown above are acceptable for scripting but are too\ncumbersome for quick command line invocations.\n\nWith this new option, the above can be done with fewer keystrokes:\n\n    1. git -C ~/foo status\n    2. git -C ../.. grep foo\n    3. for d in d1 d2 d3; do git -C $d svn rebase; done\n\nA new test script is added to verify the behavior of this option with\nother path-related options like --git-dir and --work-tree.\n\nSigned-off-by: Nazri Ramliy <ayiehere@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git.txt | 16 +++++++++-\n git.c                 | 13 +++++++-\n t/t0056-git-C.sh      | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 111 insertions(+), 2 deletions(-)\n create mode 100755 t/t0056-git-C.sh\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex c4f0ed5..5d68d33 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -9,7 +9,7 @@ git - the stupid content tracker\n SYNOPSIS\n --------\n [verse]\n-'git' [--version] [--help] [-c <name>=<value>]\n+'git' [--version] [--help] [-C <path>] [-c <name>=<value>]\n     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n     [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\n     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n@@ -395,6 +395,20 @@ displayed. See linkgit:git-help[1] for more information,\n because `git --help ...` is converted internally into `git\n help ...`.\n \n+-C <path>::\n+\tRun as if git was started in '<path>' instead of the current working\n+\tdirectory.  When multiple `-C` options are given, each subsequent\n+\tnon-absolute `-C <path>` is interpreted relative to the preceding `-C\n+\t<path>`.\n++\n+This option affects options that expect path name like `--git-dir` and\n+`--work-tree` in that their interpretations of the path names would be\n+made relative to the working directory caused by the `-C` option. For\n+example the following invocations are equivalent:\n+\n+    git --git-dir=a.git --work-tree=b -C c status\n+    git --git-dir=c/a.git --work-tree=c/b status\n+\n -c <name>=<value>::\n \tPass a configuration parameter to the command. The value\n \tgiven will override values from configuration files.\ndiff --git a/git.c b/git.c\nindex b3893e7..1188979 100644\n--- a/git.c\n+++ b/git.c\n@@ -7,7 +7,7 @@\n #include \"commit.h\"\n \n const char git_usage_string[] =\n-\t\"git [--version] [--help] [-c name=value]\\n\"\n+\t\"git [--version] [--help] [-C <path>] [-c name=value]\\n\"\n \t\"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t\"           [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\\n\"\n \t\"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n@@ -165,6 +165,17 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tset_alternate_shallow_file((*argv)[0]);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strcmp(cmd, \"-C\")) {\n+\t\t\tif (*argc < 2) {\n+\t\t\t\tfprintf(stderr, \"No directory given for -C.\\n\" );\n+\t\t\t\tusage(git_usage_string);\n+\t\t\t}\n+\t\t\tif (chdir((*argv)[1]))\n+\t\t\t\tdie_errno(\"Cannot change to '%s'\", (*argv)[1]);\n+\t\t\tif (envchanged)\n+\t\t\t\t*envchanged = 1;\n+\t\t\t(*argv)++;\n+\t\t\t(*argc)--;\n \t\t} else {\n \t\t\tfprintf(stderr, \"Unknown option: %s\\n\", cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh\nnew file mode 100755\nindex 0000000..99c0377\n--- /dev/null\n+++ b/t/t0056-git-C.sh\n@@ -0,0 +1,84 @@\n+#!/bin/sh\n+\n+test_description='\"-C <path>\" option and its effects on other path-related options'\n+\n+. ./test-lib.sh\n+\n+test_expect_success '\"git -C <path>\" runs git from the directory <path>' '\n+\ttest_create_repo dir1 &&\n+\techo 1 >dir1/a.txt &&\n+\tmsg=\"initial in dir1\" &&\n+\t(cd dir1 && git add a.txt && git commit -m \"$msg\") &&\n+\techo \"$msg\" >expected &&\n+\tgit -C dir1 log --format=%s >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Multiple -C options: \"-C dir1 -C dir2\" is equivalent to \"-C dir1/dir2\"' '\n+\ttest_create_repo dir1/dir2 &&\n+\techo 1 >dir1/dir2/b.txt &&\n+\tgit -C dir1/dir2 add b.txt &&\n+\tmsg=\"initial in dir1/dir2\" &&\n+\techo \"$msg\" >expected &&\n+\tgit -C dir1/dir2 commit -m \"$msg\" &&\n+\tgit -C dir1 -C dir2 log --format=%s >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Effect on --git-dir option: \"-C c --git-dir=a.git\" is equivalent to \"--git-dir c/a.git\"' '\n+\tmkdir c &&\n+\tmkdir c/a &&\n+\tmkdir c/a.git &&\n+\t(cd c/a.git && git init --bare) &&\n+\techo 1 >c/a/a.txt &&\n+\tgit --git-dir c/a.git --work-tree=c/a add a.txt &&\n+\tgit --git-dir c/a.git --work-tree=c/a commit -m \"initial\" &&\n+\tgit --git-dir=c/a.git log -1 --format=%s >expected &&\n+\tgit -C c --git-dir=a.git log -1 --format=%s >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Order should not matter: \"--git-dir=a.git -C c\" is equivalent to \"-C c --git-dir=a.git\"' '\n+\tgit -C c --git-dir=a.git log -1 --format=%s >expected &&\n+\tgit --git-dir=a.git -C c log -1 --format=%s >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Effect on --work-tree option: \"-C c/a.git --work-tree=../a\"  is equivalent to \"--work-tree=c/a --git-dir=c/a.git\"' '\n+\trm c/a/a.txt &&\n+\tgit --git-dir=c/a.git --work-tree=c/a status >expected &&\n+\tgit -C c/a.git --work-tree=../a status >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Order should not matter: \"--work-tree=../a -C c/a.git\" is equivalent to \"-C c/a.git --work-tree=../a\"' '\n+\tgit -C c/a.git --work-tree=../a status >expected &&\n+\tgit --work-tree=../a -C c/a.git status >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Effect on --git-dir and --work-tree options - \"-C c --git-dir=a.git --work-tree=a\" is equivalent to \"--git-dir=c/a.git --work-tree=c/a\"' '\n+\tgit --git-dir=c/a.git --work-tree=c/a status >expected &&\n+\tgit -C c --git-dir=a.git --work-tree=a status >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Order should not matter: \"-C c --git-dir=a.git --work-tree=a\" is equivalent to \"--git-dir=a.git -C c --work-tree=a\"' '\n+\tgit -C c --git-dir=a.git --work-tree=a status >expected &&\n+\tgit --git-dir=a.git -C c --work-tree=a status >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Order should not matter: \"-C c --git-dir=a.git --work-tree=a\" is equivalent to \"--git-dir=a.git --work-tree=a -C c\"' '\n+\tgit -C c --git-dir=a.git --work-tree=a status >expected &&\n+\tgit --git-dir=a.git --work-tree=a -C c status >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'Relative followed by fullpath: \"-C ./here -C /there\" is equivalent to \"-C /there\"' '\n+\techo \"initial in dir1/dir2\" >expected &&\n+\tgit -C dir1 -C \"$(pwd)/dir1/dir2\" log --format=%s >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.8.4.411.g6835a8c\n"},{"id":"227889","messageId":"xmqqob7ou434.fsf@gitster.dls.corp.google.com","threadId":"34967","inReplyTo":"20130919131854.GA19790@gmail.com","subject":"Re: [PATCH v4] Teach git to change to a given directory using -C option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-19T17:13:51Z","receivedAt":"2013-09-19T17:13:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nazri Ramliy <ayiehere@gmail.com> writes:\n\n> Now I'm noticing that you've already made the above fixes in next ;)\n\nYes.  I said I'd locally tweak and queue, didn't I?\n\n>>> +test_expect_success '\"git -C <path>\" runs git from the directory <path>' '\n>>> +     test_create_repo dir1 &&\n>>> +     echo 1 >dir1/a.txt &&\n>>> +     (cd dir1 && git add a.txt && git commit -m \"initial in dir1\") &&\n>>\n>> Curious why this does not use -C here.\n>\n> It didn't use -C there because it's in the \"prepare the expected test\n> output\" stage and we want that to succeed, whether -C works or not - we\n> haven't reached the part where we are actually testing the -C option,\n\nGood thinking.\n\nWill queue as an incremental update on top of what is already in 'next'.\n\nThanks.\n"}]}