Merge branch 'nd/dwim-wildcards-as-pathspecs'
authorJunio C Hamano <gitster@pobox.com>
Wed, 24 Feb 2016 21:25:52 +0000 (13:25 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 24 Feb 2016 21:25:52 +0000 (13:25 -0800)
"git show 'HEAD:Foo[BAR]Baz'" did not interpret the argument as a
rev, i.e. the object named by the the pathname with wildcard
characters in a tree object.

* nd/dwim-wildcards-as-pathspecs:
get_sha1: don't die() on bogus search strings
check_filename: tighten dwim-wildcard ambiguity
checkout: reorder check_filename conditional

1  2 
builtin/checkout.c
setup.c
sha1_name.c
diff --combined builtin/checkout.c
index 5af84a3118d20da437d3e08c1667f76dc2f57845,e5c16af9ef969f9d5de27bb76c6da9e75944e672..cfa66e25eb0838ba70d87d3008c6d3c57e363bd1
@@@ -18,8 -18,8 +18,8 @@@
  #include "xdiff-interface.h"
  #include "ll-merge.h"
  #include "resolve-undo.h"
 +#include "submodule-config.h"
  #include "submodule.h"
 -#include "argv-array.h"
  
  static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
@@@ -36,8 -36,6 +36,8 @@@ struct checkout_opts 
        int writeout_stage;
        int overwrite_ignore;
        int ignore_skipworktree;
 +      int ignore_other_worktrees;
 +      int show_progress;
  
        const char *new_branch;
        const char *new_branch_force;
@@@ -56,8 -54,8 +56,8 @@@ static int post_checkout_hook(struct co
                              int changed)
  {
        return run_hook_le(NULL, "post-checkout",
 -                         sha1_to_hex(old ? old->object.sha1 : null_sha1),
 -                         sha1_to_hex(new ? new->object.sha1 : null_sha1),
 +                         sha1_to_hex(old ? old->object.oid.hash : null_sha1),
 +                         sha1_to_hex(new ? new->object.oid.hash : null_sha1),
                           changed ? "1" : "0", NULL);
        /* "new" can be NULL when checking out from the index before
           a commit exists. */
@@@ -282,7 -280,7 +282,7 @@@ static int checkout_paths(const struct 
        if (opts->source_tree)
                read_tree_some(opts->source_tree, &opts->pathspec);
  
 -      ps_matched = xcalloc(1, opts->pathspec.nr);
 +      ps_matched = xcalloc(opts->pathspec.nr, 1);
  
        /*
         * Make sure all pathspecs participated in locating the paths
@@@ -401,7 -399,7 +401,7 @@@ static void describe_detached_head(cons
        if (!parse_commit(commit))
                pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
        fprintf(stderr, "%s %s... %s\n", msg,
 -              find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
 +              find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
        strbuf_release(&sb);
  }
  
@@@ -418,7 -416,7 +418,7 @@@ static int reset_tree(struct tree *tree
        opts.reset = 1;
        opts.merge = 1;
        opts.fn = oneway_merge;
 -      opts.verbose_update = !o->quiet && isatty(2);
 +      opts.verbose_update = o->show_progress;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
        parse_tree(tree);
@@@ -443,11 -441,6 +443,11 @@@ struct branch_info 
        const char *name; /* The short name used */
        const char *path; /* The full name of a real branch */
        struct commit *commit; /* The named commit */
 +      /*
 +       * if not null the branch is detached because it's already
 +       * checked out in this checkout
 +       */
 +      char *checkout;
  };
  
  static void setup_branch_path(struct branch_info *branch)
@@@ -502,7 -495,7 +502,7 @@@ static int merge_working_tree(const str
                topts.update = 1;
                topts.merge = 1;
                topts.gently = opts->merge && old->commit;
 -              topts.verbose_update = !opts->quiet && isatty(2);
 +              topts.verbose_update = opts->show_progress;
                topts.fn = twoway_merge;
                if (opts->overwrite_ignore) {
                        topts.dir = xcalloc(1, sizeof(*topts.dir));
                        setup_standard_excludes(topts.dir);
                }
                tree = parse_tree_indirect(old->commit ?
 -                                         old->commit->object.sha1 :
 +                                         old->commit->object.oid.hash :
                                           EMPTY_TREE_SHA1_BIN);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
 -              tree = parse_tree_indirect(new->commit->object.sha1);
 +              tree = parse_tree_indirect(new->commit->object.oid.hash);
                init_tree_desc(&trees[1], tree->buffer, tree->size);
  
                ret = unpack_trees(2, trees, &topts);
@@@ -612,20 -605,19 +612,20 @@@ static void update_refs_for_switch(cons
        if (opts->new_branch) {
                if (opts->new_orphan_branch) {
                        if (opts->new_branch_log && !log_all_ref_updates) {
 -                              int temp;
 -                              char log_file[PATH_MAX];
 -                              char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
 -
 -                              temp = log_all_ref_updates;
 -                              log_all_ref_updates = 1;
 -                              if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
 -                                      fprintf(stderr, _("Can not do reflog for '%s'\n"),
 -                                          opts->new_orphan_branch);
 -                                      log_all_ref_updates = temp;
 +                              int ret;
 +                              char *refname;
 +                              struct strbuf err = STRBUF_INIT;
 +
 +                              refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
 +                              ret = safe_create_reflog(refname, 1, &err);
 +                              free(refname);
 +                              if (ret) {
 +                                      fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
 +                                              opts->new_orphan_branch, err.buf);
 +                                      strbuf_release(&err);
                                        return;
                                }
 -                              log_all_ref_updates = temp;
 +                              strbuf_release(&err);
                        }
                }
                else
  
        old_desc = old->name;
        if (!old_desc && old->commit)
 -              old_desc = sha1_to_hex(old->commit->object.sha1);
 +              old_desc = oid_to_hex(&old->commit->object.oid);
  
        reflog_msg = getenv("GIT_REFLOG_ACTION");
        if (!reflog_msg)
        if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
                /* Nothing to do. */
        } else if (opts->force_detach || !new->path) {  /* No longer on any branch. */
 -              update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
 +              update_ref(msg.buf, "HEAD", new->commit->object.oid.hash, NULL,
                           REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path && advice_detached_head)
                        describe_detached_head(_("HEAD is now at"), new->commit);
                }
        } else if (new->path) { /* Switch branches. */
 -              create_symref("HEAD", new->path, msg.buf);
 +              if (create_symref("HEAD", new->path, msg.buf) < 0)
 +                      die("unable to update HEAD");
                if (!opts->quiet) {
                        if (old->path && !strcmp(new->path, old->path)) {
                                if (opts->new_branch_force)
  }
  
  static int add_pending_uninteresting_ref(const char *refname,
 -                                       const unsigned char *sha1,
 +                                       const struct object_id *oid,
                                         int flags, void *cb_data)
  {
 -      add_pending_sha1(cb_data, refname, sha1, UNINTERESTING);
 +      add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING);
        return 0;
  }
  
@@@ -705,7 -696,7 +705,7 @@@ static void describe_one_orphan(struct 
  {
        strbuf_addstr(sb, "  ");
        strbuf_addstr(sb,
 -              find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
 +              find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
        strbuf_addch(sb, ' ');
        if (!parse_commit(commit))
                pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
@@@ -752,18 -743,11 +752,18 @@@ static void suggest_reattach(struct com
  
        if (advice_detached_head)
                fprintf(stderr,
 -                      _(
 +                      Q_(
 +                      /* The singular version */
 +                      "If you want to keep it by creating a new branch, "
 +                      "this may be a good time\nto do so with:\n\n"
 +                      " git branch <new-branch-name> %s\n\n",
 +                      /* The plural version */
                        "If you want to keep them by creating a new branch, "
                        "this may be a good time\nto do so with:\n\n"
 -                      " git branch <new-branch-name> %s\n\n"),
 -                      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
 +                      " git branch <new-branch-name> %s\n\n",
 +                      /* Give ngettext() the count */
 +                      lost),
 +                      find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
  }
  
  /*
@@@ -781,10 -765,10 +781,10 @@@ static void orphaned_commit_warning(str
        setup_revisions(0, NULL, &revs, NULL);
  
        object->flags &= ~UNINTERESTING;
 -      add_pending_object(&revs, object, sha1_to_hex(object->sha1));
 +      add_pending_object(&revs, object, oid_to_hex(&object->oid));
  
        for_each_ref(add_pending_uninteresting_ref, &revs);
 -      add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING);
 +      add_pending_sha1(&revs, "HEAD", new->object.oid.hash, UNINTERESTING);
  
        refs = revs.pending;
        revs.leak_pending = 1;
@@@ -899,11 -883,10 +899,11 @@@ static const char *unique_tracking_name
  static int parse_branchname_arg(int argc, const char **argv,
                                int dwim_new_local_branch_ok,
                                struct branch_info *new,
 -                              struct tree **source_tree,
 -                              unsigned char rev[20],
 -                              const char **new_branch)
 +                              struct checkout_opts *opts,
 +                              unsigned char rev[20])
  {
 +      struct tree **source_tree = &opts->source_tree;
 +      const char **new_branch = &opts->new_branch;
        int argcount = 0;
        unsigned char branch_rev[20];
        const char *arg;
                 */
                int recover_with_dwim = dwim_new_local_branch_ok;
  
-               if (check_filename(NULL, arg) && !has_dash_dash)
+               if (!has_dash_dash &&
+                   (check_filename(NULL, arg) || !no_wildcard(arg)))
                        recover_with_dwim = 0;
                /*
                 * Accept "git checkout foo" and "git checkout foo --"
@@@ -1103,17 -1087,6 +1104,17 @@@ static int checkout_branch(struct check
                die(_("Cannot switch branch to a non-commit '%s'"),
                    new->name);
  
 +      if (new->path && !opts->force_detach && !opts->new_branch &&
 +          !opts->ignore_other_worktrees) {
 +              unsigned char sha1[20];
 +              int flag;
 +              char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
 +              if (head_ref &&
 +                  (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
 +                      die_if_checked_out(new->path);
 +              free(head_ref);
 +      }
 +
        if (!new->commit && opts->new_branch) {
                unsigned char rev[20];
                int flag;
@@@ -1156,9 -1129,6 +1157,9 @@@ int cmd_checkout(int argc, const char *
                         N_("do not limit pathspecs to sparse entries only")),
                OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
                                N_("second guess 'git checkout <no-such-branch>'")),
 +              OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
 +                       N_("do not check if another worktree is holding the given ref")),
 +              OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
                OPT_END(),
        };
  
        memset(&new, 0, sizeof(new));
        opts.overwrite_ignore = 1;
        opts.prefix = prefix;
 +      opts.show_progress = -1;
  
        gitmodules_config();
        git_config(git_checkout_config, &opts);
        argc = parse_options(argc, argv, prefix, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
  
 +      if (opts.show_progress < 0) {
 +              if (opts.quiet)
 +                      opts.show_progress = 0;
 +              else
 +                      opts.show_progress = isatty(2);
 +      }
 +
        if (conflict_style) {
                opts.merge = 1; /* implied */
                git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
                        opts.track == BRANCH_TRACK_UNSPECIFIED &&
                        !opts.new_branch;
                int n = parse_branchname_arg(argc, argv, dwim_ok,
 -                                           &new, &opts.source_tree,
 -                                           rev, &opts.new_branch);
 +                                           &new, &opts, rev);
                argv += n;
                argc -= n;
        }
diff --combined setup.c
index 0deb02238ba426144e0b7b077c9f2ad5c7993d23,69a8adaaf2899cb23eb97bde2bdfc825881a0ffc..59ec6587aa7e54b72d7a2f9fe0af51d66b2c8901
+++ b/setup.c
@@@ -4,8 -4,6 +4,8 @@@
  
  static int inside_git_dir = -1;
  static int inside_work_tree = -1;
 +static int work_tree_config_is_bogus;
 +static struct string_list unknown_extensions = STRING_LIST_INIT_DUP;
  
  /*
   * The input parameter must contain an absolute path, and it must already be
@@@ -100,7 -98,10 +100,7 @@@ char *prefix_path_gently(const char *pr
                        return NULL;
                }
        } else {
 -              sanitized = xmalloc(len + strlen(path) + 1);
 -              if (len)
 -                      memcpy(sanitized, prefix, len);
 -              strcpy(sanitized + len, path);
 +              sanitized = xstrfmt("%.*s%s", len, prefix, path);
                if (remaining_prefix)
                        *remaining_prefix = len;
                if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) {
@@@ -139,9 -140,7 +139,7 @@@ int check_filename(const char *prefix, 
                if (arg[2] == '\0') /* ":/" is root dir, always exists */
                        return 1;
                name = arg + 2;
-       } else if (!no_wildcard(arg))
-               return 1;
-       else if (prefix)
+       } else if (prefix)
                name = prefix_filename(prefix, strlen(prefix), arg);
        else
                name = arg;
@@@ -202,7 -201,7 +200,7 @@@ void verify_filename(const char *prefix
  {
        if (*arg == '-')
                die("bad flag '%s' used after filename", arg);
-       if (check_filename(prefix, arg))
+       if (check_filename(prefix, arg) || !no_wildcard(arg))
                return;
        die_verify_filename(prefix, arg, diagnose_misspelt_rev);
  }
@@@ -225,43 -224,6 +223,43 @@@ void verify_non_filename(const char *pr
            "'git <command> [<revision>...] -- [<file>...]'", arg);
  }
  
 +int get_common_dir(struct strbuf *sb, const char *gitdir)
 +{
 +      const char *git_env_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
 +      if (git_env_common_dir) {
 +              strbuf_addstr(sb, git_env_common_dir);
 +              return 1;
 +      } else {
 +              return get_common_dir_noenv(sb, gitdir);
 +      }
 +}
 +
 +int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)
 +{
 +      struct strbuf data = STRBUF_INIT;
 +      struct strbuf path = STRBUF_INIT;
 +      int ret = 0;
 +
 +      strbuf_addf(&path, "%s/commondir", gitdir);
 +      if (file_exists(path.buf)) {
 +              if (strbuf_read_file(&data, path.buf, 0) <= 0)
 +                      die_errno(_("failed to read %s"), path.buf);
 +              while (data.len && (data.buf[data.len - 1] == '\n' ||
 +                                  data.buf[data.len - 1] == '\r'))
 +                      data.len--;
 +              data.buf[data.len] = '\0';
 +              strbuf_reset(&path);
 +              if (!is_absolute_path(data.buf))
 +                      strbuf_addf(&path, "%s/", gitdir);
 +              strbuf_addbuf(&path, &data);
 +              strbuf_addstr(sb, real_path(path.buf));
 +              ret = 1;
 +      } else
 +              strbuf_addstr(sb, gitdir);
 +      strbuf_release(&data);
 +      strbuf_release(&path);
 +      return ret;
 +}
  
  /*
   * Test if it looks like we're at a git directory.
   */
  int is_git_directory(const char *suspect)
  {
 -      char path[PATH_MAX];
 -      size_t len = strlen(suspect);
 +      struct strbuf path = STRBUF_INIT;
 +      int ret = 0;
 +      size_t len;
 +
 +      /* Check worktree-related signatures */
 +      strbuf_addf(&path, "%s/HEAD", suspect);
 +      if (validate_headref(path.buf))
 +              goto done;
  
 -      if (PATH_MAX <= len + strlen("/objects"))
 -              die("Too long path: %.*s", 60, suspect);
 -      strcpy(path, suspect);
 +      strbuf_reset(&path);
 +      get_common_dir(&path, suspect);
 +      len = path.len;
 +
 +      /* Check non-worktree-related signatures */
        if (getenv(DB_ENVIRONMENT)) {
                if (access(getenv(DB_ENVIRONMENT), X_OK))
 -                      return 0;
 +                      goto done;
        }
        else {
 -              strcpy(path + len, "/objects");
 -              if (access(path, X_OK))
 -                      return 0;
 +              strbuf_setlen(&path, len);
 +              strbuf_addstr(&path, "/objects");
 +              if (access(path.buf, X_OK))
 +                      goto done;
        }
  
 -      strcpy(path + len, "/refs");
 -      if (access(path, X_OK))
 -              return 0;
 +      strbuf_setlen(&path, len);
 +      strbuf_addstr(&path, "/refs");
 +      if (access(path.buf, X_OK))
 +              goto done;
  
 -      strcpy(path + len, "/HEAD");
 -      if (validate_headref(path))
 -              return 0;
 +      ret = 1;
 +done:
 +      strbuf_release(&path);
 +      return ret;
 +}
  
 -      return 1;
 +int is_nonbare_repository_dir(struct strbuf *path)
 +{
 +      int ret = 0;
 +      int gitfile_error;
 +      size_t orig_path_len = path->len;
 +      assert(orig_path_len != 0);
 +      strbuf_complete(path, '/');
 +      strbuf_addstr(path, ".git");
 +      if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf))
 +              ret = 1;
 +      if (gitfile_error == READ_GITFILE_ERR_OPEN_FAILED ||
 +          gitfile_error == READ_GITFILE_ERR_READ_FAILED)
 +              ret = 1;
 +      strbuf_setlen(path, orig_path_len);
 +      return ret;
  }
  
  int is_inside_git_dir(void)
