All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Teach git to change to a given directory using -C option
@ 2013-04-19 12:21 Nazri Ramliy
  2013-04-19 16:12 ` Jeff King
  0 siblings, 1 reply; 7+ messages in thread
From: Nazri Ramliy @ 2013-04-19 12:21 UTC (permalink / raw)
  To: git; +Cc: Nazri Ramliy

This is similar in spirit to to "make -C dir ..." and "tar -C dir ...".

Signed-off-by: Nazri Ramliy <ayiehere@gmail.com>
---
Often I find myself needing to find out quickly the status of a repository that
is not in my currenct working directory, like this:

         $ (cd ~/foo; git log -1)

With this patch now i can simply do:

         $ git -C ~/.zsh log -1 

That's just one example. I think those who are familiar with the -C arguments
to "make" and "tar" commands would get the "handiness" of having this option in
git.

 Documentation/git.txt |  3 +++
 git.c                 | 11 ++++++++++-
 t/t0050-filesystem.sh |  9 +++++++++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/Documentation/git.txt b/Documentation/git.txt
index 6a875f2..20bba86 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -379,6 +379,9 @@ displayed. See linkgit:git-help[1] for more information,
 because `git --help ...` is converted internally into `git
 help ...`.
 
+-C <directory>::
+	Change to given directory before doing anything else.
+
 -c <name>=<value>::
 	Pass a configuration parameter to the command. The value
 	given will override values from configuration files.
diff --git a/git.c b/git.c
index 1ada169..6426a2e 100644
--- a/git.c
+++ b/git.c
@@ -53,7 +53,16 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 		/*
 		 * Check remaining flags.
 		 */
-		if (!prefixcmp(cmd, "--exec-path")) {
+		if (!prefixcmp(cmd, "-C")) {
+			if (*argc < 2) {
+				fprintf(stderr, "No directory given for -C.\n" );
+				usage(git_usage_string);
+			}
+			if (chdir((*argv)[1]))
+				die_errno("Cannot change to '%s'", (*argv)[1]);
+			(*argv)++;
+			(*argc)--;
+		} else if (!prefixcmp(cmd, "--exec-path")) {
 			cmd += 11;
 			if (*cmd == '=')
 				git_set_argv_exec_path(cmd + 1);
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index 05d78d2..ef1cb75 100755
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
@@ -88,6 +88,15 @@ test_expect_failure CASE_INSENSITIVE_FS 'add (with different case)' '
 	test "z$(git cat-file blob :$camel)" = z1
 '
 
+test_expect_success 'git -C <dir> changes directory to <dir>' '
+	test_create_repo dir1 &&
+	echo 1 >dir1/a.txt &&
+	git -C dir1 add a.txt &&
+	git -C dir1 commit -m "initial in dir1" &&
+	t1=$(git -C dir1 log --format=%s) &&
+	test "$t1" = "initial in dir1"
+'
+
 test_expect_success "setup unicode normalization tests" '
 	test_create_repo unicode &&
 	cd unicode &&
-- 
1.8.2.1.339.g52a3e01

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] Teach git to change to a given directory using -C option
  2013-04-19 12:21 [PATCH] Teach git to change to a given directory using -C option Nazri Ramliy
@ 2013-04-19 16:12 ` Jeff King
  2013-04-20 22:18   ` Jonathan Nieder
  2013-04-21 13:48   ` Nazri Ramliy
  0 siblings, 2 replies; 7+ messages in thread
From: Jeff King @ 2013-04-19 16:12 UTC (permalink / raw)
  To: Nazri Ramliy; +Cc: git

On Fri, Apr 19, 2013 at 08:21:48PM +0800, Nazri Ramliy wrote:

> This is similar in spirit to to "make -C dir ..." and "tar -C dir ...".
> 
> Signed-off-by: Nazri Ramliy <ayiehere@gmail.com>
> ---
> Often I find myself needing to find out quickly the status of a repository that
> is not in my currenct working directory, like this:
> 
>          $ (cd ~/foo; git log -1)
> 
> With this patch now i can simply do:
> 
>          $ git -C ~/.zsh log -1 
> 
> That's just one example. I think those who are familiar with the -C arguments
> to "make" and "tar" commands would get the "handiness" of having this option in
> git.

This motivation should probably go into the commit message.

I think it's worth pausing for a moment and considering if we can do
this already with existing features.

You can _almost_ do this with "git --git-dir". But it expects the actual
git directory, not a starting point for finding the git directory. And
it remains in your same working dir. So with a bare repository, these
two are equivalent:

  $ git --git-dir=/path/to/foo.git ...
  $ git -C /path/to/foo.git ...

