Skip to contentSkip to conversation

Add --pathspec-from-file option for reset, commit

PATCH Superseded Alexandr Miloslavskiy via GitGitGadget 4 participants 80 messages Nov 4, 2019 – Dec 12, 2019 Documentation

lore
Share & tools
lore
#0 lore raw

cover Add --pathspec-from-file option for reset, commit

Adds --pathspec-from-file option for porcelain commands to avoid commandline length limit.

So far I implemented it for git commit and git reset, but my goal is to support other commands as well after these patches are reviewed.

The patches are based on the following discussions:
https://public-inbox.org/git/[email protected]/T/#u
There, --stdin-paths was suggested.
https://public-inbox.org/git/[email protected]/T/#u
There, --stdin-paths was extended to --paths-file.
https://public-inbox.org/git/[email protected]/
https://github.com/gitgitgadget/git/pull/133Patch from Johannes, I used it as base of my patch. There, --pathspec-from-file is suggested in discussion.

Major changes compared to patch from Johannes:

1) --pathspec-from-file allows to read file, not just stdin 2) --literal-pathspecs should be honored. This is a good improvement because it keeps --pathspec-from-file much closer to passing args on commandline. Since the goal of the new option is to avoid commandline length limit, both behaviors should be close to each other. 3) Patches are designed with all other git commands in mind

Alexandr Miloslavskiy (5):
pathspec: add new function to parse file
doc: reset: unify <pathspec> description
reset: support the --pathspec-from-file option
doc: commit: unify <pathspec> description
commit: support the --pathspec-from-file option

 Documentation/git-commit.txt    |  19 ++++-
 Documentation/git-reset.txt     |  40 +++++++---
 builtin/commit.c                |  25 ++++++-
 builtin/reset.c                 |  22 +++++-
 pathspec.c                      |  41 +++++++++++
 pathspec.h                      |  10 +++
 t/t7107-reset-pathspec-file.sh  | 126 ++++++++++++++++++++++++++++++++
 t/t7526-commit-pathspec-file.sh | 107 +++++++++++++++++++++++++++
 8 files changed, 371 insertions(+), 19 deletions(-)
 create mode 100755 t/t7107-reset-pathspec-file.sh
 create mode 100755 t/t7526-commit-pathspec-file.sh

base-commit: da72936f544fec5a335e66432610e4cef4430991
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-445%2FSyntevoAlex%2F%230207_pathspec_from_file-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-445/SyntevoAlex/#0207_pathspec_from_file-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/445

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/5 pathspec: add new function to parse file

From: Alexandr Miloslavskiy <[email protected]>

This will be used to support the new option '--pathspec-from-file' in git add, git-commit, git reset etc.

Note also that we specifically handle CR/LF line endings to support Windows better.

To simplify code, file is first parsed into argv_array. This allows to avoid refactoring parse_pathspec().

I considered adding nul_term_line to flags instead, but decided that it doesn't fit there.

The new code is mostly taken from cmd_update_index() and split_mail_conv().

---
 pathspec.c | 41 +++++++++++++++++++++++++++++++++++++++++
 pathspec.h | 10 ++++++++++
 2 files changed, 51 insertions(+)
pathspec.c +41 -0
index 12c2b322b3..97d4e77875 100644
@@ -3,6 +3,8 @@
 #include "dir.h"
 #include "pathspec.h"
 #include "attr.h"
+#include "argv-array.h"
+#include "quote.h"
 
 /*
  * Finds which of the given pathspecs match items in the index.
@@ -613,6 +615,45 @@ void parse_pathspec(struct pathspec *pathspec,
 	}
 }
 
+void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
+			 unsigned flags, const char *prefix,
+			 const char *file, int nul_term_line)
+{
+	struct argv_array parsed_file = ARGV_ARRAY_INIT;
+	strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
+						       strbuf_getline;
+	struct strbuf buf = STRBUF_INIT;
+	struct strbuf unquoted = STRBUF_INIT;
+	FILE *in = NULL;
+
+	if (!strcmp(file, "-"))
+		in = stdin;
+	else
+		in = fopen(file, "r");
+
+	if (!in)
+		die(_("could not open '%s' for reading"), file);
+
+	while (getline_fn(&buf, in) != EOF) {
+		if (!nul_term_line && buf.buf[0] == '"') {
+			strbuf_reset(&unquoted);
+			if (unquote_c_style(&unquoted, buf.buf, NULL))
+				die(_("line is badly quoted"));
+			strbuf_swap(&buf, &unquoted);
+		}
+		argv_array_push(&parsed_file, buf.buf);
+		strbuf_reset(&buf);
+	}
+
+	strbuf_release(&unquoted);
+	strbuf_release(&buf);
+	if (in != stdin)
+		fclose(in);
+
+	parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv);
+	argv_array_clear(&parsed_file);
+}
+
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
 {
 	int i, j;
pathspec.h +10 -0
index 1c18a2c90c..d72e0b4c4a 100644
@@ -85,6 +85,16 @@ void parse_pathspec(struct pathspec *pathspec,
 		    unsigned flags,
 		    const char *prefix,
 		    const char **args);
+/*
+ * Same as parse_pathspec() but uses file as input.
+ * When 'file' is exactly "-" it uses 'stdin' instead.
+ */
+void parse_pathspec_file(struct pathspec *pathspec,
+		    unsigned magic_mask,
+		    unsigned flags,
+		    const char *prefix,
+		    const char *file,
+		    int nul_term_line);
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
 void clear_pathspec(struct pathspec *);
 
Signature · 1 line
gitgitgadget
#11 lore raw

PATCH 3/5 reset: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This option solves the problem of commandline length limit for UI's built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UI's serve as assistants that help user run git commands. In this case, replacing familiar commands with plumbing commands will confuse most users.
2) Some UI's have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

The new option is designed to behave very close to pathspecs passed in commandline args, so that switching from one to another is simple.

The new option allows to read either a specified file or stdin. Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

Decisions taken for simplicity:
1) The new option is declared incompatible with other options that could use stdin.
2) It is not allowed to pass some refspecs in args and others in file.
3) New options do not have shorthands to avoid shorthand conflicts.

Also add new '--pathspec-file-null' switch that mirrors '-z' used in various places. Some porcelain commands, such as git commit, already use '-z', therefore it needed a new unambiguous name.

---
 Documentation/git-reset.txt    |  18 ++++-
 builtin/reset.c                |  22 +++++-
 t/t7107-reset-pathspec-file.sh | 126 +++++++++++++++++++++++++++++++++
 3 files changed, 161 insertions(+), 5 deletions(-)
 create mode 100755 t/t7107-reset-pathspec-file.sh
Documentation/git-reset.txt +15 -3
index b0ea6e0ce5..d484cd2827 100644
@@ -9,18 +9,20 @@ SYNOPSIS
 --------
 [verse]
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]
 'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-In the first and second form, copy entries from `<tree-ish>` to the index.
-In the third form, set the current branch head (`HEAD`) to `<commit>`,
+In the first three forms, copy entries from `<tree-ish>` to the index.
+In the last form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
-	This form resets the index entries for all `<pathspec>` to their
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]::
+	These forms reset the index entries for all `<pathspec>` to their
 	state at `<tree-ish>`.  (It does not affect the working tree or
 	the current branch.)
 +
@@ -107,6 +109,16 @@ OPTIONS
 	`reset.quiet` config option. `--quiet` and `--no-quiet` will
 	override the default behavior.
 
+--pathspec-from-file=<file>::
+	Read `<pathspec>` from `<file>` instead. If `<file>` is exactly `-`
+	then read from standard input. Pathspecs are separated by LF or
+	CR/LF. Pathspecs can be quoted as explained for the configuration
+	variable `core.quotePath` (see linkgit:git-config[1]). See also
+	`--pathspec-file-null` and global `--literal-pathspecs`.
+
+--pathspec-file-null::
+	Only meaningful with `--pathspec-from-file`. Pathspecs are
+	separated with NUL character and are not expected to be quoted.
 
 EXAMPLES
 --------
builtin/reset.c +20 -2
index fdd572168b..0eaa6b0bca 100644
@@ -31,6 +31,7 @@
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
 	N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
+	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-null]] [<tree-ish>]"),
 	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
 	NULL
 };
@@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0, unborn;
-	const char *rev;
+	int patch_mode = 0, pathspec_file_null = 0, unborn;
+	const char *rev, *pathspec_from_file = NULL;
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
@@ -306,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
+		OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file,
+				N_("read pathspecs from file")),
+		OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null,
+				N_("with --pathspec-from-file, pathspecs are separated with NUL character")),
 		OPT_END()
 	};
 
@@ -316,6 +321,19 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
+	if (pathspec_from_file) {
+		if (patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with path arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_null);
+	} else if (pathspec_file_null)
+		die(_("--pathspec-file-null requires --pathspec-from-file"));
+
 	unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
 	if (unborn) {
 		/* reset on unborn branch: treat as reset to empty tree */
t/t7107-reset-pathspec-file.sh new file +126 -0

126 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#20 lore raw

PATCH 4/5 doc: commit: unify <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Synchronize it to git add, which has a pretty good description. This also better disambiguates <file>... header.

---
 Documentation/git-commit.txt | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)
Documentation/git-commit.txt +5 -2
index afa7b75a23..4341d0e3ab 100644
@@ -13,7 +13,7 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<file>...]
+	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -345,12 +345,15 @@ changes to tracked files.
 \--::
 	Do not interpret any more arguments as options.
 
-<file>...::
+<pathspec>...::
 	When files are given on the command line, the command
 	commits the contents of the named files, without
 	recording the changes already staged.  The contents of
 	these files are also staged for the next commit on top
 	of what have been staged before.
++
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].
 
 :git-commit: 1
 include::date-formats.txt[]
Signature · 1 line
gitgitgadget
#25 lore raw

PATCH 5/5 commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This option solves the problem of commandline length limit for UI's built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UI's serve as assistants that help user run git commands. In this case, replacing familiar commands with plumbing commands will confuse most users.
2) Some UI's have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

The new option is designed to behave very close to pathspecs passed in commandline args, so that switching from one to another is simple.

The new option allows to read either a specified file or stdin. Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

Decisions taken for simplicity:
1) The new option is declared incompatible with other options that could use stdin.
2) It is not allowed to pass some refspecs in args and others in file.
3) New options do not have shorthands to avoid shorthand conflicts.

Also add new '--pathspec-file-null' switch that mirrors '-z' used in various places. Some porcelain commands, such as git commit, already use '-z', therefore it needed a new unambiguous name.

---
 Documentation/git-commit.txt    |  14 ++++-
 builtin/commit.c                |  25 ++++++--
 t/t7526-commit-pathspec-file.sh | 107 ++++++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+), 5 deletions(-)
 create mode 100755 t/t7526-commit-pathspec-file.sh
Documentation/git-commit.txt +13 -1
index 4341d0e3ab..ec4752298d 100644
@@ -13,7 +13,8 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
+	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-null]]
+	   [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -277,6 +278,17 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
 	the last commit without committing changes that have
 	already been staged. If used together with `--allow-empty`
 	paths are also not required, and an empty commit will be created.
+	
+--pathspec-from-file=<file>::
+	Read `<pathspec>` from `<file>` instead. If `<file>` is exactly `-`
+	then read from standard input. Pathspecs are separated by LF or
+	CR/LF. Pathspecs can be quoted as explained for the configuration
+	variable `core.quotePath` (see linkgit:git-config[1]). See also
+	`--pathspec-file-null` and global `--literal-pathspecs`.
+
+--pathspec-file-null::
+	Only meaningful with `--pathspec-from-file`. Pathspecs are
+	separated with NUL character and are not expected to be quoted.
 
 -u[<mode>]::
 --untracked-files[=<mode>]::
builtin/commit.c +21 -4
index e588bc6ad3..532f305926 100644
@@ -107,9 +107,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
-static int no_post_rewrite, allow_empty_message;
+static int no_post_rewrite, allow_empty_message, pathspec_file_null;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit;
+static char *sign_commit, *pathspec_from_file;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -343,6 +343,23 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 		       PATHSPEC_PREFER_FULL,
 		       prefix, argv);
 
+	if (pathspec_from_file) {
+		if (interactive)
+			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with path arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_null);
+	}
+	else if (pathspec_file_null)
+		die(_("--pathspec-file-null requires --pathspec-from-file"));
+
+	if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+		die(_("No paths with --include/--only does not make sense."));
+
 	if (read_cache_preload(&pathspec) < 0)
 		die(_("index file corrupt"));
 
@@ -1198,8 +1215,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
 	if (also + only + all + interactive > 1)
 		die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
-	if (argc == 0 && (also || (only && !amend && !allow_empty)))
-		die(_("No paths with --include/--only does not make sense."));
 	cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
 
 	handle_untracked_files_arg(s);
@@ -1535,6 +1550,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
 		OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
 		{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+		OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file, N_("read pathspecs from file")),
+		OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null, N_("with --pathspec-from-file, pathspecs are separated with NUL character")),
 		/* end commit contents options */
 
 		OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
t/t7526-commit-pathspec-file.sh new file +107 -0

107 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#5 lore raw

PATCH 2/5 doc: reset: unify <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Synchronize it to git add, which has a pretty good description.

---
 Documentation/git-reset.txt | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)
Documentation/git-reset.txt +15 -9
index 97e0544d9e..b0ea6e0ce5 100644
@@ -8,8 +8,8 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [-q] [<tree-ish>] [--] <paths>...
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
@@ -19,27 +19,33 @@ In the third form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 
-'git reset' [-q] [<tree-ish>] [--] <paths>...::
-	This form resets the index entries for all `<paths>` to their
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
+	This form resets the index entries for all `<pathspec>` to their
 	state at `<tree-ish>`.  (It does not affect the working tree or
 	the current branch.)
 +
-This means that `git reset <paths>` is the opposite of `git add
-<paths>`. This command is equivalent to
-`git restore [--source=<tree-ish>] --staged <paths>...`.
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].
 +
-After running `git reset <paths>` to update the index entry, you can
+This means that `git reset <pathspec>` is the opposite of `git add
+<pathspec>`. This command is equivalent to
+`git restore [--source=<tree-ish>] --staged <pathspec>...`.
++
+After running `git reset <pathspec>` to update the index entry, you can
 use linkgit:git-restore[1] to check the contents out of the index to
 the working tree. Alternatively, using linkgit:git-restore[1]
 and specifying a commit with `--source`, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]::
 	Interactively select hunks in the difference between the index
 	and `<tree-ish>` (defaults to `HEAD`).  The chosen hunks are applied
 	in reverse to the index.
 +
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].
++
 This means that `git reset -p` is the opposite of `git add -p`, i.e.
 you can use it to selectively reset hunks. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
Signature · 1 line
gitgitgadget
#2 lore raw

Re: [PATCH 1/5] pathspec: add new function to parse file

In reply to Alexandr Miloslavskiy via GitGitGadget

Hi Alexandr

Thanks for working on this. I've got a couple of comments about improving the error messages but this looks fine to me

Quoted 49 lines · Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This will be used to support the new option '--pathspec-from-file' in git add, git-commit, git reset etc.

Note also that we specifically handle CR/LF line endings to support Windows better.

To simplify code, file is first parsed into argv_array. This allows to avoid refactoring parse_pathspec().

I considered adding nul_term_line to flags instead, but decided that it doesn't fit there.

The new code is mostly taken from cmd_update_index() and split_mail_conv().

---
  pathspec.c | 41 +++++++++++++++++++++++++++++++++++++++++
  pathspec.h | 10 ++++++++++
  2 files changed, 51 insertions(+)
pathspec.c +10 -0
index 12c2b322b3..97d4e77875 100644
@@ -3,6 +3,8 @@
  #include "dir.h"
  #include "pathspec.h"
  #include "attr.h"
+#include "argv-array.h"
+#include "quote.h"
 
  /*
   * Finds which of the given pathspecs match items in the index.
@@ -613,6 +615,45 @@ void parse_pathspec(struct pathspec *pathspec,
  	}
  }
 
+void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
+			 unsigned flags, const char *prefix,
+			 const char *file, int nul_term_line)
+{
+	struct argv_array parsed_file = ARGV_ARRAY_INIT;
+	strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
+						       strbuf_getline;
+	struct strbuf buf = STRBUF_INIT;

pathspec.c void parse_pathspec(struct pathspec *pathspec, · line 635

Quoted 10 lines
pathspec.c +10 -0
+	struct strbuf unquoted = STRBUF_INIT;
+	FILE *in = NULL;
+
+	if (!strcmp(file, "-"))
+		in = stdin;
+	else
+		in = fopen(file, "r");
+
+	if (!in)
+		die(_("could not open '%s' for reading"), file);

die_errno() would give a more informative message here

pathspec.c void parse_pathspec(struct pathspec *pathspec, · line 641

Quoted 5 lines
pathspec.c +5 -0
+	while (getline_fn(&buf, in) != EOF) {
+		if (!nul_term_line && buf.buf[0] == '"') {
+			strbuf_reset(&unquoted);
+			if (unquote_c_style(&unquoted, buf.buf, NULL))
+				die(_("line is badly quoted"));

It would be nice to show the offending line in the error message

Quoted 26 lines
pathspec.c +15 -0
+			strbuf_swap(&buf, &unquoted);
+		}
+		argv_array_push(&parsed_file, buf.buf);
+		strbuf_reset(&buf);
+	}
+
+	strbuf_release(&unquoted);
+	strbuf_release(&buf);
+	if (in != stdin)
+		fclose(in);
+
+	parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv);
+	argv_array_clear(&parsed_file);
+}
+
  void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
  {
  	int i, j;
pathspec.h +0 -0
index 1c18a2c90c..d72e0b4c4a 100644
@@ -85,6 +85,16 @@ void parse_pathspec(struct pathspec *pathspec,
  		    unsigned flags,
  		    const char *prefix,
  		    const char **args);

pathspec.h void parse_pathspec(struct pathspec *pathspec, · line 97

Quoted 10 lines
pathspec.h +10 -0
+/*
+ * Same as parse_pathspec() but uses file as input.
+ * When 'file' is exactly "-" it uses 'stdin' instead.
+ */
+void parse_pathspec_file(struct pathspec *pathspec,
+		    unsigned magic_mask,
+		    unsigned flags,
+		    const char *prefix,
+		    const char *file,
+		    int nul_term_line);

Do these align with the 's' in "struct pathspec" ?

Best Wishes

Phillip

Quoted 4 lines
pathspec.h +0 -0
  void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
  void clear_pathspec(struct pathspec *);
 
#12 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

Hi Alexandr

This also looks good, I've got some minor comments below. If I'm complaining about whitespace and style issues you know the patch is quite good!

Quoted 11 lines · Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This option solves the problem of commandline length limit for UI's built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UI's serve as assistants that help user run git commands. In this case, replacing familiar commands with plumbing commands will confuse most users.
2) Some UI's have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

I think reset is a good example of the second reason - it's not straight forward to implement it in plumbing commands

Quoted 6 lines

The new option is designed to behave very close to pathspecs passed in commandline args, so that switching from one to another is simple.

The new option allows to read either a specified file or stdin. Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

I think this flexibility is a good idea

Quoted 3 lines

Decisions taken for simplicity:
1) The new option is declared incompatible with other options that could use stdin.

I'm confused reset does not use stdin does it?

Quoted 1 line

2) It is not allowed to pass some refspecs in args and others in file.

s/refspecs/pathspecs/ ?

Quoted 5 lines

3) New options do not have shorthands to avoid shorthand conflicts.

Also add new '--pathspec-file-null' switch that mirrors '-z' used in various places. Some porcelain commands, such as git commit, already use '-z', therefore it needed a new unambiguous name.

As the 'lines' in the file are nul terminated perhaps it would be better to call this --pathspec-file-nul or --nul-termination. I think the use of --null to mean nul termination for config was a mistake (for grep it matches what GUN grep does but it's still unfortunate).

Quoted 15 lines
---
  Documentation/git-reset.txt    |  18 ++++-
  builtin/reset.c                |  22 +++++-
  t/t7107-reset-pathspec-file.sh | 126 +++++++++++++++++++++++++++++++++
  3 files changed, 161 insertions(+), 5 deletions(-)

create mode 100755 t/t7107-reset-pathspec-file.sh

Documentation/git-reset.txt +0 -0
index b0ea6e0ce5..d484cd2827 100644

Documentation/git-reset.txt SYNOPSIS · line 12

Quoted 4 lines
Documentation/git-reset.txt +1 -0
@@ -9,18 +9,20 @@ SYNOPSIS
  --------
  [verse]
  'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]

--pathspec-file would be shorter and still conveys the intent of the option. Is this line missing a leading space?

Quoted 5 lines
Documentation/git-reset.txt +0 -0
  'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
  'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
  DESCRIPTION
  -----------

Documentation/git-reset.txt SYNOPSIS · line 24

Quoted 10 lines
Documentation/git-reset.txt +3 -3
-In the first and second form, copy entries from `<tree-ish>` to the index.
-In the third form, set the current branch head (`HEAD`) to `<commit>`,
+In the first three forms, copy entries from `<tree-ish>` to the index.
+In the last form, set the current branch head (`HEAD`) to `<commit>`,
  optionally modifying index and working tree to match.
  The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 
  'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
-	This form resets the index entries for all `<pathspec>` to their
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]::

Alignment again

Documentation/git-reset.txt SYNOPSIS · line 25

Quoted 1 line
Documentation/git-reset.txt +1 -0
+	These forms reset the index entries for all `<pathspec>` to their

The new form does not mention <pathspec> so this could be confusing

Documentation/git-reset.txt OPTIONS · line 113

Quoted 9 lines
Documentation/git-reset.txt +2 -0
  	state at `<tree-ish>`.  (It does not affect the working tree or
  	the current branch.)
  +
@@ -107,6 +109,16 @@ OPTIONS
  	`reset.quiet` config option. `--quiet` and `--no-quiet` will
  	override the default behavior.
 
+--pathspec-from-file=<file>::
+	Read `<pathspec>` from `<file>` instead. 

As we have a separate synopsis line for --pathspec-from-file which does not mention <pathspec> it might be better just to say "read pathspecs from <file> instead of the command line".

Quoted 47 lines

If <file> is exactly -

Documentation/git-reset.txt +8 -0
+	then read from standard input. Pathspecs are separated by LF or
+	CR/LF. Pathspecs can be quoted as explained for the configuration
+	variable `core.quotePath` (see linkgit:git-config[1]). See also
+	`--pathspec-file-null` and global `--literal-pathspecs`.
+
+--pathspec-file-null::
+	Only meaningful with `--pathspec-from-file`. Pathspecs are
+	separated with NUL character and are not expected to be quoted.
 
  EXAMPLES
  --------
builtin/reset.c +7 -2
index fdd572168b..0eaa6b0bca 100644
@@ -31,6 +31,7 @@
  static const char * const git_reset_usage[] = {
  	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
  	N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
+	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-null]] [<tree-ish>]"),
  	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
  	NULL
  };
@@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
  int cmd_reset(int argc, const char **argv, const char *prefix)
  {
  	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0, unborn;
-	const char *rev;
+	int patch_mode = 0, pathspec_file_null = 0, unborn;
+	const char *rev, *pathspec_from_file = NULL;
  	struct object_id oid;
  	struct pathspec pathspec;
  	int intent_to_add = 0;
@@ -306,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
  		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
  		OPT_BOOL('N', "intent-to-add", &intent_to_add,
  				N_("record only the fact that removed paths will be added later")),
+		OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file,
+				N_("read pathspecs from file")),
+		OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null,
+				N_("with --pathspec-from-file, pathspecs are separated with NUL character")),
  		OPT_END()
  	};
 

