Merge branch 'jc/am-state-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 31 Aug 2015 22:39:01 +0000 (15:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 31 Aug 2015 22:39:03 +0000 (15:39 -0700)
Recent reimplementation of "git am" changed the format of state
files kept in $GIT_DIR/rebase-apply/ without meaning to do so,
primarily because write_file() API was cumbersome to use and it was
easy to mistakenly make text files with incomplete lines.  Update
write_file() interface to make it harder to misuse.

* jc/am-state-fix:
  write_file(): drop caller-supplied LF from calls to create a one-liner file
  write_file_v(): do not leave incomplete line at the end
  write_file(): drop "fatal" parameter
  builtin/am: make sure state files are text
  builtin/am: introduce write_state_*() helper functions

1  2 
builtin/am.c
builtin/worktree.c
cache.h
submodule.c

diff --combined builtin/am.c
@@@ -10,7 -10,6 +10,7 @@@
  #include "dir.h"
  #include "run-command.h"
  #include "quote.h"
 +#include "tempfile.h"
  #include "lockfile.h"
  #include "cache-tree.h"
  #include "refs.h"
@@@ -194,6 -193,27 +194,27 @@@ static inline const char *am_path(cons
        return mkpath("%s/%s", state->dir, path);
  }
  
+ /**
+  * For convenience to call write_file()
+  */
+ static int write_state_text(const struct am_state *state,
+                           const char *name, const char *string)
+ {
+       return write_file(am_path(state, name), "%s", string);
+ }
+ static int write_state_count(const struct am_state *state,
+                            const char *name, int value)
+ {
+       return write_file(am_path(state, name), "%d", value);
+ }
+ static int write_state_bool(const struct am_state *state,
+                           const char *name, int value)
+ {
+       return write_state_text(state, name, value ? "t" : "f");
+ }
  /**
   * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
   * at the end.
@@@ -363,7 -383,7 +384,7 @@@ static void write_author_script(const s
        sq_quote_buf(&sb, state->author_date);
        strbuf_addch(&sb, '\n');
  
-       write_file(am_path(state, "author-script"), 1, "%s", sb.buf);
+       write_state_text(state, "author-script", sb.buf);
  
        strbuf_release(&sb);
  }
@@@ -1001,13 -1021,10 +1022,10 @@@ static void am_setup(struct am_state *s
        if (state->rebasing)
                state->threeway = 1;
  
-       write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f");
-       write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
-       write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f");
-       write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f");
+       write_state_bool(state, "threeway", state->threeway);
+       write_state_bool(state, "quiet", state->quiet);
+       write_state_bool(state, "sign", state->signoff);
+       write_state_bool(state, "utf8", state->utf8);
  
        switch (state->keep) {
        case KEEP_FALSE:
                die("BUG: invalid value for state->keep");
        }
  
-       write_file(am_path(state, "keep"), 1, "%s", str);
-       write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f");
+       write_state_text(state, "keep", str);
+       write_state_bool(state, "messageid", state->message_id);
  
        switch (state->scissors) {
        case SCISSORS_UNSET:
        default:
                die("BUG: invalid value for state->scissors");
        }
-       write_file(am_path(state, "scissors"), 1, "%s", str);
+       write_state_text(state, "scissors", str);
  
        sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
-       write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf);
+       write_state_text(state, "apply-opt", sb.buf);
  
        if (state->rebasing)
-               write_file(am_path(state, "rebasing"), 1, "%s", "");
+               write_state_text(state, "rebasing", "");
        else
-               write_file(am_path(state, "applying"), 1, "%s", "");
+               write_state_text(state, "applying", "");
  
        if (!get_sha1("HEAD", curr_head)) {
-               write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
+               write_state_text(state, "abort-safety", sha1_to_hex(curr_head));
                if (!state->rebasing)
                        update_ref("am", "ORIG_HEAD", curr_head, NULL, 0,
                                        UPDATE_REFS_DIE_ON_ERR);
        } else {
-               write_file(am_path(state, "abort-safety"), 1, "%s", "");
+               write_state_text(state, "abort-safety", "");
                if (!state->rebasing)
                        delete_ref("ORIG_HEAD", NULL, 0);
        }
         * session is in progress, they should be written last.
         */
  