But with a non-bare repo, this does not work:

  $ git --git-dir=/path/to/non-bare ...

You must instead say:

  $ git --git-dir=/path/to/non-bare/.git ...

and even then, I think it will treat your current directory as the
working tree, not /path/to/non-bare.

So I think "-C" is a worthwhile addition compared to just "--git-dir".

It is redundant with "(cd foo && git ...)" in the shell, as you note,
but sometimes it is more convenient to use "-C" (especially if you are
exec-ing git from another program and want to avoid the shell entirely
for quoting reasons).

> diff --git a/Documentation/git.txt b/Documentation/git.txt
> index 6a875f2..20bba86 100644
> --- a/Documentation/git.txt
> +++ b/Documentation/git.txt
> @@ -379,6 +379,9 @@ displayed. See linkgit:git-help[1] for more information,
>  because `git --help ...` is converted internally into `git
>  help ...`.
>  
> +-C <directory>::
> +	Change to given directory before doing anything else.
> +

It might make sense to clarify this as "...anything else, including
determining the location of the git repository directory". If you think
hard about it, doing anything else would not really make much sense, but
spelling it out makes it clear what the option can be used for.

> +		if (!prefixcmp(cmd, "-C")) {

Should this be strcmp? You do not seem to handle "-Cfoo" below.

> +			if (*argc < 2) {
> +				fprintf(stderr, "No directory given for -C.\n" );
> +				usage(git_usage_string);
> +			}

I know you are copying this from the other options in the same function,
but I wonder if they should all be calling "error()" (and dropping the
terminating ".") to better match our usual error messages.

> +			if (chdir((*argv)[1]))
> +				die_errno("Cannot change to '%s'", (*argv)[1]);
> +			(*argv)++;
> +			(*argc)--;

You would want to set "*envchanged = 1" here. The intent of that flag is
that git would need to throw away things it has looked up already (like
the git dir) in order to correctly utilize the options (and since we
haven't implemented that "throw away" step, it just complains and dies).

I didn't try it, but I suspect your patch would be broken with:

  $ git config alias.logfoo '-C /path/to/foo log'
  $ cd /some/other/repo
  $ git logfoo

It would still use /some/other/repo as a $GIT_DIR, having looked it up
before processing the "-C".

-Peff

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] Teach git to change to a given directory using -C option
  2013-04-19 16:12 ` Jeff King
@ 2013-04-20 22:18   ` Jonathan Nieder
  2013-04-21  3:58     ` Jeff King
  2013-04-21 13:48   ` Nazri Ramliy
  1 sibling, 1 reply; 7+ messages in thread
From: Jonathan Nieder @ 2013-04-20 22:18 UTC (permalink / raw)
  To: Jeff King; +Cc: Nazri Ramliy, git

Jeff King wrote:
> On Fri, Apr 19, 2013 at 08:21:48PM +0800, Nazri Ramliy wrote:

>> Often I find myself needing to find out quickly the status of a repository that
>> is not in my currenct working directory, like this:
>>
>>          $ (cd ~/foo; git log -1)
>>
>> With this patch now i can simply do:
>>
>>          $ git -C ~/.zsh log -1 
>> 
>> That's just one example.
[...]
> You can _almost_ do this with "git --git-dir". But it expects the actual
> git directory, not a starting point for finding the git directory.
[...]
> It is redundant with "(cd foo && git ...)" in the shell, as you note,
> but sometimes it is more convenient to use "-C" (especially if you are
> exec-ing git from another program and want to avoid the shell entirely
> for quoting reasons).

When I want to run "git log" for a repository outside the cwd, I do
use --git-dir (or more precisely,

	$ GIT_DIR=$HOME/src/git/.git git log

), which works.

The "sometimes you just want to pass a command to 'exec'" use case
does not convince me.  I equally well might want to run "git" after
another command, or run "git" if and only if a repository exists
there, or do any number of other things.  If someone asked me how to
do that by passing a command to 'exec', I'd point them to

	sh -c 'cd foo && git ...'

as a way to answer all such questions at the same time.  So we're left
with "--git-dir does not automatically append .git when appropriate"
as the problem being solved, which is a real problem.  Maybe that is
worth fixing more directly?

It might also be convenient to be able to do something like

	git --git-dir=~/src/git log -- Documentation/

which this -C option makes easy.  *checks*  Actually it works without,
but for subtle reasons.  A more sensible way to spell that is

	git --git-dir=<wherever> -- :/Documentation/

which works fine.

All that said, I don't mind -C terribly as long as it can maintain
itself, which means including thorough documentation that covers the
purpose and how pathname parameters and envvars interact with the new
option and including tests under t/ to ensure it continues to work
correctly in the future.

Thanks for an interesting patch, and hope that helps,
Jonathan

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] Teach git to change to a given directory using -C option
  2013-04-20 22:18   ` Jonathan Nieder
@ 2013-04-21  3:58     ` Jeff King
  0 siblings, 0 replies; 7+ messages in thread
