Skip to content

Commit e2a5741

Browse files
karajan1001efiop
andauthored
Two new modes --all and --stdin for check-ignore (#4323)
* Two new mode `-all` and `--stdin` for check-ignore Fix #4321 Introduce new arguments --all for dvc check-ignore Add tests for dvc check-ignore --all * `--stdin` and targets only one 1. `--stdin` and targets can and only can have one 2. more tests * New tests for stdin mode and solve a problem 1. Add a new tests with mock stdin for interactive mode 2. Solve the problem of `assert "" in caplog.text` in tests always `True`. * Remove path_input interface 1. remove path_input and make _ask to a public method instead * Update dvc/command/check_ignore.py * Update dvc/command/check_ignore.py * Update dvc/command/check_ignore.py * Update dvc/command/check_ignore.py * Update dvc/command/check_ignore.py * Update dvc/command/check_ignore.py * Update dvc/command/check_ignore.py * Update dvc/command/check_ignore.py * fix styling Co-authored-by: karajan1001 <[email protected]> Co-authored-by: Ruslan Kuprieiev <[email protected]> Co-authored-by: Ruslan Kuprieiev <[email protected]>
1 parent ab23bcd commit e2a5741

File tree

3 files changed

+120
-34
lines changed

3 files changed

+120
-34
lines changed

dvc/command/check_ignore.py

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from dvc.command import completion
55
from dvc.command.base import CmdBase, append_doc_link
66
from dvc.exceptions import DvcException
7+
from dvc.prompt import ask
78

89
logger = logging.getLogger(__name__)
910

@@ -14,27 +15,71 @@ def __init__(self, args):
1415
self.ignore_filter = self.repo.tree.dvcignore
1516

1617
def _show_results(self, result):
17-
if result.match or self.args.non_matching:
18-
if self.args.details:
19-
logger.info("{}\t{}".format(result.patterns[-1], result.file))
20-
else:
21-
logger.info(result.file)
18+
if not result.match and not self.args.non_matching:
19+
return
2220

23-
def run(self):
24-
if self.args.non_matching and not self.args.details:
25-
raise DvcException("--non-matching is only valid with --details")
21+
if self.args.details:
22+
patterns = result.patterns
23+
if not self.args.all:
24+
patterns = patterns[-1:]
2625

27-
if self.args.quiet and self.args.details:
28-
raise DvcException("cannot both --details and --quiet")
26+
for pattern in patterns:
27+
logger.info("{}\t{}".format(pattern, result.file))
28+
else:
29+
logger.info(result.file)
30+
31+
def _check_one_file(self, target):
32+
result = self.ignore_filter.check_ignore(target)
33+
self._show_results(result)
34+
if result.match:
35+
return 0
36+
return 1
2937

38+
def _interactive_mode(self):
39+
ret = 1
40+
while True:
41+
target = ask("")
42+
if target == "":
43+
logger.info(
44+
"Empty string is not a valid pathspec. Please use . "
45+
"instead if you meant to match all paths."
46+
)
47+
break
48+
if not self._check_one_file(target):
49+
ret = 0
50+
return ret
51+
52+
def _normal_mode(self):
3053
ret = 1
3154
for target in self.args.targets:
32-
result = self.ignore_filter.check_ignore(target)
33-
self._show_results(result)
34-
if result.match:
55+
if not self._check_one_file(target):
3556
ret = 0
3657
return ret
3758

59+
def _check_args(self):
60+
if not self.args.stdin and not self.args.targets:
61+
raise DvcException("`targets` or `--stdin` needed")
62+
63+
if self.args.stdin and self.args.targets:
64+
raise DvcException("cannot have both `targets` and `--stdin`")
65+
66+
if self.args.non_matching and not self.args.details:
67+
raise DvcException(
68+
"`--non-matching` is only valid with `--details`"
69+
)
70+
71+
if self.args.all and not self.args.details:
72+
raise DvcException("`--all` is only valid with `--details`")
73+
74+
if self.args.quiet and self.args.details:
75+
raise DvcException("cannot use both `--details` and `--quiet`")
76+
77+
def run(self):
78+
self._check_args()
79+
if self.args.stdin:
80+
return self._interactive_mode()
81+
return self._normal_mode()
82+
3883

3984
def add_parser(subparsers, parent_parser):
4085
ADD_HELP = "Debug DVC ignore/exclude files"
@@ -61,9 +106,24 @@ def add_parser(subparsers, parent_parser):
61106
help="Show the target paths which don’t match any pattern. "
62107
"Only usable when `--details` is also employed",
63108
)
109+
parser.add_argument(
110+
"--stdin",
111+
action="store_true",
112+
default=False,
113+
help="Read pathnames from the standard input, one per line, "
114+
"instead of from the command-line.",
115+
)
116+
parser.add_argument(
117+
"-a",
118+
"--all",
119+
action="store_true",
120+
default=False,
121+
help="Show all of the patterns match the target paths. "
122+
"Only usable when `--details` is also employed",
123+
)
64124
parser.add_argument(
65125
"targets",
66-
nargs="+",
126+
nargs="*",
67127
help="Exact or wildcard paths of files or directories to check "
68128
"ignore patterns.",
69129
).complete = completion.FILE