builtin/reset.c int cmd_reset(int argc, const char **argv, const char *prefix) · line 326

Quoted 6 lines
builtin/reset.c +3 -0
@@ -316,6 +321,19 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
  						PARSE_OPT_KEEP_DASHDASH);
  	parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
+	if (pathspec_from_file) {
+		if (patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));

This is sensible as -p is interactive so we wouldn't expect command line length to be an issue

builtin/reset.c int cmd_reset(int argc, const char **argv, const char *prefix) · line 335

Quoted 9 lines
builtin/reset.c +9 -0
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with path arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_null);
+	} else if (pathspec_file_null)
+		die(_("--pathspec-file-null requires --pathspec-from-file"));

Style nit: the coding guidelines state that if any branch of an if statement requires braces then all the branches should be braced. This is widely ignored though.

Quoted 21 lines
builtin/reset.c +1 -0
+
  	unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
  	if (unborn) {
  		/* reset on unborn branch: treat as reset to empty tree */
t/t7107-reset-pathspec-file.sh new file +11 -0
new file mode 100755
index 0000000000..cf7f085ad5
@@ -0,0 +1,126 @@
+#!/bin/sh
+
+test_description='reset --pathspec-from-file'
+
+. ./test-lib.sh
+
+cat > expect.a <<EOF
+ D fileA.t
+EOF
+
+cat > expect.ab <<EOF

t/t7107-reset-pathspec-file.sh line 21

Quoted 10 lines
t/t7107-reset-pathspec-file.sh new file +10 -0
+ D fileA.t
+ D fileB.t
+EOF
+
+cat > expect.a_bc_d <<EOF
+D  fileA.t
+ D fileB.t
+ D fileC.t
+D  fileD.t
+EOF

These days we tend to set up the expected files within the relevant test case using <<-\EOF to allow indentation and disallow substitution (unless it's needed of course)

Quoted 62 lines
t/t7107-reset-pathspec-file.sh new file +62 -0
+test_expect_success setup '
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add . &&
+	git commit --include . -m "Commit" &&
+	checkpoint=$(git rev-parse --verify HEAD)
+'
+
+restore_checkpoint () {
+	git reset --hard "$checkpoint"
+}
+
+verify_state () {
+	git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp "$1" actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t | git reset --pathspec-from-file=- &&
+
+	verify_state expect.a
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t >list &&
+	git reset --pathspec-from-file=list &&
+
+	verify_state expect.a
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf fileA.tQfileB.t | q_to_nul | git reset --pathspec-from-file=- --pathspec-file-null &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\nfileB.t" | git reset --pathspec-from-file=- &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\r\nfileB.t" | git reset --pathspec-from-file=- &&
+

t/t7107-reset-pathspec-file.sh line 94

Quoted 10 lines
t/t7107-reset-pathspec-file.sh new file +10 -0
+	verify_state expect.ab
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
+
+	verify_state expect.a

If I've understood correctly this doesn't test if a path is correctly unquoted, only that it is accepted.

Quoted 4 lines
t/t7107-reset-pathspec-file.sh new file +4 -0
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-null' '
+	restore_checkpoint &&

t/t7107-reset-pathspec-file.sh line 108

Quoted 10 lines
t/t7107-reset-pathspec-file.sh new file +10 -0
+
+	git rm fileA.t &&
+	printf "\"file\\101.t\"" >list &&
+	# Note: "git reset" has not yet learned to fail on wrong pathspecs
+	git reset --pathspec-from-file=list --pathspec-file-null &&
+	
+	test_must_fail verify_state expect.a
+'
+
+test_expect_success '--pathspec-from-file is not compatible with --soft --hard' '

s/--soft --hard/--soft or --hard/

t/t7107-reset-pathspec-file.sh line 117

Quoted 9 lines
t/t7107-reset-pathspec-file.sh new file +9 -0
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t >list &&
+	test_must_fail git reset --soft --pathspec-from-file=list &&
+	test_must_fail git reset --hard --pathspec-from-file=list
+'
+
+test_expect_success 'only touches what was listed' '

s/^/--pathspec-from-file / ?

Best Wishes

Phillip

Quoted 10 lines
t/t7107-reset-pathspec-file.sh new file +9 -0
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t fileC.t fileD.t &&
+	printf "fileB.t\nfileC.t" | git reset --pathspec-from-file=- &&
+
+	verify_state expect.a_bc_d
+'
+
+test_done
#16 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

Hi Alexandr

Quoted 26 lines · Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]> [...]

builtin/reset.c +3 -2
index fdd572168b..0eaa6b0bca 100644
@@ -31,6 +31,7 @@
  static const char * const git_reset_usage[] = {
  	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
  	N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
+	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-null]] [<tree-ish>]"),
  	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
  	NULL
  };
@@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
  int cmd_reset(int argc, const char **argv, const char *prefix)
  {
  	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0, unborn;
-	const char *rev;
+	int patch_mode = 0, pathspec_file_null = 0, unborn;
+	const char *rev, *pathspec_from_file = NULL;
  	struct object_id oid;
  	struct pathspec pathspec;
  	int intent_to_add = 0;

builtin/reset.c int cmd_reset(int argc, const char **argv, const char *prefix) · line 313

Quoted 7 lines
builtin/reset.c +4 -0
@@ -306,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
  		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
  		OPT_BOOL('N', "intent-to-add", &intent_to_add,
  				N_("record only the fact that removed paths will be added later")),
+		OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file,
+				N_("read pathspecs from file")),
+		OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null,
+				N_("with --pathspec-from-file, pathspecs are separated with NUL character")),

One thing I forgot to mention before is that if you're going to add these options to a number of commands then maybe it is worth defining a macro for each one in parse-options.h. There are a couple of examples of this at the end of that file.

Best Wishes

Phillip

Quoted 156 lines
builtin/reset.c +13 -0
  		OPT_END()
  	};
 
@@ -316,6 +321,19 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
  						PARSE_OPT_KEEP_DASHDASH);
  	parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
+	if (pathspec_from_file) {
+		if (patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with path arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_null);
+	} else if (pathspec_file_null)
+		die(_("--pathspec-file-null requires --pathspec-from-file"));
+
  	unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
  	if (unborn) {
  		/* reset on unborn branch: treat as reset to empty tree */
t/t7107-reset-pathspec-file.sh new file +126 -0
new file mode 100755
index 0000000000..cf7f085ad5
@@ -0,0 +1,126 @@
+#!/bin/sh
+
+test_description='reset --pathspec-from-file'
+
+. ./test-lib.sh
+
+cat > expect.a <<EOF
+ D fileA.t
+EOF
+
+cat > expect.ab <<EOF
+ D fileA.t
+ D fileB.t
+EOF
+
+cat > expect.a_bc_d <<EOF
+D  fileA.t
+ D fileB.t
+ D fileC.t
+D  fileD.t
+EOF
+
+test_expect_success setup '
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add . &&
+	git commit --include . -m "Commit" &&
+	checkpoint=$(git rev-parse --verify HEAD)
+'
+
+restore_checkpoint () {
+	git reset --hard "$checkpoint"
+}
+
+verify_state () {
+	git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp "$1" actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t | git reset --pathspec-from-file=- &&
+
+	verify_state expect.a
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t >list &&
+	git reset --pathspec-from-file=list &&
+
+	verify_state expect.a
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf fileA.tQfileB.t | q_to_nul | git reset --pathspec-from-file=- --pathspec-file-null &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\nfileB.t" | git reset --pathspec-from-file=- &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf "fileA.t\r\nfileB.t" | git reset --pathspec-from-file=- &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
+
+	verify_state expect.a
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-null' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	printf "\"file\\101.t\"" >list &&
+	# Note: "git reset" has not yet learned to fail on wrong pathspecs
+	git reset --pathspec-from-file=list --pathspec-file-null &&
+	
+	test_must_fail verify_state expect.a
+'
+
+test_expect_success '--pathspec-from-file is not compatible with --soft --hard' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t >list &&
+	test_must_fail git reset --soft --pathspec-from-file=list &&
+	test_must_fail git reset --hard --pathspec-from-file=list
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t fileC.t fileD.t &&
+	printf "fileB.t\nfileC.t" | git reset --pathspec-from-file=- &&
+
+	verify_state expect.a_bc_d
+'
+
+test_done
#26 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

Hi Alexandr

Quoted 25 lines · Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This option solves the problem of commandline length limit for UI's built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UI's serve as assistants that help user run git commands. In this case, replacing familiar commands with plumbing commands will confuse most users.
2) Some UI's have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

The new option is designed to behave very close to pathspecs passed in commandline args, so that switching from one to another is simple.

The new option allows to read either a specified file or stdin. Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

Decisions taken for simplicity:
1) The new option is declared incompatible with other options that could use stdin.
2) It is not allowed to pass some refspecs in args and others in file.
3) New options do not have shorthands to avoid shorthand conflicts.

Quoted 3 lines

Also add new '--pathspec-file-null' switch that mirrors '-z' used in various places. Some porcelain commands, such as git commit, already use '-z', therefore it needed a new unambiguous name.

It might be worth tailoring the message to the command rather than having exactly the same message for commit and reset

Quoted 28 lines
---
  Documentation/git-commit.txt    |  14 ++++-
  builtin/commit.c                |  25 ++++++--
  t/t7526-commit-pathspec-file.sh | 107 ++++++++++++++++++++++++++++++++
  3 files changed, 141 insertions(+), 5 deletions(-)

create mode 100755 t/t7526-commit-pathspec-file.sh

Documentation/git-commit.txt +3 -1
index 4341d0e3ab..ec4752298d 100644
@@ -13,7 +13,8 @@ SYNOPSIS
  	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
  	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
  	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
+	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-null]]
+	   [-S[<keyid>]] [--] [<pathspec>...]
 
  DESCRIPTION
  -----------
@@ -277,6 +278,17 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
  	the last commit without committing changes that have
  	already been staged. If used together with `--allow-empty`
  	paths are also not required, and an empty commit will be created.
+	

Documentation/git-commit.txt FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) · line 291

Quoted 10 lines
Documentation/git-commit.txt +10 -0
+--pathspec-from-file=<file>::
+	Read `<pathspec>` from `<file>` instead. If `<file>` is exactly `-`
+	then read from standard input. Pathspecs are separated by LF or
+	CR/LF. Pathspecs can be quoted as explained for the configuration
+	variable `core.quotePath` (see linkgit:git-config[1]). See also
+	`--pathspec-file-null` and global `--literal-pathspecs`.
+
+--pathspec-file-null::
+	Only meaningful with `--pathspec-from-file`. Pathspecs are
+	separated with NUL character and are not expected to be quoted.

I think my comments from patch 3 about <pathspecs> and the option names apply here as well

Quoted 28 lines
Documentation/git-commit.txt +0 -0
  -u[<mode>]::
  --untracked-files[=<mode>]::
builtin/commit.c +8 -2
index e588bc6ad3..532f305926 100644
@@ -107,9 +107,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff;
  static int edit_flag = -1; /* unspecified */
  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
  static int config_commit_verbose = -1; /* unspecified */
-static int no_post_rewrite, allow_empty_message;
+static int no_post_rewrite, allow_empty_message, pathspec_file_null;
  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit;
+static char *sign_commit, *pathspec_from_file;
 
  /*
   * The default commit message cleanup mode will remove the lines
@@ -343,6 +343,23 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
  		       PATHSPEC_PREFER_FULL,
  		       prefix, argv);
 
+	if (pathspec_from_file) {
+		if (interactive)
+			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with path arguments"));

builtin/commit.c static const char *prepare_index(int argc, const char **argv, const char *prefix · line 361

Quoted 10 lines
builtin/commit.c +10 -0
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_null);
+	}
+	else if (pathspec_file_null)
+		die(_("--pathspec-file-null requires --pathspec-from-file"));
+
+	if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+		die(_("No paths with --include/--only does not make sense."));

I wonder if there is a way of calling parse_pathspec_file() from parse_and_validate_options() instead. Otherwise we end up validating options here instead which is a bit messy.

t/t7526-commit-pathspec-file.sh

Quoted 26 lines
builtin/commit.c +2 -2
  	if (read_cache_preload(&pathspec) < 0)
  		die(_("index file corrupt"));
 
@@ -1198,8 +1215,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
  	if (also + only + all + interactive > 1)
  		die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
-	if (argc == 0 && (also || (only && !amend && !allow_empty)))
-		die(_("No paths with --include/--only does not make sense."));
  	cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
 
  	handle_untracked_files_arg(s);
@@ -1535,6 +1550,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
  		OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
  		OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
  		{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+		OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file, N_("read pathspecs from file")),
+		OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null, N_("with --pathspec-from-file, pathspecs are separated with NUL character")),
  		/* end commit contents options */
 
  		OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
t/t7526-commit-pathspec-file.sh new file +0 -0
new file mode 100755
index 0000000000..c5d68e01e6

The test comments from patch 3 apply here as well I think.

Overall this series is nicely structured and is looking pretty good

Best Wishes

Phillip

Quoted 109 lines
t/t7526-commit-pathspec-file.sh new file +107 -0
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+test_description='commit --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+cat > expect.a <<EOF
+A	fileA.t
+EOF
+
+cat > expect.ab <<EOF
+A	fileA.t
+A	fileB.t
+EOF
+
+cat > expect.bc <<EOF
+A	fileB.t
+A	fileC.t
+EOF
+
+test_expect_success setup '
+	test_commit file0 &&
+	checkpoint=$(git rev-parse --verify HEAD) &&
+	
+	echo A >fileA.t &&
+	echo B >fileB.t &&
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add fileA.t fileB.t fileC.t fileD.t
+'
+
+restore_checkpoint () {
+	git reset --soft "$checkpoint"
+}
+
+verify_state () {
+	git diff-tree --no-commit-id --name-status -r HEAD >actual &&
+	test_cmp "$1" actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	echo fileA.t | git commit --pathspec-from-file=- -m "Commit" &&
+
+	verify_state expect.a
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	echo fileA.t >list &&
+	git commit --pathspec-from-file=list -m "Commit" &&
+
+	verify_state expect.a
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	printf fileA.tQfileB.t | q_to_nul | git commit --pathspec-from-file=- --pathspec-file-null -m "Commit" &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'LF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\nfileB.t" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'CRLF delimiters' '
+	restore_checkpoint &&
+
+	printf "fileA.t\r\nfileB.t" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	verify_state expect.ab
+'
+
+test_expect_success 'quotes' '
+	restore_checkpoint &&
+
+	printf "\"file\\101.t\"" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	verify_state expect.a
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-null' '
+	restore_checkpoint &&
+
+	printf "\"file\\101.t\"" >list &&
+	test_must_fail git commit --pathspec-from-file=list --pathspec-file-null -m "Commit"
+'
+
+test_expect_success 'only touches what was listed' '
+	restore_checkpoint &&
+
+	printf "fileB.t\nfileC.t" | git commit --pathspec-from-file=- -m "Commit" &&
+
+	verify_state expect.bc
+'
+
+test_done
#3 lore raw

Re: [PATCH 1/5] pathspec: add new function to parse file

In reply to Phillip Wood
Quoted 4 lines · Phillip Wood
Quoted 2 lines

+    if (!in)
+        die(_("could not open '%s' for reading"), file);

die_errno() would give a more informative message here

Thanks for the pointer!

Quoted 4 lines
Quoted 2 lines

+            if (unquote_c_style(&unquoted, buf.buf, NULL)) +                die(_("line is badly quoted"));

It would be nice to show the offending line in the error message

Good idea.

Quoted 8 lines
Quoted 6 lines

+void parse_pathspec_file(struct pathspec *pathspec, +            unsigned magic_mask,
+            unsigned flags,
+            const char *prefix,
+            const char *file,
+            int nul_term_line);

Do these align with the 's' in "struct pathspec" ?

Sorry, still struggling with my VS that tries to do crazy things. Will fix.

#13 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Phillip Wood

Hi Alexandr

Quoted 10 lines · Phillip Wood

[...]

t/t7107-reset-pathspec-file.sh line 94

Quoted 7 lines
t/t7107-reset-pathspec-file.sh new file +7 -0
+test_expect_success 'quotes' '
+    restore_checkpoint &&
+
+    git rm fileA.t &&
+    printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
+
+    verify_state expect.a

If I've understood correctly this doesn't test if a path is correctly unquoted, only that it is accepted.

Oh I think I've misunderstood. The '\\' in unquoted by printf, so git sees '\101' which is A so that is a real file.

Sorry for the confusion

Phillip

Quoted 45 lines
Quoted 6 lines

+'

t/t7107-reset-pathspec-file.sh new file +4 -0
+
+test_expect_success 'quotes not compatible with --pathspec-file-null' '
+    restore_checkpoint &&
+

t/t7107-reset-pathspec-file.sh

Quoted 10 lines
t/t7107-reset-pathspec-file.sh new file +9 -1
+    git rm fileA.t &&
+    printf "\"file\\101.t\"" >list &&
+    # Note: "git reset" has not yet learned to fail on wrong pathspecs
+    git reset --pathspec-from-file=list --pathspec-file-null &&
+
+    test_must_fail verify_state expect.a
+'
+
+test_expect_success '--pathspec-from-file is not compatible with 
--soft --hard' '

s/--soft --hard/--soft or --hard/

t/t7107-reset-pathspec-file.sh line 117

Quoted 9 lines

+    restore_checkpoint &&

t/t7107-reset-pathspec-file.sh new file +8 -0
+
+    git rm fileA.t &&
+    echo fileA.t >list &&
+    test_must_fail git reset --soft --pathspec-from-file=list &&
+    test_must_fail git reset --hard --pathspec-from-file=list
+'
+
+test_expect_success 'only touches what was listed' '

s/^/--pathspec-from-file / ?

Best Wishes

Phillip

Quoted 10 lines

+    restore_checkpoint &&

t/t7107-reset-pathspec-file.sh new file +8 -0
+
+    git rm fileA.t fileB.t fileC.t fileD.t &&
+    printf "fileB.t\nfileC.t" | git reset --pathspec-from-file=- &&
+
+    verify_state expect.a_bc_d
+'
+
+test_done
#14 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Phillip Wood
Quoted 5 lines · Phillip Wood
Quoted 3 lines

The new option allows to read either a specified file or stdin. Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

I think this flexibility is a good idea

Thanks for your support :) Previously the opinions were mixed and I was a bit afraid that this will invoke a new round of discussions.

Quoted 5 lines
Quoted 3 lines

Decisions taken for simplicity:
1) The new option is declared incompatible with other options that could use stdin.

I'm confused reset does not use stdin does it?

I understand that '--patch' interacts with user via stdin. Will double-check tomorrow.

Quoted 3 lines
Quoted 1 line

2) It is not allowed to pass some refspecs in args and others in file.

s/refspecs/pathspecs/ ?

Thanks! Not quite used to git speak and mix up things sometimes.

Quoted 5 lines
Quoted 3 lines

Also add new '--pathspec-file-null' switch that mirrors '-z' used in various places. Some porcelain commands, such as git commit, already use '-z', therefore it needed a new unambiguous name.

As the 'lines' in the file are nul terminated perhaps it would be better to call this --pathspec-file-nul or --nul-termination. I think the use of --null to mean nul termination for config was a mistake (for grep it matches what GUN grep does but it's still unfortunate).

OK, will change to '--pathspec-file-nul'

Quoted 5 lines
Quoted 2 lines

+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]

--pathspec-file would be shorter and still conveys the intent of the option. Is this line missing a leading space?

'--pathspec-from-file' was kind of suggested instead of '--paths-file' by Junio here:
https://public-inbox.org/git/[email protected]/

so Junio added '-from' to previous writing. Hmm. What do you think, taking Junio's message into account?

As for whitespaces, sorry, will fix.

Quoted 4 lines
Quoted 2 lines

+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]::

Alignment again

Will fix

Quoted 3 lines
Quoted 1 line

+    These forms reset the index entries for all <pathspec> to their

The new form does not mention <pathspec> so this could be confusing

Shall I replace '<pathspec>' with 'pathspecs' ?

Quoted 1 line

As we have a separate synopsis line for --pathspec-from-file which does not mention <pathspec> it might be better just to say "read pathspecs from <file> instead of the command line".

OK

Quoted 5 lines
Quoted 3 lines

+    if (pathspec_from_file) {
+        if (patch_mode)
+            die(_("--pathspec-from-file is incompatible with --patch"));

This is sensible as -p is interactive so we wouldn't expect command line length to be an issue

Yes, I also thought so. I doubt that user is willing to interactively decide on hundreds of files.

Quoted 4 lines
Quoted 2 lines

+    } else if (pathspec_file_null)
+        die(_("--pathspec-file-null requires --pathspec-from-file"));

Style nit: the coding guidelines state that if any branch of an if statement requires braces then all the branches should be braced. This is widely ignored though.

Took this code from a guy who ignored it :) But sure, will change.

Quoted 1 line

These days we tend to set up the expected files within the relevant test case using <<-\EOF to allow indentation and disallow substitution (unless it's needed of course)

I'll try to change this.

Quoted 9 lines

t/t7107-reset-pathspec-file.sh line 94

Quoted 7 lines
t/t7107-reset-pathspec-file.sh new file +7 -0
+test_expect_success 'quotes' '
+    restore_checkpoint &&
+
+    git rm fileA.t &&
+    printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
+
+    verify_state expect.a

If I've understood correctly this doesn't test if a path is correctly unquoted, only that it is accepted.

In my understanding, 'verify_state expect.a' should test that it's correctly understood. Am I wrong?

Quoted 4 lines
Quoted 2 lines

+test_expect_success '--pathspec-from-file is not compatible with --soft --hard' '

s/--soft --hard/--soft or --hard/

Good idea.

Quoted 3 lines
Quoted 1 line

+test_expect_success 'only touches what was listed' '

s/^/--pathspec-from-file / ?

I thought that whole test package is about '--pathspec-from-file' so I'd rather not repeat that in every test name. Shall I change that?

#17 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Phillip Wood
Quoted 7 lines · Phillip Wood
Quoted 5 lines

+        OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file, +                N_("read pathspecs from file")),
+        OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null, +                N_("with --pathspec-from-file, pathspecs are separated with NUL character")),

One thing I forgot to mention before is that if you're going to add these options to a number of commands then maybe it is worth defining a macro for each one in parse-options.h. There are a couple of examples of this at the end of that file.

Thanks for the pointer!

#27 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

In reply to Phillip Wood
Quoted 5 lines · Phillip Wood
Quoted 3 lines

Also add new '--pathspec-file-null' switch that mirrors '-z' used in various places. Some porcelain commands, such as git commit, already use '-z', therefore it needed a new unambiguous name.

It might be worth tailoring the message to the command rather than having exactly the same message for commit and reset

I also was somewhat unhappy about duplication. But I didn't figure how to do that correctly. Currently the messages for 'git reset' and 'git commit' are almost identical.

Maybe in 2nd commit I should say something like "Extend --pathspec-file-null support to git commit (see previous patch for git reset)" ?

Quoted 1 line