From: Jeff King @ 2013-04-21  3:58 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Nazri Ramliy, git

On Sat, Apr 20, 2013 at 03:18:38PM -0700, Jonathan Nieder wrote:

> The "sometimes you just want to pass a command to 'exec'" use case
> does not convince me.  I equally well might want to run "git" after
> another command, or run "git" if and only if a repository exists
> there, or do any number of other things.

Sure. I don't claim that it solves every problem, just that I have
wanted it in that situation before.

> So we're left with "--git-dir does not automatically append .git when
> appropriate" as the problem being solved, which is a real problem.
> Maybe that is worth fixing more directly?

I'm a little hesitant, because --git-dir is _not_ "pretend like I am in
directory X". Even though people may use it that way for bare
repositories, it explicitly does not change your working tree.

I'm not sure what rule you are proposing. If it is:

  1. When we get "--git-dir=a/b", look in "a/b/.git" (assuming a/b is
     not a repo itself).

  2. When we get "--git-dir=a/b", do the usual repo search from a/b,
     finding the first of "a/b", "a/b/.git", "a/.git".

The second one is what makes me nervous, as it seems too much like
"pretend that we are in a/b". But the first one seems kind of hack-ish.
I suppose it is similar to the enter_repo rule used to find remotes,
though, so at least there is some precedence.

> It might also be convenient to be able to do something like
> 
> 	git --git-dir=~/src/git log -- Documentation/
> 
> which this -C option makes easy.  *checks*  Actually it works without,
> but for subtle reasons.

I'm not sure what subtle reason that is. It does not seem to work for
me:

  $ (cd git && git log -- Documentation | wc -l)
  99152
  $ git --git-dir=git log -- Documentation | wc -l
  fatal: Not a git repository: 'git'
  0

A more interesting subtlety is this:

  $ git --git-dir=git/.git log -- Documentation | wc -l
  99152
  $ git --git-dir=git/.git log Documentation | wc -l
  fatal: ambiguous argument 'Documentation': unknown revision or path not in the working tree.
  Use '--' to separate paths from revisions, like this:
  'git <command> [<revision>...] -- [<file>...]'
  0

> All that said, I don't mind -C terribly as long as it can maintain
> itself, which means including thorough documentation that covers the
> purpose and how pathname parameters and envvars interact with the new
> option and including tests under t/ to ensure it continues to work
> correctly in the future.

Yeah, I pretty much feel the same way. "git -C" is a concept that has
occurred to me several times over the years, and I always dismissed it
as "bah, you can do the same thing easily with one line of shell". It
makes sense to me because of the precedence in other programs and I
would probably use it, but I could also live without it. I do not mind
it if it is not a maintenance burden.

-Peff

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH] Teach git to change to a given directory using -C option
  2013-04-19 16:12 ` Jeff King
  2013-04-20 22:18   ` Jonathan Nieder