-       write_file(am_path(state, "next"), 1, "%d", state->cur);
-       write_file(am_path(state, "last"), 1, "%d", state->last);
+       write_state_count(state, "next", state->cur);
+       write_state_count(state, "last", state->last);
  
        strbuf_release(&sb);
  }
@@@ -1102,12 -1116,12 +1117,12 @@@ static void am_next(struct am_state *st
        unlink(am_path(state, "original-commit"));
  
        if (!get_sha1("HEAD", head))
-               write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head));
+               write_state_text(state, "abort-safety", sha1_to_hex(head));
        else
-               write_file(am_path(state, "abort-safety"), 1, "%s", "");
+               write_state_text(state, "abort-safety", "");
  
        state->cur++;
-       write_file(am_path(state, "next"), 1, "%d", state->cur);
+       write_state_count(state, "next", state->cur);
  }
  
  /**
@@@ -1480,8 -1494,7 +1495,7 @@@ static int parse_mail_rebase(struct am_
        write_commit_patch(state, commit);
  
        hashcpy(state->orig_commit, commit_sha1);
-       write_file(am_path(state, "original-commit"), 1, "%s",
-                       sha1_to_hex(commit_sha1));
+       write_state_text(state, "original-commit", sha1_to_hex(commit_sha1));
  
        return 0;
  }
@@@ -1783,7 -1796,7 +1797,7 @@@ static void am_run(struct am_state *sta
        refresh_and_write_cache();
  
        if (index_has_changes(&sb)) {
-               write_file(am_path(state, "dirtyindex"), 1, "t");
+               write_state_bool(state, "dirtyindex", 1);
                die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
        }
  
@@@ -1962,49 -1975,16 +1976,49 @@@ static int fast_forward_to(struct tree 
        return 0;
  }
  
 +/**
 + * Merges a tree into the index. The index's stat info will take precedence
 + * over the merged tree's. Returns 0 on success, -1 on failure.
 + */
 +static int merge_tree(struct tree *tree)
 +{
 +      struct lock_file *lock_file;
 +      struct unpack_trees_options opts;
 +      struct tree_desc t[1];
 +
 +      if (parse_tree(tree))
 +              return -1;
 +
 +      lock_file = xcalloc(1, sizeof(struct lock_file));
 +      hold_locked_index(lock_file, 1);
 +
 +      memset(&opts, 0, sizeof(opts));
 +      opts.head_idx = 1;
 +      opts.src_index = &the_index;
 +      opts.dst_index = &the_index;
 +      opts.merge = 1;
 +      opts.fn = oneway_merge;
 +      init_tree_desc(&t[0], tree->buffer, tree->size);
 +
 +      if (unpack_trees(1, t, &opts)) {
 +              rollback_lock_file(lock_file);
 +              return -1;
 +      }
 +
 +      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
 +              die(_("unable to write new index file"));
 +
 +      return 0;
 +}
 +
  /**
   * Clean the index without touching entries that are not modified between
   * `head` and `remote`.
   */
  static int clean_index(const unsigned char *head, const unsigned char *remote)
  {
 -      struct lock_file *lock_file;
        struct tree *head_tree, *remote_tree, *index_tree;
        unsigned char index[GIT_SHA1_RAWSZ];
 -      struct pathspec pathspec;
  
        head_tree = parse_tree_indirect(head);
        if (!head_tree)
        if (fast_forward_to(index_tree, remote_tree, 0))
                return -1;
  
 -      memset(&pathspec, 0, sizeof(pathspec));
 -
 -      lock_file = xcalloc(1, sizeof(struct lock_file));
 -      hold_locked_index(lock_file, 1);
 -
 -      if (read_tree(remote_tree, 0, &pathspec)) {
 -              rollback_lock_file(lock_file);
 +      if (merge_tree(remote_tree))
                return -1;
 -      }
 -
 -      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
 -              die(_("unable to write new index file"));
  
        remove_branch_state();
  
@@@ -2191,7 -2181,7 +2205,7 @@@ int cmd_am(int argc, const char **argv
                OPT_BOOL('i', "interactive", &state.interactive,
                        N_("run interactively")),
                OPT_HIDDEN_BOOL('b', "binary", &binary,
 -                      N_("(historical option -- no-op")),
 +                      N_("historical option -- no-op")),
                OPT_BOOL('3', "3way", &state.threeway,
                        N_("allow fall back on 3way merging if needed")),
                OPT__QUIET(&state.quiet, N_("be quiet")),
diff --combined builtin/worktree.c
@@@ -3,8 -3,6 +3,8 @@@
  #include "dir.h"
  #include "parse-options.h"
  #include "argv-array.h"
 +#include "branch.h"
 +#include "refs.h"
  #include "run-command.h"
  #include "sigchain.h"
  #include "refs.h"
@@@ -15,13 -13,6 +15,13 @@@ static const char * const worktree_usag
        NULL
  };
  
 +struct add_opts {
 +      int force;
 +      int detach;
 +      const char *new_branch;
 +      int force_new_branch;
 +};
 +
  static int show_only;
  static int verbose;
  static unsigned long expire;
