From patchwork Fri Dec 13 08:07:48 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290105 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id A546F6C1 for ; Fri, 13 Dec 2019 08:08:15 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6F0882467B for ; Fri, 13 Dec 2019 08:08:15 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="X6Hd05Qn" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726642AbfLMIIO (ORCPT ); Fri, 13 Dec 2019 03:08:14 -0500 Received: from mail-wr1-f65.google.com ([209.85.221.65]:44065 "EHLO mail-wr1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725991AbfLMIIN (ORCPT ); Fri, 13 Dec 2019 03:08:13 -0500 Received: by mail-wr1-f65.google.com with SMTP id q10so5564730wrm.11 for ; Fri, 13 Dec 2019 00:08:10 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=hCQ3VjN0zYeZFnaFoZziCAsGb0n59gqkKfW5eGg8oO8=; b=X6Hd05Qn8jJxh/9lIQ/6WD4VFfRkFw2B/DCMlsVQxrXpol8VrO74X641IWEHrQhgH/ Lj7DpM9/RKcgRfMbSb0iWp/OvMfk2wQiy77FQEJXQeLxtSybwJrR2Tt1LuMFbyu1y/2C DG6+wI7CEht43DCPkxapoqs3M2PfqdzKkXCN+Zii1pTHbdsWMiBLyo+Zfby2uSNVkU4y yopLH5Y/b3/oqrTtubCe29PFH+p/iKzCCzIOl5hOrqVlr1+T6DFxgElXw50lMz+7qRGz 6A6KyrkvO8n5qn/yqC4ZHrBYMYXJI/uc5xO2UcB9jvCqNcrIGikr/StQXi2M7113JzVZ tyfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=hCQ3VjN0zYeZFnaFoZziCAsGb0n59gqkKfW5eGg8oO8=; b=NIuIcYFkAmyRtnA2fWZAFGHJ/EvgigaM38KOx8MvJo0xhCmXWz+abIGolA7/kgmQQA wkRbDBcIPCDyZ9/GUM9U/pnG2povHX6ckrgyhE0VnCh8dNZ75czhkEGzIeIlJfE6AQYc VNUgSrakloN+AjPhhvMbOWUACsannJK1wIVmIvBP6RWXYNxzKxI4WHqwTM+X/TwNLKPE QwklnR08c7/mS6z/BOWhtWPzzMXWfysmivjQOvyqERssgaYOzq+z3DYSSXghlPgma/2z hu2CTo23Push9tPYwER+T4YFIr8tbFigJyYYX+x75iO6042N4zPpEMYWeoonAzUn32NC SYVg== X-Gm-Message-State: APjAAAWQ2mVw/apJxCbhjw6MHH92ySTa2Id2RE/G13Zud/Y9e9M0K4yM YvhtCWGZC7dVPPuiPbWWWe9Wnk6i X-Google-Smtp-Source: APXvYqx6/62v0iw//SnqdHYk/utcEyJ6HrgnUUBsDuCzkomQ0+xGo0YXMK9JpBUisAi4oqcH2VUhHQ== X-Received: by 2002:adf:f604:: with SMTP id t4mr11765767wrp.33.1576224488990; Fri, 13 Dec 2019 00:08:08 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id n3sm9513688wmc.27.2019.12.13.00.08.08 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:08 -0800 (PST) Message-Id: <63b571f351beb95828fb754bc94dac36347e1083.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:48 +0000 Subject: [PATCH 01/19] built-in add -i: start implementing the `patch` functionality in C Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin In the previous steps, we re-implemented the main loop of `git add -i` in C, and most of the commands. Notably, we left out the actual functionality of `patch`, as the relevant code makes up more than half of `git-add--interactive.perl`, and is actually pretty independent of the rest of the commands. With this commit, we start to tackle that `patch` part. For better separation of concerns, we keep the code in a separate file, `add-patch.c`. The new code is still guarded behind the `add.interactive.useBuiltin` config setting, and for the moment, it can only be called via `git add -p`. The actual functionality follows the original implementation of 5cde71d64aff (git-add --interactive, 2006-12-10), but not too closely (for example, we use string offsets rather than copying strings around, and after seeing whether the `k` and `j` commands are applicable, in the C version we remember which previous/next hunk was undecided, and use it rather than looking again when the user asked to jump). As a further deviation from that commit, We also use a comma instead of a slash to separate the available commands in the prompt, as the current version of the Perl script does this, and we also add a line about the question mark ("print help") to the help text. While it is tempting to use this conversion of `git add -p` as an excuse to work on `apply_all_patches()` so that it does _not_ want to read a file from `stdin` or from a file, but accepts, say, an `strbuf` instead, we will refrain from this particular rabbit hole at this stage. Signed-off-by: Johannes Schindelin --- Makefile | 1 + add-interactive.h | 1 + add-patch.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++ builtin/add.c | 15 ++- 4 files changed, 277 insertions(+), 5 deletions(-) create mode 100644 add-patch.c diff --git a/Makefile b/Makefile index 6c4a1e0ee5..0345d7408b 100644 --- a/Makefile +++ b/Makefile @@ -824,6 +824,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat LIB_OBJS += abspath.o LIB_OBJS += add-interactive.o +LIB_OBJS += add-patch.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o diff --git a/add-interactive.h b/add-interactive.h index 7043b8741d..0e3d93acc9 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -4,5 +4,6 @@ struct repository; struct pathspec; int run_add_i(struct repository *r, const struct pathspec *ps); +int run_add_p(struct repository *r, const struct pathspec *ps); #endif diff --git a/add-patch.c b/add-patch.c new file mode 100644 index 0000000000..d1b1a080e4 --- /dev/null +++ b/add-patch.c @@ -0,0 +1,265 @@ +#include "cache.h" +#include "add-interactive.h" +#include "strbuf.h" +#include "run-command.h" +#include "argv-array.h" +#include "pathspec.h" + +struct hunk { + size_t start, end; + enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use; +}; + +struct add_p_state { + struct repository *r; + struct strbuf answer, buf; + + /* parsed diff */ + struct strbuf plain; + struct hunk head; + struct hunk *hunk; + size_t hunk_nr, hunk_alloc; +}; + +static void setup_child_process(struct add_p_state *s, + struct child_process *cp, ...) +{ + va_list ap; + const char *arg; + + va_start(ap, cp); + while ((arg = va_arg(ap, const char *))) + argv_array_push(&cp->args, arg); + va_end(ap); + + cp->git_cmd = 1; + argv_array_pushf(&cp->env_array, + INDEX_ENVIRONMENT "=%s", s->r->index_file); +} + +static int parse_diff(struct add_p_state *s, const struct pathspec *ps) +{ + struct strbuf *plain = &s->plain; + struct child_process cp = CHILD_PROCESS_INIT; + char *p, *pend; + size_t i; + struct hunk *hunk = NULL; + int res; + + /* Use `--no-color` explicitly, just in case `diff.color = always`. */ + setup_child_process(s, &cp, + "diff-files", "-p", "--no-color", "--", NULL); + for (i = 0; i < ps->nr; i++) + argv_array_push(&cp.args, ps->items[i].original); + + res = capture_command(&cp, plain, 0); + if (res) + return error(_("could not parse diff")); + if (!plain->len) + return 0; + strbuf_complete_line(plain); + + /* parse hunks */ + p = plain->buf; + pend = p + plain->len; + while (p != pend) { + char *eol = memchr(p, '\n', pend - p); + if (!eol) + eol = pend; + + if (starts_with(p, "diff ")) { + if (p != plain->buf) + BUG("multi-file diff not yet handled"); + hunk = &s->head; + } else if (p == plain->buf) + BUG("diff starts with unexpected line:\n" + "%.*s\n", (int)(eol - p), p); + else if (starts_with(p, "@@ ")) { + s->hunk_nr++; + ALLOC_GROW(s->hunk, s->hunk_nr, + s->hunk_alloc); + hunk = s->hunk + s->hunk_nr - 1; + memset(hunk, 0, sizeof(*hunk)); + + hunk->start = p - plain->buf; + } + + p = eol == pend ? pend : eol + 1; + hunk->end = p - plain->buf; + } + + return 0; +} + +static void render_hunk(struct add_p_state *s, struct hunk *hunk, + struct strbuf *out) +{ + strbuf_add(out, s->plain.buf + hunk->start, + hunk->end - hunk->start); +} + +static void reassemble_patch(struct add_p_state *s, struct strbuf *out) +{ + struct hunk *hunk; + size_t i; + + render_hunk(s, &s->head, out); + + for (i = 0; i < s->hunk_nr; i++) { + hunk = s->hunk + i; + if (hunk->use == USE_HUNK) + render_hunk(s, hunk, out); + } +} + +static const char help_patch_text[] = +N_("y - stage this hunk\n" + "n - do not stage this hunk\n" + "a - stage this and all the remaining hunks\n" + "d - do not stage this hunk nor any of the remaining hunks\n" + "j - leave this hunk undecided, see next undecided hunk\n" + "J - leave this hunk undecided, see next hunk\n" + "k - leave this hunk undecided, see previous undecided hunk\n" + "K - leave this hunk undecided, see previous hunk\n" + "? - print help\n"); + +static int patch_update_file(struct add_p_state *s) +{ + size_t hunk_index = 0; + ssize_t i, undecided_previous, undecided_next; + struct hunk *hunk; + char ch; + struct child_process cp = CHILD_PROCESS_INIT; + + if (!s->hunk_nr) + return 0; + + strbuf_reset(&s->buf); + render_hunk(s, &s->head, &s->buf); + fputs(s->buf.buf, stdout); + for (;;) { + if (hunk_index >= s->hunk_nr) + hunk_index = 0; + hunk = s->hunk + hunk_index; + + undecided_previous = -1; + for (i = hunk_index - 1; i >= 0; i--) + if (s->hunk[i].use == UNDECIDED_HUNK) { + undecided_previous = i; + break; + } + + undecided_next = -1; + for (i = hunk_index + 1; i < s->hunk_nr; i++) + if (s->hunk[i].use == UNDECIDED_HUNK) { + undecided_next = i; + break; + } + + /* Everything decided? */ + if (undecided_previous < 0 && undecided_next < 0 && + hunk->use != UNDECIDED_HUNK) + break; + + strbuf_reset(&s->buf); + render_hunk(s, hunk, &s->buf); + fputs(s->buf.buf, stdout); + + strbuf_reset(&s->buf); + if (undecided_previous >= 0) + strbuf_addstr(&s->buf, ",k"); + if (hunk_index) + strbuf_addstr(&s->buf, ",K"); + if (undecided_next >= 0) + strbuf_addstr(&s->buf, ",j"); + if (hunk_index + 1 < s->hunk_nr) + strbuf_addstr(&s->buf, ",J"); + printf("(%"PRIuMAX"/%"PRIuMAX") ", + (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr); + printf(_("Stage this hunk [y,n,a,d%s,?]? "), s->buf.buf); + fflush(stdout); + if (strbuf_getline(&s->answer, stdin) == EOF) + break; + strbuf_trim_trailing_newline(&s->answer); + + if (!s->answer.len) + continue; + ch = tolower(s->answer.buf[0]); + if (ch == 'y') { + hunk->use = USE_HUNK; +soft_increment: + hunk_index = undecided_next < 0 ? + s->hunk_nr : undecided_next; + } else if (ch == 'n') { + hunk->use = SKIP_HUNK; + goto soft_increment; + } else if (ch == 'a') { + for (; hunk_index < s->hunk_nr; hunk_index++) { + hunk = s->hunk + hunk_index; + if (hunk->use == UNDECIDED_HUNK) + hunk->use = USE_HUNK; + } + } else if (ch == 'd') { + for (; hunk_index < s->hunk_nr; hunk_index++) { + hunk = s->hunk + hunk_index; + if (hunk->use == UNDECIDED_HUNK) + hunk->use = SKIP_HUNK; + } + } else if (hunk_index && s->answer.buf[0] == 'K') + hunk_index--; + else if (hunk_index + 1 < s->hunk_nr && + s->answer.buf[0] == 'J') + hunk_index++; + else if (undecided_previous >= 0 && + s->answer.buf[0] == 'k') + hunk_index = undecided_previous; + else if (undecided_next >= 0 && s->answer.buf[0] == 'j') + hunk_index = undecided_next; + else + puts(_(help_patch_text)); + } + + /* Any hunk to be used? */ + for (i = 0; i < s->hunk_nr; i++) + if (s->hunk[i].use == USE_HUNK) + break; + + if (i < s->hunk_nr) { + /* At least one hunk selected: apply */ + strbuf_reset(&s->buf); + reassemble_patch(s, &s->buf); + + discard_index(s->r->index); + setup_child_process(s, &cp, "apply", "--cached", NULL); + if (pipe_command(&cp, s->buf.buf, s->buf.len, + NULL, 0, NULL, 0)) + error(_("'git apply --cached' failed")); + if (!repo_read_index(s->r)) + repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0, + 1, NULL, NULL, NULL); + } + + putchar('\n'); + return 0; +} + +int run_add_p(struct repository *r, const struct pathspec *ps) +{ + struct add_p_state s = { r, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; + + if (discard_index(r->index) < 0 || repo_read_index(r) < 0 || + repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, + NULL, NULL, NULL) < 0 || + parse_diff(&s, ps) < 0) { + strbuf_release(&s.plain); + return -1; + } + + if (s.hunk_nr) + patch_update_file(&s); + + strbuf_release(&s.answer); + strbuf_release(&s.buf); + strbuf_release(&s.plain); + return 0; +} diff --git a/builtin/add.c b/builtin/add.c index d4686d5218..1deb59a642 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -189,12 +189,17 @@ int run_add_interactive(const char *revision, const char *patch_mode, int use_builtin_add_i = git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); - if (!patch_mode) { - if (use_builtin_add_i < 0) - git_config_get_bool("add.interactive.usebuiltin", - &use_builtin_add_i); - if (use_builtin_add_i == 1) + if (use_builtin_add_i < 0) + git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i); + + if (use_builtin_add_i == 1) { + if (!patch_mode) return !!run_add_i(the_repository, pathspec); + if (strcmp(patch_mode, "--patch")) + die("'%s' not yet supported in the built-in add -p", + patch_mode); + return !!run_add_p(the_repository, pathspec); } argv_array_push(&argv, "add--interactive"); From patchwork Fri Dec 13 08:07:49 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290103 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C881A14BD for ; Fri, 13 Dec 2019 08:08:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id A776224679 for ; Fri, 13 Dec 2019 08:08:13 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="GwMmoiaG" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726518AbfLMIIM (ORCPT ); Fri, 13 Dec 2019 03:08:12 -0500 Received: from mail-wr1-f67.google.com ([209.85.221.67]:37837 "EHLO mail-wr1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725890AbfLMIIM (ORCPT ); Fri, 13 Dec 2019 03:08:12 -0500 Received: by mail-wr1-f67.google.com with SMTP id w15so5618194wru.4 for ; Fri, 13 Dec 2019 00:08:10 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=WjSHLmTjBYyz9S1qv/kniGfV3/zKvBryGHZwD207f6o=; b=GwMmoiaGx97oRXzgRmFykr4sfyXy8WNDYozs93M689hNZiT47bVa5ZyWjjdrkxeffw QL7fP1hoiCI9KHWRpY/mXx3IDyIWsTAv53TVBzMDr75AmVR3lN4MEwLB53NKhF+oF/TA 3XWfOSqUluv7zxy05uApfFZFd1YLIa9+1szqycaFmo0rD8hxlGVOQ3dcU8PH8Jqe9+c9 5MbwYArJs9BWPwi55h6qqmaPV5vT/5+D21l2c0yB4gyEt8F2RDfaAn8kFArRhC4sO2Gy nBGbZr7sTgODApFhpPt+VBksxxUzZgIFb5smjmW9tu8seoFL2DzuFXII3rkQwlqZc/NU xoXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=WjSHLmTjBYyz9S1qv/kniGfV3/zKvBryGHZwD207f6o=; b=RK8c32EPH8IclfPuuz+Rkz1CnGVrrI3SIsQzNQTUah/Un/O/RAxSfR9c3DxFVMhTS0 DtGjOhDza5HhpjDyNz0ogVkqOFp9Vbh7RqgwxsI7KKuMMAJy4Ee6YjI78lpBN18+9dCl riJ4lIxIZ5mJqYk45/KnTrnatgUrVCOLIjT2yqxecYFWH4LvUtGwao1TCnh3WoQekqW1 zteofcLPKNsr/DKZGTvjfgQ4J7SIb45TPwOCifqj9XAfGMONaxfqhUN7+tsz7L2OrOPk 5s1gMvctmzUviXBjPbL9PPVICjZy6rtaDhsPOwufGXAq7lVhCdiHuTQRkmO7PFFa8JDo MdWg== X-Gm-Message-State: APjAAAV8zONaXU+PqiXBYfL6h3lhISDgcYh5SIogI7HKAuK6PWm1WAsf Dgkb6yjlwSKXio3z1h9d0EKrf1oq X-Google-Smtp-Source: APXvYqwFt/SnN3l4XKiDWkq8Cmn0Y+QUHhEiXxuWLMZtY8a3EuE/6ozZqcI/F/PEBQDL/bx6RVVu8A== X-Received: by 2002:adf:cd03:: with SMTP id w3mr10763641wrm.191.1576224489613; Fri, 13 Dec 2019 00:08:09 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id i11sm9276339wrs.10.2019.12.13.00.08.09 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:09 -0800 (PST) Message-Id: <03feb2f28bbfb4779afb4a26009cc30c939f0436.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:49 +0000 Subject: [PATCH 02/19] built-in add -i: wire up the new C code for the `patch` command Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin The code in `git-add--interactive.perl` that takes care of the `patch` command can look quite intimidating. There are so many modes in which it can be called, for example. But for the `patch` command in `git add -i`, only one mode is relevant: the `stage` mode. And we just implemented the beginnings of that mode in C so far. So let's use it when `add.interactive.useBuiltin=true`. Now, while the code in `add-patch.c` is far from reaching feature parity with the code in `git-add--interactive.perl` (color is not implemented, the diff algorithm cannot be configured, the colored diff cannot be post-processed via `interactive.diffFilter`, many commands are unimplemented yet, etc), hooking it all up with the part of `git add -i` that is already converted to C makes it easier to test and develop it. Note: at this stage, both the `add.interactive.useBuiltin` config setting is still safely opt-in, and will probably be fore quite some time, to allow for thorough testing "in the wild" without adversely affecting existing users. Signed-off-by: Johannes Schindelin --- add-interactive.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index f395d54c08..034c1dc02f 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -917,15 +917,18 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps, count = list_and_choose(s, files, opts); if (count >= 0) { struct argv_array args = ARGV_ARRAY_INIT; + struct pathspec ps_selected = { 0 }; - argv_array_pushl(&args, "git", "add--interactive", "--patch", - "--", NULL); for (i = 0; i < files->items.nr; i++) if (files->selected[i]) argv_array_push(&args, files->items.items[i].string); - res = run_command_v_opt(args.argv, 0); + parse_pathspec(&ps_selected, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, + PATHSPEC_LITERAL_PATH, "", args.argv); + res = run_add_p(s->r, &ps_selected); argv_array_clear(&args); + clear_pathspec(&ps_selected); } return res; From patchwork Fri Dec 13 08:07:50 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290107 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6B1B76C1 for ; Fri, 13 Dec 2019 08:08:16 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 3D2452467B for ; Fri, 13 Dec 2019 08:08:16 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="dpCHmcIe" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726646AbfLMIIP (ORCPT ); Fri, 13 Dec 2019 03:08:15 -0500 Received: from mail-wm1-f67.google.com ([209.85.128.67]:51727 "EHLO mail-wm1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726368AbfLMIIN (ORCPT ); Fri, 13 Dec 2019 03:08:13 -0500 Received: by mail-wm1-f67.google.com with SMTP id d73so5270937wmd.1 for ; Fri, 13 Dec 2019 00:08:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=+PpfMtGZebBWSOtAUXgzbvL3kkBMsHYpj4TYS3Xx4uc=; b=dpCHmcIe+izi6Q8i+cS0BIpEfoTqI+Kt6I/YOf0TaWhXIuJyPxPCb55EgfQtWfm7F+ JWBWg31JhD0f47Mfsap1Nv+0vgn4mG6gv8H6aQFPKRa0800zuHIT16AO84qhqDRoo23H XQ7YvnHZFvtf5WBGD5gUsALaOfQbQSws786fUfEWY2UuWjTfyaw2/Up+1XFitygLziP6 +1LfTBNH56P9sbw1LuT9N2a53r2EakgsM2zIDqm3sEVeLyDMN1Z7dwlqszmiv9OUQaHy UjbOiTdtWVl5oDulZBsT04IkGpK8r+pSoWruGMCmF8n4dEWF+sHzL1a7utLgDLIZmwMG t7Qw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=+PpfMtGZebBWSOtAUXgzbvL3kkBMsHYpj4TYS3Xx4uc=; b=HrVqroyWaZxDLY+S4NXX1WEfMeqlkp5mqBl6pE+Ud4tgxTNWwZJ/pb3dhw5rvV7dSe aAhrWTMH6xYv6L+bFWCouz5v3R1Pl43pm7XgzfLVf99+3YQv5DL5QU6XKSW/Ny1d0gT8 omqRcOnVh2naSrMJFHQpvYzHL4g2iKVsg/OHK7k2U7eFoKFEISI3Qdgp6W4+FyKHSHyZ FyPi6IJLAajm0pKjzwPfkvd2UcqWYs41g5rq6naUCnSm9zamVmRKJnZ7WhtrU7Dd+bwd hkdhy7UXLknfnM+LCj6M0ii/6G2dmumHZmMW9YyR279hueNUdI16WH6BBuIwB8DRfF55 pILA== X-Gm-Message-State: APjAAAVFHQi1J4WbFwA+gG4w5gTmoQ1fuGBNkagaKMvM2oEjADo6khiH w7ZK1nNatCOKZcar1/rKc9RZPBxk X-Google-Smtp-Source: APXvYqxYhEp2054YVD/O+xxkXn9wG2j0uOBvyXT2e3aitAuqkY/4M3P4X+j6SV2VXERRx4rs2X0wTg== X-Received: by 2002:a1c:5419:: with SMTP id i25mr11999794wmb.150.1576224490433; Fri, 13 Dec 2019 00:08:10 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id y22sm3559418wma.35.2019.12.13.00.08.09 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:09 -0800 (PST) Message-Id: <438e6519fb7e81c451ebba3ea9efffc26b4c7a17.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:50 +0000 Subject: [PATCH 03/19] built-in add -p: show colored hunks by default Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin Just like the Perl version, we now generate two diffs if `color.diff` is set: one with and one without color. Then we parse them in parallel and record which hunks start at which offsets in both. Note that this is a (slight) deviation from the way the Perl version did it: we are no longer reading the output of `diff-files` line by line (which is more natural for Perl than for C), but in one go, and parse everything later, so we might just as well do it in synchrony. Signed-off-by: Johannes Schindelin --- add-patch.c | 79 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/add-patch.c b/add-patch.c index d1b1a080e4..79eefa9505 100644 --- a/add-patch.c +++ b/add-patch.c @@ -4,9 +4,10 @@ #include "run-command.h" #include "argv-array.h" #include "pathspec.h" +#include "color.h" struct hunk { - size_t start, end; + size_t start, end, colored_start, colored_end; enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use; }; @@ -15,7 +16,7 @@ struct add_p_state { struct strbuf answer, buf; /* parsed diff */ - struct strbuf plain; + struct strbuf plain, colored; struct hunk head; struct hunk *hunk; size_t hunk_nr, hunk_alloc; @@ -39,26 +40,50 @@ static void setup_child_process(struct add_p_state *s, static int parse_diff(struct add_p_state *s, const struct pathspec *ps) { - struct strbuf *plain = &s->plain; + struct argv_array args = ARGV_ARRAY_INIT; + struct strbuf *plain = &s->plain, *colored = NULL; struct child_process cp = CHILD_PROCESS_INIT; - char *p, *pend; - size_t i; + char *p, *pend, *colored_p = NULL, *colored_pend = NULL; + size_t i, color_arg_index; struct hunk *hunk = NULL; int res; /* Use `--no-color` explicitly, just in case `diff.color = always`. */ - setup_child_process(s, &cp, - "diff-files", "-p", "--no-color", "--", NULL); + argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL); + color_arg_index = args.argc - 2; for (i = 0; i < ps->nr; i++) - argv_array_push(&cp.args, ps->items[i].original); + argv_array_push(&args, ps->items[i].original); + setup_child_process(s, &cp, NULL); + cp.argv = args.argv; res = capture_command(&cp, plain, 0); - if (res) + if (res) { + argv_array_clear(&args); return error(_("could not parse diff")); - if (!plain->len) + } + if (!plain->len) { + argv_array_clear(&args); return 0; + } strbuf_complete_line(plain); + if (want_color_fd(1, -1)) { + struct child_process colored_cp = CHILD_PROCESS_INIT; + + setup_child_process(s, &colored_cp, NULL); + xsnprintf((char *)args.argv[color_arg_index], 8, "--color"); + colored_cp.argv = args.argv; + colored = &s->colored; + res = capture_command(&colored_cp, colored, 0); + argv_array_clear(&args); + if (res) + return error(_("could not parse colored diff")); + strbuf_complete_line(colored); + colored_p = colored->buf; + colored_pend = colored_p + colored->len; + } + argv_array_clear(&args); + /* parse hunks */ p = plain->buf; pend = p + plain->len; @@ -82,20 +107,37 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) memset(hunk, 0, sizeof(*hunk)); hunk->start = p - plain->buf; + if (colored) + hunk->colored_start = colored_p - colored->buf; } p = eol == pend ? pend : eol + 1; hunk->end = p - plain->buf; + + if (colored) { + char *colored_eol = memchr(colored_p, '\n', + colored_pend - colored_p); + if (colored_eol) + colored_p = colored_eol + 1; + else + colored_p = colored_pend; + + hunk->colored_end = colored_p - colored->buf; + } } return 0; } static void render_hunk(struct add_p_state *s, struct hunk *hunk, - struct strbuf *out) + int colored, struct strbuf *out) { - strbuf_add(out, s->plain.buf + hunk->start, - hunk->end - hunk->start); + if (colored) + strbuf_add(out, s->colored.buf + hunk->colored_start, + hunk->colored_end - hunk->colored_start); + else + strbuf_add(out, s->plain.buf + hunk->start, + hunk->end - hunk->start); } static void reassemble_patch(struct add_p_state *s, struct strbuf *out) @@ -103,12 +145,12 @@ static void reassemble_patch(struct add_p_state *s, struct strbuf *out) struct hunk *hunk; size_t i; - render_hunk(s, &s->head, out); + render_hunk(s, &s->head, 0, out); for (i = 0; i < s->hunk_nr; i++) { hunk = s->hunk + i; if (hunk->use == USE_HUNK) - render_hunk(s, hunk, out); + render_hunk(s, hunk, 0, out); } } @@ -130,12 +172,13 @@ static int patch_update_file(struct add_p_state *s) struct hunk *hunk; char ch; struct child_process cp = CHILD_PROCESS_INIT; + int colored = !!s->colored.len; if (!s->hunk_nr) return 0; strbuf_reset(&s->buf); - render_hunk(s, &s->head, &s->buf); + render_hunk(s, &s->head, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { if (hunk_index >= s->hunk_nr) @@ -162,7 +205,7 @@ static int patch_update_file(struct add_p_state *s) break; strbuf_reset(&s->buf); - render_hunk(s, hunk, &s->buf); + render_hunk(s, hunk, colored, &s->buf); fputs(s->buf.buf, stdout); strbuf_reset(&s->buf); @@ -252,6 +295,7 @@ int run_add_p(struct repository *r, const struct pathspec *ps) NULL, NULL, NULL) < 0 || parse_diff(&s, ps) < 0) { strbuf_release(&s.plain); + strbuf_release(&s.colored); return -1; } @@ -261,5 +305,6 @@ int run_add_p(struct repository *r, const struct pathspec *ps) strbuf_release(&s.answer); strbuf_release(&s.buf); strbuf_release(&s.plain); + strbuf_release(&s.colored); return 0; } From patchwork Fri Dec 13 08:07:51 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290137 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 58030930 for ; Fri, 13 Dec 2019 08:08:41 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 22AA322527 for ; Fri, 13 Dec 2019 08:08:41 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="uyU3mKL6" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726713AbfLMIIR (ORCPT ); Fri, 13 Dec 2019 03:08:17 -0500 Received: from mail-wr1-f41.google.com ([209.85.221.41]:43097 "EHLO mail-wr1-f41.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726463AbfLMIIO (ORCPT ); Fri, 13 Dec 2019 03:08:14 -0500 Received: by mail-wr1-f41.google.com with SMTP id d16so5564627wre.10 for ; Fri, 13 Dec 2019 00:08:12 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=5xjkS7Z9o9dvGIhDDQ0kagnHCtEfbWQjQvVhgQFReMI=; b=uyU3mKL68lyOYrBLk0265nD3CyEzj5Hq7fcFFV3OQ1hCbPVX/+0BwOQcQM5ST8DGx0 ohqnxjIyOzOZ0fj/wRdehHSDg09rMyWPA+crdn4O59UTIKs7wdv20KiyQo5sEmksA+Mo DFWynme8RsGcsdhEFkkrD/F0TP7qE3Eg4MKizbI7tRd991J6iFgzWcbmcFz2Cv0076sX N5D3LF658Akv9zssk4paMhKaI4zt/QZN3ZlYIDct/9voh2uLcTZd6fGltoQ+5qSe2MGJ +ooj78dYXXqzfjnWzbhllmmTjBCeP0xX7oGjHOSU+HLKfu3stPCe8AfVX2Rbh1tYUper 5Piw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=5xjkS7Z9o9dvGIhDDQ0kagnHCtEfbWQjQvVhgQFReMI=; b=falPiYK0qCaHekpfm8nrcK148Y9TQcuizbw4ELvt0LZaEpQKw+3JaRQWgOTtjHe4a5 W8d808UaIRNSzyOdaQXGxFvmDYjHXo3X50Z2lCExHPlEQpk8xVyIrXMhEukacbljbPoa eiL8C9WlJG9uSMTV8VK7PRZqthCpECD7x1pk8n2DCWrSvRsFOwo/IAPQ/5tDaKFoWZPa PUzbfuH4dWhTJakJF47JXQiCzL7lMNJma98TNikuzzw3cH5LVnHIb5l90R1JyLsQD5TX mU2p2irSJr2ju9cYXTStoFOQ/n0aGdcciN4m7DWLQ7yQAnh3PTx7KzyDIocgcgAyiLlV YEQQ== X-Gm-Message-State: APjAAAWjgT5jzr0f7KNazct5T/BIVEHTmpexfCvV+qfhMZ3QPuwCz73Q p+ElXtyabtwvgoVnGzczjeLvuJX8 X-Google-Smtp-Source: APXvYqxr/1/esf6VA5QvC8O2LwQ81KWbnilPeUgxSNm6Z/G16lArLa32C0gfy9eieznWsM6XAOrHqw== X-Received: by 2002:adf:f103:: with SMTP id r3mr10796449wro.295.1576224491228; Fri, 13 Dec 2019 00:08:11 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id d8sm8913307wrx.71.2019.12.13.00.08.10 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:10 -0800 (PST) Message-Id: In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:51 +0000 Subject: [PATCH 04/19] built-in add -p: adjust hunk headers as needed Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin When skipping a hunk that adds a different number of lines than it removes, we need to adjust the subsequent hunk headers of non-skipped hunks: in pathological cases, the context is not enough to determine precisely where the patch should be applied. This problem was identified in 23fea4c240 (t3701: add failing test for pathological context lines, 2018-03-01) and fixed in the Perl version in fecc6f3a68 (add -p: adjust offsets of subsequent hunks when one is skipped, 2018-03-01). And this patch fixes it in the C version of `git add -p`. In contrast to the Perl version, we try to keep the extra text on the hunk header (which typically contains the signature of the function whose code is changed in the hunk) intact. Note: while the C version does not support staging mode changes at this stage, we already prepare for this by simply skipping the hunk header if both old and new offset is 0 (this cannot happen for regular hunks, and we will use this as an indicator that we are looking at a special hunk). Likewise, we already prepare for hunk splitting by handling the absence of extra text in the hunk header gracefully: only the first split hunk will have that text, the others will not (indicated by an empty extra text start/end range). Preparing for hunk splitting already at this stage avoids an indentation change of the entire hunk header-printing block later, and is almost as easy to review as without that handling. Signed-off-by: Johannes Schindelin --- add-interactive.c | 14 +---- add-interactive.h | 15 +++++ add-patch.c | 145 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 151 insertions(+), 23 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index 034c1dc02f..29356c5aa2 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -10,16 +10,6 @@ #include "dir.h" #include "run-command.h" -struct add_i_state { - struct repository *r; - int use_color; - char header_color[COLOR_MAXLEN]; - char help_color[COLOR_MAXLEN]; - char prompt_color[COLOR_MAXLEN]; - char error_color[COLOR_MAXLEN]; - char reset_color[COLOR_MAXLEN]; -}; - static void init_color(struct repository *r, struct add_i_state *s, const char *slot_name, char *dst, const char *default_color) @@ -36,7 +26,7 @@ static void init_color(struct repository *r, struct add_i_state *s, free(key); } -static void init_add_i_state(struct add_i_state *s, struct repository *r) +void init_add_i_state(struct add_i_state *s, struct repository *r) { const char *value; @@ -54,6 +44,8 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE); init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED); init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET); + init_color(r, s, "fraginfo", s->fraginfo_color, + diff_get_color(s->use_color, DIFF_FRAGINFO)); } /* diff --git a/add-interactive.h b/add-interactive.h index 0e3d93acc9..584f304a9a 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -1,6 +1,21 @@ #ifndef ADD_INTERACTIVE_H #define ADD_INTERACTIVE_H +#include "color.h" + +struct add_i_state { + struct repository *r; + int use_color; + char header_color[COLOR_MAXLEN]; + char help_color[COLOR_MAXLEN]; + char prompt_color[COLOR_MAXLEN]; + char error_color[COLOR_MAXLEN]; + char reset_color[COLOR_MAXLEN]; + char fraginfo_color[COLOR_MAXLEN]; +}; + +void init_add_i_state(struct add_i_state *s, struct repository *r); + struct repository; struct pathspec; int run_add_i(struct repository *r, const struct pathspec *ps); diff --git a/add-patch.c b/add-patch.c index 79eefa9505..e266a96ca7 100644 --- a/add-patch.c +++ b/add-patch.c @@ -5,14 +5,26 @@ #include "argv-array.h" #include "pathspec.h" #include "color.h" +#include "diff.h" + +struct hunk_header { + unsigned long old_offset, old_count, new_offset, new_count; + /* + * Start/end offsets to the extra text after the second `@@` in the + * hunk header, e.g. the function signature. This is expected to + * include the newline. + */ + size_t extra_start, extra_end, colored_extra_start, colored_extra_end; +}; struct hunk { size_t start, end, colored_start, colored_end; enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use; + struct hunk_header header; }; struct add_p_state { - struct repository *r; + struct add_i_state s; struct strbuf answer, buf; /* parsed diff */ @@ -35,7 +47,70 @@ static void setup_child_process(struct add_p_state *s, cp->git_cmd = 1; argv_array_pushf(&cp->env_array, - INDEX_ENVIRONMENT "=%s", s->r->index_file); + INDEX_ENVIRONMENT "=%s", s->s.r->index_file); +} + +static int parse_range(const char **p, + unsigned long *offset, unsigned long *count) +{ + char *pend; + + *offset = strtoul(*p, &pend, 10); + if (pend == *p) + return -1; + if (*pend != ',') { + *count = 1; + *p = pend; + return 0; + } + *count = strtoul(pend + 1, (char **)p, 10); + return *p == pend + 1 ? -1 : 0; +} + +static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk) +{ + struct hunk_header *header = &hunk->header; + const char *line = s->plain.buf + hunk->start, *p = line; + char *eol = memchr(p, '\n', s->plain.len - hunk->start); + + if (!eol) + eol = s->plain.buf + s->plain.len; + + if (!skip_prefix(p, "@@ -", &p) || + parse_range(&p, &header->old_offset, &header->old_count) < 0 || + !skip_prefix(p, " +", &p) || + parse_range(&p, &header->new_offset, &header->new_count) < 0 || + !skip_prefix(p, " @@", &p)) + return error(_("could not parse hunk header '%.*s'"), + (int)(eol - line), line); + + hunk->start = eol - s->plain.buf + (*eol == '\n'); + header->extra_start = p - s->plain.buf; + header->extra_end = hunk->start; + + if (!s->colored.len) { + header->colored_extra_start = header->colored_extra_end = 0; + return 0; + } + + /* Now find the extra text in the colored diff */ + line = s->colored.buf + hunk->colored_start; + eol = memchr(line, '\n', s->colored.len - hunk->colored_start); + if (!eol) + eol = s->colored.buf + s->colored.len; + p = memmem(line, eol - line, "@@ -", 4); + if (!p) + return error(_("could not parse colored hunk header '%.*s'"), + (int)(eol - line), line); + p = memmem(p + 4, eol - p - 4, " @@", 3); + if (!p) + return error(_("could not parse colored hunk header '%.*s'"), + (int)(eol - line), line); + hunk->colored_start = eol - s->colored.buf + (*eol == '\n'); + header->colored_extra_start = p + 3 - s->colored.buf; + header->colored_extra_end = hunk->colored_start; + + return 0; } static int parse_diff(struct add_p_state *s, const struct pathspec *ps) @@ -109,6 +184,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) hunk->start = p - plain->buf; if (colored) hunk->colored_start = colored_p - colored->buf; + + if (parse_hunk_header(s, hunk) < 0) + return -1; } p = eol == pend ? pend : eol + 1; @@ -130,8 +208,43 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } static void render_hunk(struct add_p_state *s, struct hunk *hunk, - int colored, struct strbuf *out) + ssize_t delta, int colored, struct strbuf *out) { + struct hunk_header *header = &hunk->header; + + if (hunk->header.old_offset != 0 || hunk->header.new_offset != 0) { + /* + * Generate the hunk header dynamically, except for special + * hunks (such as the diff header). + */ + const char *p; + size_t len; + unsigned long old_offset = header->old_offset; + unsigned long new_offset = header->new_offset; + + if (!colored) { + p = s->plain.buf + header->extra_start; + len = header->extra_end - header->extra_start; + } else { + strbuf_addstr(out, s->s.fraginfo_color); + p = s->colored.buf + header->colored_extra_start; + len = header->colored_extra_end + - header->colored_extra_start; + } + + new_offset += delta; + + strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@", + old_offset, header->old_count, + new_offset, header->new_count); + if (len) + strbuf_add(out, p, len); + else if (colored) + strbuf_addf(out, "%s\n", GIT_COLOR_RESET); + else + strbuf_addch(out, '\n'); + } + if (colored) strbuf_add(out, s->colored.buf + hunk->colored_start, hunk->colored_end - hunk->colored_start); @@ -144,13 +257,17 @@ static void reassemble_patch(struct add_p_state *s, struct strbuf *out) { struct hunk *hunk; size_t i; + ssize_t delta = 0; - render_hunk(s, &s->head, 0, out); + render_hunk(s, &s->head, 0, 0, out); for (i = 0; i < s->hunk_nr; i++) { hunk = s->hunk + i; - if (hunk->use == USE_HUNK) - render_hunk(s, hunk, 0, out); + if (hunk->use != USE_HUNK) + delta += hunk->header.old_count + - hunk->header.new_count; + else + render_hunk(s, hunk, delta, 0, out); } } @@ -178,7 +295,7 @@ static int patch_update_file(struct add_p_state *s) return 0; strbuf_reset(&s->buf); - render_hunk(s, &s->head, colored, &s->buf); + render_hunk(s, &s->head, 0, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { if (hunk_index >= s->hunk_nr) @@ -205,7 +322,7 @@ static int patch_update_file(struct add_p_state *s) break; strbuf_reset(&s->buf); - render_hunk(s, hunk, colored, &s->buf); + render_hunk(s, hunk, 0, colored, &s->buf); fputs(s->buf.buf, stdout); strbuf_reset(&s->buf); @@ -272,13 +389,13 @@ static int patch_update_file(struct add_p_state *s) strbuf_reset(&s->buf); reassemble_patch(s, &s->buf); - discard_index(s->r->index); + discard_index(s->s.r->index); setup_child_process(s, &cp, "apply", "--cached", NULL); if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0)) error(_("'git apply --cached' failed")); - if (!repo_read_index(s->r)) - repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0, + if (!repo_read_index(s->s.r)) + repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, 1, NULL, NULL, NULL); } @@ -288,7 +405,11 @@ static int patch_update_file(struct add_p_state *s) int run_add_p(struct repository *r, const struct pathspec *ps) { - struct add_p_state s = { r, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; + struct add_p_state s = { + { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT + }; + + init_add_i_state(&s.s, r); if (discard_index(r->index) < 0 || repo_read_index(r) < 0 || repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, From patchwork Fri Dec 13 08:07:52 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290139 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 87272930 for ; Fri, 13 Dec 2019 08:08:44 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6502D22527 for ; Fri, 13 Dec 2019 08:08:44 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="fcGStxoO" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726830AbfLMIIn (ORCPT ); Fri, 13 Dec 2019 03:08:43 -0500 Received: from mail-wr1-f68.google.com ([209.85.221.68]:42075 "EHLO mail-wr1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725890AbfLMIIO (ORCPT ); Fri, 13 Dec 2019 03:08:14 -0500 Received: by mail-wr1-f68.google.com with SMTP id q6so5566481wro.9 for ; Fri, 13 Dec 2019 00:08:12 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=YLnhQQvkpK6MEhWrppIdMIuQxCGIvIQUSBjOiJFLlNc=; b=fcGStxoOU2rHo9TNIVfJm6vTv7kIxCHqD7u4w4pMoQGqyStMfUKknD1Tss8J5EzSvF C3y58IgIKTj4JZIcsW+PC+F9J6rWPKTntjqGFdn4sDz6Trem6BdmWp3E/LSoEYl1WlGD ldIPYMiwda4nAXcXpuROXAghrqlLLWwjDDYCm3MVQTbIz5vbMPtYEIj6mIpHkrcrqI3c T2SdOte6fVZfBBuFOJ23SrG4SJYRyTya31fVkGSO2IJ2ZgDQcDXY4hMNxAUn+nbnCgvz S572sIngxzBnwuLQ4MdE672XX1BLzF24BHxEZ+hg3uwPymMCWn9Ar0qkOqgoXZ656T3b B34A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=YLnhQQvkpK6MEhWrppIdMIuQxCGIvIQUSBjOiJFLlNc=; b=YgumMPYBenhmvdodZZFZYggNiuORfpa9BNAYvGBtfaojiAgwdz7u5cmWX9h7cq707e X8SRH62azlp4jUK09a+9JD8u6t3UZCL0NK+Dykf3R2r0rlQtbNIHbNp/DzcM+1PIEVEV y2cae9xT8JTBjsqDDzd214bR591neqemLlqPzLh0zirhvn8xUyFKn8aEBGt8C+7McxJY LYxzjLoPUaXlMWa1iI8xuXXu59GK0wMV6XMn4w5/joJH6/fd5vcMPLzwjnYd17hbCEA8 DYCU0p6bCgasfGjxFoBlPC1YKXg0jtgRM/fMgzoiXqN9yKo3OM4Q9bNMcEHESz1g6E5/ ZmQw== X-Gm-Message-State: APjAAAUT4R6oxecdU7Wk7OsJxt35h/3hpmHBsLR7HwATb2RmeQoH+F+7 VkU+7lbUzyFpzonP9pzv/VRYb76a X-Google-Smtp-Source: APXvYqwI7q4HARtDOCcwk1U7cmvz+r+dAmuXcPYy18lWsK0Nx4WFy1p4o3/NNrlZGp69z7E1xhoP+Q== X-Received: by 2002:a5d:6284:: with SMTP id k4mr10945077wru.398.1576224492122; Fri, 13 Dec 2019 00:08:12 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id x18sm9128949wrr.75.2019.12.13.00.08.11 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:11 -0800 (PST) Message-Id: <7d3a59dd117a670734e8420093e7ccbe28d1650c.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:52 +0000 Subject: [PATCH 05/19] built-in add -p: color the prompt and the help text Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin ... just like the Perl version ;-) Signed-off-by: Johannes Schindelin --- add-patch.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/add-patch.c b/add-patch.c index e266a96ca7..dab2ff2381 100644 --- a/add-patch.c +++ b/add-patch.c @@ -334,9 +334,12 @@ static int patch_update_file(struct add_p_state *s) strbuf_addstr(&s->buf, ",j"); if (hunk_index + 1 < s->hunk_nr) strbuf_addstr(&s->buf, ",J"); - printf("(%"PRIuMAX"/%"PRIuMAX") ", - (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr); - printf(_("Stage this hunk [y,n,a,d%s,?]? "), s->buf.buf); + color_fprintf(stdout, s->s.prompt_color, + "(%"PRIuMAX"/%"PRIuMAX") ", + (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr); + color_fprintf(stdout, s->s.prompt_color, + _("Stage this hunk [y,n,a,d%s,?]? "), + s->buf.buf); fflush(stdout); if (strbuf_getline(&s->answer, stdin) == EOF) break; @@ -376,7 +379,8 @@ static int patch_update_file(struct add_p_state *s) else if (undecided_next >= 0 && s->answer.buf[0] == 'j') hunk_index = undecided_next; else - puts(_(help_patch_text)); + color_fprintf(stdout, s->s.help_color, + _(help_patch_text)); } /* Any hunk to be used? */ From patchwork Fri Dec 13 08:07:53 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290111 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5AC176C1 for ; Fri, 13 Dec 2019 08:08:21 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 38BD924658 for ; Fri, 13 Dec 2019 08:08:21 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="tnat4KXd" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726744AbfLMIIU (ORCPT ); Fri, 13 Dec 2019 03:08:20 -0500 Received: from mail-wm1-f68.google.com ([209.85.128.68]:37599 "EHLO mail-wm1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726487AbfLMIIP (ORCPT ); Fri, 13 Dec 2019 03:08:15 -0500 Received: by mail-wm1-f68.google.com with SMTP id f129so5532739wmf.2 for ; Fri, 13 Dec 2019 00:08:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=gE7TdA4S7ydhUPwwWQ9b9w+MPUTElkGqFGCAyzeiKyA=; b=tnat4KXdWzhA7TBFlkuPDbXLXyIFkYTmngcIO9mtv6OgPG6FtOEJpLQQO9ZbJyKtOB GS9mKc8IOua/QF57mlTQP1ccd7QqPd2CT155/bQH2imNU276eXfVeIEdyY20lroXsR+R 3QapQnUmHFyEm8EgUEm9z1dRBStXmiUjQOst68ShUwdv//RjqN3kX9qnXWP8pNUTWq4e m1sma9y45j9e8f4KjSCNzrcKkczM/pPEOu0oHhlE2DRERrZ2XinTYJcdAYRgKnmJmhUx LVHAk4d46y6C0ZmKlp2izpN7zyLe/Ztpx+0pAREh60nOXH655r394bx/ak/P6sLxeA1t /9dw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=gE7TdA4S7ydhUPwwWQ9b9w+MPUTElkGqFGCAyzeiKyA=; b=mEM0+ucfGp/xOLQJ+GH3o1qnFuKbaUnQvCUBOEIGR3FImmLsdpT6b+bM9o4ELZsvGZ 9L6GsjR49h2N1efU2gDvc6uKqtarbwK0gLr2ykr7RmVoRzccbXWdTmY5WxZy5CLHiwKD tpB247JJGeAkD3FBaOu76QAhk/ZfVfHuSTO7qJzhOoyNY9azfP7C2lqml8Zn8Eo9wpgf vS8LYD6B15fDfwN9tB2xdwOvlKSpdfhaLVsZQKg2GjLZWfcaJBuxsBGnslcJHQyxKBux rdKsauhAySn+3bopVU0HsNOK5dBZhHP9yRWcgek9h1NO+c2hc3uyQQVbwPwobYNBxA8p w2/A== X-Gm-Message-State: APjAAAWvnu94cK9IYJbZZtWMk7M07DW1NuzzL7llp+Gr9VlGDUuwgPGz QMlR+14aaQgMOQhNnloEtm7Q6M1P X-Google-Smtp-Source: APXvYqyF5PrcROXg77lRqAitaSfDvTulXlcyx1damJX9lrEcUSgolA0cTlHTtTeMgKSKmbus85FYDQ== X-Received: by 2002:a1c:638a:: with SMTP id x132mr12493627wmb.43.1576224492904; Fri, 13 Dec 2019 00:08:12 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id p17sm9038363wrx.20.2019.12.13.00.08.12 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:12 -0800 (PST) Message-Id: <9ef0db2d8826f2b5c884d9e9834eedc89cd29c39.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:53 +0000 Subject: [PATCH 06/19] built-in add -p: offer a helpful error message when hunk navigation failed Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin ... just like the Perl version currently does... Signed-off-by: Johannes Schindelin --- add-patch.c | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/add-patch.c b/add-patch.c index dab2ff2381..f59471cdf2 100644 --- a/add-patch.c +++ b/add-patch.c @@ -34,6 +34,18 @@ struct add_p_state { size_t hunk_nr, hunk_alloc; }; +static void err(struct add_p_state *s, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + fputs(s->s.error_color, stderr); + vfprintf(stderr, fmt, args); + fputs(s->s.reset_color, stderr); + fputc('\n', stderr); + va_end(args); +} + static void setup_child_process(struct add_p_state *s, struct child_process *cp, ...) { @@ -368,17 +380,27 @@ static int patch_update_file(struct add_p_state *s) if (hunk->use == UNDECIDED_HUNK) hunk->use = SKIP_HUNK; } - } else if (hunk_index && s->answer.buf[0] == 'K') - hunk_index--; - else if (hunk_index + 1 < s->hunk_nr && - s->answer.buf[0] == 'J') - hunk_index++; - else if (undecided_previous >= 0 && - s->answer.buf[0] == 'k') - hunk_index = undecided_previous; - else if (undecided_next >= 0 && s->answer.buf[0] == 'j') - hunk_index = undecided_next; - else + } else if (s->answer.buf[0] == 'K') { + if (hunk_index) + hunk_index--; + else + err(s, _("No previous hunk")); + } else if (s->answer.buf[0] == 'J') { + if (hunk_index + 1 < s->hunk_nr) + hunk_index++; + else + err(s, _("No next hunk")); + } else if (s->answer.buf[0] == 'k') { + if (undecided_previous >= 0) + hunk_index = undecided_previous; + else + err(s, _("No previous hunk")); + } else if (s->answer.buf[0] == 'j') { + if (undecided_next >= 0) + hunk_index = undecided_next; + else + err(s, _("No next hunk")); + } else color_fprintf(stdout, s->s.help_color, _(help_patch_text)); } From patchwork Fri Dec 13 08:07:54 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290109 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 3599E930 for ; Fri, 13 Dec 2019 08:08:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 00C3224671 for ; Fri, 13 Dec 2019 08:08:20 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="R4D5DgFd" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726736AbfLMIIT (ORCPT ); Fri, 13 Dec 2019 03:08:19 -0500 Received: from mail-wr1-f67.google.com ([209.85.221.67]:33500 "EHLO mail-wr1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726623AbfLMIIP (ORCPT ); Fri, 13 Dec 2019 03:08:15 -0500 Received: by mail-wr1-f67.google.com with SMTP id b6so5652995wrq.0 for ; Fri, 13 Dec 2019 00:08:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=b/wl3nCG4T1rjxfgLJQySEL32poHbbqczxEu4yayelM=; b=R4D5DgFdaWMlilR9abT/8H+MnBDE7W5VILo+6y3mNNevCA+r8GHnEzrP5EfGkDphUK o0nP8543ZmtpdTbaAwmT0B9d176PhvxBFpkLj+juS2SDwMyH2/4ToKcZQlGyiAWHElWu rCg7YJCLcafvLSrG1udaVztg1yVU7WpPiIBRaLg3jVJMpgw48gMHcpRqajoKOmAOBUx5 e/+iMdayKw7SO78jw80ZOx8s3B3jPQBoFqGr1EyAjqTJXrPv3mmPB85CETSu9YAcIH7R 47ICl5rVleXx/6wYEV1FGsNtSlPLFNPBbxX+ZFYEVg8qjL1MFWIanKkokb1+FNiRi40d VFeg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=b/wl3nCG4T1rjxfgLJQySEL32poHbbqczxEu4yayelM=; b=ggOYtJqJebW6Vh+1/MSibrFC6V9hkXnTRgtRGmIO1xGEOm3S7SX2oVglqxNHGtupky qiun/j35tFg3swnGRYFmp1/rNoHISQbd1VAszjYmMkoiv6pko55f9HPiAuwcwsFoS3PD 8XbJysOYBnX2uV7EMC4SwHnfExNsGzhIqbgFv44r5nyFzAA074gAJgi3ETjh/keEgvVU 08iKfnXdgTtY52klDg8A1aGz4B3k3oBoogi3IWNE7TeFYRpv02GJXJJPgzdQAM9b0SNq fDHqlY5xzVxzVqcevzF7mnOuMpO60JdkswM4bs5FJLxsq2X2q16ZZJnr/1l6Sz3gsWI3 uNzw== X-Gm-Message-State: APjAAAUcQQumZ5f5w4pGcu3bhGdlPpRxDY/nFLS2p0gXnOS6bTYyXI6z a+nCpRqxXn/Zqi5p/kGywG00PBoT X-Google-Smtp-Source: APXvYqx5R1TNR7Jv14DrwkgJaZjaEDih9NpltaAwMJEXidXYE1T3RhfSRgEuRy/Ed8ilh/2dOLgezQ== X-Received: by 2002:a5d:50cf:: with SMTP id f15mr11146983wrt.381.1576224493641; Fri, 13 Dec 2019 00:08:13 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id x11sm9364123wmg.46.2019.12.13.00.08.13 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:13 -0800 (PST) Message-Id: <0cd1522044c238286c4762dfb18d413e856f59e8.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:54 +0000 Subject: [PATCH 07/19] built-in add -p: support multi-file diffs Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin For simplicity, the initial implementation in C handled only a single modified file. Now it handles an arbitrary number of files. Signed-off-by: Johannes Schindelin --- add-patch.c | 91 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/add-patch.c b/add-patch.c index f59471cdf2..7c1b3b3935 100644 --- a/add-patch.c +++ b/add-patch.c @@ -29,9 +29,12 @@ struct add_p_state { /* parsed diff */ struct strbuf plain, colored; - struct hunk head; - struct hunk *hunk; - size_t hunk_nr, hunk_alloc; + struct file_diff { + struct hunk head; + struct hunk *hunk; + size_t hunk_nr, hunk_alloc; + } *file_diff; + size_t file_diff_nr; }; static void err(struct add_p_state *s, const char *fmt, ...) @@ -131,7 +134,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) struct strbuf *plain = &s->plain, *colored = NULL; struct child_process cp = CHILD_PROCESS_INIT; char *p, *pend, *colored_p = NULL, *colored_pend = NULL; - size_t i, color_arg_index; + size_t file_diff_alloc = 0, i, color_arg_index; + struct file_diff *file_diff = NULL; struct hunk *hunk = NULL; int res; @@ -171,7 +175,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } argv_array_clear(&args); - /* parse hunks */ + /* parse files and hunks */ p = plain->buf; pend = p + plain->len; while (p != pend) { @@ -180,17 +184,23 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) eol = pend; if (starts_with(p, "diff ")) { - if (p != plain->buf) - BUG("multi-file diff not yet handled"); - hunk = &s->head; + s->file_diff_nr++; + ALLOC_GROW(s->file_diff, s->file_diff_nr, + file_diff_alloc); + file_diff = s->file_diff + s->file_diff_nr - 1; + memset(file_diff, 0, sizeof(*file_diff)); + hunk = &file_diff->head; + hunk->start = p - plain->buf; + if (colored_p) + hunk->colored_start = colored_p - colored->buf; } else if (p == plain->buf) BUG("diff starts with unexpected line:\n" "%.*s\n", (int)(eol - p), p); else if (starts_with(p, "@@ ")) { - s->hunk_nr++; - ALLOC_GROW(s->hunk, s->hunk_nr, - s->hunk_alloc); - hunk = s->hunk + s->hunk_nr - 1; + file_diff->hunk_nr++; + ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, + file_diff->hunk_alloc); + hunk = file_diff->hunk + file_diff->hunk_nr - 1; memset(hunk, 0, sizeof(*hunk)); hunk->start = p - plain->buf; @@ -265,16 +275,17 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, hunk->end - hunk->start); } -static void reassemble_patch(struct add_p_state *s, struct strbuf *out) +static void reassemble_patch(struct add_p_state *s, + struct file_diff *file_diff, struct strbuf *out) { struct hunk *hunk; size_t i; ssize_t delta = 0; - render_hunk(s, &s->head, 0, 0, out); + render_hunk(s, &file_diff->head, 0, 0, out); - for (i = 0; i < s->hunk_nr; i++) { - hunk = s->hunk + i; + for (i = 0; i < file_diff->hunk_nr; i++) { + hunk = file_diff->hunk + i; if (hunk->use != USE_HUNK) delta += hunk->header.old_count - hunk->header.new_count; @@ -294,7 +305,8 @@ N_("y - stage this hunk\n" "K - leave this hunk undecided, see previous hunk\n" "? - print help\n"); -static int patch_update_file(struct add_p_state *s) +static int patch_update_file(struct add_p_state *s, + struct file_diff *file_diff) { size_t hunk_index = 0; ssize_t i, undecided_previous, undecided_next; @@ -303,27 +315,27 @@ static int patch_update_file(struct add_p_state *s) struct child_process cp = CHILD_PROCESS_INIT; int colored = !!s->colored.len; - if (!s->hunk_nr) + if (!file_diff->hunk_nr) return 0; strbuf_reset(&s->buf); - render_hunk(s, &s->head, 0, colored, &s->buf); + render_hunk(s, &file_diff->head, 0, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { - if (hunk_index >= s->hunk_nr) + if (hunk_index >= file_diff->hunk_nr) hunk_index = 0; - hunk = s->hunk + hunk_index; + hunk = file_diff->hunk + hunk_index; undecided_previous = -1; for (i = hunk_index - 1; i >= 0; i--) - if (s->hunk[i].use == UNDECIDED_HUNK) { + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { undecided_previous = i; break; } undecided_next = -1; - for (i = hunk_index + 1; i < s->hunk_nr; i++) - if (s->hunk[i].use == UNDECIDED_HUNK) { + for (i = hunk_index + 1; i < file_diff->hunk_nr; i++) + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { undecided_next = i; break; } @@ -344,11 +356,12 @@ static int patch_update_file(struct add_p_state *s) strbuf_addstr(&s->buf, ",K"); if (undecided_next >= 0) strbuf_addstr(&s->buf, ",j"); - if (hunk_index + 1 < s->hunk_nr) + if (hunk_index + 1 < file_diff->hunk_nr) strbuf_addstr(&s->buf, ",J"); color_fprintf(stdout, s->s.prompt_color, "(%"PRIuMAX"/%"PRIuMAX") ", - (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr); + (uintmax_t)hunk_index + 1, + (uintmax_t)file_diff->hunk_nr); color_fprintf(stdout, s->s.prompt_color, _("Stage this hunk [y,n,a,d%s,?]? "), s->buf.buf); @@ -364,19 +377,19 @@ static int patch_update_file(struct add_p_state *s) hunk->use = USE_HUNK; soft_increment: hunk_index = undecided_next < 0 ? - s->hunk_nr : undecided_next; + file_diff->hunk_nr : undecided_next; } else if (ch == 'n') { hunk->use = SKIP_HUNK; goto soft_increment; } else if (ch == 'a') { - for (; hunk_index < s->hunk_nr; hunk_index++) { - hunk = s->hunk + hunk_index; + for (; hunk_index < file_diff->hunk_nr; hunk_index++) { + hunk = file_diff->hunk + hunk_index; if (hunk->use == UNDECIDED_HUNK) hunk->use = USE_HUNK; } } else if (ch == 'd') { - for (; hunk_index < s->hunk_nr; hunk_index++) { - hunk = s->hunk + hunk_index; + for (; hunk_index < file_diff->hunk_nr; hunk_index++) { + hunk = file_diff->hunk + hunk_index; if (hunk->use == UNDECIDED_HUNK) hunk->use = SKIP_HUNK; } @@ -386,7 +399,7 @@ static int patch_update_file(struct add_p_state *s) else err(s, _("No previous hunk")); } else if (s->answer.buf[0] == 'J') { - if (hunk_index + 1 < s->hunk_nr) + if (hunk_index + 1 < file_diff->hunk_nr) hunk_index++; else err(s, _("No next hunk")); @@ -406,14 +419,14 @@ static int patch_update_file(struct add_p_state *s) } /* Any hunk to be used? */ - for (i = 0; i < s->hunk_nr; i++) - if (s->hunk[i].use == USE_HUNK) + for (i = 0; i < file_diff->hunk_nr; i++) + if (file_diff->hunk[i].use == USE_HUNK) break; - if (i < s->hunk_nr) { + if (i < file_diff->hunk_nr) { /* At least one hunk selected: apply */ strbuf_reset(&s->buf); - reassemble_patch(s, &s->buf); + reassemble_patch(s, file_diff, &s->buf); discard_index(s->s.r->index); setup_child_process(s, &cp, "apply", "--cached", NULL); @@ -434,6 +447,7 @@ int run_add_p(struct repository *r, const struct pathspec *ps) struct add_p_state s = { { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; + size_t i; init_add_i_state(&s.s, r); @@ -446,8 +460,9 @@ int run_add_p(struct repository *r, const struct pathspec *ps) return -1; } - if (s.hunk_nr) - patch_update_file(&s); + for (i = 0; i < s.file_diff_nr; i++) + if (patch_update_file(&s, s.file_diff + i)) + break; strbuf_release(&s.answer); strbuf_release(&s.buf); From patchwork Fri Dec 13 08:07:55 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290135 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 646006C1 for ; Fri, 13 Dec 2019 08:08:40 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 428EA24658 for ; Fri, 13 Dec 2019 08:08:40 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JmFwsDZr" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726595AbfLMIIS (ORCPT ); Fri, 13 Dec 2019 03:08:18 -0500 Received: from mail-wm1-f68.google.com ([209.85.128.68]:51739 "EHLO mail-wm1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726368AbfLMIIP (ORCPT ); Fri, 13 Dec 2019 03:08:15 -0500 Received: by mail-wm1-f68.google.com with SMTP id d73so5271151wmd.1 for ; Fri, 13 Dec 2019 00:08:15 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=EmNEX5sLXUgDTtw4uuhLNsmq52UU9EShJfNBHXccmds=; b=JmFwsDZrQI9Z5WtoaGvEHIO/dGrFfHiuf5UHV0xU3CyyVE8MjMeIR1gp3NywBddg1i 04/04Ivo493so3zVeCiRSdte11pzGuRj2gIFZLlGX3aw69lrTcRGxIwDHMF0SHkoZ0YQ jEMG1koSk+X1WK0VoH64/k6Mqxw9T98Mcb1RJ5EJtI4jjP0r5BjWJj0D7N2C3FMTB+oC DBU9wepRD6vUVZmUdVjwzkM8xLuzlcZVrzf3waImF1+wM2UoMIDQFKkqtFbURdV7IGs8 t0k+vbWU88n6OFsUpyIgT/Uj/QRyefbEI4RquwUF4HIxf9Tn3Hd2LUMT1h0RR84dewN8 krQQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=EmNEX5sLXUgDTtw4uuhLNsmq52UU9EShJfNBHXccmds=; b=bNxwWw/EnFmEYAbwMxEq3mFY8YZmgUlV6wrpOGQ7R8/91TGFQ1iVj5bhdvD+E/31Hi ENBOF56CHG0O46nfdprO3BsL7R524DSRNUxTdnvhLFGxbTOwWMa0GvYIQdttVYTgj28n I2+bow4586GiZw2fKShUKLyihF+2CIBylqqGk3q7prl99ULS09ysUWaUf8LtJq2vJ/Dv p6exV28+L9FeCcsH9SQLbGHVPKmsfNxgK2oReBTNVQPmgtiZqSw+0QxsHq0jQDGtmn3O O7ny62aYxoTjuCm3VvpL/rKJhCizDNHgIKmct+tW2BjamWGbdhRow1NF+LVtMoJ1e+HD JpoQ== X-Gm-Message-State: APjAAAVRNvTxDXGMHOtU6MGELFgdYOCGZwNWp0M9hHlFDeu1nJ+Mgu8K X9d+IC3SJ0b5Y9w98bpQg1n253nG X-Google-Smtp-Source: APXvYqygobC755x7Tp5xidazjswCh51nWaXm7BGqo6g2+v9ky8ibPnFbR9EFiqcJrW+8ov5Bx1MZdA== X-Received: by 2002:a1c:3141:: with SMTP id x62mr11355316wmx.18.1576224494432; Fri, 13 Dec 2019 00:08:14 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id l7sm9017766wrq.61.2019.12.13.00.08.13 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:13 -0800 (PST) Message-Id: <556604361d28377e4f453b851527293ae865c27a.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:55 +0000 Subject: [PATCH 08/19] built-in add -p: handle deleted empty files Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin This addresses the same problem as 24ab81ae4d (add-interactive: handle deletion of empty files, 2009-10-27), although in a different way: we not only stick the "deleted file" line into its own pseudo hunk, but also the entire remainder (if any) of the same diff. That way, we do not have to play any funny games with regards to coalescing the diff after the user selected what (possibly pseudo-)hunks to stage. Signed-off-by: Johannes Schindelin --- add-patch.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/add-patch.c b/add-patch.c index 7c1b3b3935..c32541f46d 100644 --- a/add-patch.c +++ b/add-patch.c @@ -33,6 +33,7 @@ struct add_p_state { struct hunk head; struct hunk *hunk; size_t hunk_nr, hunk_alloc; + unsigned deleted:1; } *file_diff; size_t file_diff_nr; }; @@ -180,6 +181,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) pend = p + plain->len; while (p != pend) { char *eol = memchr(p, '\n', pend - p); + const char *deleted = NULL; + if (!eol) eol = pend; @@ -196,7 +199,11 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } else if (p == plain->buf) BUG("diff starts with unexpected line:\n" "%.*s\n", (int)(eol - p), p); - else if (starts_with(p, "@@ ")) { + else if (file_diff->deleted) + ; /* keep the rest of the file in a single "hunk" */ + else if (starts_with(p, "@@ ") || + (hunk == &file_diff->head && + skip_prefix(p, "deleted file", &deleted))) { file_diff->hunk_nr++; ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, file_diff->hunk_alloc); @@ -207,7 +214,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) if (colored) hunk->colored_start = colored_p - colored->buf; - if (parse_hunk_header(s, hunk) < 0) + if (deleted) + file_diff->deleted = 1; + else if (parse_hunk_header(s, hunk) < 0) return -1; } From patchwork Fri Dec 13 08:07:56 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290115 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 4CC94930 for ; Fri, 13 Dec 2019 08:08:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2129D2465A for ; Fri, 13 Dec 2019 08:08:23 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="CJLMFOye" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726762AbfLMIIW (ORCPT ); Fri, 13 Dec 2019 03:08:22 -0500 Received: from mail-wr1-f66.google.com ([209.85.221.66]:42081 "EHLO mail-wr1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726638AbfLMIIS (ORCPT ); Fri, 13 Dec 2019 03:08:18 -0500 Received: by mail-wr1-f66.google.com with SMTP id q6so5566642wro.9 for ; Fri, 13 Dec 2019 00:08:16 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=qXuPaAPMgSHBDfzvUw3jAPvhvc003sVj6D1eanc8WVI=; b=CJLMFOyeWE0+PxbLFWvRN6kUwxmVVQzikbrz4tteqScaNR6smqDzYyD8qhy8ExHRzn snc7W8l214A0ckd4KGM/Hqi0qN+9bdmYQpXSa6Y9lixmFXov8lRwgE+FL4RPk55SuonW yNc/XQkZREC9FA0GsfR/CQ6xeTw5Ck4FSZRx2V5xUI/Grq4yvTLQFqAKcx0U0tGV6URa uk0O3gnBfqe9d9kzVa0s1OQatjG8osoorteqVjspJE7EnlkQQwc7FN8GrsKaziWUKdbv bTNpAHCp4luTRXD0mCjBTd5JQ9GbmIkT5Qv6KY78ofDsdz2RAdhSMWcpRDyADn4wmuF+ 7gEw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=qXuPaAPMgSHBDfzvUw3jAPvhvc003sVj6D1eanc8WVI=; b=KVqqTrnFLyqR+OvQLb0KAcQyLodT3pOF5W1pCmyle9h65Gge15rRlKDYSxOPgq2sfg 8JAXdk/mhgy9Zl6LHBcCICPHxyLiRy/HKCTZF0sq7MhMy2Jb2jJDDQ4dhBIjUgFpAmxd WE1XlTUaOtaKuOJfhpqhpWIQG+l1opggM/TgnpjmTyZq8VLfCRV7iW9xMMvCleC1oGoZ 33BVDnikLH3XvCREg1gz9xAxUr10ZCWzkipKiTklpEswH29qBqvahxXoyuZxzfyAoFot b7jhV9tBhn/C+B/H8dqhS6ar713OJf8O20tla/1zLQ81Sm2QRkeuD8tweRvo6o6cIncG tyOQ== X-Gm-Message-State: APjAAAWNueWFclAT9GuxmJpbKktH0QNgeFK7DHanCyxpzmAPeXPrppy6 2Gbv+Lp9kichzpJQjMZRlew6KEx/ X-Google-Smtp-Source: APXvYqyE6nOhFEnWRHYnntPBZae/WUrkKEEUcFfExbCjb4BmOV0MqWoF9tgQ/3kMdRsbtg10NzBQFA== X-Received: by 2002:adf:d184:: with SMTP id v4mr11192984wrc.76.1576224495327; Fri, 13 Dec 2019 00:08:15 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id s15sm8980480wrp.4.2019.12.13.00.08.14 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:14 -0800 (PST) Message-Id: In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:56 +0000 Subject: [PATCH 09/19] built-in app -p: allow selecting a mode change as a "hunk" Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin This imitates the way the Perl version treats mode changes: it offers the mode change up for the user to decide, as if it was a diff hunk. In contrast to the Perl version, we make use of the fact that the mode line is the first hunk, and explicitly strip out that line from the diff header if that "hunk" was not selected to be applied, and skipping that hunk while coalescing the diff. The Perl version plays some kind of diff line lego instead. Signed-off-by: Johannes Schindelin --- add-patch.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/add-patch.c b/add-patch.c index c32541f46d..2007f55e04 100644 --- a/add-patch.c +++ b/add-patch.c @@ -33,7 +33,7 @@ struct add_p_state { struct hunk head; struct hunk *hunk; size_t hunk_nr, hunk_alloc; - unsigned deleted:1; + unsigned deleted:1, mode_change:1; } *file_diff; size_t file_diff_nr; }; @@ -129,6 +129,17 @@ static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk) return 0; } +static int is_octal(const char *p, size_t len) +{ + if (!len) + return 0; + + while (len--) + if (*p < '0' || *(p++) > '7') + return 0; + return 1; +} + static int parse_diff(struct add_p_state *s, const struct pathspec *ps) { struct argv_array args = ARGV_ARRAY_INIT; @@ -181,7 +192,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) pend = p + plain->len; while (p != pend) { char *eol = memchr(p, '\n', pend - p); - const char *deleted = NULL; + const char *deleted = NULL, *mode_change = NULL; if (!eol) eol = pend; @@ -218,8 +229,53 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) file_diff->deleted = 1; else if (parse_hunk_header(s, hunk) < 0) return -1; + } else if (hunk == &file_diff->head && + skip_prefix(p, "old mode ", &mode_change) && + is_octal(mode_change, eol - mode_change)) { + if (file_diff->mode_change) + BUG("double mode change?\n\n%.*s", + (int)(eol - plain->buf), plain->buf); + if (file_diff->hunk_nr++) + BUG("mode change in the middle?\n\n%.*s", + (int)(eol - plain->buf), plain->buf); + + /* + * Do *not* change `hunk`: the mode change pseudo-hunk + * is _part of_ the header "hunk". + */ + file_diff->mode_change = 1; + ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, + file_diff->hunk_alloc); + memset(file_diff->hunk, 0, sizeof(struct hunk)); + file_diff->hunk->start = p - plain->buf; + if (colored_p) + file_diff->hunk->colored_start = + colored_p - colored->buf; + } else if (hunk == &file_diff->head && + skip_prefix(p, "new mode ", &mode_change) && + is_octal(mode_change, eol - mode_change)) { + + /* + * Extend the "mode change" pseudo-hunk to include also + * the "new mode" line. + */ + if (!file_diff->mode_change) + BUG("'new mode' without 'old mode'?\n\n%.*s", + (int)(eol - plain->buf), plain->buf); + if (file_diff->hunk_nr != 1) + BUG("mode change in the middle?\n\n%.*s", + (int)(eol - plain->buf), plain->buf); + if (p - plain->buf != file_diff->hunk->end) + BUG("'new mode' does not immediately follow " + "'old mode'?\n\n%.*s", + (int)(eol - plain->buf), plain->buf); } + if (file_diff->deleted && file_diff->mode_change) + BUG("diff contains delete *and* a mode change?!?\n%.*s", + (int)(eol - (plain->buf + file_diff->head.start)), + plain->buf + file_diff->head.start); + p = eol == pend ? pend : eol + 1; hunk->end = p - plain->buf; @@ -233,6 +289,16 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) hunk->colored_end = colored_p - colored->buf; } + + if (mode_change) { + if (file_diff->hunk_nr != 1) + BUG("mode change in hunk #%d???", + (int)file_diff->hunk_nr); + /* Adjust the end of the "mode change" pseudo-hunk */ + file_diff->hunk->end = hunk->end; + if (colored) + file_diff->hunk->colored_end = hunk->colored_end; + } } return 0; @@ -284,6 +350,39 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, hunk->end - hunk->start); } +static void render_diff_header(struct add_p_state *s, + struct file_diff *file_diff, int colored, + struct strbuf *out) +{ + /* + * If there was a mode change, the first hunk is a pseudo hunk that + * corresponds to the mode line in the header. If the user did not want + * to stage that "hunk", we actually have to cut it out from the header. + */ + int skip_mode_change = + file_diff->mode_change && file_diff->hunk->use != USE_HUNK; + struct hunk *head = &file_diff->head, *first = file_diff->hunk; + + if (!skip_mode_change) { + render_hunk(s, head, 0, colored, out); + return; + } + + if (colored) { + const char *p = s->colored.buf; + + strbuf_add(out, p + head->colored_start, + first->colored_start - head->colored_start); + strbuf_add(out, p + first->colored_end, + head->colored_end - first->colored_end); + } else { + const char *p = s->plain.buf; + + strbuf_add(out, p + head->start, first->start - head->start); + strbuf_add(out, p + first->end, head->end - first->end); + } +} + static void reassemble_patch(struct add_p_state *s, struct file_diff *file_diff, struct strbuf *out) { @@ -291,9 +390,9 @@ static void reassemble_patch(struct add_p_state *s, size_t i; ssize_t delta = 0; - render_hunk(s, &file_diff->head, 0, 0, out); + render_diff_header(s, file_diff, 0, out); - for (i = 0; i < file_diff->hunk_nr; i++) { + for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) { hunk = file_diff->hunk + i; if (hunk->use != USE_HUNK) delta += hunk->header.old_count @@ -328,7 +427,7 @@ static int patch_update_file(struct add_p_state *s, return 0; strbuf_reset(&s->buf); - render_hunk(s, &file_diff->head, 0, colored, &s->buf); + render_diff_header(s, file_diff, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { if (hunk_index >= file_diff->hunk_nr) From patchwork Fri Dec 13 08:07:57 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290113 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 568DA6C1 for ; Fri, 13 Dec 2019 08:08:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 361642465A for ; Fri, 13 Dec 2019 08:08:22 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="WrQVCFhb" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726757AbfLMIIV (ORCPT ); Fri, 13 Dec 2019 03:08:21 -0500 Received: from mail-wm1-f66.google.com ([209.85.128.66]:40105 "EHLO mail-wm1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726674AbfLMIIS (ORCPT ); Fri, 13 Dec 2019 03:08:18 -0500 Received: by mail-wm1-f66.google.com with SMTP id t14so5521963wmi.5 for ; Fri, 13 Dec 2019 00:08:16 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=JSOKuOixMMAiWFnOLJKv9BBGlVka3bCAxtf42Rkumss=; b=WrQVCFhbHsCzehdenZ/NGrogRjHjAZ27b+Cue/USRV1YhnlAe0/HvFSGg82jMI7R36 PoHIjPFizgEAEuqvp0YsjI8g7ONNSGqdLJ/KkUXdwyPHd6fACXaAJQL1ryUSb0LmEfkq 2rWxh9ovn9Uv/NC48DA9whSxlupDugiXFtVcvXmJgAaOn0Zu7y33tuqPHZvKF3yMMwGK a10TSUtKpSUk0bV61R4PrJpYo0Co5fWZK7YyNhPT/6qH8pQdLfRL/AFUvItovKOEuQCO z7M2I42N74RMHfuMlitb6N+Ua5kJUTeELbbiXVov8/Tvsp3QbPXV6e7YcA0mJMzSqpXo d3ew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=JSOKuOixMMAiWFnOLJKv9BBGlVka3bCAxtf42Rkumss=; b=ckgLldynYhjMbV4G+S+6uAF7zkgSEwVBdFvZV2qg6f9dgqLUzNsURiwNEeaRWBB8Ma wFxoPIrO6ZbP0v879PC2uVAm6VbBR8PQ9rTpAtWbkqHhP82nD/cmkpX93Ddfr18pA1VF wBsuzPdil5VpP0fNwIAUvaMv4h+1AovvbnP2H6wPljT583JcO0PPWNmSCGiD46vH4XRt ruF4zyyOZnbwITa98f1qXjleDwlluue5ZwSus21W/HV0H4vfAwo72dHLiV7Czp0dCJI9 yhl5elYw7Nhy4gB0cTa9N1xGrNdiyOX5KjzcAlYCVNDMMLZVDrK79X7EQ6QhEjAkxTHN ppOw== X-Gm-Message-State: APjAAAWtK7n+Ccl27/htKV9IDY3IwRn0ab1AX6SWtWoepvGEgsczktl3 j4OlexP2+bUKOXG9dFlw9yWT79sW X-Google-Smtp-Source: APXvYqwTaBI4m8XMQEcp0Cv6oOZatOIs5v8PjzlCuhlprshU9QQRjV2D9oJcK0gOlQCpNcn8x5RmCQ== X-Received: by 2002:a7b:c389:: with SMTP id s9mr11787174wmj.7.1576224495909; Fri, 13 Dec 2019 00:08:15 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id e16sm8863503wrs.73.2019.12.13.00.08.15 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:15 -0800 (PST) Message-Id: <8d588896603e560b82f37e1924dd012a63df6d95.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:57 +0000 Subject: [PATCH 10/19] built-in add -p: show different prompts for mode changes and deletions Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin Just like the Perl version, we now helpfully ask the user whether they want to stage a mode change, or a deletion. Note that we define the prompts in an array, in preparation for a later patch that changes those prompts to yet different versions for `git reset -p`, `git stash -p` and `git checkout -p` (which all call the `git add -p` machinery to do the actual work). Signed-off-by: Johannes Schindelin --- add-patch.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/add-patch.c b/add-patch.c index 2007f55e04..171025b08d 100644 --- a/add-patch.c +++ b/add-patch.c @@ -7,6 +7,16 @@ #include "color.h" #include "diff.h" +enum prompt_mode_type { + PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK +}; + +static const char *prompt_mode[] = { + N_("Stage mode change [y,n,a,d%s,?]? "), + N_("Stage deletion [y,n,a,d%s,?]? "), + N_("Stage this hunk [y,n,a,d%s,?]? ") +}; + struct hunk_header { unsigned long old_offset, old_count, new_offset, new_count; /* @@ -422,6 +432,7 @@ static int patch_update_file(struct add_p_state *s, char ch; struct child_process cp = CHILD_PROCESS_INIT; int colored = !!s->colored.len; + enum prompt_mode_type prompt_mode_type; if (!file_diff->hunk_nr) return 0; @@ -466,13 +477,20 @@ static int patch_update_file(struct add_p_state *s, strbuf_addstr(&s->buf, ",j"); if (hunk_index + 1 < file_diff->hunk_nr) strbuf_addstr(&s->buf, ",J"); + + if (file_diff->deleted) + prompt_mode_type = PROMPT_DELETION; + else if (file_diff->mode_change && !hunk_index) + prompt_mode_type = PROMPT_MODE_CHANGE; + else + prompt_mode_type = PROMPT_HUNK; + color_fprintf(stdout, s->s.prompt_color, "(%"PRIuMAX"/%"PRIuMAX") ", (uintmax_t)hunk_index + 1, (uintmax_t)file_diff->hunk_nr); color_fprintf(stdout, s->s.prompt_color, - _("Stage this hunk [y,n,a,d%s,?]? "), - s->buf.buf); + _(prompt_mode[prompt_mode_type]), s->buf.buf); fflush(stdout); if (strbuf_getline(&s->answer, stdin) == EOF) break; From patchwork Fri Dec 13 08:07:58 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290125 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 993726C1 for ; Fri, 13 Dec 2019 08:08:29 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6EA4E24658 for ; Fri, 13 Dec 2019 08:08:29 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="sLYnWps/" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726786AbfLMII2 (ORCPT ); Fri, 13 Dec 2019 03:08:28 -0500 Received: from mail-wr1-f48.google.com ([209.85.221.48]:35532 "EHLO mail-wr1-f48.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726671AbfLMIIU (ORCPT ); Fri, 13 Dec 2019 03:08:20 -0500 Received: by mail-wr1-f48.google.com with SMTP id g17so5618363wro.2 for ; Fri, 13 Dec 2019 00:08:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=39e6o6WIMT5n4pTJWFwh4ccy+MRiSfyrnLdNUVcDxOM=; b=sLYnWps/TMPEvceTn+Co3NLGjZFIkRMsrQk7GaRQhjjQKeNKoFgbPDnujdlUnb9A3U 9aH8S7znkznFCYkK5ANMeIbYtqi4FjOvgDyaoItId6t4MJLinw+jpzvNCpogIGala2ns MN+HbSzYAKFnB+fn0hk9IQu2HpUP/i0/txUnCBQNi5rTaG6DQbv0Y9N455eaXAA4UJqd doOG5bjxyFviH0K48UZpGm+UuRgwKU8fJV4TGVEJr+wQaIgQcHhDWsIbx0K5YtxLHr0l 6uPA9FnE8+XFoWJ0jHCtW9/gMyqu1Ejms39S+rmYsbwfuaCUru7/ZXThNbJsuVwIm6PZ WFlA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=39e6o6WIMT5n4pTJWFwh4ccy+MRiSfyrnLdNUVcDxOM=; b=uiC7pFtPKoCvj4ALrci/2bV9LWSyIp6FDyLLu7gtc691EIBeeMPpkSXUQrEcVVys+E ZZJ8hyM8DbYPnxslym0uIEo79dUNacDxs6jrTctxNg03sKtkvGu/h0vCQ2gu5NJ6ZDQ+ sj7l0dOVuqAJiE5BXnnWQANH78AViRsY56l37tKXq3KpgECl/VMKzqSKDqmY2D1eCTde 9YIA670Iv1YoW/eLpsyCNKh1e0fZWQNQTAQ2+U9QeSVewJ6iYeJEBtVXZzaiqaQN3Yvf ahbvmhCLU/LIXAAUiJOjiandpvQqQRvLQwor6ZVuzzHS5WjpZsgmayrd7HivPSGzes+Q A2pg== X-Gm-Message-State: APjAAAVl6XFaUGdlzZb/XV+og3cu5XmXu3CTynF+EtHxwuT/xhd2vPKU 2uZZq/y33kI9zxaPmX1u3b8cYSXQ X-Google-Smtp-Source: APXvYqzyj3ZD1AdonIAW1DsbOvJXMmvpL45yqynIwgk+GwVu4KHbhV3qlFbft5lK6FqgLeMAnLIiXg== X-Received: by 2002:adf:e3d0:: with SMTP id k16mr11407388wrm.241.1576224496673; Fri, 13 Dec 2019 00:08:16 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id k13sm9197603wrx.59.2019.12.13.00.08.16 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:16 -0800 (PST) Message-Id: <2cd9855fffe97ce31640dfa5ed22bffa98047403.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:58 +0000 Subject: [PATCH 11/19] built-in add -p: implement the hunk splitting feature Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin If this developer's workflow is any indication, then this is *the* most useful feature of Git's interactive `add `command. Note: once again, this is not a verbatim conversion from the Perl code to C: the `hunk_splittable()` function, for example, essentially did all the work of splitting the hunk, just to find out whether more than one hunk would have been the result (and then tossed that result into the trash). In C we instead count the number of resulting hunks (without actually doing the work of splitting, but just counting the transitions from non-context lines to context lines), and store that information with the hunk, and we do that *while* parsing the diff in the first place. Another deviation: the built-in `git add -p` was designed with a single strbuf holding the diff (and another one holding the colored diff, if that one was asked for) in mind, and hunks essentially store just the start and end offsets pointing into that strbuf. As a consequence, when we split hunks, we now use a special mode where the hunk header is generated dynamically, and only the rest of the hunk is stored using such start/end offsets. This way, we also avoid the frequent formatting/re-parsing of the hunk header of the Perl version. Signed-off-by: Johannes Schindelin --- add-patch.c | 215 ++++++++++++++++++++++++++++++++++++- t/t3701-add-interactive.sh | 12 +++ 2 files changed, 225 insertions(+), 2 deletions(-) diff --git a/add-patch.c b/add-patch.c index 171025b08d..2d34ddd7f4 100644 --- a/add-patch.c +++ b/add-patch.c @@ -28,7 +28,7 @@ struct hunk_header { }; struct hunk { - size_t start, end, colored_start, colored_end; + size_t start, end, colored_start, colored_end, splittable_into; enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use; struct hunk_header header; }; @@ -155,7 +155,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) struct argv_array args = ARGV_ARRAY_INIT; struct strbuf *plain = &s->plain, *colored = NULL; struct child_process cp = CHILD_PROCESS_INIT; - char *p, *pend, *colored_p = NULL, *colored_pend = NULL; + char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0'; size_t file_diff_alloc = 0, i, color_arg_index; struct file_diff *file_diff = NULL; struct hunk *hunk = NULL; @@ -217,6 +217,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) hunk->start = p - plain->buf; if (colored_p) hunk->colored_start = colored_p - colored->buf; + marker = '\0'; } else if (p == plain->buf) BUG("diff starts with unexpected line:\n" "%.*s\n", (int)(eol - p), p); @@ -225,6 +226,13 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) else if (starts_with(p, "@@ ") || (hunk == &file_diff->head && skip_prefix(p, "deleted file", &deleted))) { + if (marker == '-' || marker == '+') + /* + * Should not happen; previous hunk did not end + * in a context line? Handle it anyway. + */ + hunk->splittable_into++; + file_diff->hunk_nr++; ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, file_diff->hunk_alloc); @@ -239,6 +247,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) file_diff->deleted = 1; else if (parse_hunk_header(s, hunk) < 0) return -1; + + /* + * Start counting into how many hunks this one can be + * split + */ + marker = *p; } else if (hunk == &file_diff->head && skip_prefix(p, "old mode ", &mode_change) && is_octal(mode_change, eol - mode_change)) { @@ -286,6 +300,11 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) (int)(eol - (plain->buf + file_diff->head.start)), plain->buf + file_diff->head.start); + if ((marker == '-' || marker == '+') && *p == ' ') + hunk->splittable_into++; + if (marker && *p != '\\') + marker = *p; + p = eol == pend ? pend : eol + 1; hunk->end = p - plain->buf; @@ -311,9 +330,30 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } } + if (marker == '-' || marker == '+') + /* + * Last hunk ended in non-context line (i.e. it appended lines + * to the file, so there are no trailing context lines). + */ + hunk->splittable_into++; + return 0; } +static size_t find_next_line(struct strbuf *sb, size_t offset) +{ + char *eol; + + if (offset >= sb->len) + BUG("looking for next line beyond buffer (%d >= %d)\n%s", + (int)offset, (int)sb->len, sb->buf); + + eol = memchr(sb->buf + offset, '\n', sb->len - offset); + if (!eol) + return sb->len; + return eol - sb->buf + 1; +} + static void render_hunk(struct add_p_state *s, struct hunk *hunk, ssize_t delta, int colored, struct strbuf *out) { @@ -412,6 +452,165 @@ static void reassemble_patch(struct add_p_state *s, } } +static int split_hunk(struct add_p_state *s, struct file_diff *file_diff, + size_t hunk_index) +{ + int colored = !!s->colored.len, first = 1; + struct hunk *hunk = file_diff->hunk + hunk_index; + size_t splittable_into; + size_t end, colored_end, current, colored_current = 0, context_line_count; + struct hunk_header remaining, *header; + char marker, ch; + + if (hunk_index >= file_diff->hunk_nr) + BUG("invalid hunk index: %d (must be >= 0 and < %d)", + (int)hunk_index, (int)file_diff->hunk_nr); + + if (hunk->splittable_into < 2) + return 0; + splittable_into = hunk->splittable_into; + + end = hunk->end; + colored_end = hunk->colored_end; + + remaining = hunk->header; + + file_diff->hunk_nr += splittable_into - 1; + ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, file_diff->hunk_alloc); + if (hunk_index + splittable_into < file_diff->hunk_nr) + memmove(file_diff->hunk + hunk_index + splittable_into, + file_diff->hunk + hunk_index + 1, + (file_diff->hunk_nr - hunk_index - splittable_into) + * sizeof(*hunk)); + hunk = file_diff->hunk + hunk_index; + hunk->splittable_into = 1; + memset(hunk + 1, 0, (splittable_into - 1) * sizeof(*hunk)); + + header = &hunk->header; + header->old_count = header->new_count = 0; + + current = hunk->start; + if (colored) + colored_current = hunk->colored_start; + marker = '\0'; + context_line_count = 0; + + while (splittable_into > 1) { + ch = s->plain.buf[current]; + + if (!ch) + BUG("buffer overrun while splitting hunks"); + + /* + * Is this the first context line after a chain of +/- lines? + * Then record the start of the next split hunk. + */ + if ((marker == '-' || marker == '+') && ch == ' ') { + first = 0; + hunk[1].start = current; + if (colored) + hunk[1].colored_start = colored_current; + context_line_count = 0; + } + + /* + * Was the previous line a +/- one? Alternatively, is this the + * first line (and not a +/- one)? + * + * Then just increment the appropriate counter and continue + * with the next line. + */ + if (marker != ' ' || (ch != '-' && ch != '+')) { +next_hunk_line: + /* Comment lines are attached to the previous line */ + if (ch == '\\') + ch = marker ? marker : ' '; + + /* current hunk not done yet */ + if (ch == ' ') + context_line_count++; + else if (ch == '-') + header->old_count++; + else if (ch == '+') + header->new_count++; + else + BUG("unhandled diff marker: '%c'", ch); + marker = ch; + current = find_next_line(&s->plain, current); + if (colored) + colored_current = + find_next_line(&s->colored, + colored_current); + continue; + } + + /* + * We got us the start of a new hunk! + * + * This is a context line, so it is shared with the previous + * hunk, if any. + */ + + if (first) { + if (header->old_count || header->new_count) + BUG("counts are off: %d/%d", + (int)header->old_count, + (int)header->new_count); + + header->old_count = context_line_count; + header->new_count = context_line_count; + context_line_count = 0; + first = 0; + goto next_hunk_line; + } + + remaining.old_offset += header->old_count; + remaining.old_count -= header->old_count; + remaining.new_offset += header->new_count; + remaining.new_count -= header->new_count; + + /* initialize next hunk header's offsets */ + hunk[1].header.old_offset = + header->old_offset + header->old_count; + hunk[1].header.new_offset = + header->new_offset + header->new_count; + + /* add one split hunk */ + header->old_count += context_line_count; + header->new_count += context_line_count; + + hunk->end = current; + if (colored) + hunk->colored_end = colored_current; + + hunk++; + hunk->splittable_into = 1; + hunk->use = hunk[-1].use; + header = &hunk->header; + + header->old_count = header->new_count = context_line_count; + context_line_count = 0; + + splittable_into--; + marker = ch; + } + + /* last hunk simply gets the rest */ + if (header->old_offset != remaining.old_offset) + BUG("miscounted old_offset: %lu != %lu", + header->old_offset, remaining.old_offset); + if (header->new_offset != remaining.new_offset) + BUG("miscounted new_offset: %lu != %lu", + header->new_offset, remaining.new_offset); + header->old_count = remaining.old_count; + header->new_count = remaining.new_count; + hunk->end = end; + if (colored) + hunk->colored_end = colored_end; + + return 0; +} + static const char help_patch_text[] = N_("y - stage this hunk\n" "n - do not stage this hunk\n" @@ -421,6 +620,7 @@ N_("y - stage this hunk\n" "J - leave this hunk undecided, see next hunk\n" "k - leave this hunk undecided, see previous undecided hunk\n" "K - leave this hunk undecided, see previous hunk\n" + "s - split the current hunk into smaller hunks\n" "? - print help\n"); static int patch_update_file(struct add_p_state *s, @@ -477,6 +677,8 @@ static int patch_update_file(struct add_p_state *s, strbuf_addstr(&s->buf, ",j"); if (hunk_index + 1 < file_diff->hunk_nr) strbuf_addstr(&s->buf, ",J"); + if (hunk->splittable_into > 1) + strbuf_addstr(&s->buf, ",s"); if (file_diff->deleted) prompt_mode_type = PROMPT_DELETION; @@ -539,6 +741,15 @@ static int patch_update_file(struct add_p_state *s, hunk_index = undecided_next; else err(s, _("No next hunk")); + } else if (s->answer.buf[0] == 's') { + size_t splittable_into = hunk->splittable_into; + if (splittable_into < 2) + err(s, _("Sorry, cannot split this hunk")); + else if (!split_hunk(s, file_diff, + hunk - file_diff->hunk)) + color_fprintf_ln(stdout, s->s.header_color, + _("Split into %d hunks."), + (int)splittable_into); } else color_fprintf(stdout, s->s.help_color, _(help_patch_text)); diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 5db6432e33..fe383be50e 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -442,6 +442,18 @@ test_expect_failure 'split hunk "add -p (no, yes, edit)"' ' ! grep "^+31" actual ' +test_expect_success 'split hunk with incomplete line at end' ' + git reset --hard && + printf "missing LF" >>test && + git add test && + test_write_lines before 10 20 30 40 50 60 70 >test && + git grep --cached missing && + test_write_lines s n y q | git add -p && + test_must_fail git grep --cached missing && + git grep before && + test_must_fail git grep --cached before +' + test_expect_failure 'edit, adding lines to the first hunk' ' test_write_lines 10 11 20 30 40 50 51 60 >test && git reset && From patchwork Fri Dec 13 08:07:59 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290117 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 28BA9930 for ; Fri, 13 Dec 2019 08:08:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 084432465E for ; Fri, 13 Dec 2019 08:08:25 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="VYQp3235" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726767AbfLMIIX (ORCPT ); Fri, 13 Dec 2019 03:08:23 -0500 Received: from mail-wm1-f67.google.com ([209.85.128.67]:52368 "EHLO mail-wm1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726739AbfLMIIU (ORCPT ); Fri, 13 Dec 2019 03:08:20 -0500 Received: by mail-wm1-f67.google.com with SMTP id p9so5270821wmc.2 for ; Fri, 13 Dec 2019 00:08:19 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=Zagw8BugEWLxFwJ+vo56ycxQDxc0qcHDFD+dt8RRyrY=; b=VYQp3235vUhYDrGE6qRozmbEIWlOIDyLGGqldLNo/wFDIjdM3O3eXTdPYbBJZjNAF6 HeUkysNE/R13xQZJbQg3WM3C/4qdLsBmSTZrgJixbyhaEaZ/24cWqRUt7k17aXTrxve/ CVism6DyMBhwV3BIXCAWQXQ9+CTKHK2sltpXLgzZgM7DICvFW7B+oB2xqgnOa/tkrlqN XD9IUf8NaCe6ZmICcv2eJq26V+h64Yj+wSFtRLPr463XM8s1WamtJuZs/Lg3r9MNtM3d LGX6IcrA99x+Nd3SZLpgLT0PY1OP5HSRhBU2cqqMGa8TcycZu/ma9hBRX+7jy7a9MldG Syww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=Zagw8BugEWLxFwJ+vo56ycxQDxc0qcHDFD+dt8RRyrY=; b=Ch2/3w+TT5W28sy1tP0zLUQfRjJwSue1cn+CxTep4elG7ZWENFokZX1Hgi+i7UUoII V0gldic1qfEgtzrIgwjRc9gBmuaEiTcacqlnmxG80CytfDN1RNXX0Gqt7i3y8nj7v8CT A4mGCdtnoHHB8mHWHTSX8WWhUerp1tZUogTaKWS65TDXQsMXQJXPqALnl+tIHeMe343N ZutWmfkISTYEyp51r2zclRK/OdeRvEN/MdOZ7DXralzBP4rhiK5wabq58PEaW8ErXcNV Zjw5Bqe46GeZWeI7Vuw+0Vg73y1j2aPDL22hBDWxgeqc52K/CgIJDDRi6Y26ouO39C4L HSRQ== X-Gm-Message-State: APjAAAW9NkufEv14DDQo6KgmccxzqxH5paZa65HVbFJXe96nhUQcf3v0 5Kj6IEjkFYYbwvg9H9Mu8SvrCvNN X-Google-Smtp-Source: APXvYqyived3a4QEipkWYetWEQqibi+AdN7r+gPA98jxbMFsmRgywAX1CcDcFBQDq9VN/2EyzVYwAQ== X-Received: by 2002:a1c:7e13:: with SMTP id z19mr11622114wmc.67.1576224497561; Fri, 13 Dec 2019 00:08:17 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id o4sm8964344wrw.97.2019.12.13.00.08.16 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:16 -0800 (PST) Message-Id: In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:07:59 +0000 Subject: [PATCH 12/19] built-in add -p: coalesce hunks after splitting them Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin This is considered "the right thing to do", according to 933e44d3a0 ("add -p": work-around an old laziness that does not coalesce hunks, 2011-04-06). Note: we cannot simply modify the hunks while merging them; Once we implement hunk editing, we will call `reassemble_patch()` whenever a hunk is edited, therefore we must not modify the hunks (because the user might e.g. hit `K` and change their mind whether to stage the previous hunk). Signed-off-by: Johannes Schindelin --- add-patch.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/add-patch.c b/add-patch.c index 2d34ddd7f4..c8d84aec68 100644 --- a/add-patch.c +++ b/add-patch.c @@ -433,6 +433,55 @@ static void render_diff_header(struct add_p_state *s, } } +/* Coalesce hunks again that were split */ +static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff, + size_t *hunk_index, struct hunk *merged) +{ + size_t i = *hunk_index; + struct hunk *hunk = file_diff->hunk + i; + /* `header` corresponds to the merged hunk */ + struct hunk_header *header = &merged->header, *next; + + if (hunk->use != USE_HUNK) + return 0; + + *merged = *hunk; + /* We simply skip the colored part (if any) when merging hunks */ + merged->colored_start = merged->colored_end = 0; + + for (; i + 1 < file_diff->hunk_nr; i++) { + hunk++; + next = &hunk->header; + + /* + * Stop merging hunks when: + * + * - the hunk is not selected for use, or + * - the hunk does not overlap with the already-merged hunk(s) + */ + if (hunk->use != USE_HUNK || + header->new_offset >= next->new_offset || + header->new_offset + header->new_count < next->new_offset || + merged->start >= hunk->start || + merged->end < hunk->start) + break; + + merged->end = hunk->end; + merged->colored_end = hunk->colored_end; + + header->old_count = next->old_offset + next->old_count + - header->old_offset; + header->new_count = next->new_offset + next->new_count + - header->new_offset; + } + + if (i == *hunk_index) + return 0; + + *hunk_index = i; + return 1; +} + static void reassemble_patch(struct add_p_state *s, struct file_diff *file_diff, struct strbuf *out) { @@ -443,12 +492,19 @@ static void reassemble_patch(struct add_p_state *s, render_diff_header(s, file_diff, 0, out); for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) { + struct hunk merged = { 0 }; + hunk = file_diff->hunk + i; if (hunk->use != USE_HUNK) delta += hunk->header.old_count - hunk->header.new_count; - else + else { + /* merge overlapping hunks into a temporary hunk */ + if (merge_hunks(s, file_diff, &i, &merged)) + hunk = &merged; + render_hunk(s, hunk, delta, 0, out); + } } } From patchwork Fri Dec 13 08:08:00 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290119 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8A1426C1 for ; Fri, 13 Dec 2019 08:08:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6850D2465B for ; Fri, 13 Dec 2019 08:08:25 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="qyNDoypZ" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726772AbfLMIIY (ORCPT ); Fri, 13 Dec 2019 03:08:24 -0500 Received: from mail-wr1-f65.google.com ([209.85.221.65]:37851 "EHLO mail-wr1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726623AbfLMIIU (ORCPT ); Fri, 13 Dec 2019 03:08:20 -0500 Received: by mail-wr1-f65.google.com with SMTP id w15so5618598wru.4 for ; Fri, 13 Dec 2019 00:08:19 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=VT9oSHUP0RKB+EtGYSHiqIDVovWaaPzNGKQuJobuqtQ=; b=qyNDoypZpJxUmLVkzf1c9Z8Y4/1yQr+62F3ETYKXCNQTihSN5OB2szCISqU5WOsaqw FPVzVFd7QYqDYtnnYgK364eR8IojRL/VPvTUYqucyImiN70+WgVKDLqExEnv3oF1ZGPH bfLCz0tc1meWbn/MedYXiH+jOa/0Tdxuk5kIg3V4/aknf0Pu6zB+EeH9sbdKI6hlfJCG lxtfaKauGDGsXQrf51Ou4P64tQjVW5PZq06EF8D5HiHJtAH2Npd5iUltt9FUzyQwXl5X fVLmsq8ovTH3gdTu3GXM6iHUSd09BlDr+gWEYdiFFiDpZuq1/iXpZeScb3Ub65C403N3 Srhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=VT9oSHUP0RKB+EtGYSHiqIDVovWaaPzNGKQuJobuqtQ=; b=Ylp/PmTeGDlY7IwchWGZ8nt6ZTW3iDrpXBdNOgg+UvMCVVKZ210bUX/Pt7gR2IaL9v Usv1BOKsazolT0dliyNU5iRe8kTgCdT8PlcoIObiqqV8gRAql6IkCNfF2hAGsj8fSFxq RHJYMst8TP6TBLe6CAYJHQjLaye0Ol186kPm47U6ugB+aKsi5wjEn5m7hGGkCZmZJlFd dGfI0a0t5hsWjn31l8XbzosUbPo4St0JLk/6AY+aiswltuUlI3PwzGnSIX2F8eK4l+m6 QaFaBfw/15WCuWZYvAgsK78CKbI9HPca+m9nOkfrJStX5rBZfKqE9wvG6qjrFUYD3KOW zfuw== X-Gm-Message-State: APjAAAX1kk4cdzyGsVkmpCHBhL/dlswtaYwG87rHvBYtFvJElBJvszoG lKcqyEK3r9+pLX3nUnRvOnlJpZpl X-Google-Smtp-Source: APXvYqzpHZeELeUkBFAcCi2aZit6dL7MzkPuI0xer1bb4iDQFUEVAb3Ed0RZduPVtlWL8Y/xqjg4wA== X-Received: by 2002:a5d:6748:: with SMTP id l8mr11420789wrw.188.1576224498369; Fri, 13 Dec 2019 00:08:18 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id b15sm559527wmj.13.2019.12.13.00.08.17 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:17 -0800 (PST) Message-Id: <18c008fbe119036700faf2a82711c2b0f453df6a.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:08:00 +0000 Subject: [PATCH 13/19] strbuf: add a helper function to call the editor "on an strbuf" Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin This helper supports the scenario where Git has a populated `strbuf` and wants to let the user edit it interactively. In `git add -p`, we will use this to allow interactive hunk editing: the diff hunks are already in memory, but we need to write them out to a file so that an editor can be launched, then read everything back once the user is done editing. Signed-off-by: Johannes Schindelin --- strbuf.c | 28 ++++++++++++++++++++++++++++ strbuf.h | 11 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/strbuf.c b/strbuf.c index aa48d179a9..f19da55b07 100644 --- a/strbuf.c +++ b/strbuf.c @@ -1125,3 +1125,31 @@ int strbuf_normalize_path(struct strbuf *src) strbuf_release(&dst); return 0; } + +int strbuf_edit_interactively(struct strbuf *buffer, const char *path, + const char *const *env) +{ + char *path2 = NULL; + int fd, res = 0; + + if (!is_absolute_path(path)) + path = path2 = xstrdup(git_path("%s", path)); + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + res = error_errno(_("could not open '%s' for writing"), path); + else if (write_in_full(fd, buffer->buf, buffer->len) < 0) { + res = error_errno(_("could not write to '%s'"), path); + close(fd); + } else if (close(fd) < 0) + res = error_errno(_("could not close '%s'"), path); + else { + strbuf_reset(buffer); + if (launch_editor(path, buffer, env) < 0) + res = error_errno(_("could not edit '%s'"), path); + unlink(path); + } + + free(path2); + return res; +} diff --git a/strbuf.h b/strbuf.h index 84cf969721..bfa66569a4 100644 --- a/strbuf.h +++ b/strbuf.h @@ -621,6 +621,17 @@ int launch_editor(const char *path, struct strbuf *buffer, int launch_sequence_editor(const char *path, struct strbuf *buffer, const char *const *env); +/* + * In contrast to `launch_editor()`, this function writes out the contents + * of the specified file first, then clears the `buffer`, then launches + * the editor and reads back in the file contents into the `buffer`. + * Finally, it deletes the temporary file. + * + * If `path` is relative, it refers to a file in the `.git` directory. + */ +int strbuf_edit_interactively(struct strbuf *buffer, const char *path, + const char *const *env); + void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, From patchwork Fri Dec 13 08:08:01 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290123 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 10311930 for ; Fri, 13 Dec 2019 08:08:29 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id CF45B24658 for ; Fri, 13 Dec 2019 08:08:28 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="XAY3f37T" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726783AbfLMII1 (ORCPT ); Fri, 13 Dec 2019 03:08:27 -0500 Received: from mail-wm1-f52.google.com ([209.85.128.52]:50595 "EHLO mail-wm1-f52.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726750AbfLMIIX (ORCPT ); Fri, 13 Dec 2019 03:08:23 -0500 Received: by mail-wm1-f52.google.com with SMTP id a5so5281230wmb.0 for ; Fri, 13 Dec 2019 00:08:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=oIKUqb8zXvthx3CpYus3Q8kPps4SSG3JdA7cfqHGew0=; b=XAY3f37TXd/wB/WLzgo2dA1/rAdjldsopXqSDgHuHeaWz0CkZlRtJ78OeXEAdN0bRf E5UBf11y5hji7EcS86vSxjwJievw/volb8o939WD8mQGwVwxq5x+2LALuIjwduHN10fv cY72v4I5+LqjVOWjwS64NHIfAVjhiswUZiLaWPllz6URnLg8zlG9LzHflTzXoKOBN6Cn TbPxRajiFyMHrgt70pOoWUz99KCO3fGK7zKf6krlk8M/x6RtZI+HZos1m+9EbAdpeDZl eRzqoEukCWn5GcGc4Vv9xR1K9t9xL8Xwm5rrycx19lO0Jfz2yG/s3gA1LiwavUCCAQZz lkJg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=oIKUqb8zXvthx3CpYus3Q8kPps4SSG3JdA7cfqHGew0=; b=T4sKR7DgKv3vsIK21nqgWXV+MgtECVBG8URiRvRVH+hEm+24Fap/QaSq8pPHGbx68H RrCCuhH3z9vR0UbBMeWzM7DE/f9Zx8WiQEaoCMtIQaBsu5S6lDvs/wmxt532patVK5OY RLyNh29TpzbGDncukN7jjoQojsG/c+5yDnpi+sqgXNTGIXt0f4WXsz6YUuA0WU7n96nV grcfobTAynm6XiYtPEFsfBRQ4hSsTkNcF812e3hVKrIdeAr3beMDB7YXIb2UlhfphiHe gagyftX16Dv85A5LkbyBtMwQL/wf4Ep7M9T8+GYIFw6vGHOov7JI+oH1gU+DUNzW4IXU rV8Q== X-Gm-Message-State: APjAAAWfCzQJHTPc27ptsdwYWIHW/clRsg8rWSn4v6GAZG0q2F04Uwpj yyEdMoPLgjjaSo+dR1aK9dlq+hKW X-Google-Smtp-Source: APXvYqwWdteYRm775CxiuZxJ1TTmHxGu0Ynz8M1z51nYgMzII5b2SzOeeXC1BAHSU5B9/Bo54TdCSg== X-Received: by 2002:a7b:c93a:: with SMTP id h26mr11453216wml.83.1576224499162; Fri, 13 Dec 2019 00:08:19 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id x11sm9364325wmg.46.2019.12.13.00.08.18 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:18 -0800 (PST) Message-Id: <1e23fcd44e17b2980da5997344d6aaa7efe0754a.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:08:01 +0000 Subject: [PATCH 14/19] built-in add -p: implement hunk editing Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin Just like `git add --edit` allows the user to edit the diff before it is being applied to the index, this feature allows the user to edit the diff *hunk*. Naturally, it gets a bit more complicated here because the result has to play well with the remaining hunks of the overall diff. Therefore, we have to do a loop in which we let the user edit the hunk, then test whether the result would work, and if not, drop the edits and let the user decide whether to try editing the hunk again. Note: in contrast to the Perl version, we use the same diff "coalescing" (i.e. merging overlapping hunks into a single one) also for the check after editing, and we introduce a new flag for that purpose that asks the `reassemble_patch()` function to pretend that all hunks were selected for use. This allows us to continue to run `git apply` *without* the `--allow-overlap` option (unlike the Perl version), and it also fixes two known breakages in `t3701-add-interactive.sh` (which we cannot mark as resolved so far because the Perl script version is still the default and continues to have those breakages). Signed-off-by: Johannes Schindelin --- add-interactive.c | 6 + add-interactive.h | 3 + add-patch.c | 333 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 325 insertions(+), 17 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index 29356c5aa2..6a5048c83e 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -46,6 +46,12 @@ void init_add_i_state(struct add_i_state *s, struct repository *r) init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET); init_color(r, s, "fraginfo", s->fraginfo_color, diff_get_color(s->use_color, DIFF_FRAGINFO)); + init_color(r, s, "context", s->context_color, + diff_get_color(s->use_color, DIFF_CONTEXT)); + init_color(r, s, "old", s->file_old_color, + diff_get_color(s->use_color, DIFF_FILE_OLD)); + init_color(r, s, "new", s->file_new_color, + diff_get_color(s->use_color, DIFF_FILE_NEW)); } /* diff --git a/add-interactive.h b/add-interactive.h index 584f304a9a..062dc3646c 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -12,6 +12,9 @@ struct add_i_state { char error_color[COLOR_MAXLEN]; char reset_color[COLOR_MAXLEN]; char fraginfo_color[COLOR_MAXLEN]; + char context_color[COLOR_MAXLEN]; + char file_old_color[COLOR_MAXLEN]; + char file_new_color[COLOR_MAXLEN]; }; void init_add_i_state(struct add_i_state *s, struct repository *r); diff --git a/add-patch.c b/add-patch.c index c8d84aec68..ea863ca09d 100644 --- a/add-patch.c +++ b/add-patch.c @@ -29,6 +29,7 @@ struct hunk_header { struct hunk { size_t start, end, colored_start, colored_end, splittable_into; + ssize_t delta; enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use; struct hunk_header header; }; @@ -435,14 +436,14 @@ static void render_diff_header(struct add_p_state *s, /* Coalesce hunks again that were split */ static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff, - size_t *hunk_index, struct hunk *merged) + size_t *hunk_index, int use_all, struct hunk *merged) { - size_t i = *hunk_index; + size_t i = *hunk_index, delta; struct hunk *hunk = file_diff->hunk + i; /* `header` corresponds to the merged hunk */ struct hunk_header *header = &merged->header, *next; - if (hunk->use != USE_HUNK) + if (!use_all && hunk->use != USE_HUNK) return 0; *merged = *hunk; @@ -459,20 +460,99 @@ static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff, * - the hunk is not selected for use, or * - the hunk does not overlap with the already-merged hunk(s) */ - if (hunk->use != USE_HUNK || - header->new_offset >= next->new_offset || - header->new_offset + header->new_count < next->new_offset || - merged->start >= hunk->start || - merged->end < hunk->start) + if ((!use_all && hunk->use != USE_HUNK) || + header->new_offset >= next->new_offset + merged->delta || + header->new_offset + header->new_count + < next->new_offset + merged->delta) break; - merged->end = hunk->end; - merged->colored_end = hunk->colored_end; + /* + * If the hunks were not edited, and overlap, we can simply + * extend the line range. + */ + if (merged->start < hunk->start && merged->end > hunk->start) { + merged->end = hunk->end; + merged->colored_end = hunk->colored_end; + delta = 0; + } else { + const char *plain = s->plain.buf; + size_t overlapping_line_count = header->new_offset + + header->new_count - merged->delta + - next->new_offset; + size_t overlap_end = hunk->start; + size_t overlap_start = overlap_end; + size_t overlap_next, len, j; + + /* + * One of the hunks was edited: the modified hunk was + * appended to the strbuf `s->plain`. + * + * Let's ensure that at least the last context line of + * the first hunk overlaps with the corresponding line + * of the second hunk, and then merge. + */ + for (j = 0; j < overlapping_line_count; j++) { + overlap_next = find_next_line(&s->plain, + overlap_end); + + if (overlap_next > hunk->end) + BUG("failed to find %d context lines " + "in:\n%.*s", + (int)overlapping_line_count, + (int)(hunk->end - hunk->start), + plain + hunk->start); + + if (plain[overlap_end] != ' ') + return error(_("expected context line " + "#%d in\n%.*s"), + (int)(j + 1), + (int)(hunk->end + - hunk->start), + plain + hunk->start); + + overlap_start = overlap_end; + overlap_end = overlap_next; + } + len = overlap_end - overlap_start; + + if (len > merged->end - merged->start || + memcmp(plain + merged->end - len, + plain + overlap_start, len)) + return error(_("hunks do not overlap:\n%.*s\n" + "\tdoes not end with:\n%.*s"), + (int)(merged->end - merged->start), + plain + merged->start, + (int)len, plain + overlap_start); + + /* + * Since the start-end ranges are not adjacent, we + * cannot simply take the union of the ranges. To + * address that, we temporarily append the union of the + * lines to the `plain` strbuf. + */ + if (merged->end != s->plain.len) { + size_t start = s->plain.len; + + strbuf_add(&s->plain, plain + merged->start, + merged->end - merged->start); + plain = s->plain.buf; + merged->start = start; + merged->end = s->plain.len; + } + + strbuf_add(&s->plain, + plain + overlap_end, + hunk->end - overlap_end); + merged->end = s->plain.len; + merged->splittable_into += hunk->splittable_into; + delta = merged->delta; + merged->delta += hunk->delta; + } header->old_count = next->old_offset + next->old_count - header->old_offset; - header->new_count = next->new_offset + next->new_count - - header->new_offset; + header->new_count = next->new_offset + delta + + next->new_count - header->new_offset; } if (i == *hunk_index) @@ -483,10 +563,11 @@ static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff, } static void reassemble_patch(struct add_p_state *s, - struct file_diff *file_diff, struct strbuf *out) + struct file_diff *file_diff, int use_all, + struct strbuf *out) { struct hunk *hunk; - size_t i; + size_t save_len = s->plain.len, i; ssize_t delta = 0; render_diff_header(s, file_diff, 0, out); @@ -495,15 +576,24 @@ static void reassemble_patch(struct add_p_state *s, struct hunk merged = { 0 }; hunk = file_diff->hunk + i; - if (hunk->use != USE_HUNK) + if (!use_all && hunk->use != USE_HUNK) delta += hunk->header.old_count - hunk->header.new_count; else { /* merge overlapping hunks into a temporary hunk */ - if (merge_hunks(s, file_diff, &i, &merged)) + if (merge_hunks(s, file_diff, &i, use_all, &merged)) hunk = &merged; render_hunk(s, hunk, delta, 0, out); + + /* + * In case `merge_hunks()` used `plain` as a scratch + * pad (this happens when an edited hunk had to be + * coalesced with another hunk). + */ + strbuf_setlen(&s->plain, save_len); + + delta += hunk->delta; } } } @@ -667,6 +757,204 @@ static int split_hunk(struct add_p_state *s, struct file_diff *file_diff, return 0; } +static void recolor_hunk(struct add_p_state *s, struct hunk *hunk) +{ + const char *plain = s->plain.buf; + size_t current, eol, next; + + if (!s->colored.len) + return; + + hunk->colored_start = s->colored.len; + for (current = hunk->start; current < hunk->end; ) { + for (eol = current; eol < hunk->end; eol++) + if (plain[eol] == '\n') + break; + next = eol + (eol < hunk->end); + if (eol > current && plain[eol - 1] == '\r') + eol--; + + strbuf_addstr(&s->colored, + plain[current] == '-' ? + s->s.file_old_color : + plain[current] == '+' ? + s->s.file_new_color : + s->s.context_color); + strbuf_add(&s->colored, plain + current, eol - current); + strbuf_addstr(&s->colored, GIT_COLOR_RESET); + if (next > eol) + strbuf_add(&s->colored, plain + eol, next - eol); + current = next; + } + hunk->colored_end = s->colored.len; +} + +static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk) +{ + size_t i; + + strbuf_reset(&s->buf); + strbuf_commented_addf(&s->buf, _("Manual hunk edit mode -- see bottom for " + "a quick guide.\n")); + render_hunk(s, hunk, 0, 0, &s->buf); + strbuf_commented_addf(&s->buf, + _("---\n" + "To remove '%c' lines, make them ' ' lines " + "(context).\n" + "To remove '%c' lines, delete them.\n" + "Lines starting with %c will be removed.\n"), + '-', '+', comment_line_char); + strbuf_commented_addf(&s->buf, + _("If the patch applies cleanly, the edited hunk " + "will immediately be\n" + "marked for staging.\n")); + /* + * TRANSLATORS: 'it' refers to the patch mentioned in the previous + * messages. + */ + strbuf_commented_addf(&s->buf, + _("If it does not apply cleanly, you will be " + "given an opportunity to\n" + "edit again. If all lines of the hunk are " + "removed, then the edit is\n" + "aborted and the hunk is left unchanged.\n")); + + if (strbuf_edit_interactively(&s->buf, "addp-hunk-edit.diff", NULL) < 0) + return -1; + + /* strip out commented lines */ + hunk->start = s->plain.len; + for (i = 0; i < s->buf.len; ) { + size_t next = find_next_line(&s->buf, i); + + if (s->buf.buf[i] != comment_line_char) + strbuf_add(&s->plain, s->buf.buf + i, next - i); + i = next; + } + + hunk->end = s->plain.len; + if (hunk->end == hunk->start) + /* The user aborted editing by deleting everything */ + return 0; + + recolor_hunk(s, hunk); + + /* + * If the hunk header is intact, parse it, otherwise simply use the + * hunk header prior to editing (which will adjust `hunk->start` to + * skip the hunk header). + */ + if (s->plain.buf[hunk->start] == '@' && + parse_hunk_header(s, hunk) < 0) + return error(_("could not parse hunk header")); + + return 1; +} + +static ssize_t recount_edited_hunk(struct add_p_state *s, struct hunk *hunk, + size_t orig_old_count, size_t orig_new_count) +{ + struct hunk_header *header = &hunk->header; + size_t i; + + header->old_count = header->new_count = 0; + for (i = hunk->start; i < hunk->end; ) { + switch (s->plain.buf[i]) { + case '-': + header->old_count++; + break; + case '+': + header->new_count++; + break; + case ' ': case '\r': case '\n': + header->old_count++; + header->new_count++; + break; + } + + i = find_next_line(&s->plain, i); + } + + return orig_old_count - orig_new_count + - header->old_count + header->new_count; +} + +static int run_apply_check(struct add_p_state *s, + struct file_diff *file_diff) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + strbuf_reset(&s->buf); + reassemble_patch(s, file_diff, 1, &s->buf); + + setup_child_process(s, &cp, + "apply", "--cached", "--check", NULL); + if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0)) + return error(_("'git apply --cached' failed")); + + return 0; +} + +static int prompt_yesno(struct add_p_state *s, const char *prompt) +{ + for (;;) { + color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt)); + fflush(stdout); + if (strbuf_getline(&s->answer, stdin) == EOF) + return -1; + strbuf_trim_trailing_newline(&s->answer); + switch (tolower(s->answer.buf[0])) { + case 'n': return 0; + case 'y': return 1; + } + } +} + +static int edit_hunk_loop(struct add_p_state *s, + struct file_diff *file_diff, struct hunk *hunk) +{ + size_t plain_len = s->plain.len, colored_len = s->colored.len; + struct hunk backup; + + backup = *hunk; + + for (;;) { + int res = edit_hunk_manually(s, hunk); + if (res == 0) { + /* abandonded */ + *hunk = backup; + return -1; + } + + if (res > 0) { + hunk->delta += + recount_edited_hunk(s, hunk, + backup.header.old_count, + backup.header.new_count); + if (!run_apply_check(s, file_diff)) + return 0; + } + + /* Drop edits (they were appended to s->plain) */ + strbuf_setlen(&s->plain, plain_len); + strbuf_setlen(&s->colored, colored_len); + *hunk = backup; + + /* + * TRANSLATORS: do not translate [y/n] + * The program will only accept that input at this point. + * Consider translating (saying "no" discards!) as + * (saying "n" for "no" discards!) if the translation + * of the word "no" does not start with n. + */ + res = prompt_yesno(s, _("Your edited hunk does not apply. " + "Edit again (saying \"no\" discards!) " + "[y/n]? ")); + if (res < 1) + return -1; + } +} + static const char help_patch_text[] = N_("y - stage this hunk\n" "n - do not stage this hunk\n" @@ -677,6 +965,7 @@ N_("y - stage this hunk\n" "k - leave this hunk undecided, see previous undecided hunk\n" "K - leave this hunk undecided, see previous hunk\n" "s - split the current hunk into smaller hunks\n" + "e - manually edit the current hunk\n" "? - print help\n"); static int patch_update_file(struct add_p_state *s, @@ -735,6 +1024,9 @@ static int patch_update_file(struct add_p_state *s, strbuf_addstr(&s->buf, ",J"); if (hunk->splittable_into > 1) strbuf_addstr(&s->buf, ",s"); + if (hunk_index + 1 > file_diff->mode_change && + !file_diff->deleted) + strbuf_addstr(&s->buf, ",e"); if (file_diff->deleted) prompt_mode_type = PROMPT_DELETION; @@ -806,6 +1098,13 @@ static int patch_update_file(struct add_p_state *s, color_fprintf_ln(stdout, s->s.header_color, _("Split into %d hunks."), (int)splittable_into); + } else if (s->answer.buf[0] == 'e') { + if (hunk_index + 1 == file_diff->mode_change) + err(s, _("Sorry, cannot edit this hunk")); + else if (edit_hunk_loop(s, file_diff, hunk) >= 0) { + hunk->use = USE_HUNK; + goto soft_increment; + } } else color_fprintf(stdout, s->s.help_color, _(help_patch_text)); @@ -819,7 +1118,7 @@ static int patch_update_file(struct add_p_state *s, if (i < file_diff->hunk_nr) { /* At least one hunk selected: apply */ strbuf_reset(&s->buf); - reassemble_patch(s, file_diff, &s->buf); + reassemble_patch(s, file_diff, 0, &s->buf); discard_index(s->s.r->index); setup_child_process(s, &cp, "apply", "--cached", NULL); From patchwork Fri Dec 13 08:08:02 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290121 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 9FD6D930 for ; Fri, 13 Dec 2019 08:08:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 74FE324658 for ; Fri, 13 Dec 2019 08:08:27 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="O176a/gm" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726779AbfLMII0 (ORCPT ); Fri, 13 Dec 2019 03:08:26 -0500 Received: from mail-wm1-f67.google.com ([209.85.128.67]:54397 "EHLO mail-wm1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726752AbfLMIIV (ORCPT ); Fri, 13 Dec 2019 03:08:21 -0500 Received: by mail-wm1-f67.google.com with SMTP id b19so1071039wmj.4 for ; Fri, 13 Dec 2019 00:08:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=Z1rw34g52yejBNfZuXNfegd9xSsPbQsKJODHVlJOziY=; b=O176a/gmjDkDk6WVhji16PTHT2X2brURcMbQL1rfJTMlz7AM/9KsvEbricrOt6WUGV NmURnxCjZQJO4VnMaQtOeo4W0dxT4sKZ6GmDbPqoCgDszJgnjOxADgMZksyxGDYD/xt/ AXiSMAcpo9rU+7FyGgwPPbYM6/pE9QJVII2snNs423q3RrkuGgUrHFT015oAr5y2JDSt xNNYNCwFTbRJle4HnlfLhjuUdcXebZ2pTVVWHkxOUnqLG7mnbwcIexN9fU8JS6w88p+a FzeKOK3nDShjzyNNHX68/Nj8XP39axVXLHbZVgkeFZq3nsM06kHJt07E4I4W+kQyrm2H FtyA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=Z1rw34g52yejBNfZuXNfegd9xSsPbQsKJODHVlJOziY=; b=nH1UBAiKKpyEXvVQRj1o5NENTAmBJiq30pap2wz281PlvUdrveZa5w8zwdyymTw1Fa jYaMrbKl+LKM6cHYtMLubDOGqVrY5MhHWU1IC+quGzl0/AA36zvXaDqfDa/ULTW/eGA0 blEuZiJETOhmey9+sUs/XXKzAxQ/pDDSIKrQAW/eTGEkLIk5WF6OK2U+I/vKEGYDAsX9 KjDBtuawD0cBO/rvF5iD3yh+xkG/ebmsD0HETjEa0IOGCIf0d9wkKB0VOatkEJpRK2fa 5aWK/QdTc0w77TfP83DhSleDU3apIi5yT/q1oQ4fH4H2S8v5HZ10LJueRCLQDDlYh0gB znsw== X-Gm-Message-State: APjAAAVMEupMxQQAsKJvZx7zzQUwFXWnEfn8w1xmJqNmZycs8cg87v1j buSKDWtVCuXpnJrsEfxz+UTxKQrl X-Google-Smtp-Source: APXvYqxmoY+kR7Bs4FLGMYriDwFm7zJb2RLN2bT1sSFWiHneyb8UH9QQDrQTz7t63I+el3NpKFzUow== X-Received: by 2002:a05:600c:2c13:: with SMTP id q19mr11300457wmg.144.1576224499877; Fri, 13 Dec 2019 00:08:19 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id f16sm9253527wrm.65.2019.12.13.00.08.19 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:19 -0800 (PST) Message-Id: <9a2cf7ed71fe012de93a5d23b72c9c3aa0bdcdf9.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:08:02 +0000 Subject: [PATCH 15/19] built-in add -p: implement the 'g' ("goto") command Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin With this patch, it is now possible to see a summary of the available hunks and to navigate between them (by number). A test is added to verify that this behavior matches the one of the Perl version of `git add -p`. Signed-off-by: Johannes Schindelin --- add-patch.c | 88 ++++++++++++++++++++++++++++++++++++++ t/t3701-add-interactive.sh | 16 +++++++ 2 files changed, 104 insertions(+) diff --git a/add-patch.c b/add-patch.c index ea863ca09d..fdbb1e3e22 100644 --- a/add-patch.c +++ b/add-patch.c @@ -955,6 +955,54 @@ static int edit_hunk_loop(struct add_p_state *s, } } +#define SUMMARY_HEADER_WIDTH 20 +#define SUMMARY_LINE_WIDTH 80 +static void summarize_hunk(struct add_p_state *s, struct hunk *hunk, + struct strbuf *out) +{ + struct hunk_header *header = &hunk->header; + struct strbuf *plain = &s->plain; + size_t len = out->len, i; + + strbuf_addf(out, " -%lu,%lu +%lu,%lu ", + header->old_offset, header->old_count, + header->new_offset, header->new_count); + if (out->len - len < SUMMARY_HEADER_WIDTH) + strbuf_addchars(out, ' ', + SUMMARY_HEADER_WIDTH + len - out->len); + for (i = hunk->start; i < hunk->end; i = find_next_line(plain, i)) + if (plain->buf[i] != ' ') + break; + if (i < hunk->end) + strbuf_add(out, plain->buf + i, find_next_line(plain, i) - i); + if (out->len - len > SUMMARY_LINE_WIDTH) + strbuf_setlen(out, len + SUMMARY_LINE_WIDTH); + strbuf_complete_line(out); +} + +#define DISPLAY_HUNKS_LINES 20 +static size_t display_hunks(struct add_p_state *s, + struct file_diff *file_diff, size_t start_index) +{ + size_t end_index = start_index + DISPLAY_HUNKS_LINES; + + if (end_index > file_diff->hunk_nr) + end_index = file_diff->hunk_nr; + + while (start_index < end_index) { + struct hunk *hunk = file_diff->hunk + start_index++; + + strbuf_reset(&s->buf); + strbuf_addf(&s->buf, "%c%2d: ", hunk->use == USE_HUNK ? '+' + : hunk->use == SKIP_HUNK ? '-' : ' ', + (int)start_index); + summarize_hunk(s, hunk, &s->buf); + fputs(s->buf.buf, stdout); + } + + return end_index; +} + static const char help_patch_text[] = N_("y - stage this hunk\n" "n - do not stage this hunk\n" @@ -964,6 +1012,7 @@ N_("y - stage this hunk\n" "J - leave this hunk undecided, see next hunk\n" "k - leave this hunk undecided, see previous undecided hunk\n" "K - leave this hunk undecided, see previous hunk\n" + "g - select a hunk to go to\n" "s - split the current hunk into smaller hunks\n" "e - manually edit the current hunk\n" "? - print help\n"); @@ -1022,6 +1071,8 @@ static int patch_update_file(struct add_p_state *s, strbuf_addstr(&s->buf, ",j"); if (hunk_index + 1 < file_diff->hunk_nr) strbuf_addstr(&s->buf, ",J"); + if (file_diff->hunk_nr > 1) + strbuf_addstr(&s->buf, ",g"); if (hunk->splittable_into > 1) strbuf_addstr(&s->buf, ",s"); if (hunk_index + 1 > file_diff->mode_change && @@ -1089,6 +1140,43 @@ static int patch_update_file(struct add_p_state *s, hunk_index = undecided_next; else err(s, _("No next hunk")); + } else if (s->answer.buf[0] == 'g') { + char *pend; + unsigned long response; + + if (file_diff->hunk_nr < 2) { + err(s, _("No other hunks to goto")); + continue; + } + strbuf_remove(&s->answer, 0, 1); + strbuf_trim(&s->answer); + i = hunk_index - DISPLAY_HUNKS_LINES / 2; + if (i < file_diff->mode_change) + i = file_diff->mode_change; + while (s->answer.len == 0) { + i = display_hunks(s, file_diff, i); + printf("%s", i < file_diff->hunk_nr ? + _("go to which hunk ( to see " + "more)? ") : _("go to which hunk? ")); + fflush(stdout); + if (strbuf_getline(&s->answer, + stdin) == EOF) + break; + strbuf_trim_trailing_newline(&s->answer); + } + + strbuf_trim(&s->answer); + response = strtoul(s->answer.buf, &pend, 10); + if (*pend || pend == s->answer.buf) + err(s, _("Invalid number: '%s'"), + s->answer.buf); + else if (0 < response && response <= file_diff->hunk_nr) + hunk_index = response - 1; + else + err(s, Q_("Sorry, only %d hunk available.", + "Sorry, only %d hunks available.", + file_diff->hunk_nr), + (int)file_diff->hunk_nr); } else if (s->answer.buf[0] == 's') { size_t splittable_into = hunk->splittable_into; if (splittable_into < 2) diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index fe383be50e..57c656a20c 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -413,6 +413,22 @@ test_expect_success 'split hunk setup' ' test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test ' +test_expect_success 'goto hunk' ' + test_when_finished "git reset" && + tr _ " " >expect <<-EOF && + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1: -1,2 +1,3 +15 + _ 2: -2,4 +3,8 +21 + go to which hunk? @@ -1,2 +1,3 @@ + _10 + +15 + _20 + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_ + EOF + test_write_lines s y g 1 | git add -p >actual && + tail -n 7 actual.trimmed && + test_cmp expect actual.trimmed +' + test_expect_success 'split hunk "add -p (edit)"' ' # Split, say Edit and do nothing. Then: # From patchwork Fri Dec 13 08:08:03 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290131 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 0CF13930 for ; Fri, 13 Dec 2019 08:08:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id D63AB2465A for ; Fri, 13 Dec 2019 08:08:38 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="TrK8Zsnl" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726823AbfLMIIh (ORCPT ); Fri, 13 Dec 2019 03:08:37 -0500 Received: from mail-wr1-f67.google.com ([209.85.221.67]:34374 "EHLO mail-wr1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726759AbfLMIIX (ORCPT ); Fri, 13 Dec 2019 03:08:23 -0500 Received: by mail-wr1-f67.google.com with SMTP id t2so5625570wrr.1 for ; Fri, 13 Dec 2019 00:08:21 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=aNXrykXjaip7Gt3NJPyE79aV54iuwbi/BvyEmKiedOg=; b=TrK8Zsnl98bdbLolTjXcxpGW0RCZTLv3FDHOF+BPzlScfDJWsj10pSVPUVeT8eSiIb uvA+q+UGwvbt4BNMchTdYTxo2JnGpqz3cMrcyEqJA2OS7+QrcFhu9IZTRAyge7ra0jfN /u2x0PUngU5RFSNKStFf+ZCYIjVaYPo09hZdTjvGNvM4rAnNi242KlNGc8lABc7MtEvi ewcRq0ayf1yUPK9udxWy9T+HQ6MdNBKWtdfhIc70ZiCLYkUixR+FEe74SNiHJwSeLYXh 4IYhAiAClvGfS2xXNJTHZmdE/iX91fMZUqa+DOQD52/6WgA0kDhKzyRK4KsHEJZ+V7eN yvBg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=aNXrykXjaip7Gt3NJPyE79aV54iuwbi/BvyEmKiedOg=; b=qXcNMY4T0CSfQGBImfbvAkxI+AxnyQCOmHYeqI2A+gdpwi49lguOoNPYt2ya1rzL4S 39hnZFqo45cJVyvwcOxNip3KlLWvCP533+b30Weun+rpvYcHiUqWe18YmjczFPdCsbvH M85v9vbyQ4sKfFtKrACPNk0fuNJ1wYTDCpXZRGfP5Z0G4ELeth90pc9+kY6j659KU2Dt K/ocZDW3KyNYTB8yjlllB5JEqIU6gC5uBBs/AN8j3SaysMzqpR5OkGAeWjJ057LsA1hH LA5xeO/xOUbE1n79pgGII82EljvdvlxEquHc/TtbmYJlrwWDvT7+VaaprAjWYkqumony awIA== X-Gm-Message-State: APjAAAVX8B9hCeYDdBqRojCKk5uuzbkYHQDaybh/eRQ04nX9B6IdXTxJ HKfGtxYUdsd/o8MxdH/7BbLPce6x X-Google-Smtp-Source: APXvYqw+kkd34NaDxO96rJJZKL76YlSrwcTLvpL8b0/IOOldPlWykIHbRVQQRkA1whxWOsj4BeR9Pg== X-Received: by 2002:a05:6000:12c9:: with SMTP id l9mr11769915wrx.304.1576224500638; Fri, 13 Dec 2019 00:08:20 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id a20sm9600028wmd.19.2019.12.13.00.08.20 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:20 -0800 (PST) Message-Id: In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:08:03 +0000 Subject: [PATCH 16/19] built-in add -p: implement the '/' ("search regex") command Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin This patch implements the hunk searching feature in the C version of `git add -p`. A test is added to verify that this behavior matches the one of the Perl version of `git add -p`. Note that this involves a change of behavior: the Perl version uses (of course) the Perl flavor of regular expressions, while this patch uses the regcomp()/regexec(), i.e. POSIX extended regular expressions. In practice, this behavior change is unlikely to matter. Signed-off-by: Johannes Schindelin --- add-patch.c | 50 +++++++++++++++++++++++++++++++++++++- t/t3701-add-interactive.sh | 14 +++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/add-patch.c b/add-patch.c index fdbb1e3e22..fd72850c65 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1013,6 +1013,7 @@ N_("y - stage this hunk\n" "k - leave this hunk undecided, see previous undecided hunk\n" "K - leave this hunk undecided, see previous hunk\n" "g - select a hunk to go to\n" + "/ - search for a hunk matching the given regex\n" "s - split the current hunk into smaller hunks\n" "e - manually edit the current hunk\n" "? - print help\n"); @@ -1072,7 +1073,7 @@ static int patch_update_file(struct add_p_state *s, if (hunk_index + 1 < file_diff->hunk_nr) strbuf_addstr(&s->buf, ",J"); if (file_diff->hunk_nr > 1) - strbuf_addstr(&s->buf, ",g"); + strbuf_addstr(&s->buf, ",g,/"); if (hunk->splittable_into > 1) strbuf_addstr(&s->buf, ",s"); if (hunk_index + 1 > file_diff->mode_change && @@ -1177,6 +1178,53 @@ static int patch_update_file(struct add_p_state *s, "Sorry, only %d hunks available.", file_diff->hunk_nr), (int)file_diff->hunk_nr); + } else if (s->answer.buf[0] == '/') { + regex_t regex; + int ret; + + if (file_diff->hunk_nr < 2) { + err(s, _("No other hunks to search")); + continue; + } + strbuf_remove(&s->answer, 0, 1); + strbuf_trim_trailing_newline(&s->answer); + if (s->answer.len == 0) { + printf("%s", _("search for regex? ")); + fflush(stdout); + if (strbuf_getline(&s->answer, + stdin) == EOF) + break; + strbuf_trim_trailing_newline(&s->answer); + if (s->answer.len == 0) + continue; + } + ret = regcomp(®ex, s->answer.buf, + REG_EXTENDED | REG_NOSUB | REG_NEWLINE); + if (ret) { + char errbuf[1024]; + + regerror(ret, ®ex, errbuf, sizeof(errbuf)); + err(s, _("Malformed search regexp %s: %s"), + s->answer.buf, errbuf); + continue; + } + i = hunk_index; + for (;;) { + /* render the hunk into a scratch buffer */ + render_hunk(s, file_diff->hunk + i, 0, 0, + &s->buf); + if (regexec(®ex, s->buf.buf, 0, NULL, 0) + != REG_NOMATCH) + break; + i++; + if (i == file_diff->hunk_nr) + i = 0; + if (i != hunk_index) + continue; + err(s, _("No hunk matches the given pattern")); + break; + } + hunk_index = i; } else if (s->answer.buf[0] == 's') { size_t splittable_into = hunk->splittable_into; if (splittable_into < 2) diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 57c656a20c..12ee321707 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -429,6 +429,20 @@ test_expect_success 'goto hunk' ' test_cmp expect actual.trimmed ' +test_expect_success 'navigate to hunk via regex' ' + test_when_finished "git reset" && + tr _ " " >expect <<-EOF && + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,2 +1,3 @@ + _10 + +15 + _20 + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_ + EOF + test_write_lines s y /1,2 | git add -p >actual && + tail -n 5 actual.trimmed && + test_cmp expect actual.trimmed +' + test_expect_success 'split hunk "add -p (edit)"' ' # Split, say Edit and do nothing. Then: # From patchwork Fri Dec 13 08:08:04 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290133 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5C53D14BD for ; Fri, 13 Dec 2019 08:08:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 3B83D24658 for ; Fri, 13 Dec 2019 08:08:39 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="J+LDbozH" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726825AbfLMIIh (ORCPT ); Fri, 13 Dec 2019 03:08:37 -0500 Received: from mail-wm1-f66.google.com ([209.85.128.66]:34232 "EHLO mail-wm1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726765AbfLMIIX (ORCPT ); Fri, 13 Dec 2019 03:08:23 -0500 Received: by mail-wm1-f66.google.com with SMTP id f4so5381320wmj.1 for ; Fri, 13 Dec 2019 00:08:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=9ZaireUYGoeKPqHNrCW7PIQGFdgOF8wXarhdIAoI8HY=; b=J+LDbozH1WJw6Q+qO+kgOsjfgmc30cuVSsUhmfqZG2RoUHYJOCHG/LGvFjgWOfs+od nbyw3Y0+a3ibluQFfkM14YRle1FWRqSkTRj+woK35kW96SY/ctyObFbd3buk81+/xnc8 C/8aIkJeVV4HStrp7MeaCZ89y+JVBG1MQnO1bHKje8efLx0tq0zk1ALY25jSNGo4bwye 9XRHVipaAnI9QXpcLs+2UTg99cLppp/ZSqQzXI406IxrlOatcgX9F+OjjbIYJbT/lNh4 g1YaySBx9fB7zkg35FX36tJzROs8p1xbyzwZg5g/jlJPnsaxAjoI/qc6X19xtI23zRPw FV/g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=9ZaireUYGoeKPqHNrCW7PIQGFdgOF8wXarhdIAoI8HY=; b=Dqt1Qecdc1MFPpDZJV64TJYbNbeUvQWaSkj/hgqDMAAaGSpZr946orLmCRfZX0Chd7 k/nf4+SbqmRHa5CJBYQrdotqbLHp/NeEefVGzWFkaAbzeMBWFGicEmD14WTTVvhLK2jh ZzSwa1B9NNLWFAnQkHVzLBU63GbSCPYHff1X3pgTm0kAVPjAUTK1gmPcekLMszk+/zhN qQ3lsFi4JaOKnLhPQtpsEKQFo/7tgcY4VtH+yYRtM2SnC77YqKwC3zUQcJ67LknTvJv6 hVVtRWTS1d3/nIFbRakHmBUrK1IY1JPuVgYPhsAjU6G0uRlfGS0HmA96r5lkLaEuddq7 mE5Q== X-Gm-Message-State: APjAAAX7K5NGiQLb9QzwHRL+oQcHFjt1QcIeY5rVjTbQu0UozJ4Fd/Vi s6gowW5X6dOKuDUuqhWUugYiEEeO X-Google-Smtp-Source: APXvYqwTvQnoyQdYQo8TVe/HfUl6b3VQqNoI2KEdfaYsrS2sY9Isko8BWw1rogZzGOOeAuIpvPD68A== X-Received: by 2002:a1c:cc11:: with SMTP id h17mr9151699wmb.19.1576224501478; Fri, 13 Dec 2019 00:08:21 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id k16sm9461531wru.0.2019.12.13.00.08.20 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:20 -0800 (PST) Message-Id: <6c33fbb68453707c874fde14347ce368b555c449.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:08:04 +0000 Subject: [PATCH 17/19] built-in add -p: implement the 'q' ("quit") command Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin This command is actually very similar to the 'd' ("do not stage this hunk or any of the later hunks in the file") command: it just does something on top, namely leave the loop and return a value indicating that we're quittin'. Signed-off-by: Johannes Schindelin --- add-patch.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/add-patch.c b/add-patch.c index fd72850c65..5e9829a8b4 100644 --- a/add-patch.c +++ b/add-patch.c @@ -12,9 +12,9 @@ enum prompt_mode_type { }; static const char *prompt_mode[] = { - N_("Stage mode change [y,n,a,d%s,?]? "), - N_("Stage deletion [y,n,a,d%s,?]? "), - N_("Stage this hunk [y,n,a,d%s,?]? ") + N_("Stage mode change [y,n,a,q,d%s,?]? "), + N_("Stage deletion [y,n,a,q,d%s,?]? "), + N_("Stage this hunk [y,n,a,q,d%s,?]? ") }; struct hunk_header { @@ -1006,6 +1006,7 @@ static size_t display_hunks(struct add_p_state *s, static const char help_patch_text[] = N_("y - stage this hunk\n" "n - do not stage this hunk\n" + "q - quit; do not stage this hunk or any of the remaining ones\n" "a - stage this and all the remaining hunks\n" "d - do not stage this hunk nor any of the remaining hunks\n" "j - leave this hunk undecided, see next undecided hunk\n" @@ -1026,7 +1027,7 @@ static int patch_update_file(struct add_p_state *s, struct hunk *hunk; char ch; struct child_process cp = CHILD_PROCESS_INIT; - int colored = !!s->colored.len; + int colored = !!s->colored.len, quit = 0; enum prompt_mode_type prompt_mode_type; if (!file_diff->hunk_nr) @@ -1115,12 +1116,16 @@ static int patch_update_file(struct add_p_state *s, if (hunk->use == UNDECIDED_HUNK) hunk->use = USE_HUNK; } - } else if (ch == 'd') { + } else if (ch == 'd' || ch == 'q') { for (; hunk_index < file_diff->hunk_nr; hunk_index++) { hunk = file_diff->hunk + hunk_index; if (hunk->use == UNDECIDED_HUNK) hunk->use = SKIP_HUNK; } + if (ch == 'q') { + quit = 1; + break; + } } else if (s->answer.buf[0] == 'K') { if (hunk_index) hunk_index--; @@ -1267,7 +1272,7 @@ static int patch_update_file(struct add_p_state *s, } putchar('\n'); - return 0; + return quit; } int run_add_p(struct repository *r, const struct pathspec *ps) From patchwork Fri Dec 13 08:08:05 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290129 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 0BF6B930 for ; Fri, 13 Dec 2019 08:08:32 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id DF09D24656 for ; Fri, 13 Dec 2019 08:08:31 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Eie6R3hy" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726797AbfLMIIa (ORCPT ); Fri, 13 Dec 2019 03:08:30 -0500 Received: from mail-wm1-f68.google.com ([209.85.128.68]:53852 "EHLO mail-wm1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726769AbfLMIIY (ORCPT ); Fri, 13 Dec 2019 03:08:24 -0500 Received: by mail-wm1-f68.google.com with SMTP id w8so2041571wmd.3 for ; Fri, 13 Dec 2019 00:08:23 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=+6YxCnqxGRVtpNgz0NtAVbo5UDhh4Edtk1YCNEAvYcg=; b=Eie6R3hycoe0qYQt1/VfhUEDit9U/ft4JQNcwui2iHxKDT9lK3RjpCb65rgg7jyrB4 wutIQlNP8ZTeN43HYQykoflyThQTz5JAurNkor3p115yrzs9QMqrkXmHv+13EtTvMANt OhfOyTNWRLiD1L8KJrIHgbn0oasDmrwypK1jsUqjhILRoQ8Z4TrzgQ7tMlqbTJYx9BRy FOl49XxUUQFpNa+9vwL1jsMlrsiOY3yBu0KKBcTqFPVSmW/3+wtJX16lg7WacSNW3Spk w/TXutgoV3LqYuQ5jEFXbWIPeb7GP/+mVM0oBKUCrDHYs4V9Lt15+Nc78vSrV9xWh10e VXDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=+6YxCnqxGRVtpNgz0NtAVbo5UDhh4Edtk1YCNEAvYcg=; b=syaBwcx4g4lx6EGrNU3Y1vOU6j67XE+Kni5J/I0AnIS4t0XikT9IT78EwoZf9gBBEN F2ujPzKYkf73LavkcLdjr6y3G9xGOfvmIOG5CQe37IBV++V5OZB/rsAiQkNfnE2AVBgH 2m2FuJ+DVuD2qhdPTi9I7XqUdIlPDXGY/032F21wmbi62T4kZRnxrkZdb9UVJZ7wEgN5 Nk1meVKsmjLqoxkntd1kBWGA180aQC+RHd4LiPCVTHOWe6xIzMHvFkUz0fJkK5B6nJdx m4jAhKOWuVNUcRydHhaEn5GpHXcNoJDr84urKir1hN0jqAodLxPbacGhJSt2lPFGXbAu TbJA== X-Gm-Message-State: APjAAAWfBMns1rRgku/PvbNwjXXvkcDwMx/jT1KqBeKwjFMaEQ+DW0bT uo1om8zYGm0R5kkg3x9em0U1O/VM X-Google-Smtp-Source: APXvYqyAzA0ugcAEcKltry74VOQegqJExO/egV9s8TDEt8aXcjTQVCtzQwkjoeEJoQMbDGjhLEBtnw== X-Received: by 2002:a7b:c5d8:: with SMTP id n24mr11393153wmk.124.1576224502164; Fri, 13 Dec 2019 00:08:22 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id f5sm9226206wmh.12.2019.12.13.00.08.21 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:21 -0800 (PST) Message-Id: <0c95067fb3aba3c13afe0976d52cb092903d3a9f.1576224486.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:08:05 +0000 Subject: [PATCH 18/19] built-in add -p: only show the applicable parts of the help text Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin When displaying the only hunk in a file's diff, the prompt already excludes the commands to navigate to the previous/next hunk. Let's also let the `?` command show only the help lines corresponding to the commands that are displayed in the prompt. Signed-off-by: Johannes Schindelin --- add-patch.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/add-patch.c b/add-patch.c index 5e9829a8b4..1eb0ab97bb 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1008,8 +1008,10 @@ N_("y - stage this hunk\n" "n - do not stage this hunk\n" "q - quit; do not stage this hunk or any of the remaining ones\n" "a - stage this and all the remaining hunks\n" - "d - do not stage this hunk nor any of the remaining hunks\n" - "j - leave this hunk undecided, see next undecided hunk\n" + "d - do not stage this hunk nor any of the remaining hunks\n"); + +static const char help_patch_remainder[] = +N_("j - leave this hunk undecided, see next undecided hunk\n" "J - leave this hunk undecided, see next hunk\n" "k - leave this hunk undecided, see previous undecided hunk\n" "K - leave this hunk undecided, see previous hunk\n" @@ -1246,9 +1248,31 @@ static int patch_update_file(struct add_p_state *s, hunk->use = USE_HUNK; goto soft_increment; } - } else - color_fprintf(stdout, s->s.help_color, + } else { + const char *p = _(help_patch_remainder), *eol = p; + + color_fprintf(stdout, s->s.help_color, "%s", _(help_patch_text)); + + /* + * Show only those lines of the remainder that are + * actually applicable with the current hunk. + */ + for (; *p; p = eol + (*eol == '\n')) { + eol = strchrnul(p, '\n'); + + /* + * `s->buf` still contains the part of the + * commands shown in the prompt that are not + * always available. + */ + if (*p != '?' && !strchr(s->buf.buf, *p)) + continue; + + color_fprintf_ln(stdout, s->s.help_color, + "%.*s", (int)(eol - p), p); + } + } } /* Any hunk to be used? */ From patchwork Fri Dec 13 08:08:06 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11290127 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 02316930 for ; Fri, 13 Dec 2019 08:08:31 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id D4E4124656 for ; Fri, 13 Dec 2019 08:08:30 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="SU2jN1ql" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726792AbfLMII3 (ORCPT ); Fri, 13 Dec 2019 03:08:29 -0500 Received: from mail-wr1-f66.google.com ([209.85.221.66]:39746 "EHLO mail-wr1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726741AbfLMIIY (ORCPT ); Fri, 13 Dec 2019 03:08:24 -0500 Received: by mail-wr1-f66.google.com with SMTP id y11so5602091wrt.6 for ; Fri, 13 Dec 2019 00:08:23 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=taGbAjju/CWGhORJ2etym7NKiySSVYEwpdGKW5i+xAY=; b=SU2jN1ql2OT6kjEbSuxy7+ahdCL0pHWPhdzh2nv0kRPQUgY4Q1axYD5EQWyTqoIXS9 +J2hmRwW5SDffNHZzZrfGG8j/cVI16eBa1DHblTih5z2cYwP2bg26QzEmWohdSaKDc1h 0tHzLt0QVw/Eg3kxfBZF7cxcydVnRp/W76AjP6GRvHw06+/Aau8Zzp9HBO1WBLRP7Zg+ QsNdjcLJobMJ8CwK2PEbShsBHTCufqmP4PBYuF6eSJj7wb3By6S3iN6J9Mf5FiQhXmAB aCBupQmGP8HOob6cDtuOXATJVm3+MG21nCP7FumERwhdMsL6mB6eafu1TxQzDRU0VNms 2z1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=taGbAjju/CWGhORJ2etym7NKiySSVYEwpdGKW5i+xAY=; b=MRRmchEmNPeSOFYYAQoSk9UKM5HNLI11zuwYFuJ3Z7IYIQWVwKWEW5SN8uAN/LWOEW UYfu2G3ngUifpAyyyPG8Jw84TE8MN2ozEMekSFWV5ZB+kB1cAAeho+BPD1fMqSYD8AJD HbPijJNFpLtw6hv6domarRpu14cm5zWEc5fj+ggXgpEzzHDDPeCLWCvNJac6R3NaYLb4 jTPu8ppReMYBkPiC3fhPBJfuZDL33/y9FofWK5gb4GAv8rafqcB14YuU2sXUeDHzH3no kji4GUmSXW7G5c38sVAshW1UqHAsMQDp2TVomOLjf1FYGJGaAkGIzJDAsOSFayi02SA+ Qf8g== X-Gm-Message-State: APjAAAWho44p6154t0xtgDI+jinTvGc6qy2L7rO2jCrw8ogDvJclzIDf y/9nd21551RnMmaCjo75TXwDJ2IF X-Google-Smtp-Source: APXvYqxf3zbWpju35pyqWad48OgypOC2jMKZUe6kuyGyXJf3H9JTR8YjIDdtIX+VpImTiukrbwhSRQ== X-Received: by 2002:adf:ef8b:: with SMTP id d11mr10735997wro.45.1576224502948; Fri, 13 Dec 2019 00:08:22 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id f1sm9389745wml.11.2019.12.13.00.08.22 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 13 Dec 2019 00:08:22 -0800 (PST) Message-Id: In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Fri, 13 Dec 2019 08:08:06 +0000 Subject: [PATCH 19/19] built-in add -p: show helpful hint when nothing can be staged Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Johannes Schindelin , Junio C Hamano , Johannes Schindelin Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Johannes Schindelin This patch will make `git add -p` show "No changes." or "Only binary files changed." in that case. Signed-off-by: Johannes Schindelin --- add-patch.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/add-patch.c b/add-patch.c index 1eb0ab97bb..2c46fe5b33 100644 --- a/add-patch.c +++ b/add-patch.c @@ -44,7 +44,7 @@ struct add_p_state { struct hunk head; struct hunk *hunk; size_t hunk_nr, hunk_alloc; - unsigned deleted:1, mode_change:1; + unsigned deleted:1, mode_change:1,binary:1; } *file_diff; size_t file_diff_nr; }; @@ -294,7 +294,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) BUG("'new mode' does not immediately follow " "'old mode'?\n\n%.*s", (int)(eol - plain->buf), plain->buf); - } + } else if (hunk == &file_diff->head && + starts_with(p, "Binary files ")) + file_diff->binary = 1; if (file_diff->deleted && file_diff->mode_change) BUG("diff contains delete *and* a mode change?!?\n%.*s", @@ -1304,7 +1306,7 @@ int run_add_p(struct repository *r, const struct pathspec *ps) struct add_p_state s = { { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; - size_t i; + size_t i, binary_count = 0; init_add_i_state(&s.s, r); @@ -1318,9 +1320,16 @@ int run_add_p(struct repository *r, const struct pathspec *ps) } for (i = 0; i < s.file_diff_nr; i++) - if (patch_update_file(&s, s.file_diff + i)) + if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) + binary_count++; + else if (patch_update_file(&s, s.file_diff + i)) break; + if (s.file_diff_nr == 0) + fprintf(stderr, _("No changes.\n")); + else if (binary_count == s.file_diff_nr) + fprintf(stderr, _("Only binary files changed.\n")); + strbuf_release(&s.answer); strbuf_release(&s.buf); strbuf_release(&s.plain);