Skip to content

Commit

Permalink
reset: support the --stdin option
Browse files Browse the repository at this point in the history
Just like with other Git commands, this option makes it read the paths
from the standard input. It comes in handy when resetting many, many
paths at once and wildcards are not an option (e.g. when the paths are
generated by a tool).

Note: we first parse the entire list and perform the actual reset action
only in a second phase. This make the implementation simpler than adding
the pathspecs immediately after the corresponding lines are read.

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

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
  • Loading branch information
dscho committed Sep 4, 2019
1 parent 8104ec9 commit 017945a
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 1 deletion.
11 changes: 11 additions & 0 deletions Documentation/git-reset.txt
Expand Up @@ -10,6 +10,7 @@ SYNOPSIS
[verse]
'git reset' [-q] [<tree-ish>] [--] <paths>...
'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]
'git reset' [-q] [--stdin [-z]] [<tree-ish>]
'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

DESCRIPTION
Expand Down Expand Up @@ -100,6 +101,16 @@ OPTIONS
`reset.quiet` config option. `--quiet` and `--no-quiet` will
override the default behavior.

--stdin::
Instead of taking the list of paths from the command line,
read the list of paths from the standard input. The paths are
read verbatim, i.e. without special pathspecs handling. Paths
are expected to be separated by LF or CR/LF, unless the `-z`
option is in effect.

-z::
Only meaningful with `--stdin`; paths are separated with
NUL character instead of LF.

EXAMPLES
--------
Expand Down
45 changes: 44 additions & 1 deletion builtin/reset.c
Expand Up @@ -25,12 +25,16 @@
#include "cache-tree.h"
#include "submodule.h"
#include "submodule-config.h"
#include "strbuf.h"
#include "quote.h"
#include "argv-array.h"

#define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000)

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] [--stdin [-z]] [<tree-ish>]"),
N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
NULL
};
Expand Down Expand Up @@ -284,7 +288,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;
int patch_mode = 0, nul_term_line = 0, read_from_stdin = 0, unborn;
struct argv_array stdin_paths = ARGV_ARRAY_INIT;
const char *rev;
struct object_id oid;
struct pathspec pathspec;
Expand All @@ -306,6 +311,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_BOOL('z', NULL, &nul_term_line,
N_("paths are separated with NUL character")),
OPT_BOOL(0, "stdin", &read_from_stdin,
N_("read paths from <stdin>")),
OPT_END()
};

Expand All @@ -316,6 +325,38 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_DASHDASH);
parse_args(&pathspec, argv, prefix, patch_mode, &rev);

if (read_from_stdin) {
strbuf_getline_fn getline_fn = nul_term_line ?
strbuf_getline_nul : strbuf_getline;
int flags = PATHSPEC_PREFER_FULL;
struct strbuf buf = STRBUF_INIT;
struct strbuf unquoted = STRBUF_INIT;

if (patch_mode)
die(_("--stdin is incompatible with --patch"));

if (pathspec.nr)
die(_("--stdin is incompatible with path arguments"));

while (getline_fn(&buf, stdin) != 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(&stdin_paths, buf.buf);
strbuf_reset(&buf);
}
strbuf_release(&unquoted);
strbuf_release(&buf);

flags |= PATHSPEC_LITERAL_PATH;
parse_pathspec(&pathspec, 0, flags, prefix, stdin_paths.argv);

} else if (nul_term_line)
die(_("-z requires --stdin"));

unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
Expand Down Expand Up @@ -416,5 +457,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (!pathspec.nr)
remove_branch_state(the_repository);

argv_array_clear(&stdin_paths);

return update_ref_status;
}
41 changes: 41 additions & 0 deletions t/t7107-reset-stdin.sh
@@ -0,0 +1,41 @@
#!/bin/sh

test_description='reset --stdin'

. ./test-lib.sh

test_expect_success 'reset --stdin' '
test_commit hello &&
git rm hello.t &&
test -z "$(git ls-files hello.t)" &&
echo hello.t | git reset --stdin &&
test hello.t = "$(git ls-files hello.t)"
'

test_expect_success 'reset --stdin -z' '
test_commit world &&
git rm hello.t world.t &&
test -z "$(git ls-files hello.t world.t)" &&
printf world.tQworld.tQhello.tQ | q_to_nul | git reset --stdin -z &&
printf "hello.t\nworld.t\n" >expect &&
git ls-files >actual &&
test_cmp expect actual
'

test_expect_success '--stdin requires --mixed' '
echo hello.t >list &&
test_must_fail git reset --soft --stdin <list &&
test_must_fail git reset --hard --stdin <list &&
git reset --mixed --stdin <list
'


test_expect_success '--stdin trims carriage returns' '
test_commit endline &&
git rm endline.t &&
printf "endline.t\r\n" >list &&
git reset --stdin <list &&
test endline.t = "$(git ls-files endline.t)"
'

test_done

0 comments on commit 017945a

Please sign in to comment.