I think my comments from patch 3 about <pathspecs> and the option names apply here as well

Yes, sure, I will try to apply your suggestions to all patches. Hopefully without forgetting things :)

Quoted 4 lines
Quoted 2 lines

+    if (!pathspec.nr && (also || (only && !amend && !allow_empty))) +        die(_("No paths with --include/--only does not make sense."));

I wonder if there is a way of calling parse_pathspec_file() from parse_and_validate_options() instead. Otherwise we end up validating options here instead which is a bit messy.

Yes, I was also somewhat unhappy about that. I will give it more thought.

Quoted 1 line

Overall this series is nicely structured and is looking pretty good

Thanks, and also thanks for reviewing my patches!

#6 lore raw

Re: [PATCH 2/5] doc: reset: unify <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Quoted 8 lines · writes:

optionally modifying index and working tree to match. The <tree-ish>/<commit> defaults to HEAD in all forms.

-'git reset' [-q] [<tree-ish>] [--] <paths>...::

Documentation/git-reset.txt +2 -1
-	This form resets the index entries for all `<paths>` to their
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
+	This form resets the index entries for all `<pathspec>` to their
 	state at `<tree-ish>`.  (It does not affect the working tree or

Documentation/git-reset.txt In the third form, set the current branch head (`HEAD`) to `<commit>`, · line 30

Quoted 10 lines
Documentation/git-reset.txt +3 -4
 	the current branch.)
 +
-This means that `git reset <paths>` is the opposite of `git add
-<paths>`. This command is equivalent to
-`git restore [--source=<tree-ish>] --staged <paths>...`.
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].
 +
