Merge branch 'js/alias-early-config'
authorJunio C Hamano <gitster@pobox.com>
Sat, 24 Jun 2017 21:28:39 +0000 (14:28 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 24 Jun 2017 21:28:40 +0000 (14:28 -0700)
The code to pick up and execute command alias definition from the
configuration used to switch to the top of the working tree and
then come back when the expanded alias was executed, which was
unnecessarilyl complex. Attempt to simplify the logic by using the
early-config mechanism that does not chdir around.

* js/alias-early-config:
alias: use the early config machinery to expand aliases
t7006: demonstrate a problem with aliases in subdirectories
t1308: relax the test verifying that empty alias values are disallowed
help: use early config when autocorrecting aliases
config: report correct line number upon error
discover_git_directory(): avoid setting invalid git_dir

1  2 
config.c
git.c
help.c
setup.c
t/t1300-repo-config.sh
t/t1308-config-set.sh
diff --combined config.c
index 34a139c40bd57ff2dd23318beca53fd2e2948000,3df7515db2e70e70a97b3a29dfd77d10dc462b3d..547daf87d40ebfe67272e913635826441dd97320
+++ b/config.c
@@@ -214,7 -214,6 +214,7 @@@ static int include_by_gitdir(const stru
        struct strbuf pattern = STRBUF_INIT;
        int ret = 0, prefix;
        const char *git_dir;
 +      int already_tried_absolute = 0;
  
        if (opts->git_dir)
                git_dir = opts->git_dir;
        strbuf_add(&pattern, cond, cond_len);
        prefix = prepare_include_condition_pattern(&pattern);
  
 +again:
        if (prefix < 0)
                goto done;
  
        ret = !wildmatch(pattern.buf + prefix, text.buf + prefix,
                         icase ? WM_CASEFOLD : 0, NULL);
  
 +      if (!ret && !already_tried_absolute) {
 +              /*
 +               * We've tried e.g. matching gitdir:~/work, but if
 +               * ~/work is a symlink to /mnt/storage/work
 +               * strbuf_realpath() will expand it, so the rule won't
 +               * match. Let's match against a
 +               * strbuf_add_absolute_path() version of the path,
 +               * which'll do the right thing
 +               */
 +              strbuf_reset(&text);
 +              strbuf_add_absolute_path(&text, git_dir);
 +              already_tried_absolute = 1;
 +              goto again;
 +      }
  done:
        strbuf_release(&pattern);
        strbuf_release(&text);
@@@ -604,7 -588,8 +604,8 @@@ static int get_value(config_fn_t fn, vo
         */
        cf->linenr--;
        ret = fn(name->buf, value, data);
-       cf->linenr++;
+       if (ret >= 0)
+               cf->linenr++;
        return ret;
  }
  
@@@ -1438,7 -1423,7 +1439,7 @@@ int git_config_from_file(config_fn_t fn
        int ret = -1;
        FILE *f;
  
 -      f = fopen(filename, "r");
 +      f = fopen_or_warn(filename, "r");
        if (f) {
                flockfile(f);
                ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
@@@ -1981,7 -1966,7 +1982,7 @@@ int git_config_get_expiry(const char *k
        if (ret)
                return ret;
        if (strcmp(*output, "now")) {
 -              unsigned long now = approxidate("now");
 +              timestamp_t now = approxidate("now");
                if (approxidate(*output) >= now)
                        git_die_config(key, _("Invalid %s: '%s'"), key, *output);
        }
@@@ -2637,7 -2622,7 +2638,7 @@@ int git_config_rename_section_in_file(c
        struct lock_file *lock;
        int out_fd;
        char buf[1024];
 -      FILE *config_file;
 +      FILE *config_file = NULL;
        struct stat st;
  
        if (new_name && !section_name_is_ok(new_name)) {
        }
  
        if (!(config_file = fopen(config_filename, "rb"))) {
 +              ret = warn_on_fopen_errors(config_filename);
 +              if (ret)
 +                      goto out;
                /* no config file means nothing to rename, no error */
                goto commit_and_out;
        }
                }
        }
        fclose(config_file);
 +      config_file = NULL;
  commit_and_out:
        if (commit_lock_file(lock) < 0)
                ret = error_errno("could not write config file %s",
                                  config_filename);
  out:
 +      if (config_file)
 +              fclose(config_file);
        rollback_lock_file(lock);
  out_no_rollback:
        free(filename_buf);
diff --combined git.c
index 1b8b7f51a65fa2a8e62d11d7ad99f19e2926977f,58ef570294da1ffc8fed38609d140833426dd14f..6150d6d281e5f804816bbbbd7d17d1df93aefc7d
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -16,53 -16,7 +16,9 @@@ const char git_more_info_string[] 
           "to read about a specific subcommand or concept.");
  
  static int use_pager = -1;
- static char *orig_cwd;
- static const char *env_names[] = {
-       GIT_DIR_ENVIRONMENT,
-       GIT_WORK_TREE_ENVIRONMENT,
-       GIT_IMPLICIT_WORK_TREE_ENVIRONMENT,
-       GIT_PREFIX_ENVIRONMENT
- };
- static char *orig_env[4];
- static int save_restore_env_balance;
  
- static void save_env_before_alias(void)
- {
-       int i;
-       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]);
-               orig_env[i] = xstrdup_or_null(orig_env[i]);
-       }
- }
- static void restore_env(int external_alias)
- {
-       int i;
-       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 (external_alias &&
-                   !strcmp(env_names[i], GIT_PREFIX_ENVIRONMENT))
-                       continue;
-               if (orig_env[i]) {
-                       setenv(env_names[i], orig_env[i], 1);
-                       free(orig_env[i]);
-               } else {
-                       unsetenv(env_names[i]);
-               }
-       }
- }
 +static void list_builtins(void);
 +
  static void commit_pager_choice(void) {
        switch (use_pager) {
        case 0:
@@@ -234,9 -188,6 +190,9 @@@ static int handle_options(const char **
                        }
                        (*argv)++;
                        (*argc)--;
 +              } else if (!strcmp(cmd, "--list-builtins")) {
 +                      list_builtins();
 +                      exit(0);
                } else {
                        fprintf(stderr, "Unknown option: %s\n", cmd);
                        usage(git_usage_string);
@@@ -255,19 -206,18 +211,18 @@@ static int handle_alias(int *argcp, con
        const char **new_argv;
        const char *alias_command;
        char *alias_string;
-       int unused_nongit;
-       save_env_before_alias();
-       setup_git_directory_gently(&unused_nongit);
  
        alias_command = (*argv)[0];
        alias_string = alias_lookup(alias_command);
        if (alias_string) {
                if (alias_string[0] == '!') {
                        struct child_process child = CHILD_PROCESS_INIT;
+                       int nongit_ok;
+                       /* Aliases expect GIT_PREFIX, GIT_DIR etc to be set */
+                       setup_git_directory_gently(&nongit_ok);
  
                        commit_pager_choice();
-                       restore_env(1);
  
                        child.use_shell = 1;
                        argv_array_push(&child.args, alias_string + 1);
                ret = 1;
        }
  
-       restore_env(0);
        errno = saved_errno;
  
        return ret;
@@@ -534,13 -482,6 +487,13 @@@ int is_builtin(const char *s
        return !!get_builtin(s);
  }
  
 +static void list_builtins(void)
 +{
 +      int i;
 +      for (i = 0; i < ARRAY_SIZE(commands); i++)
 +              printf("%s\n", commands[i].cmd);
 +}
 +
  #ifdef STRIP_EXTENSION
  static void strip_extension(const char **argv)
  {
diff --combined help.c
index f637fc80062094784d5734df91523642933ddcfc,91ec8ab36f1c31ffecbd41f1d227ee9e13047951..5cdfac7dc20d23089a1230c885dc1701a2caf1ac
--- 1/help.c
--- 2/help.c
+++ b/help.c
@@@ -1,7 -1,6 +1,7 @@@
  #include "cache.h"
  #include "builtin.h"
  #include "exec_cmd.h"
 +#include "run-command.h"
  #include "levenshtein.h"
  #include "help.h"
  #include "common-cmds.h"
@@@ -9,7 -8,6 +9,7 @@@
  #include "column.h"
  #include "version.h"
  #include "refs.h"
 +#include "parse-options.h"
  
  void add_cmdname(struct cmdnames *cmds, const char *name, int len)
  {
@@@ -98,6 -96,48 +98,6 @@@ static void pretty_print_cmdnames(struc
        string_list_clear(&list, 0);
  }
  
 -static int is_executable(const char *name)
 -{
 -      struct stat st;
 -
 -      if (stat(name, &st) || /* stat, not lstat */
 -          !S_ISREG(st.st_mode))
 -              return 0;
 -
 -#if defined(GIT_WINDOWS_NATIVE)
 -      /*
 -       * On Windows there is no executable bit. The file extension
 -       * indicates whether it can be run as an executable, and Git
 -       * has special-handling to detect scripts and launch them
 -       * through the indicated script interpreter. We test for the
 -       * file extension first because virus scanners may make
 -       * it quite expensive to open many files.
 -       */
 -      if (ends_with(name, ".exe"))
 -              return S_IXUSR;
 -
 -{
 -      /*
 -       * Now that we know it does not have an executable extension,
 -       * peek into the file instead.
 -       */
 -      char buf[3] = { 0 };
 -      int n;
 -      int fd = open(name, O_RDONLY);
 -      st.st_mode &= ~S_IXUSR;
 -      if (fd >= 0) {
 -              n = read(fd, buf, 2);
 -              if (n == 2)
 -                      /* look for a she-bang */
 -                      if (!strcmp(buf, "#!"))
 -                              st.st_mode |= S_IXUSR;
 -              close(fd);
 -      }
 -}
 -#endif
 -      return st.st_mode & S_IXUSR;
 -}
 -
  static void list_commands_in_dir(struct cmdnames *cmds,
                                         const char *path,
                                         const char *prefix)
@@@ -290,7 -330,7 +290,7 @@@ const char *help_unknown_cmd(const cha
        memset(&other_cmds, 0, sizeof(other_cmds));
        memset(&aliases, 0, sizeof(aliases));
  
-       git_config(git_unknown_cmd_config, NULL);
+       read_early_config(git_unknown_cmd_config, NULL);
  
        load_command_list("git-", &main_cmds, &other_cmds);
  
  
        if (SIMILAR_ENOUGH(best_similarity)) {
                fprintf_ln(stderr,
 -                         Q_("\nDid you mean this?",
 -                            "\nDid you mean one of these?",
 +                         Q_("\nThe most similar command is",
 +                            "\nThe most similar commands are",
                           n));
  
                for (i = 0; i < n; i++)
  
  int cmd_version(int argc, const char **argv, const char *prefix)
  {
 +      int build_options = 0;
 +      const char * const usage[] = {
 +              N_("git version [<options>]"),
 +              NULL
 +      };
 +      struct option options[] = {
 +              OPT_BOOL(0, "build-options", &build_options,
 +                       "also print build options"),
 +              OPT_END()
 +      };
 +
 +      argc = parse_options(argc, argv, prefix, options, usage, 0);
 +
        /*
         * The format of this string should be kept stable for compatibility
         * with external projects that rely on the output of "git version".
 +       *
 +       * Always show the version, even if other options are given.
         */
        printf("git version %s\n", git_version_string);
 -      while (*++argv) {
 -              if (!strcmp(*argv, "--build-options")) {
 -                      printf("sizeof-long: %d\n", (int)sizeof(long));
 -                      /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
 -              }
 +
 +      if (build_options) {
 +              printf("sizeof-long: %d\n", (int)sizeof(long));
 +              /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
        }
        return 0;
  }
diff --combined setup.c
index 751d02b9be627176009cdda4f1b20a3674b0dc95,ba3241bf026e87414d8f652c59fef0d120c92c19..ae3e0ddda22d9b517485a5c91b8d191c3e9ef92e
+++ b/setup.c
@@@ -134,27 -134,23 +134,27 @@@ int path_inside_repo(const char *prefix
  
  int check_filename(const char *prefix, const char *arg)
  {
 -      const char *name;
        char *to_free = NULL;
        struct stat st;
  
 -      if (starts_with(arg, ":/")) {
 -              if (arg[2] == '\0') /* ":/" is root dir, always exists */
 +      if (skip_prefix(arg, ":/", &arg)) {
 +              if (!*arg) /* ":/" is root dir, always exists */
                        return 1;
 -              name = arg + 2;
 -      } else if (prefix)
 -              name = to_free = prefix_filename(prefix, arg);
 -      else
 -              name = arg;
 -      if (!lstat(name, &st)) {
 +              prefix = NULL;
 +      } else if (skip_prefix(arg, ":!", &arg) ||
 +                 skip_prefix(arg, ":^", &arg)) {
 +              if (!*arg) /* excluding everything is silly, but allowed */
 +                      return 1;
 +      }
 +
 +      if (prefix)
 +              arg = to_free = prefix_filename(prefix, arg);
 +
 +      if (!lstat(arg, &st)) {
                free(to_free);
                return 1; /* file exists */
        }
 -      if (errno == ENOENT || errno == ENOTDIR) {
 +      if (is_missing_file_error(errno)) {
                free(to_free);
                return 0; /* file does not exist */
        }
@@@ -185,24 -181,6 +185,24 @@@ static void NORETURN die_verify_filenam
  
  }
  
 +/*
 + * Check for arguments that don't resolve as actual files,
 + * but which look sufficiently like pathspecs that we'll consider
 + * them such for the purposes of rev/pathspec DWIM parsing.
 + */
 +static int looks_like_pathspec(const char *arg)
 +{
 +      /* anything with a wildcard character */
 +      if (!no_wildcard(arg))
 +              return 1;
 +
 +      /* long-form pathspec magic */
 +      if (starts_with(arg, ":("))
 +              return 1;
 +
 +      return 0;
 +}
 +
  /*
   * Verify a filename that we got as an argument for a pathspec
   * entry. Note that a filename that begins with "-" never verifies
@@@ -229,7 -207,7 +229,7 @@@ void verify_filename(const char *prefix
  {
        if (*arg == '-')
                die("bad flag '%s' used after filename", arg);
 -      if (check_filename(prefix, arg) || !no_wildcard(arg))
 +      if (looks_like_pathspec(arg) || check_filename(prefix, arg))
                return;
        die_verify_filename(prefix, arg, diagnose_misspelt_rev);
  }
@@@ -725,16 -703,11 +725,16 @@@ static const char *setup_discovered_git
  
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
 +              char *to_free = NULL;
 +              const char *ret;
 +
                if (offset != cwd->len && !is_absolute_path(gitdir))
 -                      gitdir = real_pathdup(gitdir, 1);
 +                      gitdir = to_free = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
 -              return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
 +              ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok);
 +              free(to_free);
 +              return ret;
        }
  
        /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
@@@ -775,7 -748,7 +775,7 @@@ static const char *setup_bare_git_dir(s
  
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
 -              const char *gitdir;
 +              static const char *gitdir;
  
                gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
                if (chdir(cwd->buf))
@@@ -1004,6 -977,7 +1004,7 @@@ const char *discover_git_directory(stru
                warning("ignoring git dir '%s': %s",
                        gitdir->buf + gitdir_offset, err.buf);
                strbuf_release(&err);
+               strbuf_setlen(gitdir, gitdir_offset);
                return NULL;
        }
  
diff --combined t/t1300-repo-config.sh
index 13b7851f7c2feb87f63449d917380452e85c1f84,f664bfc67148a738be4a0ec58fc71b88344022b7..a37ef0422212eafdae4b4c0fae9e28d4a183117f
@@@ -703,6 -703,12 +703,12 @@@ test_expect_success 'invalid unit' 
        test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual
  '
  
+ test_expect_success 'line number is reported correctly' '
+       printf "[bool]\n\tvar\n" >invalid &&
+       test_must_fail git config -f invalid --path bool.var 2>actual &&
+       test_i18ngrep "line 2" actual
+ '
  test_expect_success 'invalid stdin config' '
        echo "[broken" | test_must_fail git config --list --file - >output 2>&1 &&
        test_i18ngrep "bad config line 1 in standard input" output
@@@ -1539,10 -1545,4 +1545,10 @@@ test_expect_success !MINGW '--show-orig
        test_cmp expect output
  '
  
 +test_expect_success '--local requires a repo' '
 +      # we expect 128 to ensure that we do not simply
 +      # fail to find anything and return code "1"
 +      test_expect_code 128 nongit git config --local foo.bar
 +'
 +
  test_done
diff --combined t/t1308-config-set.sh
index e495a616161660bcade644dea42b5c8e53cc64b3,69a0aa56d6d7b759e71a57d24b838fe21a544777..bafed5c9b88481992ca6cf4cd6c13596c7baf16b
@@@ -183,22 -183,11 +183,22 @@@ test_expect_success 'proper error on no
        test_cmp expect actual
  '
  
 +test_expect_success 'proper error on directory "files"' '
 +      echo "Error (-1) reading configuration file a-directory." >expect &&
 +      mkdir a-directory &&
 +      test_expect_code 2 test-config configset_get_value foo.bar a-directory 2>output &&
 +      grep "^warning:" output &&
 +      grep "^Error" output >actual &&
 +      test_cmp expect actual
 +'
 +
  test_expect_success POSIXPERM,SANITY 'proper error on non-accessible files' '
        chmod -r .git/config &&
        test_when_finished "chmod +r .git/config" &&
        echo "Error (-1) reading configuration file .git/config." >expect &&
 -      test_expect_code 2 test-config configset_get_value foo.bar .git/config 2>actual &&
 +      test_expect_code 2 test-config configset_get_value foo.bar .git/config 2>output &&
 +      grep "^warning:" output &&
 +      grep "^Error" output >actual &&
        test_cmp expect actual
  '
  
@@@ -226,7 -215,9 +226,9 @@@ test_expect_success 'check line errors 
                br
        EOF
        test_expect_code 128 git br 2>result &&
-       test_i18ngrep "fatal: .*alias\.br.*\.git/config.*line 2" result
+       test_i18ngrep "missing value for .alias\.br" result &&
+       test_i18ngrep "fatal: .*\.git/config" result &&
+       test_i18ngrep "fatal: .*line 2" result
  '
  
  test_expect_success 'error on modifying repo config without repo' '