@@@ -350,10 -286,6 +348,10 @@@ void setup_work_tree(void
  
        if (initialized)
                return;
 +
 +      if (work_tree_config_is_bogus)
 +              die("unable to set up work tree using invalid config");
 +
        work_tree = get_git_work_tree();
        git_dir = get_git_dir();
        if (!is_absolute_path(git_dir))
        initialized = 1;
  }
  
 +static int check_repo_format(const char *var, const char *value, void *cb)
 +{
 +      const char *ext;
 +
 +      if (strcmp(var, "core.repositoryformatversion") == 0)
 +              repository_format_version = git_config_int(var, value);
 +      else if (strcmp(var, "core.sharedrepository") == 0)
 +              shared_repository = git_config_perm(var, value);
 +      else if (skip_prefix(var, "extensions.", &ext)) {
 +              /*
 +               * record any known extensions here; otherwise,
 +               * we fall through to recording it as unknown, and
 +               * check_repository_format will complain
 +               */
 +              if (!strcmp(ext, "noop"))
 +                      ;
 +              else if (!strcmp(ext, "preciousobjects"))
 +                      repository_format_precious_objects = git_config_bool(var, value);
 +              else
 +                      string_list_append(&unknown_extensions, ext);
 +      }
 +      return 0;
 +}
 +
  static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
  {
 -      char repo_config[PATH_MAX+1];
 +      struct strbuf sb = STRBUF_INIT;
 +      const char *repo_config;
 +      config_fn_t fn;
 +      int ret = 0;
 +
 +      string_list_clear(&unknown_extensions, 0);
 +
 +      if (get_common_dir(&sb, gitdir))
 +              fn = check_repo_format;
 +      else
 +              fn = check_repository_format_version;
 +      strbuf_addstr(&sb, "/config");
 +      repo_config = sb.buf;
  
        /*
         * git_config() can't be used here because it calls git_pathdup()
         * Use a gentler version of git_config() to check if this repo
         * is a good one.
         */
 -      snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
 -      git_config_early(check_repository_format_version, NULL, repo_config);
 -      if (GIT_REPO_VERSION < repository_format_version) {
 +      git_config_early(fn, NULL, repo_config);
 +      if (GIT_REPO_VERSION_READ < repository_format_version) {
                if (!nongit_ok)
                        die ("Expected git repo version <= %d, found %d",
 -                           GIT_REPO_VERSION, repository_format_version);
 +                           GIT_REPO_VERSION_READ, repository_format_version);
                warning("Expected git repo version <= %d, found %d",
 -                      GIT_REPO_VERSION, repository_format_version);
 +                      GIT_REPO_VERSION_READ, repository_format_version);
                warning("Please upgrade Git");
                *nongit_ok = -1;
 -              return -1;
 +              ret = -1;
        }
 -      return 0;
 +
 +      if (repository_format_version >= 1 && unknown_extensions.nr) {
 +              int i;
 +
 +              if (!nongit_ok)
 +                      die("unknown repository extension: %s",
 +                          unknown_extensions.items[0].string);
 +
 +              for (i = 0; i < unknown_extensions.nr; i++)
 +                      warning("unknown repository extension: %s",
 +                              unknown_extensions.items[i].string);
 +              *nongit_ok = -1;
 +              ret = -1;
 +      }
 +
 +      strbuf_release(&sb);
 +      return ret;
  }
  
  /*
   * Try to read the location of the git directory from the .git file,
   * return path to git directory if found.
 + *
 + * On failure, if return_error_code is not NULL, return_error_code
 + * will be set to an error code and NULL will be returned. If
 + * return_error_code is NULL the function will die instead (for most
 + * cases).
   */
 -const char *read_gitfile(const char *path)
 +const char *read_gitfile_gently(const char *path, int *return_error_code)
  {
 -      char *buf;
 -      char *dir;
 +      const int max_file_size = 1 << 20;  /* 1MB */
 +      int error_code = 0;
 +      char *buf = NULL;
 +      char *dir = NULL;
        const char *slash;
        struct stat st;
        int fd;
        ssize_t len;
  
 -      if (stat(path, &st))
 -              return NULL;
 -      if (!S_ISREG(st.st_mode))
 -              return NULL;
 +      if (stat(path, &st)) {
 +              error_code = READ_GITFILE_ERR_STAT_FAILED;
 +              goto cleanup_return;
 +      }
 +      if (!S_ISREG(st.st_mode)) {
 +              error_code = READ_GITFILE_ERR_NOT_A_FILE;
 +              goto cleanup_return;
 +      }
 +      if (st.st_size > max_file_size) {
 +              error_code = READ_GITFILE_ERR_TOO_LARGE;
 +              goto cleanup_return;
 +      }
        fd = open(path, O_RDONLY);
 -      if (fd < 0)
 -              die_errno("Error opening '%s'", path);
 +      if (fd < 0) {
 +              error_code = READ_GITFILE_ERR_OPEN_FAILED;
 +              goto cleanup_return;
 +      }
        buf = xmalloc(st.st_size + 1);
        len = read_in_full(fd, buf, st.st_size);
        close(fd);
 -      if (len != st.st_size)
 -              die("Error reading %s", path);
 +      if (len != st.st_size) {
 +              error_code = READ_GITFILE_ERR_READ_FAILED;
 +              goto cleanup_return;
 +      }
        buf[len] = '\0';
 -      if (!starts_with(buf, "gitdir: "))
 -              die("Invalid gitfile format: %s", path);
 +      if (!starts_with(buf, "gitdir: ")) {
 +              error_code = READ_GITFILE_ERR_INVALID_FORMAT;
 +              goto cleanup_return;
 +      }
        while (buf[len - 1] == '\n' || buf[len - 1] == '\r')
                len--;
 -      if (len < 9)
 -              die("No path in gitfile: %s", path);
 +      if (len < 9) {
 +              error_code = READ_GITFILE_ERR_NO_PATH;
 +              goto cleanup_return;
 +      }
        buf[len] = '\0';
        dir = buf + 8;
  
        if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
                size_t pathlen = slash+1 - path;
 -              size_t dirlen = pathlen + len - 8;
 -              dir = xmalloc(dirlen + 1);
 -              strncpy(dir, path, pathlen);
 -              strncpy(dir + pathlen, buf + 8, len - 8);
 -              dir[dirlen] = '\0';
 +              dir = xstrfmt("%.*s%.*s", (int)pathlen, path,
 +                            (int)(len - 8), buf + 8);
                free(buf);
                buf = dir;
        }
 -
 -      if (!is_git_directory(dir))
 -              die("Not a git repository: %s", dir);
 +      if (!is_git_directory(dir)) {
 +              error_code = READ_GITFILE_ERR_NOT_A_REPO;
 +              goto cleanup_return;
 +      }
        path = real_path(dir);
  
 +cleanup_return:
 +      if (return_error_code)
 +              *return_error_code = error_code;
 +      else if (error_code) {
 +              switch (error_code) {
 +              case READ_GITFILE_ERR_STAT_FAILED:
 +              case READ_GITFILE_ERR_NOT_A_FILE:
 +                      /* non-fatal; follow return path */
 +                      break;
 +              case READ_GITFILE_ERR_OPEN_FAILED:
 +                      die_errno("Error opening '%s'", path);
 +              case READ_GITFILE_ERR_TOO_LARGE:
 +                      die("Too large to be a .git file: '%s'", path);
 +              case READ_GITFILE_ERR_READ_FAILED:
 +                      die("Error reading %s", path);
 +              case READ_GITFILE_ERR_INVALID_FORMAT:
 +                      die("Invalid gitfile format: %s", path);
 +              case READ_GITFILE_ERR_NO_PATH:
 +                      die("No path in gitfile: %s", path);
 +              case READ_GITFILE_ERR_NOT_A_REPO:
 +                      die("Not a git repository: %s", dir);
 +              default:
 +                      assert(0);
 +              }
 +      }
 +
        free(buf);
 -      return path;
 +      return error_code ? NULL : path;
  }
  
  static const char *setup_explicit_git_dir(const char *gitdirenv,
        if (work_tree_env)
                set_git_work_tree(work_tree_env);
        else if (is_bare_repository_cfg > 0) {
 -              if (git_work_tree_cfg) /* #22.2, #30 */
 -                      die("core.bare and core.worktree do not make sense");
 +              if (git_work_tree_cfg) {
 +                      /* #22.2, #30 */
 +                      warning("core.bare and core.worktree do not make sense");
 +                      work_tree_config_is_bogus = 1;
 +              }
  
                /* #18, #26 */
                set_git_dir(gitdirenv);
@@@ -968,10 -799,11 +966,10 @@@ int git_config_perm(const char *var, co
  
  int check_repository_format_version(const char *var, const char *value, void *cb)
  {
 -      if (strcmp(var, "core.repositoryformatversion") == 0)
 -              repository_format_version = git_config_int(var, value);
 -      else if (strcmp(var, "core.sharedrepository") == 0)
 -              shared_repository = git_config_perm(var, value);
 -      else if (strcmp(var, "core.bare") == 0) {
 +      int ret = check_repo_format(var, value, cb);
 +      if (ret)
 +              return ret;
 +      if (strcmp(var, "core.bare") == 0) {
                is_bare_repository_cfg = git_config_bool(var, value);
                if (is_bare_repository_cfg == 1)
                        inside_work_tree = -1;
diff --combined sha1_name.c
index 89918ca158379a02368b0604b14465fa14855c8e,6a2195fcc1c29006b27305fd4d1d22976e752cdc..d0f844db897751adca937578185c7421095bbc1e
@@@ -6,7 -6,6 +6,7 @@@
  #include "tree-walk.h"
  #include "refs.h"
  #include "remote.h"
 +#include "dir.h"
  
  static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
  
@@@ -96,15 -95,11 +96,15 @@@ static void find_short_object_filename(
        }
        fakeent->next = alt_odb_list;
  
 -      sprintf(hex, "%.2s", hex_pfx);
 +      xsnprintf(hex, sizeof(hex), "%.2s", hex_pfx);
        for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) {
                struct dirent *de;
                DIR *dir;
 -              sprintf(alt->name, "%.2s/", hex_pfx);
 +              /*
 +               * every alt_odb struct has 42 extra bytes after the base
 +               * for exactly this purpose
 +               */
 +              xsnprintf(alt->name, 42, "%.2s/", hex_pfx);
                dir = opendir(alt->base);
                if (!dir)
                        continue;
@@@ -372,13 -367,14 +372,13 @@@ int for_each_abbrev(const char *prefix
        return ds.ambiguous;
  }
  
 -const char *find_unique_abbrev(const unsigned char *sha1, int len)
 +int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len)
  {
        int status, exists;
 -      static char hex[41];
  
 -      memcpy(hex, sha1_to_hex(sha1), 40);
 +      sha1_to_hex_r(hex, sha1);
        if (len == 40 || !len)
 -              return hex;
 +              return 40;
        exists = has_sha1_file(sha1);
        while (len < 40) {
                unsigned char sha1_ret[20];
                    ? !status
                    : status == SHORT_NAME_NOT_FOUND) {
                        hex[len] = 0;
 -                      return hex;
 +                      return len;
                }
                len++;
        }
 +      return len;
 +}
 +
 +const char *find_unique_abbrev(const unsigned char *sha1, int len)
 +{
 +      static char hex[GIT_SHA1_HEXSZ + 1];
 +      find_unique_abbrev_r(hex, sha1, len);
        return hex;
  }
  
@@@ -426,12 -415,12 +426,12 @@@ static int ambiguous_path(const char *p
        return slash;
  }
  
 -static inline int upstream_mark(const char *string, int len)
 +static inline int at_mark(const char *string, int len,
 +                        const char **suffix, int nr)
  {
 -      const char *suffix[] = { "@{upstream}", "@{u}" };
        int i;
  
 -      for (i = 0; i < ARRAY_SIZE(suffix); i++) {
 +      for (i = 0; i < nr; i++) {
                int suffix_len = strlen(suffix[i]);
                if (suffix_len <= len
                    && !memcmp(string, suffix[i], suffix_len))
        return 0;
  }
  
 +static inline int upstream_mark(const char *string, int len)
 +{
 +      const char *suffix[] = { "@{upstream}", "@{u}" };
 +      return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
 +}
 +
 +static inline int push_mark(const char *string, int len)
 +{
 +      const char *suffix[] = { "@{push}" };
 +      return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
 +}
 +
  static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
  static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
  
@@@ -499,8 -476,7 +499,8 @@@ static int get_sha1_basic(const char *s
                                        nth_prior = 1;
                                        continue;
                                }
 -                              if (!upstream_mark(str + at, len - at)) {
 +                              if (!upstream_mark(str + at, len - at) &&
 +                                  !push_mark(str + at, len - at)) {
                                        reflog_len = (len-1) - (at+2);
                                        len = at;
                                }
                                if (!(flags & GET_SHA1_QUIETLY)) {
                                        warning("Log for '%.*s' only goes "
                                                "back to %s.", len, str,
 -                                              show_date(co_time, co_tz, DATE_RFC2822));
 +                                              show_date(co_time, co_tz, DATE_MODE(RFC2822)));
                                }
                        } else {
                                if (flags & GET_SHA1_QUIETLY) {
@@@ -616,13 -592,13 +616,13 @@@ static int get_parent(const char *name
        if (parse_commit(commit))
                return -1;
        if (!idx) {
 -              hashcpy(result, commit->object.sha1);
 +              hashcpy(result, commit->object.oid.hash);
                return 0;
        }
        p = commit->parents;
        while (p) {
                if (!--idx) {
 -                      hashcpy(result, p->item->object.sha1);
 +                      hashcpy(result, p->item->object.oid.hash);
                        return 0;
                }
                p = p->next;
@@@ -649,7 -625,7 +649,7 @@@ static int get_nth_ancestor(const char 
                        return -1;
                commit = commit->parents->item;
        }
 -      hashcpy(result, commit->object.sha1);
 +      hashcpy(result, commit->object.oid.hash);
        return 0;
  }
  
@@@ -659,7 -635,7 +659,7 @@@ struct object *peel_to_type(const char 
        if (name && !namelen)
                namelen = strlen(name);
        while (1) {
 -              if (!o || (!o->parsed && !parse_object(o->sha1)))
 +              if (!o || (!o->parsed && !parse_object(o->oid.hash)))
                        return NULL;
                if (expected_type == OBJ_ANY || o->type == expected_type)
                        return o;
@@@ -736,9 -712,9 +736,9 @@@ static int peel_onion(const char *name
                return -1;
        if (!expected_type) {
                o = deref_tag(o, name, sp - name - 2);
 -              if (!o || (!o->parsed && !parse_object(o->sha1)))
 +              if (!o || (!o->parsed && !parse_object(o->oid.hash)))
                        return -1;
 -              hashcpy(sha1, o->sha1);
 +              hashcpy(sha1, o->oid.hash);
                return 0;
        }
  
        if (!o)
                return -1;
  
 -      hashcpy(sha1, o->sha1);
 +      hashcpy(sha1, o->oid.hash);
        if (sp[0] == '/') {
                /* "$commit^{/foo}" */
                char *prefix;
@@@ -848,22 -824,18 +848,22 @@@ static int get_sha1_1(const char *name
   * through history and returning the first commit whose message starts
   * the given regular expression.
   *
 - * For future extension, ':/!' is reserved. If you want to match a message
 - * beginning with a '!', you have to repeat the exclamation mark.
 + * For negative-matching, prefix the pattern-part with '!-', like: ':/!-WIP'.
 + *
 + * For a literal '!' character at the beginning of a pattern, you have to repeat
 + * that, like: ':/!!foo'
 + *
 + * For future extension, all other sequences beginning with ':/!' are reserved.
   */
  
  /* Remember to update object flag allocation in object.h */
  #define ONELINE_SEEN (1u<<20)
  
 -static int handle_one_ref(const char *path,
 -              const unsigned char *sha1, int flag, void *cb_data)
 +static int handle_one_ref(const char *path, const struct object_id *oid,
 +                        int flag, void *cb_data)
  {
        struct commit_list **list = cb_data;
 -      struct object *object = parse_object(sha1);
 +      struct object *object = parse_object(oid->hash);
        if (!object)
                return 0;
        if (object->type == OBJ_TAG) {
@@@ -882,22 -854,16 +882,22 @@@ static int get_sha1_oneline(const char 
  {
        struct commit_list *backup = NULL, *l;
        int found = 0;
 +      int negative = 0;
        regex_t regex;
  
        if (prefix[0] == '!') {
 -              if (prefix[1] != '!')
 -                      return -1;
                prefix++;
-                       die ("Invalid search pattern: %s", prefix);
 +
 +              if (prefix[0] == '-') {
 +                      prefix++;
 +                      negative = 1;
 +              } else if (prefix[0] != '!') {
++                      return -1;
 +              }
        }
  
        if (regcomp(&regex, prefix, REG_EXTENDED))
-               die("Invalid search pattern: %s", prefix);
+               return -1;
  
        for (l = list; l; l = l->next) {
                l->item->object.flags |= ONELINE_SEEN;
                int matches;
  
                commit = pop_most_recent_commit(&list, ONELINE_SEEN);
 -              if (!parse_object(commit->object.sha1))
 +              if (!parse_object(commit->object.oid.hash))
                        continue;
                buf = get_commit_buffer(commit, NULL);
                p = strstr(buf, "\n\n");
 -              matches = p && !regexec(&regex, p + 2, 0, NULL, 0);
 +              matches = negative ^ (p && !regexec(&regex, p + 2, 0, NULL, 0));
                unuse_commit_buffer(commit, buf);
  
                if (matches) {
 -                      hashcpy(sha1, commit->object.sha1);
 +                      hashcpy(sha1, commit->object.oid.hash);
                        found = 1;
                        break;
                }
@@@ -1032,7 -998,7 +1032,7 @@@ int get_sha1_mb(const char *name, unsig
                st = -1;
        else {
                st = 0;
 -              hashcpy(sha1, mbs->item->object.sha1);
 +              hashcpy(sha1, mbs->item->object.oid.hash);
        }
        free_commit_list(mbs);
        return st;
@@@ -1089,36 -1055,46 +1089,36 @@@ static void set_shortened_ref(struct st
        free(s);
  }
  
 -static const char *get_upstream_branch(const char *branch_buf, int len)
 -{
 -      char *branch = xstrndup(branch_buf, len);
 -      struct branch *upstream = branch_get(*branch ? branch : NULL);
 -
 -      /*
 -       * Upstream can be NULL only if branch refers to HEAD and HEAD
 -       * points to something different than a branch.
 -       */
 -      if (!upstream)
 -              die(_("HEAD does not point to a branch"));
 -      if (!upstream->merge || !upstream->merge[0]->dst) {
 -              if (!ref_exists(upstream->refname))
 -                      die(_("No such branch: '%s'"), branch);
 -              if (!upstream->merge) {
 -                      die(_("No upstream configured for branch '%s'"),
 -                              upstream->name);
 -              }
 -              die(
 -                      _("Upstream branch '%s' not stored as a remote-tracking branch"),
 -                      upstream->merge[0]->src);
 -      }
 -      free(branch);
 -
 -      return upstream->merge[0]->dst;
 -}
 -
 -static int interpret_upstream_mark(const char *name, int namelen,
 -                                 int at, struct strbuf *buf)
 +static int interpret_branch_mark(const char *name, int namelen,
 +                               int at, struct strbuf *buf,
 +                               int (*get_mark)(const char *, int),
 +                               const char *(*get_data)(struct branch *,
 +                                                       struct strbuf *))
  {
        int len;
 +      struct branch *branch;
 +      struct strbuf err = STRBUF_INIT;
 +      const char *value;
  
 -      len = upstream_mark(name + at, namelen - at);
 +      len = get_mark(name + at, namelen - at);
        if (!len)
                return -1;
  
        if (memchr(name, ':', at))
                return -1;
  
 -      set_shortened_ref(buf, get_upstream_branch(name, at));
 +      if (at) {
 +              char *name_str = xmemdupz(name, at);
 +              branch = branch_get(name_str);
 +              free(name_str);
 +      } else
 +              branch = branch_get(NULL);
 +
 +      value = get_data(branch, &err);
 +      if (!value)
 +              die("%s", err.buf);
 +
 +      set_shortened_ref(buf, value);
        return len + at;
  }
  
@@@ -1169,13 -1145,7 +1169,13 @@@ int interpret_branch_name(const char *n
                if (len > 0)
                        return reinterpret(name, namelen, len, buf);
  
 -              len = interpret_upstream_mark(name, namelen, at - name, buf);
 +              len = interpret_branch_mark(name, namelen, at - name, buf,
 +                                          upstream_mark, branch_get_upstream);
 +              if (len > 0)
 +                      return len;
 +
 +              len = interpret_branch_mark(name, namelen, at - name, buf,
 +                                          push_mark, branch_get_push);
                if (len > 0)
                        return len;
        }
@@@ -1267,13 -1237,14 +1267,13 @@@ static void diagnose_invalid_sha1_path(
                                       const char *object_name,
                                       int object_name_len)
  {
 -      struct stat st;
        unsigned char sha1[20];
        unsigned mode;
  
        if (!prefix)
                prefix = "";
  
 -      if (!lstat(filename, &st))
 +      if (file_exists(filename))
                die("Path '%s' exists on disk, but not in '%.*s'.",
                    filename, object_name_len, object_name);
        if (errno == ENOENT || errno == ENOTDIR) {
@@@ -1300,10 -1271,12 +1300,10 @@@ static void diagnose_invalid_index_path
                                        const char *prefix,
                                        const char *filename)
  {
 -      struct stat st;
        const struct cache_entry *ce;
        int pos;
        unsigned namelen = strlen(filename);
 -      unsigned fullnamelen;
 -      char *fullname;
 +      struct strbuf fullname = STRBUF_INIT;
  
        if (!prefix)
                prefix = "";
        }
  
        /* Confusion between relative and absolute filenames? */
 -      fullnamelen = namelen + strlen(prefix);
 -      fullname = xmalloc(fullnamelen + 1);
 -      strcpy(fullname, prefix);
 -      strcat(fullname, filename);
 -      pos = cache_name_pos(fullname, fullnamelen);
 +      strbuf_addstr(&fullname, prefix);
 +      strbuf_addstr(&fullname, filename);
 +      pos = cache_name_pos(fullname.buf, fullname.len);
        if (pos < 0)
                pos = -pos - 1;
        if (pos < active_nr) {
                ce = active_cache[pos];
 -              if (ce_namelen(ce) == fullnamelen &&
 -                  !memcmp(ce->name, fullname, fullnamelen))
 +              if (ce_namelen(ce) == fullname.len &&
 +                  !memcmp(ce->name, fullname.buf, fullname.len))
                        die("Path '%s' is in the index, but not '%s'.\n"
                            "Did you mean ':%d:%s' aka ':%d:./%s'?",
 -                          fullname, filename,
 -                          ce_stage(ce), fullname,
 +                          fullname.buf, filename,
 +                          ce_stage(ce), fullname.buf,
                            ce_stage(ce), filename);
        }
  
 -      if (!lstat(filename, &st))
 +      if (file_exists(filename))
                die("Path '%s' exists on disk, but not in the index.", filename);
        if (errno == ENOENT || errno == ENOTDIR)
                die("Path '%s' does not exist (neither on disk nor in the index).",
                    filename);
  
 -      free(fullname);
 +      strbuf_release(&fullname);
  }
  
  
@@@ -1396,7 -1371,6 +1396,7 @@@ static int get_sha1_with_context_1(cons
                int pos;
                if (!only_to_die && namelen > 2 && name[1] == '/') {
                        struct commit_list *list = NULL;
 +
                        for_each_ref(handle_one_ref, &list);
                        commit_list_sort_by_date(&list);
                        return get_sha1_oneline(name + 2, sha1, list);
                        new_filename = resolve_relative_path(filename);
                        if (new_filename)
                                filename = new_filename;
 -                      ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
 -                      if (ret && only_to_die) {
 -                              diagnose_invalid_sha1_path(prefix, filename,
 -                                                         tree_sha1,
 -                                                         name, len);
 +                      if (flags & GET_SHA1_FOLLOW_SYMLINKS) {
 +                              ret = get_tree_entry_follow_symlinks(tree_sha1,
 +                                      filename, sha1, &oc->symlink_path,
 +                                      &oc->mode);
 +                      } else {
 +                              ret = get_tree_entry(tree_sha1, filename,
 +                                                   sha1, &oc->mode);
 +                              if (ret && only_to_die) {
 +                                      diagnose_invalid_sha1_path(prefix,
 +                                                                 filename,
 +                                                                 tree_sha1,
 +                                                                 name, len);
 +                              }
                        }
                        hashcpy(oc->tree, tree_sha1);
                        strlcpy(oc->path, filename, sizeof(oc->path));
@@@ -1503,7 -1469,5 +1503,7 @@@ void maybe_die_on_misspelt_object_name(
  
  int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
  {
 +      if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
 +              die("BUG: incompatible flags for get_sha1_with_context");
        return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
  }