@ 2013-04-21 13:48   ` Nazri Ramliy
  1 sibling, 0 replies; 7+ messages in thread
From: Nazri Ramliy @ 2013-04-21 13:48 UTC (permalink / raw)
  To: peff, git; +Cc: Nazri Ramliy

This is similar in spirit to to "make -C dir ..." and "tar -C dir ...".

Currently finding out the status of a git repository that is located
away from the current working directory without going to that directory
can be done in the following ways:

    1. (cd ~/foo && git status)
    2. git --git-dir=~/foo/.git --work-dir=~/foo status
    3. GIT_DIR=~/foo/.git GIT_WORK_TREE=~/foo git status

While doable the methods shown above are arguably more suitable for
scripting than quick command line invocations.

With this new option, the above can be done a bit more tersely:

    $ git -C ~/foo status

A new test script is added to verify the behavior of this option with
other path-related options like --git-dir and --work-tree.

Signed-off-by: Nazri Ramliy <ayiehere@gmail.com>
---
Jeff: Thanks for pointing out the mistakes.

But I did not address your concern:
> I know you are copying this from the other options in the same function,
> but I wonder if they should all be calling "error()" (and dropping the
> terminating ".") to better match our usual error messages.

because I'd rather have that fix be done in a separate topic.

The other points raised are all valid and fixed in this new patch.

 Documentation/git.txt | 13 +++++++++
 git.c                 | 15 ++++++++--
 t/t0056-git-C.sh      | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+), 2 deletions(-)
 create mode 100755 t/t0056-git-C.sh

diff --git a/Documentation/git.txt b/Documentation/git.txt
index 6a875f2..6064b3d 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -379,6 +379,19 @@ displayed. See linkgit:git-help[1] for more information,
 because `git --help ...` is converted internally into `git
 help ...`.
 
+-C <directory>::
+	Run as if git were started in <directory> instead of the current
+	working directory. If multiple -C options are given, subsequent
+	directory arguments are interpreted relative to the previous one: -C
+	/usr -C src is equivalent to -C /usr/src. This option affects options
+	that expect path name like --git-dir and --work-tree in that their
+	interpretations of the path names would be made relative to the
+	effective working directory caused by the -C option. For example the
+	following invocations are equivalent:
+
+	    git --git-dir=a.git --work-tree=b -C c status
+	    git --git-dir=c/a.git --work-tree=c/b status
+
 -c <name>=<value>::
 	Pass a configuration parameter to the command. The value
 	given will override values from configuration files.
diff --git a/git.c b/git.c
index 1ada169..a8731e9 100644
--- a/git.c
+++ b/git.c
@@ -6,7 +6,7 @@
 #include "run-command.h"
 
 const char git_usage_string[] =
-	"git [--version] [--help] [-c name=value]\n"
+	"git [--version] [--help] [-C directory] [-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"
@@ -53,7 +53,18 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 		/*
 		 * Check remaining flags.
 		 */
-		if (!prefixcmp(cmd, "--exec-path")) {
+		if (!strcmp(cmd, "-C")) {
+			if (*argc < 2) {
+				fprintf(stderr, "No directory given for -C.\n" );
+				usage(git_usage_string);
+			}
+			if (chdir((*argv)[1]))
+				die_errno("Cannot change to '%s'", (*argv)[1]);
+			if (envchanged)
+				*envchanged = 1;
+			(*argv)++;
+			(*argc)--;
+		} else if (!prefixcmp(cmd, "--exec-path")) {
 			cmd += 11;
 			if (*cmd == '=')
 				git_set_argv_exec_path(cmd + 1);
diff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh
new file mode 100755
index 0000000..370eae6
--- /dev/null
+++ b/t/t0056-git-C.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='"-C <directory>" option and it effects on other path-related options'
+
+. ./test-lib.sh
+
+test_expect_success '"git -C <dir>" runs git from the directory <dir>' '
+	test_create_repo dir1 &&
+	echo 1 >dir1/a.txt &&
+	(cd dir1 && git add a.txt && git commit -m "initial in dir1") &&
+	expected="initial in dir1" &&
+	actual=$(git -C dir1 log --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Multiple -C options: "-C dir1 -C dir2" is equivalent to "-C dir1/dir2"' '
+	test_create_repo dir1/dir2 &&
+	echo 1 >dir1/dir2/a.txt &&
+	git -C dir1/dir2 add a.txt &&
+	expected="initial in dir1/dir2" &&
+	git -C dir1/dir2 commit -m "$expected" &&
+	actual=$(git -C dir1 -C dir2 log --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Effect on --git-dir option: "-C c --git-dir=a.git" is equivalent to "--git-dir c/a.git"' '
+	mkdir c &&
+	mkdir c/a &&
+	mkdir c/a.git &&
+	(cd c/a.git && git init --bare) &&
+	echo 1 >c/a/a.txt &&
+	git --git-dir c/a.git --work-tree=c/a add a.txt &&
+	git --git-dir c/a.git --work-tree=c/a commit -m "initial" &&
+	expected="$(git --git-dir=c/a.git log -1 --format=%s)" &&
+	actual=$(git -C c --git-dir=a.git log -1 --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Order should not matter: "--git-dir=a.git -C c" is equivalent to "-C c --git-dir=a.git"' '
+	expected="$(git -C c --git-dir=a.git log -1 --format=%s)" &&
+	actual=$(git --git-dir=a.git -C c log -1 --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	rm c/a/a.txt &&
+	expected="$(git --git-dir=c/a.git --work-tree=c/a status)" &&
+	actual="$(git -C c/a.git --work-tree=../a status)" &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Order should not matter: "--work-tree=../a -C c/a.git" is equivalent to "-C c/a.git --work-tree=../a"' '
+	expected="$(git -C c/a.git --work-tree=../a status)" &&
+	actual="$(git --work-tree=../a -C c/a.git status)" &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	expected="$(git --git-dir=c/a.git --work-tree=c/a status)" &&
+	actual="$(git -C c --git-dir=a.git --work-tree=a status)" &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	expected="$(git -C c --git-dir=a.git --work-tree=a status)" &&
+	actual="$(git --git-dir=a.git -C c --work-tree=a status)" &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	expected="$(git -C c --git-dir=a.git --work-tree=a status)" &&
+	actual="$(git --git-dir=a.git --work-tree=a -C c status)" &&
+	test "$expected" = "$actual"
+'
+
+test_done
-- 
1.8.2.1.339.g52a3e01

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] Teach git to change to a given directory using -C option
  2013-08-30 13:35 Nazri Ramliy
@ 2013-09-01  4:48 ` Eric Sunshine
  0 siblings, 0 replies; 7+ messages in thread
