From patchwork Mon Nov 4 12:15:21 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: 11225673 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 ACC031515 for ; Mon, 4 Nov 2019 12:15:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 7D9C021D81 for ; Mon, 4 Nov 2019 12:15:36 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="lKiXWdy2" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728346AbfKDMPf (ORCPT ); Mon, 4 Nov 2019 07:15:35 -0500 Received: from mail-wm1-f65.google.com ([209.85.128.65]:51482 "EHLO mail-wm1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727322AbfKDMPf (ORCPT ); Mon, 4 Nov 2019 07:15:35 -0500 Received: by mail-wm1-f65.google.com with SMTP id q70so16395557wme.1 for ; Mon, 04 Nov 2019 04:15:33 -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=SfmLn9REjipp/wD4CfiKk04OVkxMAOT+kBo/By0k19E=; b=lKiXWdy2s0cDh5OBy+Tsk8PyIoNCjGVkwudRzYXnXLYd9K2wDl2TL6q0Xp5Tlp1jQv DsBzVrsNghI9ny6nxyfCdZwCPr0qvmEW4RFX3PTv6rup7cq1drRaCYMp2Xa6JyQveRxi fMrwxZddzM0KMSDiGYwQPduzSgW7ED21HdikuJUH2PL5l//gJbHM64iIHX3f+1Wl7TCI Zr9MuQuo+L/o0zTU2fKNNcs/nxWXSic0MVeGmsSpsEJMGvK3RCOFtdQ0ktayCVeMiUZ7 aWfW6PvvYWwltyFWNoshNLMr0tuMz/w11XcHnzTDntHnDPGLVYJ5tfNKw9oPiCK/sIAc o1aw== 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=SfmLn9REjipp/wD4CfiKk04OVkxMAOT+kBo/By0k19E=; b=cdG81aUQfrv8otpH4zL4OGv5qi9ODI4WaNfACY00mkUeDGppfWphUQ7WpMvftzzjFa gqgzCIihnpvsww1zT6X6iJ14eOIEDZLcPExMXEgBH0OHWMmlTmoilr73Qh6Sx4kW0BQf Gv9OnX+/bUbMpUnDOidP4niDgVKrxj9y5yz/U5UKraHgPxXvS5fRGkAqHP1wC2ug0rCQ x3ef1DlPa+5T/kJl6xIVjdZ4Tdk4LR9omg2QTsANaI22GQaJrxeFJ4ii1kURUfcX4KEM Z9tgQ1Q9fvn2IlTC/vj87FEfTs4dTIeK/j5cAuNSqpq2UStp/GSgJRiWpPadyOPgczQG hYnA== X-Gm-Message-State: APjAAAWYnrItMa7hmsO2z+4MTIbOMFqOWv3oSXGAKRbHjjMFAwlEUU0z LZyp/AkJuz4XhJLcskSrpBtIrsj6 X-Google-Smtp-Source: APXvYqyEZQDGGy09wgThlGQ3lRutCQ4wr6asQ3ZJc6whoiqqWlzs7My0joGAz6CDRlgI8Czu7CK2IQ== X-Received: by 2002:a1c:410a:: with SMTP id o10mr22665679wma.117.1572869732571; Mon, 04 Nov 2019 04:15:32 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id p14sm6671690wrq.72.2019.11.04.04.15.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:32 -0800 (PST) Message-Id: In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:21 +0000 Subject: [PATCH v5 1/9] Start to implement a built-in version of `git add --interactive` Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , 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 hardly the first conversion of a Git command that is implemented as a script to a built-in. So far, the most successful strategy for such conversions has been to add a built-in helper and call that for more and more functionality from the script, as more and more parts are converted. With the interactive add, we choose a different strategy. The sole reason for this is that on Windows (where such a conversion has the most benefits in terms of speed and robustness) we face the very specific problem that a `system()` call in Perl seems to close `stdin` in the parent process when the spawned process consumes even one character from `stdin`. And that just does not work for us here, as it would stop the main loop as soon as any interactive command was performed by the helper. Which is almost all of the commands in `git add -i`. It is almost as if Perl told us once again that it does not want us to use it on Windows. Instead, we follow the opposite route where we start with a bare-bones version of the built-in interactive add, guarded by the new `add.interactive.useBuiltin` config variable, and then add more and more functionality to it, until it is feature complete. At this point, the built-in version of `git add -i` only states that it cannot do anything yet ;-) Signed-off-by: Johannes Schindelin --- Documentation/config/add.txt | 5 +++++ Makefile | 1 + add-interactive.c | 7 +++++++ add-interactive.h | 8 ++++++++ builtin/add.c | 10 ++++++++++ t/README | 4 ++++ 6 files changed, 35 insertions(+) create mode 100644 add-interactive.c create mode 100644 add-interactive.h diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt index 4d753f006e..c9f748f81c 100644 --- a/Documentation/config/add.txt +++ b/Documentation/config/add.txt @@ -5,3 +5,8 @@ add.ignore-errors (deprecated):: option of linkgit:git-add[1]. `add.ignore-errors` is deprecated, as it does not follow the usual naming convention for configuration variables. + +add.interactive.useBuiltin:: + [EXPERIMENTAL] Set to `true` to use the experimental built-in + implementation of the interactive version of linkgit:git-add[1] + instead of the Perl script version. Is `false` by default. diff --git a/Makefile b/Makefile index 58b92af54b..6c4a1e0ee5 100644 --- a/Makefile +++ b/Makefile @@ -823,6 +823,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat -name '*.h' -print))) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o diff --git a/add-interactive.c b/add-interactive.c new file mode 100644 index 0000000000..482e458dc6 --- /dev/null +++ b/add-interactive.c @@ -0,0 +1,7 @@ +#include "cache.h" +#include "add-interactive.h" + +int run_add_i(struct repository *r, const struct pathspec *ps) +{ + die(_("No commands are available in the built-in `git add -i` yet!")); +} diff --git a/add-interactive.h b/add-interactive.h new file mode 100644 index 0000000000..7043b8741d --- /dev/null +++ b/add-interactive.h @@ -0,0 +1,8 @@ +#ifndef ADD_INTERACTIVE_H +#define ADD_INTERACTIVE_H + +struct repository; +struct pathspec; +int run_add_i(struct repository *r, const struct pathspec *ps); + +#endif diff --git a/builtin/add.c b/builtin/add.c index dd18e5c9b6..4f625691b5 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -20,6 +20,7 @@ #include "bulk-checkin.h" #include "argv-array.h" #include "submodule.h" +#include "add-interactive.h" static const char * const builtin_add_usage[] = { N_("git add [] [--] ..."), @@ -185,6 +186,14 @@ int run_add_interactive(const char *revision, const char *patch_mode, { int status, i; struct argv_array argv = ARGV_ARRAY_INIT; + int use_builtin_add_i = + git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); + if (use_builtin_add_i < 0) + git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i); + + if (use_builtin_add_i == 1 && !patch_mode) + return !!run_add_i(the_repository, pathspec); argv_array_push(&argv, "add--interactive"); if (patch_mode) @@ -319,6 +328,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors = git_config_bool(var, value); return 0; } + return git_default_config(var, value, cb); } diff --git a/t/README b/t/README index 60d5b77bcc..5132ec83f8 100644 --- a/t/README +++ b/t/README @@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=, when false, disables the built-in version of git-stash. See 'stash.useBuiltin' in git-config(1). +GIT_TEST_ADD_I_USE_BUILTIN=, when true, enables the +built-in version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS= enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the From patchwork Mon Nov 4 12:15:22 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11225679 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 726A51515 for ; Mon, 4 Nov 2019 12:15:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 4DD8B21D81 for ; Mon, 4 Nov 2019 12:15:39 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="B/TZTORi" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728648AbfKDMPi (ORCPT ); Mon, 4 Nov 2019 07:15:38 -0500 Received: from mail-wm1-f67.google.com ([209.85.128.67]:38159 "EHLO mail-wm1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727998AbfKDMPh (ORCPT ); Mon, 4 Nov 2019 07:15:37 -0500 Received: by mail-wm1-f67.google.com with SMTP id z19so11648450wmk.3 for ; Mon, 04 Nov 2019 04:15:34 -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:mime-version :content-transfer-encoding:fcc:to:cc; bh=rFMaUJqcvtNZv6SXcUG484Xgjg/pJ3HxwuEagPqFxzA=; b=B/TZTORip39O3Y1L6Cad+a/Ro28AAl31Q/Ee5xPmZ6TPQLxACSuYueQ1JNs2x6KwLn jfigja131stGxKkPWfDG5Uio5r46Q0Iu6FnEetRmLkokeo48LQaxnEk9IB3s3Q204sxC AElwlBwJtVmm70oItyedA9nzKHRjyWKCcOHeeB/X0VG7wroSdJGLiYTu1NyU6uzlVEZF rPvgcxfxMepKBuyZnhVsBMqpf0cpK1W9Mqmu0cuJ9PazhNVMCp6oMN96oGG9jGyQ9Ae1 x8LuKviuLNbo80oMa8NWof9VeHt96n+SoK3yx9pleqv3xDLCaJod/qJV5WLUbUlnqArx CA9w== 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:mime-version:content-transfer-encoding:fcc:to:cc; bh=rFMaUJqcvtNZv6SXcUG484Xgjg/pJ3HxwuEagPqFxzA=; b=W8Fc9LPDowUToTluaNbljX/E6QGVQYCtLXMhkdhp6X0w9MH0buhPNAxHGQD1tWjd3R Er54g+EqTolcP0P6Vdn7dQaPQQCra6QdQaGi3/P+5bXLxBfKlVli2vrV0r4faLO+NLpf RxPWuCKk9JC9e3gvtvDjk/RxqrQL4H4nRU2v17scAvvNO5IAgrxvlnh/mwkiBDZoPtb2 lRTMqwv8mdALoTLubIDXvsnqu11NP68m7N+4aIs/NxRFrFIpbi4wuK03fQ3MJWnvkk+2 4vEmnQ0EAZSrVlg+CyoQQA57D1KXazFHCdZ6Z686jOg8k42oWtSq24N4SXDFh6Ml1rQ0 dY6w== X-Gm-Message-State: APjAAAXNTnXfasgFlWv10HdoVwEsENT/tVr1oPg+GgLdkL5XmJ4s0Z9x FLTosg9LPeH+szp9eUiFyB+UAeyo X-Google-Smtp-Source: APXvYqw0y0uoujxtH1mFNROehe/waLC8CsCwfyv0hm43bZKt/RuEU8GjHZIE1tOKBM/PvVKXM8PnWQ== X-Received: by 2002:a05:600c:c5:: with SMTP id u5mr6146169wmm.35.1572869733718; Mon, 04 Nov 2019 04:15:33 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id s12sm18655649wme.20.2019.11.04.04.15.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:33 -0800 (PST) Message-Id: <2fc8cc354690841cd8496d9a7660faf572580aef.1572869730.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Daniel Ferreira via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:22 +0000 Subject: [PATCH v5 2/9] diff: export diffstat interface MIME-Version: 1.0 Fcc: Sent To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , Johannes Schindelin , Junio C Hamano , Daniel Ferreira Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Daniel Ferreira Make the diffstat interface (namely, the diffstat_t struct and compute_diffstat) no longer be internal to diff.c and allow it to be used by other parts of git. This is helpful for code that may want to easily extract information from files using the diff machinery, while flushing it differently from how the show_* functions used by diff_flush() do it. One example is the builtin implementation of git-add--interactive's status. Signed-off-by: Daniel Ferreira Signed-off-by: Slavica Đukić Signed-off-by: Johannes Schindelin --- diff.c | 39 ++++++++++++++++----------------------- diff.h | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/diff.c b/diff.c index afe4400a60..5703a9b78f 100644 --- a/diff.c +++ b/diff.c @@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { - int nr; - int alloc; - struct diffstat_file { - char *from_name; - char *name; - char *print_name; - const char *comments; - unsigned is_unmerged:1; - unsigned is_binary:1; - unsigned is_renamed:1; - unsigned is_interesting:1; - uintmax_t added, deleted; - } **files; -}; - static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, const char *name_a, const char *name_b) @@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o gather_dirstat(options, &dir, changed, "", 0); } -static void free_diffstat_info(struct diffstat_t *diffstat) +void free_diffstat_info(struct diffstat_t *diffstat) { int i; for (i = 0; i < diffstat->nr; i++) { @@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) { struct diffstat_t diffstat; - memset(&diffstat, 0, sizeof(struct diffstat_t)); - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (check_pair_status(p)) - diff_flush_stat(p, options, &diffstat); - } + compute_diffstat(options, &diffstat, q); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) @@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options) return ignored; } +void compute_diffstat(struct diff_options *options, + struct diffstat_t *diffstat, + struct diff_queue_struct *q) +{ + int i; + + memset(diffstat, 0, sizeof(struct diffstat_t)); + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_stat(p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid, diff --git a/diff.h b/diff.h index 7f8f024feb..d986ddc3b5 100644 --- a/diff.h +++ b/diff.h @@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err); void diff_emit_submodule_pipethrough(struct diff_options *o, const char *line, int len); +struct diffstat_t { + int nr; + int alloc; + struct diffstat_file { + char *from_name; + char *name; + char *print_name; + const char *comments; + unsigned is_unmerged:1; + unsigned is_binary:1; + unsigned is_renamed:1; + unsigned is_interesting:1; + uintmax_t added, deleted; + } **files; +}; + enum color_diff { DIFF_RESET = 0, DIFF_CONTEXT = 1, @@ -334,6 +350,10 @@ void diff_change(struct diff_options *, struct diff_filepair *diff_unmerge(struct diff_options *, const char *path); +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat, + struct diff_queue_struct *q); +void free_diffstat_info(struct diffstat_t *diffstat); + #define DIFF_SETUP_REVERSE 1 #define DIFF_SETUP_USE_SIZE_CACHE 4 From patchwork Mon Nov 4 12:15:23 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11225675 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 6D4961515 for ; Mon, 4 Nov 2019 12:15:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 3B0B9218BA for ; Mon, 4 Nov 2019 12:15:38 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="n8DAq3il" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728416AbfKDMPh (ORCPT ); Mon, 4 Nov 2019 07:15:37 -0500 Received: from mail-wm1-f67.google.com ([209.85.128.67]:54328 "EHLO mail-wm1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727838AbfKDMPh (ORCPT ); Mon, 4 Nov 2019 07:15:37 -0500 Received: by mail-wm1-f67.google.com with SMTP id z26so3220906wmi.4 for ; Mon, 04 Nov 2019 04:15:35 -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:mime-version :content-transfer-encoding:fcc:to:cc; bh=lJQ2WGr3MfSsDJkCQj2oQttYDfevOMbXuCs8U7gTQt0=; b=n8DAq3il7fhUC7GYJd4pXik3vhYvum6DXvgFQE4QIPIRchN3c9Nnc8VeeHDwsPcDar /mAPs1+hDt76k6PifInLb7mL8nD/2AzgEBAptVm8EYwNBRGClKwOSvgWQcrjaBBKSOTs QQ5lLzh75tLDswoxE3TFF4zPJDC628xS9lAkkxCfs+0EiqpNxvXoT+ZxprzkoO7MnQ+z UPRbGlN5+rvW4FbRN0eVwo24WSgowVkEU2fXBNNPTdbKMkx1hrY+Cxo2Lp5V7LTIMLRh efECfGfruM+pZxd0SKI0KeV/tbe7LKWucqI5bCnipOM4OWzzjP6XLecqWOe6BipZguxA ln6g== 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:mime-version:content-transfer-encoding:fcc:to:cc; bh=lJQ2WGr3MfSsDJkCQj2oQttYDfevOMbXuCs8U7gTQt0=; b=VxkKThZvkh+FyIMeMpMqg9cKUXN29Uxbmdfkkb4xQgPcnXlP4qqgLpGnb8rEa+OKm0 gKrfB3xyV0URDbj1ZKa+PRdlvjEO0RvwZJQcjhIZdJau//Ecof7fmPBkauhOGNsdlxQz V893D0KjSukHlD+y8lRv1K6c7ElR0stE4BKI5zA0wgqfkoPIVXJbx8Z0nscj9ZVqYx+J wF0qPPr/KF9q5Bjaobk+l7AqxLUXzVLI/Am/fD6iYC76M4PQft8egEqh0b3ZzO+RSVFy IidDJBSeO/3RIluCVzouSxNEKrXZ0uHtF6RRg6L9acQ1P1Su1NwldcNpb7LCSJ/9Pkot 8ISQ== X-Gm-Message-State: APjAAAU+eGznGz2MUu6yQ7iebxkQkU67SBQlv0iRMNinpdmdg2aCnvm9 ZJ5fFYRpQ75udJA/F45OZx2iMToL X-Google-Smtp-Source: APXvYqxpS7rtKII8hkxjUYdxaWk9GxlhR2whWEyPe3v187xyiLeQ+VYLOcp70Ng3KiBgs6vJORsavA== X-Received: by 2002:a1c:411:: with SMTP id 17mr20995380wme.122.1572869734461; Mon, 04 Nov 2019 04:15:34 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id 65sm25876471wrs.9.2019.11.04.04.15.33 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:34 -0800 (PST) Message-Id: <6aaa0de4f4564b1d8da0691851dac0d22f87e565.1572869730.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Daniel Ferreira via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:23 +0000 Subject: [PATCH v5 3/9] built-in add -i: implement the `status` command MIME-Version: 1.0 Fcc: Sent To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , Johannes Schindelin , Junio C Hamano , Daniel Ferreira Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Daniel Ferreira This implements the `status` command of `git add -i`. The data structures introduced in this commit will be extended later, as needed. At this point, we re-implement only part of the `list_and_choose()` function of the Perl script `git-add--interactive.perl` and call it `list()`. It does not yet color anything, or do columns, or allow user input. Over the course of the next commits, we will introduce a `list_and_choose()` function that uses `list()` to display the list of options and let the user choose one or more of the displayed items. This will be used to implement the main loop of the built-in `git add -i`, at which point the new `status` command can actually be used. Signed-off-by: Daniel Ferreira Signed-off-by: Slavica Đukić Signed-off-by: Johannes Schindelin --- add-interactive.c | 251 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 250 insertions(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index 482e458dc6..aa35184d87 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -1,7 +1,256 @@ #include "cache.h" #include "add-interactive.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" +#include "string-list.h" + +struct add_i_state { + struct repository *r; +}; + +static void init_add_i_state(struct add_i_state *s, struct repository *r) +{ + s->r = r; +} + +struct list_options { + const char *header; + void (*print_item)(int i, struct string_list_item *item, void *print_item_data); + void *print_item_data; +}; + +static void list(struct string_list *list, struct list_options *opts) +{ + int i; + + if (!list->nr) + return; + + if (opts->header) + printf("%s\n", opts->header); + + for (i = 0; i < list->nr; i++) { + opts->print_item(i, list->items + i, opts->print_item_data); + putchar('\n'); + } +} + +struct adddel { + uintmax_t add, del; + unsigned seen:1, binary:1; +}; + +struct file_item { + struct adddel index, worktree; +}; + +static void add_file_item(struct string_list *files, const char *name) +{ + struct file_item *item = xcalloc(sizeof(*item), 1); + + string_list_append(files, name)->util = item; +} + +struct pathname_entry { + struct hashmap_entry ent; + const char *name; + struct file_item *item; +}; + +static int pathname_entry_cmp(const void *unused_cmp_data, + const struct hashmap_entry *he1, + const struct hashmap_entry *he2, + const void *name) +{ + const struct pathname_entry *e1 = + container_of(he1, const struct pathname_entry, ent); + const struct pathname_entry *e2 = + container_of(he2, const struct pathname_entry, ent); + + return strcmp(e1->name, name ? (const char *)name : e2->name); +} + +struct collection_status { + enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase; + + const char *reference; + + struct string_list *files; + struct hashmap file_map; +}; + +static void collect_changes_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + struct collection_status *s = data; + struct diffstat_t stat = { 0 }; + int i; + + if (!q->nr) + return; + + compute_diffstat(options, &stat, q); + + for (i = 0; i < stat.nr; i++) { + const char *name = stat.files[i]->name; + int hash = strhash(name); + struct pathname_entry *entry; + struct file_item *file_item; + struct adddel *adddel; + + entry = hashmap_get_entry_from_hash(&s->file_map, hash, name, + struct pathname_entry, ent); + if (!entry) { + add_file_item(s->files, name); + + entry = xcalloc(sizeof(*entry), 1); + hashmap_entry_init(&entry->ent, hash); + entry->name = s->files->items[s->files->nr - 1].string; + entry->item = s->files->items[s->files->nr - 1].util; + hashmap_add(&s->file_map, &entry->ent); + } + + file_item = entry->item; + adddel = s->phase == FROM_INDEX ? + &file_item->index : &file_item->worktree; + adddel->seen = 1; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; + if (stat.files[i]->is_binary) + adddel->binary = 1; + } + free_diffstat_info(&stat); +} + +static int get_modified_files(struct repository *r, struct string_list *files, + const struct pathspec *ps) +{ + struct object_id head_oid; + int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + &head_oid, NULL); + struct collection_status s = { FROM_WORKTREE }; + + if (discard_index(r->index) < 0 || + repo_read_index_preload(r, ps, 0) < 0) + return error(_("could not read index")); + + string_list_clear(files, 1); + s.files = files; + hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0); + + for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) { + struct rev_info rev; + struct setup_revision_opt opt = { 0 }; + + opt.def = is_initial ? + empty_tree_oid_hex() : oid_to_hex(&head_oid); + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, &opt); + + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + + if (ps) + copy_pathspec(&rev.prune_data, ps); + + if (s.phase == FROM_INDEX) + run_diff_index(&rev, 1); + else { + rev.diffopt.flags.ignore_dirty_submodules = 1; + run_diff_files(&rev, 0); + } + } + hashmap_free_entries(&s.file_map, struct pathname_entry, ent); + + /* While the diffs are ordered already, we ran *two* diffs... */ + string_list_sort(files); + + return 0; +} + +static void render_adddel(struct strbuf *buf, + struct adddel *ad, const char *no_changes) +{ + if (ad->binary) + strbuf_addstr(buf, _("binary")); + else if (ad->seen) + strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX, + (uintmax_t)ad->add, (uintmax_t)ad->del); + else + strbuf_addstr(buf, no_changes); +} + +struct print_file_item_data { + const char *modified_fmt; + struct strbuf buf, index, worktree; +}; + +static void print_file_item(int i, struct string_list_item *item, + void *print_file_item_data) +{ + struct file_item *c = item->util; + struct print_file_item_data *d = print_file_item_data; + + strbuf_reset(&d->index); + strbuf_reset(&d->worktree); + strbuf_reset(&d->buf); + + render_adddel(&d->worktree, &c->worktree, _("nothing")); + render_adddel(&d->index, &c->index, _("unchanged")); + strbuf_addf(&d->buf, d->modified_fmt, + d->index.buf, d->worktree.buf, item->string); + + printf(" %2d: %s", i + 1, d->buf.buf); +} + +static int run_status(struct add_i_state *s, const struct pathspec *ps, + struct string_list *files, struct list_options *opts) +{ + if (get_modified_files(s->r, files, ps) < 0) + return -1; + + list(files, opts); + putchar('\n'); + + return 0; +} int run_add_i(struct repository *r, const struct pathspec *ps) { - die(_("No commands are available in the built-in `git add -i` yet!")); + struct add_i_state s = { NULL }; + struct print_file_item_data print_file_item_data = { + "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT + }; + struct list_options opts = { + NULL, print_file_item, &print_file_item_data + }; + struct strbuf header = STRBUF_INIT; + struct string_list files = STRING_LIST_INIT_DUP; + int res = 0; + + init_add_i_state(&s, r); + strbuf_addstr(&header, " "); + strbuf_addf(&header, print_file_item_data.modified_fmt, + _("staged"), _("unstaged"), _("path")); + opts.header = header.buf; + + 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) + warning(_("could not refresh index")); + + res = run_status(&s, ps, &files, &opts); + + string_list_clear(&files, 1); + strbuf_release(&print_file_item_data.buf); + strbuf_release(&print_file_item_data.index); + strbuf_release(&print_file_item_data.worktree); + strbuf_release(&header); + + return res; } From patchwork Mon Nov 4 12:15:24 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11225677 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 CA48516B1 for ; Mon, 4 Nov 2019 12:15:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 9B298218BA for ; Mon, 4 Nov 2019 12:15:38 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="KtFQKzLV" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728562AbfKDMPi (ORCPT ); Mon, 4 Nov 2019 07:15:38 -0500 Received: from mail-wr1-f67.google.com ([209.85.221.67]:43098 "EHLO mail-wr1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727322AbfKDMPh (ORCPT ); Mon, 4 Nov 2019 07:15:37 -0500 Received: by mail-wr1-f67.google.com with SMTP id n1so16797108wra.10 for ; Mon, 04 Nov 2019 04:15:36 -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:mime-version :content-transfer-encoding:fcc:to:cc; bh=sdshgj6d0JnATjGjpeuLigTFqXl39TQNKLV3UwDcNBo=; b=KtFQKzLVdzkPI79fxe3T013nce14LPQBg4FoaTkOAwSZ+/R02+6GsBoaW+MH/j3X9u ZHGnVMEoThJJiCjtkU5+A+EDan4P8bpqSD2mOoAVg9YA+8mNlcqRvWiY4ZJy85zI7wZ/ vdDcCwq9J16HhpXA/lA1RZk7V+8VNpbNV/EhjXM5tBavyjcFMfQpu5H7ZALucZl1IbGG mkdoidW/9uGtQiyrvK5p1BH+AlPHOm0lLo91zIRrXMZyAB4CcsWn1P6CBqpA1TNKHBxv 6+6ynHVpIRBPQderjxAjAwi0x/A4Qr4vyPwyNP6pFWiZQWibOlVCni6WPnBoFWpAlbCW wkkg== 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:mime-version:content-transfer-encoding:fcc:to:cc; bh=sdshgj6d0JnATjGjpeuLigTFqXl39TQNKLV3UwDcNBo=; b=RuHqfdcUWeiu3ifHnIelnBnQWZnVlAhXDpaY1914HrqvxvBSSGInXlIajvgvLFxnhy grp15J18W9OrM8aWnj4hZ4wQk9LxtEE3axmpToUYld7fwyNMpMz6bQH/XuoSPN3IEY6X WAnbg9sduLzBFql40ecDPHcb1aiPqRJi4MBGP9lKjxEdBty3eqf4IEdkCHeEMHvXBWpy 38W0lllb4+IJPClQIVax0LqRbOubcinzKUNMQwMcuVxXuYrLWyLXm7rHgEyZ/hqwf19Q GOLe3lQoGMXvFYyhA8jQ5z79o7Zacqt3zZl/dze3//DpJcXoTU+eG6wcWewlSr7Tv30k 72Lg== X-Gm-Message-State: APjAAAUdf9tgVUtMjbYmUoFV6vWmRFyOiEQr5bbtPk4pAPJ42zDmUFWs 4nfC2W9RqMNHb0f4eMPffg/BjJyz X-Google-Smtp-Source: APXvYqzgd8ivK/eNrWueRSUBx4imrXhIXGJq/ceqOX4ZPoz2gnTEtxDEA71Xrk7P672RkFr55/NX0Q== X-Received: by 2002:adf:e286:: with SMTP id v6mr17808181wri.241.1572869735447; Mon, 04 Nov 2019 04:15:35 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id y2sm17629165wmy.2.2019.11.04.04.15.34 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:34 -0800 (PST) Message-Id: In-Reply-To: References: From: " =?utf-8?b?U2xhdmljYSDEkHVracSH?= via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:24 +0000 Subject: [PATCH v5 4/9] built-in add -i: color the header in the `status` command MIME-Version: 1.0 Fcc: Sent To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , Johannes Schindelin , Junio C Hamano , =?utf-8?b?U2xhdmljYSDEkHVracSH?= Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= For simplicity, we only implemented the `status` command without colors. This patch starts adding color, matching what the Perl script `git-add--interactive.perl` does. Original-Patch-By: Daniel Ferreira Signed-off-by: Slavica Đukić Signed-off-by: Johannes Schindelin --- add-interactive.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index aa35184d87..174e07ce83 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -1,5 +1,7 @@ #include "cache.h" #include "add-interactive.h" +#include "color.h" +#include "config.h" #include "diffcore.h" #include "revision.h" #include "refs.h" @@ -7,11 +9,40 @@ struct add_i_state { struct repository *r; + int use_color; + char header_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) +{ + char *key = xstrfmt("color.interactive.%s", slot_name); + const char *value; + + if (!s->use_color) + dst[0] = '\0'; + else if (repo_config_get_value(r, key, &value) || + color_parse(value, dst)) + strlcpy(dst, default_color, COLOR_MAXLEN); + + free(key); +} + static void init_add_i_state(struct add_i_state *s, struct repository *r) { - s->r = r; + const char *value; + + s->r = r; + + if (repo_config_get_value(r, "color.interactive", &value)) + s->use_color = -1; + else + s->use_color = + git_config_colorbool("color.interactive", value); + s->use_color = want_color(s->use_color); + + init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD); } struct list_options { @@ -20,7 +51,8 @@ struct list_options { void *print_item_data; }; -static void list(struct string_list *list, struct list_options *opts) +static void list(struct add_i_state *s, struct string_list *list, + struct list_options *opts) { int i; @@ -28,7 +60,8 @@ static void list(struct string_list *list, struct list_options *opts) return; if (opts->header) - printf("%s\n", opts->header); + color_fprintf_ln(stdout, s->header_color, + "%s", opts->header); for (i = 0; i < list->nr; i++) { opts->print_item(i, list->items + i, opts->print_item_data); @@ -213,7 +246,7 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps, if (get_modified_files(s->r, files, ps) < 0) return -1; - list(files, opts); + list(s, files, opts); putchar('\n'); return 0; From patchwork Mon Nov 4 12:15:25 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: 11225685 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 D6F9116B1 for ; Mon, 4 Nov 2019 12:15:43 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id A5E05222C2 for ; Mon, 4 Nov 2019 12:15:43 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kWwTJHEi" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728794AbfKDMPl (ORCPT ); Mon, 4 Nov 2019 07:15:41 -0500 Received: from mail-wr1-f66.google.com ([209.85.221.66]:46184 "EHLO mail-wr1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728377AbfKDMPj (ORCPT ); Mon, 4 Nov 2019 07:15:39 -0500 Received: by mail-wr1-f66.google.com with SMTP id b3so11022010wrs.13 for ; Mon, 04 Nov 2019 04:15:37 -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=3bJKMOutT6c3VZ5WmS7+2ODb4vvEEnvB3DXZ+5+HhFI=; b=kWwTJHEixNNPnj3IbUI7LAjaFCBhJh8T1tzjdVHIBpLZBFN5dindGs06uaavvqpECV LzyxrmFwn/ILikThyOavbofJy/tQ6kzZXcVHSBSzxQWGjGCNrXx5pxtN7OfnVctlP0a+ 1gKwPZdf0XWpeEfkzo28uQQGATgTA2Z1CcAtCekwFSWbGiD5yVkiY13L69CUVQBnF8C2 r9Fg9wimOg64XGdaI8T0j+M4HD8ZwkPVxGpqgT8S88uefMlPWvCtFTPMGXuQSt3B4GSQ erhNXWcNe7vN+MIuAtdr/6OaywpTuosZV49e3/x6tbJmoOi7WW+7QlHIUpDd4bfCFCmy QZDg== 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=3bJKMOutT6c3VZ5WmS7+2ODb4vvEEnvB3DXZ+5+HhFI=; b=uBQYLWmkuiyNDtiVJAzKhlE4SCa9+ak7h3uksE53UABQ6a9/907b7J6eFfWmWOnUPZ cWE7OUlsvJXaLGYym2VqjhDcida87/S0Wn/zDGlsf8nv/skGF1Y/Cpa7jmb/Ew9eebmN Al1dXTDnCM4iVQO5kBDMrHxp3/N8GFlqk0qRoU1cQJaU0SS/z1jBKEytCLTH4jx6Cjde NmM1udnNqIbdqQZmO4mok0ilQuEW7nIdSrZb9Va1kXdYN+ZbuzJlNu/M0y2EcunOlX5q 8rLsZaze0JqPjaskguperYiTmLCF01L8WbE7hljv3Q3F1NG4EZdxrihuNivXtk4kovUZ 0keQ== X-Gm-Message-State: APjAAAWWOCcfuWxCRNmJdFY+3nJ1RKwNZe46VE6n4WPoIswoPspL/CC4 lqA2xm7wtFi3Qm4o195EOOxOr5Pp X-Google-Smtp-Source: APXvYqxTwlK9n785DOupn1xLXioveZGkt8Q55RwCS8y0jUzOoWpbmqjh09RhlvgBYU6vv+6aJGkoFw== X-Received: by 2002:a5d:49c9:: with SMTP id t9mr23668020wrs.146.1572869736254; Mon, 04 Nov 2019 04:15:36 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id i3sm16610073wrw.69.2019.11.04.04.15.35 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:35 -0800 (PST) Message-Id: <25590fbbbee7efc34477bfea233684e93ee7fe60.1572869730.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:25 +0000 Subject: [PATCH v5 5/9] built-in add -i: implement the main loop Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , 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 reason why we did not start with the main loop to begin with is that it is the first user of `list_and_choose()`, which uses the `list()` function that we conveniently introduced for use by the `status` command. Apart from the "and choose" part, there are more differences between the way the `status` command calls the `list_and_choose()` function in the Perl version of `git add -i` compared to the other callers of said function. The most important ones: - The list is not only shown, but the user is also asked to make a choice, possibly selecting multiple entries. - The list of items is prefixed with a marker indicating what items have been selected, if multi-selection is allowed. - Initially, for each item a unique prefix (if there exists any within the given parameters) is determined, and shown in the list, and accepted as a shortcut for the selection. These features will be implemented later, except the part where the user can choose a command. At this stage, though, the built-in `git add -i` still only supports the `status` command, with the remaining commands to follow over the course of the next commits. In addition, we also modify `list()` to support displaying the commands in columns, even if there is currently only one. The Perl script `git-add--interactive.perl` mixed the purposes of the "list" and the "and choose" part into the same function. In the C version, we will keep them separate instead, calling the `list()` function from the `list_and_choose()` function. Note that we only have a prompt ending in a single ">" at this stage; later commits will add commands that display a double ">>" to indicate that the user is in a different loop than the main one. Signed-off-by: Johannes Schindelin --- add-interactive.c | 135 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index 174e07ce83..c6f7fbad36 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -46,6 +46,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) } struct list_options { + int columns; const char *header; void (*print_item)(int i, struct string_list_item *item, void *print_item_data); void *print_item_data; @@ -54,7 +55,7 @@ struct list_options { static void list(struct add_i_state *s, struct string_list *list, struct list_options *opts) { - int i; + int i, last_lf = 0; if (!list->nr) return; @@ -65,8 +66,96 @@ static void list(struct add_i_state *s, struct string_list *list, for (i = 0; i < list->nr; i++) { opts->print_item(i, list->items + i, opts->print_item_data); + + if ((opts->columns) && ((i + 1) % (opts->columns))) { + putchar('\t'); + last_lf = 0; + } + else { + putchar('\n'); + last_lf = 1; + } + } + + if (!last_lf) putchar('\n'); +} +struct list_and_choose_options { + struct list_options list_opts; + + const char *prompt; +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* + * Returns the selected index. + * + * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, + * `LIST_AND_CHOOSE_QUIT` is returned. + */ +static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items, + struct list_and_choose_options *opts) +{ + struct strbuf input = STRBUF_INIT; + ssize_t res = LIST_AND_CHOOSE_ERROR; + + for (;;) { + char *p, *endp; + + strbuf_reset(&input); + + list(s, items, &opts->list_opts); + + printf("%s%s", opts->prompt, "> "); + fflush(stdout); + + if (strbuf_getline(&input, stdin) == EOF) { + putchar('\n'); + res = LIST_AND_CHOOSE_QUIT; + break; + } + strbuf_trim(&input); + + if (!input.len) + break; + + p = input.buf; + for (;;) { + size_t sep = strcspn(p, " \t\r\n,"); + ssize_t index = -1; + + if (!sep) { + if (!*p) + break; + p++; + continue; + } + + if (isdigit(*p)) { + index = strtoul(p, &endp, 10) - 1; + if (endp != p + sep) + index = -1; + } + + p[sep] = '\0'; + if (index < 0 || index >= items->nr) + printf(_("Huh (%s)?\n"), p); + else { + res = index; + break; + } + + p += sep + 1; + } + + if (res != LIST_AND_CHOOSE_ERROR) + break; } + + strbuf_release(&input); + return res; } struct adddel { @@ -252,20 +341,48 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps, return 0; } +typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps, + struct string_list *files, + struct list_options *opts); + +static void print_command_item(int i, struct string_list_item *item, + void *print_command_item_data) +{ + printf(" %2d: %s", i + 1, item->string); +} + int run_add_i(struct repository *r, const struct pathspec *ps) { struct add_i_state s = { NULL }; + struct list_and_choose_options main_loop_opts = { + { 4, N_("*** Commands ***"), print_command_item, NULL }, + N_("What now") + }; + struct { + const char *string; + command_t command; + } command_list[] = { + { "status", run_status }, + }; + struct string_list commands = STRING_LIST_INIT_NODUP; + struct print_file_item_data print_file_item_data = { "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; struct list_options opts = { - NULL, print_file_item, &print_file_item_data + 0, NULL, print_file_item, &print_file_item_data }; struct strbuf header = STRBUF_INIT; struct string_list files = STRING_LIST_INIT_DUP; + ssize_t i; int res = 0; + for (i = 0; i < ARRAY_SIZE(command_list); i++) + string_list_append(&commands, command_list[i].string) + ->util = command_list[i].command; + init_add_i_state(&s, r); + strbuf_addstr(&header, " "); strbuf_addf(&header, print_file_item_data.modified_fmt, _("staged"), _("unstaged"), _("path")); @@ -279,11 +396,25 @@ int run_add_i(struct repository *r, const struct pathspec *ps) res = run_status(&s, ps, &files, &opts); + for (;;) { + i = list_and_choose(&s, &commands, &main_loop_opts); + if (i == LIST_AND_CHOOSE_QUIT) { + printf(_("Bye.\n")); + res = 0; + break; + } + if (i != LIST_AND_CHOOSE_ERROR) { + command_t command = commands.items[i].util; + res = command(&s, ps, &files, &opts); + } + } + string_list_clear(&files, 1); strbuf_release(&print_file_item_data.buf); strbuf_release(&print_file_item_data.index); strbuf_release(&print_file_item_data.worktree); strbuf_release(&header); + string_list_clear(&commands, 0); return res; } From patchwork Mon Nov 4 12:15:26 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11225691 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 EB89716B1 for ; Mon, 4 Nov 2019 12:15:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id BAD0921D81 for ; Mon, 4 Nov 2019 12:15:46 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="oSly33u8" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728839AbfKDMPo (ORCPT ); Mon, 4 Nov 2019 07:15:44 -0500 Received: from mail-wm1-f67.google.com ([209.85.128.67]:50384 "EHLO mail-wm1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727322AbfKDMPk (ORCPT ); Mon, 4 Nov 2019 07:15:40 -0500 Received: by mail-wm1-f67.google.com with SMTP id 11so16435708wmk.0 for ; Mon, 04 Nov 2019 04:15:38 -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:mime-version :content-transfer-encoding:fcc:to:cc; bh=kSYfCLCAHtsmzDNlIaADdzszo1MkBEkH9vO4RVbgBCE=; b=oSly33u8wdG13+LXS6ijRy8sFJ0l9nuofZtWw7vhJn5a81SDMha3AbyIOUrw1svX53 tPWa+UagNszrz2Q/8TM9fe1A1NPz0P8Ef1PCVf4q3YBP+XntJCiS0lvH3nlhzfFOV4H3 dyAIMLaDm2shD8FycG5as6xWsy8ey6JQ5YoWKyHPL/6WBgJxB4w9McK7mIN6lH1PIVUX xHLURdYUS4n2qB0/EtzmLrGI6R3wA5lCT3EDFetO9Sqt7/wxjWUrnHUamADJATpV3weF 8i49V6WgNHSQJoin1jbu4BdeL6CxwZk9z6D65UI1rZbwX2zjrpI39CmoPUfpvYgzwglV NUeg== 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:mime-version:content-transfer-encoding:fcc:to:cc; bh=kSYfCLCAHtsmzDNlIaADdzszo1MkBEkH9vO4RVbgBCE=; b=V2GI2Qv6TiAvcPxirAylHCO7NA0SOc81hDprGK8xnSYkkN6mL/ugFv4MN5y2ZlpN7u wUahVcX0nofsqTs4qyljT1Nn9ZaS8vp96oie2yGQXjVdRyPn7LV6M1t9MbdHDxsVPuqx jtssHrtW+x+VduZf5XOqbyPgDQmLvtrQBY4/hF/3PoInaqiBQnaKfTERCoaACNPvTqIn sFqDCLCa9KsHBQcYXeoAPIQO93MlB6vMKJFiHsODuwrWeM+WoU5fReBRDeE/w4/DUP9H QQBB8rMl9SiUpQF0S30N0geKLJpH4FcKGnhry47qYJbfflOF6YKlJTrIZ16kvWUUVHGA gkoA== X-Gm-Message-State: APjAAAVkhPMgeJ0wdTs9CXDbSObBnhYCH9T+BLXe9+i+Q7+9vQZ7MH18 rRv5stXqe+ay/ZUaqZn5YUoCDgH2 X-Google-Smtp-Source: APXvYqweKhsER+DzsCOm78YChdVx7uXyhD5lPT4LFfZ6pSoR40ztng+N6sQPiZfkImAKIJBCKte5TQ== X-Received: by 2002:a1c:2e09:: with SMTP id u9mr21546269wmu.108.1572869737017; Mon, 04 Nov 2019 04:15:37 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id n13sm9426180wmi.25.2019.11.04.04.15.36 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:36 -0800 (PST) Message-Id: <57fdc01463b5fe1637dcfa4b027bde8e99f7fe86.1572869730.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:26 +0000 Subject: [PATCH v5 6/9] built-in add -i: show unique prefixes of the commands MIME-Version: 1.0 Fcc: Sent To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , 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 in the Perl script `git-add--interactive.perl`, for each command a unique prefix is determined (if there exists any within the given parameters), and shown in the list, and accepted as a shortcut for the command. To determine the unique prefixes, as well as to look up the command in question, we use a copy of the list and sort it. While this might seem like overkill for a single command, it will make much more sense when all the commands are implemented, and when we reuse the same logic to present a list of files to edit, with convenient unique prefixes. At the start of the development of this patch series, a dedicated data structure was introduced that imitated the Trie that the Perl version implements. However, this was deemed overkill, and we now simply sort the list before determining the length of the unique prefixes by looking at each item's neighbor. As a bonus, we now use the same sorted list to perform a binary search using the user-provided prefix as search key. Original-patch-by: Slavica Đukić Helped-by: SZEDER Gábor Signed-off-by: Johannes Schindelin --- add-interactive.c | 188 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 11 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index c6f7fbad36..eb559555ad 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -45,6 +45,132 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD); } +/* + * A "prefix item list" is a list of items that are identified by a string, and + * a unique prefix (if any) is determined for each item. + * + * It is implemented in the form of a pair of `string_list`s, the first one + * duplicating the strings, with the `util` field pointing at a structure whose + * first field must be `size_t prefix_length`. + * + * That `prefix_length` field will be computed by `find_unique_prefixes()`; It + * will be set to zero if no valid, unique prefix could be found. + * + * The second `string_list` is called `sorted` and does _not_ duplicate the + * strings but simply reuses the first one's, with the `util` field pointing at + * the `string_item_list` of the first `string_list`. It will be populated and + * sorted by `find_unique_prefixes()`. + */ +struct prefix_item_list { + struct string_list items; + struct string_list sorted; + size_t min_length, max_length; +}; +#define PREFIX_ITEM_LIST_INIT \ + { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 } + +static void prefix_item_list_clear(struct prefix_item_list *list) +{ + string_list_clear(&list->items, 1); + string_list_clear(&list->sorted, 0); +} + +static void extend_prefix_length(struct string_list_item *p, + const char *other_string, size_t max_length) +{ + size_t *len = p->util; + + if (!*len || memcmp(p->string, other_string, *len)) + return; + + for (;;) { + char c = p->string[*len]; + + /* + * Is `p` a strict prefix of `other`? Or have we exhausted the + * maximal length of the prefix? Or is the current character a + * multi-byte UTF-8 one? If so, there is no valid, unique + * prefix. + */ + if (!c || ++*len > max_length || !isascii(c)) { + *len = 0; + break; + } + + if (c != other_string[*len - 1]) + break; + } +} + +static void find_unique_prefixes(struct prefix_item_list *list) +{ + size_t i; + + if (list->sorted.nr == list->items.nr) + return; + + string_list_clear(&list->sorted, 0); + /* Avoid reallocating incrementally */ + list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items), + list->items.nr)); + list->sorted.nr = list->sorted.alloc = list->items.nr; + + for (i = 0; i < list->items.nr; i++) { + list->sorted.items[i].string = list->items.items[i].string; + list->sorted.items[i].util = list->items.items + i; + } + + string_list_sort(&list->sorted); + + for (i = 0; i < list->sorted.nr; i++) { + struct string_list_item *sorted_item = list->sorted.items + i; + struct string_list_item *item = sorted_item->util; + size_t *len = item->util; + + *len = 0; + while (*len < list->min_length) { + char c = item->string[(*len)++]; + + if (!c || !isascii(c)) { + *len = 0; + break; + } + } + + if (i > 0) + extend_prefix_length(item, sorted_item[-1].string, + list->max_length); + if (i + 1 < list->sorted.nr) + extend_prefix_length(item, sorted_item[1].string, + list->max_length); + } +} + +static ssize_t find_unique(const char *string, struct prefix_item_list *list) +{ + int index = string_list_find_insert_index(&list->sorted, string, 1); + struct string_list_item *item; + + if (list->items.nr != list->sorted.nr) + BUG("prefix_item_list in inconsistent state (%"PRIuMAX + " vs %"PRIuMAX")", + (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr); + + if (index < 0) + item = list->sorted.items[-1 - index].util; + else if (index > 0 && + starts_with(list->sorted.items[index - 1].string, string)) + return -1; + else if (index + 1 < list->sorted.nr && + starts_with(list->sorted.items[index + 1].string, string)) + return -1; + else if (index < list->sorted.nr) + item = list->sorted.items[index].util; + else + return -1; + return item - list->items.items; +} + struct list_options { int columns; const char *header; @@ -95,18 +221,21 @@ struct list_and_choose_options { * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, * `LIST_AND_CHOOSE_QUIT` is returned. */ -static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items, +static ssize_t list_and_choose(struct add_i_state *s, + struct prefix_item_list *items, struct list_and_choose_options *opts) { struct strbuf input = STRBUF_INIT; ssize_t res = LIST_AND_CHOOSE_ERROR; + find_unique_prefixes(items); + for (;;) { char *p, *endp; strbuf_reset(&input); - list(s, items, &opts->list_opts); + list(s, &items->items, &opts->list_opts); printf("%s%s", opts->prompt, "> "); fflush(stdout); @@ -140,7 +269,10 @@ static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items, } p[sep] = '\0'; - if (index < 0 || index >= items->nr) + if (index < 0) + index = find_unique(p, items); + + if (index < 0 || index >= items->items.nr) printf(_("Huh (%s)?\n"), p); else { res = index; @@ -306,6 +438,23 @@ static void render_adddel(struct strbuf *buf, strbuf_addstr(buf, no_changes); } +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix(const char *prefix, size_t prefix_len) +{ + return prefix_len && prefix && + /* + * We expect `prefix` to be NUL terminated, therefore this + * `strcspn()` call is okay, even if it might do much more + * work than strictly necessary. + */ + strcspn(prefix, " \t\r\n,") >= prefix_len && /* separators */ + *prefix != '-' && /* deselection */ + !isdigit(*prefix) && /* selection */ + (prefix_len != 1 || + (*prefix != '*' && /* "all" wildcard */ + *prefix != '?')); /* prompt help */ +} + struct print_file_item_data { const char *modified_fmt; struct strbuf buf, index, worktree; @@ -345,10 +494,23 @@ typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps, struct string_list *files, struct list_options *opts); +struct command_item { + size_t prefix_length; + command_t command; +}; + static void print_command_item(int i, struct string_list_item *item, void *print_command_item_data) { - printf(" %2d: %s", i + 1, item->string); + struct command_item *util = item->util; + + if (!util->prefix_length || + !is_valid_prefix(item->string, util->prefix_length)) + printf(" %2d: %s", i + 1, item->string); + else + printf(" %2d: [%.*s]%s", i + 1, + (int)util->prefix_length, item->string, + item->string + util->prefix_length); } int run_add_i(struct repository *r, const struct pathspec *ps) @@ -364,7 +526,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) } command_list[] = { { "status", run_status }, }; - struct string_list commands = STRING_LIST_INIT_NODUP; + struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT; struct print_file_item_data print_file_item_data = { "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT @@ -377,9 +539,12 @@ int run_add_i(struct repository *r, const struct pathspec *ps) ssize_t i; int res = 0; - for (i = 0; i < ARRAY_SIZE(command_list); i++) - string_list_append(&commands, command_list[i].string) - ->util = command_list[i].command; + for (i = 0; i < ARRAY_SIZE(command_list); i++) { + struct command_item *util = xcalloc(sizeof(*util), 1); + util->command = command_list[i].command; + string_list_append(&commands.items, command_list[i].string) + ->util = util; + } init_add_i_state(&s, r); @@ -404,8 +569,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps) break; } if (i != LIST_AND_CHOOSE_ERROR) { - command_t command = commands.items[i].util; - res = command(&s, ps, &files, &opts); + struct command_item *util = + commands.items.items[i].util; + res = util->command(&s, ps, &files, &opts); } } @@ -414,7 +580,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) strbuf_release(&print_file_item_data.index); strbuf_release(&print_file_item_data.worktree); strbuf_release(&header); - string_list_clear(&commands, 0); + prefix_item_list_clear(&commands); return res; } From patchwork Mon Nov 4 12:15:27 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: 11225681 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 517CC1864 for ; Mon, 4 Nov 2019 12:15:43 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2B26C222C2 for ; Mon, 4 Nov 2019 12:15:43 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Xk7M2aJQ" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728807AbfKDMPm (ORCPT ); Mon, 4 Nov 2019 07:15:42 -0500 Received: from mail-wm1-f67.google.com ([209.85.128.67]:55449 "EHLO mail-wm1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727998AbfKDMPj (ORCPT ); Mon, 4 Nov 2019 07:15:39 -0500 Received: by mail-wm1-f67.google.com with SMTP id m17so7131663wmi.5 for ; Mon, 04 Nov 2019 04:15:38 -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=F4n1id0vdhNeuIF3K+W90lr/L83cbu1tHsoETPVfkxY=; b=Xk7M2aJQ/DLU4DNT3fq7T/Hdk2GNm74SIbUkaSd7QYYVGQuwo8pFzH/wDL9kg4aNLX AytLc/7CMHmH55+PQtFTXFtt0/7eddcT5ovSuh7JwWFEd7cZ6ViTA5R7iuieGHy/uNru E22tfTnJmdp+aWauAdxPw7Qry8uYYbM3RJtKzWMMxW/FBDDvasia9G9lMwKXdKUkdbVy j1LEB1tY8hRLwbW98wsS/zWvkliiMpWq8+abXt/RKMsBHGyCUtBQFS77HjOYaElYTx0a lMJWsz2XT/Yz0jhiM+D4plH6So0rsUQo3JjyeCybvfau3nHwgL3chyTs6fNRH3cNV98y VPcg== 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=F4n1id0vdhNeuIF3K+W90lr/L83cbu1tHsoETPVfkxY=; b=iEuVdlg8T0uL4LawYvsInkOkgb+1zh+Czn00tJWJNb1CQxWX1k9ACbr54w+t3tJPXl cXo7q0ZZUkH9j3iUkXkOXk5bSn3uV1WHbPitLY0VxguDmHsR/9dZcWGWDe/fOTkxlA6d l6YMing3dyzBdKhbqWhFHNa+42TzkyrPRanuKmRmL0Ytv0nHPP4jQFnP+dTS9H6P0hBt N8vOqHRLKNrpc8Zi9XD0iBOhnmvDfufxvkbx/wYXrAYT2JUsgkpiJHhEtl3gOERc8FJ4 8XOhqTCcrS3/cZxVMBCLD0wumT84DfamiPRRweqUGrcPdsoUAONq8ElvAWlEUWlzYnPY NCBA== X-Gm-Message-State: APjAAAUy/RaPPsIaAwCkdg6dTC3BOWxnJ9Zt4aUC7gumMhHs1rkhvYK9 M7zirdgehP4hC7rh1BDFq0CHO43P X-Google-Smtp-Source: APXvYqwSr3LAjV7qLpHcT1wirW/15qWgXd3BHMAwzqDdxmSSD0dL3It9xJSJ4PTTouNVeLgsZUF9Wg== X-Received: by 2002:a1c:ca:: with SMTP id 193mr14227084wma.103.1572869737884; Mon, 04 Nov 2019 04:15:37 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id a15sm17445468wrw.10.2019.11.04.04.15.37 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:37 -0800 (PST) Message-Id: <77ad5f333a7f72135ed084dc2d0cf20d372302c8.1572869730.git.gitgitgadget@gmail.com> In-Reply-To: References: From: "Johannes Schindelin via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:27 +0000 Subject: [PATCH v5 7/9] built-in add -i: support `?` (prompt help) Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , 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 change, we print out the same colored help text that the Perl-based `git add -i` prints in the main loop when question mark is entered. Signed-off-by: Johannes Schindelin --- add-interactive.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index eb559555ad..d96e18fce5 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -11,6 +11,7 @@ struct add_i_state { struct repository *r; int use_color; char header_color[COLOR_MAXLEN]; + char help_color[COLOR_MAXLEN]; }; static void init_color(struct repository *r, struct add_i_state *s, @@ -43,6 +44,7 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) s->use_color = want_color(s->use_color); init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD); + init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED); } /* @@ -210,6 +212,7 @@ struct list_and_choose_options { struct list_options list_opts; const char *prompt; + void (*print_help)(struct add_i_state *s); }; #define LIST_AND_CHOOSE_ERROR (-1) @@ -250,6 +253,11 @@ static ssize_t list_and_choose(struct add_i_state *s, if (!input.len) break; + if (!strcmp(input.buf, "?")) { + opts->print_help(s); + continue; + } + p = input.buf; for (;;) { size_t sep = strcspn(p, " \t\r\n,"); @@ -513,12 +521,24 @@ static void print_command_item(int i, struct string_list_item *item, item->string + util->prefix_length); } +static void command_prompt_help(struct add_i_state *s) +{ + const char *help_color = s->help_color; + color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:")); + color_fprintf_ln(stdout, help_color, "1 - %s", + _("select a numbered item")); + color_fprintf_ln(stdout, help_color, "foo - %s", + _("select item based on unique prefix")); + color_fprintf_ln(stdout, help_color, " - %s", + _("(empty) select nothing")); +} + int run_add_i(struct repository *r, const struct pathspec *ps) { struct add_i_state s = { NULL }; struct list_and_choose_options main_loop_opts = { { 4, N_("*** Commands ***"), print_command_item, NULL }, - N_("What now") + N_("What now"), command_prompt_help }; struct { const char *string; From patchwork Mon Nov 4 12:15:28 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11225687 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 263A016B1 for ; Mon, 4 Nov 2019 12:15:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 01B6E21E6F for ; Mon, 4 Nov 2019 12:15:45 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="QR6dNGKd" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728811AbfKDMPn (ORCPT ); Mon, 4 Nov 2019 07:15:43 -0500 Received: from mail-wr1-f65.google.com ([209.85.221.65]:33864 "EHLO mail-wr1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728619AbfKDMPl (ORCPT ); Mon, 4 Nov 2019 07:15:41 -0500 Received: by mail-wr1-f65.google.com with SMTP id e6so14957465wrw.1 for ; Mon, 04 Nov 2019 04:15:39 -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:mime-version :content-transfer-encoding:fcc:to:cc; bh=fIjZABpx2ohEMwxZDJXzfAERtcrwofElrO0Wc6s98eY=; b=QR6dNGKdLf9wlUpJfXGpQRjnFJXZh8Ypm7sjlouEI193gw9fwl5MsKAF7uYCzIiXns DDM+XCJbMlYJtmYRrgNQQ900nj1jd3YR5gyJCfF5N/JOLezSlugBgb3OYU/f6Xpln0lt /keCKsy8Im1mQuHEaFD6vL1UdIp/pe7C1F+aXF+YsLs+7OB6VWZRV/GYkhZ06jtU7hCu JJmjvQaPay5cOd6M83Wpdno5cptWJh0LClXUeO/JToWa3mTBvXXg6oeOD2+j+mC6eoBo /QIWBoGl83V2+FeI/FpVs5JgtBnVbbpwOp9LlOMHl1qoJCLAwExhQPk+4ZLlYAX7Ipei Uv4A== 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:mime-version:content-transfer-encoding:fcc:to:cc; bh=fIjZABpx2ohEMwxZDJXzfAERtcrwofElrO0Wc6s98eY=; b=fSDqEAJlO/WmUWV4/GsojEkMc58A9BSiOHhrjmI4KCUFF49aHiFhqk0pv8Rui9fuAm pFksPpN68m9r5fTIxy8SacL363RB9Omt2KLrp9aWNoDYy649yBVbIcVczXt/DCjDw0jL lyT+/9VxRxN+3yTuP3sTUwRU3KrrUWPxqox0+NG8yXW+a7WCZahbq6lW9OiO/m4r9g4e CCLx4mzvGBCgrvOglBbtzHmILSaWr/yAkrBIrsjsQb/NlPq6dd6JG++OoUhf7bRott6x 0GwkdnQVevUAF7K1iCeY6TQkHPZ/ACM3oWU49yutpfki9F2lT2/VplG+Xb1xxapbQxFS Byag== X-Gm-Message-State: APjAAAWaz8wssLWq0gMIzxo7lMEK1RsEYF5mw+Fi8l2YDqJIaIb5EOaf +zlMR6MBT2MeLYw00QunwlijUi9t X-Google-Smtp-Source: APXvYqyFf7PWEeeWUQ7DgI6KPPkvWnsWlxTUL/WJ6fz7K0tDQ1BIYeRXm6VZ459nZEUBkgIXzKnp/g== X-Received: by 2002:adf:c50a:: with SMTP id q10mr15369560wrf.374.1572869738739; Mon, 04 Nov 2019 04:15:38 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id s13sm15782953wmc.28.2019.11.04.04.15.38 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:38 -0800 (PST) Message-Id: <3d0b172a7f863fe079b77589a6410b228dcaba96.1572869730.git.gitgitgadget@gmail.com> In-Reply-To: References: From: " =?utf-8?b?U2xhdmljYSDEkHVracSH?= via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:28 +0000 Subject: [PATCH v5 8/9] built-in add -i: use color in the main loop MIME-Version: 1.0 Fcc: Sent To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , Johannes Schindelin , Junio C Hamano , =?utf-8?b?U2xhdmljYSDEkHVracSH?= Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= The error messages as well as the unique prefixes are colored in `git add -i` by default; We need to do the same in the built-in version. Signed-off-by: Slavica Đukić Signed-off-by: Johannes Schindelin --- add-interactive.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index d96e18fce5..64d84ba1dc 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -12,6 +12,9 @@ struct add_i_state { 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, @@ -45,6 +48,9 @@ static void init_add_i_state(struct add_i_state *s, struct repository *r) init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD); init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED); + 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); } /* @@ -240,7 +246,8 @@ static ssize_t list_and_choose(struct add_i_state *s, list(s, &items->items, &opts->list_opts); - printf("%s%s", opts->prompt, "> "); + color_fprintf(stdout, s->prompt_color, "%s", opts->prompt); + fputs("> ", stdout); fflush(stdout); if (strbuf_getline(&input, stdin) == EOF) { @@ -281,7 +288,8 @@ static ssize_t list_and_choose(struct add_i_state *s, index = find_unique(p, items); if (index < 0 || index >= items->items.nr) - printf(_("Huh (%s)?\n"), p); + color_fprintf_ln(stdout, s->error_color, + _("Huh (%s)?"), p); else { res = index; break; @@ -507,18 +515,23 @@ struct command_item { command_t command; }; +struct print_command_item_data { + const char *color, *reset; +}; + static void print_command_item(int i, struct string_list_item *item, void *print_command_item_data) { + struct print_command_item_data *d = print_command_item_data; struct command_item *util = item->util; if (!util->prefix_length || !is_valid_prefix(item->string, util->prefix_length)) printf(" %2d: %s", i + 1, item->string); else - printf(" %2d: [%.*s]%s", i + 1, - (int)util->prefix_length, item->string, - item->string + util->prefix_length); + printf(" %2d: %s%.*s%s%s", i + 1, + d->color, (int)util->prefix_length, item->string, + d->reset, item->string + util->prefix_length); } static void command_prompt_help(struct add_i_state *s) @@ -536,8 +549,9 @@ static void command_prompt_help(struct add_i_state *s) int run_add_i(struct repository *r, const struct pathspec *ps) { struct add_i_state s = { NULL }; + struct print_command_item_data data = { "[", "]" }; struct list_and_choose_options main_loop_opts = { - { 4, N_("*** Commands ***"), print_command_item, NULL }, + { 4, N_("*** Commands ***"), print_command_item, &data }, N_("What now"), command_prompt_help }; struct { @@ -568,6 +582,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps) init_add_i_state(&s, r); + /* + * When color was asked for, use the prompt color for + * highlighting, otherwise use square brackets. + */ + if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + } + strbuf_addstr(&header, " "); strbuf_addf(&header, print_file_item_data.modified_fmt, _("staged"), _("unstaged"), _("path")); From patchwork Mon Nov 4 12:15:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Linus Arver via GitGitGadget X-Patchwork-Id: 11225689 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 73B341515 for ; Mon, 4 Nov 2019 12:15:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 4EE8921D81 for ; Mon, 4 Nov 2019 12:15:46 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="GI364Tdt" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728844AbfKDMPp (ORCPT ); Mon, 4 Nov 2019 07:15:45 -0500 Received: from mail-wr1-f67.google.com ([209.85.221.67]:37741 "EHLO mail-wr1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728713AbfKDMPl (ORCPT ); Mon, 4 Nov 2019 07:15:41 -0500 Received: by mail-wr1-f67.google.com with SMTP id t1so10867439wrv.4 for ; Mon, 04 Nov 2019 04:15:40 -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:mime-version :content-transfer-encoding:fcc:to:cc; bh=jkoX4BSraB5J7HguuB4qp/jaTlS4oYRdTv4MEN5DTlQ=; b=GI364TdtXdtK+8JkS7SyCUpRyAUAFSzFWFlpY1jyZhZ5jhNuMftkCrn3eYRSBrvPws HRTJcZwySKR63JmYM/H03s3IN3RenDOcWn0CIqnRP/sTPR0HEa4OwlGEjdncyRUB3nil a0Oy9gVbg4rZd7vUnixu8EkPO19ypbh8QuvkExzju7/3RvitaSDoEOGW0SadzA9O150g XTxCJfn4GDj/l/RiuTE2vAxBhEnDdmuQp3ob3RAU+Hbf559cdVnMljTBIMAoYFLSumh/ ZZfj2sEb1+s2RlTx1emhuWMpmRnsTajFDeLuXWKYc7URa24iLe3dW6Bi+Fd7hN08/o4J Smgw== 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:mime-version:content-transfer-encoding:fcc:to:cc; bh=jkoX4BSraB5J7HguuB4qp/jaTlS4oYRdTv4MEN5DTlQ=; b=EI1LRPBSBoZVjerTt0PbUqqvPcb0ENtYh/gMxzF22Y7V5rulaj2ae5lFU6RFh2CUJv cpelg9iPslTTKYEkXPUmx5zDRyZiQ74AlS+7JEdiF9GGna2uWvE9cf+W7i/G1VnCfdLU c9/x+yRp6a/tUwKOOKqCz/J4IfiMJoQ+AA4mrqTVbIDUe025qyMGzV8eEDJAZIhMrbB0 hUkuh2Iwl3LU1xnkDeCly8/UzrZfWD8TsPnBcrWXuGC6vHvAIEASFLD67TajVm6gD7BJ XC31fM5mOWIGD/1rAW3lIxL1EjdA5qOycYfzhDpfA35PiiUNxUUd0431xrhSLY+mgYj0 wCSA== X-Gm-Message-State: APjAAAWdXHQYKeYHUES/xjngcLBWVpA9MRT5QX43wS2iM/Lc6aKFm6l5 qqQOeFIjhCmMcbKgJQvpPqtEMLKT X-Google-Smtp-Source: APXvYqxprdnNMmprjomYOJXRCFIgbKM3Mq7XziPGoqL03mMG8Q6f2VBIbijePSXfSkwemV34W5eROw== X-Received: by 2002:a5d:4585:: with SMTP id p5mr1957122wrq.134.1572869739425; Mon, 04 Nov 2019 04:15:39 -0800 (PST) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id y2sm17629384wmy.2.2019.11.04.04.15.38 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Nov 2019 04:15:39 -0800 (PST) Message-Id: <85e508ef119658c154da35482095a4f0f15adc80.1572869730.git.gitgitgadget@gmail.com> In-Reply-To: References: From: " =?utf-8?b?U2xhdmljYSDEkHVracSH?= via GitGitGadget" Date: Mon, 04 Nov 2019 12:15:29 +0000 Subject: [PATCH v5 9/9] built-in add -i: implement the `help` command MIME-Version: 1.0 Fcc: Sent To: git@vger.kernel.org Cc: Jeff Hostetler , Jeff King , Johannes Schindelin , Junio C Hamano , =?utf-8?b?U2xhdmljYSDEkHVracSH?= Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: =?UTF-8?q?Slavica=20=C4=90uki=C4=87?= This imitates the code to show the help text from the Perl script `git-add--interactive.perl` in the built-in version. To make sure that it renders exactly like the Perl version of `git add -i`, we also add a test case for that to `t3701-add-interactive.sh`. Signed-off-by: Slavica Đukić Signed-off-by: Johannes Schindelin --- add-interactive.c | 21 +++++++++++++++++++++ t/t3701-add-interactive.sh | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/add-interactive.c b/add-interactive.c index 64d84ba1dc..4e5241e865 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -506,6 +506,26 @@ static int run_status(struct add_i_state *s, const struct pathspec *ps, return 0; } +static int run_help(struct add_i_state *s, const struct pathspec *unused_ps, + struct string_list *unused_files, + struct list_options *unused_opts) +{ + color_fprintf_ln(stdout, s->help_color, "status - %s", + _("show paths with changes")); + color_fprintf_ln(stdout, s->help_color, "update - %s", + _("add working tree state to the staged set of changes")); + color_fprintf_ln(stdout, s->help_color, "revert - %s", + _("revert staged set of changes back to the HEAD version")); + color_fprintf_ln(stdout, s->help_color, "patch - %s", + _("pick hunks and update selectively")); + color_fprintf_ln(stdout, s->help_color, "diff - %s", + _("view diff between HEAD and index")); + color_fprintf_ln(stdout, s->help_color, "add untracked - %s", + _("add contents of untracked files to the staged set of changes")); + + return 0; +} + typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps, struct string_list *files, struct list_options *opts); @@ -559,6 +579,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) command_t command; } command_list[] = { { "status", run_status }, + { "help", run_help }, }; struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d50e165ca8..d4f9386621 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' ' test_write_lines a b a b a a b a b a >expect && test_cmp expect a ' + +test_expect_success 'show help from add--helper' ' + git reset --hard && + cat >expect <<-EOF && + + *** Commands *** + 1: status 2: update 3: revert 4: add untracked + 5: patch 6: diff 7: quit 8: help + What now> status - show paths with changes + update - add working tree state to the staged set of changes + revert - revert staged set of changes back to the HEAD version + patch - pick hunks and update selectively + diff - view diff between HEAD and index + add untracked - add contents of untracked files to the staged set of changes + *** Commands *** + 1: status 2: update 3: revert 4: add untracked + 5: patch 6: diff 7: quit 8: help + What now>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color actual && + test_i18ncmp expect actual +' + test_done