{"thread":{"id":"33544","subject":"[PATCH] Teach git to change to a given directory using -C option","startedAt":"2013-04-19T12:21:48Z","lastAt":"2013-04-21T13:48:46Z","messageCount":5,"participants":["Nazri Ramliy","Jeff King","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"214829","messageId":"1366374108-23725-1-git-send-email-ayiehere@gmail.com","threadId":"33544","inReplyTo":null,"subject":"[PATCH] Teach git to change to a given directory using -C option","fromName":"Nazri Ramliy","fromEmail":"ayiehere@gmail.com","sentAt":"2013-04-19T12:21:48Z","receivedAt":"2013-04-19T12:21:48Z","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\nSigned-off-by: Nazri Ramliy <ayiehere@gmail.com>\n---\nOften I find myself needing to find out quickly the status of a repository that\nis not in my currenct working directory, like this:\n\n         $ (cd ~/foo; git log -1)\n\nWith this patch now i can simply do:\n\n         $ git -C ~/.zsh log -1 \n\nThat's just one example. I think those who are familiar with the -C arguments\nto \"make\" and \"tar\" commands would get the \"handiness\" of having this option in\ngit.\n\n Documentation/git.txt |  3 +++\n git.c                 | 11 ++++++++++-\n t/t0050-filesystem.sh |  9 +++++++++\n 3 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 6a875f2..20bba86 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -379,6 +379,9 @@ 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+\tChange to given directory before doing anything else.\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 1ada169..6426a2e 100644\n--- a/git.c\n+++ b/git.c\n@@ -53,7 +53,16 @@ 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 (!prefixcmp(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\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/t0050-filesystem.sh b/t/t0050-filesystem.sh\nindex 05d78d2..ef1cb75 100755\n--- a/t/t0050-filesystem.sh\n+++ b/t/t0050-filesystem.sh\n@@ -88,6 +88,15 @@ test_expect_failure CASE_INSENSITIVE_FS 'add (with different case)' '\n \ttest \"z$(git cat-file blob :$camel)\" = z1\n '\n \n+test_expect_success 'git -C <dir> changes directory to <dir>' '\n+\ttest_create_repo dir1 &&\n+\techo 1 >dir1/a.txt &&\n+\tgit -C dir1 add a.txt &&\n+\tgit -C dir1 commit -m \"initial in dir1\" &&\n+\tt1=$(git -C dir1 log --format=%s) &&\n+\ttest \"$t1\" = \"initial in dir1\"\n+'\n+\n test_expect_success \"setup unicode normalization tests\" '\n \ttest_create_repo unicode &&\n \tcd unicode &&\n-- \n1.8.2.1.339.g52a3e01\n"},{"id":"214834","messageId":"20130419161250.GC14263@sigill.intra.peff.net","threadId":"33544","inReplyTo":"1366374108-23725-1-git-send-email-ayiehere@gmail.com","subject":"Re: [PATCH] Teach git to change to a given directory using -C option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-04-19T16:12:50Z","receivedAt":"2013-04-19T16:12:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Apr 19, 2013 at 08:21:48PM +0800, Nazri Ramliy wrote:\n\n> This is similar in spirit to to \"make -C dir ...\" and \"tar -C dir ...\".\n> \n> Signed-off-by: Nazri Ramliy <ayiehere@gmail.com>\n> ---\n> Often I find myself needing to find out quickly the status of a repository that\n> is not in my currenct working directory, like this:\n> \n>          $ (cd ~/foo; git log -1)\n> \n> With this patch now i can simply do:\n> \n>          $ git -C ~/.zsh log -1 \n> \n> That's just one example. I think those who are familiar with the -C arguments\n> to \"make\" and \"tar\" commands would get the \"handiness\" of having this option in\n> git.\n\nThis motivation should probably go into the commit message.\n\nI think it's worth pausing for a moment and considering if we can do\nthis already with existing features.\n\nYou can _almost_ do this with \"git --git-dir\". But it expects the actual\ngit directory, not a starting point for finding the git directory. And\nit remains in your same working dir. So with a bare repository, these\ntwo are equivalent:\n\n  $ git --git-dir=/path/to/foo.git ...\n  $ git -C /path/to/foo.git ...\n\nBut with a non-bare repo, this does not work:\n\n  $ git --git-dir=/path/to/non-bare ...\n\nYou must instead say:\n\n  $ git --git-dir=/path/to/non-bare/.git ...\n\nand even then, I think it will treat your current directory as the\nworking tree, not /path/to/non-bare.\n\nSo I think \"-C\" is a worthwhile addition compared to just \"--git-dir\".\n\nIt is redundant with \"(cd foo && git ...)\" in the shell, as you note,\nbut sometimes it is more convenient to use \"-C\" (especially if you are\nexec-ing git from another program and want to avoid the shell entirely\nfor quoting reasons).\n\n> diff --git a/Documentation/git.txt b/Documentation/git.txt\n> index 6a875f2..20bba86 100644\n> --- a/Documentation/git.txt\n> +++ b/Documentation/git.txt\n> @@ -379,6 +379,9 @@ 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> +\tChange to given directory before doing anything else.\n> +\n\nIt might make sense to clarify this as \"...anything else, including\ndetermining the location of the git repository directory\". If you think\nhard about it, doing anything else would not really make much sense, but\nspelling it out makes it clear what the option can be used for.\n\n> +\t\tif (!prefixcmp(cmd, \"-C\")) {\n\nShould this be strcmp? You do not seem to handle \"-Cfoo\" below.\n\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\nI know you are copying this from the other options in the same function,\nbut I wonder if they should all be calling \"error()\" (and dropping the\nterminating \".\") to better match our usual error messages.\n\n> +\t\t\tif (chdir((*argv)[1]))\n> +\t\t\t\tdie_errno(\"Cannot change to '%s'\", (*argv)[1]);\n> +\t\t\t(*argv)++;\n> +\t\t\t(*argc)--;\n\nYou would want to set \"*envchanged = 1\" here. The intent of that flag is\nthat git would need to throw away things it has looked up already (like\nthe git dir) in order to correctly utilize the options (and since we\nhaven't implemented that \"throw away\" step, it just complains and dies).\n\nI didn't try it, but I suspect your patch would be broken with:\n\n  $ git config alias.logfoo '-C /path/to/foo log'\n  $ cd /some/other/repo\n  $ git logfoo\n\nIt would still use /some/other/repo as a $GIT_DIR, having looked it up\nbefore processing the \"-C\".\n\n-Peff\n"},{"id":"214944","messageId":"20130420221752.GA9980@elie.Belkin","threadId":"33544","inReplyTo":"20130419161250.GC14263@sigill.intra.peff.net","subject":"Re: [PATCH] Teach git to change to a given directory using -C option","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-04-20T22:18:38Z","receivedAt":"2013-04-20T22:18:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jeff King wrote:\n> On Fri, Apr 19, 2013 at 08:21:48PM +0800, Nazri Ramliy wrote:\n\n>> Often I find myself needing to find out quickly the status of a repository that\n>> is not in my currenct working directory, like this:\n>>\n>>          $ (cd ~/foo; git log -1)\n>>\n>> With this patch now i can simply do:\n>>\n>>          $ git -C ~/.zsh log -1 \n>> \n>> That's just one example.\n[...]\n> You can _almost_ do this with \"git --git-dir\". But it expects the actual\n> git directory, not a starting point for finding the git directory.\n[...]\n> It is redundant with \"(cd foo && git ...)\" in the shell, as you note,\n> but sometimes it is more convenient to use \"-C\" (especially if you are\n> exec-ing git from another program and want to avoid the shell entirely\n> for quoting reasons).\n\nWhen I want to run \"git log\" for a repository outside the cwd, I do\nuse --git-dir (or more precisely,\n\n\t$ GIT_DIR=$HOME/src/git/.git git log\n\n), which works.\n\nThe \"sometimes you just want to pass a command to 'exec'\" use case\ndoes not convince me.  I equally well might want to run \"git\" after\nanother command, or run \"git\" if and only if a repository exists\nthere, or do any number of other things.  If someone asked me how to\ndo that by passing a command to 'exec', I'd point them to\n\n\tsh -c 'cd foo && git ...'\n\nas a way to answer all such questions at the same time.  So we're left\nwith \"--git-dir does not automatically append .git when appropriate\"\nas the problem being solved, which is a real problem.  Maybe that is\nworth fixing more directly?\n\nIt might also be convenient to be able to do something like\n\n\tgit --git-dir=~/src/git log -- Documentation/\n\nwhich this -C option makes easy.  *checks*  Actually it works without,\nbut for subtle reasons.  A more sensible way to spell that is\n\n\tgit --git-dir=<wherever> -- :/Documentation/\n\nwhich works fine.\n\nAll that said, I don't mind -C terribly as long as it can maintain\nitself, which means including thorough documentation that covers the\npurpose and how pathname parameters and envvars interact with the new\noption and including tests under t/ to ensure it continues to work\ncorrectly in the future.\n\nThanks for an interesting patch, and hope that helps,\nJonathan\n"},{"id":"214955","messageId":"20130421035857.GC18890@sigill.intra.peff.net","threadId":"33544","inReplyTo":"20130420221752.GA9980@elie.Belkin","subject":"Re: [PATCH] Teach git to change to a given directory using -C option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-04-21T03:58:58Z","receivedAt":"2013-04-21T03:58:58Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Apr 20, 2013 at 03:18:38PM -0700, Jonathan Nieder wrote:\n\n> The \"sometimes you just want to pass a command to 'exec'\" use case\n> does not convince me.  I equally well might want to run \"git\" after\n> another command, or run \"git\" if and only if a repository exists\n> there, or do any number of other things.\n\nSure. I don't claim that it solves every problem, just that I have\nwanted it in that situation before.\n\n> So we're left with \"--git-dir does not automatically append .git when\n> appropriate\" as the problem being solved, which is a real problem.\n> Maybe that is worth fixing more directly?\n\nI'm a little hesitant, because --git-dir is _not_ \"pretend like I am in\ndirectory X\". Even though people may use it that way for bare\nrepositories, it explicitly does not change your working tree.\n\nI'm not sure what rule you are proposing. If it is:\n\n  1. When we get \"--git-dir=a/b\", look in \"a/b/.git\" (assuming a/b is\n     not a repo itself).\n\n  2. When we get \"--git-dir=a/b\", do the usual repo search from a/b,\n     finding the first of \"a/b\", \"a/b/.git\", \"a/.git\".\n\nThe second one is what makes me nervous, as it seems too much like\n\"pretend that we are in a/b\". But the first one seems kind of hack-ish.\nI suppose it is similar to the enter_repo rule used to find remotes,\nthough, so at least there is some precedence.\n\n> It might also be convenient to be able to do something like\n> \n> \tgit --git-dir=~/src/git log -- Documentation/\n> \n> which this -C option makes easy.  *checks*  Actually it works without,\n> but for subtle reasons.\n\nI'm not sure what subtle reason that is. It does not seem to work for\nme:\n\n  $ (cd git && git log -- Documentation | wc -l)\n  99152\n  $ git --git-dir=git log -- Documentation | wc -l\n  fatal: Not a git repository: 'git'\n  0\n\nA more interesting subtlety is this:\n\n  $ git --git-dir=git/.git log -- Documentation | wc -l\n  99152\n  $ git --git-dir=git/.git log Documentation | wc -l\n  fatal: ambiguous argument 'Documentation': unknown revision or path not in the working tree.\n  Use '--' to separate paths from revisions, like this:\n  'git <command> [<revision>...] -- [<file>...]'\n  0\n\n> All that said, I don't mind -C terribly as long as it can maintain\n> itself, which means including thorough documentation that covers the\n> purpose and how pathname parameters and envvars interact with the new\n> option and including tests under t/ to ensure it continues to work\n> correctly in the future.\n\nYeah, I pretty much feel the same way. \"git -C\" is a concept that has\noccurred to me several times over the years, and I always dismissed it\nas \"bah, you can do the same thing easily with one line of shell\". It\nmakes sense to me because of the precedence in other programs and I\nwould probably use it, but I could also live without it. I do not mind\nit if it is not a maintenance burden.\n\n-Peff\n"},{"id":"215019","messageId":"1366552126-6090-1-git-send-email-ayiehere@gmail.com","threadId":"33544","inReplyTo":"20130419161250.GC14263@sigill.intra.peff.net","subject":"[PATCH] Teach git to change to a given directory using -C option","fromName":"Nazri Ramliy","fromEmail":"ayiehere@gmail.com","sentAt":"2013-04-21T13:48:46Z","receivedAt":"2013-04-21T13:48:46Z","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 finding out the status of a git repository that is located\naway from the current working directory without going to that directory\ncan be done in the following ways:\n\n    1. (cd ~/foo && git status)\n    2. git --git-dir=~/foo/.git --work-dir=~/foo status\n    3. GIT_DIR=~/foo/.git GIT_WORK_TREE=~/foo git status\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 a bit more tersely:\n\n    $ git -C ~/foo status\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---\nJeff: Thanks for pointing out the mistakes.\n\nBut I did not address your concern:\n> I know you are copying this from the other options in the same function,\n> but I wonder if they should all be calling \"error()\" (and dropping the\n> terminating \".\") to better match our usual error messages.\n\nbecause I'd rather have that fix be done in a separate topic.\n\nThe other points raised are all valid and fixed in this new patch.\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 6a875f2..6064b3d 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -379,6 +379,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 1ada169..a8731e9 100644\n--- a/git.c\n+++ b/git.c\n@@ -6,7 +6,7 @@\n #include \"run-command.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@@ -53,7 +53,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.2.1.339.g52a3e01\n"}]}