dvc/prompt.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
logger = logging.getLogger(__name__)
88

99

10-
def _ask(prompt, limited_to=None):
10+
def ask(prompt, limited_to=None):
1111
if not sys.stdout.isatty():
1212
return None
1313

@@ -39,7 +39,7 @@ def confirm(statement):
3939
bool: whether or not specified statement was confirmed.
4040
"""
4141
prompt = f"{statement} [y/n]"
42-
answer = _ask(prompt, limited_to=["yes", "no", "y", "n"])
42+
answer = ask(prompt, limited_to=["yes", "no", "y", "n"])
4343
return answer and answer.startswith("y")
4444

4545

tests/func/test_check_ignore.py

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88

99
@pytest.mark.parametrize(
10-
"file,ret,output", [("ignored", 0, "ignored\n"), ("not_ignored", 1, "")]
10+
"file,ret,output", [("ignored", 0, True), ("not_ignored", 1, False)]
1111
)
1212
def test_check_ignore(tmp_dir, dvc, file, ret, output, caplog):
1313
tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "ignored")
1414

1515
assert main(["check-ignore", file]) == ret
16-
assert output in caplog.text
16+
assert (file in caplog.text) is output
1717

1818

1919
@pytest.mark.parametrize(
@@ -39,26 +39,29 @@ def test_check_ignore_details(tmp_dir, dvc, file, ret, output, caplog):
3939
assert output in caplog.text
4040

4141

42-
@pytest.mark.parametrize(
43-
"non_matching,output", [(["-n"], "::\tfile\n"), ([], "")]
44-
)
45-
def test_check_ignore_non_matching(tmp_dir, dvc, non_matching, output, caplog):
46-
tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "other")
47-
48-
assert main(["check-ignore", "-d"] + non_matching + ["file"]) == 1
49-
assert output in caplog.text
50-
51-
52-
def test_check_ignore_non_matching_without_details(tmp_dir, dvc):
42+
@pytest.mark.parametrize("non_matching", [True, False])
43+
def test_check_ignore_non_matching(tmp_dir, dvc, non_matching, caplog):
5344
tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "other")
45+
if non_matching:
46+
assert main(["check-ignore", "-d", "-n", "file"]) == 1
47+
else:
48+
assert main(["check-ignore", "-d", "file"]) == 1
5449

55-
assert main(["check-ignore", "-n", "file"]) == 255
56-
50+
assert ("::\tfile\n" in caplog.text) is non_matching
5751

58-
def test_check_ignore_details_with_quiet(tmp_dir, dvc):
59-
tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "other")
6052

61-
assert main(["check-ignore", "-d", "-q", "file"]) == 255
53+
@pytest.mark.parametrize(
54+
"args",
55+
[
56+
["-n", "file"],
57+
["-a", "file"],
58+
["-q", "-d", "file"],
59+
["--stdin", "file"],
60+
[],
61+
],
62+
)
63+
def test_check_ignore_error_args_cases(tmp_dir, dvc, args):
64+
assert main(["check-ignore"] + args) == 255
6265

6366

6467
@pytest.mark.parametrize("path,ret", [({"dir": {}}, 0), ({"dir": "files"}, 1)])
@@ -107,3 +110,26 @@ def test_check_sub_dir_ignore_file(tmp_dir, dvc, caplog):
107110
with sub_dir.chdir():
108111
assert main(["check-ignore", "-d", "foo"]) == 0
109112
assert ".dvcignore:2:foo\tfoo" in caplog.text
113+
114+
115+
def test_check_ignore_details_all(tmp_dir, dvc, caplog):
116+
tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "f*\n!foo")
117+
118+
assert main(["check-ignore", "-d", "-a", "foo"]) == 0
119+
assert "{}:1:f*\tfoo\n".format(DvcIgnore.DVCIGNORE_FILE) in caplog.text
120+
assert "{}:2:!foo\tfoo\n".format(DvcIgnore.DVCIGNORE_FILE) in caplog.text
121+
122+
123+
@pytest.mark.parametrize(
124+
"file,ret,output", [("ignored", 0, True), ("not_ignored", 1, False)]
125+
)
126+
def test_check_ignore_stdin_mode(
127+
tmp_dir, dvc, file, ret, output, caplog, mocker
128+
):
129+
tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "ignored")
130+
mocker.patch("builtins.input", side_effect=[file, ""])
131+
stdout_mock = mocker.patch("sys.stdout")
132+
stdout_mock.isatty.return_value = True
133+
134+
assert main(["check-ignore", "--stdin"]) == ret
135+
assert (file in caplog.text) is output

0 commit comments

Comments
 (0)