-After running `git reset <paths>` to update the index entry, you can
+This means that `git reset <pathspec>` is the opposite of `git add

It is good to show which document to read more on <pathspec>, but inserting it just before "This means..." breaks the flow of thought.

As we introduce/explain tree-ish and commit used in the synopsis in the ealier part of the description, it probably is a much better place to also introduce/explain pathspec, perhaps like so:

In the first and second form, ...
In the third form, ...
The <tree-ish>/<commit> defaults to HEAD in all forms.
+The <pathspec> is used to limit the paths affected by the +operation in the first two forms (see the entry for +'pathspec' in linkgit:gitglossary[7] for more details).

Quoted 3 lines

+This means that git reset <pathspec> is the opposite of git add +<pathspec>. This command is equivalent to
+git restore [--source=<tree-ish>] --staged <pathspec>....

Any time I see "... X. This means Y." either in the doc or in the proposed log message, I wish the author (not you in this case, obviously) thought twice about rewriting so that they do not say one thing and immediately have to rephrase it, i.e. either just say Y without saying X, or saying X more clearly without having to say Y.

In this case, however, I think X and Y are related but both relevant. The subcommand resets the index entries for chosen paths to match what is in the tree-ish, which is the same as restoring from a tree to the index.

It is not quite the opposite of adding to the index from the working tree. In this sequence:

	$ edit newfile
	$ git add newfile

and then further

	$ edit newfile
	$ git add newfile
	$ git reset -- newfile

we are taken back to the state _before_ any of the changes made to newfile (in fact, since HEAD does not have newfile, the resulting index would not know about it, either).

Thanks.

#18 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Quoted 8 lines · writes:

From: Alexandr Miloslavskiy <[email protected]>

This option solves the problem of commandline length limit for UI's built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UI's serve as assistants that help user run git commands. In this case, replacing familiar commands with plumbing commands will confuse most users.

"UI's that serve as assistants that help user run git commands" does not have to avoid plumbing commands at all. Only the ones that "show" the commands that are run on behalf of the users (perhaps so that the users can learn from such examples?) do, and I think I learned that it was your motivating use case from an earlier discussion. Perhaps

UIs that help users to formulate git commands to run need to present Porcelain commands to be used, as it is not reasonable to demonstrate arcane combination of plumbing commands as an example for their interactive use.

would probably be more readable without bending what you wanted to say too much?

Quoted 3 lines

2) Some UI's have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

There is not a lot of sympathy for such argument ;-)

Quoted 1 line

2) It is not allowed to pass some refspecs in args and others in file.

Did you mean refspec, not pathspec?

Quoted 5 lines

3) New options do not have shorthands to avoid shorthand conflicts.

Also add new '--pathspec-file-null' switch that mirrors '-z' used in various places. Some porcelain commands, such as git commit, already use '-z', therefore it needed a new unambiguous name.

I do not understand this part. Wouldn't it natural to expect that "-z", when used with "--pathspec-from-file", tell us that the named file is NUL delimited collection of records? What's different about "--pathspec-file-null" that it is confusing to all it "-z"?

I actually do not mind not having "-z" and using only "--pathspec-file-null". A new option with long name that feels similar to existing "-z" but does sufficiently different things so that it needs a different name, however, feels counter-productive for the purpose of using it in the UI that produces commands to be learned by end-users.

Documentation/git-reset.txt OPTIONS · line 115

Quoted 4 lines

+--pathspec-from-file=<file>::

Documentation/git-reset.txt +3 -0
+	Read `<pathspec>` from `<file>` instead. If `<file>` is exactly `-`
+	then read from standard input. Pathspecs are separated by LF or
+	CR/LF. Pathspecs can be quoted as explained for the configuration

When I invented the terms "pathspec", "refspec", etc. for this project, I meant them to be collective nouns. A pathspec is a set of pathspec elements, each of which is usually given as a separate argument on the command line. So "Read pathspec from file" is good (not "Read pathspecs from file").

And "Pathspec elements" are separated by LF or CR/LF.

A tangent. Since we do not allow NUL in a pathspec element, we do not even need the "-z" option. When we read pathspec from a file, we can take any of CRLF, LF or NUL as the separator.

Ah, sorry, that would not help very much. With "-z" we are allowing to express pathspec elements inside which there are embedded LF or CR/LF. Sorry about the noise.

Documentation/git-reset.txt OPTIONS · line 121

Quoted 3 lines

+--pathspec-file-null::

Documentation/git-reset.txt +2 -0
+	Only meaningful with `--pathspec-from-file`. Pathspecs are
+	separated with NUL character and are not expected to be quoted.

OK.

builtin/reset.c int cmd_reset(int argc, const char **argv, const char *prefix) · line 329

Quoted 6 lines
builtin/reset.c +6 -0
+	if (pathspec_from_file) {
+		if (patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with path arguments"));

Shouldn't the error message say pathspec arguments instead?

t/t7107-reset-pathspec-file.sh line 7

Quoted 13 lines
t/t7107-reset-pathspec-file.sh new file +7 -0
new file mode 100755
index 0000000000..cf7f085ad5
@@ -0,0 +1,126 @@
+#!/bin/sh
+
+test_description='reset --pathspec-from-file'
+
+. ./test-lib.sh
+
+cat > expect.a <<EOF

Style:

- Modern test scripts strive to perform these set-up procedure inside (the first) test_expect_success.

- No SP between the redirection operator and its source/destination filename.

- Quote end-of-here-document (EOF in the above) if you do not rely on parameter interpolation inside the here document; this helps readers by telling them that what is presented is used verbatim.

Quoted 18 lines
t/t7107-reset-pathspec-file.sh new file +18 -0
+ D fileA.t
+EOF
+
+cat > expect.ab <<EOF
+ D fileA.t
+ D fileB.t
+EOF
+
+cat > expect.a_bc_d <<EOF
+D  fileA.t
+ D fileB.t
+ D fileC.t
+D  fileD.t
+EOF
+
+test_expect_success setup '
+	echo A >fileA.t &&
+	echo B >fileB.t &&

t/t7107-reset-pathspec-file.sh line 35

Quoted 10 lines
t/t7107-reset-pathspec-file.sh new file +10 -0
+	echo C >fileC.t &&
+	echo D >fileD.t &&
+	git add . &&
+	git commit --include . -m "Commit" &&
+	checkpoint=$(git rev-parse --verify HEAD)
+'
+
+restore_checkpoint () {
+	git reset --hard "$checkpoint"
+}

Hmm, wouldn't it be cleaner to use a lightweight tag or something to keep checkpoint, instead of a variable that is hard to examine when tests break and needs debugging?

Quoted 18 lines
t/t7107-reset-pathspec-file.sh new file +18 -0
+verify_state () {
+	git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual &&
+	test_cmp "$1" actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t | git reset --pathspec-from-file=- &&
+	verify_state expect.a
+'
+
+test_expect_success '--pathspec-from-file from file' '
+	restore_checkpoint &&
+
+	git rm fileA.t &&
+	echo fileA.t >list &&

t/t7107-reset-pathspec-file.sh line 65

Quoted 10 lines
t/t7107-reset-pathspec-file.sh new file +10 -0
+	git reset --pathspec-from-file=list &&
+
+	verify_state expect.a
+'
+
+test_expect_success 'NUL delimiters' '
+	restore_checkpoint &&
+
+	git rm fileA.t fileB.t &&
+	printf fileA.tQfileB.t | q_to_nul | git reset --pathspec-from-file=- --pathspec-file-null &&

This feeds "fileA.t<NUL>fileB.t" without <NUL> after "fileB.t" to the command. Intended?

Rather, perhaps

printf "%s\0" fileA.t fileB.t

without q-to-nul, once you use printf anyway?

If you truly mean "delimiter" (as opposed to "terminator"),

printf "fileA.t\0fileB.t"

can also lose " | q_to_nul".

Thanks.

#21 lore raw

Re: [PATCH 4/5] doc: commit: unify <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Quoted 4 lines · writes:

From: Alexandr Miloslavskiy <[email protected]>

Synchronize it to git add, which has a pretty good description. This also better disambiguates <file>... header.

"When files are given on..." is no longer true with this change (it wasn't true with the code before this change anyway).

When pathspec is given on the command line, commit the contents of the files that match the pathspec without recording the changes already added to the index. ...

The second sentence also says "these files", but that can be left as-is, since it would refer to "the files that match ..." explained in the above sentence.

Documentation/git-commit.txt changes to tracked files. · line 356

Quoted 2 lines
Documentation/git-commit.txt +2 -0
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].

I am not sure if we want to repeat this all over the place.

We do not say "For details about the <commit> syntax, see the 'SPECIFYING REVISIONS' section of linkgit:git-rev-parse[1]" for every command that takes <commit> from the command line.

Is your urge to suggest adding this sentence coming from that you are much more familiar with <commit> than <pathspec>? In other words, if you were more familiar with Git, would you still be adding this (and not corresponding one for <commit>)?

#36 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Quoted 11 lines · writes:

From: Alexandr Miloslavskiy <[email protected]>

This option solves the problem of commandline length limit for UI's built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UI's serve as assistants that help user run git commands. In this case, replacing familiar commands with plumbing commands will confuse most users.
2) Some UI's have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

I think all the comments I made on "reset" with these two options, both to the proposed log message and to the patch text, applies to this this step, too, so I won't repeat them.

Thanks for working on this topic.

#38 lore raw

PATCH v2 1/6 parse-options.h: add new options `--pathspec-from-file`, `--pathspec-file-nul`

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Support for various porcelain commands will arrive via additional patches.

--pathspec-from-file solves the problem of commandline length limit for UIs built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UIs show executed commands to user. In this case, porcelain commands are expected. One reason for that is letting user learn git commands by clicking UI buttons. The other reason is letting user study the history of commands in case of any unexpected results. Both of these will lose most of their value if UI uses combinations of arcane plumbing commands.
2) Some UIs have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

--pathspec-from-file will behave very close to pathspec passed in commandline args, so that switching from one to another is simple.

--pathspec-from-file will read either a specified file or stdin (when file is exactly "-"). Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

--pathspec-file-nul switch mirrors -z already used in various places. Some porcelain commands, such as git commit, already use -z, therefore it needed a new unambiguous name.

New options do not have shorthands to avoid shorthand conflicts. It is not expected that they will be typed in console.

---
 parse-options.h | 2 ++
 1 file changed, 2 insertions(+)
parse-options.h +2 -0
index 38a33a087e..c6cc01e715 100644
@@ -330,5 +330,7 @@ int parse_opt_passthru_argv(const struct option *, const char *, int);
 #define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
 #define OPT_WITHOUT(v, h) _OPT_CONTAINS_OR_WITH("without", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
 #define OPT_CLEANUP(v) OPT_STRING(0, "cleanup", v, N_("mode"), N_("how to strip spaces and #comments from message"))
+#define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
+#define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, "pathspec-file-nul", v, N_("with --pathspec-from-file, pathspec elements are separated with NUL character"))
 
 #endif
Signature · 1 line
gitgitgadget
#39 lore raw

PATCH v2 2/6 pathspec: add new function to parse file

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This will be used to support the new option '--pathspec-from-file' in git add, git-commit, git reset etc.

Note also that we specifically handle CR/LF line endings to support Windows better.

To simplify code, file is first parsed into argv_array. This allows to avoid refactoring parse_pathspec().

I considered adding nul_term_line to flags instead, but decided that it doesn't fit there.

The new code is mostly taken from cmd_update_index() and split_mail_conv().

---
 pathspec.c | 38 ++++++++++++++++++++++++++++++++++++++
 pathspec.h | 10 ++++++++++
 2 files changed, 48 insertions(+)
pathspec.c +38 -0
index 12c2b322b3..2e9b376318 100644
@@ -3,6 +3,8 @@
 #include "dir.h"
 #include "pathspec.h"
 #include "attr.h"
+#include "argv-array.h"
+#include "quote.h"
 
 /*
  * Finds which of the given pathspecs match items in the index.
@@ -613,6 +615,42 @@ void parse_pathspec(struct pathspec *pathspec,
 	}
 }
 
+void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
+			 unsigned flags, const char *prefix,
+			 const char *file, int nul_term_line)
+{
+	struct argv_array parsed_file = ARGV_ARRAY_INIT;
+	strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
+						       strbuf_getline;
+	struct strbuf buf = STRBUF_INIT;
+	struct strbuf unquoted = STRBUF_INIT;
+	FILE *in = NULL;
+
+	if (!strcmp(file, "-"))
+		in = stdin;
+	else
+		in = xfopen(file, "r");
+
+	while (getline_fn(&buf, in) != EOF) {
+		if (!nul_term_line && buf.buf[0] == '"') {
+			strbuf_reset(&unquoted);
+			if (unquote_c_style(&unquoted, buf.buf, NULL))
+				die(_("line is badly quoted: %s"), buf.buf);
+			strbuf_swap(&buf, &unquoted);
+		}
+		argv_array_push(&parsed_file, buf.buf);
+		strbuf_reset(&buf);
+	}
+
+	strbuf_release(&unquoted);
+	strbuf_release(&buf);
+	if (in != stdin)
+		fclose(in);
+
+	parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv);
+	argv_array_clear(&parsed_file);
+}
+
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
 {
 	int i, j;
pathspec.h +10 -0
index 1c18a2c90c..a27dc81ba2 100644
@@ -85,6 +85,16 @@ void parse_pathspec(struct pathspec *pathspec,
 		    unsigned flags,
 		    const char *prefix,
 		    const char **args);
+/*
+ * Same as parse_pathspec() but uses file as input.
+ * When 'file' is exactly "-" it uses 'stdin' instead.
+ */
+void parse_pathspec_file(struct pathspec *pathspec,
+			 unsigned magic_mask,
+			 unsigned flags,
+			 const char *prefix,
+			 const char *file,
+			 int nul_term_line);
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
 void clear_pathspec(struct pathspec *);
 
Signature · 1 line
gitgitgadget
#37 lore raw

v2 cover Add --pathspec-from-file option for reset, commit

In reply to Alexandr Miloslavskiy via GitGitGadget

Adds --pathspec-from-file option for porcelain commands to avoid commandline length limit.

So far I implemented it for git commit and git reset, but my goal is to support other commands as well after these patches are reviewed.

The patches are based on the following discussions:
https://public-inbox.org/git/[email protected]/T/#u
There, --stdin-paths was suggested.
https://public-inbox.org/git/[email protected]/T/#u
There, --stdin-paths was extended to --paths-file.
https://public-inbox.org/git/[email protected]/
https://github.com/gitgitgadget/git/pull/133Patch from Johannes, I used it as base of my patch. There, --pathspec-from-file is suggested in discussion.

Major changes compared to patch from Johannes:

1) --pathspec-from-file allows to read file, not just stdin

2) --literal-pathspecs should be honored. This is a good improvement because it keeps --pathspec-from-file much closer to passing args on commandline. Since the goal of the new option is to avoid commandline length limit, both behaviors should be close to each other.

3) Patches are designed with all other git commands in mind

Changes from V1: A lot of small changes here and there, in accordance with review suggestions.

Alexandr Miloslavskiy (6):
parse-options.h: add new options --pathspec-from-file,

    `--pathspec-file-nul`

pathspec: add new function to parse file
doc: reset: unify <pathspec> description
reset: support the --pathspec-from-file option
doc: commit: unify <pathspec> description
commit: support the --pathspec-from-file option

 Documentation/git-commit.txt    |  29 ++++--
 Documentation/git-reset.txt     |  37 +++++---
 builtin/commit.c                |  25 +++++-
 builtin/reset.c                 |  25 +++++-
 parse-options.h                 |   2 +
 pathspec.c                      |  38 ++++++++
 pathspec.h                      |  10 +++
 t/t7107-reset-pathspec-file.sh  | 155 ++++++++++++++++++++++++++++++++
 t/t7526-commit-pathspec-file.sh | 130 +++++++++++++++++++++++++++
 9 files changed, 425 insertions(+), 26 deletions(-)
 create mode 100755 t/t7107-reset-pathspec-file.sh
 create mode 100755 t/t7526-commit-pathspec-file.sh

base-commit: da72936f544fec5a335e66432610e4cef4430991
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-445%2FSyntevoAlex%2F%230207_pathspec_from_file-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-445/SyntevoAlex/#0207_pathspec_from_file-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/445

Range-diff vs v1:

 -:  ---------- > 1:  2dfaccf0d5 parse-options.h: add new options `--pathspec-from-file`, `--pathspec-file-nul`
 1:  52e7a84a2e ! 2:  96697ba072 pathspec: add new function to parse file
     @@ -50,16 +50,13 @@
      +	if (!strcmp(file, "-"))
      +		in = stdin;
      +	else
     -+		in = fopen(file, "r");
     -+
     -+	if (!in)
     -+		die(_("could not open '%s' for reading"), file);
     ++		in = xfopen(file, "r");
      +
      +	while (getline_fn(&buf, in) != EOF) {
      +		if (!nul_term_line && buf.buf[0] == '"') {
      +			strbuf_reset(&unquoted);
      +			if (unquote_c_style(&unquoted, buf.buf, NULL))
     -+				die(_("line is badly quoted"));
     ++				die(_("line is badly quoted: %s"), buf.buf);
      +			strbuf_swap(&buf, &unquoted);
      +		}
      +		argv_array_push(&parsed_file, buf.buf);
     @@ -91,11 +88,11 @@
      + * When 'file' is exactly "-" it uses 'stdin' instead.
      + */
      +void parse_pathspec_file(struct pathspec *pathspec,
     -+		    unsigned magic_mask,
     -+		    unsigned flags,
     -+		    const char *prefix,
     -+		    const char *file,
     -+		    int nul_term_line);
     ++			 unsigned magic_mask,
     ++			 unsigned flags,
     ++			 const char *prefix,
     ++			 const char *file,
     ++			 int nul_term_line);
       void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
       void clear_pathspec(struct pathspec *);

2: 1740ac7a29 ! 3: f961a5155a doc: reset: unify <pathspec> description
@@ -21,8 +21,11 @@

DESCRIPTION
@@
+ In the third form, set the current branch head (HEAD) to <commit>, optionally modifying index and working tree to match.
The <tree-ish>/<commit> defaults to HEAD in all forms.
++The <pathspec> is used to limit the paths affected by the operation ++(see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).

-'git reset' [-q] [<tree-ish>] [--] <paths>...::
- This form resets the index entries for all <paths> to their
@@ -34,14 +37,11 @@
-This means that git reset <paths> is the opposite of git add -<paths>. This command is equivalent to
-git restore [--source=<tree-ish>] --staged <paths>....
-+For more details about the <pathspec> syntax, see the 'pathspec' entry -+in linkgit:gitglossary[7].
- +
--After running git reset <paths> to update the index entry, you can
+This means that git reset <pathspec> is the opposite of git add +<pathspec>. This command is equivalent to
+git restore [--source=<tree-ish>] --staged <pathspec>....
-++
+ +
+-After running git reset <paths> to update the index entry, you can
+After running git reset <pathspec> to update the index entry, you can
use linkgit:git-restore[1] to check the contents out of the index to the working tree. Alternatively, using linkgit:git-restore[1]
@@ -54,10 +54,18 @@
Interactively select hunks in the difference between the index and <tree-ish> (defaults to HEAD). The chosen hunks are applied in reverse to the index.
- +
-+For more details about the <pathspec> syntax, see the 'pathspec' entry -+in linkgit:gitglossary[7].
-++
- This means that git reset -p is the opposite of git add -p, i.e.
- you can use it to selectively reset hunks. See the ``Interactive Mode''
- section of linkgit:git-add[1] to learn how to operate the --patch mode. +
+ diff --git a/builtin/reset.c b/builtin/reset.c
+ --- a/builtin/reset.c
+ +++ b/builtin/reset.c
+@@
+
+ static const char * const git_reset_usage[] = {
+ N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
+- N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
+- N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
++ N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
++ N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
+ NULL
+ };
+
3: 8d9f1fbc18 ! 4: d72d4f16b5 reset: support the --pathspec-from-file option

     @@ -1,33 +1,13 @@
      Author: Alexandr Miloslavskiy <[email protected]>
      
     -    reset: support the --pathspec-from-file option
     -
     -    This option solves the problem of commandline length limit for UI's
     -    built on top of git. Plumbing commands are not always a good fit, for
     -    two major reasons:
     -    1) Some UI's serve as assistants that help user run git commands. In
     -       this case, replacing familiar commands with plumbing commands will
     -       confuse most users.
     -    2) Some UI's have started and grown with porcelain commands. Replacing
     -       existing logic with plumbing commands could be cumbersome and prone
     -       to various new problems.
     -
     -    The new option is designed to behave very close to pathspecs passed in
     -    commandline args, so that switching from one to another is simple.
     -
     -    The new option allows to read either a specified file or `stdin`.
     -    Reading from file is a good way to avoid competing for stdin, and
     -    also gives some extra flexibility.
     +    reset: support the `--pathspec-from-file` option
      
          Decisions taken for simplicity:
     -    1) The new option is declared incompatible with other options that
     -       could use `stdin`.
     -    2) It is not allowed to pass some refspecs in args and others in file.
     -    3) New options do not have shorthands to avoid shorthand conflicts.
     -
     -    Also add new '--pathspec-file-null' switch that mirrors '-z' used in
     -    various places. Some porcelain commands, such as `git commit`, already
     -    use '-z', therefore it needed a new unambiguous name.
     +    1) For now, `--pathspec-from-file` is declared incompatible with
     +       `--patch`, even when <file> is not `stdin`. Such use case it not
     +       really expected. Also, it is harder to support in `git commit`, so
     +       I decided to make it incompatible in all places.
     +    2) It is not allowed to pass pathspec in both args and file.
      
          Co-authored-by: Johannes Schindelin <[email protected]>
          Signed-off-by: Alexandr Miloslavskiy <[email protected]>
     @@ -39,7 +19,7 @@
       --------
       [verse]
       'git reset' [-q] [<tree-ish>] [--] <pathspec>...
     -+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]
     ++'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
       'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
       'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
       
     @@ -51,11 +31,13 @@
      +In the last form, set the current branch head (`HEAD`) to `<commit>`,
       optionally modifying index and working tree to match.
       The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
     + The <pathspec> is used to limit the paths affected by the operation
     + (see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).
       
       'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
      -	This form resets the index entries for all `<pathspec>` to their
     -+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-null]] [<tree-ish>]::
     -+	These forms reset the index entries for all `<pathspec>` to their
     ++'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]::
     ++	These forms reset the index entries matching pathspec to their
       	state at `<tree-ish>`.  (It does not affect the working tree or
       	the current branch.)
       +
     @@ -64,14 +46,15 @@
       	override the default behavior.
       
      +--pathspec-from-file=<file>::
     -+	Read `<pathspec>` from `<file>` instead. If `<file>` is exactly `-`
     -+	then read from standard input. Pathspecs are separated by LF or
     -+	CR/LF. Pathspecs can be quoted as explained for the configuration
     -+	variable `core.quotePath` (see linkgit:git-config[1]). See also
     -+	`--pathspec-file-null` and global `--literal-pathspecs`.
     -+
     -+--pathspec-file-null::
     -+	Only meaningful with `--pathspec-from-file`. Pathspecs are
     ++	Pathspec is passed in `<file>` instead of commandline args. If
     ++	`<file>` is exactly `-` then standard input is used. Pathspec
     ++	elements are separated by LF or CR/LF. Pathspec elements can be
     ++	quoted as explained for the configuration variable `core.quotePath`
     ++	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
     ++	global `--literal-pathspecs`.
     ++
     ++--pathspec-file-nul::
     ++	Only meaningful with `--pathspec-from-file`. Pathspec elements are
      +	separated with NUL character and are not expected to be quoted.
       
       EXAMPLES
     @@ -83,9 +66,9 @@
      @@
       static const char * const git_reset_usage[] = {
       	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
     - 	N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
     -+	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-null]] [<tree-ish>]"),
     - 	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
     + 	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
     ++	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
     + 	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
       	NULL
       };
      @@
     @@ -94,7 +77,7 @@
       	int reset_type = NONE, update_ref_status = 0, quiet = 0;
      -	int patch_mode = 0, unborn;
      -	const char *rev;
     -+	int patch_mode = 0, pathspec_file_null = 0, unborn;
     ++	int patch_mode = 0, pathspec_file_nul = 0, unborn;
      +	const char *rev, *pathspec_from_file = NULL;
       	struct object_id oid;
       	struct pathspec pathspec;
     @@ -103,10 +86,8 @@
       		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
       		OPT_BOOL('N', "intent-to-add", &intent_to_add,
       				N_("record only the fact that removed paths will be added later")),
     -+		OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file,
     -+				N_("read pathspecs from file")),
     -+		OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null,
     -+				N_("with --pathspec-from-file, pathspecs are separated with NUL character")),
     ++		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
     ++		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
       		OPT_END()
       	};
       
     @@ -119,13 +100,14 @@
      +			die(_("--pathspec-from-file is incompatible with --patch"));
      +
      +		if (pathspec.nr)
     -+			die(_("--pathspec-from-file is incompatible with path arguments"));
     ++			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
      +
      +		parse_pathspec_file(&pathspec, 0,
      +				    PATHSPEC_PREFER_FULL,
     -+				    prefix, pathspec_from_file, pathspec_file_null);
     -+	} else if (pathspec_file_null)
     -+		die(_("--pathspec-file-null requires --pathspec-from-file"));
     ++				    prefix, pathspec_from_file, pathspec_file_nul);
     ++	} else if (pathspec_file_nul) {
     ++		die(_("--pathspec-file-nul requires --pathspec-from-file"));
     ++	}
      +
       	unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
       	if (unborn) {
     @@ -142,21 +124,7 @@
      +
      +. ./test-lib.sh
      +
     -+cat > expect.a <<EOF
     -+ D fileA.t
     -+EOF
     -+
     -+cat > expect.ab <<EOF
     -+ D fileA.t
     -+ D fileB.t
     -+EOF
     -+
     -+cat > expect.a_bc_d <<EOF
     -+D  fileA.t
     -+ D fileB.t
     -+ D fileC.t
     -+D  fileD.t
     -+EOF
     ++test_tick
      +
      +test_expect_success setup '
      +	echo A >fileA.t &&
     @@ -165,16 +133,16 @@
      +	echo D >fileD.t &&
      +	git add . &&
      +	git commit --include . -m "Commit" &&
     -+	checkpoint=$(git rev-parse --verify HEAD)
     ++	git tag checkpoint
      +'
      +
      +restore_checkpoint () {
     -+	git reset --hard "$checkpoint"
     ++	git reset --hard checkpoint
      +}
      +
     -+verify_state () {
     ++verify_expect () {
      +	git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual &&
     -+	test_cmp "$1" actual
     ++	test_cmp expect actual
      +}
      +
      +test_expect_success '--pathspec-from-file from stdin' '
     @@ -183,7 +151,10 @@
      +	git rm fileA.t &&
      +	echo fileA.t | git reset --pathspec-from-file=- &&
      +
     -+	verify_state expect.a
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success '--pathspec-from-file from file' '
     @@ -193,34 +164,62 @@
      +	echo fileA.t >list &&
      +	git reset --pathspec-from-file=list &&
      +
     -+	verify_state expect.a
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'NUL delimiters' '
      +	restore_checkpoint &&
      +
      +	git rm fileA.t fileB.t &&
     -+	printf fileA.tQfileB.t | q_to_nul | git reset --pathspec-from-file=- --pathspec-file-null &&
     ++	printf "fileA.t\0fileB.t\0" | git reset --pathspec-from-file=- --pathspec-file-nul &&
      +
     -+	verify_state expect.ab
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	 D fileB.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'LF delimiters' '
      +	restore_checkpoint &&
      +
      +	git rm fileA.t fileB.t &&
     ++	printf "fileA.t\nfileB.t\n" | git reset --pathspec-from-file=- &&
     ++
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	 D fileB.t
     ++	EOF
     ++	verify_expect
     ++'
     ++
     ++test_expect_success 'no trailing delimiter' '
     ++	restore_checkpoint &&
     ++
     ++	git rm fileA.t fileB.t &&
      +	printf "fileA.t\nfileB.t" | git reset --pathspec-from-file=- &&
      +
     -+	verify_state expect.ab
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	 D fileB.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'CRLF delimiters' '
      +	restore_checkpoint &&
      +
      +	git rm fileA.t fileB.t &&
     -+	printf "fileA.t\r\nfileB.t" | git reset --pathspec-from-file=- &&
     ++	printf "fileA.t\r\nfileB.t\r\n" | git reset --pathspec-from-file=- &&
      +
     -+	verify_state expect.ab
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	 D fileB.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'quotes' '
     @@ -229,21 +228,27 @@
      +	git rm fileA.t &&
      +	printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
      +
     -+	verify_state expect.a
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	EOF
     ++	verify_expect
      +'
      +
     -+test_expect_success 'quotes not compatible with --pathspec-file-null' '
     ++test_expect_success 'quotes not compatible with --pathspec-file-nul' '
      +	restore_checkpoint &&
      +
      +	git rm fileA.t &&
      +	printf "\"file\\101.t\"" >list &&
      +	# Note: "git reset" has not yet learned to fail on wrong pathspecs
     -+	git reset --pathspec-from-file=list --pathspec-file-null &&
     ++	git reset --pathspec-from-file=list --pathspec-file-nul &&
      +	
     -+	test_must_fail verify_state expect.a
     ++	cat >expect <<-\EOF &&
     ++	 D fileA.t
     ++	EOF
     ++	test_must_fail verify_expect
      +'
      +
     -+test_expect_success '--pathspec-from-file is not compatible with --soft --hard' '
     ++test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' '
      +	restore_checkpoint &&
      +
      +	git rm fileA.t &&
     @@ -256,9 +261,15 @@
      +	restore_checkpoint &&
      +
      +	git rm fileA.t fileB.t fileC.t fileD.t &&
     -+	printf "fileB.t\nfileC.t" | git reset --pathspec-from-file=- &&
     -+
     -+	verify_state expect.a_bc_d
     ++	printf "fileB.t\nfileC.t\n" | git reset --pathspec-from-file=- &&
     ++
     ++	cat >expect <<-\EOF &&
     ++	D  fileA.t
     ++	 D fileB.t
     ++	 D fileC.t
     ++	D  fileD.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_done

4: 251d06e27f ! 5: 20c4495fd3 doc: commit: unify <pathspec> description
@@ -24,12 +24,16 @@
Do not interpret any more arguments as options.

-<file>...::
+- When files are given on the command line, the command
+- commits the contents of the named files, without
+- recording the changes already staged. The contents of
+- these files are also staged for the next commit on top
+- of what have been staged before.
+<pathspec>...::
- When files are given on the command line, the command
- commits the contents of the named files, without
- recording the changes already staged. The contents of
- these files are also staged for the next commit on top
- of what have been staged before.
++ When pathspec is given on the command line, commit the contents of
++ the files that match the pathspec without recording the changes
++ already added to the index. The contents of these files are also
++ staged for the next commit on top of what have been staged before.
++
+For more details about the <pathspec> syntax, see the 'pathspec' entry +in linkgit:gitglossary[7].
5: f484704689 ! 6: cb5fc9b14d commit: support the --pathspec-from-file option

     @@ -2,32 +2,12 @@
      
          commit: support the --pathspec-from-file option
      
     -    This option solves the problem of commandline length limit for UI's
     -    built on top of git. Plumbing commands are not always a good fit, for
     -    two major reasons:
     -    1) Some UI's serve as assistants that help user run git commands. In
     -       this case, replacing familiar commands with plumbing commands will
     -       confuse most users.
     -    2) Some UI's have started and grown with porcelain commands. Replacing
     -       existing logic with plumbing commands could be cumbersome and prone
     -       to various new problems.
     -
     -    The new option is designed to behave very close to pathspecs passed in
     -    commandline args, so that switching from one to another is simple.
     -
     -    The new option allows to read either a specified file or `stdin`.
     -    Reading from file is a good way to avoid competing for stdin, and
     -    also gives some extra flexibility.
     -
          Decisions taken for simplicity:
     -    1) The new option is declared incompatible with other options that
     -       could use `stdin`.
     -    2) It is not allowed to pass some refspecs in args and others in file.
     -    3) New options do not have shorthands to avoid shorthand conflicts.
     -
     -    Also add new '--pathspec-file-null' switch that mirrors '-z' used in
     -    various places. Some porcelain commands, such as `git commit`, already
     -    use '-z', therefore it needed a new unambiguous name.
     +    1) For now, `--pathspec-from-file` is declared incompatible with
     +       `--interactive/--patch`, even when <file> is not `stdin`. Such use
     +       case it not really expected. Also, it would require changes to
     +       `interactive_add()`.
     +    2) It is not allowed to pass pathspec in both args and file.
      
          Signed-off-by: Alexandr Miloslavskiy <[email protected]>
      
     @@ -39,7 +19,7 @@
       	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
       	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
      -	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
     -+	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-null]]
     ++	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
      +	   [-S[<keyid>]] [--] [<pathspec>...]
       
       DESCRIPTION
     @@ -50,14 +30,15 @@
       	paths are also not required, and an empty commit will be created.
      +	
      +--pathspec-from-file=<file>::
     -+	Read `<pathspec>` from `<file>` instead. If `<file>` is exactly `-`
     -+	then read from standard input. Pathspecs are separated by LF or
     -+	CR/LF. Pathspecs can be quoted as explained for the configuration
     -+	variable `core.quotePath` (see linkgit:git-config[1]). See also
     -+	`--pathspec-file-null` and global `--literal-pathspecs`.
     -+
     -+--pathspec-file-null::
     -+	Only meaningful with `--pathspec-from-file`. Pathspecs are
     ++	Pathspec is passed in `<file>` instead of commandline args. If
     ++	`<file>` is exactly `-` then standard input is used. Pathspec
     ++	elements are separated by LF or CR/LF. Pathspec elements can be
     ++	quoted as explained for the configuration variable `core.quotePath`
     ++	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
     ++	global `--literal-pathspecs`.
     ++
     ++--pathspec-file-nul::
     ++	Only meaningful with `--pathspec-from-file`. Pathspec elements are
      +	separated with NUL character and are not expected to be quoted.
       
       -u[<mode>]::
     @@ -71,7 +52,7 @@
       static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
       static int config_commit_verbose = -1; /* unspecified */
      -static int no_post_rewrite, allow_empty_message;
     -+static int no_post_rewrite, allow_empty_message, pathspec_file_null;
     ++static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
      -static char *sign_commit;
      +static char *sign_commit, *pathspec_from_file;
     @@ -87,14 +68,14 @@
      +			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
      +
      +		if (pathspec.nr)
     -+			die(_("--pathspec-from-file is incompatible with path arguments"));
     ++			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
      +
      +		parse_pathspec_file(&pathspec, 0,
      +				    PATHSPEC_PREFER_FULL,
     -+				    prefix, pathspec_from_file, pathspec_file_null);
     ++				    prefix, pathspec_from_file, pathspec_file_nul);
     ++	} else if (pathspec_file_nul) {
     ++		die(_("--pathspec-file-nul requires --pathspec-from-file"));
      +	}
     -+	else if (pathspec_file_null)
     -+		die(_("--pathspec-file-null requires --pathspec-from-file"));
      +
      +	if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
      +		die(_("No paths with --include/--only does not make sense."));
     @@ -115,8 +96,8 @@
       		OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
       		OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
       		{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
     -+		OPT_FILENAME(0, "pathspec-from-file", &pathspec_from_file, N_("read pathspecs from file")),
     -+		OPT_BOOL(0, "pathspec-file-null", &pathspec_file_null, N_("with --pathspec-from-file, pathspecs are separated with NUL character")),
     ++		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
     ++		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
       		/* end commit contents options */
       
       		OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
     @@ -134,23 +115,9 @@
      +
      +test_tick
      +
     -+cat > expect.a <<EOF
     -+A	fileA.t
     -+EOF
     -+
     -+cat > expect.ab <<EOF
     -+A	fileA.t
     -+A	fileB.t
     -+EOF
     -+
     -+cat > expect.bc <<EOF
     -+A	fileB.t
     -+A	fileC.t
     -+EOF
     -+
      +test_expect_success setup '
      +	test_commit file0 &&
     -+	checkpoint=$(git rev-parse --verify HEAD) &&
     ++	git tag checkpoint &&
      +	
      +	echo A >fileA.t &&
      +	echo B >fileB.t &&
     @@ -160,12 +127,12 @@
      +'
      +
      +restore_checkpoint () {
     -+	git reset --soft "$checkpoint"
     ++	git reset --soft checkpoint
      +}
      +
     -+verify_state () {
     ++verify_expect () {
      +	git diff-tree --no-commit-id --name-status -r HEAD >actual &&
     -+	test_cmp "$1" actual
     ++	test_cmp expect actual
      +}
      +
      +test_expect_success '--pathspec-from-file from stdin' '
     @@ -173,7 +140,10 @@
      +
      +	echo fileA.t | git commit --pathspec-from-file=- -m "Commit" &&
      +
     -+	verify_state expect.a
     ++	cat >expect <<-\EOF &&
     ++	A	fileA.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success '--pathspec-from-file from file' '
     @@ -182,31 +152,58 @@
      +	echo fileA.t >list &&
      +	git commit --pathspec-from-file=list -m "Commit" &&
      +
     -+	verify_state expect.a
     ++	cat >expect <<-\EOF &&
     ++	A	fileA.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'NUL delimiters' '
      +	restore_checkpoint &&
      +
     -+	printf fileA.tQfileB.t | q_to_nul | git commit --pathspec-from-file=- --pathspec-file-null -m "Commit" &&
     ++	printf "fileA.t\0fileB.t\0" | git commit --pathspec-from-file=- --pathspec-file-nul -m "Commit" &&
      +
     -+	verify_state expect.ab
     ++	cat >expect <<-\EOF &&
     ++	A	fileA.t
     ++	A	fileB.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'LF delimiters' '
      +	restore_checkpoint &&
      +
     ++	printf "fileA.t\nfileB.t\n" | git commit --pathspec-from-file=- -m "Commit" &&
     ++
     ++	cat >expect <<-\EOF &&
     ++	A	fileA.t
     ++	A	fileB.t
     ++	EOF
     ++	verify_expect
     ++'
     ++
     ++test_expect_success 'no trailing delimiter' '
     ++	restore_checkpoint &&
     ++
      +	printf "fileA.t\nfileB.t" | git commit --pathspec-from-file=- -m "Commit" &&
      +
     -+	verify_state expect.ab
     ++	cat >expect <<-\EOF &&
     ++	A	fileA.t
     ++	A	fileB.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'CRLF delimiters' '
      +	restore_checkpoint &&
      +
     -+	printf "fileA.t\r\nfileB.t" | git commit --pathspec-from-file=- -m "Commit" &&
     ++	printf "fileA.t\r\nfileB.t\r\n" | git commit --pathspec-from-file=- -m "Commit" &&
      +
     -+	verify_state expect.ab
     ++	cat >expect <<-\EOF &&
     ++	A	fileA.t
     ++	A	fileB.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_expect_success 'quotes' '
     @@ -214,22 +211,29 @@
      +
      +	printf "\"file\\101.t\"" | git commit --pathspec-from-file=- -m "Commit" &&
      +
     -+	verify_state expect.a
     ++	cat >expect <<-\EOF &&
     ++	A	fileA.t
     ++	EOF
     ++	verify_expect expect
      +'
      +
     -+test_expect_success 'quotes not compatible with --pathspec-file-null' '
     ++test_expect_success 'quotes not compatible with --pathspec-file-nul' '
      +	restore_checkpoint &&
      +
      +	printf "\"file\\101.t\"" >list &&
     -+	test_must_fail git commit --pathspec-from-file=list --pathspec-file-null -m "Commit"
     ++	test_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m "Commit"
      +'
      +
      +test_expect_success 'only touches what was listed' '
      +	restore_checkpoint &&
      +
     -+	printf "fileB.t\nfileC.t" | git commit --pathspec-from-file=- -m "Commit" &&
     ++	printf "fileB.t\nfileC.t\n" | git commit --pathspec-from-file=- -m "Commit" &&
      +
     -+	verify_state expect.bc
     ++	cat >expect <<-\EOF &&
     ++	A	fileB.t
     ++	A	fileC.t
     ++	EOF
     ++	verify_expect
      +'
      +
      +test_done
Signature · 1 line
gitgitgadget
#42 lore raw

PATCH v2 3/6 doc: reset: unify <pathspec> description

From: Alexandr Miloslavskiy <[email protected]>

Synchronize it to git add, which has a pretty good description.

---
 Documentation/git-reset.txt | 20 +++++++++++---------
 builtin/reset.c             |  4 ++--
 2 files changed, 13 insertions(+), 11 deletions(-)
Documentation/git-reset.txt +11 -9
index 97e0544d9e..f2ccb1426c 100644
@@ -8,8 +8,8 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [-q] [<tree-ish>] [--] <paths>...
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
@@ -18,24 +18,26 @@ In the first and second form, copy entries from `<tree-ish>` to the index.
 In the third form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
+The <pathspec> is used to limit the paths affected by the operation
+(see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).
 
-'git reset' [-q] [<tree-ish>] [--] <paths>...::
-	This form resets the index entries for all `<paths>` to their
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
+	This form resets the index entries for all `<pathspec>` to their
 	state at `<tree-ish>`.  (It does not affect the working tree or
 	the current branch.)
 +
-This means that `git reset <paths>` is the opposite of `git add
-<paths>`. This command is equivalent to
-`git restore [--source=<tree-ish>] --staged <paths>...`.
+This means that `git reset <pathspec>` is the opposite of `git add
+<pathspec>`. This command is equivalent to
+`git restore [--source=<tree-ish>] --staged <pathspec>...`.
 +
-After running `git reset <paths>` to update the index entry, you can
+After running `git reset <pathspec>` to update the index entry, you can
 use linkgit:git-restore[1] to check the contents out of the index to
 the working tree. Alternatively, using linkgit:git-restore[1]
 and specifying a commit with `--source`, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]::
 	Interactively select hunks in the difference between the index
 	and `<tree-ish>` (defaults to `HEAD`).  The chosen hunks are applied
 	in reverse to the index.
builtin/reset.c +2 -2
index fdd572168b..9291c0fd72 100644
@@ -30,8 +30,8 @@
 
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
-	N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
-	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
+	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
+	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
 	NULL
 };
 
Signature · 1 line
gitgitgadget
#50 lore raw

PATCH v2 6/6 commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --interactive/--patch, even when <file> is not stdin. Such use case it not really expected. Also, it would require changes to interactive_add().
2) It is not allowed to pass pathspec in both args and file.

---
 Documentation/git-commit.txt    |  15 +++-
 builtin/commit.c                |  25 +++++-
 t/t7526-commit-pathspec-file.sh | 130 ++++++++++++++++++++++++++++++++
 3 files changed, 165 insertions(+), 5 deletions(-)
 create mode 100755 t/t7526-commit-pathspec-file.sh
Documentation/git-commit.txt +14 -1
index 915c212a0d..bbd53959bd 100644
@@ -13,7 +13,8 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
+	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
+	   [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -277,6 +278,18 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
 	the last commit without committing changes that have
 	already been staged. If used together with `--allow-empty`
 	paths are also not required, and an empty commit will be created.
+	
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and are not expected to be quoted.
 
 -u[<mode>]::
 --untracked-files[=<mode>]::
builtin/commit.c +21 -4
index e588bc6ad3..ed40729355 100644
@@ -107,9 +107,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
-static int no_post_rewrite, allow_empty_message;
+static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit;
+static char *sign_commit, *pathspec_from_file;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -343,6 +343,23 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 		       PATHSPEC_PREFER_FULL,
 		       prefix, argv);
 
+	if (pathspec_from_file) {
+		if (interactive)
+			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
+	if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+		die(_("No paths with --include/--only does not make sense."));
+
 	if (read_cache_preload(&pathspec) < 0)
 		die(_("index file corrupt"));
 
@@ -1198,8 +1215,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
 	if (also + only + all + interactive > 1)
 		die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
-	if (argc == 0 && (also || (only && !amend && !allow_empty)))
-		die(_("No paths with --include/--only does not make sense."));
 	cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
 
 	handle_untracked_files_arg(s);
@@ -1535,6 +1550,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
 		OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
 		{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 		/* end commit contents options */
 
 		OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
t/t7526-commit-pathspec-file.sh new file +130 -0

130 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#46 lore raw

PATCH v2 5/6 doc: commit: unify <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Synchronize it to git add, which has a pretty good description. This also better disambiguates <file>... header.

---
 Documentation/git-commit.txt | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)
Documentation/git-commit.txt +9 -7
index afa7b75a23..915c212a0d 100644
@@ -13,7 +13,7 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<file>...]
+	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -345,12 +345,14 @@ changes to tracked files.
 \--::
 	Do not interpret any more arguments as options.
 
-<file>...::
-	When files are given on the command line, the command
-	commits the contents of the named files, without
-	recording the changes already staged.  The contents of
-	these files are also staged for the next commit on top
-	of what have been staged before.
+<pathspec>...::
+	When pathspec is given on the command line, commit the contents of
+	the files that match the pathspec without recording the changes
+	already added to the index. The contents of these files are also
+	staged for the next commit on top of what have been staged before.
++
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].
 
 :git-commit: 1
 include::date-formats.txt[]
Signature · 1 line
gitgitgadget
#45 lore raw

PATCH v2 4/6 reset: support the `--pathspec-from-file` option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --patch, even when <file> is not stdin. Such use case it not really expected. Also, it is harder to support in git commit, so I decided to make it incompatible in all places.
2) It is not allowed to pass pathspec in both args and file.

---
 Documentation/git-reset.txt    |  19 +++-
 builtin/reset.c                |  21 ++++-
 t/t7107-reset-pathspec-file.sh | 155 +++++++++++++++++++++++++++++++++
 3 files changed, 190 insertions(+), 5 deletions(-)
 create mode 100755 t/t7107-reset-pathspec-file.sh
Documentation/git-reset.txt +16 -3
index f2ccb1426c..d2cd0af6a4 100644
@@ -9,20 +9,22 @@ SYNOPSIS
 --------
 [verse]
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
 'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-In the first and second form, copy entries from `<tree-ish>` to the index.
-In the third form, set the current branch head (`HEAD`) to `<commit>`,
+In the first three forms, copy entries from `<tree-ish>` to the index.
+In the last form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 The <pathspec> is used to limit the paths affected by the operation
 (see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).
 
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
-	This form resets the index entries for all `<pathspec>` to their
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]::
+	These forms reset the index entries matching pathspec to their
 	state at `<tree-ish>`.  (It does not affect the working tree or
 	the current branch.)
 +
@@ -103,6 +105,17 @@ OPTIONS
 	`reset.quiet` config option. `--quiet` and `--no-quiet` will
 	override the default behavior.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and are not expected to be quoted.
 
 EXAMPLES
 --------
builtin/reset.c +19 -2
index 9291c0fd72..246bf9d737 100644
@@ -31,6 +31,7 @@
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
 	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
+	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
 	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
 	NULL
 };
@@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0, unborn;
-	const char *rev;
+	int patch_mode = 0, pathspec_file_nul = 0, unborn;
+	const char *rev, *pathspec_from_file = NULL;
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
@@ -306,6 +307,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
+		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 		OPT_END()
 	};
 
@@ -316,6 +319,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
+	if (pathspec_from_file) {
+		if (patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
 	unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
 	if (unborn) {
 		/* reset on unborn branch: treat as reset to empty tree */
t/t7107-reset-pathspec-file.sh new file +155 -0

155 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#15 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Phillip Wood

I think I have implemented most suggestions in PatchV2. Thanks!

Quoted 1 line · Phillip Wood

I'm confused reset does not use stdin does it?

I have improved commit message to clarify.

Quoted 2 lines

--pathspec-file would be shorter and still conveys the intent of the option.

I decided to stay with writing suggested by Junio.

Quoted 1 line

Is this line missing a leading space?

I think it's good. Maybe you were confused by patch formatting?

Quoted 1 line

Alignment again

I think it's good again :)

Quoted 1 line

If I've understood correctly this doesn't test if a path is correctly unquoted, only that it is accepted.

Already agreed that test is good, just a bit non-obvious.

Quoted 1 line

s/^/--pathspec-from-file / ?

I decided not to repeat '--pathspec-from-file' in every test, because test package already has it in description. Is that good?

#28 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

In reply to Phillip Wood

I think I have implemented most suggestions in PatchV2. Thanks!

Quoted 1 line

It might be worth tailoring the message to the command rather than having exactly the same message for commit and reset

I decided to move the general comment to base commit where options are introduced and not repeat it where options are supported.

Quoted 1 line

I wonder if there is a way of calling parse_pathspec_file() from parse_and_validate_options() instead. Otherwise we end up validating options here instead which is a bit messy.

The code looks a bit too entangled to support that without making it worse. The biggest concern I have is that parse_and_validate_options() will populate pathspec and some other function will need to remember to clean it up. I like it better when pathspec is handled in one place.

I agree that things are not perfect, but this seems to be a consequence of other existing problems. For example, I would have expected a structure instead of a handful of global variables. That would have solved many problems. However, I didn't have the bravery to dive into this refactoring.

#7 lore raw

Re: [PATCH 2/5] doc: reset: unify <pathspec> description

In reply to Junio C Hamano

I think I have implemented most suggestions in PatchV2. Thanks!

Quoted 19 lines

Any time I see "... X. This means Y." either in the doc or in the proposed log message, I wish the author (not you in this case, obviously) thought twice about rewriting so that they do not say one thing and immediately have to rephrase it, i.e. either just say Y without saying X, or saying X more clearly without having to say Y.

In this case, however, I think X and Y are related but both relevant. The subcommand resets the index entries for chosen paths to match what is in the tree-ish, which is the same as restoring from a tree to the index.

It is not quite the opposite of adding to the index from the working tree. In this sequence:

	$ edit newfile
	$ git add newfile

and then further

Quoted 7 lines
	$ edit newfile
	$ git add newfile
	$ git reset -- newfile

we are taken back to the state _before_ any of the changes made to newfile (in fact, since HEAD does not have newfile, the resulting index would not know about it, either).

I am a bit confused, is it correct that you don't expect me to change my patches?

#19 lore raw

Re: [PATCH 3/5] reset: support the --pathspec-from-file option

In reply to Junio C Hamano

I think I have implemented most suggestions in PatchV2. Thanks!

Quoted 5 lines
Quoted 3 lines

2) Some UI's have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

There is not a lot of sympathy for such argument ;-)

Let's think that this merely reinforces the first point :)

Quoted 11 lines

I do not understand this part. Wouldn't it natural to expect that "-z", when used with "--pathspec-from-file", tell us that the named file is NUL delimited collection of records? What's different about "--pathspec-file-null" that it is confusing to all it "-z"?

I actually do not mind not having "-z" and using only "--pathspec-file-null". A new option with long name that feels similar to existing "-z" but does sufficiently different things so that it needs a different name, however, feels counter-productive for the purpose of using it in the UI that produces commands to be learned by end-users.

I didn't want to run into a situation where '-z' suddenly applies to multiple things (for example, if later it's decided to allow --patch with --pathspec-from-file).

Regarding UI, I think that (insert our UI name here) will not use '--pathspec-from-file' unless there are really many files. When it does happen, user will probably have an easier time understanding '--pathspec-file-nul' then '-z'.

Quoted 7 lines

When I invented the terms "pathspec", "refspec", etc. for this project, I meant them to be collective nouns. A pathspec is a set of pathspec elements, each of which is usually given as a separate argument on the command line. So "Read pathspec from file" is good (not "Read pathspecs from file").

And "Pathspec elements" are separated by LF or CR/LF.

I changed that. However, git grep pathspecs is quite interesting. My own taste says that "pathspecs" is more expected then "pathspec".

Quoted 7 lines

A tangent. Since we do not allow NUL in a pathspec element, we do not even need the "-z" option. When we read pathspec from a file, we can take any of CRLF, LF or NUL as the separator.

Ah, sorry, that would not help very much. With "-z" we are allowing to express pathspec elements inside which there are embedded LF or CR/LF. Sorry about the noise.

Yes, NUL delimiter allows to avoid escaping.

Quoted 3 lines

- Quote end-of-here-document (EOF in the above) if you do not rely on parameter interpolation inside the here document; this helps readers by telling them that what is presented is used verbatim.

Tests overwhelmingly prefer escaped form (\EOF) instead, so I used that.

Quoted 2 lines

This feeds "fileA.t<NUL>fileB.t" without <NUL> after "fileB.t" to the command. Intended?

Kind of intended. I changed tests to always use trailing delimiters, and also added a new dedicated test without trailing delimiter.

#22 lore raw

Re: [PATCH 4/5] doc: commit: unify <pathspec> description

In reply to Junio C Hamano

I think I have implemented most suggestions in PatchV2. Thanks!

Quoted 10 lines

I am not sure if we want to repeat this all over the place.

We do not say "For details about the <commit> syntax, see the 'SPECIFYING REVISIONS' section of linkgit:git-rev-parse[1]" for every command that takes <commit> from the command line.

Is your urge to suggest adding this sentence coming from that you are much more familiar with <commit> than <pathspec>? In other words, if you were more familiar with Git, would you still be adding this (and not corresponding one for <commit>)?

Commit is a well known term, dating from ancient times like CVS or even older.

Pathspec, however, is something new. When I pretend to be someone new to git, I see it this way:
1) Let's read "git commit" documentation
2) Where on this commandline do I put my filename?!

So yes, I would repeat it in every location that could be popular for new users.

#8 lore raw

Re: [PATCH 2/5] doc: reset: unify <pathspec> description

In reply to Alexandr Miloslavskiy
Quoted 12 lines · Alexandr Miloslavskiy
Quoted 12 lines

It is not quite the opposite of adding to the index from the working tree. In this sequence:

	$ edit newfile
	$ git add newfile

and then further

	$ edit newfile
	$ git add newfile
	$ git reset -- newfile
Quoted 6 lines
Quoted 3 lines

we are taken back to the state _before_ any of the changes made to newfile (in fact, since HEAD does not have newfile, the resulting index would not know about it, either).

I am a bit confused, is it correct that you don't expect me to change my patches?

I do not know if removal of the only-half-correct "This is the opposite of add" should be part of this change, or it should be a separate fix. The half-wrong sentene was not introduced by this patch, so leaving it as-is is OK. It just leaves another thing for us to think about later.

Thanks.

#23 lore raw

Re: [PATCH 4/5] doc: commit: unify <pathspec> description

In reply to Alexandr Miloslavskiy
Quoted 8 lines · Alexandr Miloslavskiy

I think I have implemented most suggestions in PatchV2. Thanks!

Quoted 6 lines

I am not sure if we want to repeat this all over the place.

We do not say "For details about the <commit> syntax, see the 'SPECIFYING REVISIONS' section of linkgit:git-rev-parse[1]" for every command that takes <commit> from the command line.

Quoted 7 lines
Quoted 4 lines

Is your urge to suggest adding this sentence coming from that you are much more familiar with <commit> than <pathspec>? In other words, if you were more familiar with Git, would you still be adding this (and not corresponding one for <commit>)?

Commit is a well known term, dating from ancient times like CVS or even older.

That's more or less irrelevant.

I am reacting to this from your change that you omitted quoting in your reply:

Documentation/git-commit.txt changes to tracked files. · line 356

Quoted 2 lines
Documentation/git-commit.txt +2 -0
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].

That sentence is for those who have some notion of <pathspec> but does not know enough about its syntax.

CVS does not let you specify <commit> like "master^{/^fix frotz}~4"; A user a user who is familiar with CVS's commits would still want to refer to the section "For details about the <commit> syntax".

I am not advocating to add the reference to SPECIFYING REVISIONS section; instead we should let readers know that every time they see <defined word>, they can refer to the glossary for more details.

Quoted 1 line

Pathspec, however, is something new.

Compared to CVS, everything in Git may be new, but that was a news in 2010, not this year.

#9 lore raw

Re: [PATCH 2/5] doc: reset: unify <pathspec> description

In reply to Junio C Hamano
Quoted 5 lines · Junio C Hamano

I do not know if removal of the only-half-correct "This is the opposite of add" should be part of this change, or it should be a separate fix. The half-wrong sentene was not introduced by this patch, so leaving it as-is is OK. It just leaves another thing for us to think about later.

My feeling is that this problem is separate from the topic I'm working on. I only touched docs to synchronize <pathspec>, so that I can copy&paste new option description in next commit without tailoring it to local speech. Also, I must admit that upon reading your explanation, I felt that I lack the experience to update the writing properly.

#24 lore raw

Re: [PATCH 4/5] doc: commit: unify <pathspec> description

In reply to Junio C Hamano
Quoted 8 lines · Junio C Hamano

I am reacting to this from your change that you omitted quoting in your reply:

Documentation/git-commit.txt changes to tracked files. · line 356

Quoted 2 lines
Documentation/git-commit.txt +2 -0
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].

That sentence is for those who have some notion of <pathspec> but does not know enough about its syntax.

In the perfect world, I would expect _every_ 'pathspec' word in text to be an HTML-style link to a dedicated article, not just a paragraph in glossary.

MSDN is in general a good example [1]: there, it's easy to read only a small portion of article, ignoring anything you're not interested in, and still have all links at hand.

Regarding dedicated page: the content of 'pathspec' in glossary is already long enough for a page, and it could benefit from additional examples. Also, having a dedicated page makes linking easier, so that user doesn't have to scroll glossary.

Regarding links: I don't really understand what git's doc format allows. Is it just pure text in worst (or even average) case?

If it's usually something with clickable links, it could be worth to just insert links everywhere.

If it's usually plaintext, then "see the 'pathspec' entry in linkgit:gitglossary[7]" is a bit too verbose to repeat on every occasion. Still, I'd like to see links everywhere. One big reason is to let reader know that the explanation actually exists!

A compromise solution is to give every article header like this:

This article uses the following terms which are explained in linkgit:gitglossary[7]:
* pathspec
* tree-ish
* refspec

If it's close to top of article, then chances are that everyone will notice it. Also, it will not require extra verbosity in plaintext.

Quoted 7 lines

CVS does not let you specify <commit> like "master^{/^fix frotz}~4"; A user a user who is familiar with CVS's commits would still want to refer to the section "For details about the <commit> syntax".

I am not advocating to add the reference to SPECIFYING REVISIONS section; instead we should let readers know that every time they see <defined word>, they can refer to the glossary for more details.

I now think that my arguments apply to <pathspec> AND <commit> in the same way. Indeed a user can't know complex <commit> variants until he/she reads it in git docs.

----

[1] https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea

#10 lore raw

Re: [PATCH 2/5] doc: reset: unify <pathspec> description

In reply to Alexandr Miloslavskiy
Quoted 7 lines · Alexandr Miloslavskiy
Quoted 5 lines · Junio C Hamano

I do not know if removal of the only-half-correct "This is the opposite of add" should be part of this change, or it should be a separate fix. The half-wrong sentene was not introduced by this patch, so leaving it as-is is OK. It just leaves another thing for us to think about later.

Quoted 6 lines

My feeling is that this problem is separate from the topic I'm working on. I only touched docs to synchronize <pathspec>, so that I can copy&paste new option description in next commit without tailoring it to local speech. Also, I must admit that upon reading your explanation, I felt that I lack the experience to update the writing properly.

Yeah, I do think it is cleaner to leave it as a separate issue.

Thanks.

#40 lore raw

Re: [PATCH v2 2/6] pathspec: add new function to parse file

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

pathspec.c void parse_pathspec(struct pathspec *pathspec, · line 627

Quoted 10 lines · writes:
pathspec.c +10 -0
+void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
+			 unsigned flags, const char *prefix,
+			 const char *file, int nul_term_line)
+{
+	struct argv_array parsed_file = ARGV_ARRAY_INIT;
+	strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
+						       strbuf_getline;
+	struct strbuf buf = STRBUF_INIT;
+	struct strbuf unquoted = STRBUF_INIT;
+	FILE *in = NULL;

Useless initialization to NULL can be dropped here.

Quoted 4 lines
pathspec.c +4 -0
+	if (!strcmp(file, "-"))
+		in = stdin;
+	else
+		in = xfopen(file, "r");
#43 lore raw

Re: [PATCH v2 3/6] doc: reset: unify <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Quoted 4 lines · writes:

-'git reset' [-q] [<tree-ish>] [--] <paths>... -'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...] +'git reset' [-q] [<tree-ish>] [--] <pathspec>... +'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]

Good.

Documentation/git-reset.txt In the first and second form, copy entries from `<tree-ish>` to the index. · line 22

Quoted 6 lines
Documentation/git-reset.txt +2 -0
@@ -18,24 +18,26 @@ In the first and second form, copy entries from `<tree-ish>` to the index.
 In the third form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
+The <pathspec> is used to limit the paths affected by the operation
+(see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).

Good.

Documentation/git-reset.txt In the first and second form, copy entries from `<tree-ish>` to the index. · line 25

Quoted 4 lines
Documentation/git-reset.txt +2 -2
-'git reset' [-q] [<tree-ish>] [--] <paths>...::
-	This form resets the index entries for all `<paths>` to their
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
+	This form resets the index entries for all `<pathspec>` to their

This is not so good. The original pretended as if <paths> are the exact pathnames, so it was sort-of OK for it to say "for all paths". Since we are highlighting the fact that these are not pathnames but the patterns to match pathnames, however, the description needs a slight update to match, perhaps like

ths form resets the index entries for all paths that match the <pathspec> to their...

Documentation/git-reset.txt In the first and second form, copy entries from `<tree-ish>` to the index. · line 27

Quoted 2 lines
Documentation/git-reset.txt +0 -0
 	state at `<tree-ish>`.  (It does not affect the working tree or
 	the current branch.)

Thanks.

#51 lore raw

Re: [PATCH v2 6/6] commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Documentation/git-commit.txt FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) · line 288

Quoted 12 lines · writes:
Documentation/git-commit.txt +8 -0
@@ -277,6 +278,18 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
 	the last commit without committing changes that have
 	already been staged. If used together with `--allow-empty`
 	paths are also not required, and an empty commit will be created.
+	
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.

OK.

Documentation/git-commit.txt FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) · line 292

Quoted 3 lines
Documentation/git-commit.txt +3 -0
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and are not expected to be quoted.

Although it is not incorrect, "are not expected to be quoted" feels a bit weak as the technical description. Are they not expected to be quoted, but the command gracefully works on them even when they are found to be quoted?

Rephrasing to avoid such misinterpretation may be worth doing, perhaps

... and are taken as-is without unquoting (i.e. as if core.quotePath is set to false).

or something like that?

Thanks.

#47 lore raw

Re: [PATCH v2 5/6] doc: commit: unify <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Documentation/git-commit.txt changes to tracked files. · line 355

Quoted 8 lines · writes:

+<pathspec>...::

Documentation/git-commit.txt +7 -0
+	When pathspec is given on the command line, commit the contents of
+	the files that match the pathspec without recording the changes
+	already added to the index. The contents of these files are also
+	staged for the next commit on top of what have been staged before.
++
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].

What you added in [PATCH 3/6] (git reset doc) sends a slightly different message, i.e.

The <pathspec> is used to limit the paths affected by the operation (see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).

and I think that was more appropriate than what we see here. You are referring your readers to the glossary entry not just for the syntax but also the entire concept of <pathspec>.

IOW, it would be needed to drop "syntax" from "about the <pathspec> syntax" from here to match the update to "git reset" documentation.

Thanks.

#53 lore raw

v3 cover Add --pathspec-from-file option for reset, commit

In reply to Alexandr Miloslavskiy via GitGitGadget

Changes from V2:

1) Some polishing in documentation 2) = NULL removed from parse_pathspec_file()

Alexandr Miloslavskiy (6):
parse-options.h: add new options --pathspec-from-file,

    `--pathspec-file-nul`

pathspec: add new function to parse file
doc: reset: synchronize <pathspec> description
reset: support the --pathspec-from-file option
doc: commit: synchronize <pathspec> description
commit: support the --pathspec-from-file option

 Documentation/git-commit.txt    |  29 ++++--
 Documentation/git-reset.txt     |  48 +++++++---
 builtin/commit.c                |  25 +++++-
 builtin/reset.c                 |  25 +++++-
 parse-options.h                 |   2 +
 pathspec.c                      |  38 ++++++++
 pathspec.h                      |  10 +++
 t/t7107-reset-pathspec-file.sh  | 155 ++++++++++++++++++++++++++++++++
 t/t7526-commit-pathspec-file.sh | 130 +++++++++++++++++++++++++++
 9 files changed, 434 insertions(+), 28 deletions(-)
 create mode 100755 t/t7107-reset-pathspec-file.sh
 create mode 100755 t/t7526-commit-pathspec-file.sh

base-commit: da72936f544fec5a335e66432610e4cef4430991
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-445%2FSyntevoAlex%2F%230207_pathspec_from_file-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-445/SyntevoAlex/#0207_pathspec_from_file-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/445

Range-diff vs v2:

 1:  2dfaccf0d5 = 1:  19b80326ea parse-options.h: add new options `--pathspec-from-file`, `--pathspec-file-nul`
 2:  96697ba072 ! 2:  55a7c6ec3c pathspec: add new function to parse file
     @@ -45,7 +45,7 @@
      +						       strbuf_getline;
      +	struct strbuf buf = STRBUF_INIT;
      +	struct strbuf unquoted = STRBUF_INIT;
     -+	FILE *in = NULL;
     ++	FILE *in;
      +
      +	if (!strcmp(file, "-"))
      +		in = stdin;

3: f961a5155a ! 3: d9f32e523c doc: reset: unify <pathspec> description
@@ -1,8 +1,8 @@
Author: Alexandr Miloslavskiy <[email protected]>

- doc: reset: unify <pathspec> description
+ doc: reset: synchronize <pathspec> description

- Synchronize it to git add, which has a pretty good description.
+ git add shows an example of good writing, follow it.

@@ -21,18 +21,17 @@

DESCRIPTION
@@
- In the third form, set the current branch head (HEAD) to <commit>, optionally modifying index and working tree to match.
The <tree-ish>/<commit> defaults to HEAD in all forms.
-+The <pathspec> is used to limit the paths affected by the operation -+(see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).

-'git reset' [-q] [<tree-ish>] [--] <paths>...::
- This form resets the index entries for all <paths> to their
+- state at <tree-ish>. (It does not affect the working tree or
+- the current branch.)
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
-+ This form resets the index entries for all <pathspec> to their
- state at <tree-ish>. (It does not affect the working tree or
- the current branch.)
++ This form resets the index entries for all paths that match the
++ <pathspec> to their state at <tree-ish>. (It does not affect
++ the working tree or the current branch.)
+
-This means that git reset <paths> is the opposite of git add -<paths>. This command is equivalent to
@@ -54,6 +53,20 @@
Interactively select hunks in the difference between the index and <tree-ish> (defaults to HEAD). The chosen hunks are applied in reverse to the index.
+@@
+ reset.quiet config option. --quiet and --no-quiet will
+ override the default behavior.
+
++\--::
++ Do not interpret any more arguments as options.
++
++<pathspec>...::
++ Limits the paths affected by the operation.
+++
++For more details, see the 'pathspec' entry in linkgit:gitglossary[7]. +
+ EXAMPLES
+ --------

diff --git a/builtin/reset.c b/builtin/reset.c
--- a/builtin/reset.c
4: d72d4f16b5 ! 4: 8a10ff881b reset: support the --pathspec-from-file option
@@ -31,15 +31,13 @@
+In the last form, set the current branch head (HEAD) to <commit>,
optionally modifying index and working tree to match.
The <tree-ish>/<commit> defaults to HEAD in all forms.
- The <pathspec> is used to limit the paths affected by the operation
- (see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).

'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
-- This form resets the index entries for all <pathspec> to their
+- This form resets the index entries for all paths that match the
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]::
-+ These forms reset the index entries matching pathspec to their
- state at <tree-ish>. (It does not affect the working tree or
- the current branch.)
++ These forms reset the index entries for all paths that match the
+ <pathspec> to their state at <tree-ish>. (It does not affect
+ the working tree or the current branch.)
+
@@
reset.quiet config option. --quiet and --no-quiet will
@@ -55,10 +53,12 @@
+
+--pathspec-file-nul::
+ Only meaningful with --pathspec-from-file. Pathspec elements are
-+ separated with NUL character and are not expected to be quoted.
++ separated with NUL character and all other characters are taken
++ literally (including newlines and quotes).
++
+ \--::
+ Do not interpret any more arguments as options.

- EXAMPLES
- --------

diff --git a/builtin/reset.c b/builtin/reset.c
--- a/builtin/reset.c
@@ -241,7 +241,7 @@
+ printf "\"file\\101.t\"" >list &&
+ # Note: "git reset" has not yet learned to fail on wrong pathspecs
+ git reset --pathspec-from-file=list --pathspec-file-nul &&
-+
++
+ cat >expect <<-\EOF &&
+ D fileA.t
+ EOF
5: 20c4495fd3 ! 5: 0b79797e77 doc: commit: unify <pathspec> description
@@ -1,8 +1,8 @@
Author: Alexandr Miloslavskiy <[email protected]>

- doc: commit: unify <pathspec> description
+ doc: commit: synchronize <pathspec> description

- Synchronize it to git add, which has a pretty good description.
+ git add shows an example of good writing, follow it.
This also better disambiguates <file>... header.

@@ -35,8 +35,7 @@
+ already added to the index. The contents of these files are also
+ staged for the next commit on top of what have been staged before.
++
-+For more details about the <pathspec> syntax, see the 'pathspec' entry -+in linkgit:gitglossary[7].
++For more details, see the 'pathspec' entry in linkgit:gitglossary[7].

:git-commit: 1
include::date-formats.txt[]
6: cb5fc9b14d ! 6: 7e48212002 commit: support the --pathspec-from-file option

     @@ -25,10 +25,9 @@
       DESCRIPTION
       -----------
      @@
     - 	the last commit without committing changes that have
       	already been staged. If used together with `--allow-empty`
       	paths are also not required, and an empty commit will be created.
     -+	
     + 
      +--pathspec-from-file=<file>::
      +	Pathspec is passed in `<file>` instead of commandline args. If
      +	`<file>` is exactly `-` then standard input is used. Pathspec
     @@ -39,10 +38,12 @@
      +
      +--pathspec-file-nul::
      +	Only meaningful with `--pathspec-from-file`. Pathspec elements are
     -+	separated with NUL character and are not expected to be quoted.
     - 
     ++	separated with NUL character and all other characters are taken
     ++	literally (including newlines and quotes).
     ++
       -u[<mode>]::
       --untracked-files[=<mode>]::
     + 	Show untracked files.
      
       diff --git a/builtin/commit.c b/builtin/commit.c
       --- a/builtin/commit.c
     @@ -118,7 +119,7 @@
      +test_expect_success setup '
      +	test_commit file0 &&
      +	git tag checkpoint &&
     -+	
     ++
      +	echo A >fileA.t &&
      +	echo B >fileB.t &&
      +	echo C >fileC.t &&
Signature · 1 line
gitgitgadget
#56 lore raw

PATCH v3 3/6 doc: reset: synchronize <pathspec> description

From: Alexandr Miloslavskiy <[email protected]>

git add shows an example of good writing, follow it.

---
 Documentation/git-reset.txt | 29 ++++++++++++++++++-----------
 builtin/reset.c             |  4 ++--
 2 files changed, 20 insertions(+), 13 deletions(-)
Documentation/git-reset.txt +18 -11
index 97e0544d9e..d517a43e73 100644
@@ -8,8 +8,8 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [-q] [<tree-ish>] [--] <paths>...
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
@@ -19,23 +19,23 @@ In the third form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 
-'git reset' [-q] [<tree-ish>] [--] <paths>...::
-	This form resets the index entries for all `<paths>` to their
-	state at `<tree-ish>`.  (It does not affect the working tree or
-	the current branch.)
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
+	This form resets the index entries for all paths that match the
+	`<pathspec>` to their state at `<tree-ish>`.  (It does not affect
+	the working tree or the current branch.)
 +
-This means that `git reset <paths>` is the opposite of `git add
-<paths>`. This command is equivalent to
-`git restore [--source=<tree-ish>] --staged <paths>...`.
+This means that `git reset <pathspec>` is the opposite of `git add
+<pathspec>`. This command is equivalent to
+`git restore [--source=<tree-ish>] --staged <pathspec>...`.
 +
-After running `git reset <paths>` to update the index entry, you can
+After running `git reset <pathspec>` to update the index entry, you can
 use linkgit:git-restore[1] to check the contents out of the index to
 the working tree. Alternatively, using linkgit:git-restore[1]
 and specifying a commit with `--source`, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]::
 	Interactively select hunks in the difference between the index
 	and `<tree-ish>` (defaults to `HEAD`).  The chosen hunks are applied
 	in reverse to the index.
@@ -101,6 +101,13 @@ OPTIONS
 	`reset.quiet` config option. `--quiet` and `--no-quiet` will
 	override the default behavior.
 
+\--::
+	Do not interpret any more arguments as options.
+
+<pathspec>...::
+	Limits the paths affected by the operation.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
 EXAMPLES
 --------
builtin/reset.c +2 -2
index fdd572168b..9291c0fd72 100644
@@ -30,8 +30,8 @@
 
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
-	N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
-	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
+	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
+	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
 	NULL
 };
 
Signature · 1 line
gitgitgadget
#55 lore raw

PATCH v3 2/6 pathspec: add new function to parse file

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This will be used to support the new option '--pathspec-from-file' in git add, git-commit, git reset etc.

Note also that we specifically handle CR/LF line endings to support Windows better.

To simplify code, file is first parsed into argv_array. This allows to avoid refactoring parse_pathspec().

I considered adding nul_term_line to flags instead, but decided that it doesn't fit there.

The new code is mostly taken from cmd_update_index() and split_mail_conv().

---
 pathspec.c | 38 ++++++++++++++++++++++++++++++++++++++
 pathspec.h | 10 ++++++++++
 2 files changed, 48 insertions(+)
pathspec.c +38 -0
index 12c2b322b3..128f27fcb7 100644
@@ -3,6 +3,8 @@
 #include "dir.h"
 #include "pathspec.h"
 #include "attr.h"
+#include "argv-array.h"
+#include "quote.h"
 
 /*
  * Finds which of the given pathspecs match items in the index.
@@ -613,6 +615,42 @@ void parse_pathspec(struct pathspec *pathspec,
 	}
 }
 
+void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
+			 unsigned flags, const char *prefix,
+			 const char *file, int nul_term_line)
+{
+	struct argv_array parsed_file = ARGV_ARRAY_INIT;
+	strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
+						       strbuf_getline;
+	struct strbuf buf = STRBUF_INIT;
+	struct strbuf unquoted = STRBUF_INIT;
+	FILE *in;
+
+	if (!strcmp(file, "-"))
+		in = stdin;
+	else
+		in = xfopen(file, "r");
+
+	while (getline_fn(&buf, in) != EOF) {
+		if (!nul_term_line && buf.buf[0] == '"') {
+			strbuf_reset(&unquoted);
+			if (unquote_c_style(&unquoted, buf.buf, NULL))
+				die(_("line is badly quoted: %s"), buf.buf);
+			strbuf_swap(&buf, &unquoted);
+		}
+		argv_array_push(&parsed_file, buf.buf);
+		strbuf_reset(&buf);
+	}
+
+	strbuf_release(&unquoted);
+	strbuf_release(&buf);
+	if (in != stdin)
+		fclose(in);
+
+	parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv);
+	argv_array_clear(&parsed_file);
+}
+
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
 {
 	int i, j;
pathspec.h +10 -0
index 1c18a2c90c..a27dc81ba2 100644
@@ -85,6 +85,16 @@ void parse_pathspec(struct pathspec *pathspec,
 		    unsigned flags,
 		    const char *prefix,
 		    const char **args);
+/*
+ * Same as parse_pathspec() but uses file as input.
+ * When 'file' is exactly "-" it uses 'stdin' instead.
+ */
+void parse_pathspec_file(struct pathspec *pathspec,
+			 unsigned magic_mask,
+			 unsigned flags,
+			 const char *prefix,
+			 const char *file,
+			 int nul_term_line);
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
 void clear_pathspec(struct pathspec *);
 
Signature · 1 line
gitgitgadget
#58 lore raw

PATCH v3 5/6 doc: commit: synchronize <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

git add shows an example of good writing, follow it. This also better disambiguates <file>... header.

---
 Documentation/git-commit.txt | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)
Documentation/git-commit.txt +8 -7
index afa7b75a23..a0c44978ee 100644
@@ -13,7 +13,7 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<file>...]
+	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -345,12 +345,13 @@ changes to tracked files.
 \--::
 	Do not interpret any more arguments as options.
 
-<file>...::
-	When files are given on the command line, the command
-	commits the contents of the named files, without
-	recording the changes already staged.  The contents of
-	these files are also staged for the next commit on top
-	of what have been staged before.
+<pathspec>...::
+	When pathspec is given on the command line, commit the contents of
+	the files that match the pathspec without recording the changes
+	already added to the index. The contents of these files are also
+	staged for the next commit on top of what have been staged before.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
 :git-commit: 1
 include::date-formats.txt[]
Signature · 1 line
gitgitgadget
#57 lore raw

PATCH v3 4/6 reset: support the `--pathspec-from-file` option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --patch, even when <file> is not stdin. Such use case it not really expected. Also, it is harder to support in git commit, so I decided to make it incompatible in all places.
2) It is not allowed to pass pathspec in both args and file.

---
 Documentation/git-reset.txt    |  21 ++++-
 builtin/reset.c                |  21 ++++-
 t/t7107-reset-pathspec-file.sh | 155 +++++++++++++++++++++++++++++++++
 3 files changed, 192 insertions(+), 5 deletions(-)
 create mode 100755 t/t7107-reset-pathspec-file.sh
Documentation/git-reset.txt +18 -3
index d517a43e73..932080c55d 100644
@@ -9,18 +9,20 @@ SYNOPSIS
 --------
 [verse]
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
 'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-In the first and second form, copy entries from `<tree-ish>` to the index.
-In the third form, set the current branch head (`HEAD`) to `<commit>`,
+In the first three forms, copy entries from `<tree-ish>` to the index.
+In the last form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
-	This form resets the index entries for all paths that match the
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]::
+	These forms reset the index entries for all paths that match the
 	`<pathspec>` to their state at `<tree-ish>`.  (It does not affect
 	the working tree or the current branch.)
 +
@@ -101,6 +103,19 @@ OPTIONS
 	`reset.quiet` config option. `--quiet` and `--no-quiet` will
 	override the default behavior.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 \--::
 	Do not interpret any more arguments as options.
 
builtin/reset.c +19 -2
index 9291c0fd72..246bf9d737 100644
@@ -31,6 +31,7 @@
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
 	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
+	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
 	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
 	NULL
 };
@@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0, unborn;
-	const char *rev;
+	int patch_mode = 0, pathspec_file_nul = 0, unborn;
+	const char *rev, *pathspec_from_file = NULL;
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
@@ -306,6 +307,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
+		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 		OPT_END()
 	};
 
@@ -316,6 +319,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
+	if (pathspec_from_file) {
+		if (patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
 	unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
 	if (unborn) {
 		/* reset on unborn branch: treat as reset to empty tree */
t/t7107-reset-pathspec-file.sh new file +155 -0

155 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#54 lore raw

PATCH v3 1/6 parse-options.h: add new options `--pathspec-from-file`, `--pathspec-file-nul`

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Support for various porcelain commands will arrive via additional patches.

--pathspec-from-file solves the problem of commandline length limit for UIs built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UIs show executed commands to user. In this case, porcelain commands are expected. One reason for that is letting user learn git commands by clicking UI buttons. The other reason is letting user study the history of commands in case of any unexpected results. Both of these will lose most of their value if UI uses combinations of arcane plumbing commands.
2) Some UIs have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

--pathspec-from-file will behave very close to pathspec passed in commandline args, so that switching from one to another is simple.

--pathspec-from-file will read either a specified file or stdin (when file is exactly "-"). Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

--pathspec-file-nul switch mirrors -z already used in various places. Some porcelain commands, such as git commit, already use -z, therefore it needed a new unambiguous name.

New options do not have shorthands to avoid shorthand conflicts. It is not expected that they will be typed in console.

---
 parse-options.h | 2 ++
 1 file changed, 2 insertions(+)
parse-options.h +2 -0
index 38a33a087e..c6cc01e715 100644
@@ -330,5 +330,7 @@ int parse_opt_passthru_argv(const struct option *, const char *, int);
 #define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
 #define OPT_WITHOUT(v, h) _OPT_CONTAINS_OR_WITH("without", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
 #define OPT_CLEANUP(v) OPT_STRING(0, "cleanup", v, N_("mode"), N_("how to strip spaces and #comments from message"))
+#define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
+#define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, "pathspec-file-nul", v, N_("with --pathspec-from-file, pathspec elements are separated with NUL character"))
 
 #endif
Signature · 1 line
gitgitgadget
#59 lore raw

PATCH v3 6/6 commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --interactive/--patch, even when <file> is not stdin. Such use case it not really expected. Also, it would require changes to interactive_add().
2) It is not allowed to pass pathspec in both args and file.

---
 Documentation/git-commit.txt    |  16 +++-
 builtin/commit.c                |  25 +++++-
 t/t7526-commit-pathspec-file.sh | 130 ++++++++++++++++++++++++++++++++
 3 files changed, 166 insertions(+), 5 deletions(-)
 create mode 100755 t/t7526-commit-pathspec-file.sh
Documentation/git-commit.txt +15 -1
index a0c44978ee..ced5a9beab 100644
@@ -13,7 +13,8 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
+	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
+	   [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -278,6 +279,19 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
 	already been staged. If used together with `--allow-empty`
 	paths are also not required, and an empty commit will be created.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 -u[<mode>]::
 --untracked-files[=<mode>]::
 	Show untracked files.
builtin/commit.c +21 -4
index e588bc6ad3..ed40729355 100644
@@ -107,9 +107,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
-static int no_post_rewrite, allow_empty_message;
+static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit;
+static char *sign_commit, *pathspec_from_file;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -343,6 +343,23 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 		       PATHSPEC_PREFER_FULL,
 		       prefix, argv);
 
+	if (pathspec_from_file) {
+		if (interactive)
+			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
+	if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+		die(_("No paths with --include/--only does not make sense."));
+
 	if (read_cache_preload(&pathspec) < 0)
 		die(_("index file corrupt"));
 
@@ -1198,8 +1215,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
 	if (also + only + all + interactive > 1)
 		die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
-	if (argc == 0 && (also || (only && !amend && !allow_empty)))
-		die(_("No paths with --include/--only does not make sense."));
 	cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
 
 	handle_untracked_files_arg(s);
@@ -1535,6 +1550,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
 		OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
 		{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 		/* end commit contents options */
 
 		OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
t/t7526-commit-pathspec-file.sh new file +130 -0

130 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#41 lore raw

Re: [PATCH v2 2/6] pathspec: add new function to parse file

In reply to Junio C Hamano
Quoted 3 lines · Junio C Hamano

pathspec.c void parse_pathspec(struct pathspec *pathspec, · line 627

Quoted 1 line
pathspec.c +1 -0
+	FILE *in = NULL;

Useless initialization to NULL can be dropped here.

Changed in V3.

#44 lore raw

Re: [PATCH v2 3/6] doc: reset: unify <pathspec> description

In reply to Junio C Hamano
Quoted 11 lines · Junio C Hamano

Documentation/git-reset.txt In the first and second form, copy entries from `<tree-ish>` to the index. · line 25

Quoted 4 lines

-'git reset' [-q] [<tree-ish>] [--] <paths>...::

Documentation/git-reset.txt +2 -1
-	This form resets the index entries for all `<paths>` to their
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
+	This form resets the index entries for all `<pathspec>` to their

This is not so good. The original pretended as if <paths> are the exact pathnames, so it was sort-of OK for it to say "for all paths". Since we are highlighting the fact that these are not pathnames but the patterns to match pathnames, however, the description needs a slight update to match, perhaps like

Quoted 5 lines

ths form resets the index entries for all paths that match the <pathspec> to their...

Quoted 2 lines

state at <tree-ish>. (It does not affect the working tree or the current branch.)

Yes, I wasn't paying enough attention.
Changed in V3, thanks!

#48 lore raw

Re: [PATCH v2 5/6] doc: commit: unify <pathspec> description

In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano

Documentation/git-commit.txt changes to tracked files. · line 355

Quoted 2 lines
Documentation/git-commit.txt +2 -0
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].

What you added in [PATCH 3/6] (git reset doc) sends a slightly different message, i.e.

The <pathspec> is used to limit the paths affected by the operation (see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).

Quoted 6 lines

and I think that was more appropriate than what we see here. You are referring your readers to the glossary entry not just for the syntax but also the entire concept of <pathspec>.

IOW, it would be needed to drop "syntax" from "about the <pathspec> syntax" from here to match the update to "git reset" documentation.

I shortened it even more in V3, I think it's better now?

#52 lore raw

Re: [PATCH v2 6/6] commit: support the --pathspec-from-file option

In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano

Documentation/git-commit.txt FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) · line 292

Quoted 3 lines

+--pathspec-file-nul::

Documentation/git-commit.txt +2 -0
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and are not expected to be quoted.

Although it is not incorrect, "are not expected to be quoted" feels a bit weak as the technical description. Are they not expected to be quoted, but the command gracefully works on them even when they are found to be quoted?

Quoted 7 lines

Rephrasing to avoid such misinterpretation may be worth doing, perhaps

... and are taken as-is without unquoting (i.e. as if core.quotePath is set to false).

or something like that?

I think that a reference to core.quotePath could be confusing here, because it doesn't really affect the option. I have reworded it in V3, please see if you like it.

Thanks for pointing out!

#49 lore raw

Re: [PATCH v2 5/6] doc: commit: unify <pathspec> description

In reply to Junio C Hamano
Quoted 6 lines · Junio C Hamano

Documentation/git-commit.txt changes to tracked files. · line 355

Quoted 2 lines
Documentation/git-commit.txt +2 -0
+For more details about the <pathspec> syntax, see the 'pathspec' entry
+in linkgit:gitglossary[7].

What you added in [PATCH 3/6] (git reset doc) sends a slightly different message, i.e.

Quoted 6 lines

The <pathspec> is used to limit the paths affected by the operation (see the entry for 'pathspec' in linkgit:gitglossary[7] for more details).

and I think that was more appropriate than what we see here. You are referring your readers to the glossary entry not just for the syntax but also the entire concept of <pathspec>.

This has shown me that I didn't synchronize docs enough. I have studied docs for other commands and found out that most of them list <pathspec> in a separate paragraph under options.

I find it very reasonable, because that's where I would normally expect it as a reader, together with other options.

So I adjusted 'git-reset' docs to also list <pathspec> under options.

#60 lore raw
In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Quoted 5 lines · writes:

Changes from V2:

1) Some polishing in documentation 2) = NULL removed from parse_pathspec_file()

Quoted 8 lines

Alexandr Miloslavskiy (6):
parse-options.h: add new options --pathspec-from-file,

    `--pathspec-file-nul`

pathspec: add new function to parse file
doc: reset: synchronize <pathspec> description
reset: support the --pathspec-from-file option
doc: commit: synchronize <pathspec> description
commit: support the --pathspec-from-file option

Nicely done. I think this is now ready for 'next'.

Thanks.

#61 lore raw
Quoted 1 line · Junio C Hamano

Nicely done. I think this is now ready for 'next'.

Wonderful, thanks for your assistance!

I will continue with patches for other commands, then.

#62 lore raw

v4 cover Add --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

Changes from V3:
================

The branch was rebased onto latest master. These patches remain unchanged since they were accepted in V3: parse-options.h: add new options --pathspec-from-file, --pathspec-file-nulpathspec: add new function to parse file doc: reset: synchronize description reset: support the --pathspec-from-file option doc: commit: synchronize description commit:
support the --pathspec-from-file option These patches are new, extending support to more git commands: cmd_add: prepare for next patch add: support the --pathspec-from-file option doc: checkout: remove duplicate synopsis
doc: checkout: fix broken text reference doc: checkout: synchronize description doc: restore: synchronize description checkout, restore: support the --pathspec-from-file option

Alexandr Miloslavskiy (13):
parse-options.h: add new options --pathspec-from-file,

    `--pathspec-file-nul`

pathspec: add new function to parse file
doc: reset: synchronize <pathspec> description
reset: support the --pathspec-from-file option
doc: commit: synchronize <pathspec> description
commit: support the --pathspec-from-file option
cmd_add: prepare for next patch
add: support the --pathspec-from-file option
doc: checkout: remove duplicate synopsis
doc: checkout: fix broken text reference
doc: checkout: synchronize <pathspec> description
doc: restore: synchronize <pathspec> description checkout, restore: support the --pathspec-from-file option

 Documentation/git-add.txt         |  16 ++-
 Documentation/git-checkout.txt    |  50 +++++++---
 Documentation/git-commit.txt      |  29 ++++--
 Documentation/git-reset.txt       |  48 ++++++---
 Documentation/git-restore.txt     |  26 ++++-
 builtin/add.c                     |  60 ++++++++----
 builtin/checkout.c                |  31 +++++-
 builtin/commit.c                  |  25 ++++-
 builtin/reset.c                   |  25 ++++-
 parse-options.h                   |   2 +
 pathspec.c                        |  38 ++++++++
 pathspec.h                        |  10 ++
 t/t2026-checkout-pathspec-file.sh | 139 +++++++++++++++++++++++++++
 t/t2072-restore-pathspec-file.sh  | 139 +++++++++++++++++++++++++++
 t/t3704-add-pathspec-file.sh      | 127 ++++++++++++++++++++++++
 t/t7107-reset-pathspec-file.sh    | 155 ++++++++++++++++++++++++++++++
 t/t7526-commit-pathspec-file.sh   | 130 +++++++++++++++++++++++++
 t/t9902-completion.sh             |   2 +
 18 files changed, 982 insertions(+), 70 deletions(-)
 create mode 100755 t/t2026-checkout-pathspec-file.sh
 create mode 100755 t/t2072-restore-pathspec-file.sh
 create mode 100755 t/t3704-add-pathspec-file.sh
 create mode 100755 t/t7107-reset-pathspec-file.sh
 create mode 100755 t/t7526-commit-pathspec-file.sh

base-commit: 228f53135a4a41a37b6be8e4d6e2b6153db4a8ed
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-445%2FSyntevoAlex%2F%230207_pathspec_from_file-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-445/SyntevoAlex/#0207_pathspec_from_file-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/445

Range-diff vs v3:

  1:  19b80326ea =  1:  cca5aee392 parse-options.h: add new options `--pathspec-from-file`, `--pathspec-file-nul`
  2:  55a7c6ec3c =  2:  fea64dfbf9 pathspec: add new function to parse file
  3:  d9f32e523c =  3:  1182ba3953 doc: reset: synchronize <pathspec> description
  4:  8a10ff881b =  4:  cea470fc91 reset: support the `--pathspec-from-file` option
  5:  0b79797e77 =  5:  0e1ac7e8a7 doc: commit: synchronize <pathspec> description
  6:  7e48212002 =  6:  c877866c13 commit: support the --pathspec-from-file option
  -:  ---------- >  7:  a97910cb55 cmd_add: prepare for next patch
  -:  ---------- >  8:  9a62da3470 add: support the --pathspec-from-file option
  -:  ---------- >  9:  5e449c8d29 doc: checkout: remove duplicate synopsis
  -:  ---------- > 10:  a498dda97b doc: checkout: fix broken text reference
  -:  ---------- > 11:  fd166755aa doc: checkout: synchronize <pathspec> description
  -:  ---------- > 12:  9e37a740e6 doc: restore: synchronize <pathspec> description
  -:  ---------- > 13:  c4dd4eaf13 checkout, restore: support the --pathspec-from-file option
Signature · 1 line
gitgitgadget
#63 lore raw

PATCH v4 1/13 parse-options.h: add new options `--pathspec-from-file`, `--pathspec-file-nul`

From: Alexandr Miloslavskiy <[email protected]>

Support for various porcelain commands will arrive via additional patches.

--pathspec-from-file solves the problem of commandline length limit for UIs built on top of git. Plumbing commands are not always a good fit, for two major reasons:
1) Some UIs show executed commands to user. In this case, porcelain commands are expected. One reason for that is letting user learn git commands by clicking UI buttons. The other reason is letting user study the history of commands in case of any unexpected results. Both of these will lose most of their value if UI uses combinations of arcane plumbing commands.
2) Some UIs have started and grown with porcelain commands. Replacing existing logic with plumbing commands could be cumbersome and prone to various new problems.

--pathspec-from-file will behave very close to pathspec passed in commandline args, so that switching from one to another is simple.

--pathspec-from-file will read either a specified file or stdin (when file is exactly "-"). Reading from file is a good way to avoid competing for stdin, and also gives some extra flexibility.

--pathspec-file-nul switch mirrors -z already used in various places. Some porcelain commands, such as git commit, already use -z, therefore it needed a new unambiguous name.

New options do not have shorthands to avoid shorthand conflicts. It is not expected that they will be typed in console.

---
 parse-options.h | 2 ++
 1 file changed, 2 insertions(+)
parse-options.h +2 -0
index 38a33a087e..c6cc01e715 100644
@@ -330,5 +330,7 @@ int parse_opt_passthru_argv(const struct option *, const char *, int);
 #define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
 #define OPT_WITHOUT(v, h) _OPT_CONTAINS_OR_WITH("without", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG)
 #define OPT_CLEANUP(v) OPT_STRING(0, "cleanup", v, N_("mode"), N_("how to strip spaces and #comments from message"))
+#define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
+#define OPT_PATHSPEC_FILE_NUL(v)  OPT_BOOL(0, "pathspec-file-nul", v, N_("with --pathspec-from-file, pathspec elements are separated with NUL character"))
 
 #endif
Signature · 1 line
gitgitgadget
#65 lore raw

PATCH v4 3/13 doc: reset: synchronize <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

git add shows an example of good writing, follow it.

---
 Documentation/git-reset.txt | 29 ++++++++++++++++++-----------
 builtin/reset.c             |  4 ++--
 2 files changed, 20 insertions(+), 13 deletions(-)
Documentation/git-reset.txt +18 -11
index 97e0544d9e..d517a43e73 100644
@@ -8,8 +8,8 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [-q] [<tree-ish>] [--] <paths>...
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
@@ -19,23 +19,23 @@ In the third form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 
-'git reset' [-q] [<tree-ish>] [--] <paths>...::
-	This form resets the index entries for all `<paths>` to their
-	state at `<tree-ish>`.  (It does not affect the working tree or
-	the current branch.)
+'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
+	This form resets the index entries for all paths that match the
+	`<pathspec>` to their state at `<tree-ish>`.  (It does not affect
+	the working tree or the current branch.)
 +
-This means that `git reset <paths>` is the opposite of `git add
-<paths>`. This command is equivalent to
-`git restore [--source=<tree-ish>] --staged <paths>...`.
+This means that `git reset <pathspec>` is the opposite of `git add
+<pathspec>`. This command is equivalent to
+`git restore [--source=<tree-ish>] --staged <pathspec>...`.
 +
-After running `git reset <paths>` to update the index entry, you can
+After running `git reset <pathspec>` to update the index entry, you can
 use linkgit:git-restore[1] to check the contents out of the index to
 the working tree. Alternatively, using linkgit:git-restore[1]
 and specifying a commit with `--source`, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]::
 	Interactively select hunks in the difference between the index
 	and `<tree-ish>` (defaults to `HEAD`).  The chosen hunks are applied
 	in reverse to the index.
@@ -101,6 +101,13 @@ OPTIONS
 	`reset.quiet` config option. `--quiet` and `--no-quiet` will
 	override the default behavior.
 
+\--::
+	Do not interpret any more arguments as options.
+
+<pathspec>...::
+	Limits the paths affected by the operation.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
 EXAMPLES
 --------
builtin/reset.c +2 -2
index fdd572168b..9291c0fd72 100644
@@ -30,8 +30,8 @@
 
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
-	N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
-	N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
+	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
+	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
 	NULL
 };
 
Signature · 1 line
gitgitgadget
#64 lore raw

PATCH v4 2/13 pathspec: add new function to parse file

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

This will be used to support the new option '--pathspec-from-file' in git add, git-commit, git reset etc.

Note also that we specifically handle CR/LF line endings to support Windows better.

To simplify code, file is first parsed into argv_array. This allows to avoid refactoring parse_pathspec().

I considered adding nul_term_line to flags instead, but decided that it doesn't fit there.

The new code is mostly taken from cmd_update_index() and split_mail_conv().

---
 pathspec.c | 38 ++++++++++++++++++++++++++++++++++++++
 pathspec.h | 10 ++++++++++
 2 files changed, 48 insertions(+)
pathspec.c +38 -0
index 12c2b322b3..128f27fcb7 100644
@@ -3,6 +3,8 @@
 #include "dir.h"
 #include "pathspec.h"
 #include "attr.h"
+#include "argv-array.h"
+#include "quote.h"
 
 /*
  * Finds which of the given pathspecs match items in the index.
@@ -613,6 +615,42 @@ void parse_pathspec(struct pathspec *pathspec,
 	}
 }
 
+void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
+			 unsigned flags, const char *prefix,
+			 const char *file, int nul_term_line)
+{
+	struct argv_array parsed_file = ARGV_ARRAY_INIT;
+	strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
+						       strbuf_getline;
+	struct strbuf buf = STRBUF_INIT;
+	struct strbuf unquoted = STRBUF_INIT;
+	FILE *in;
+
+	if (!strcmp(file, "-"))
+		in = stdin;
+	else
+		in = xfopen(file, "r");
+
+	while (getline_fn(&buf, in) != EOF) {
+		if (!nul_term_line && buf.buf[0] == '"') {
+			strbuf_reset(&unquoted);
+			if (unquote_c_style(&unquoted, buf.buf, NULL))
+				die(_("line is badly quoted: %s"), buf.buf);
+			strbuf_swap(&buf, &unquoted);
+		}
+		argv_array_push(&parsed_file, buf.buf);
+		strbuf_reset(&buf);
+	}
+
+	strbuf_release(&unquoted);
+	strbuf_release(&buf);
+	if (in != stdin)
+		fclose(in);
+
+	parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv);
+	argv_array_clear(&parsed_file);
+}
+
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
 {
 	int i, j;
pathspec.h +10 -0
index 1c18a2c90c..a27dc81ba2 100644
@@ -85,6 +85,16 @@ void parse_pathspec(struct pathspec *pathspec,
 		    unsigned flags,
 		    const char *prefix,
 		    const char **args);
+/*
+ * Same as parse_pathspec() but uses file as input.
+ * When 'file' is exactly "-" it uses 'stdin' instead.
+ */
+void parse_pathspec_file(struct pathspec *pathspec,
+			 unsigned magic_mask,
+			 unsigned flags,
+			 const char *prefix,
+			 const char *file,
+			 int nul_term_line);
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
 void clear_pathspec(struct pathspec *);
 
Signature · 1 line
gitgitgadget
#67 lore raw

PATCH v4 5/13 doc: commit: synchronize <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

git add shows an example of good writing, follow it. This also better disambiguates <file>... header.

---
 Documentation/git-commit.txt | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)
Documentation/git-commit.txt +8 -7
index afa7b75a23..a0c44978ee 100644
@@ -13,7 +13,7 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<file>...]
+	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -345,12 +345,13 @@ changes to tracked files.
 \--::
 	Do not interpret any more arguments as options.
 
-<file>...::
-	When files are given on the command line, the command
-	commits the contents of the named files, without
-	recording the changes already staged.  The contents of
-	these files are also staged for the next commit on top
-	of what have been staged before.
+<pathspec>...::
+	When pathspec is given on the command line, commit the contents of
+	the files that match the pathspec without recording the changes
+	already added to the index. The contents of these files are also
+	staged for the next commit on top of what have been staged before.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
 :git-commit: 1
 include::date-formats.txt[]
Signature · 1 line
gitgitgadget
#68 lore raw

PATCH v4 6/13 commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --interactive/--patch, even when <file> is not stdin. Such use case it not really expected. Also, it would require changes to interactive_add().
2) It is not allowed to pass pathspec in both args and file.

---
 Documentation/git-commit.txt    |  16 +++-
 builtin/commit.c                |  25 +++++-
 t/t7526-commit-pathspec-file.sh | 130 ++++++++++++++++++++++++++++++++
 3 files changed, 166 insertions(+), 5 deletions(-)
 create mode 100755 t/t7526-commit-pathspec-file.sh
Documentation/git-commit.txt +15 -1
index a0c44978ee..ced5a9beab 100644
@@ -13,7 +13,8 @@ SYNOPSIS
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-	   [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
+	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
+	   [-S[<keyid>]] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -278,6 +279,19 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
 	already been staged. If used together with `--allow-empty`
 	paths are also not required, and an empty commit will be created.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 -u[<mode>]::
 --untracked-files[=<mode>]::
 	Show untracked files.
builtin/commit.c +21 -4
index 294dc574cd..2db2ad0de4 100644
@@ -107,9 +107,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff;
 static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
-static int no_post_rewrite, allow_empty_message;
+static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit;
+static char *sign_commit, *pathspec_from_file;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -343,6 +343,23 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 		       PATHSPEC_PREFER_FULL,
 		       prefix, argv);
 
+	if (pathspec_from_file) {
+		if (interactive)
+			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
+	if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+		die(_("No paths with --include/--only does not make sense."));
+
 	if (read_cache_preload(&pathspec) < 0)
 		die(_("index file corrupt"));
 
@@ -1198,8 +1215,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
 	if (also + only + all + interactive > 1)
 		die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
-	if (argc == 0 && (also || (only && !amend && !allow_empty)))
-		die(_("No paths with --include/--only does not make sense."));
 	cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
 
 	handle_untracked_files_arg(s);
@@ -1513,6 +1528,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
 		OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
 		{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 		/* end commit contents options */
 
 		OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
t/t7526-commit-pathspec-file.sh new file +130 -0

130 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#69 lore raw

PATCH v4 7/13 cmd_add: prepare for next patch

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Some code blocks were moved down to be able to test for pathspec.nr in the next patch. Blocks are moved as is without any changes. This is done as separate patch to reduce the amount of diffs in next patch.

---
 builtin/add.c | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)
builtin/add.c +17 -17
index dd18e5c9b6..4fabdc72e6 100644
@@ -418,10 +418,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 	if (addremove && take_worktree_changes)
 		die(_("-A and -u are mutually incompatible"));
 
-	if (!take_worktree_changes && addremove_explicit < 0 && argc)
-		/* Turn "git add pathspec..." to "git add -A pathspec..." */
-		addremove = 1;
-
 	if (!show_only && ignore_missing)
 		die(_("Option --ignore-missing can only be used together with --dry-run"));
 
@@ -434,19 +430,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
 	hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 
-	flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
-		 (show_only ? ADD_CACHE_PRETEND : 0) |
-		 (intent_to_add ? ADD_CACHE_INTENT : 0) |
-		 (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
-		 (!(addremove || take_worktree_changes)
-		  ? ADD_CACHE_IGNORE_REMOVAL : 0));
-
-	if (require_pathspec && argc == 0) {
-		fprintf(stderr, _("Nothing specified, nothing added.\n"));
-		fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
-		return 0;
-	}
-
 	/*
 	 * Check the "pathspec '%s' did not match any files" block
 	 * below before enabling new magic.
@@ -456,6 +439,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		       PATHSPEC_SYMLINK_LEADING_PATH,
 		       prefix, argv);
 
+	if (require_pathspec && argc == 0) {
+		fprintf(stderr, _("Nothing specified, nothing added.\n"));
+		fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
+		return 0;
+	}
+
+	if (!take_worktree_changes && addremove_explicit < 0 && argc)
+		/* Turn "git add pathspec..." to "git add -A pathspec..." */
+		addremove = 1;
+
+	flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
+		 (show_only ? ADD_CACHE_PRETEND : 0) |
+		 (intent_to_add ? ADD_CACHE_INTENT : 0) |
+		 (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
+		 (!(addremove || take_worktree_changes)
+		  ? ADD_CACHE_IGNORE_REMOVAL : 0));
+
 	if (read_cache_preload(&pathspec) < 0)
 		die(_("index file corrupt"));
 
Signature · 1 line
gitgitgadget
#72 lore raw

PATCH v4 10/13 doc: checkout: fix broken text reference

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

---
 Documentation/git-checkout.txt | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)
Documentation/git-checkout.txt +4 -6
index 2011fdbb1d..d47046e050 100644
@@ -95,12 +95,10 @@ using `--ours` or `--theirs`.  With `-m`, changes made to the working tree
 file can be discarded to re-create the original conflicted merge result.
 
 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]::
-	This is similar to the "check out paths to the working tree
-	from either the index or from a tree-ish" mode described
-	above, but lets you use the interactive interface to show
-	the "diff" output and choose which hunks to use in the
-	result.  See below for the description of `--patch` option.
-
+	This is similar to the previous mode, but lets you use the
+	interactive interface to show the "diff" output and choose which
+	hunks to use in the result.  See below for the description of
+	`--patch` option.
 
 OPTIONS
 -------
Signature · 1 line
gitgitgadget
#71 lore raw

PATCH v4 9/13 doc: checkout: remove duplicate synopsis

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

It was added in [1]. I understand that the duplicate change was not intentional and comes from an oversight.

Also, in explanation, there was only one section for two synopsis entries.

Fix both problems by removing duplicate synopsis.

<paths> vs <pathspec> is resolved in next patch.

[1] Commit b59698ae ("checkout doc: clarify command line args for "checkout paths" mode" 2017-10-11)

---
 Documentation/git-checkout.txt | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
Documentation/git-checkout.txt +1 -2
index cf3cac0a2b..2011fdbb1d 100644
@@ -13,7 +13,6 @@ SYNOPSIS
 'git checkout' [-q] [-f] [-m] [--detach] <commit>
 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
-'git checkout' [<tree-ish>] [--] <pathspec>...
 'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]
 
 DESCRIPTION
@@ -79,7 +78,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout
 +
 Omitting `<branch>` detaches `HEAD` at the tip of the current branch.
 
-'git checkout' [<tree-ish>] [--] <pathspec>...::
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::
 
 	Overwrite paths in the working tree by replacing with the
 	contents in the index or in the `<tree-ish>` (most often a
Signature · 1 line
gitgitgadget
#66 lore raw

PATCH v4 4/13 reset: support the `--pathspec-from-file` option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --patch, even when <file> is not stdin. Such use case it not really expected. Also, it is harder to support in git commit, so I decided to make it incompatible in all places.
2) It is not allowed to pass pathspec in both args and file.

---
 Documentation/git-reset.txt    |  21 ++++-
 builtin/reset.c                |  21 ++++-
 t/t7107-reset-pathspec-file.sh | 155 +++++++++++++++++++++++++++++++++
 3 files changed, 192 insertions(+), 5 deletions(-)
 create mode 100755 t/t7107-reset-pathspec-file.sh
Documentation/git-reset.txt +18 -3
index d517a43e73..932080c55d 100644
@@ -9,18 +9,20 @@ SYNOPSIS
 --------
 [verse]
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
 'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-In the first and second form, copy entries from `<tree-ish>` to the index.
-In the third form, set the current branch head (`HEAD`) to `<commit>`,
+In the first three forms, copy entries from `<tree-ish>` to the index.
+In the last form, set the current branch head (`HEAD`) to `<commit>`,
 optionally modifying index and working tree to match.
 The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
 
 'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
-	This form resets the index entries for all paths that match the
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]::
+	These forms reset the index entries for all paths that match the
 	`<pathspec>` to their state at `<tree-ish>`.  (It does not affect
 	the working tree or the current branch.)
 +
@@ -101,6 +103,19 @@ OPTIONS
 	`reset.quiet` config option. `--quiet` and `--no-quiet` will
 	override the default behavior.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 \--::
 	Do not interpret any more arguments as options.
 
builtin/reset.c +19 -2
index 9291c0fd72..246bf9d737 100644
@@ -31,6 +31,7 @@
 static const char * const git_reset_usage[] = {
 	N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
 	N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
+	N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
 	N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
 	NULL
 };
@@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int reset_type = NONE, update_ref_status = 0, quiet = 0;
-	int patch_mode = 0, unborn;
-	const char *rev;
+	int patch_mode = 0, pathspec_file_nul = 0, unborn;
+	const char *rev, *pathspec_from_file = NULL;
 	struct object_id oid;
 	struct pathspec pathspec;
 	int intent_to_add = 0;
@@ -306,6 +307,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
 		OPT_BOOL('N', "intent-to-add", &intent_to_add,
 				N_("record only the fact that removed paths will be added later")),
+		OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 		OPT_END()
 	};
 
@@ -316,6 +319,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 						PARSE_OPT_KEEP_DASHDASH);
 	parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
+	if (pathspec_from_file) {
+		if (patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));
+
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
 	unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
 	if (unborn) {
 		/* reset on unborn branch: treat as reset to empty tree */
t/t7107-reset-pathspec-file.sh new file +155 -0

155 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#70 lore raw

PATCH v4 8/13 add: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --interactive/--patch/--edit, even when <file> is not stdin. Such use case it not really expected. Also, it would require changes to interactive_add() and edit_patch().
2) It is not allowed to pass pathspec in both args and file.

---
 Documentation/git-add.txt    |  16 ++++-
 builtin/add.c                |  30 +++++++--
 t/t3704-add-pathspec-file.sh | 127 +++++++++++++++++++++++++++++++++++
 3 files changed, 168 insertions(+), 5 deletions(-)
 create mode 100755 t/t3704-add-pathspec-file.sh
Documentation/git-add.txt +15 -1
index 8b0e4c7fa8..be5e3ac54b 100644
@@ -11,7 +11,8 @@ SYNOPSIS
 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
 	  [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
 	  [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
-	  [--chmod=(+|-)x] [--] [<pathspec>...]
+	  [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
+	  [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -187,6 +188,19 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
 	bit is only changed in the index, the files on disk are left
 	unchanged.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 \--::
 	This option can be used to separate command-line options from
 	the list of files, (useful when filenames might be mistaken
builtin/add.c +26 -4
index 4fabdc72e6..9f6b263aba 100644
@@ -28,6 +28,8 @@ static const char * const builtin_add_usage[] = {
 static int patch_interactive, add_interactive, edit_interactive;
 static int take_worktree_changes;
 static int add_renormalize;
+static int pathspec_file_nul;
+static const char *pathspec_from_file;
 
 struct update_callback_data {
 	int flags;
@@ -309,6 +311,8 @@ static struct option builtin_add_options[] = {
 		   N_("override the executable bit of the listed files")),
 	OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
 			N_("warn when adding an embedded repository")),
+	OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+	OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
 	OPT_END(),
 };
 
@@ -402,11 +406,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 			  builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
 	if (patch_interactive)
 		add_interactive = 1;
-	if (add_interactive)
+	if (add_interactive) {
+		if (pathspec_from_file)
+			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
 		exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
+	}
 
-	if (edit_interactive)
+	if (edit_interactive) {
+		if (pathspec_from_file)
+			die(_("--pathspec-from-file is incompatible with --edit"));
 		return(edit_patch(argc, argv, prefix));
+	}
 	argc--;
 	argv++;
 
@@ -439,13 +449,25 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		       PATHSPEC_SYMLINK_LEADING_PATH,
 		       prefix, argv);
 
-	if (require_pathspec && argc == 0) {
+	if (pathspec_from_file) {
+		if (pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		parse_pathspec_file(&pathspec, PATHSPEC_ATTR,
+				    PATHSPEC_PREFER_FULL |
+				    PATHSPEC_SYMLINK_LEADING_PATH,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
+	if (require_pathspec && pathspec.nr == 0) {
 		fprintf(stderr, _("Nothing specified, nothing added.\n"));
 		fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
 		return 0;
 	}
 
-	if (!take_worktree_changes && addremove_explicit < 0 && argc)
+	if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)
 		/* Turn "git add pathspec..." to "git add -A pathspec..." */
 		addremove = 1;
 
t/t3704-add-pathspec-file.sh new file +127 -0

127 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#75 lore raw

PATCH v4 13/13 checkout, restore: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

Decisions taken for simplicity:
1) For now, --pathspec-from-file is declared incompatible with --patch, even when <file> is not stdin. Such use case it not really expected.
2) It is not allowed to pass pathspec in both args and file.

you must specify path(s) to restore block was moved down to be able to test for pathspec.nr instead, because testing for argc is no longer correct.

git switch does not support the new options because it doesn't expect <pathspec> arguments.

---
 Documentation/git-checkout.txt    |  15 ++++
 Documentation/git-restore.txt     |  14 +++
 builtin/checkout.c                |  31 ++++++-
 t/t2026-checkout-pathspec-file.sh | 139 ++++++++++++++++++++++++++++++
 t/t2072-restore-pathspec-file.sh  | 139 ++++++++++++++++++++++++++++++
 t/t9902-completion.sh             |   2 +
 6 files changed, 336 insertions(+), 4 deletions(-)
 create mode 100755 t/t2026-checkout-pathspec-file.sh
 create mode 100755 t/t2072-restore-pathspec-file.sh
Documentation/git-checkout.txt +15 -0
index 93124f3ad9..ffe3c1bff2 100644
@@ -13,6 +13,7 @@ SYNOPSIS
 'git checkout' [-q] [-f] [-m] [--detach] <commit>
 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]
 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]
 
 DESCRIPTION
@@ -79,6 +80,7 @@ be used to detach `HEAD` at the tip of the branch (`git checkout
 Omitting `<branch>` detaches `HEAD` at the tip of the current branch.
 
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]::
 
 	Overwrite the contents of the files that match the pathspec.
 	When the `<tree-ish>` (most often a commit) is not given, 