@@@ -181,35 -172,19 +181,35 @@@ static const char *worktree_basename(co
        return name;
  }
  
 -static int add_worktree(const char *path, const char **child_argv)
 +static int add_worktree(const char *path, const char *refname,
 +                      const struct add_opts *opts)
  {
        struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
        const char *name;
        struct stat st;
        struct child_process cp;
 +      struct argv_array child_env = ARGV_ARRAY_INIT;
        int counter = 0, len, ret;
 -      unsigned char rev[20];
 +      struct strbuf symref = STRBUF_INIT;
 +      struct commit *commit = NULL;
  
        if (file_exists(path) && !is_empty_dir(path))
                die(_("'%s' already exists"), path);
  
 +      /* is 'refname' a branch or commit? */
 +      if (opts->force_new_branch) /* definitely a branch */
 +              ;
 +      else if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
 +               ref_exists(symref.buf)) { /* it's a branch */
 +              if (!opts->force)
 +                      die_if_checked_out(symref.buf);
 +      } else { /* must be a commit */
 +              commit = lookup_commit_reference_by_name(refname);
 +              if (!commit)
 +                      die(_("invalid reference: %s"), refname);
 +      }
 +
        name = worktree_basename(path, &len);
        strbuf_addstr(&sb_repo,
                      git_path("worktrees/%.*s", (int)(path + len - name), name));
         * after the preparation is over.
         */
        strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-       write_file(sb.buf, 1, "initializing\n");
+       write_file(sb.buf, "initializing");
  
        strbuf_addf(&sb_git, "%s/.git", path);
        if (safe_create_leading_directories_const(sb_git.buf))
  
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-       write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
-       write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
+       write_file(sb.buf, "%s", real_path(sb_git.buf));
+       write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
                   real_path(get_git_common_dir()), name);
        /*
         * This is to keep resolve_ref() happy. We need a valid HEAD
 -       * or is_git_directory() will reject the directory. Moreover, HEAD
 -       * in the new worktree must resolve to the same value as HEAD in
 -       * the current tree since the command invoked to populate the new
 -       * worktree will be handed the branch/ref specified by the user.
 -       * For instance, if the user asks for the new worktree to be based
 -       * at HEAD~5, then the resolved HEAD~5 in the new worktree must
 -       * match the resolved HEAD~5 in the current tree in order to match
 -       * the user's expectation.
 +       * or is_git_directory() will reject the directory. Any value which
 +       * looks like an object ID will do since it will be immediately
 +       * replaced by the symbolic-ref or update-ref invocation in the new
 +       * worktree.
         */
 -      if (!resolve_ref_unsafe("HEAD", 0, rev, NULL))
 -              die(_("unable to resolve HEAD"));
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
-       write_file(sb.buf, 1, "0000000000000000000000000000000000000000\n");
 -      write_file(sb.buf, "%s", sha1_to_hex(rev));