From: Eric Sunshine @ 2013-09-01  4:48 UTC (permalink / raw)
  To: Nazri Ramliy; +Cc: Git List, Jeff King, Jonathan Nieder

On Fri, Aug 30, 2013 at 9:35 AM, Nazri Ramliy <ayiehere@gmail.com> wrote:
> This is similar in spirit to to "make -C dir ..." and "tar -C dir ...".
>
> Currently it takes more effort (keypresses) to invoke git command in a
> different directory than the current one without leaving the current
> directory:
>
>     1. (cd ~/foo && git status)
>        git --git-dir=~/foo/.git --work-dir=~/foo status
>        GIT_DIR=~/foo/.git GIT_WORK_TREE=~/foo git status
>     2. (cd ../..; git grep foo)
>     3. for d in d1 d2 d3; do (cd $d && git svn rebase); done
>
> While doable the methods shown above are arguably more suitable for
> scripting than quick command line invocations.
>
> With this new option, the above can be done with less keystrokes:

Grammar: s/less/fewer/

More below...

>     1. git -C ~/foo status
>     2. git -C ../.. grep foo
>     3. for d in d1 d2 d3; do git -C $d svn rebase; done
>
> A new test script is added to verify the behavior of this option with
> other path-related options like --git-dir and --work-tree.
>
> Signed-off-by: Nazri Ramliy <ayiehere@gmail.com>
> ---
> This is a reroll of [1]. The only difference is the rewording of the
> commit message.  I'm resending this as I've found it to be useful in my
> daily git usage in that it helps me stay focused on what I'm doing in
> the current directory while needing to run git on another directory.
>
> nazri.
>
> [1] http://permalink.gmane.org/gmane.comp.version-control.git/221954
>
>  Documentation/git.txt | 13 +++++++++
>  git.c                 | 15 ++++++++--
>  t/t0056-git-C.sh      | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 102 insertions(+), 2 deletions(-)
>  create mode 100755 t/t0056-git-C.sh
>
> diff --git a/Documentation/git.txt b/Documentation/git.txt
> index dca11cc..0d44fa2 100644
> --- a/Documentation/git.txt
> +++ b/Documentation/git.txt
> @@ -395,6 +395,19 @@ displayed. See linkgit:git-help[1] for more information,
>  because `git --help ...` is converted internally into `git
>  help ...`.
>
> +-C <directory>::

The synopsis at the top of git.txt mentions --git-dir and --work-tree.
For consistency, -C probably ought to be mentioned there, as well.

Other options which accept a directory, such as --git-dir and
--work-tree, are documented as accepting <path>, but -C is
inconsistently documented as accepting <directory>.

> +       Run as if git were started in <directory> instead of the current
> +       working directory. If multiple -C options are given, subsequent
> +       directory arguments are interpreted relative to the previous one: -C
> +       /usr -C src is equivalent to -C /usr/src. This option affects options

The fragment "interpreted relative" seems ambiguous when absolute
paths are involved. For instance, what happens when the user specifies
"-C /foo/ -C /bar/". From the implementation I can see that the
working directory becomes /bar, but without checking the
implementation, it's not clear what the result would be. For instance,
if the implementation merely did string concatenation of the -C
arguments, then input "-C /foo/ -C /bar/" might try to set the working
directory to "/foo//bar/" which would be interpreted as "/foo/bar/" on
Unix, but would probably fail on Windows. Perhaps rewriting might
remove the ambiguity?

    [...] When multiple -C options are given, each subsequent non-absolute
    -C <path> is interpreted relative to the preceding -C <path>. [...]

