Merge branch 'jk/maint-cleanup-after-exec-failure'
authorJunio C Hamano <gitster@pobox.com>
Tue, 3 Feb 2009 08:26:12 +0000 (00:26 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 3 Feb 2009 08:26:12 +0000 (00:26 -0800)
* jk/maint-cleanup-after-exec-failure:
git: use run_command() to execute dashed externals
run_command(): help callers distinguish errors
run_command(): handle missing command errors more gracefully
git: s/run_command/run_builtin/

1  2 
git.c
run-command.c
run-command.h
diff --combined git.c
index 320cb435646361b9d542d22d1592f985008fb750,af747613f02af94bb6e3cba6d4e070061e2d8c0f..c2b181ed78daa4510f5cfb7bbff5b78f449f872a
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -2,6 -2,7 +2,7 @@@
  #include "exec_cmd.h"
  #include "cache.h"
  #include "quote.h"
+ #include "run-command.h"
  
  const char git_usage_string[] =
        "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
@@@ -158,7 -159,7 +159,7 @@@ static int handle_alias(int *argcp, con
                        if (ret >= 0 && WIFEXITED(ret) &&
                            WEXITSTATUS(ret) != 127)
                                exit(WEXITSTATUS(ret));
 -                      die("Failed to run '%s' when expanding alias '%s'\n",
 +                      die("Failed to run '%s' when expanding alias '%s'",
                            alias_string + 1, alias_command);
                }
                count = split_cmdline(alias_string, &new_argv);
@@@ -219,7 -220,7 +220,7 @@@ struct cmd_struct 
        int option;
  };
  
- static int run_command(struct cmd_struct *p, int argc, const char **argv)
+ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
  {
        int status;
        struct stat st;
@@@ -384,7 -385,7 +385,7 @@@ static void handle_internal_command(in
                struct cmd_struct *p = commands+i;
                if (strcmp(p->cmd, cmd))
                        continue;
-               exit(run_command(p, argc, argv));
+               exit(run_builtin(p, argc, argv));
        }
  }
  
@@@ -392,6 -393,7 +393,7 @@@ static void execv_dashed_external(cons
  {
        struct strbuf cmd = STRBUF_INIT;
        const char *tmp;
+       int status;
  
        strbuf_addf(&cmd, "git-%s", argv[0]);
  
  
        trace_argv_printf(argv, "trace: exec:");
  
-       /* execvp() can only ever return if it fails */
-       execvp(cmd.buf, (char **)argv);
-       trace_printf("trace: exec failed: %s\n", strerror(errno));
+       /*
+        * if we fail because the command is not found, it is
+        * OK to return. Otherwise, we just pass along the status code.
+        */
+       status = run_command_v_opt(argv, 0);
+       if (status != -ERR_RUN_COMMAND_EXEC) {
+               if (IS_RUN_COMMAND_ERR(status))
+                       die("unable to run '%s'", argv[0]);
+               exit(-status);
+       }
+       errno = ENOENT; /* as if we called execvp */
  
        argv[0] = tmp;
  
        strbuf_release(&cmd);
  }
  
 -
 -int main(int argc, const char **argv)
 +static int run_argv(int *argcp, const char ***argv)
  {
 -      const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help";
 -      char *slash = (char *)cmd + strlen(cmd);
        int done_alias = 0;
  
 -      /*
 -       * Take the basename of argv[0] as the command
 -       * name, and the dirname as the default exec_path
 -       * if we don't have anything better.
 -       */
 -      do
 -              --slash;
 -      while (cmd <= slash && !is_dir_sep(*slash));
 -      if (cmd <= slash) {
 -              *slash++ = 0;
 -              git_set_argv0_path(cmd);
 -              cmd = slash;
 +      while (1) {
 +              /* See if it's an internal command */
 +              handle_internal_command(*argcp, *argv);
 +
 +              /* .. then try the external ones */
 +              execv_dashed_external(*argv);
 +
 +              /* It could be an alias -- this works around the insanity
 +               * of overriding "git log" with "git show" by having
 +               * alias.log = show
 +               */
 +              if (done_alias || !handle_alias(argcp, argv))
 +                      break;
 +              done_alias = 1;
        }
  
 +      return done_alias;
 +}
 +
 +
 +int main(int argc, const char **argv)
 +{
 +      const char *cmd;
 +
 +      cmd = git_extract_argv0_path(argv[0]);
 +      if (!cmd)
 +              cmd = "git-help";
 +
        /*
         * "git-xxxx" is the same as "git xxxx", but we obviously:
         *
        setup_path();
  
        while (1) {
 -              /* See if it's an internal command */
 -              handle_internal_command(argc, argv);
 -
 -              /* .. then try the external ones */
 -              execv_dashed_external(argv);
 -
 -              /* It could be an alias -- this works around the insanity
 -               * of overriding "git log" with "git show" by having
 -               * alias.log = show
 -               */
 -              if (done_alias || !handle_alias(&argc, &argv))
 +              static int done_help = 0;
 +              static int was_alias = 0;
 +              was_alias = run_argv(&argc, &argv);
 +              if (errno != ENOENT)
                        break;
 -              done_alias = 1;
 -      }
 -
 -      if (errno == ENOENT) {
 -              if (done_alias) {
 +              if (was_alias) {
                        fprintf(stderr, "Expansion of alias '%s' failed; "
                                "'%s' is not a git-command\n",
                                cmd, argv[0]);
                        exit(1);
                }
 -              argv[0] = help_unknown_cmd(cmd);
 -              handle_internal_command(argc, argv);
 -              execv_dashed_external(argv);
 +              if (!done_help) {
 +                      cmd = argv[0] = help_unknown_cmd(cmd);
 +                      done_help = 1;
 +              } else
 +                      break;
        }
  
        fprintf(stderr, "Failed to run command '%s': %s\n",
diff --combined run-command.c
index db9ce59204aa4292e82ccd0aaf052cf4159cecdf,44fccc9d5ef4d01eb3c73d6ce8cfbb0cfff0362b..b05c734d05e99cd009a0df26f0fc95fa13ae6f25
@@@ -118,7 -118,9 +118,9 @@@ int start_command(struct child_process 
                } else {
                        execvp(cmd->argv[0], (char *const*) cmd->argv);
                }
-               die("exec %s failed.", cmd->argv[0]);
+               trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
+                               strerror(errno));
+               exit(127);
        }
  #else
        int s0 = -1, s1 = -1, s2 = -1;  /* backups of stdin, stdout, stderr */
  #endif
  
        if (cmd->pid < 0) {
+               int err = errno;
                if (need_in)
                        close_pair(fdin);
                else if (cmd->in)
                        close(cmd->out);
                if (need_err)
                        close_pair(fderr);
-               return -ERR_RUN_COMMAND_FORK;
+               return err == ENOENT ?
+                       -ERR_RUN_COMMAND_EXEC :
+                       -ERR_RUN_COMMAND_FORK;
        }
  
        if (need_in)
@@@ -236,9 -241,14 +241,14 @@@ static int wait_or_whine(pid_t pid
                if (!WIFEXITED(status))
                        return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
                code = WEXITSTATUS(status);
-               if (code)
+               switch (code) {
+               case 127:
+                       return -ERR_RUN_COMMAND_EXEC;
+               case 0:
+                       return 0;
+               default:
                        return -code;
-               return 0;
+               }
        }
  }
  
@@@ -342,48 -352,3 +352,48 @@@ int finish_async(struct async *async
  #endif
        return ret;
  }
 +
 +int run_hook(const char *index_file, const char *name, ...)
 +{
 +      struct child_process hook;
 +      const char **argv = NULL, *env[2];
 +      char index[PATH_MAX];
 +      va_list args;
 +      int ret;
 +      size_t i = 0, alloc = 0;
 +
 +      if (access(git_path("hooks/%s", name), X_OK) < 0)
 +              return 0;
 +
 +      va_start(args, name);
 +      ALLOC_GROW(argv, i + 1, alloc);
 +      argv[i++] = git_path("hooks/%s", name);
 +      while (argv[i-1]) {
 +              ALLOC_GROW(argv, i + 1, alloc);
 +              argv[i++] = va_arg(args, const char *);
 +      }
 +      va_end(args);
 +
 +      memset(&hook, 0, sizeof(hook));
 +      hook.argv = argv;
 +      hook.no_stdin = 1;
 +      hook.stdout_to_stderr = 1;
 +      if (index_file) {
 +              snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
 +              env[0] = index;
 +              env[1] = NULL;
 +              hook.env = env;
 +      }
 +
 +      ret = start_command(&hook);
 +      free(argv);
 +      if (ret) {
 +              warning("Could not spawn %s", argv[0]);
 +              return ret;
 +      }
 +      ret = finish_command(&hook);
 +      if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
 +              warning("%s exited due to uncaught signal", argv[0]);
 +
 +      return ret;
 +}
diff --combined run-command.h
index 0211e1d471d37a41f77098e8fff8a031fb26cb71,e90d9282ff5a0a6dde2d1a9813063a7b2c7bcf91..15e870a65eb037cd49d1e01251711915da06d260
@@@ -10,6 -10,7 +10,7 @@@ enum 
        ERR_RUN_COMMAND_WAITPID_SIGNAL,
        ERR_RUN_COMMAND_WAITPID_NOEXIT,
  };
+ #define IS_RUN_COMMAND_ERR(x) ((x) <= -ERR_RUN_COMMAND_FORK)
  
  struct child_process {
        const char **argv;
@@@ -49,8 -50,6 +50,8 @@@ int start_command(struct child_process 
  int finish_command(struct child_process *);
  int run_command(struct child_process *);
  
 +extern int run_hook(const char *index_file, const char *name, ...);
 +
  #define RUN_COMMAND_NO_STDIN 1
  #define RUN_GIT_CMD        2  /*If this is to be git sub-command */
  #define RUN_COMMAND_STDOUT_TO_STDERR 4