++      write_file(sb.buf, "0000000000000000000000000000000000000000");
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
-       write_file(sb.buf, 1, "../..\n");
+       write_file(sb.buf, "../..");
  
 -      fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
 +      fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);
  
 -      setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
 -      setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
 -      setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
 +      argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
 +      argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
        memset(&cp, 0, sizeof(cp));
        cp.git_cmd = 1;
 -      cp.argv = child_argv;
 +
 +      if (commit)
 +              argv_array_pushl(&cp.args, "update-ref", "HEAD",
 +                               sha1_to_hex(commit->object.sha1), NULL);
 +      else
 +              argv_array_pushl(&cp.args, "symbolic-ref", "HEAD",
 +                               symref.buf, NULL);
 +      cp.env = child_env.argv;
 +      ret = run_command(&cp);
 +      if (ret)
 +              goto done;
 +
 +      cp.argv = NULL;
 +      argv_array_clear(&cp.args);
 +      argv_array_pushl(&cp.args, "reset", "--hard", NULL);
 +      cp.env = child_env.argv;
        ret = run_command(&cp);
        if (!ret) {
                is_junk = 0;
                junk_work_tree = NULL;
                junk_git_dir = NULL;
        }
 +done:
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/locked", sb_repo.buf);
        unlink_or_warn(sb.buf);
 +      argv_array_clear(&child_env);
        strbuf_release(&sb);
 +      strbuf_release(&symref);
        strbuf_release(&sb_repo);
        strbuf_release(&sb_git);
        return ret;
  
  static int add(int ac, const char **av, const char *prefix)
  {
 -      int force = 0, detach = 0;
 -      const char *new_branch = NULL, *new_branch_force = NULL;
 +      struct add_opts opts;
 +      const char *new_branch_force = NULL;
        const char *path, *branch;
 -      struct argv_array cmd = ARGV_ARRAY_INIT;
        struct option options[] = {
 -              OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
 -              OPT_STRING('b', NULL, &new_branch, N_("branch"),
 +              OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
 +              OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
                           N_("create a new branch")),
                OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
                           N_("create or reset a branch")),
 -              OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")),
 +              OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
                OPT_END()
        };
  
 +      memset(&opts, 0, sizeof(opts));
        ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
 -      if (new_branch && new_branch_force)
 -              die(_("-b and -B are mutually exclusive"));
 +      if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1)
 +              die(_("-b, -B, and --detach are mutually exclusive"));
        if (ac < 1 || ac > 2)
                usage_with_options(worktree_usage, options);
  
        path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
        branch = ac < 2 ? "HEAD" : av[1];
  
 -      if (ac < 2 && !new_branch && !new_branch_force) {
 +      opts.force_new_branch = !!new_branch_force;
 +      if (opts.force_new_branch)
 +              opts.new_branch = new_branch_force;
 +
 +      if (ac < 2 && !opts.new_branch && !opts.detach) {
                int n;
                const char *s = worktree_basename(path, &n);
 -              new_branch = xstrndup(s, n);
 +              opts.new_branch = xstrndup(s, n);
 +      }
 +
 +      if (opts.new_branch) {
 +              struct child_process cp;
 +              memset(&cp, 0, sizeof(cp));
 +              cp.git_cmd = 1;
 +              argv_array_push(&cp.args, "branch");
 +              if (opts.force_new_branch)
 +                      argv_array_push(&cp.args, "--force");
 +              argv_array_push(&cp.args, opts.new_branch);
 +              argv_array_push(&cp.args, branch);
 +              if (run_command(&cp))
 +                      return -1;
 +              branch = opts.new_branch;
        }
  
 -      argv_array_push(&cmd, "checkout");
 -      if (force)
 -              argv_array_push(&cmd, "--ignore-other-worktrees");
 -      if (new_branch)
 -              argv_array_pushl(&cmd, "-b", new_branch, NULL);
 -      if (new_branch_force)
 -              argv_array_pushl(&cmd, "-B", new_branch_force, NULL);
 -      if (detach)
 -              argv_array_push(&cmd, "--detach");
 -      argv_array_push(&cmd, branch);
 -
 -      return add_worktree(path, cmd.argv);
 +      return add_worktree(path, branch, &opts);
  }
  
  int cmd_worktree(int ac, const char **av, const char *prefix)