@@ -306,6 +308,19 @@ Note that this option uses the no overlay mode by default (see also
 	working tree, but not in `<tree-ish>` are removed, to make them
 	match `<tree-ish>` exactly.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
Documentation/git-restore.txt +14 -0
index d7bf016bba..5bf60d4943 100644
@@ -9,6 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...
+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] --pathspec-from-file=<file> [--pathspec-file-nul]
 'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]
 
 DESCRIPTION
@@ -113,6 +114,19 @@ in linkgit:git-checkout[1] for details.
 	appear in the `--source` tree are removed, to make them match
 	`<tree>` exactly. The default is no-overlay mode.
 
+--pathspec-from-file=<file>::
+	Pathspec is passed in `<file>` instead of commandline args. If
+	`<file>` is exactly `-` then standard input is used. Pathspec
+	elements are separated by LF or CR/LF. Pathspec elements can be
+	quoted as explained for the configuration variable `core.quotePath`
+	(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+	global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+	Only meaningful with `--pathspec-from-file`. Pathspec elements are
+	separated with NUL character and all other characters are taken
+	literally (including newlines and quotes).
+
 \--::
 	Do not interpret any more arguments as options.
 
builtin/checkout.c +27 -4
index 3634a3dac1..b52c490c8f 100644
@@ -70,6 +70,8 @@ struct checkout_opts {
 	int checkout_worktree;
 	const char *ignore_unmerged_opt;
 	int ignore_unmerged;
+	int pathspec_file_nul;
+	const char *pathspec_from_file;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -1480,6 +1482,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
+		OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
+		OPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul),
 		OPT_END()
 	};
 	struct option *newopts = parse_options_concat(prevopts, options);
