threads / patch / 8509

patchmake git barf when an alias changes environment variables

Subject: [PATCH] make git barf when an alias changes environment variables

## tl;dr

3 messages between Jun 8, 2007 and Jun 13, 2007. Diffs are folded; open one to read it.

replies: 2people: 2as markdown or json

Matthias Lederhofer· Jun 8, 2007, 20:57 UTC · lore

Aliases changing environment variables (GIT_DIR or GIT_WORK_TREE) can cause problems: git has to use GIT_DIR to read the aliases from the config. After running handle_options for the alias the options of the alias may have changed environment variables. Depending on the implementation of setenv the memory location obtained through getenv earlier may contain the old value or the new value (or even be used for something else?). To avoid these problems git errors out if an alias uses any option which changes environment variables.

Signed-off-by: Matthias Lederhofer <matled@gmx.net>
---
This is on top of ml/worktree even though the problem is also present in
master.  It could also be split into one patch which can be applied to
master directly and another one adding the missing parts in the
ml/worktree branch.
Another option instead of dying would be to execute git again.  I
decided not to because of
(1) It causes problems with aliases starting the pager: After the exec
    git wouldn't know that the pager is running and colors would be
    missing.
(2) Up to now aliases not starting with '!' have recursion detection
    and are only evaluated as aliases once.  Executing git again would
    break this.
Example:

Here is an example how to break git aliases, it works with libc of FreeBSD but not with glibc. libc of FreeBSD reuses the old location for the environment variable if the new value is smaller than the old one.

There is a repository in a/.git and a repository in b/.git. Both have valid HEADs:

% git --git-dir a/.git rev-list --pretty=oneline HEAD 496a0d181f6878ddda8926103a7cf28a668c46ef a

% git --git-dir b/.git rev-list --pretty=oneline HEAD 3c2858eded8ae7ff964549f85f479808d185283c b

Set up some aliases using --git-dir:

% git --git-dir a/.git config alias.works \ '--git-dir a/.git rev-list --pretty=oneline HEAD'

% git --git-dir a/.git config alias.breaks \ '--git-dir b/.git rev-list --pretty=oneline HEAD'

% git --git-dir a/.git config alias.good '!git --git-dir b/.git rev-list --pretty=oneline HEAD'

The first one works because the value of the environment variable does not change, the second one does not work because git reads the ref HEAD from b/.git but searches the object in a/.git. The third one always works.

% git --git-dir a/.git works 496a0d181f6878ddda8926103a7cf28a668c46ef a

% git --git-dir a/.git breaks fatal: bad object HEAD [1] 15643 exit 128 git --git-dir a/.git breaks

% git --git-dir a/.git good 3c2858eded8ae7ff964549f85f479808d185283c b

With the patch:

% git --git-dir a/.git breaks fatal: alias 'breaks' changes environment variables You can use '!git' in the alias to do this. [1] 17295 exit 128 git --git-dir a/.git breaks

---
 git.c |   22 ++++++++++++++++++----
 1 files changed, 18 insertions(+), 4 deletions(-)
Show changes to git.c +18 −4
diff --git a/git.c b/git.c
index cd3910a..33edd62 100644
--- a/git.c
+++ b/git.c
@@ -28,7 +28,7 @@ static void prepend_to_path(const char *dir, int len)
 	free(path);
 }
 
-static int handle_options(const char*** argv, int* argc)
+static int handle_options(const char*** argv, int* argc, int* envchanged)
 {
 	int handled = 0;
 
@@ -64,24 +64,34 @@ static int handle_options(const char*** argv, int* argc)
 				usage(git_usage_string);
 			}
 			setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