diff --combined cache.h
+++ b/cache.h
@@@ -708,59 -708,22 +708,59 @@@ extern int check_repository_format(void
  #define DATA_CHANGED    0x0020
  #define TYPE_CHANGED    0x0040
  
 +/*
 + * Return a statically allocated filename, either generically (mkpath), in
 + * the repository directory (git_path), or in a submodule's repository
 + * directory (git_path_submodule). In all cases, note that the result
 + * may be overwritten by another call to _any_ of the functions. Consider
 + * using the safer "dup" or "strbuf" formats below (in some cases, the
 + * unsafe versions have already been removed).
 + */
 +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +
  extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
  extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 +extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
 +                                    const char *fmt, ...)
 +      __attribute__((format (printf, 3, 4)));
  extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
  extern char *mkpathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
 -
 -/* Return a statically allocated filename matching the sha1 signature */
 -extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_path_submodule(const char *path, const char *fmt, ...)
 +extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 +
  extern void report_linked_checkout_garbage(void);
  
 +/*
 + * You can define a static memoized git path like:
 + *
 + *    static GIT_PATH_FUNC(git_path_foo, "FOO");
 + *
 + * or use one of the global ones below.
 + */
 +#define GIT_PATH_FUNC(func, filename) \
 +      const char *func(void) \
 +      { \
 +              static char *ret; \
 +              if (!ret) \
 +                      ret = git_pathdup(filename); \
 +              return ret; \
 +      }
 +
 +const char *git_path_cherry_pick_head(void);
 +const char *git_path_revert_head(void);
 +const char *git_path_squash_msg(void);
 +const char *git_path_merge_msg(void);
 +const char *git_path_merge_rr(void);
 +const char *git_path_merge_mode(void);
 +const char *git_path_merge_head(void);
 +const char *git_path_fetch_head(void);
 +const char *git_path_shallow(void);
 +
  /*
   * Return the name of the file in the local object database that would
   * be used to store a loose object with the specified sha1.  The
@@@ -982,7 -945,7 +982,7 @@@ extern int do_check_packed_object_crc
  
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
 -extern int move_temp_to_file(const char *tmpfile, const char *filename);
 +extern int finalize_object_file(const char *tmpfile, const char *filename);
  
  extern int has_sha1_pack(const unsigned char *sha1);
  
@@@ -1440,7 -1403,6 +1440,7 @@@ extern int git_config_pathname(const ch
  extern int git_config_set_in_file(const char *, const char *, const char *);
  extern int git_config_set(const char *, const char *);
  extern int git_config_parse_key(const char *, char **, int *);
 +extern int git_config_key_is_valid(const char *key);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
@@@ -1577,8 -1539,9 +1577,9 @@@ static inline ssize_t write_str_in_full
  {
        return write_in_full(fd, str, strlen(str));
  }
- __attribute__((format (printf, 3, 4)))
- extern int write_file(const char *path, int fatal, const char *fmt, ...);
+ extern int write_file(const char *path, const char *fmt, ...);
+ extern int write_file_gently(const char *path, const char *fmt, ...);
  
  /* pager.c */
  extern void setup_pager(void);
diff --combined submodule.c
@@@ -1,5 -1,4 +1,5 @@@
  #include "cache.h"
 +#include "submodule-config.h"
  #include "submodule.h"
  #include "dir.h"
  #include "diff.h"
@@@ -13,6 -12,9 +13,6 @@@
  #include "argv-array.h"
  #include "blob.h"
  
 -static struct string_list config_name_for_path;
 -static struct string_list config_fetch_recurse_submodules_for_name;
 -static struct string_list config_ignore_for_name;
  static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
  static struct string_list changed_submodule_paths;
  static int initialized_fetch_ref_tips;