@@ -1618,10 +1622,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 			die(_("reference is not a tree: %s"), opts->from_treeish);
 	}
 
-	if (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc &&
-	    !opts->patch_mode)	/* patch mode is special */
-		die(_("you must specify path(s) to restore"));
-
 	if (argc) {
 		parse_pathspec(&opts->pathspec, 0,
 			       opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
@@ -1641,10 +1641,33 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
+	}
+
+	if (opts->pathspec_from_file) {
+		if (opts->pathspec.nr)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		if (opts->force_detach)
+			die(_("--pathspec-from-file is incompatible with --detach"));
 
+		if (opts->patch_mode)
+			die(_("--pathspec-from-file is incompatible with --patch"));
+
+		parse_pathspec_file(&opts->pathspec, 0,
+				    0,
+				    prefix, opts->pathspec_from_file, opts->pathspec_file_nul);
+	} else if (opts->pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	}
+
+	if (opts->pathspec.nr) {
 		if (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge)
 			die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
 			      "checking out of the index."));
+	} else {
+		if (opts->accept_pathspec && !opts->empty_pathspec_ok &&
+		    !opts->patch_mode)	/* patch mode is special */
+			die(_("you must specify path(s) to restore"));
 	}
 
 	if (opts->new_branch) {
t/t2026-checkout-pathspec-file.sh new file +139 -0

139 lines not on this page · raw message

t/t2072-restore-pathspec-file.sh new file +139 -0

139 lines not on this page · raw message

t/t9902-completion.sh +2 -0
index ec3eccfd3d..93877ba9cd 100755
@@ -1438,6 +1438,8 @@ test_expect_success 'double dash "git checkout"' '
 	--no-guess Z
 	--no-... Z
 	--overlay Z
+	--pathspec-file-nul Z
+	--pathspec-from-file=Z
 	EOF
 '
 
Signature · 1 line
gitgitgadget
#73 lore raw

PATCH v4 11/13 doc: checkout: synchronize <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

git add shows an example of good writing, follow it.

---
 Documentation/git-checkout.txt | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)
