Skip to content

Commit a529f7d

Browse files
authored
Merge pull request #2479 from efiop/always_reproduce
run: introduce --always-changed
2 parents 9638b45 + 357ce82 commit a529f7d

File tree

4 files changed

+31
-1
lines changed

4 files changed

+31
-1
lines changed

dvc/command/run.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def run(self):
5151
no_commit=self.args.no_commit,
5252
outs_persist=self.args.outs_persist,
5353
outs_persist_no_cache=self.args.outs_persist_no_cache,
54+
always_changed=self.args.always_changed,
5455
)
5556
except DvcException:
5657
logger.exception("failed to run command")
@@ -189,6 +190,12 @@ def add_parser(subparsers, parent_parser):
189190
help="Declare output file or directory that will not be "
190191
"removed upon repro (do not put into DVC cache).",
191192
)
193+
run_parser.add_argument(
194+
"--always-changed",
195+
action="store_true",
196+
default=False,
197+
help="Always consider this DVC-file as changed.",
198+
)
192199
run_parser.add_argument(
193200
"command", nargs=argparse.REMAINDER, help="Command to execute."
194201
)

dvc/stage.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ class Stage(object):
139139
PARAM_OUTS = "outs"
140140
PARAM_LOCKED = "locked"
141141
PARAM_META = "meta"
142+
PARAM_ALWAYS_CHANGED = "always_changed"
142143

143144
SCHEMA = {
144145
Optional(PARAM_MD5): Or(str, None),
@@ -148,6 +149,7 @@ class Stage(object):
148149
Optional(PARAM_OUTS): Or(And(list, Schema([output.SCHEMA])), None),
149150
Optional(PARAM_LOCKED): bool,
150151
Optional(PARAM_META): object,
152+
Optional(PARAM_ALWAYS_CHANGED): bool,
151153
}
152154

153155
TAG_REGEX = r"^(?P<path>.*)@(?P<tag>[^\\/@:]*)$"
@@ -164,6 +166,7 @@ def __init__(
164166
locked=False,
165167
tag=None,
166168
state=None,
169+
always_changed=False,
167170
):
168171
if deps is None:
169172
deps = []
@@ -179,6 +182,7 @@ def __init__(
179182
self.md5 = md5
180183
self.locked = locked
181184
self.tag = tag
185+
self.always_changed = always_changed
182186
self._state = state or {}
183187

184188
def __repr__(self):
@@ -244,6 +248,9 @@ def _changed_deps(self):
244248
)
245249
return True
246250

251+
if self.always_changed:
252+
return True
253+
247254
for dep in self.deps:
248255
status = dep.status()
249256
if status:
@@ -457,6 +464,7 @@ def create(repo, **kwargs):
457464
wdir=wdir,
458465
cmd=kwargs.get("cmd", None),
459466
locked=kwargs.get("locked", False),
467+
always_changed=kwargs.get("always_changed", False),
460468
)
461469

462470
Stage._fill_stage_outputs(stage, **kwargs)
@@ -515,6 +523,7 @@ def create(repo, **kwargs):
515523
not ignore_build_cache
516524
and stage.is_cached
517525
and not stage.is_callback
526+
and not stage.always_changed
518527
):
519528
logger.info("Stage is cached, skipping.")
520529
return None
@@ -619,6 +628,7 @@ def load(repo, fname):
619628
md5=d.get(Stage.PARAM_MD5),
620629
locked=d.get(Stage.PARAM_LOCKED, False),
621630
tag=tag,
631+
always_changed=d.get(Stage.PARAM_ALWAYS_CHANGED, False),
622632
state=state,
623633
)
624634

@@ -643,6 +653,7 @@ def dumpd(self):
643653
Stage.PARAM_DEPS: [d.dumpd() for d in self.deps],
644654
Stage.PARAM_OUTS: [o.dumpd() for o in self.outs],
645655
Stage.PARAM_META: self._state.get("meta"),
656+
Stage.PARAM_ALWAYS_CHANGED: self.always_changed,
646657
}.items()
647658
if value
648659
}
@@ -843,6 +854,7 @@ def run(self, dry=False, no_commit=False, force=False):
843854
if (
844855
not force
845856
and not self.is_callback
857+
and not self.always_changed
846858
and self._already_cached()
847859
):
848860
self.checkout()
@@ -889,7 +901,7 @@ def status(self):
889901
if self.changed_md5():
890902
ret.append("changed checksum")
891903

892-
if self.is_callback:
904+
if self.is_callback or self.always_changed:
893905
ret.append("always changed")
894906

895907
if ret:

tests/unit/command/test_run.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def test_run(mocker, dvc_repo):
3232
"outs-persist",
3333
"--outs-persist-no-cache",
3434
"outs-persist-no-cache",
35+
"--always-changed",
3536
"command",
3637
]
3738
)
@@ -58,6 +59,7 @@ def test_run(mocker, dvc_repo):
5859
ignore_build_cache=True,
5960
remove_outs=True,
6061
no_commit=True,
62+
always_changed=True,
6163
cmd="command",
6264
)
6365

@@ -83,6 +85,7 @@ def test_run_args_from_cli(mocker, dvc_repo):
8385
ignore_build_cache=False,
8486
remove_outs=False,
8587
no_commit=False,
88+
always_changed=False,
8689
cmd="echo foo",
8790
)
8891

@@ -108,5 +111,6 @@ def test_run_args_with_spaces(mocker, dvc_repo):
108111
ignore_build_cache=False,
109112
remove_outs=False,
110113
no_commit=False,
114+
always_changed=False,
111115
cmd='echo "foo bar"',
112116
)

tests/unit/test_stage.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,10 @@ def test_stage_run_ignore_sigint(mocker):
107107
assert communicate.called_once_with()
108108
signal_mock.assert_any_call(signal.SIGINT, signal.SIG_IGN)
109109
assert signal.getsignal(signal.SIGINT) == signal.default_int_handler
110+
111+
112+
def test_always_changed():
113+
stage = Stage(None, "path", always_changed=True)
114+
stage.save()
115+
assert stage.changed()
116+
assert stage.status()["path"] == ["always changed"]

0 commit comments

Comments
 (0)