> +       that expect path name like --git-dir and --work-tree in that their
> +       interpretations of the path names would be made relative to the
> +       effective working directory caused by the -C option. For example the
> +       following invocations are equivalent:
> +
> +           git --git-dir=a.git --work-tree=b -C c status
> +           git --git-dir=c/a.git --work-tree=c/b status
> +
>  -c <name>=<value>::
>         Pass a configuration parameter to the command. The value
>         given will override values from configuration files.
> diff --git a/git.c b/git.c
> index 2025f77..2207ee5 100644
> --- a/git.c
> +++ b/git.c
> @@ -7,7 +7,7 @@
>  #include "commit.h"
>
>  const char git_usage_string[] =
> -       "git [--version] [--help] [-c name=value]\n"
> +       "git [--version] [--help] [-C directory] [-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"

For existing options accepting an argument, the argument is formatted
as <argument>. The -C option does not follow suit.

As mentioned above, all other options accepting a directory are
documented as taking <path>, but -C is inconsistent and is documented
as taking 'directory' instead.

> @@ -54,7 +54,18 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
>                 /*
>                  * Check remaining flags.
>                  */
> -               if (!prefixcmp(cmd, "--exec-path")) {
> +               if (!strcmp(cmd, "-C")) {
> +                       if (*argc < 2) {
> +                               fprintf(stderr, "No directory given for -C.\n" );
> +                               usage(git_usage_string);
> +                       }
> +                       if (chdir((*argv)[1]))
> +                               die_errno("Cannot change to '%s'", (*argv)[1]);
> +                       if (envchanged)
> +                               *envchanged = 1;
> +                       (*argv)++;
> +                       (*argc)--;
> +               } else if (!prefixcmp(cmd, "--exec-path")) {
>                         cmd += 11;
>                         if (*cmd == '=')
>                                 git_set_argv_exec_path(cmd + 1);
> diff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh
> new file mode 100755
> index 0000000..370eae6
> --- /dev/null
> +++ b/t/t0056-git-C.sh
> @@ -0,0 +1,76 @@
> +#!/bin/sh
> +
> +test_description='"-C <directory>" option and it effects on other path-related options'

s/it/its/
s/<directory>/<path>/

> +
> +. ./test-lib.sh
> +
> +test_expect_success '"git -C <dir>" runs git from the directory <dir>' '

s/<dir>/<path>/g

> +       test_create_repo dir1 &&
> +       echo 1 >dir1/a.txt &&
> +       (cd dir1 && git add a.txt && git commit -m "initial in dir1") &&
> +       expected="initial in dir1" &&
> +       actual=$(git -C dir1 log --format=%s) &&
> +       test "$expected" = "$actual"
> +'

Modern git tests tend to place the expected and actual outputs in
files and then use test_cmp to verify that they are identical. For
instance:

    echo "initial in dir1" >expected &&
    git -C dir1 log --format="%s" >actual &&
    test_cmp expected actual

The benefit of doing so is that test_cmp will print the difference
between the expected and actual values when they differ, which
facilitates debugging a failed test.

> +test_expect_success 'Multiple -C options: "-C dir1 -C dir2" is equivalent to "-C dir1/dir2"' '
> +       test_create_repo dir1/dir2 &&
> +       echo 1 >dir1/dir2/a.txt &&
> +       git -C dir1/dir2 add a.txt &&
> +       expected="initial in dir1/dir2" &&
> +       git -C dir1/dir2 commit -m "$expected" &&
> +       actual=$(git -C dir1 -C dir2 log --format=%s) &&
> +       test "$expected" = "$actual"
> +'

It would make sense also to test multiple -C options with combinations
of absolute and and relative paths.

> +test_expect_success 'Effect on --git-dir option: "-C c --git-dir=a.git" is equivalent to "--git-dir c/a.git"' '
> +       mkdir c &&
> +       mkdir c/a &&
> +       mkdir c/a.git &&
> +       (cd c/a.git && git init --bare) &&
> +       echo 1 >c/a/a.txt &&
> +       git --git-dir c/a.git --work-tree=c/a add a.txt &&
> +       git --git-dir c/a.git --work-tree=c/a commit -m "initial" &&
> +       expected="$(git --git-dir=c/a.git log -1 --format=%s)" &&
> +       actual=$(git -C c --git-dir=a.git log -1 --format=%s) &&
> +       test "$expected" = "$actual"
> +'
> +
> +test_expect_success 'Order should not matter: "--git-dir=a.git -C c" is equivalent to "-C c --git-dir=a.git"' '
> +       expected="$(git -C c --git-dir=a.git log -1 --format=%s)" &&
> +       actual=$(git --git-dir=a.git -C c log -1 --format=%s) &&
> +       test "$expected" = "$actual"
> +'
> +
> +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"' '
> +       rm c/a/a.txt &&
> +       expected="$(git --git-dir=c/a.git --work-tree=c/a status)" &&
> +       actual="$(git -C c/a.git --work-tree=../a status)" &&
> +       test "$expected" = "$actual"
> +'
> +
> +test_expect_success 'Order should not matter: "--work-tree=../a -C c/a.git" is equivalent to "-C c/a.git --work-tree=../a"' '
> +       expected="$(git -C c/a.git --work-tree=../a status)" &&
> +       actual="$(git --work-tree=../a -C c/a.git status)" &&
> +       test "$expected" = "$actual"
> +'
> +
> +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"' '
> +       expected="$(git --git-dir=c/a.git --work-tree=c/a status)" &&
> +       actual="$(git -C c --git-dir=a.git --work-tree=a status)" &&
> +       test "$expected" = "$actual"
> +'
> +
> +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"' '
> +       expected="$(git -C c --git-dir=a.git --work-tree=a status)" &&
> +       actual="$(git --git-dir=a.git -C c --work-tree=a status)" &&
> +       test "$expected" = "$actual"
> +'
> +
> +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"' '
> +       expected="$(git -C c --git-dir=a.git --work-tree=a status)" &&
> +       actual="$(git --git-dir=a.git --work-tree=a -C c status)" &&
> +       test "$expected" = "$actual"
> +'
> +
> +test_done
> --
> 1.8.4.1.g098df5a

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH] Teach git to change to a given directory using -C option
@ 2013-08-30 13:35 Nazri Ramliy
  2013-09-01  4:48 ` Eric Sunshine
  0 siblings, 1 reply; 7+ messages in thread
From: Nazri Ramliy @ 2013-08-30 13:35 UTC (permalink / raw)
  To: git, peff, jrnieder; +Cc: Nazri Ramliy

This is similar in spirit to to "make -C dir ..." and "tar -C dir ...".

Currently it takes more effort (keypresses) to invoke git command in a
different directory than the current one without leaving the current
directory:

    1. (cd ~/foo && git status)
       git --git-dir=~/foo/.git --work-dir=~/foo status
       GIT_DIR=~/foo/.git GIT_WORK_TREE=~/foo git status
    2. (cd ../..; git grep foo)
    3. for d in d1 d2 d3; do (cd $d && git svn rebase); done

While doable the methods shown above are arguably more suitable for
scripting than quick command line invocations.

With this new option, the above can be done with less keystrokes:

    1. git -C ~/foo status
    2. git -C ../.. grep foo
    3. for d in d1 d2 d3; do git -C $d svn rebase; done

A new test script is added to verify the behavior of this option with
other path-related options like --git-dir and --work-tree.

Signed-off-by: Nazri Ramliy <ayiehere@gmail.com>
---
This is a reroll of [1]. The only difference is the rewording of the
commit message.  I'm resending this as I've found it to be useful in my
daily git usage in that it helps me stay focused on what I'm doing in
the current directory while needing to run git on another directory.

nazri.

[1] http://permalink.gmane.org/gmane.comp.version-control.git/221954

 Documentation/git.txt | 13 +++++++++
 git.c                 | 15 ++++++++--
 t/t0056-git-C.sh      | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+), 2 deletions(-)
 create mode 100755 t/t0056-git-C.sh

diff --git a/Documentation/git.txt b/Documentation/git.txt
index dca11cc..0d44fa2 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -395,6 +395,19 @@ displayed. See linkgit:git-help[1] for more information,
 because `git --help ...` is converted internally into `git
 help ...`.
 
+-C <directory>::
+	Run as if git were started in <directory> instead of the current
+	working directory. If multiple -C options are given, subsequent
+	directory arguments are interpreted relative to the previous one: -C
+	/usr -C src is equivalent to -C /usr/src. This option affects options
+	that expect path name like --git-dir and --work-tree in that their
+	interpretations of the path names would be made relative to the
+	effective working directory caused by the -C option. For example the
+	following invocations are equivalent:
+
+	    git --git-dir=a.git --work-tree=b -C c status
+	    git --git-dir=c/a.git --work-tree=c/b status
+
 -c <name>=<value>::
 	Pass a configuration parameter to the command. The value
 	given will override values from configuration files.
diff --git a/git.c b/git.c
index 2025f77..2207ee5 100644
--- a/git.c
+++ b/git.c
@@ -7,7 +7,7 @@
 #include "commit.h"
 
 const char git_usage_string[] =
-	"git [--version] [--help] [-c name=value]\n"
+	"git [--version] [--help] [-C directory] [-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"
@@ -54,7 +54,18 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 		/*
 		 * Check remaining flags.
 		 */
-		if (!prefixcmp(cmd, "--exec-path")) {
+		if (!strcmp(cmd, "-C")) {
+			if (*argc < 2) {
+				fprintf(stderr, "No directory given for -C.\n" );
+				usage(git_usage_string);
+			}
+			if (chdir((*argv)[1]))
+				die_errno("Cannot change to '%s'", (*argv)[1]);
+			if (envchanged)
+				*envchanged = 1;
+			(*argv)++;
+			(*argc)--;
+		} else if (!prefixcmp(cmd, "--exec-path")) {
 			cmd += 11;
 			if (*cmd == '=')
 				git_set_argv_exec_path(cmd + 1);
diff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh
new file mode 100755
index 0000000..370eae6
--- /dev/null
+++ b/t/t0056-git-C.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='"-C <directory>" option and it effects on other path-related options'
+
+. ./test-lib.sh
+
+test_expect_success '"git -C <dir>" runs git from the directory <dir>' '
+	test_create_repo dir1 &&
+	echo 1 >dir1/a.txt &&
+	(cd dir1 && git add a.txt && git commit -m "initial in dir1") &&
+	expected="initial in dir1" &&
+	actual=$(git -C dir1 log --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Multiple -C options: "-C dir1 -C dir2" is equivalent to "-C dir1/dir2"' '
+	test_create_repo dir1/dir2 &&
+	echo 1 >dir1/dir2/a.txt &&
+	git -C dir1/dir2 add a.txt &&
+	expected="initial in dir1/dir2" &&
+	git -C dir1/dir2 commit -m "$expected" &&
+	actual=$(git -C dir1 -C dir2 log --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Effect on --git-dir option: "-C c --git-dir=a.git" is equivalent to "--git-dir c/a.git"' '
+	mkdir c &&
+	mkdir c/a &&
+	mkdir c/a.git &&
+	(cd c/a.git && git init --bare) &&
+	echo 1 >c/a/a.txt &&
+	git --git-dir c/a.git --work-tree=c/a add a.txt &&
+	git --git-dir c/a.git --work-tree=c/a commit -m "initial" &&
+	expected="$(git --git-dir=c/a.git log -1 --format=%s)" &&
+	actual=$(git -C c --git-dir=a.git log -1 --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Order should not matter: "--git-dir=a.git -C c" is equivalent to "-C c --git-dir=a.git"' '
+	expected="$(git -C c --git-dir=a.git log -1 --format=%s)" &&
+	actual=$(git --git-dir=a.git -C c log -1 --format=%s) &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	rm c/a/a.txt &&
+	expected="$(git --git-dir=c/a.git --work-tree=c/a status)" &&
+	actual="$(git -C c/a.git --work-tree=../a status)" &&
+	test "$expected" = "$actual"
+'
+
+test_expect_success 'Order should not matter: "--work-tree=../a -C c/a.git" is equivalent to "-C c/a.git --work-tree=../a"' '
+	expected="$(git -C c/a.git --work-tree=../a status)" &&
+	actual="$(git --work-tree=../a -C c/a.git status)" &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	expected="$(git --git-dir=c/a.git --work-tree=c/a status)" &&
+	actual="$(git -C c --git-dir=a.git --work-tree=a status)" &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	expected="$(git -C c --git-dir=a.git --work-tree=a status)" &&
+	actual="$(git --git-dir=a.git -C c --work-tree=a status)" &&
+	test "$expected" = "$actual"
+'
+
+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"' '
+	expected="$(git -C c --git-dir=a.git --work-tree=a status)" &&
+	actual="$(git --git-dir=a.git --work-tree=a -C c status)" &&
+	test "$expected" = "$actual"
+'
+
+test_done
-- 
1.8.4.1.g098df5a

^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2013-09-01  4:48 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-04-19 12:21 [PATCH] Teach git to change to a given directory using -C option Nazri Ramliy
2013-04-19 16:12 ` Jeff King
2013-04-20 22:18   ` Jonathan Nieder
2013-04-21  3:58     ` Jeff King
2013-04-21 13:48   ` Nazri Ramliy
2013-08-30 13:35 Nazri Ramliy
2013-09-01  4:48 ` Eric Sunshine

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.