Merge branch 'tg/add-chmod+x-fix' into maint
authorJunio C Hamano <gitster@pobox.com>
Thu, 29 Sep 2016 23:49:47 +0000 (16:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 29 Sep 2016 23:49:47 +0000 (16:49 -0700)
"git add --chmod=+x <pathspec>" added recently only toggled the
executable bit for paths that are either new or modified. This has
been corrected to flip the executable bit for all paths that match
the given pathspec.

* tg/add-chmod+x-fix:
t3700-add: do not check working tree file mode without POSIXPERM
t3700-add: create subdirectory gently
add: modify already added files when --chmod is given
read-cache: introduce chmod_index_entry
update-index: add test for chmod flags

1  2 
builtin/checkout.c
builtin/commit.c
builtin/update-index.c
cache.h
read-cache.c
diff --combined builtin/checkout.c
index 0ad96786c9a257e2b4f3fd8325c68ae183c4b0f9,3398c61e9a64ab686bf59fb027277f3de578b5fb..32cf317ec244f0f08d2a2174e3e5b6279cde5b85
@@@ -154,8 -154,8 +154,8 @@@ static int check_stages(unsigned stages
        return 0;
  }
  
 -static int checkout_stage(int stage, struct cache_entry *ce, int pos,
 -                        struct checkout *state)
 +static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
 +                        const struct checkout *state)
  {
        while (pos < active_nr &&
               !strcmp(active_cache[pos]->name, ce->name)) {
                return error(_("path '%s' does not have their version"), ce->name);
  }
  
 -static int checkout_merged(int pos, struct checkout *state)
 +static int checkout_merged(int pos, const struct checkout *state)
  {
        struct cache_entry *ce = active_cache[pos];
        const char *path = ce->name;
@@@ -276,7 -276,7 +276,7 @@@ static int checkout_paths(const struct 
  
        hold_locked_index(lock_file, 1);
        if (read_cache_preload(&opts->pathspec) < 0)
 -              return error(_("corrupt index file"));
 +              return error(_("index file corrupt"));
  
        if (opts->source_tree)
                read_tree_some(opts->source_tree, &opts->pathspec);
@@@ -470,7 -470,7 +470,7 @@@ static int merge_working_tree(const str
  
        hold_locked_index(lock_file, 1);
        if (read_cache_preload(NULL) < 0)
 -              return error(_("corrupt index file"));
 +              return error(_("index file corrupt"));
  
        resolve_undo_clear();
        if (opts->force) {
                         * entries in the index.
                         */
  
-                       add_files_to_cache(NULL, NULL, 0, 0);
+                       add_files_to_cache(NULL, NULL, 0);
                        /*
                         * NEEDSWORK: carrying over local changes
                         * when branches have different end-of-line
                        o.ancestor = old->name;
                        o.branch1 = new->name;
                        o.branch2 = "local";
 -                      merge_trees(&o, new->commit->tree, work,
 +                      ret = merge_trees(&o, new->commit->tree, work,
                                old->commit->tree, &result);
 +                      if (ret < 0)
 +                              exit(128);
                        ret = reset_tree(new->commit->tree, opts, 0,
                                         writeout_error);
 +                      strbuf_release(&o.obuf);
                        if (ret)
                                return ret;
                }
@@@ -658,8 -655,7 +658,8 @@@ static void update_refs_for_switch(cons
                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)
 +                      if (old->path &&
 +                          advice_detached_head && !opts->force_detach)
                                detach_advice(new->name);
                        describe_detached_head(_("HEAD is now at"), new->commit);
                }
@@@ -707,7 -703,8 +707,7 @@@ static int add_pending_uninteresting_re
  static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
  {
        strbuf_addstr(sb, "  ");
 -      strbuf_addstr(sb,
 -              find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
 +      strbuf_add_unique_abbrev(sb, commit->object.oid.hash, DEFAULT_ABBREV);
        strbuf_addch(sb, ' ');
        if (!parse_commit(commit))
                pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
@@@ -985,7 -982,7 +985,7 @@@ static int parse_branchname_arg(int arg
                int recover_with_dwim = dwim_new_local_branch_ok;
  
                if (!has_dash_dash &&
 -                  (check_filename(NULL, arg) || !no_wildcard(arg)))
 +                  (check_filename(opts->prefix, arg) || !no_wildcard(arg)))
                        recover_with_dwim = 0;
                /*
                 * Accept "git checkout foo" and "git checkout foo --"
  
        if (!*source_tree)                   /* case (1): want a tree */
                die(_("reference is not a tree: %s"), arg);
 -      if (!has_dash_dash) {/* case (3).(d) -> (1) */
 +      if (!has_dash_dash) {   /* case (3).(d) -> (1) */
                /*
                 * Do not complain the most common case
                 *      git checkout branch
                 * it would be extremely annoying.
                 */
                if (argc)
 -                      verify_non_filename(NULL, arg);
 +                      verify_non_filename(opts->prefix, arg);
        } else {
                argcount++;
                argv++;
@@@ -1141,7 -1138,7 +1141,7 @@@ int cmd_checkout(int argc, const char *
                OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
                           N_("create/reset and checkout a branch")),
                OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
 -              OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
 +              OPT_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")),
                OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
                        BRANCH_TRACK_EXPLICIT),
                OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --combined builtin/commit.c
index 77e3dc849419e697abe8f2f4c7d753cce17634a8,443ff9196d16a9b6fc2d1d8b22393b1533faeb8f..7a1ade0d2771cdbd21524381fae0c43a9d0c20cd
@@@ -92,9 -92,8 +92,9 @@@ N_("If you wish to skip this commit, us
  "Then \"git cherry-pick --continue\" will resume cherry-picking\n"
  "the remaining commits.\n");
  
 +static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
 +
  static const char *use_message_buffer;
 -static const char commit_editmsg[] = "COMMIT_EDITMSG";
  static struct lock_file index_lock; /* real index */
  static struct lock_file false_lock; /* used only for partial commits */
  static enum {
@@@ -387,7 -386,7 +387,7 @@@ static const char *prepare_index(int ar
         */
        if (all || (also && pathspec.nr)) {
                hold_locked_index(&index_lock, 1);
-               add_files_to_cache(also ? prefix : NULL, &pathspec, 0, 0);
+               add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
                refresh_cache_or_die(refresh_flags);
                update_main_cache_tree(WRITE_TREE_SILENT);
                if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
@@@ -715,7 -714,7 +715,7 @@@ static int prepare_to_commit(const cha
                char *buffer;
                buffer = strstr(use_message_buffer, "\n\n");
                if (buffer)
 -                      strbuf_addstr(&sb, buffer + 2);
 +                      strbuf_addstr(&sb, skip_blank_lines(buffer + 2));
                hook_arg1 = "commit";
                hook_arg2 = use_message;
        } else if (fixup_message) {
                hook_arg2 = "";
        }
  
 -      s->fp = fopen_for_writing(git_path(commit_editmsg));
 +      s->fp = fopen_for_writing(git_path_commit_editmsg());
        if (s->fp == NULL)
 -              die_errno(_("could not open '%s'"), git_path(commit_editmsg));
 +              die_errno(_("could not open '%s'"), git_path_commit_editmsg());
  
        /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */
        old_display_comment_prefix = s->display_comment_prefix;
        }
  
        if (run_commit_hook(use_editor, index_file, "prepare-commit-msg",
 -                          git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
 +                          git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL))
                return 0;
  
        if (use_editor) {
                const char *env[2] = { NULL };
                env[0] =  index;
                snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
 -              if (launch_editor(git_path(commit_editmsg), NULL, env)) {
 +              if (launch_editor(git_path_commit_editmsg(), NULL, env)) {
                        fprintf(stderr,
                        _("Please supply the message using either -m or -F option.\n"));
                        exit(1);
        }
  
        if (!no_verify &&
 -          run_commit_hook(use_editor, index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
 +          run_commit_hook(use_editor, index_file, "commit-msg", git_path_commit_editmsg(), NULL)) {
                return 0;
        }
  
@@@ -1617,7 -1616,7 +1617,7 @@@ int cmd_commit(int argc, const char **a
                OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
                OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
                OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 -              OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
 +              OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
                OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
                OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
                            STATUS_FORMAT_SHORT),
  
        /* Finally, get the commit message */
        strbuf_reset(&sb);
 -      if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
 +      if (strbuf_read_file(&sb, git_path_commit_editmsg(), 0) < 0) {
                int saved_errno = errno;
                rollback_index_files();
                die(_("could not read commit message: %s"), strerror(saved_errno));
diff --combined builtin/update-index.c
index ba04b197d8cc0b121475b1590952fe34ad1455d1,8ef21fedc842c996c07d629be72e5df810d8540d..9e9e04059c989339bb1956b736e8379d2cff0462
@@@ -419,30 -419,18 +419,18 @@@ static int add_cacheinfo(unsigned int m
        return 0;
  }
  
- static void chmod_path(int flip, const char *path)
+ static void chmod_path(char flip, const char *path)
  {
        int pos;
        struct cache_entry *ce;
-       unsigned int mode;
  
        pos = cache_name_pos(path, strlen(path));
        if (pos < 0)
                goto fail;
        ce = active_cache[pos];
-       mode = ce->ce_mode;
-       if (!S_ISREG(mode))
+       if (chmod_cache_entry(ce, flip) < 0)
                goto fail;
-       switch (flip) {
-       case '+':
-               ce->ce_mode |= 0111; break;
-       case '-':
-               ce->ce_mode &= ~0111; break;
-       default:
-               goto fail;
-       }
-       cache_tree_invalidate_path(&the_index, path);
-       ce->ce_flags |= CE_UPDATE_IN_BASE;
-       active_cache_changed |= CE_ENTRY_CHANGED;
        report("chmod %cx '%s'", flip, path);
        return;
   fail:
@@@ -759,7 -747,7 +747,7 @@@ static int do_reupdate(int ac, const ch
                if (save_nr != active_nr)
                        goto redo;
        }
 -      free_pathspec(&pathspec);
 +      clear_pathspec(&pathspec);
        return 0;
  }
  
@@@ -1146,7 -1134,7 +1134,7 @@@ int cmd_update_index(int argc, const ch
                report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
                break;
        default:
 -              die("Bug: bad untracked_cache value: %d", untracked_cache);
 +              die("BUG: bad untracked_cache value: %d", untracked_cache);
        }
  
        if (active_cache_changed) {
diff --combined cache.h
index b0dae4bac1a1c22035b175a43c9fe436e789f5d7,009432f9a8b1c30895a488aebdc234c17813ba69..4cba08ecb1096dfdbcaae0eb82adad4a7825d35d
+++ b/cache.h
@@@ -367,8 -367,9 +367,9 @@@ extern void free_name_hash(struct index
  #define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name))
  #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
  #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
- #define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags), 0)
- #define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags), 0)
+ #define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
+ #define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags))
+ #define chmod_cache_entry(ce, flip) chmod_index_entry(&the_index, (ce), (flip))
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
@@@ -581,9 -582,10 +582,10 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_IGNORE_ERRORS       4
  #define ADD_CACHE_IGNORE_REMOVAL 8
  #define ADD_CACHE_INTENT 16
- extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags, int force_mode);
- extern int add_file_to_index(struct index_state *, const char *path, int flags, int force_mode);
+ extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
+ extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options);
+ extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
  extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
  extern int index_name_is_other(const struct index_state *, const char *, int);
@@@ -632,7 -634,6 +634,7 @@@ extern void fill_stat_cache_info(struc
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
  extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
 +extern struct cache_entry *refresh_cache_entry(struct cache_entry *, unsigned int);
  
  extern void update_index_if_able(struct index_state *, struct lock_file *);
  
@@@ -1004,11 -1005,6 +1006,11 @@@ int adjust_shared_perm(const char *path
   * directory while we were working.  To be robust against this kind of
   * race, callers might want to try invoking the function again when it
   * returns SCLD_VANISHED.
 + *
 + * safe_create_leading_directories() temporarily changes path while it
 + * is working but restores it before returning.
 + * safe_create_leading_directories_const() doesn't modify path, even
 + * temporarily.
   */
  enum scld_error {
        SCLD_OK = 0,
@@@ -1139,16 -1135,6 +1141,16 @@@ static inline unsigned int hexval(unsig
        return hexval_table[c];
  }
  
 +/*
 + * Convert two consecutive hexadecimal digits into a char.  Return a
 + * negative value on error.  Don't run over the end of short strings.
 + */
 +static inline int hex2chr(const char *s)
 +{
 +      int val = hexval(s[0]);
 +      return (val < 0) ? val : (val << 4) | hexval(s[1]);
 +}
 +
  /* Convert to/from hex/sha1 representation */
  #define MINIMUM_ABBREV minimum_abbrev
  #define DEFAULT_ABBREV default_abbrev
@@@ -1209,7 -1195,6 +1211,7 @@@ extern int get_oid_hex(const char *hex
   *   printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two));
   */
  extern char *sha1_to_hex_r(char *out, const unsigned char *sha1);
 +extern char *oid_to_hex_r(char *out, const struct object_id *oid);
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
  extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
  
@@@ -1240,8 -1225,7 +1242,8 @@@ struct date_mode 
                DATE_ISO8601_STRICT,
                DATE_RFC2822,
                DATE_STRFTIME,
 -              DATE_RAW
 +              DATE_RAW,
 +              DATE_UNIX
        } type;
        const char *strftime_fmt;
        int local;
@@@ -1280,7 -1264,6 +1282,7 @@@ extern const char *ident_default_email(
  extern const char *git_editor(void);
  extern const char *git_pager(int stdout_is_tty);
  extern int git_ident_config(const char *, const char *, void *);
 +extern void reset_ident_date(void);
  
  struct ident_split {
        const char *name_begin;
@@@ -1389,13 -1372,6 +1391,13 @@@ extern struct packed_git 
        char pack_name[FLEX_ARRAY]; /* more */
  } *packed_git;
  
 +/*
 + * A most-recently-used ordered version of the packed_git list, which can
 + * be iterated instead of packed_git (and marked via mru_mark).
 + */
 +struct mru;
 +extern struct mru *packed_git_mru;
 +
  struct pack_entry {
        off_t offset;
        unsigned char sha1[20];
@@@ -1435,6 -1411,7 +1437,6 @@@ extern unsigned char *use_pack(struct p
  extern void close_pack_windows(struct packed_git *);
  extern void close_all_packs(void);
  extern void unuse_pack(struct pack_window **);
 -extern void free_pack_by_name(const char *);
  extern void clear_delta_base_cache(void);
  extern struct packed_git *add_packed_git(const char *path, size_t path_len, int local);
  
@@@ -1533,7 -1510,7 +1535,7 @@@ struct object_info 
        /* Request */
        enum object_type *typep;
        unsigned long *sizep;
 -      unsigned long *disk_sizep;
 +      off_t *disk_sizep;
        unsigned char *delta_base_sha1;
        struct strbuf *typename;
  
@@@ -1584,18 -1561,10 +1586,18 @@@ struct git_config_source 
        const char *blob;
  };
  
 +enum config_origin_type {
 +      CONFIG_ORIGIN_BLOB,
 +      CONFIG_ORIGIN_FILE,
 +      CONFIG_ORIGIN_STDIN,
 +      CONFIG_ORIGIN_SUBMODULE_BLOB,
 +      CONFIG_ORIGIN_CMDLINE
 +};
 +
  typedef int (*config_fn_t)(const char *, const char *, void *);
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
 -extern int git_config_from_mem(config_fn_t fn, const char *origin_type,
 +extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
                                        const char *name, const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
@@@ -1637,16 -1606,6 +1639,16 @@@ extern const char *get_log_output_encod
  extern const char *get_commit_output_encoding(void);
  
  extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
 +
 +enum config_scope {
 +      CONFIG_SCOPE_UNKNOWN = 0,
 +      CONFIG_SCOPE_SYSTEM,
 +      CONFIG_SCOPE_GLOBAL,
 +      CONFIG_SCOPE_REPO,
 +      CONFIG_SCOPE_CMDLINE,
 +};
 +
 +extern enum config_scope current_config_scope(void);
  extern const char *current_config_origin_type(void);
  extern const char *current_config_name(void);
  
@@@ -1739,8 -1698,6 +1741,8 @@@ extern int ignore_untracked_cache_confi
  struct key_value_info {
        const char *filename;
        int linenr;
 +      enum config_origin_type origin_type;
 +      enum config_scope scope;
  };
  
  extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
@@@ -1766,6 -1723,8 +1768,6 @@@ extern int copy_file(const char *dst, c
  extern int copy_file_with_time(const char *dst, const char *src, int mode);
  
  extern void write_or_die(int fd, const void *buf, size_t count);
 -extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
 -extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
  extern void fsync_or_die(int fd, const char *);
  
  extern ssize_t read_in_full(int fd, void *buf, size_t count);
@@@ -1777,21 -1736,8 +1779,21 @@@ static inline ssize_t write_str_in_full
        return write_in_full(fd, str, strlen(str));
  }
  
 -extern int write_file(const char *path, const char *fmt, ...);
 -extern int write_file_gently(const char *path, const char *fmt, ...);
 +/**
 + * Open (and truncate) the file at path, write the contents of buf to it,
 + * and close it. Dies if any errors are encountered.
 + */
 +extern void write_file_buf(const char *path, const char *buf, size_t len);
 +
 +/**
 + * Like write_file_buf(), but format the contents into a buffer first.
 + * Additionally, write_file() will append a newline if one is not already
 + * present, making it convenient to write text files:
 + *
 + *   write_file(path, "counter: %d", ctr);
 + */
 +__attribute__((format (printf, 2, 3)))
 +extern void write_file(const char *path, const char *fmt, ...);
  
  /* pager.c */
  extern void setup_pager(void);
@@@ -1828,7 -1774,7 +1830,7 @@@ void packet_trace_identity(const char *
   * return 0 if success, 1 - if addition of a file failed and
   * ADD_FILES_IGNORE_ERRORS was specified in flags
   */
- int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags, int force_mode);
+ int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
diff --combined read-cache.c
index 491e52d120a6c02e6a4e7de1e2f5934db4de9f22,c2b2e970bc0388b8984fbb7883fedb6c94faeb27..248432af430d7e5b147898b2ac61f2ebbb468070
@@@ -19,6 -19,9 +19,6 @@@
  #include "split-index.h"
  #include "utf8.h"
  
 -static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
 -                                             unsigned int options);
 -
  /* Mask for the name length in ce_flags in the on-disk index */
  
  #define CE_NAMEMASK  (0x0fff)
@@@ -627,7 -630,7 +627,7 @@@ void set_object_name_for_intent_to_add_
        hashcpy(ce->sha1, sha1);
  }
  
- int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags, int force_mode)
+ int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags)
  {
        int size, namelen, was_same;
        mode_t st_mode = st->st_mode;
        else
                ce->ce_flags |= CE_INTENT_TO_ADD;
  
-       if (S_ISREG(st_mode) && force_mode)
-               ce->ce_mode = create_ce_mode(force_mode);
-       else if (trust_executable_bit && has_symlinks)
+       if (trust_executable_bit && has_symlinks) {
                ce->ce_mode = create_ce_mode(st_mode);
-       else {
+       else {
                /* If there is an existing entry, pick the mode bits and type
                 * from it, otherwise assume unexecutable regular file.
                 */
        return 0;
  }
  
- int add_file_to_index(struct index_state *istate, const char *path,
-       int flags, int force_mode)
+ int add_file_to_index(struct index_state *istate, const char *path, int flags)
  {
        struct stat st;
        if (lstat(path, &st))
                die_errno("unable to stat '%s'", path);
-       return add_to_index(istate, path, &st, flags, force_mode);
+       return add_to_index(istate, path, &st, flags);
  }
  
  struct cache_entry *make_cache_entry(unsigned int mode,
        return ret;
  }
  
+ /*
+  * Chmod an index entry with either +x or -x.
+  *
+  * Returns -1 if the chmod for the particular cache entry failed (if it's
+  * not a regular file), -2 if an invalid flip argument is passed in, 0
+  * otherwise.
+  */
+ int chmod_index_entry(struct index_state *istate, struct cache_entry *ce,
+                     char flip)
+ {
+       if (!S_ISREG(ce->ce_mode))
+               return -1;
+       switch (flip) {
+       case '+':
+               ce->ce_mode |= 0111;
+               break;
+       case '-':
+               ce->ce_mode &= ~0111;
+               break;
+       default:
+               return -2;
+       }
+       cache_tree_invalidate_path(istate, ce->name);
+       ce->ce_flags |= CE_UPDATE_IN_BASE;
+       istate->cache_changed |= CE_ENTRY_CHANGED;
+       return 0;
+ }
  int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
  {
        int len = ce_namelen(a);
@@@ -1254,7 -1284,7 +1281,7 @@@ int refresh_index(struct index_state *i
        return has_errors;
  }
  
 -static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
 +struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
                                               unsigned int options)
  {
        return refresh_cache_ent(&the_index, ce, options, NULL, NULL);