From ea41cfc4f54f884582dbda307287f12bb1fc15e9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 27 Jul 2009 20:37:10 +0200 Subject: [PATCH 001/102] Make 'git stash -k' a short form for 'git stash save --keep-index' To save me from the carpal tunnel syndrome, make 'git stash' accept the short option '-k' instead of '--keep-index', and for even more convenience, let's DWIM when this developer forgot to type the 'save' command. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/git-stash.txt | 3 ++- git-stash.sh | 16 +++++++++------- t/t3903-stash.sh | 8 ++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 1c64a02fe5..a031836a26 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,7 +13,8 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [] 'git stash' branch [] -'git stash' [save [--keep-index] [-q|--quiet] []] +'git stash' [save [-k|--keep-index] [-q|--quiet] []] +'git stash' [-k|--keep-index] 'git stash' clear 'git stash' create diff --git a/git-stash.sh b/git-stash.sh index 03e589f764..13edc0eefd 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -7,7 +7,8 @@ USAGE="list [] or: $dashless drop [-q|--quiet] [] or: $dashless ( pop | apply ) [--index] [-q|--quiet] [] or: $dashless branch [] - or: $dashless [save [--keep-index] [-q|--quiet] []] + or: $dashless [save [-k|--keep-index] [-q|--quiet] []] + or: $dashless [-k|--keep-index] or: $dashless clear" SUBDIRECTORY_OK=Yes @@ -98,7 +99,7 @@ save_stash () { while test $# != 0 do case "$1" in - --keep-index) + -k|--keep-index) keep_index=t ;; -q|--quiet) @@ -353,12 +354,13 @@ branch) apply_to_branch "$@" ;; *) - if test $# -eq 0 - then - save_stash && + case $#,"$1" in + 0,|1,-k|1,--keep-index) + save_stash "$@" && say '(To restore them type "git stash apply")' - else + ;; + *) usage - fi + esac ;; esac diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7a3fb67957..e16ad93d2c 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -200,4 +200,12 @@ test_expect_success 'drop -q is quiet' ' test ! -s output.out ' +test_expect_success 'stash -k' ' + echo bar3 > file && + echo bar4 > file2 && + git add file2 && + git stash -k && + test bar,bar4 = $(cat file),$(cat file2) +' + test_done From 29796c6ccff3e70622398379fdcdfa3fe43333ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 4 Aug 2009 16:25:40 -0700 Subject: [PATCH 002/102] diff-index: report unmerged new entries Since an earlier change to diff-index by d1f2d7e (Make run_diff_index() use unpack_trees(), not read_tree(), 2008-01-19), we stopped reporting an unmerged path that does not exist in the tree, but we should. Signed-off-by: Junio C Hamano --- diff-lib.c | 4 ++-- t/t7060-wtstatus.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100755 t/t7060-wtstatus.sh diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde74..ad5b6cac7b 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -348,8 +348,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, match_missing = !revs->ignore_merges; if (cached && idx && ce_stage(idx)) { - if (tree) - diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1); + diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, + idx->sha1); return; } diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh new file mode 100755 index 0000000000..5ad2cd1d04 --- /dev/null +++ b/t/t7060-wtstatus.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='basic work tree status reporting' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B oneside added && + git checkout A^0 && + test_commit C oneside created +' + +test_expect_success 'A/A conflict' ' + git checkout B^0 && + test_must_fail git merge C +' + +test_expect_success 'Report path with conflict' ' + git diff --cached --name-status >actual && + echo "U oneside" >expect && + test_cmp expect actual +' + +test_expect_success 'Report new path with conflict' ' + git diff --cached --name-status HEAD^ >actual && + echo "U oneside" >expect && + test_cmp expect actual +' + +test_done From 26da1d78674204c482ec90905dd4de3f6bcd3c5f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 4 Aug 2009 22:08:16 -0700 Subject: [PATCH 003/102] diff-index: keep the original index intact When comparing the index and a tree, we used to read the contents of the tree into stage #1 of the index and compared them with stage #0. In order not to lose sight of entries originally unmerged in the index, we hoisted them to stage #3 before reading the tree. Commit d1f2d7e (Make run_diff_index() use unpack_trees(), not read_tree(), 2008-01-19) changed all this. These days, we instead use unpack_trees() API to traverse the tree and compare the contents with the index, without modifying the index at all. There is no reason to hoist the unmerged entries to stage #3 anymore. Signed-off-by: Junio C Hamano --- diff-lib.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/diff-lib.c b/diff-lib.c index ad5b6cac7b..2a82dac101 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -308,22 +308,6 @@ static int show_modified(struct rev_info *revs, return 0; } -/* - * This turns all merge entries into "stage 3". That guarantees that - * when we read in the new tree (into "stage 1"), we won't lose sight - * of the fact that we had unmerged entries. - */ -static void mark_merge_entries(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= CE_STAGEMASK; - } -} - /* * This gets a mix of an existing index and a tree, one pathname entry * at a time. The index entry may be a single stage-0 one, but it could @@ -435,8 +419,6 @@ int run_diff_index(struct rev_info *revs, int cached) struct unpack_trees_options opts; struct tree_desc t; - mark_merge_entries(); - ent = revs->pending.objects[0].item; tree_name = revs->pending.objects[0].name; tree = parse_tree_indirect(ent->sha1); From 50b7e70f338e54f3534ee1b14c3bdb4c80d0dcf7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 4 Aug 2009 23:49:33 -0700 Subject: [PATCH 004/102] wt-status.c: rework the way changes to the index and work tree are summarized Introduce a new infrastructure to find and summarize changes in a single string list, and rewrite wt_status_print_{updated,changed} functions using it. The goal of this change is to give more information on conflicted paths in the status output. Signed-off-by: Junio C Hamano --- builtin-commit.c | 13 +-- wt-status.c | 226 +++++++++++++++++++++++++++++++++++++---------- wt-status.h | 10 +++ 3 files changed, 199 insertions(+), 50 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 4bcce06fbf..6d12c2e8b6 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -217,12 +217,15 @@ static void create_base_index(void) exit(128); /* We've already reported the error, finish dying */ } -static char *prepare_index(int argc, const char **argv, const char *prefix) +static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) { int fd; struct string_list partial; const char **pathspec = NULL; + int refresh_flags = REFRESH_QUIET; + if (is_status) + refresh_flags |= REFRESH_UNMERGED; if (interactive) { if (interactive_add(argc, argv, prefix) != 0) die("interactive add failed"); @@ -253,7 +256,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) if (all || (also && pathspec && *pathspec)) { int fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); - refresh_cache(REFRESH_QUIET); + refresh_cache(refresh_flags); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) die("unable to write new_index file"); @@ -272,7 +275,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) */ if (!pathspec || !*pathspec) { fd = hold_locked_index(&index_lock, 1); - refresh_cache(REFRESH_QUIET); + refresh_cache(refresh_flags); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) die("unable to write new_index file"); @@ -825,7 +828,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix); - index_file = prepare_index(argc, argv, prefix); + index_file = prepare_index(argc, argv, prefix, 1); commitable = run_status(stdout, index_file, prefix, 0); @@ -907,7 +910,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix); - index_file = prepare_index(argc, argv, prefix); + index_file = prepare_index(argc, argv, prefix, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ diff --git a/wt-status.c b/wt-status.c index 47735d8129..9aab567753 100644 --- a/wt-status.c +++ b/wt-status.c @@ -56,6 +56,7 @@ void wt_status_prepare(struct wt_status *s) s->reference = "HEAD"; s->fp = stdout; s->index_file = get_index_file(); + s->change.strdup_strings = 1; } static void wt_status_print_cached_header(struct wt_status *s) @@ -98,18 +99,35 @@ static void wt_status_print_trailer(struct wt_status *s) #define quote_path quote_path_relative -static void wt_status_print_filepair(struct wt_status *s, - int t, struct diff_filepair *p) +static void wt_status_print_change_data(struct wt_status *s, + int change_type, + struct string_list_item *it) { - const char *c = color(t); + struct wt_status_change_data *d = it->util; + const char *c = color(change_type); + int status = status; + char *one_name; + char *two_name; const char *one, *two; struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT; - one = quote_path(p->one->path, -1, &onebuf, s->prefix); - two = quote_path(p->two->path, -1, &twobuf, s->prefix); + one_name = two_name = it->string; + switch (change_type) { + case WT_STATUS_UPDATED: + status = d->index_status; + if (d->head_path) + one_name = d->head_path; + break; + case WT_STATUS_CHANGED: + status = d->worktree_status; + break; + } + + one = quote_path(one_name, -1, &onebuf, s->prefix); + two = quote_path(two_name, -1, &twobuf, s->prefix); color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); - switch (p->status) { + switch (status) { case DIFF_STATUS_ADDED: color_fprintf(s->fp, c, "new file: %s", one); break; @@ -135,64 +153,91 @@ static void wt_status_print_filepair(struct wt_status *s, color_fprintf(s->fp, c, "unmerged: %s", one); break; default: - die("bug: unhandled diff status %c", p->status); + die("bug: unhandled diff status %c", status); } fprintf(s->fp, "\n"); strbuf_release(&onebuf); strbuf_release(&twobuf); } -static void wt_status_print_updated_cb(struct diff_queue_struct *q, - struct diff_options *options, - void *data) +static void wt_status_collect_changed_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) { struct wt_status *s = data; - int shown_header = 0; int i; + + if (!q->nr) + return; + s->workdir_dirty = 1; for (i = 0; i < q->nr; i++) { - if (q->queue[i]->status == 'U') - continue; - if (!shown_header) { - wt_status_print_cached_header(s); - s->commitable = 1; - shown_header = 1; + struct diff_filepair *p; + struct string_list_item *it; + struct wt_status_change_data *d; + + p = q->queue[i]; + it = string_list_insert(p->one->path, &s->change); + d = it->util; + if (!d) { + d = xcalloc(1, sizeof(*d)); + it->util = d; } - wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]); + if (!d->worktree_status) + d->worktree_status = p->status; } - if (shown_header) - wt_status_print_trailer(s); } -static void wt_status_print_changed_cb(struct diff_queue_struct *q, - struct diff_options *options, - void *data) +static void wt_status_collect_updated_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) { struct wt_status *s = data; int i; - if (q->nr) { - int has_deleted = 0; - s->workdir_dirty = 1; - for (i = 0; i < q->nr; i++) - if (q->queue[i]->status == DIFF_STATUS_DELETED) { - has_deleted = 1; - break; - } - wt_status_print_dirty_header(s, has_deleted); + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p; + struct string_list_item *it; + struct wt_status_change_data *d; + + p = q->queue[i]; + it = string_list_insert(p->two->path, &s->change); + d = it->util; + if (!d) { + d = xcalloc(1, sizeof(*d)); + it->util = d; + } + if (!d->index_status) + d->index_status = p->status; + switch (p->status) { + case DIFF_STATUS_COPIED: + case DIFF_STATUS_RENAMED: + d->head_path = xstrdup(p->one->path); + break; + } } - for (i = 0; i < q->nr; i++) - wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]); - if (q->nr) - wt_status_print_trailer(s); } -static void wt_status_print_updated(struct wt_status *s) +static void wt_status_collect_changes_worktree(struct wt_status *s) { struct rev_info rev; + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_collect_changed_cb; + rev.diffopt.format_callback_data = s; + run_diff_files(&rev, 0); +} + +static void wt_status_collect_changes_index(struct wt_status *s) +{ + struct rev_info rev; + init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = wt_status_print_updated_cb; + rev.diffopt.format_callback = wt_status_collect_updated_cb; rev.diffopt.format_callback_data = s; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; @@ -200,15 +245,104 @@ static void wt_status_print_updated(struct wt_status *s) run_diff_index(&rev, 1); } +static void wt_status_collect_changes_initial(struct wt_status *s) +{ + int i; + + for (i = 0; i < active_nr; i++) { + struct string_list_item *it; + struct wt_status_change_data *d; + struct cache_entry *ce = active_cache[i]; + + it = string_list_insert(ce->name, &s->change); + d = it->util; + if (!d) { + d = xcalloc(1, sizeof(*d)); + it->util = d; + } + if (ce_stage(ce)) + d->index_status = DIFF_STATUS_UNMERGED; + else + d->index_status = DIFF_STATUS_ADDED; + } +} + +void wt_status_collect_changes(struct wt_status *s) +{ + wt_status_collect_changes_worktree(s); + + if (s->is_initial) + wt_status_collect_changes_initial(s); + else + wt_status_collect_changes_index(s); +} + +static void wt_status_print_updated(struct wt_status *s) +{ + int shown_header = 0; + int i; + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + it = &(s->change.items[i]); + d = it->util; + if (!d->index_status || + d->index_status == DIFF_STATUS_UNMERGED) + continue; + if (!shown_header) { + wt_status_print_cached_header(s); + s->commitable = 1; + shown_header = 1; + } + wt_status_print_change_data(s, WT_STATUS_UPDATED, it); + } + if (shown_header) + wt_status_print_trailer(s); +} + +/* + * -1 : has delete + * 0 : no change + * 1 : some change but no delete + */ +static int wt_status_check_worktree_changes(struct wt_status *s) +{ + int i; + int changes = 0; + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + d = s->change.items[i].util; + if (!d->worktree_status) + continue; + changes = 1; + if (d->worktree_status == DIFF_STATUS_DELETED) + return -1; + } + return changes; +} + static void wt_status_print_changed(struct wt_status *s) { - struct rev_info rev; - init_revisions(&rev, ""); - setup_revisions(0, NULL, &rev, NULL); - rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = wt_status_print_changed_cb; - rev.diffopt.format_callback_data = s; - run_diff_files(&rev, 0); + int i; + int worktree_changes = wt_status_check_worktree_changes(s); + + if (!worktree_changes) + return; + + wt_status_print_dirty_header(s, worktree_changes < 0); + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + it = &(s->change.items[i]); + d = it->util; + if (!d->worktree_status) + continue; + wt_status_print_change_data(s, WT_STATUS_CHANGED, it); + } + wt_status_print_trailer(s); } static void wt_status_print_submodule_summary(struct wt_status *s) @@ -337,6 +471,8 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } + wt_status_collect_changes(s); + if (s->is_initial) { color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit"); diff --git a/wt-status.h b/wt-status.h index 78add09bd6..82a602b3bb 100644 --- a/wt-status.h +++ b/wt-status.h @@ -2,6 +2,7 @@ #define STATUS_H #include +#include "string-list.h" enum color_wt_status { WT_STATUS_HEADER, @@ -18,6 +19,13 @@ enum untracked_status_type { }; extern enum untracked_status_type show_untracked_files; +struct wt_status_change_data { + int worktree_status; + int index_status; + int stagemask; + char *head_path; +}; + struct wt_status { int is_initial; char *branch; @@ -33,6 +41,7 @@ struct wt_status { const char *index_file; FILE *fp; const char *prefix; + struct string_list change; }; int git_status_config(const char *var, const char *value, void *cb); @@ -40,5 +49,6 @@ extern int wt_status_use_color; extern int wt_status_relative_paths; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); +void wt_status_collect_changes(struct wt_status *s); #endif /* STATUS_H */ From 4d4d5726aee31522e90df21ef62ee3377c5d8f8d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Aug 2009 00:04:51 -0700 Subject: [PATCH 005/102] status: show worktree status of conflicted paths separately When a path is unmerged in the index, we used to always say "unmerged" in the "Changed but not updated" section, even when the path was deleted in the work tree. Remove unmerged entries from the "Updated" section, and create a new section "Unmerged paths". Describe how the different stages conflict in more detail in this new section. Note that with the current 3-way merge policy (with or without recursive), certain combinations of index stages should never happen. For example, having only stage #2 means that a path that did not exist in the common ancestor was added by us while the other branch did not do anything to it, which would have autoresolved to take our addition. The code nevertheless prepares for the possibility that future merge policies may leave a path in such a state. Signed-off-by: Junio C Hamano --- t/t7060-wtstatus.sh | 27 +++++++++++++ wt-status.c | 95 +++++++++++++++++++++++++++++++++++++++++++-- wt-status.h | 1 + 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 5ad2cd1d04..1044aa6549 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -28,4 +28,31 @@ test_expect_success 'Report new path with conflict' ' test_cmp expect actual ' +cat >expect <..." to unstage) +# (use "git add ..." to mark resolution) +# +# deleted by us: foo +# +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success 'M/D conflict does not segfault' ' + mkdir mdconflict && + ( + cd mdconflict && + git init && + test_commit initial foo "" && + test_commit modify foo foo && + git checkout -b side HEAD^ && + git rm foo && + git commit -m delete && + test_must_fail git merge master && + test_must_fail git status > ../actual + ) && + test_cmp expect actual +' + test_done diff --git a/wt-status.c b/wt-status.c index 9aab567753..97fedfaa19 100644 --- a/wt-status.c +++ b/wt-status.c @@ -20,6 +20,7 @@ static char wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_RED, /* WT_STATUS_CHANGED */ GIT_COLOR_RED, /* WT_STATUS_UNTRACKED */ GIT_COLOR_RED, /* WT_STATUS_NOBRANCH */ + GIT_COLOR_RED, /* WT_STATUS_UNMERGED */ }; enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; @@ -37,6 +38,8 @@ static int parse_status_slot(const char *var, int offset) return WT_STATUS_UNTRACKED; if (!strcasecmp(var+offset, "nobranch")) return WT_STATUS_NOBRANCH; + if (!strcasecmp(var+offset, "unmerged")) + return WT_STATUS_UNMERGED; die("bad config variable '%s'", var); } @@ -59,6 +62,18 @@ void wt_status_prepare(struct wt_status *s) s->change.strdup_strings = 1; } +static void wt_status_print_unmerged_header(struct wt_status *s) +{ + const char *c = color(WT_STATUS_HEADER); + color_fprintf_ln(s->fp, c, "# Unmerged paths:"); + if (!s->is_initial) + color_fprintf_ln(s->fp, c, "# (use \"git reset %s ...\" to unstage)", s->reference); + else + color_fprintf_ln(s->fp, c, "# (use \"git rm --cached ...\" to unstage)"); + color_fprintf_ln(s->fp, c, "# (use \"git add ...\" to mark resolution)"); + color_fprintf_ln(s->fp, c, "#"); +} + static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER); @@ -99,6 +114,29 @@ static void wt_status_print_trailer(struct wt_status *s) #define quote_path quote_path_relative +static void wt_status_print_unmerged_data(struct wt_status *s, + struct string_list_item *it) +{ + const char *c = color(WT_STATUS_UNMERGED); + struct wt_status_change_data *d = it->util; + struct strbuf onebuf = STRBUF_INIT; + const char *one, *how = "bug"; + + one = quote_path(it->string, -1, &onebuf, s->prefix); + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + switch (d->stagemask) { + case 1: how = "both deleted:"; break; + case 2: how = "added by us:"; break; + case 3: how = "deleted by them:"; break; + case 4: how = "added by them:"; break; + case 5: how = "deleted by us:"; break; + case 6: how = "both added:"; break; + case 7: how = "both modified:"; break; + } + color_fprintf(s->fp, c, "%-20s%s\n", how, one); + strbuf_release(&onebuf); +} + static void wt_status_print_change_data(struct wt_status *s, int change_type, struct string_list_item *it) @@ -187,6 +225,26 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, } } +static int unmerged_mask(const char *path) +{ + int pos, mask; + struct cache_entry *ce; + + pos = cache_name_pos(path, strlen(path)); + if (0 <= pos) + return 0; + + mask = 0; + pos = -pos-1; + while (pos < active_nr) { + ce = active_cache[pos++]; + if (strcmp(ce->name, path) || !ce_stage(ce)) + break; + mask |= (1 << (ce_stage(ce) - 1)); + } + return mask; +} + static void wt_status_collect_updated_cb(struct diff_queue_struct *q, struct diff_options *options, void *data) @@ -213,6 +271,9 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q, case DIFF_STATUS_RENAMED: d->head_path = xstrdup(p->one->path); break; + case DIFF_STATUS_UNMERGED: + d->stagemask = unmerged_mask(p->two->path); + break; } } } @@ -260,8 +321,10 @@ static void wt_status_collect_changes_initial(struct wt_status *s) d = xcalloc(1, sizeof(*d)); it->util = d; } - if (ce_stage(ce)) + if (ce_stage(ce)) { d->index_status = DIFF_STATUS_UNMERGED; + d->stagemask |= (1 << (ce_stage(ce) - 1)); + } else d->index_status = DIFF_STATUS_ADDED; } @@ -277,6 +340,29 @@ void wt_status_collect_changes(struct wt_status *s) wt_status_collect_changes_index(s); } +static void wt_status_print_unmerged(struct wt_status *s) +{ + int shown_header = 0; + int i; + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + it = &(s->change.items[i]); + d = it->util; + if (!d->stagemask) + continue; + if (!shown_header) { + wt_status_print_unmerged_header(s); + shown_header = 1; + } + wt_status_print_unmerged_data(s, it); + } + if (shown_header) + wt_status_print_trailer(s); + +} + static void wt_status_print_updated(struct wt_status *s) { int shown_header = 0; @@ -314,7 +400,8 @@ static int wt_status_check_worktree_changes(struct wt_status *s) for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; d = s->change.items[i].util; - if (!d->worktree_status) + if (!d->worktree_status || + d->worktree_status == DIFF_STATUS_UNMERGED) continue; changes = 1; if (d->worktree_status == DIFF_STATUS_DELETED) @@ -338,7 +425,8 @@ static void wt_status_print_changed(struct wt_status *s) struct string_list_item *it; it = &(s->change.items[i]); d = it->util; - if (!d->worktree_status) + if (!d->worktree_status || + d->worktree_status == DIFF_STATUS_UNMERGED) continue; wt_status_print_change_data(s, WT_STATUS_CHANGED, it); } @@ -479,6 +567,7 @@ void wt_status_print(struct wt_status *s) color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); } + wt_status_print_unmerged(s); wt_status_print_updated(s); wt_status_print_changed(s); if (wt_status_submodule_summary) diff --git a/wt-status.h b/wt-status.h index 82a602b3bb..f80142ffde 100644 --- a/wt-status.h +++ b/wt-status.h @@ -10,6 +10,7 @@ enum color_wt_status { WT_STATUS_CHANGED, WT_STATUS_UNTRACKED, WT_STATUS_NOBRANCH, + WT_STATUS_UNMERGED, }; enum untracked_status_type { From 3a5d13a3c32e0f39d8fc83330255fac27af5d853 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 7 Aug 2009 23:03:36 -0700 Subject: [PATCH 006/102] commit: --dry-run This teaches --dry-run option to "git commit". It is the same as "git status", but in the longer term we would want to change the semantics of "git status" not to be the preview of commit, and this is the first step for doing so. Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 7 ++++++- builtin-commit.c | 31 +++++++++++++++++-------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index b5d81be7ec..d01ff5adae 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -8,7 +8,7 @@ git-commit - Record changes to the repository SYNOPSIS -------- [verse] -'git commit' [-a | --interactive] [-s] [-v] [-u] [--amend] +'git commit' [-a | --interactive] [-s] [-v] [-u] [--amend] [--dry-run] [(-c | -C) ] [-F | -m ] [--allow-empty] [--no-verify] [-e] [--author=] [--cleanup=] [--] [[-i | -o ]...] @@ -198,6 +198,11 @@ specified. --quiet:: Suppress commit summary message. +--dry-run:: + Do not create a commit, but show a list of paths that are + to be committed, paths with local changes that will be left + uncommitted and paths that are untracked. + \--:: Do not interpret any more arguments as options. diff --git a/builtin-commit.c b/builtin-commit.c index 6d12c2e8b6..3a7e35d60f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -51,7 +51,7 @@ static const char *template_file; static char *edit_message, *use_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, no_verify, allow_empty; +static int quiet, verbose, no_verify, allow_empty, dry_run; static char *untracked_files_arg; /* * The default commit message cleanup mode will remove the lines @@ -103,6 +103,7 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), + OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), @@ -813,28 +814,28 @@ static int parse_and_validate_options(int argc, const char *argv[], return argc; } +static int dry_run_commit(int argc, const char **argv, const char *prefix) +{ + int commitable; + const char *index_file; + + index_file = prepare_index(argc, argv, prefix, 1); + commitable = run_status(stdout, index_file, prefix, 0); + rollback_index_files(); + + return commitable ? 0 : 1; +} + int cmd_status(int argc, const char **argv, const char *prefix) { - const char *index_file; - int commitable; - git_config(git_status_config, NULL); - if (wt_status_use_color == -1) wt_status_use_color = git_use_color_default; - if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix); - - index_file = prepare_index(argc, argv, prefix, 1); - - commitable = run_status(stdout, index_file, prefix, 0); - - rollback_index_files(); - - return commitable ? 0 : 1; + return dry_run_commit(argc, argv, prefix); } static void print_summary(const char *prefix, const unsigned char *sha1) @@ -909,6 +910,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_use_color = git_use_color_default; argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix); + if (dry_run) + return dry_run_commit(argc, argv, prefix); index_file = prepare_index(argc, argv, prefix, 0); From d249b098893809c6de6e5e20063a22ac2c629ce7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 9 Aug 2009 21:59:30 -0700 Subject: [PATCH 007/102] wt-status: move many global settings to wt_status structure Turn four global variables (wt_status_use_color, show_tracked_files, wt_status_relative_paths, and wt_status_submodule_summary) into fields of wt_status structure. They can also lose "wt_status_" prefix. Get rid of "untracked" field that was used only to keep track of otherwise available information redundantly. Signed-off-by: Junio C Hamano --- builtin-commit.c | 87 ++++++++++++++++++++++++++---------------------- wt-status.c | 81 ++++++++++++++++++++++---------------------- wt-status.h | 9 ++--- 3 files changed, 93 insertions(+), 84 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 3a7e35d60f..716eec832d 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -343,27 +343,24 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int return false_lock.filename; } -static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn) +static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, + struct wt_status *s) { - struct wt_status s; - - wt_status_prepare(&s); - if (wt_status_relative_paths) - s.prefix = prefix; + if (s->relative_paths) + s->prefix = prefix; if (amend) { - s.amend = 1; - s.reference = "HEAD^1"; + s->amend = 1; + s->reference = "HEAD^1"; } - s.verbose = verbose; - s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES); - s.index_file = index_file; - s.fp = fp; - s.nowarn = nowarn; + s->verbose = verbose; + s->index_file = index_file; + s->fp = fp; + s->nowarn = nowarn; - wt_status_print(&s); + wt_status_print(s); - return s.commitable; + return s->commitable; } static int is_a_merge(const unsigned char *sha1) @@ -417,7 +414,8 @@ static void determine_author_info(void) author_date = date; } -static int prepare_to_commit(const char *index_file, const char *prefix) +static int prepare_to_commit(const char *index_file, const char *prefix, + struct wt_status *s) { struct stat statbuf; int commitable, saved_color_setting; @@ -559,10 +557,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix) if (ident_shown) fprintf(fp, "#\n"); - saved_color_setting = wt_status_use_color; - wt_status_use_color = 0; - commitable = run_status(fp, index_file, prefix, 1); - wt_status_use_color = saved_color_setting; + saved_color_setting = s->use_color; + s->use_color = 0; + commitable = run_status(fp, index_file, prefix, 1, s); + s->use_color = saved_color_setting; } else { unsigned char sha1[20]; const char *parent = "HEAD"; @@ -583,7 +581,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix) if (!commitable && !in_merge && !allow_empty && !(amend && is_a_merge(head_sha1))) { - run_status(stdout, index_file, prefix, 0); + run_status(stdout, index_file, prefix, 0, s); return 0; } @@ -695,7 +693,8 @@ static const char *find_author_by_nickname(const char *name) static int parse_and_validate_options(int argc, const char *argv[], const char * const usage[], - const char *prefix) + const char *prefix, + struct wt_status *s) { int f = 0; @@ -798,11 +797,11 @@ static int parse_and_validate_options(int argc, const char *argv[], if (!untracked_files_arg) ; /* default already initialized */ else if (!strcmp(untracked_files_arg, "no")) - show_untracked_files = SHOW_NO_UNTRACKED_FILES; + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; else if (!strcmp(untracked_files_arg, "normal")) - show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; else if (!strcmp(untracked_files_arg, "all")) - show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else die("Invalid untracked files mode '%s'", untracked_files_arg); @@ -814,13 +813,14 @@ static int parse_and_validate_options(int argc, const char *argv[], return argc; } -static int dry_run_commit(int argc, const char **argv, const char *prefix) +static int dry_run_commit(int argc, const char **argv, const char *prefix, + struct wt_status *s) { int commitable; const char *index_file; index_file = prepare_index(argc, argv, prefix, 1); - commitable = run_status(stdout, index_file, prefix, 0); + commitable = run_status(stdout, index_file, prefix, 0, s); rollback_index_files(); return commitable ? 0 : 1; @@ -828,14 +828,18 @@ static int dry_run_commit(int argc, const char **argv, const char *prefix) int cmd_status(int argc, const char **argv, const char *prefix) { - git_config(git_status_config, NULL); - if (wt_status_use_color == -1) - wt_status_use_color = git_use_color_default; + struct wt_status s; + + wt_status_prepare(&s); + git_config(git_status_config, &s); + if (s.use_color == -1) + s.use_color = git_use_color_default; if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; - argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix); - return dry_run_commit(argc, argv, prefix); + argc = parse_and_validate_options(argc, argv, builtin_status_usage, + prefix, &s); + return dry_run_commit(argc, argv, prefix, &s); } static void print_summary(const char *prefix, const unsigned char *sha1) @@ -887,10 +891,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1) static int git_commit_config(const char *k, const char *v, void *cb) { + struct wt_status *s = cb; + if (!strcmp(k, "commit.template")) return git_config_string(&template_file, k, v); - return git_status_config(k, v, cb); + return git_status_config(k, v, s); } int cmd_commit(int argc, const char **argv, const char *prefix) @@ -903,21 +909,24 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; int allow_fast_forward = 1; + struct wt_status s; - git_config(git_commit_config, NULL); + wt_status_prepare(&s); + git_config(git_commit_config, &s); - if (wt_status_use_color == -1) - wt_status_use_color = git_use_color_default; + if (s.use_color == -1) + s.use_color = git_use_color_default; - argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix); + argc = parse_and_validate_options(argc, argv, builtin_commit_usage, + prefix, &s); if (dry_run) - return dry_run_commit(argc, argv, prefix); + return dry_run_commit(argc, argv, prefix, &s); index_file = prepare_index(argc, argv, prefix, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ - if (!prepare_to_commit(index_file, prefix)) { + if (!prepare_to_commit(index_file, prefix, &s)) { rollback_index_files(); return 1; } diff --git a/wt-status.c b/wt-status.c index 97fedfaa19..af93bb52db 100644 --- a/wt-status.c +++ b/wt-status.c @@ -11,9 +11,6 @@ #include "run-command.h" #include "remote.h" -int wt_status_relative_paths = 1; -int wt_status_use_color = -1; -static int wt_status_submodule_summary; static char wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ GIT_COLOR_GREEN, /* WT_STATUS_UPDATED */ @@ -23,8 +20,6 @@ static char wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_RED, /* WT_STATUS_UNMERGED */ }; -enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - static int parse_status_slot(const char *var, int offset) { if (!strcasecmp(var+offset, "header")) @@ -43,9 +38,9 @@ static int parse_status_slot(const char *var, int offset) die("bad config variable '%s'", var); } -static const char *color(int slot) +static const char *color(int slot, struct wt_status *s) { - return wt_status_use_color > 0 ? wt_status_colors[slot] : ""; + return s->use_color > 0 ? wt_status_colors[slot] : ""; } void wt_status_prepare(struct wt_status *s) @@ -54,6 +49,9 @@ void wt_status_prepare(struct wt_status *s) const char *head; memset(s, 0, sizeof(*s)); + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + s->use_color = -1; + s->relative_paths = 1; head = resolve_ref("HEAD", sha1, 0, NULL); s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; @@ -64,7 +62,7 @@ void wt_status_prepare(struct wt_status *s) static void wt_status_print_unmerged_header(struct wt_status *s) { - const char *c = color(WT_STATUS_HEADER); + const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Unmerged paths:"); if (!s->is_initial) color_fprintf_ln(s->fp, c, "# (use \"git reset %s ...\" to unstage)", s->reference); @@ -76,7 +74,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s) static void wt_status_print_cached_header(struct wt_status *s) { - const char *c = color(WT_STATUS_HEADER); + const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changes to be committed:"); if (!s->is_initial) { color_fprintf_ln(s->fp, c, "# (use \"git reset %s ...\" to unstage)", s->reference); @@ -89,7 +87,7 @@ static void wt_status_print_cached_header(struct wt_status *s) static void wt_status_print_dirty_header(struct wt_status *s, int has_deleted) { - const char *c = color(WT_STATUS_HEADER); + const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changed but not updated:"); if (!has_deleted) color_fprintf_ln(s->fp, c, "# (use \"git add ...\" to update what will be committed)"); @@ -101,7 +99,7 @@ static void wt_status_print_dirty_header(struct wt_status *s, static void wt_status_print_untracked_header(struct wt_status *s) { - const char *c = color(WT_STATUS_HEADER); + const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Untracked files:"); color_fprintf_ln(s->fp, c, "# (use \"git add ...\" to include in what will be committed)"); color_fprintf_ln(s->fp, c, "#"); @@ -109,7 +107,7 @@ static void wt_status_print_untracked_header(struct wt_status *s) static void wt_status_print_trailer(struct wt_status *s) { - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } #define quote_path quote_path_relative @@ -117,13 +115,13 @@ static void wt_status_print_trailer(struct wt_status *s) static void wt_status_print_unmerged_data(struct wt_status *s, struct string_list_item *it) { - const char *c = color(WT_STATUS_UNMERGED); + const char *c = color(WT_STATUS_UNMERGED, s); struct wt_status_change_data *d = it->util; struct strbuf onebuf = STRBUF_INIT; const char *one, *how = "bug"; one = quote_path(it->string, -1, &onebuf, s->prefix); - color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); switch (d->stagemask) { case 1: how = "both deleted:"; break; case 2: how = "added by us:"; break; @@ -142,7 +140,7 @@ static void wt_status_print_change_data(struct wt_status *s, struct string_list_item *it) { struct wt_status_change_data *d = it->util; - const char *c = color(change_type); + const char *c = color(change_type, s); int status = status; char *one_name; char *two_name; @@ -164,7 +162,7 @@ static void wt_status_print_change_data(struct wt_status *s, one = quote_path(one_name, -1, &onebuf, s->prefix); two = quote_path(two_name, -1, &twobuf, s->prefix); - color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); switch (status) { case DIFF_STATUS_ADDED: color_fprintf(s->fp, c, "new file: %s", one); @@ -450,7 +448,7 @@ static void wt_status_print_submodule_summary(struct wt_status *s) NULL }; - sprintf(summary_limit, "%d", wt_status_submodule_summary); + sprintf(summary_limit, "%d", s->submodule_summary); snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file); memset(&sm_summary, 0, sizeof(sm_summary)); @@ -471,8 +469,7 @@ static void wt_status_print_untracked(struct wt_status *s) struct strbuf buf = STRBUF_INIT; memset(&dir, 0, sizeof(dir)); - - if (!s->untracked) + if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; setup_standard_excludes(&dir); @@ -487,8 +484,8 @@ static void wt_status_print_untracked(struct wt_status *s) wt_status_print_untracked_header(s); shown_header = 1; } - color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); - color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s", + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", quote_path(ent->name, ent->len, &buf, s->prefix)); } @@ -532,15 +529,15 @@ static void wt_status_print_tracking(struct wt_status *s) return; for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1) - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# %.*s", (int)(ep - cp), cp); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } void wt_status_print(struct wt_status *s) { unsigned char sha1[20]; - const char *branch_color = color(WT_STATUS_HEADER); + const char *branch_color = color(WT_STATUS_HEADER, s); s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; if (s->branch) { @@ -550,10 +547,10 @@ void wt_status_print(struct wt_status *s) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { branch_name = ""; - branch_color = color(WT_STATUS_NOBRANCH); + branch_color = color(WT_STATUS_NOBRANCH, s); on_what = "Not currently on any branch."; } - color_fprintf(s->fp, color(WT_STATUS_HEADER), "# "); + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# "); color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name); if (!s->is_initial) wt_status_print_tracking(s); @@ -562,17 +559,17 @@ void wt_status_print(struct wt_status *s) wt_status_collect_changes(s); if (s->is_initial) { - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit"); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } wt_status_print_unmerged(s); wt_status_print_updated(s); wt_status_print_changed(s); - if (wt_status_submodule_summary) + if (s->submodule_summary) wt_status_print_submodule_summary(s); - if (show_untracked_files) + if (s->show_untracked_files) wt_status_print_untracked(s); else if (s->commitable) fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n"); @@ -590,7 +587,7 @@ void wt_status_print(struct wt_status *s) printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); else if (s->is_initial) printf("nothing to commit (create/copy files and use \"git add\" to track)\n"); - else if (!show_untracked_files) + else if (!s->show_untracked_files) printf("nothing to commit (use -u to show untracked files)\n"); else printf("nothing to commit (working directory clean)\n"); @@ -599,15 +596,17 @@ void wt_status_print(struct wt_status *s) int git_status_config(const char *k, const char *v, void *cb) { + struct wt_status *s = cb; + if (!strcmp(k, "status.submodulesummary")) { int is_bool; - wt_status_submodule_summary = git_config_bool_or_int(k, v, &is_bool); - if (is_bool && wt_status_submodule_summary) - wt_status_submodule_summary = -1; + s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); + if (is_bool && s->submodule_summary) + s->submodule_summary = -1; return 0; } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - wt_status_use_color = git_config_colorbool(k, v, -1); + s->use_color = git_config_colorbool(k, v, -1); return 0; } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { @@ -618,21 +617,21 @@ int git_status_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "status.relativepaths")) { - wt_status_relative_paths = git_config_bool(k, v); + s->relative_paths = git_config_bool(k, v); return 0; } if (!strcmp(k, "status.showuntrackedfiles")) { if (!v) return config_error_nonbool(k); else if (!strcmp(v, "no")) - show_untracked_files = SHOW_NO_UNTRACKED_FILES; + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; else if (!strcmp(v, "normal")) - show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; else if (!strcmp(v, "all")) - show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else return error("Invalid untracked files mode '%s'", v); return 0; } - return git_diff_ui_config(k, v, cb); + return git_diff_ui_config(k, v, NULL); } diff --git a/wt-status.h b/wt-status.h index f80142ffde..cd06a4d955 100644 --- a/wt-status.h +++ b/wt-status.h @@ -18,7 +18,6 @@ enum untracked_status_type { SHOW_NORMAL_UNTRACKED_FILES, SHOW_ALL_UNTRACKED_FILES }; -extern enum untracked_status_type show_untracked_files; struct wt_status_change_data { int worktree_status; @@ -33,8 +32,12 @@ struct wt_status { const char *reference; int verbose; int amend; - int untracked; int nowarn; + int use_color; + int relative_paths; + int submodule_summary; + enum untracked_status_type show_untracked_files; + /* These are computed during processing of the individual sections */ int commitable; int workdir_dirty; @@ -46,8 +49,6 @@ struct wt_status { }; int git_status_config(const char *var, const char *value, void *cb); -extern int wt_status_use_color; -extern int wt_status_relative_paths; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect_changes(struct wt_status *s); From 23900a964608bbfda6989a0a2cd4342f19f9194c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 9 Aug 2009 23:08:40 -0700 Subject: [PATCH 008/102] wt-status: move wt_status_colors[] into wt_status structure The benefit of this one alone is somewhat iffy, but for completeness this moves the wt_status_colors[] color palette to the wt_status structure to complete the libification started by the previous commit. Signed-off-by: Junio C Hamano --- wt-status.c | 9 +++++---- wt-status.h | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/wt-status.c b/wt-status.c index af93bb52db..cfbaf309ad 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1,6 +1,5 @@ #include "cache.h" #include "wt-status.h" -#include "color.h" #include "object.h" #include "dir.h" #include "commit.h" @@ -11,7 +10,7 @@ #include "run-command.h" #include "remote.h" -static char wt_status_colors[][COLOR_MAXLEN] = { +static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ GIT_COLOR_GREEN, /* WT_STATUS_UPDATED */ GIT_COLOR_RED, /* WT_STATUS_CHANGED */ @@ -40,7 +39,7 @@ static int parse_status_slot(const char *var, int offset) static const char *color(int slot, struct wt_status *s) { - return s->use_color > 0 ? wt_status_colors[slot] : ""; + return s->use_color > 0 ? s->color_palette[slot] : ""; } void wt_status_prepare(struct wt_status *s) @@ -49,6 +48,8 @@ void wt_status_prepare(struct wt_status *s) const char *head; memset(s, 0, sizeof(*s)); + memcpy(s->color_palette, default_wt_status_colors, + sizeof(default_wt_status_colors)); s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->use_color = -1; s->relative_paths = 1; @@ -613,7 +614,7 @@ int git_status_config(const char *k, const char *v, void *cb) int slot = parse_status_slot(k, 13); if (!v) return config_error_nonbool(k); - color_parse(v, k, wt_status_colors[slot]); + color_parse(v, k, s->color_palette[slot]); return 0; } if (!strcmp(k, "status.relativepaths")) { diff --git a/wt-status.h b/wt-status.h index cd06a4d955..0297fabdc1 100644 --- a/wt-status.h +++ b/wt-status.h @@ -3,9 +3,10 @@ #include #include "string-list.h" +#include "color.h" enum color_wt_status { - WT_STATUS_HEADER, + WT_STATUS_HEADER = 0, WT_STATUS_UPDATED, WT_STATUS_CHANGED, WT_STATUS_UNTRACKED, @@ -37,6 +38,7 @@ struct wt_status { int relative_paths; int submodule_summary; enum untracked_status_type show_untracked_files; + char color_palette[WT_STATUS_UNMERGED+1][COLOR_MAXLEN]; /* These are computed during processing of the individual sections */ int commitable; From f766b36783c7ceeb0427f5e1af862b9a67ae1c4c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 9 Aug 2009 23:12:19 -0700 Subject: [PATCH 009/102] Make git_status_config() file scope static to builtin-commit.c Signed-off-by: Junio C Hamano --- builtin-commit.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ wt-status.c | 60 ------------------------------------------------ wt-status.h | 1 - 3 files changed, 60 insertions(+), 61 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 716eec832d..1c200eb963 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -826,6 +826,66 @@ static int dry_run_commit(int argc, const char **argv, const char *prefix, return commitable ? 0 : 1; } +static int parse_status_slot(const char *var, int offset) +{ + if (!strcasecmp(var+offset, "header")) + return WT_STATUS_HEADER; + if (!strcasecmp(var+offset, "updated") + || !strcasecmp(var+offset, "added")) + return WT_STATUS_UPDATED; + if (!strcasecmp(var+offset, "changed")) + return WT_STATUS_CHANGED; + if (!strcasecmp(var+offset, "untracked")) + return WT_STATUS_UNTRACKED; + if (!strcasecmp(var+offset, "nobranch")) + return WT_STATUS_NOBRANCH; + if (!strcasecmp(var+offset, "unmerged")) + return WT_STATUS_UNMERGED; + die("bad config variable '%s'", var); +} + +static int git_status_config(const char *k, const char *v, void *cb) +{ + struct wt_status *s = cb; + + if (!strcmp(k, "status.submodulesummary")) { + int is_bool; + s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); + if (is_bool && s->submodule_summary) + s->submodule_summary = -1; + return 0; + } + if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { + s->use_color = git_config_colorbool(k, v, -1); + return 0; + } + if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { + int slot = parse_status_slot(k, 13); + if (!v) + return config_error_nonbool(k); + color_parse(v, k, s->color_palette[slot]); + return 0; + } + if (!strcmp(k, "status.relativepaths")) { + s->relative_paths = git_config_bool(k, v); + return 0; + } + if (!strcmp(k, "status.showuntrackedfiles")) { + if (!v) + return config_error_nonbool(k); + else if (!strcmp(v, "no")) + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(v, "normal")) + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(v, "all")) + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + else + return error("Invalid untracked files mode '%s'", v); + return 0; + } + return git_diff_ui_config(k, v, NULL); +} + int cmd_status(int argc, const char **argv, const char *prefix) { struct wt_status s; diff --git a/wt-status.c b/wt-status.c index cfbaf309ad..4cbe03a70a 100644 --- a/wt-status.c +++ b/wt-status.c @@ -19,24 +19,6 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_RED, /* WT_STATUS_UNMERGED */ }; -static int parse_status_slot(const char *var, int offset) -{ - if (!strcasecmp(var+offset, "header")) - return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "updated") - || !strcasecmp(var+offset, "added")) - return WT_STATUS_UPDATED; - if (!strcasecmp(var+offset, "changed")) - return WT_STATUS_CHANGED; - if (!strcasecmp(var+offset, "untracked")) - return WT_STATUS_UNTRACKED; - if (!strcasecmp(var+offset, "nobranch")) - return WT_STATUS_NOBRANCH; - if (!strcasecmp(var+offset, "unmerged")) - return WT_STATUS_UNMERGED; - die("bad config variable '%s'", var); -} - static const char *color(int slot, struct wt_status *s) { return s->use_color > 0 ? s->color_palette[slot] : ""; @@ -594,45 +576,3 @@ void wt_status_print(struct wt_status *s) printf("nothing to commit (working directory clean)\n"); } } - -int git_status_config(const char *k, const char *v, void *cb) -{ - struct wt_status *s = cb; - - if (!strcmp(k, "status.submodulesummary")) { - int is_bool; - s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); - if (is_bool && s->submodule_summary) - s->submodule_summary = -1; - return 0; - } - if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - s->use_color = git_config_colorbool(k, v, -1); - return 0; - } - if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { - int slot = parse_status_slot(k, 13); - if (!v) - return config_error_nonbool(k); - color_parse(v, k, s->color_palette[slot]); - return 0; - } - if (!strcmp(k, "status.relativepaths")) { - s->relative_paths = git_config_bool(k, v); - return 0; - } - if (!strcmp(k, "status.showuntrackedfiles")) { - if (!v) - return config_error_nonbool(k); - else if (!strcmp(v, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(v, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(v, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - return error("Invalid untracked files mode '%s'", v); - return 0; - } - return git_diff_ui_config(k, v, NULL); -} diff --git a/wt-status.h b/wt-status.h index 0297fabdc1..95f5af8902 100644 --- a/wt-status.h +++ b/wt-status.h @@ -50,7 +50,6 @@ struct wt_status { struct string_list change; }; -int git_status_config(const char *var, const char *value, void *cb); void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect_changes(struct wt_status *s); From 7637868362ab64fe22521c645006ba70c4ef83a9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 10 Aug 2009 00:36:33 -0700 Subject: [PATCH 010/102] wt-status: collect untracked files in a separate "collect" phase In a way similar to updated and locally modified files are collected. Signed-off-by: Junio C Hamano --- wt-status.c | 58 +++++++++++++++++++++++++++++++++-------------------- wt-status.h | 3 ++- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/wt-status.c b/wt-status.c index 4cbe03a70a..63598ce40c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -41,6 +41,7 @@ void wt_status_prepare(struct wt_status *s) s->fp = stdout; s->index_file = get_index_file(); s->change.strdup_strings = 1; + s->untracked.strdup_strings = 1; } static void wt_status_print_unmerged_header(struct wt_status *s) @@ -311,7 +312,30 @@ static void wt_status_collect_changes_initial(struct wt_status *s) } } -void wt_status_collect_changes(struct wt_status *s) +static void wt_status_collect_untracked(struct wt_status *s) +{ + int i; + struct dir_struct dir; + + if (!s->show_untracked_files) + return; + memset(&dir, 0, sizeof(dir)); + if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES) + dir.flags |= + DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; + setup_standard_excludes(&dir); + + fill_directory(&dir, NULL); + for(i = 0; i < dir.nr; i++) { + struct dir_entry *ent = dir.entries[i]; + if (!cache_name_is_other(ent->name, ent->len)) + continue; + s->workdir_untracked = 1; + string_list_insert(ent->name, &s->untracked); + } +} + +void wt_status_collect(struct wt_status *s) { wt_status_collect_changes_worktree(s); @@ -319,6 +343,7 @@ void wt_status_collect_changes(struct wt_status *s) wt_status_collect_changes_initial(s); else wt_status_collect_changes_index(s); + wt_status_collect_untracked(s); } static void wt_status_print_unmerged(struct wt_status *s) @@ -446,31 +471,20 @@ static void wt_status_print_submodule_summary(struct wt_status *s) static void wt_status_print_untracked(struct wt_status *s) { - struct dir_struct dir; int i; - int shown_header = 0; struct strbuf buf = STRBUF_INIT; - memset(&dir, 0, sizeof(dir)); - if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES) - dir.flags |= - DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; - setup_standard_excludes(&dir); + if (!s->untracked.nr) + return; - fill_directory(&dir, NULL); - for(i = 0; i < dir.nr; i++) { - struct dir_entry *ent = dir.entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) - continue; - if (!shown_header) { - s->workdir_untracked = 1; - wt_status_print_untracked_header(s); - shown_header = 1; - } + wt_status_print_untracked_header(s); + for (i = 0; i < s->untracked.nr; i++) { + struct string_list_item *it; + it = &(s->untracked.items[i]); color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", - quote_path(ent->name, ent->len, - &buf, s->prefix)); + quote_path(it->string, strlen(it->string), + &buf, s->prefix)); } strbuf_release(&buf); } @@ -539,7 +553,7 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } - wt_status_collect_changes(s); + wt_status_collect(s); if (s->is_initial) { color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); @@ -566,7 +580,7 @@ void wt_status_print(struct wt_status *s) ; /* nothing */ else if (s->workdir_dirty) printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); - else if (s->workdir_untracked) + else if (s->untracked.nr) printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); else if (s->is_initial) printf("nothing to commit (create/copy files and use \"git add\" to track)\n"); diff --git a/wt-status.h b/wt-status.h index 95f5af8902..a0e75177be 100644 --- a/wt-status.h +++ b/wt-status.h @@ -48,10 +48,11 @@ struct wt_status { FILE *fp; const char *prefix; struct string_list change; + struct string_list untracked; }; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); -void wt_status_collect_changes(struct wt_status *s); +void wt_status_collect(struct wt_status *s); #endif /* STATUS_H */ From 540e694b139dd034b21de087001ac9b6d7606c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Tue, 11 Aug 2009 22:43:59 +0700 Subject: [PATCH 011/102] Prevent diff machinery from examining assume-unchanged entries on worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff-lib.c | 6 ++++-- t/t4039-diff-assume-unchanged.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100755 t/t4039-diff-assume-unchanged.sh diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde74..22da66ef14 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -162,7 +162,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) if (ce_uptodate(ce)) continue; - changed = check_removed(ce, &st); + /* If CE_VALID is set, don't look at workdir for file removal */ + changed = (ce->ce_flags & CE_VALID) ? 0 : check_removed(ce, &st); if (changed) { if (changed < 0) { perror(ce->name); @@ -337,6 +338,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct rev_info *revs = o->unpack_data; int match_missing, cached; + /* if the entry is not checked out, don't examine work tree */ + cached = o->index_only || (idx && (idx->ce_flags & CE_VALID)); /* * Backward compatibility wart - "diff-index -m" does * not mean "do not ignore merges", but "match_missing". @@ -344,7 +347,6 @@ static void do_oneway_diff(struct unpack_trees_options *o, * But with the revision flag parsing, that's found in * "!revs->ignore_merges". */ - cached = o->index_only; match_missing = !revs->ignore_merges; if (cached && idx && ce_stage(idx)) { diff --git a/t/t4039-diff-assume-unchanged.sh b/t/t4039-diff-assume-unchanged.sh new file mode 100755 index 0000000000..9d9498bd95 --- /dev/null +++ b/t/t4039-diff-assume-unchanged.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='diff with assume-unchanged entries' + +. ./test-lib.sh + +# external diff has been tested in t4020-diff-external.sh + +test_expect_success 'setup' ' + echo zero > zero && + git add zero && + git commit -m zero && + echo one > one && + echo two > two && + git add one two && + git commit -m onetwo && + git update-index --assume-unchanged one && + echo borked >> one && + test "$(git ls-files -v one)" = "h one" +' + +test_expect_success 'diff-index does not examine assume-unchanged entries' ' + git diff-index HEAD^ -- one | grep -q 5626abf0f72e58d7a153368ba57db4c673c0e171 +' + +test_expect_success 'diff-files does not examine assume-unchanged entries' ' + rm one && + test -z "$(git diff-files -- one)" +' + +test_done From 9a217391e931763d168d177decfe5e962d306bac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 13 Aug 2009 12:41:14 -0700 Subject: [PATCH 012/102] Fix "unpack-objects --strict" When unpack-objects is run under the --strict option, objects that have pointers to other objects are verified for the reachability at the end, by calling check_object() on each of them, and letting check_object to walk the reachable objects from them using fsck_walk() recursively. The function however misunderstands the semantics of fsck_walk() function when it makes a call to it, setting itself as the callback. fsck_walk() expects the callback function to return a non-zero value to signal an error (negative value causes an immediate abort, positive value is still an error but allows further checks on sibling objects) and return zero to signal a success. The function however returned 1 on some non error cases, and to cover up this mistake, complained only when fsck_walk() did not detect any error. To fix this double-bug, make the function return zero on all success cases, and also check for non-zero return from fsck_walk() for an error. Signed-off-by: Junio C Hamano --- builtin-unpack-objects.c | 16 +++++++++------- t/t5531-deep-submodule-push.sh | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 t/t5531-deep-submodule-push.sh diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 8e831be476..bae00eabb0 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -181,10 +181,10 @@ static void write_cached_object(struct object *obj) static int check_object(struct object *obj, int type, void *data) { if (!obj) - return 0; + return 1; if (obj->flags & FLAG_WRITTEN) - return 1; + return 0; if (type != OBJ_ANY && obj->type != type) die("object type mismatch"); @@ -195,22 +195,24 @@ static int check_object(struct object *obj, int type, void *data) if (type != obj->type || type <= 0) die("object of unexpected type"); obj->flags |= FLAG_WRITTEN; - return 1; + return 0; } if (fsck_object(obj, 1, fsck_error_function)) die("Error in object"); - if (!fsck_walk(obj, check_object, 0)) + if (fsck_walk(obj, check_object, 0)) die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); write_cached_object(obj); - return 1; + return 0; } static void write_rest(void) { unsigned i; - for (i = 0; i < nr_objects; i++) - check_object(obj_list[i].obj, OBJ_ANY, 0); + for (i = 0; i < nr_objects; i++) { + if (obj_list[i].obj) + check_object(obj_list[i].obj, OBJ_ANY, 0); + } } static void added_object(unsigned nr, enum object_type type, diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh new file mode 100644 index 0000000000..65d8d474bc --- /dev/null +++ b/t/t5531-deep-submodule-push.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='unpack-objects' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir pub.git && + GIT_DIR=pub.git git init --bare + GIT_DIR=pub.git git config receive.fsckobjects true && + mkdir work && + ( + cd work && + git init && + mkdir -p gar/bage && + ( + cd gar/bage && + git init && + >junk && + git add junk && + git commit -m "Initial junk" + ) && + git add gar/bage && + git commit -m "Initial superproject" + ) +' + +test_expect_success push ' + ( + cd work && + git push ../pub.git master + ) +' + +test_done From 8f0bef6df91c48d79f982bdb55f784ce445ba5b2 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Thu, 13 Aug 2009 14:29:39 +0200 Subject: [PATCH 013/102] git-apply--interactive: Refactor patch mode code This makes some aspects of the 'git add -p' loop configurable (within the code), so that we can later reuse git-add--interactive for other similar tools. Most fields are fairly straightforward, but APPLY gets a subroutine (instead of just a string a la 'apply --cached') so that we can handle 'checkout -p', which will need to atomically apply the patch twice (index and worktree). Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 82 ++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index df9f231635..360610314e 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -73,6 +73,22 @@ sub colored { # command line options my $patch_mode; +sub apply_patch; + +my %patch_modes = ( + 'stage' => { + DIFF => 'diff-files -p', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Stage', + TARGET => '', + PARTICIPLE => 'staging', + FILTER => 'file-only', + }, +); + +my %patch_mode_flavour = %{$patch_modes{stage}}; + sub run_cmd_pipe { if ($^O eq 'MSWin32' || $^O eq 'msys') { my @invalid = grep {m/[":*]/} @_; @@ -613,12 +629,21 @@ sub add_untracked_cmd { print "\n"; } +sub run_git_apply { + my $cmd = shift; + my $fh; + open $fh, '| git ' . $cmd; + print $fh @_; + return close $fh; +} + sub parse_diff { my ($path) = @_; - my @diff = run_cmd_pipe(qw(git diff-files -p --), $path); + my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); + my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { - @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path); + @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path); } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; @@ -877,6 +902,7 @@ sub edit_hunk_manually { or die "failed to open hunk edit file for writing: " . $!; print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; print $fh @$oldtext; + my $participle = $patch_mode_flavour{PARTICIPLE}; print $fh <{TEXT}}; - } - return close $fh; + return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check', + map { @{$_->{TEXT}} } @_); } sub _restore_terminal_and_die { @@ -988,12 +1011,14 @@ sub edit_hunk_loop { } sub help_patch_cmd { - print colored $help_color, <<\EOF ; -y - stage this hunk -n - do not stage this hunk -q - quit, do not stage this hunk nor any of the remaining ones -a - stage this and all the remaining hunks in the file -d - do not stage this hunk nor any of the remaining hunks in the file + my $verb = lc $patch_mode_flavour{VERB}; + my $target = $patch_mode_flavour{TARGET}; + print colored $help_color, <{BINARY}) } @all_mods; my @them; @@ -1138,8 +1172,9 @@ sub patch_update_file { for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, 'Stage ', - ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'), + print colored $prompt_color, $patch_mode_flavour{VERB}, + ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'), + $patch_mode_flavour{TARGET}, " [y,n,q,a,d,/$other,?]? "; my $line = prompt_single_character; if ($line) { @@ -1313,16 +1348,9 @@ sub patch_update_file { if (@result) { my $fh; - - open $fh, '| git apply --cached --recount'; - for (@{$head->{TEXT}}, @result) { - print $fh $_; - } - if (!close $fh) { - for (@{$head->{TEXT}}, @result) { - print STDERR $_; - } - } + my @patch = (@{$head->{TEXT}}, @result); + my $apply_routine = $patch_mode_flavour{APPLY}; + &$apply_routine(@patch); refresh(); } From b319ef70a94731a5c6f18d07a49d5dda3f06f5d3 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Thu, 13 Aug 2009 14:29:40 +0200 Subject: [PATCH 014/102] Add a small patch-mode testing library The tests for {reset,commit,stash} -p will frequently have to set both worktree and index states to known values, and verify that the outcome (again both worktree and index) are what was expected. Add a small helper library that lets us do these tasks more easily. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- t/lib-patch-mode.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100755 t/lib-patch-mode.sh diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh new file mode 100755 index 0000000000..afb4b6686c --- /dev/null +++ b/t/lib-patch-mode.sh @@ -0,0 +1,36 @@ +. ./test-lib.sh + +set_state () { + echo "$3" > "$1" && + git add "$1" && + echo "$2" > "$1" +} + +save_state () { + noslash="$(echo "$1" | tr / _)" && + cat "$1" > _worktree_"$noslash" && + git show :"$1" > _index_"$noslash" +} + +set_and_save_state () { + set_state "$@" && + save_state "$1" +} + +verify_state () { + test "$(cat "$1")" = "$2" && + test "$(git show :"$1")" = "$3" +} + +verify_saved_state () { + noslash="$(echo "$1" | tr / _)" && + verify_state "$1" "$(cat _worktree_"$noslash")" "$(cat _index_"$noslash")" +} + +save_head () { + git rev-parse HEAD > _head +} + +verify_saved_head () { + test "$(cat _head)" = "$(git rev-parse HEAD)" +} From 46b5139cae7306194a39fdaf5c6abc12ab531c84 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Thu, 13 Aug 2009 14:29:41 +0200 Subject: [PATCH 015/102] builtin-add: refactor the meat of interactive_add() This moves the call setup for 'git add--interactive' to a separate function, as other users will call it without running validate_pathspec() first. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- builtin-add.c | 49 ++++++++++++++++++++++++++++++++----------------- commit.h | 2 ++ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index 581a2a1748..c422a62f32 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -131,10 +131,37 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p return pathspec; } +int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec) +{ + int status, ac, pc = 0; + const char **args; + + if (pathspec) + while (pathspec[pc]) + pc++; + + args = xcalloc(sizeof(const char *), (pc + 5)); + ac = 0; + args[ac++] = "add--interactive"; + if (patch_mode) + args[ac++] = patch_mode; + if (revision) + args[ac++] = revision; + args[ac++] = "--"; + if (pc) { + memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc); + ac += pc; + } + args[ac] = NULL; + + status = run_command_v_opt(args, RUN_GIT_CMD); + free(args); + return status; +} + int interactive_add(int argc, const char **argv, const char *prefix) { - int status, ac; - const char **args; const char **pathspec = NULL; if (argc) { @@ -143,21 +170,9 @@ int interactive_add(int argc, const char **argv, const char *prefix) return -1; } - args = xcalloc(sizeof(const char *), (argc + 4)); - ac = 0; - args[ac++] = "add--interactive"; - if (patch_interactive) - args[ac++] = "--patch"; - args[ac++] = "--"; - if (argc) { - memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc); - ac += argc; - } - args[ac] = NULL; - - status = run_command_v_opt(args, RUN_GIT_CMD); - free(args); - return status; + return run_add_interactive(NULL, + patch_interactive ? "--patch" : NULL, + pathspec); } static int edit_patch(int argc, const char **argv, const char *prefix) diff --git a/commit.h b/commit.h index ba9f63813e..339f1f6f04 100644 --- a/commit.h +++ b/commit.h @@ -137,6 +137,8 @@ int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit **, int); extern int interactive_add(int argc, const char **argv, const char *prefix); +extern int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec); static inline int single_parent(struct commit *commit) { From 5fd448f1142c61da31b28c74d7d340938ab0e01d Mon Sep 17 00:00:00 2001 From: Ori Avtalion Date: Tue, 11 Aug 2009 14:12:13 +0300 Subject: [PATCH 016/102] git stash: Give friendlier errors when there is nothing to apply The change makes sure a stash (given or default) exists before checking if the working tree is dirty. If the default stash is requested, the old message was scary and included a 'fatal' error from rev-parse: fatal: Needed a single revision : no valid stashed state found It is replaced with a friendlier 'Nothing to apply' error, similar to 'git stash branch'. If a specific stash is specified, the 'Needed a single revision' errors from rev-parse are suppressed. Signed-off-by: Ori Avtalion Acked-by: Thomas Rast Acked-by: Nanako Shiraishi Signed-off-by: Junio C Hamano --- git-stash.sh | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/git-stash.sh b/git-stash.sh index 03e589f764..d61c9d03bc 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -162,10 +162,6 @@ show_stash () { } apply_stash () { - git update-index -q --refresh && - git diff-files --quiet --ignore-submodules || - die 'Cannot apply to a dirty working tree, please stage your changes' - unstash_index= while test $# != 0 @@ -184,18 +180,27 @@ apply_stash () { shift done - # current index state - c_tree=$(git write-tree) || - die 'Cannot apply a stash in the middle of a merge' + if test $# = 0 + then + have_stash || die 'Nothing to apply' + fi # stash records the work tree, and is a merge between the # base commit (first parent) and the index tree (second parent). - s=$(git rev-parse --verify --default $ref_stash "$@") && - w_tree=$(git rev-parse --verify "$s:") && - b_tree=$(git rev-parse --verify "$s^1:") && - i_tree=$(git rev-parse --verify "$s^2:") || + s=$(git rev-parse --quiet --verify --default $ref_stash "$@") && + w_tree=$(git rev-parse --quiet --verify "$s:") && + b_tree=$(git rev-parse --quiet --verify "$s^1:") && + i_tree=$(git rev-parse --quiet --verify "$s^2:") || die "$*: no valid stashed state found" + git update-index -q --refresh && + git diff-files --quiet --ignore-submodules || + die 'Cannot apply to a dirty working tree, please stage your changes' + + # current index state + c_tree=$(git write-tree) || + die 'Cannot apply a stash in the middle of a merge' + unstashed_index_tree= if test -n "$unstash_index" && test "$b_tree" != "$i_tree" && test "$c_tree" != "$i_tree" From 1c244f6ee5775b227177a66cdcf49a410d9d6871 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Thu, 13 Aug 2009 21:32:50 +0200 Subject: [PATCH 017/102] git submodule summary: add --files option git submodule summary is providing similar functionality for submodules as git diff-index does for a git project (including the meaning of --cached). But the analogon to git diff-files is missing, so add a --files option to summarize the differences between the index of the super project and the last commit checked out in the working tree of the submodule. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 13 +++++++++++-- git-submodule.sh | 19 ++++++++++++++++--- t/t7401-submodule-summary.sh | 22 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 7dd73ae14e..145802a936 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -15,7 +15,7 @@ SYNOPSIS 'git submodule' [--quiet] init [--] [...] 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference ] [--merge] [--] [...] -'git submodule' [--quiet] summary [--cached] [--summary-limit ] [commit] [--] [...] +'git submodule' [--quiet] summary [--cached|--files] [--summary-limit ] [commit] [--] [...] 'git submodule' [--quiet] foreach 'git submodule' [--quiet] sync [--] [...] @@ -127,7 +127,11 @@ summary:: Show commit summary between the given commit (defaults to HEAD) and working tree/index. For a submodule in question, a series of commits in the submodule between the given super project commit and the - index or working tree (switched by --cached) are shown. + index or working tree (switched by --cached) are shown. If the option + --files is given, show the series of commits in the submodule between + the index of super project the and the working tree of the submodule + (this option doesn't allow to use the --cached option or to provide an + explicit commit). foreach:: Evaluates an arbitrary shell command in each checked out submodule. @@ -169,6 +173,11 @@ OPTIONS commands typically use the commit found in the submodule HEAD, but with this option, the commit stored in the index is used instead. +--files:: + This option is only valid for the summary command. This command + compares the commit in the index with that in the submodule HEAD + when this option is used. + -n:: --summary-limit:: This option is only valid for the summary command. diff --git a/git-submodule.sh b/git-submodule.sh index ebed711da4..9bdd6ea3d0 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -4,7 +4,7 @@ # # Copyright (c) 2007 Lars Hjemli -USAGE="[--quiet] [--cached] \ +USAGE="[--quiet] [--cached|--files] \ [add [-b branch] ]|[status|init|update [-i|--init] [-N|--no-fetch] [--rebase|--merge]|summary [-n|--summary-limit ] []] \ [--] [...]|[foreach ]|[sync [--] [...]]" OPTIONS_SPEC= @@ -16,6 +16,7 @@ command= branch= reference= cached= +files= nofetch= update= @@ -460,6 +461,7 @@ set_name_rev () { cmd_summary() { summary_limit=-1 for_status= + diff_cmd=diff-index # parse $args after "submodule ... summary". while test $# -ne 0 @@ -468,6 +470,9 @@ cmd_summary() { --cached) cached="$1" ;; + --files) + files="$1" + ;; --for-status) for_status="$1" ;; @@ -504,9 +509,17 @@ cmd_summary() { head=HEAD fi + if [ -n "$files" ] + then + test -n "$cached" && + die "--cached cannot be used with --files" + diff_cmd=diff-files + head= + fi + cd_to_toplevel # Get modified modules cared by user - modules=$(git diff-index $cached --raw $head -- "$@" | + modules=$(git $diff_cmd $cached --raw $head -- "$@" | egrep '^:([0-7]* )?160000' | while read mod_src mod_dst sha1_src sha1_dst status name do @@ -520,7 +533,7 @@ cmd_summary() { test -z "$modules" && return - git diff-index $cached --raw $head -- $modules | + git $diff_cmd $cached --raw $head -- $modules | egrep '^:([0-7]* )?160000' | cut -c2- | while read mod_src mod_dst sha1_src sha1_dst status name diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 61498293b9..6cc16c39fe 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -56,6 +56,15 @@ test_expect_success 'modified submodule(forward)' " EOF " +test_expect_success 'modified submodule(forward), --files' " + git submodule summary --files >actual && + diff actual - <<-EOF +* sm1 $head1...$head2 (1): + > Add foo3 + +EOF +" + commit_file sm1 && cd sm1 && git reset --hard HEAD~2 >/dev/null && @@ -114,6 +123,15 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' " EOF " +test_expect_success 'typechanged submodule(submodule->blob), --files' " + git submodule summary --files >actual && + diff actual - <<-EOF +* sm1 $head5(blob)->$head4(submodule) (3): + > Add foo5 + +EOF +" + rm -rf sm1 && git checkout-index sm1 test_expect_success 'typechanged submodule(submodule->blob)' " @@ -205,4 +223,8 @@ test_expect_success '--for-status' " EOF " +test_expect_success 'fail when using --files together with --cached' " + test_must_fail git submodule summary --files --cached +" + test_done From ef92e1a43632c5b29b89da058f45d9c085ad18b7 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Sat, 15 Aug 2009 10:40:42 +0200 Subject: [PATCH 018/102] Documentaqtion/git-submodule.txt: Typofix Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 145802a936..bb7d159179 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -129,7 +129,7 @@ summary:: in the submodule between the given super project commit and the index or working tree (switched by --cached) are shown. If the option --files is given, show the series of commits in the submodule between - the index of super project the and the working tree of the submodule + the index of the super project and the working tree of the submodule (this option doesn't allow to use the --cached option or to provide an explicit commit). From 60c2993c92fa1aa7f4d4aab7de6d8769052ced6f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 15 Aug 2009 01:58:37 -0700 Subject: [PATCH 019/102] Documentation/git-commit.txt: describe --dry-run Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index d01ff5adae..64f94cfe12 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u] [--amend] [--dry-run] - [(-c | -C) ] [-F | -m ] + [(-c | -C) ] [-F | -m ] [--dry-run] [--allow-empty] [--no-verify] [-e] [--author=] [--cleanup=] [--] [[-i | -o ]...] @@ -42,10 +42,9 @@ The content to be added can be specified in several ways: by one which files should be part of the commit, before finalizing the operation. Currently, this is done by invoking 'git-add --interactive'. -The 'git-status' command can be used to obtain a +The `--dry-run` option can be used to obtain a summary of what is included by any of the above for the next -commit by giving the same set of parameters you would give to -this command. +commit by giving the same set of parameters (options and paths). If you make a commit and then find a mistake immediately after that, you can recover from it with 'git-reset'. @@ -70,6 +69,12 @@ OPTIONS Like '-C', but with '-c' the editor is invoked, so that the user can further edit the commit message. +--dry-run:: + Do not actually make a commit, but show the list of paths + with updates in the index, paths with changes in the work tree, + and paths that are untracked, similar to the one that is given + in the commit log editor. + -F :: --file=:: Take the commit message from the given file. Use '-' to From 3fa509dfbdb8526453f4213f79b371bdfa493f0e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 15 Aug 2009 02:14:14 -0700 Subject: [PATCH 020/102] git commit --dry-run -v: show diff in color when asked The earlier implementation of --dry-run didn't duplicate the use of color "git status -v" set up for diff output. Signed-off-by: Junio C Hamano --- builtin-commit.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 1c200eb963..200ffdaad4 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -979,9 +979,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix) argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix, &s); - if (dry_run) + if (dry_run) { + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; return dry_run_commit(argc, argv, prefix, &s); - + } index_file = prepare_index(argc, argv, prefix, 0); /* Set up everything for writing the commit object. This includes From d002ef4d9446b9fe4d0c397131edce58781df2f1 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 15 Aug 2009 13:48:31 +0200 Subject: [PATCH 021/102] Implement 'git reset --patch' This introduces a --patch mode for git-reset. The basic case is git reset --patch -- [files...] which acts as the opposite of 'git add --patch -- [files...]': it offers hunks for *un*staging. Advanced usage is git reset --patch -- [files...] which offers hunks from the diff between the index and for forward application to the index. (That is, the basic case is just = HEAD.) Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/git-reset.txt | 15 ++++++-- builtin-reset.c | 19 ++++++++++ git-add--interactive.perl | 57 +++++++++++++++++++++++++++--- t/t7105-reset-patch.sh | 69 +++++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 6 deletions(-) create mode 100755 t/t7105-reset-patch.sh diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index abb25d1c00..469cf6dbac 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git reset' [--mixed | --soft | --hard | --merge] [-q] [] 'git reset' [-q] [] [--] ... +'git reset' --patch [] [--] [...] DESCRIPTION ----------- @@ -23,8 +24,9 @@ the undo in the history. If you want to undo a commit other than the latest on a branch, linkgit:git-revert[1] is your friend. -The second form with 'paths' is used to revert selected paths in -the index from a given commit, without moving HEAD. +The second and third forms with 'paths' and/or --patch are used to +revert selected paths in the index from a given commit, without moving +HEAD. OPTIONS @@ -50,6 +52,15 @@ OPTIONS and updates the files that are different between the named commit and the current commit in the working tree. +-p:: +--patch:: + Interactively select hunks in the difference between the index + and (defaults to HEAD). The chosen hunks are applied + in reverse to the index. ++ +This means that `git reset -p` is the opposite of `git add -p` (see +linkgit:git-add[1]). + -q:: Be quiet, only report errors. diff --git a/builtin-reset.c b/builtin-reset.c index 5fa1789d0c..246a127b5f 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -142,6 +142,17 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } +static int interactive_reset(const char *revision, const char **argv, + const char *prefix) +{ + const char **pathspec = NULL; + + if (*argv) + pathspec = get_pathspec(prefix, argv); + + return run_add_interactive(revision, "--patch=reset", pathspec); +} + static int read_from_tree(const char *prefix, const char **argv, unsigned char *tree_sha1, int refresh_flags) { @@ -183,6 +194,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) int cmd_reset(int argc, const char **argv, const char *prefix) { int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; + int patch_mode = 0; const char *rev = "HEAD"; unsigned char sha1[20], *orig = NULL, sha1_orig[20], *old_orig = NULL, sha1_old_orig[20]; @@ -198,6 +210,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", MERGE), OPT_BOOLEAN('q', NULL, &quiet, "disable showing new HEAD in hard reset and progress message"), + OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; @@ -251,6 +264,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die("Could not parse object '%s'.", rev); hashcpy(sha1, commit->object.sha1); + if (patch_mode) { + if (reset_type != NONE) + die("--patch is incompatible with --{hard,mixed,soft}"); + return interactive_reset(rev, argv + i, prefix); + } + /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 360610314e..d14f48c837 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -72,6 +72,7 @@ sub colored { # command line options my $patch_mode; +my $patch_mode_revision; sub apply_patch; @@ -85,6 +86,24 @@ my %patch_modes = ( PARTICIPLE => 'staging', FILTER => 'file-only', }, + 'reset_head' => { + DIFF => 'diff-index -p --cached', + APPLY => sub { apply_patch 'apply -R --cached', @_; }, + APPLY_CHECK => 'apply -R --cached', + VERB => 'Unstage', + TARGET => '', + PARTICIPLE => 'unstaging', + FILTER => 'index-only', + }, + 'reset_nothead' => { + DIFF => 'diff-index -R -p --cached', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Apply', + TARGET => ' to index', + PARTICIPLE => 'applying', + FILTER => 'index-only', + }, ); my %patch_mode_flavour = %{$patch_modes{stage}}; @@ -206,7 +225,14 @@ sub list_modified { return if (!@tracked); } - my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + my $reference; + if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') { + $reference = $patch_mode_revision; + } elsif (is_initial_commit()) { + $reference = get_empty_tree(); + } else { + $reference = 'HEAD'; + } for (run_cmd_pipe(qw(git diff-index --cached --numstat --summary), $reference, '--', @tracked)) { @@ -640,6 +666,9 @@ sub run_git_apply { sub parse_diff { my ($path) = @_; my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); + if (defined $patch_mode_revision) { + push @diff_cmd, $patch_mode_revision; + } my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { @@ -1391,11 +1420,31 @@ EOF sub process_args { return unless @ARGV; my $arg = shift @ARGV; - if ($arg eq "--patch") { - $patch_mode = 1; - $arg = shift @ARGV or die "missing --"; + if ($arg =~ /--patch(?:=(.*))?/) { + if (defined $1) { + if ($1 eq 'reset') { + $patch_mode = 'reset_head'; + $patch_mode_revision = 'HEAD'; + $arg = shift @ARGV or die "missing --"; + if ($arg ne '--') { + $patch_mode_revision = $arg; + $patch_mode = ($arg eq 'HEAD' ? + 'reset_head' : 'reset_nothead'); + $arg = shift @ARGV or die "missing --"; + } + } elsif ($1 eq 'stage') { + $patch_mode = 'stage'; + $arg = shift @ARGV or die "missing --"; + } else { + die "unknown --patch mode: $1"; + } + } else { + $patch_mode = 'stage'; + $arg = shift @ARGV or die "missing --"; + } die "invalid argument $arg, expecting --" unless $arg eq "--"; + %patch_mode_flavour = %{$patch_modes{$patch_mode}}; } elsif ($arg ne "--") { die "invalid argument $arg, expecting --"; diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh new file mode 100755 index 0000000000..c1f4fc3c65 --- /dev/null +++ b/t/t7105-reset-patch.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='git reset --patch' +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add dir && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before foo, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_and_save_state dir/foo work work + (echo n; echo n) | git reset -p && + verify_saved_state dir/foo && + verify_saved_state bar +' + +test_expect_success 'git reset -p' ' + (echo n; echo y) | git reset -p && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^' ' + (echo n; echo y) | git reset -p HEAD^ && + verify_state dir/foo work parent && + verify_saved_state bar +' + +# The idea in the rest is that bar sorts first, so we always say 'y' +# first and if the path limiter fails it'll apply to bar instead of +# dir/foo. There's always an extra 'n' to reject edits to dir/foo in +# the failure case (and thus get out of the loop). + +test_expect_success 'git reset -p dir' ' + set_state dir/foo work work + (echo y; echo n) | git reset -p dir && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p -- foo (inside dir)' ' + set_state dir/foo work work + (echo y; echo n) | (cd dir && git reset -p -- foo) && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^ -- dir' ' + (echo y; echo n) | git reset -p HEAD^ -- dir && + verify_state dir/foo work parent && + verify_saved_state bar +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + + +test_done From 4f353658b9c15e9188530fac5ae79d0aa1538e85 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 15 Aug 2009 13:48:30 +0200 Subject: [PATCH 022/102] Implement 'git checkout --patch' This introduces a --patch mode for git-checkout. In the index usage git checkout --patch -- [files...] it lets the user discard edits from the at the granularity of hunks (by selecting hunks from 'git diff' and then reverse applying them to the worktree). We also accept a revision argument. In the case git checkout --patch HEAD -- [files...] we offer hunks from the difference between HEAD and the worktree, and reverse applies them to both index and worktree, allowing you to discard staged changes completely. In the non-HEAD usage git checkout --patch -- [files...] it offers hunks from the difference between the worktree and . The chosen hunks are then applied to both index and worktree. The application to worktree and index is done "atomically" in the sense that we first check if the patch applies to the index (it should always apply to the worktree). If it does not, we give the user a choice to either abort or apply to the worktree anyway. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 13 +++- builtin-checkout.c | 19 ++++++ git-add--interactive.perl | 61 +++++++++++++++++++ t/t2015-checkout-patch.sh | 107 +++++++++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100755 t/t2015-checkout-patch.sh diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index ad4b31e892..26a5447fbf 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git checkout' [-q] [-f] [-m] [] 'git checkout' [-q] [-f] [-m] [-b ] [] 'git checkout' [-f|--ours|--theirs|-m|--conflict=