@@@ -39,6 -41,7 +39,6 @@@ static int gitmodules_is_unmerged
   */
  static int gitmodules_is_modified;
  
 -
  int is_staging_gitmodules_ok(void)
  {
        return !gitmodules_is_modified;
@@@ -52,7 -55,7 +52,7 @@@
  int update_path_in_gitmodules(const char *oldpath, const char *newpath)
  {
        struct strbuf entry = STRBUF_INIT;
 -      struct string_list_item *path_option;
 +      const struct submodule *submodule;
  
        if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
                return -1;
        if (gitmodules_is_unmerged)
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      path_option = unsorted_string_list_lookup(&config_name_for_path, oldpath);
 -      if (!path_option) {
 +      submodule = submodule_from_path(null_sha1, oldpath);
 +      if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
                return -1;
        }
        strbuf_addstr(&entry, "submodule.");
 -      strbuf_addstr(&entry, path_option->util);
 +      strbuf_addstr(&entry, submodule->name);
        strbuf_addstr(&entry, ".path");
        if (git_config_set_in_file(".gitmodules", entry.buf, newpath) < 0) {
                /* Maybe the user already did that, don't error out here */
@@@ -86,7 -89,7 +86,7 @@@
  int remove_path_from_gitmodules(const char *path)
  {
        struct strbuf sect = STRBUF_INIT;
 -      struct string_list_item *path_option;
 +      const struct submodule *submodule;
  
        if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
                return -1;
        if (gitmodules_is_unmerged)
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      path_option = unsorted_string_list_lookup(&config_name_for_path, path);
 -      if (!path_option) {
 +      submodule = submodule_from_path(null_sha1, path);
 +      if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), path);
                return -1;
        }
        strbuf_addstr(&sect, "submodule.");
 -      strbuf_addstr(&sect, path_option->util);
 +      strbuf_addstr(&sect, submodule->name);
        if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) {
                /* Maybe the user already did that, don't error out here */
                warning(_("Could not remove .gitmodules entry for %s"), path);
@@@ -162,10 -165,12 +162,10 @@@ done
  void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                                             const char *path)
  {
 -      struct string_list_item *path_option, *ignore_option;
 -      path_option = unsorted_string_list_lookup(&config_name_for_path, path);
 -      if (path_option) {
 -              ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util);
 -              if (ignore_option)
 -                      handle_ignore_submodules_arg(diffopt, ignore_option->util);
 +      const struct submodule *submodule = submodule_from_path(null_sha1, path);
 +      if (submodule) {
 +              if (submodule->ignore)
 +                      handle_ignore_submodules_arg(diffopt, submodule->ignore);
                else if (gitmodules_is_unmerged)
                        DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
        }
@@@ -214,6 -219,58 +214,6 @@@ void gitmodules_config(void
        }
  }
  
 -int parse_submodule_config_option(const char *var, const char *value)
 -{
 -      struct string_list_item *config;
 -      const char *name, *key;
 -      int namelen;
 -
 -      if (parse_config_key(var, "submodule", &name, &namelen, &key) < 0 || !name)
 -              return 0;
 -
 -      if (!strcmp(key, "path")) {
 -              if (!value)
 -                      return config_error_nonbool(var);
 -
 -              config = unsorted_string_list_lookup(&config_name_for_path, value);
 -              if (config)
 -                      free(config->util);
 -              else
 -                      config = string_list_append(&config_name_for_path, xstrdup(value));
 -              config->util = xmemdupz(name, namelen);
 -      } else if (!strcmp(key, "fetchrecursesubmodules")) {
 -              char *name_cstr = xmemdupz(name, namelen);
 -              config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name_cstr);
 -              if (!config)
 -                      config = string_list_append(&config_fetch_recurse_submodules_for_name, name_cstr);
 -              else
 -                      free(name_cstr);
 -              config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value);
 -      } else if (!strcmp(key, "ignore")) {
 -              char *name_cstr;
 -
 -              if (!value)
 -                      return config_error_nonbool(var);
 -
 -              if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
 -                  strcmp(value, "all") && strcmp(value, "none")) {
 -                      warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var);
 -                      return 0;
 -              }
 -
 -              name_cstr = xmemdupz(name, namelen);
 -              config = unsorted_string_list_lookup(&config_ignore_for_name, name_cstr);
 -              if (config) {
 -                      free(config->util);
 -                      free(name_cstr);
 -              } else
 -                      config = string_list_append(&config_ignore_for_name, name_cstr);
 -              config->util = xstrdup(value);
 -              return 0;
 -      }
 -      return 0;
 -}
 -
  void handle_ignore_submodules_arg(struct diff_options *diffopt,
                                  const char *arg)
  {
@@@ -288,6 -345,20 +288,6 @@@ static void print_submodule_summary(str
        strbuf_release(&sb);
  }
  
 -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg)
 -{
 -      switch (git_config_maybe_bool(opt, arg)) {
 -      case 1:
 -              return RECURSE_SUBMODULES_ON;
 -      case 0:
 -              return RECURSE_SUBMODULES_OFF;
 -      default:
 -              if (!strcmp(arg, "on-demand"))
 -                      return RECURSE_SUBMODULES_ON_DEMAND;
 -              die("bad %s argument: %s", opt, arg);
 -      }
 -}
 -
  void show_submodule_summary(FILE *f, const char *path,
                const char *line_prefix,
                unsigned char one[20], unsigned char two[20],
@@@ -575,7 -646,7 +575,7 @@@ static void calculate_changed_submodule
        struct argv_array argv = ARGV_ARRAY_INIT;
  
        /* No need to check if there are no submodules configured */
 -      if (!config_name_for_path.nr)
 +      if (!submodule_from_path(NULL, NULL))
                return;
  
        init_revisions(&rev, NULL);
@@@ -622,6 -693,7 +622,6 @@@ int fetch_populated_submodules(const st
        int i, result = 0;
        struct child_process cp = CHILD_PROCESS_INIT;
        struct argv_array argv = ARGV_ARRAY_INIT;
 -      struct string_list_item *name_for_path;
        const char *work_tree = get_git_work_tree();
        if (!work_tree)
                goto out;
                struct strbuf submodule_git_dir = STRBUF_INIT;
                struct strbuf submodule_prefix = STRBUF_INIT;
                const struct cache_entry *ce = active_cache[i];
 -              const char *git_dir, *name, *default_argv;
 +              const char *git_dir, *default_argv;
 +              const struct submodule *submodule;
  
                if (!S_ISGITLINK(ce->ce_mode))
                        continue;
  
 -              name = ce->name;
 -              name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name);
 -              if (name_for_path)
 -                      name = name_for_path->util;
 +              submodule = submodule_from_path(null_sha1, ce->name);
 +              if (!submodule)
 +                      submodule = submodule_from_name(null_sha1, ce->name);
  
                default_argv = "yes";
                if (command_line_option == RECURSE_SUBMODULES_DEFAULT) {
 -                      struct string_list_item *fetch_recurse_submodules_option;
 -                      fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name);
 -                      if (fetch_recurse_submodules_option) {
 -                              if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_OFF)
 +                      if (submodule &&
 +                          submodule->fetch_recurse !=
 +                                              RECURSE_SUBMODULES_NONE) {
 +                              if (submodule->fetch_recurse ==
 +                                              RECURSE_SUBMODULES_OFF)
                                        continue;
 -                              if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_ON_DEMAND) {
 +                              if (submodule->fetch_recurse ==
 +                                              RECURSE_SUBMODULES_ON_DEMAND) {
                                        if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
                                                continue;
                                        default_argv = "on-demand";
@@@ -1033,7 -1103,7 +1033,7 @@@ void connect_work_tree_and_git_dir(cons
  
        /* Update gitfile */
        strbuf_addf(&file_name, "%s/.git", work_tree);
-       write_file(file_name.buf, 1, "gitdir: %s\n",
+       write_file(file_name.buf, "gitdir: %s",
                   relative_path(git_dir, real_work_tree, &rel_path));
  
        /* Update core.worktree setting */