+			if (envchanged)
+				*envchanged = 1;
 			(*argv)++;
 			(*argc)--;
 			handled++;
 		} else if (!prefixcmp(cmd, "--git-dir=")) {
 			setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
+			if (envchanged)
+				*envchanged = 1;
 		} else if (!strcmp(cmd, "--work-tree")) {
 			if (*argc < 2) {
 				fprintf(stderr, "No directory given for --work-tree.\n" );
 				usage(git_usage_string);
 			}
 			setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
+			if (envchanged)
+				*envchanged = 1;
 			(*argv)++;
 			(*argc)--;
 		} else if (!prefixcmp(cmd, "--work-tree=")) {
 			setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
+			if (envchanged)
+				*envchanged = 1;
 		} else if (!strcmp(cmd, "--bare")) {
 			static char git_dir[PATH_MAX+1];
 			setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
+			if (envchanged)
+				*envchanged = 1;
 		} else {
 			fprintf(stderr, "Unknown option: %s\n", cmd);
 			usage(git_usage_string);
@@ -160,7 +170,7 @@ static int split_cmdline(char *cmdline, const char ***argv)
 
 static int handle_alias(int *argcp, const char ***argv)
 {
-	int nongit = 0, ret = 0, saved_errno = errno;
+	int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno;
 	const char *subdir;
 	int count, option_count;
 	const char** new_argv;
@@ -181,7 +191,11 @@ static int handle_alias(int *argcp, const char ***argv)
 			    alias_string + 1, alias_command);
 		}
 		count = split_cmdline(alias_string, &new_argv);
-		option_count = handle_options(&new_argv, &count);
+		option_count = handle_options(&new_argv, &count, &envchanged);
+		if (envchanged)
+			die("alias '%s' changes environment variables\n"
+				 "You can use '!git' in the alias to do this.",
+				 alias_command);
 		memmove(new_argv - option_count, new_argv,
 				count * sizeof(char *));
 		new_argv -= option_count;
@@ -375,7 +389,7 @@ int main(int argc, const char **argv, char **envp)
 	/* Look for flags.. */
 	argv++;
 	argc--;
-	handle_options(&argv, &argc);
+	handle_options(&argv, &argc, NULL);
 	if (argc > 0) {
 		if (!prefixcmp(argv[0], "--"))
 			argv[0] += 2;
-- 
1.5.2.1.888.gd5e4e
Junio C Hamano· Jun 13, 2007, 05:21 UTC · re: Matthias Lederhofer · lore

Re: [PATCH] make git barf when an alias changes environment variables

What does this patch do when you do this?
	: Because I usually work with this repository...
	$ GIT_DIR=$some_repository
        $ export GIT_DIR
        : Then much later, I happen to visit another repository
        : to take a peek...
	$ cd $somewhere_else
        $ git --git-dir .git some-command

I think --git-dir request on the command line is primarily to allow overriding whatever the user has in the environment as the fallback default, so if the patch makes it barf that is not very nice.

                
Matthias Lederhofer· Jun 13, 2007, 05:41 UTC · re: Junio C Hamano · lore

Re: [PATCH] make git barf when an alias changes environment variables

Junio C Hamano <gitster@pobox.com> wrote:
Show 11 quoted lines
> What does this patch do when you do this?
> 
> 	: Because I usually work with this repository...
>       $ GIT_DIR=$some_repository
>       $ export GIT_DIR
> 
>       : Then much later, I happen to visit another repository
>       : to take a peek...
> 	$ cd $somewhere_else
>       $ git --git-dir .git some-command
> 
This one has no alias involved as far as I see, so there is no change.

The patch makes git barf when an alias changes the environment (i.e. an alias uses --git-dir/--work-tree/--bare). When git has read the configuration file to find out about aliases setup_git_env has been called and a pointer returned by getenv("GIT_DIR") is already stored in a static pointer. If the environment variable is changed afterwards git might or might not use the new value depending on the implementation.

That is what my example shows: FreeBSD libc reuses the old memory location if the new value of an environment variable fits in the old place. Because setup_git_env uses xstrdup on the object dir and some other directories the old value is used for those but the new one is used for git_dir (which is just a pointer returned by getenv). This way HEAD is read from the new repository but objects are searched in the old repository.

← back to recent threads