Documentation/git-checkout.txt +15 -9
index d47046e050..93124f3ad9 100644
@@ -12,13 +12,13 @@ SYNOPSIS
 'git checkout' [-q] [-f] [-m] --detach [<branch>]
 'git checkout' [-q] [-f] [-m] [--detach] <commit>
 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
-'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...
+'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
 Updates files in the working tree to match the version in the index
-or the specified tree.  If no paths are given, 'git checkout' will
+or the specified tree.  If no pathspec was given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
@@ -78,13 +78,13 @@ be used to detach `HEAD` at the tip of the branch (`git checkout
 +
 Omitting `<branch>` detaches `HEAD` at the tip of the current branch.
 
-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...::
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...::
 
-	Overwrite paths in the working tree by replacing with the
-	contents in the index or in the `<tree-ish>` (most often a
-	commit).  When a `<tree-ish>` is given, the paths that
-	match the `<pathspec>` are updated both in the index and in
-	the working tree.
+	Overwrite the contents of the files that match the pathspec.
+	When the `<tree-ish>` (most often a commit) is not given, 
+	overwrite working tree with the contents in the index.
+	When the `<tree-ish>` is given, overwrite both the index and
+	the working tree with the contents at the `<tree-ish>`.
 +
 The index may contain unmerged entries because of a previous failed merge.
 By default, if you try to check out such an entry from the index, the
@@ -336,7 +336,13 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 	Tree to checkout from (when paths are given). If not specified,
 	the index will be used.
 
+\--::
+	Do not interpret any more arguments as options.
 
+<pathspec>...::
+	Limits the paths affected by the operation.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 
 DETACHED HEAD
 -------------
Signature · 1 line
gitgitgadget
#74 lore raw

PATCH v4 12/13 doc: restore: synchronize <pathspec> description

In reply to Alexandr Miloslavskiy via GitGitGadget

From: Alexandr Miloslavskiy <[email protected]>

git add shows an example of good writing, follow it.

---
 Documentation/git-restore.txt | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
Documentation/git-restore.txt +10 -2
index 1ab2e40ea9..d7bf016bba 100644
@@ -8,8 +8,8 @@ git-restore - Restore working tree files
 SYNOPSIS
 --------
 [verse]
-'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] <pathspec>...
-'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [<pathspec>...]
+'git restore' [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>...
+'git restore' (-p|--patch) [<options>] [--source=<tree>] [--staged] [--worktree] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -113,6 +113,14 @@ in linkgit:git-checkout[1] for details.
 	appear in the `--source` tree are removed, to make them match
 	`<tree>` exactly. The default is no-overlay mode.
 
+\--::
+	Do not interpret any more arguments as options.
+
+<pathspec>...::
+	Limits the paths affected by the operation.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
+
 EXAMPLES
 --------
 
Signature · 1 line
gitgitgadget
#76 lore raw

Re: [PATCH v4 00/13] Add --pathspec-from-file option

In reply to Alexandr Miloslavskiy via GitGitGadget

"Alexandr Miloslavskiy via GitGitGadget" <[email protected]>

Quoted 1 line · writes:

Changes from V3:

Yikes, perhaps our mails crossed or something? I think the previous round is already in 'next'.

Let's wait and see they cook enough to graduate to 'master', and build a separate series on top to teach other commands the option using the facility introduced by the current series (which is the first 6 patches you sent here).

Thanks.

#77 lore raw

Re: [PATCH v4 00/13] Add --pathspec-from-file option

Quoted 9 lines · Junio C Hamano
Quoted 1 line

Changes from V3:

Yikes, perhaps our mails crossed or something? I think the previous round is already in 'next'.

Let's wait and see they cook enough to graduate to 'master', and build a separate series on top to teach other commands the option using the facility introduced by the current series (which is the first 6 patches you sent here).

My intent is to support more commands, so I was working on other patches in the background. Today more patches were ready and I wasn't sure whether to submit another topic or continue the old one. After some thinking, I decided to continue the old one.

Apparently, I guessed wrong :) Roger: I will wait until the first part graduates to 'master', then submit more patches.

Please give me an advice: when the time comes, shall I prepare even more patches and submit a massive branch, or shall I submit today's remaining patches, then wait again? I imagine that massive branches are scary and will deter reviewers?

#78 lore raw

Re: [PATCH v4 00/13] Add --pathspec-from-file option

Quoted 12 lines · Alexandr Miloslavskiy
Quoted 9 lines · Junio C Hamano
Quoted 1 line

Changes from V3:

Yikes, perhaps our mails crossed or something? I think the previous round is already in 'next'.

Let's wait and see they cook enough to graduate to 'master', and build a separate series on top to teach other commands the option using the facility introduced by the current series (which is the first 6 patches you sent here).

Quoted 4 lines

My intent is to support more commands, so I was working on other patches in the background. Today more patches were ready and I wasn't sure whether to submit another topic or continue the old one. After some thinking, I decided to continue the old one.

Well I've split the new patches into its own topic to queue on the am/pathspec-f-f-checkout branch, that builds directly on top of the am/pathspec-from-file branch, for now. I suspect that they may want to be two topics (i.e. for "add" and for "checkout/restore"), but I'd like to keep them out of 'next' either way for a while until the base topic proves to be solid enough.

Quoted 4 lines

Please give me an advice: when the time comes, shall I prepare even more patches and submit a massive branch, or shall I submit today's remaining patches, then wait again? I imagine that massive branches are scary and will deter reviewers?

Scary will probably not be an issue for the follow-up topics around the pathspec-from-file theme, but a huge topic tends to wear out the author and the reviewers, inviting trivial bugs that would otherwise be found easily go unnoticed.

Thanks.

#79 lore raw

Re: [PATCH v4 00/13] Add --pathspec-from-file option

Quoted 4 lines · Junio C Hamano

Scary will probably not be an issue for the follow-up topics around the pathspec-from-file theme, but a huge topic tends to wear out the author and the reviewers, inviting trivial bugs that would otherwise be found easily go unnoticed.

OK, thanks! I will pause for now and wait until existing patches are in master.

#29 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

In reply to Alexandr Miloslavskiy

Hi Alexandr

Sorry it has taken me so long to reply

Quoted 8 lines · Alexandr Miloslavskiy

I think I have implemented most suggestions in PatchV2. Thanks!

Quoted 2 lines

It might be worth tailoring the message to the command rather than having exactly the same message for commit and reset

I decided to move the general comment to base commit where options are introduced and not repeat it where options are supported.

Quoted 8 lines
Quoted 3 lines

I wonder if there is a way of calling parse_pathspec_file() from parse_and_validate_options() instead. Otherwise we end up validating options here instead which is a bit messy.

The code looks a bit too entangled to support that without making it worse. The biggest concern I have is that parse_and_validate_options() will populate pathspec and some other function will need to remember to clean it up. I like it better when pathspec is handled in one place.

I don't think it's so bad if the pathspec is cleaned up just after it is used, the diff below applies on top of your patch - see what you think. The diff also adds dies if --all is given with --pathspec-from-file which (together with a test) would be worth adding to you series I think.

Quoted 6 lines

I agree that things are not perfect, but this seems to be a consequence of other existing problems. For example, I would have expected a structure instead of a handful of global variables. That would have solved many problems. However, I didn't have the bravery to dive into this refactoring.

Yes it is a pain that the builtin functions tend to use a lot of global variables rather than a structure.

Best Wishes

Phillip

--- >8 ---

builtin/commit.c +39 -31
index ed40729355..bb9515c44b 100644
@@ -330,37 +330,18 @@ static void refresh_cache_or_die(int refresh_flags)
 }
 
 static const char *prepare_index(int argc, const char **argv, const char *prefix,
+				 struct pathspec *pathspec,
 				 const struct commit *current_head, int is_status)
 {
 	struct string_list partial = STRING_LIST_INIT_DUP;
-	struct pathspec pathspec;
+
 	int refresh_flags = REFRESH_QUIET;
 	const char *ret;
 
 	if (is_status)
 		refresh_flags |= REFRESH_UNMERGED;
-	parse_pathspec(&pathspec, 0,
-		       PATHSPEC_PREFER_FULL,
-		       prefix, argv);
 
-	if (pathspec_from_file) {
-		if (interactive)
-			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
-
-		if (pathspec.nr)
-			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
-
-		parse_pathspec_file(&pathspec, 0,
-				    PATHSPEC_PREFER_FULL,
-				    prefix, pathspec_from_file, pathspec_file_nul);
-	} else if (pathspec_file_nul) {
-		die(_("--pathspec-file-nul requires --pathspec-from-file"));
-	}
-
-	if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
-		die(_("No paths with --include/--only does not make sense."));
-
-	if (read_cache_preload(&pathspec) < 0)
+	if (read_cache_preload(pathspec) < 0)
 		die(_("index file corrupt"));
 
 	if (interactive) {
@@ -411,9 +392,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 	 * (A) if all goes well, commit the real index;
 	 * (B) on failure, rollback the real index.
 	 */
-	if (all || (also && pathspec.nr)) {
+	if (all || (also && pathspec->nr)) {
 		hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
-		add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
+		add_files_to_cache(also ? prefix : NULL, pathspec, 0);
 		refresh_cache_or_die(refresh_flags);
 		update_main_cache_tree(WRITE_TREE_SILENT);
 		if (write_locked_index(&the_index, &index_lock, 0))
@@ -432,7 +413,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 	 * and create commit from the_index.
 	 * We still need to refresh the index here.
 	 */
-	if (!only && !pathspec.nr) {
+	if (!only && !pathspec->nr) {
 		hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
 		refresh_cache_or_die(refresh_flags);
 		if (active_cache_changed
@@ -474,7 +455,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 			die(_("cannot do a partial commit during a cherry-pick."));
 	}
 
-	if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec))
+	if (list_paths(&partial, !current_head ? NULL : "HEAD", pathspec))
 		exit(1);
 
 	discard_cache();
@@ -505,7 +486,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 	read_cache_from(ret);
 out:
 	string_list_clear(&partial, 0);
-	clear_pathspec(&pathspec);
+	clear_pathspec(pathspec);
 	return ret;
 }
 
@@ -1148,6 +1129,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
 				      const struct option *options,
 				      const char * const usage[],
 				      const char *prefix,
+				      struct pathspec *pathspec,
 				      struct commit *current_head,
 				      struct wt_status *s)
 {
@@ -1223,19 +1205,42 @@ static int parse_and_validate_options(int argc, const char *argv[],
 		die(_("paths '%s ...' with -a does not make sense"),
 		    argv[0]);
 
+	if (pathspec_from_file) {
+		if (interactive)
+			die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+
+		if (argc)
+			die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+		if (all)
+			die(_("--pathspec-from-file is incompatible with --all"));
+
+		parse_pathspec_file(pathspec, 0,
+				    PATHSPEC_PREFER_FULL,
+				    prefix, pathspec_from_file, pathspec_file_nul);
+	} else if (pathspec_file_nul) {
+		die(_("--pathspec-file-nul requires --pathspec-from-file"));
+	} else {
+		parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL, prefix, argv);
+	}
+
+	if (!pathspec->nr && (also || (only && !amend && !allow_empty)))
+		die(_("No paths with --include/--only does not make sense."));
+
 	if (status_format != STATUS_FORMAT_NONE)
 		dry_run = 1;
 
 	return argc;
 }
 
 static int dry_run_commit(int argc, const char **argv, const char *prefix,
+			  struct pathspec *pathspec,
 			  const struct commit *current_head, struct wt_status *s)
 {
 	int committable;
 	const char *index_file;
 
-	index_file = prepare_index(argc, argv, prefix, current_head, 1);
+	index_file = prepare_index(argc, argv, prefix, pathspec, current_head, 1);
 	committable = run_status(stdout, index_file, prefix, 0, s);
 	rollback_index_files();
 
@@ -1571,6 +1576,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	struct commit *current_head = NULL;
 	struct commit_extra_header *extra = NULL;
 	struct strbuf err = STRBUF_INIT;
+	struct pathspec pathspec;
 
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage_with_options(builtin_commit_usage, builtin_commit_options);
@@ -1590,13 +1596,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	verbose = -1; /* unspecified */
 	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
 					  builtin_commit_usage,
-					  prefix, current_head, &s);
+					  prefix, &pathspec, current_head, &s);
 	if (verbose == -1)
 		verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose;
 
 	if (dry_run)
-		return dry_run_commit(argc, argv, prefix, current_head, &s);
-	index_file = prepare_index(argc, argv, prefix, current_head, 0);
+		return dry_run_commit(argc, argv, prefix, &pathspec,
+				     current_head, &s);
+	index_file = prepare_index(argc, argv, prefix, &pathspec, current_head,
+				   0);
 
 	/* Set up everything for writing the commit object.  This includes
 	   running hooks, writing the trees, and interacting with the user.  */
#30 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

Quoted 4 lines · Phillip Wood

I don't think it's so bad if the pathspec is cleaned up just after it is used, the diff below applies on top of your patch - see what you think. The diff also adds dies if --all is given with --pathspec-from-file which (together with a test) would be worth adding to you series I think.

Unfortunately, your reply came too late, topic was cooking in pu/next for a while and merged into master yesterday: c58ae96f.

I understand that your patch consists of two parts:

1) Adding test for --all
------------------------
I must admit that I overlooked that there was a similar test for args-based pathspec. I will add this part in my next topic for --pathspec-from-file. I expect it to appear in the next day or two. I will try to remember to CC you to it.

2) Moving parsing/validation into parse_and_validate_options()
------------------------
Again, I agree that having parsing/validation outside is suboptimal.
However, with current code, I find it to be a choice between two evils, and my choice was "outside but clear" to "inside but obscure".

What I find obscure in your suggestion/patch is that innocently looking prepare_index() suddenly clears pathspec as well. It's even harder to see when called through dry_run_commit().

Now, let me illustrate. There's a similar case in my TODO list, this time involving a real bug in git. In init_db(), the bug occurs in set_git_dir(real_path(git_dir)), also due to unexpected clearing.

Now that I pointed my finger at it, please try to find what's wrong. I imagine that it won't be easy at all. And it's way harder when there's no reason to dig deep into a very specific line of code.

I really try to avoid such type of pitfalls. That's why my choice between two evils is what I did.

#31 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

Hi Alexandr

Quoted 7 lines · Alexandr Miloslavskiy
Quoted 4 lines · Phillip Wood

I don't think it's so bad if the pathspec is cleaned up just after it is used, the diff below applies on top of your patch - see what you think. The diff also adds dies if --all is given with --pathspec-from-file which (together with a test) would be worth adding to you series I think.

Unfortunately, your reply came too late, topic was cooking in pu/next for a while and merged into master yesterday: c58ae96f.

Ah I thought it might be a bit late to fix up the patch, there could always be a follow patch though.

Quoted 8 lines

I understand that your patch consists of two parts:

1) Adding test for --all
------------------------
I must admit that I overlooked that there was a similar test for args-based pathspec. I will add this part in my next topic for --pathspec-from-file. I expect it to appear in the next day or two. I
will try to remember to CC you to it.

Thanks, one other thing I forgot to mention yesterday is to ask what the expected behavior is if the user passes an empty file to --pathspec-from-file. With no pathspecs on the command line commit, checkout, reset and restore-files all default to operating on all paths but passing --pathspec-from-file implies the user wants to specify specific paths so I think it would perhaps be better to error out if no paths are given. There is a precedent for this in checkout-index which does nothing if no paths are given (though I can't remember if it errors out or not).

Quoted 7 lines

2) Moving parsing/validation into parse_and_validate_options()
------------------------
Again, I agree that having parsing/validation outside is suboptimal.
However, with current code, I find it to be a choice between two evils, and my choice was "outside but clear" to "inside but obscure".

What I find obscure in your suggestion/patch is that innocently looking prepare_index() suddenly clears pathspec as well. It's even harder to see when called through dry_run_commit().

It would be easy enough to clear pathspec in cmd_commit() I just didn't bother to do it.

Quoted 6 lines

Now, let me illustrate. There's a similar case in my TODO list, this time involving a real bug in git. In init_db(), the bug occurs in set_git_dir(real_path(git_dir)), also due to unexpected clearing.

Now that I pointed my finger at it, please try to find what's wrong. I imagine that it won't be easy at all. And it's way harder when there's no reason to dig deep into a very specific line of code.

I really try to avoid such type of pitfalls. That's why my choice between two evils is what I did.

If I had to hazard a guess I'd say that either set_git_dir() calls real_path() which messes up the path passed to it or it does not copy the path passed to it and it is messed up by some other code calling real_path() after set_git_dir() has returned. If my guess is correct (I wouldn't be surprised if it's wrong) I think that's a bit different to the pathspec case as it's about the lifetime of the return value of a function rather than a function freeing an argument passed to it.

Best Wishes

Phillip

#32 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

Quoted 3 lines · Phillip Wood

Thanks, one other thing I forgot to mention yesterday is to ask what the expected behavior is if the user passes an empty file to
--pathspec-from-file. With no pathspecs on the command line commit,
checkout, reset and restore-files all default to operating on all paths but passing --pathspec-from-file implies the user wants to specify specific paths so I think it would perhaps be better to error out if no paths are given. There is a precedent for this in checkout-index which does nothing if no paths are given (though I can't remember if it errors out or not).

Back when I composed patch for git commit, I spent some time thinking about this question. I agree that empty --pathspec-from-file is weird. Eventually I decided that --pathspec-from-file shall be as close as possible to passing pathspec args. So empty file should behave the same way as passing zero pathspec args. It's more or less the question "if user does something unexpected, what is expected?". I decided that treating as zero args doesn't sound dangerous to me, and it's a very weird case anyway, so let's just treat as zero args.

Quoted 3 lines
Quoted 1 line

What I find obscure in your suggestion/patch is that innocently looking prepare_index() suddenly clears pathspec as well. It's even harder to see when called through dry_run_commit().

It would be easy enough to clear pathspec in cmd_commit() I just didn't bother to do it.

Yes, this thought also came to me after I replied. I agree that this is viable and seems to solve most problems.

Now I'm in the situation where before me, code already wasn't perfect, and I can continue to extend it in the same direction, or try to refactor to make it better. I actually choose the second path: [1][2]. These were two "don't" lessons for me, as both topics now gather dust in mail graveyard. No good deed remains unpunished, I guess?

Quoted 1 line

I think that's a bit different to the pathspec case as it's about the lifetime of the return value of a function rather than a function freeing an argument passed to it.

Both cases are about lifetime that ends where it was not expected. You can probably agree that it never makes code easier to understand, and at best it will be properly accounted for and does not explode. Again, both choices were somewhat evil.

[1] https://public-inbox.org/git/[email protected]/
[2] https://public-inbox.org/git/[email protected]/T/#u

#33 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

Quoted 4 lines · Alexandr Miloslavskiy

and I can continue to extend it in the same direction, or try to refactor to make it better. I actually choose the second path:
[1][2]. These were two "don't" lessons for me, as both topics now gather dust in mail graveyard.

If nobody commented on your patches, that is not a punishment. It could have been that reviewers were busy addressing other issues back then, in which case a gentle ping by resending a polished version (it could be that the changes were not presented well to attract reviewers' attention---polishing the proposed log messages without changing the patch text might be all it takes to make them realize that the topic is worth looking at) would help.

Quoted 4 lines

[1]
https://public-inbox.org/git/[email protected]/
[2]
https://public-inbox.org/git/[email protected]/T/#u

I actually was meaning to read and comment on [1], but lack of time pushed it down in my queue. Sorry about that.

#34 lore raw

Re: [PATCH 5/5] commit: support the --pathspec-from-file option

Quoted 7 lines · Junio C Hamano

If nobody commented on your patches, that is not a punishment. It could have been that reviewers were busy addressing other issues back then, in which case a gentle ping by resending a polished version (it could be that the changes were not presented well to attract reviewers' attention---polishing the proposed log messages without changing the patch text might be all it takes to make them realize that the topic is worth looking at) would help.

Thanks for letting me know! This is actually a relief.

I will then merge both topics with my next '--pathspec-from-file' batch, because they are all parts of one work. I just thought that since they can be submitted separately I should do that, maybe that was a mistake.