Merge branch 'ab/grep-preparatory-cleanup' into sb/submodule-blanket-recursive
[gitweb.git] / git.c
diff --git a/git.c b/git.c
index c19d6bf47bdc2831c8d5c98e4921910ae1ff4db5..8ff44f081d43176474b267de5451f2c2e88089d0 100644 (file)
--- a/git.c
+++ b/git.c
@@ -15,7 +15,6 @@ const char git_more_info_string[] =
           "concept guides. See 'git help <command>' or 'git help <concept>'\n"
           "to read about a specific subcommand or concept.");
 
-static struct startup_info git_startup_info;
 static int use_pager = -1;
 static char *orig_cwd;
 static const char *env_names[] = {
@@ -25,33 +24,40 @@ static const char *env_names[] = {
        GIT_PREFIX_ENVIRONMENT
 };
 static char *orig_env[4];
-static int saved_environment;
+static int save_restore_env_balance;
 
-static void save_env(void)
+static void save_env_before_alias(void)
 {
        int i;
-       if (saved_environment)
-               return;
-       saved_environment = 1;
+
+       assert(save_restore_env_balance == 0);
+       save_restore_env_balance = 1;
        orig_cwd = xgetcwd();
        for (i = 0; i < ARRAY_SIZE(env_names); i++) {
                orig_env[i] = getenv(env_names[i]);
-               if (orig_env[i])
-                       orig_env[i] = xstrdup(orig_env[i]);
+               orig_env[i] = xstrdup_or_null(orig_env[i]);
        }
 }
 
-static void restore_env(void)
+static void restore_env(int external_alias)
 {
        int i;
-       if (orig_cwd && chdir(orig_cwd))
+
+       assert(save_restore_env_balance == 1);
+       save_restore_env_balance = 0;
+       if (!external_alias && orig_cwd && chdir(orig_cwd))
                die_errno("could not move to %s", orig_cwd);
        free(orig_cwd);
        for (i = 0; i < ARRAY_SIZE(env_names); i++) {
-               if (orig_env[i])
+               if (external_alias &&
+                   !strcmp(env_names[i], GIT_PREFIX_ENVIRONMENT))
+                       continue;
+               if (orig_env[i]) {
                        setenv(env_names[i], orig_env[i], 1);
-               else
+                       free(orig_env[i]);
+               } else {
                        unsetenv(env_names[i]);
+               }
        }
 }
 
@@ -157,6 +163,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
                        setenv(GIT_WORK_TREE_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
+               } else if (!strcmp(cmd, "--super-prefix")) {
+                       if (*argc < 2) {
+                               fprintf(stderr, "No prefix given for --super-prefix.\n" );
+                               usage(git_usage_string);
+                       }
+                       setenv(GIT_SUPER_PREFIX_ENVIRONMENT, (*argv)[1], 1);
+                       if (envchanged)
+                               *envchanged = 1;
+                       (*argv)++;
+                       (*argc)--;
+               } else if (skip_prefix(cmd, "--super-prefix=", &cmd)) {
+                       setenv(GIT_SUPER_PREFIX_ENVIRONMENT, cmd, 1);
+                       if (envchanged)
+                               *envchanged = 1;
                } else if (!strcmp(cmd, "--bare")) {
                        char *cwd = xgetcwd();
                        is_bare_repository_cfg = 1;
@@ -226,14 +246,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 static int handle_alias(int *argcp, const char ***argv)
 {
        int envchanged = 0, ret = 0, saved_errno = errno;
-       const char *subdir;
        int count, option_count;
        const char **new_argv;
        const char *alias_command;
        char *alias_string;
        int unused_nongit;
 
-       subdir = setup_git_directory_gently(&unused_nongit);
+       save_env_before_alias();
+       setup_git_directory_gently(&unused_nongit);
 
        alias_command = (*argv)[0];
        alias_string = alias_lookup(alias_command);
@@ -242,6 +262,7 @@ static int handle_alias(int *argcp, const char ***argv)
                        struct child_process child = CHILD_PROCESS_INIT;
 
                        commit_pager_choice();
+                       restore_env(1);
 
                        child.use_shell = 1;
                        argv_array_push(&child.args, alias_string + 1);
@@ -287,8 +308,7 @@ static int handle_alias(int *argcp, const char ***argv)
                ret = 1;
        }
 
-       if (subdir && chdir(subdir))
-               die_errno("Cannot change to '%s'", subdir);
+       restore_env(0);
 
        errno = saved_errno;
 
@@ -303,7 +323,7 @@ static int handle_alias(int *argcp, const char ***argv)
  * RUN_SETUP for reading from the configuration file.
  */
 #define NEED_WORK_TREE         (1<<3)
-#define NO_SETUP               (1<<4)
+#define SUPPORT_SUPER_PREFIX   (1<<4)
 
 struct cmd_struct {
        const char *cmd;
@@ -338,6 +358,11 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
        }
        commit_pager_choice();
 
+       if (!help && get_super_prefix()) {
+               if (!(p->option & SUPPORT_SUPER_PREFIX))
+                       die("%s doesn't support --super-prefix", p->cmd);
+       }
+
        if (!help && p->option & NEED_WORK_TREE)
                setup_work_tree();
 
@@ -369,7 +394,7 @@ static struct cmd_struct commands[] = {
        { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE },
        { "annotate", cmd_annotate, RUN_SETUP },
        { "apply", cmd_apply, RUN_SETUP_GENTLY },
-       { "archive", cmd_archive },
+       { "archive", cmd_archive, RUN_SETUP_GENTLY },
        { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
        { "blame", cmd_blame, RUN_SETUP },
        { "branch", cmd_branch, RUN_SETUP },
@@ -385,7 +410,7 @@ static struct cmd_struct commands[] = {
        { "cherry", cmd_cherry, RUN_SETUP },
        { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
        { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
-       { "clone", cmd_clone, NO_SETUP },
+       { "clone", cmd_clone },
        { "column", cmd_column, RUN_SETUP_GENTLY },
        { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
        { "commit-tree", cmd_commit_tree, RUN_SETUP },
@@ -397,6 +422,7 @@ static struct cmd_struct commands[] = {
        { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
        { "diff-index", cmd_diff_index, RUN_SETUP },
        { "diff-tree", cmd_diff_tree, RUN_SETUP },
+       { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE },
        { "fast-export", cmd_fast_export, RUN_SETUP },
        { "fetch", cmd_fetch, RUN_SETUP },
        { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
@@ -407,18 +433,18 @@ static struct cmd_struct commands[] = {
        { "fsck-objects", cmd_fsck, RUN_SETUP },
        { "gc", cmd_gc, RUN_SETUP },
        { "get-tar-commit-id", cmd_get_tar_commit_id },
-       { "grep", cmd_grep, RUN_SETUP_GENTLY },
+       { "grep", cmd_grep, RUN_SETUP_GENTLY | SUPPORT_SUPER_PREFIX },
        { "hash-object", cmd_hash_object },
        { "help", cmd_help },
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
-       { "init", cmd_init_db, NO_SETUP },
-       { "init-db", cmd_init_db, NO_SETUP },
+       { "init", cmd_init_db },
+       { "init-db", cmd_init_db },
        { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
        { "log", cmd_log, RUN_SETUP },
-       { "ls-files", cmd_ls_files, RUN_SETUP },
+       { "ls-files", cmd_ls_files, RUN_SETUP | SUPPORT_SUPER_PREFIX },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
        { "ls-tree", cmd_ls_tree, RUN_SETUP },
-       { "mailinfo", cmd_mailinfo },
+       { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY },
        { "mailsplit", cmd_mailsplit },
        { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
        { "merge-base", cmd_merge_base, RUN_SETUP },
@@ -438,13 +464,14 @@ static struct cmd_struct commands[] = {
        { "pack-objects", cmd_pack_objects, RUN_SETUP },
        { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
        { "pack-refs", cmd_pack_refs, RUN_SETUP },
-       { "patch-id", cmd_patch_id },
+       { "patch-id", cmd_patch_id, RUN_SETUP_GENTLY },
        { "pickaxe", cmd_blame, RUN_SETUP },
        { "prune", cmd_prune, RUN_SETUP },
        { "prune-packed", cmd_prune_packed, RUN_SETUP },
        { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
        { "push", cmd_push, RUN_SETUP },
-       { "read-tree", cmd_read_tree, RUN_SETUP },
+       { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
+       { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
        { "receive-pack", cmd_receive_pack },
        { "reflog", cmd_reflog, RUN_SETUP },
        { "remote", cmd_remote, RUN_SETUP },
@@ -466,7 +493,7 @@ static struct cmd_struct commands[] = {
        { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
-       { "submodule--helper", cmd_submodule__helper, RUN_SETUP },
+       { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX},
        { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
        { "tag", cmd_tag, RUN_SETUP },
        { "unpack-file", cmd_unpack_file, RUN_SETUP },
@@ -516,61 +543,67 @@ static void strip_extension(const char **argv)
 
 static void handle_builtin(int argc, const char **argv)
 {
+       struct argv_array args = ARGV_ARRAY_INIT;
        const char *cmd;
        struct cmd_struct *builtin;
 
        strip_extension(argv);
        cmd = argv[0];
 
-       /* Turn "git cmd --help" into "git help cmd" */
+       /* Turn "git cmd --help" into "git help --exclude-guides cmd" */
        if (argc > 1 && !strcmp(argv[1], "--help")) {
+               int i;
+
                argv[1] = argv[0];
                argv[0] = cmd = "help";
+
+               for (i = 0; i < argc; i++) {
+                       argv_array_push(&args, argv[i]);
+                       if (!i)
+                               argv_array_push(&args, "--exclude-guides");
+               }
+
+               argc++;
+               argv = args.argv;
        }
 
        builtin = get_builtin(cmd);
-       if (builtin) {
-               if (saved_environment && (builtin->option & NO_SETUP))
-                       restore_env();
-               else
-                       exit(run_builtin(builtin, argc, argv));
-       }
+       if (builtin)
+               exit(run_builtin(builtin, argc, argv));
+       argv_array_clear(&args);
 }
 
 static void execv_dashed_external(const char **argv)
 {
-       struct strbuf cmd = STRBUF_INIT;
-       const char *tmp;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        int status;
 
+       if (get_super_prefix())
+               die("%s doesn't support --super-prefix", argv[0]);
+
        if (use_pager == -1)
                use_pager = check_pager_config(argv[0]);
        commit_pager_choice();
 
-       strbuf_addf(&cmd, "git-%s", argv[0]);
+       argv_array_pushf(&cmd.args, "git-%s", argv[0]);
+       argv_array_pushv(&cmd.args, argv + 1);
+       cmd.clean_on_exit = 1;
+       cmd.wait_after_clean = 1;
+       cmd.silent_exec_failure = 1;
 
-       /*
-        * argv[0] must be the git command, but the argv array
-        * belongs to the caller, and may be reused in
-        * subsequent loop iterations. Save argv[0] and
-        * restore it on error.
-        */
-       tmp = argv[0];
-       argv[0] = cmd.buf;
-
-       trace_argv_printf(argv, "trace: exec:");
+       trace_argv_printf(cmd.args.argv, "trace: exec:");
 
        /*
-        * if we fail because the command is not found, it is
-        * OK to return. Otherwise, we just pass along the status code.
+        * If we fail because the command is not found, it is
+        * OK to return. Otherwise, we just pass along the status code,
+        * or our usual generic code if we were not even able to exec
+        * the program.
         */
-       status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE | RUN_CLEAN_ON_EXIT);
-       if (status >= 0 || errno != ENOENT)
+       status = run_command(&cmd);
+       if (status >= 0)
                exit(status);
-
-       argv[0] = tmp;
-
-       strbuf_release(&cmd);
+       else if (errno != ENOENT)
+               exit(128);
 }
 
 static int run_argv(int *argcp, const char ***argv)
@@ -578,8 +611,17 @@ static int run_argv(int *argcp, const char ***argv)
        int done_alias = 0;
 
        while (1) {
-               /* See if it's a builtin */
-               handle_builtin(*argcp, *argv);
+               /*
+                * If we tried alias and futzed with our environment,
+                * it no longer is safe to invoke builtins directly in
+                * general.  We have to spawn them as dashed externals.
+                *
+                * NEEDSWORK: if we can figure out cases
+                * where it is safe to do, we can avoid spawning a new
+                * process.
+                */
+               if (!done_alias)
+                       handle_builtin(*argcp, *argv);
 
                /* .. then try the external ones */
                execv_dashed_external(*argv);
@@ -590,7 +632,6 @@ static int run_argv(int *argcp, const char ***argv)
                 */
                if (done_alias)
                        break;
-               save_env();
                if (!handle_alias(argcp, argv))
                        break;
                done_alias = 1;
@@ -599,49 +640,19 @@ static int run_argv(int *argcp, const char ***argv)
        return done_alias;
 }
 
-/*
- * Many parts of Git have subprograms communicate via pipe, expect the
- * upstream of a pipe to die with SIGPIPE when the downstream of a
- * pipe does not need to read all that is written.  Some third-party
- * programs that ignore or block SIGPIPE for their own reason forget
- * to restore SIGPIPE handling to the default before spawning Git and
- * break this carefully orchestrated machinery.
- *
- * Restore the way SIGPIPE is handled to default, which is what we
- * expect.
- */
-static void restore_sigpipe_to_default(void)
+int cmd_main(int argc, const char **argv)
 {
-       sigset_t unblock;
-
-       sigemptyset(&unblock);
-       sigaddset(&unblock, SIGPIPE);
-       sigprocmask(SIG_UNBLOCK, &unblock, NULL);
-       signal(SIGPIPE, SIG_DFL);
-}
-
-int main(int argc, char **av)
-{
-       const char **argv = (const char **) av;
        const char *cmd;
        int done_help = 0;
 
-       startup_info = &git_startup_info;
-
-       cmd = git_extract_argv0_path(argv[0]);
+       cmd = argv[0];
        if (!cmd)
                cmd = "git-help";
-
-       /*
-        * Always open file descriptors 0/1/2 to avoid clobbering files
-        * in die().  It also avoids messing up when the pipes are dup'ed
-        * onto stdin/stdout/stderr in the child processes we spawn.
-        */
-       sanitize_stdfds();
-
-       restore_sigpipe_to_default();
-
-       git_setup_gettext();
+       else {
+               const char *slash = find_last_dir_sep(cmd);
+               if (slash)
+                       cmd = slash + 1;
+       }
 
        trace_command_performance(argv);