{"thread":{"id":"34810","subject":"[PATCH] Teach git to change to a given directory using -C option","startedAt":"2013-08-30T13:35:19Z","lastAt":"2013-09-01T04:48:23Z","messageCount":2,"participants":["Nazri Ramliy","Eric Sunshine"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"226341","messageId":"1377869719-5942-1-git-send-email-ayiehere@gmail.com","threadId":"34810","inReplyTo":null,"subject":"[PATCH] Teach git to change to a given directory using -C option","fromName":"Nazri Ramliy","fromEmail":"ayiehere@gmail.com","sentAt":"2013-08-30T13:35:19Z","receivedAt":"2013-08-30T13:35:19Z","isPatch":true,"sender":{"key":"ayiehere@gmail.com","avatar":"https://avatars.githubusercontent.com/u/164756?v=4"},"body":"This is similar in spirit to to \"make -C dir ...\" and \"tar -C dir ...\".\n\nCurrently it takes more effort (keypresses) to invoke git command in a\ndifferent directory than the current one without leaving the current\ndirectory:\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\nWhile doable the methods shown above are arguably more suitable for\nscripting than quick command line invocations.\n\nWith this new option, the above can be done with less 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>\n---\nThis is a reroll of [1]. The only difference is the rewording of the\ncommit message.  I'm resending this as I've found it to be useful in my\ndaily git usage in that it helps me stay focused on what I'm doing in\nthe current directory while needing to run git on another directory.\n\nnazri.\n\n[1] http://permalink.gmane.org/gmane.comp.version-control.git/221954\n\n Documentation/git.txt | 13 +++++++++\n git.c                 | 15 ++++++++--\n t/t0056-git-C.sh      | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 102 insertions(+), 2 deletions(-)\n create mode 100755 t/t0056-git-C.sh\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex dca11cc..0d44fa2 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -395,6 +395,19 @@ displayed. See linkgit:git-help[1] for more information,\n because `git --help ...` is converted internally into `git\n help ...`.\n \n+-C <directory>::\n+\tRun as if git were started in <directory> instead of the current\n+\tworking directory. If multiple -C options are given, subsequent\n+\tdirectory arguments are interpreted relative to the previous one: -C\n+\t/usr -C src is equivalent to -C /usr/src. This option affects options\n+\tthat expect path name like --git-dir and --work-tree in that their\n+\tinterpretations of the path names would be made relative to the\n+\teffective working directory caused by the -C option. For example the\n+\tfollowing invocations are equivalent:\n+\n+\t    git --git-dir=a.git --work-tree=b -C c status\n+\t    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 2025f77..2207ee5 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 directory] [-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@@ -54,7 +54,18 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t/*\n \t\t * Check remaining flags.\n \t\t */\n-\t\tif (!prefixcmp(cmd, \"--exec-path\")) {\n+\t\tif (!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 if (!prefixcmp(cmd, \"--exec-path\")) {\n \t\t\tcmd += 11;\n \t\t\tif (*cmd == '=')\n \t\t\t\tgit_set_argv_exec_path(cmd + 1);\ndiff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh\nnew file mode 100755\nindex 0000000..370eae6\n--- /dev/null\n+++ b/t/t0056-git-C.sh\n@@ -0,0 +1,76 @@\n+#!/bin/sh\n+\n+test_description='\"-C <directory>\" option and it effects on other path-related options'\n+\n+. ./test-lib.sh\n+\n+test_expect_success '\"git -C <dir>\" runs git from the directory <dir>' '\n+\ttest_create_repo dir1 &&\n+\techo 1 >dir1/a.txt &&\n+\t(cd dir1 && git add a.txt && git commit -m \"initial in dir1\") &&\n+\texpected=\"initial in dir1\" &&\n+\tactual=$(git -C dir1 log --format=%s) &&\n+\ttest \"$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/a.txt &&\n+\tgit -C dir1/dir2 add a.txt &&\n+\texpected=\"initial in dir1/dir2\" &&\n+\tgit -C dir1/dir2 commit -m \"$expected\" &&\n+\tactual=$(git -C dir1 -C dir2 log --format=%s) &&\n+\ttest \"$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+\texpected=\"$(git --git-dir=c/a.git log -1 --format=%s)\" &&\n+\tactual=$(git -C c --git-dir=a.git log -1 --format=%s) &&\n+\ttest \"$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+\texpected=\"$(git -C c --git-dir=a.git log -1 --format=%s)\" &&\n+\tactual=$(git --git-dir=a.git -C c log -1 --format=%s) &&\n+\ttest \"$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+\texpected=\"$(git --git-dir=c/a.git --work-tree=c/a status)\" &&\n+\tactual=\"$(git -C c/a.git --work-tree=../a status)\" &&\n+\ttest \"$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+\texpected=\"$(git -C c/a.git --work-tree=../a status)\" &&\n+\tactual=\"$(git --work-tree=../a -C c/a.git status)\" &&\n+\ttest \"$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+\texpected=\"$(git --git-dir=c/a.git --work-tree=c/a status)\" &&\n+\tactual=\"$(git -C c --git-dir=a.git --work-tree=a status)\" &&\n+\ttest \"$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+\texpected=\"$(git -C c --git-dir=a.git --work-tree=a status)\" &&\n+\tactual=\"$(git --git-dir=a.git -C c --work-tree=a status)\" &&\n+\ttest \"$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+\texpected=\"$(git -C c --git-dir=a.git --work-tree=a status)\" &&\n+\tactual=\"$(git --git-dir=a.git --work-tree=a -C c status)\" &&\n+\ttest \"$expected\" = \"$actual\"\n+'\n+\n+test_done\n-- \n1.8.4.1.g098df5a\n"},{"id":"226462","messageId":"CAPig+cSMZ+uBe=SiTpbfy1kBYRBh60uWZVv5cJhi7tkHMqeTDw@mail.gmail.com","threadId":"34810","inReplyTo":"1377869719-5942-1-git-send-email-ayiehere@gmail.com","subject":"Re: [PATCH] Teach git to change to a given directory using -C option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-09-01T04:48:23Z","receivedAt":"2013-09-01T04:48:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Aug 30, 2013 at 9:35 AM, Nazri Ramliy <ayiehere@gmail.com> wrote:\n> This is similar in spirit to to \"make -C dir ...\" and \"tar -C dir ...\".\n>\n> Currently it takes more effort (keypresses) to invoke git command in a\n> different directory than the current one without leaving the current\n> 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>\n> While doable the methods shown above are arguably more suitable for\n> scripting than quick command line invocations.\n>\n> With this new option, the above can be done with less keystrokes:\n\nGrammar: s/less/fewer/\n\nMore below...\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>\n> A new test script is added to verify the behavior of this option with\n> other path-related options like --git-dir and --work-tree.\n>\n> Signed-off-by: Nazri Ramliy <ayiehere@gmail.com>\n> ---\n> This is a reroll of [1]. The only difference is the rewording of the\n> commit message.  I'm resending this as I've found it to be useful in my\n> daily git usage in that it helps me stay focused on what I'm doing in\n> the current directory while needing to run git on another directory.\n>\n> nazri.\n>\n> [1] http://permalink.gmane.org/gmane.comp.version-control.git/221954\n>\n>  Documentation/git.txt | 13 +++++++++\n>  git.c                 | 15 ++++++++--\n>  t/t0056-git-C.sh      | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 102 insertions(+), 2 deletions(-)\n>  create mode 100755 t/t0056-git-C.sh\n>\n> diff --git a/Documentation/git.txt b/Documentation/git.txt\n> index dca11cc..0d44fa2 100644\n> --- a/Documentation/git.txt\n> +++ b/Documentation/git.txt\n> @@ -395,6 +395,19 @@ displayed. See linkgit:git-help[1] for more information,\n>  because `git --help ...` is converted internally into `git\n>  help ...`.\n>\n> +-C <directory>::\n\nThe synopsis at the top of git.txt mentions --git-dir and --work-tree.\nFor consistency, -C probably ought to be mentioned there, as well.\n\nOther options which accept a directory, such as --git-dir and\n--work-tree, are documented as accepting <path>, but -C is\ninconsistently documented as accepting <directory>.\n\n> +       Run as if git were started in <directory> instead of the current\n> +       working directory. If multiple -C options are given, subsequent\n> +       directory arguments are interpreted relative to the previous one: -C\n> +       /usr -C src is equivalent to -C /usr/src. This option affects options\n\nThe fragment \"interpreted relative\" seems ambiguous when absolute\npaths are involved. For instance, what happens when the user specifies\n\"-C /foo/ -C /bar/\". From the implementation I can see that the\nworking directory becomes /bar, but without checking the\nimplementation, it's not clear what the result would be. For instance,\nif the implementation merely did string concatenation of the -C\narguments, then input \"-C /foo/ -C /bar/\" might try to set the working\ndirectory to \"/foo//bar/\" which would be interpreted as \"/foo/bar/\" on\nUnix, but would probably fail on Windows. Perhaps rewriting might\nremove the ambiguity?\n\n    [...] When multiple -C options are given, each subsequent non-absolute\n    -C <path> is interpreted relative to the preceding -C <path>. [...]\n\n> +       that expect path name like --git-dir and --work-tree in that their\n> +       interpretations of the path names would be made relative to the\n> +       effective working directory caused by the -C option. For example the\n> +       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>         Pass a configuration parameter to the command. The value\n>         given will override values from configuration files.\n> diff --git a/git.c b/git.c\n> index 2025f77..2207ee5 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> -       \"git [--version] [--help] [-c name=value]\\n\"\n> +       \"git [--version] [--help] [-C directory] [-c name=value]\\n\"\n>         \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n>         \"           [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\\n\"\n>         \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n\nFor existing options accepting an argument, the argument is formatted\nas <argument>. The -C option does not follow suit.\n\nAs mentioned above, all other options accepting a directory are\ndocumented as taking <path>, but -C is inconsistent and is documented\nas taking 'directory' instead.\n\n> @@ -54,7 +54,18 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n>                 /*\n>                  * Check remaining flags.\n>                  */\n> -               if (!prefixcmp(cmd, \"--exec-path\")) {\n> +               if (!strcmp(cmd, \"-C\")) {\n> +                       if (*argc < 2) {\n> +                               fprintf(stderr, \"No directory given for -C.\\n\" );\n> +                               usage(git_usage_string);\n> +                       }\n> +                       if (chdir((*argv)[1]))\n> +                               die_errno(\"Cannot change to '%s'\", (*argv)[1]);\n> +                       if (envchanged)\n> +                               *envchanged = 1;\n> +                       (*argv)++;\n> +                       (*argc)--;\n> +               } else if (!prefixcmp(cmd, \"--exec-path\")) {\n>                         cmd += 11;\n>                         if (*cmd == '=')\n>                                 git_set_argv_exec_path(cmd + 1);\n> diff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh\n> new file mode 100755\n> index 0000000..370eae6\n> --- /dev/null\n> +++ b/t/t0056-git-C.sh\n> @@ -0,0 +1,76 @@\n> +#!/bin/sh\n> +\n> +test_description='\"-C <directory>\" option and it effects on other path-related options'\n\ns/it/its/\ns/<directory>/<path>/\n\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success '\"git -C <dir>\" runs git from the directory <dir>' '\n\ns/<dir>/<path>/g\n\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> +       expected=\"initial in dir1\" &&\n> +       actual=$(git -C dir1 log --format=%s) &&\n> +       test \"$expected\" = \"$actual\"\n> +'\n\nModern git tests tend to place the expected and actual outputs in\nfiles and then use test_cmp to verify that they are identical. For\ninstance:\n\n    echo \"initial in dir1\" >expected &&\n    git -C dir1 log --format=\"%s\" >actual &&\n    test_cmp expected actual\n\nThe benefit of doing so is that test_cmp will print the difference\nbetween the expected and actual values when they differ, which\nfacilitates debugging a failed test.\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> +       expected=\"initial in dir1/dir2\" &&\n> +       git -C dir1/dir2 commit -m \"$expected\" &&\n> +       actual=$(git -C dir1 -C dir2 log --format=%s) &&\n> +       test \"$expected\" = \"$actual\"\n> +'\n\nIt would make sense also to test multiple -C options with combinations\nof absolute and and relative paths.\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> +       mkdir c &&\n> +       mkdir c/a &&\n> +       mkdir c/a.git &&\n> +       (cd c/a.git && git init --bare) &&\n> +       echo 1 >c/a/a.txt &&\n> +       git --git-dir c/a.git --work-tree=c/a add a.txt &&\n> +       git --git-dir c/a.git --work-tree=c/a commit -m \"initial\" &&\n> +       expected=\"$(git --git-dir=c/a.git log -1 --format=%s)\" &&\n> +       actual=$(git -C c --git-dir=a.git log -1 --format=%s) &&\n> +       test \"$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> +       expected=\"$(git -C c --git-dir=a.git log -1 --format=%s)\" &&\n> +       actual=$(git --git-dir=a.git -C c log -1 --format=%s) &&\n> +       test \"$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> +       rm c/a/a.txt &&\n> +       expected=\"$(git --git-dir=c/a.git --work-tree=c/a status)\" &&\n> +       actual=\"$(git -C c/a.git --work-tree=../a status)\" &&\n> +       test \"$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> +       expected=\"$(git -C c/a.git --work-tree=../a status)\" &&\n> +       actual=\"$(git --work-tree=../a -C c/a.git status)\" &&\n> +       test \"$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> +       expected=\"$(git --git-dir=c/a.git --work-tree=c/a status)\" &&\n> +       actual=\"$(git -C c --git-dir=a.git --work-tree=a status)\" &&\n> +       test \"$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> +       expected=\"$(git -C c --git-dir=a.git --work-tree=a status)\" &&\n> +       actual=\"$(git --git-dir=a.git -C c --work-tree=a status)\" &&\n> +       test \"$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> +       expected=\"$(git -C c --git-dir=a.git --work-tree=a status)\" &&\n> +       actual=\"$(git --git-dir=a.git --work-tree=a -C c status)\" &&\n> +       test \"$expected\" = \"$actual\"\n> +'\n> +\n> +test_done\n> --\n> 1.8.4.1.g098df5a\n"}]}