reset: support "--mixed --intent-to-add" mode

When --mixed is used, entries could be removed from index if the target ref does not have them. When "reset" is used in preparation for commit spliting (in a dirty worktree), it could be hard to track what files to be added back. The new option --intent-to-add simplifies it by marking all removed files intent-to-add. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>

Nguyễn Thái Ngọc Duy committed Feb 4, 2014 at 09:20 UTC b4b313f94ab06fabe78fa91b3f2ebbc7ef08b012
5 files changed +50 -15
Documentation/git-reset.txt
+4 -1
@@ -10,7 +10,7 @@ SYNOPSIS
10 [verse]
11 'git reset' [-q] [<tree-ish>] [--] <paths>...
12 'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]
13 -'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
13 +'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
14
15 DESCRIPTION
16 -----------
@@ -60,6 +60,9 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
60 Resets the index but not the working tree (i.e., the changed files
61 are preserved but not marked for commit) and reports what has not
62 been updated. This is the default action.
63 ++
64 +If `-N` is specified, removed paths are marked as intent-to-add (see
65 +linkgit:git-add[1]).
66
67 --hard::
68 Resets the index and working tree. Any changes to tracked files in the
builtin/reset.c
+28 -12
@@ -116,25 +116,34 @@ static void update_index_from_diff(struct diff_queue_struct *q,
116 struct diff_options *opt, void *data)
117 {
118 int i;
119 + int intent_to_add = *(int *)data;
120
121 for (i = 0; i < q->nr; i++) {
122 struct diff_filespec *one = q->queue[i]->one;
122 - if (one->mode && !is_null_sha1(one->sha1)) {
123 - struct cache_entry *ce;
124 - ce = make_cache_entry(one->mode, one->sha1, one->path,
125 - 0, 0);
126 - if (!ce)
127 - die(_("make_cache_entry failed for path '%s'"),
128 - one->path);
129 - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
130 - ADD_CACHE_OK_TO_REPLACE);
131 - } else
123 + int is_missing = !(one->mode && !is_null_sha1(one->sha1));
124 + struct cache_entry *ce;
125 +
126 + if (is_missing && !intent_to_add) {
127 remove_file_from_cache(one->path);
128 + continue;
129 + }
130 +
131 + ce = make_cache_entry(one->mode, one->sha1, one->path,
132 + 0, 0);
133 + if (!ce)
134 + die(_("make_cache_entry failed for path '%s'"),
135 + one->path);
136 + if (is_missing) {
137 + ce->ce_flags |= CE_INTENT_TO_ADD;
138 + set_object_name_for_intent_to_add_entry(ce);
139 + }
140 + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
141 }
142 }
143
144 static int read_from_tree(const struct pathspec *pathspec,
137 - unsigned char *tree_sha1)
145 + unsigned char *tree_sha1,
146 + int intent_to_add)
147 {
148 struct diff_options opt;
149
@@ -142,6 +151,7 @@ static int read_from_tree(const struct pathspec *pathspec,
151 copy_pathspec(&opt.pathspec, pathspec);
152 opt.output_format = DIFF_FORMAT_CALLBACK;
153 opt.format_callback = update_index_from_diff;
154 + opt.format_callback_data = &intent_to_add;
155
156 if (do_diff_cache(tree_sha1, &opt))
157 return 1;
@@ -258,6 +268,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
268 const char *rev;
269 unsigned char sha1[20];
270 struct pathspec pathspec;
271 + int intent_to_add = 0;
272 const struct option options[] = {
273 OPT__QUIET(&quiet, N_("be quiet, only report errors")),
274 OPT_SET_INT(0, "mixed", &reset_type,
@@ -270,6 +281,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
281 OPT_SET_INT(0, "keep", &reset_type,
282 N_("reset HEAD but keep local changes"), KEEP),
283 OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
284 + OPT_BOOL('N', "intent-to-add", &intent_to_add,
285 + N_("record only the fact that removed paths will be added later")),
286 OPT_END()
287 };
288
@@ -327,6 +340,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
340 die(_("%s reset is not allowed in a bare repository"),
341 _(reset_type_names[reset_type]));
342
343 + if (intent_to_add && reset_type != MIXED)
344 + die(_("-N can only be used with --mixed"));
345 +
346 /* Soft reset does not touch the index file nor the working tree
347 * at all, but requires them in a good order. Other resets reset
348 * the index file to the tree object we are switching to. */
@@ -338,7 +354,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
354 int newfd = hold_locked_index(lock, 1);
355 if (reset_type == MIXED) {
356 int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
341 - if (read_from_tree(&pathspec, sha1))
357 + if (read_from_tree(&pathspec, sha1, intent_to_add))
358 return 1;
359 refresh_index(&the_index, flags, NULL, NULL,
360 _("Unstaged changes after reset:"));
cache.h
+1
@@ -489,6 +489,7 @@ extern int add_to_index(struct index_state *, const char *path, struct stat *, i
489 extern int add_file_to_index(struct index_state *, const char *path, int flags);
490 extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
491 extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
492 +extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
493 extern int index_name_is_other(const struct index_state *, const char *, int);
494 extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
495
read-cache.c
+2 -2
@@ -579,7 +579,7 @@ static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_
579 return new;
580 }
581
582 -static void record_intent_to_add(struct cache_entry *ce)
582 +void set_object_name_for_intent_to_add_entry(struct cache_entry *ce)
583 {
584 unsigned char sha1[20];
585 if (write_sha1_file("", 0, blob_type, sha1))
@@ -665,7 +665,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
665 if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT))
666 return error("unable to index file %s", path);
667 } else
668 - record_intent_to_add(ce);
668 + set_object_name_for_intent_to_add_entry(ce);
669
670 if (ignore_case && alias && different_name(ce, alias))
671 ce = create_alias_ce(ce, alias);
t/t7102-reset.sh
+15
@@ -535,4 +535,19 @@ test_expect_success 'reset with paths accepts tree' '
535 git diff HEAD --exit-code
536 '
537
538 +test_expect_success 'reset -N keeps removed files as intent-to-add' '
539 + echo new-file >new-file &&
540 + git add new-file &&
541 + git reset -N HEAD &&
542 +
543 + tree=$(git write-tree) &&
544 + git ls-tree $tree new-file >actual &&
545 + >expect &&
546 + test_cmp expect actual &&
547 +
548 + git diff --name-only >actual &&
549 + echo new-file >expect &&
550 + test_cmp expect actual
551 +'
552 +
553 test_done