mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
diff: reimplement interface and tests from scratch
This commit is contained in:
+12
-128
@@ -1,12 +1,7 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import humanize
|
||||
import inflect
|
||||
from funcy import compact
|
||||
|
||||
from dvc.command.base import append_doc_link
|
||||
from dvc.command.base import CmdBase
|
||||
from dvc.command.base import CmdBase, append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
|
||||
@@ -14,122 +9,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdDiff(CmdBase):
|
||||
@staticmethod
|
||||
def _print_size(size):
|
||||
if size < 0:
|
||||
change = "decreased by {}"
|
||||
elif size > 0:
|
||||
change = "increased by {}"
|
||||
else:
|
||||
change = "not changed"
|
||||
natur_size = humanize.naturalsize(abs(size))
|
||||
return change.format(natur_size)
|
||||
|
||||
@staticmethod
|
||||
def _get_md5_string(sign, file_name, checksum):
|
||||
sample_msg = ""
|
||||
if file_name:
|
||||
sample_msg = "{}{} with md5 {}\n"
|
||||
sample_msg = sample_msg.format(sign, file_name, checksum)
|
||||
return sample_msg
|
||||
|
||||
@classmethod
|
||||
def _get_dir_changes(cls, dct):
|
||||
import dvc.repo.diff as diff
|
||||
|
||||
engine = inflect.engine()
|
||||
changes_msg = (
|
||||
"{} {} untouched, {} {} modified, {} {} added, "
|
||||
"{} {} deleted, size was {}"
|
||||
)
|
||||
changes_msg = changes_msg.format(
|
||||
dct[diff.DIFF_IDENT],
|
||||
engine.plural("file", dct[diff.DIFF_IDENT]),
|
||||
dct[diff.DIFF_CHANGE],
|
||||
engine.plural("file", dct[diff.DIFF_CHANGE]),
|
||||
dct[diff.DIFF_NEW],
|
||||
engine.plural("file", dct[diff.DIFF_NEW]),
|
||||
dct[diff.DIFF_DEL],
|
||||
engine.plural("file", dct[diff.DIFF_DEL]),
|
||||
cls._print_size(dct[diff.DIFF_SIZE]),
|
||||
)
|
||||
return changes_msg
|
||||
|
||||
@classmethod
|
||||
def _get_file_changes(cls, dct):
|
||||
import dvc.repo.diff as diff
|
||||
|
||||
if (
|
||||
dct.get(diff.DIFF_OLD_FILE)
|
||||
and dct.get(diff.DIFF_NEW_FILE)
|
||||
and dct[diff.DIFF_SIZE] == 0
|
||||
):
|
||||
msg = "file size was not changed"
|
||||
elif dct.get(diff.DIFF_NEW_FILE):
|
||||
msg = "added file with size {}".format(
|
||||
humanize.naturalsize(dct[diff.DIFF_SIZE])
|
||||
)
|
||||
elif dct.get(diff.DIFF_OLD_FILE):
|
||||
msg = "deleted file with size {}".format(
|
||||
humanize.naturalsize(abs(dct[diff.DIFF_SIZE]))
|
||||
)
|
||||
else:
|
||||
msg = "file was modified, file size {}".format(
|
||||
cls._print_size(dct[diff.DIFF_SIZE])
|
||||
)
|
||||
return msg
|
||||
|
||||
@classmethod
|
||||
def _get_royal_changes(cls, dct):
|
||||
import dvc.repo.diff as diff
|
||||
|
||||
if dct[diff.DIFF_SIZE] != diff.DIFF_SIZE_UNKNOWN:
|
||||
if dct.get("is_dir"):
|
||||
return cls._get_dir_changes(dct)
|
||||
else:
|
||||
return cls._get_file_changes(dct)
|
||||
return "size is ?"
|
||||
|
||||
@classmethod
|
||||
def _show(cls, diff_dct):
|
||||
import dvc.repo.diff as diff
|
||||
|
||||
msg = "dvc diff from {} to {}".format(
|
||||
diff_dct[diff.DIFF_A_REF], diff_dct[diff.DIFF_B_REF]
|
||||
)
|
||||
if diff_dct.get(diff.DIFF_EQUAL):
|
||||
logger.info(msg)
|
||||
return
|
||||
for dct in diff_dct[diff.DIFF_LIST]:
|
||||
msg += "\n\ndiff for '{}'\n".format(dct[diff.DIFF_TARGET])
|
||||
msg += cls._get_md5_string(
|
||||
"-",
|
||||
dct.get(diff.DIFF_OLD_FILE),
|
||||
dct.get(diff.DIFF_OLD_CHECKSUM),
|
||||
)
|
||||
msg += cls._get_md5_string(
|
||||
"+",
|
||||
dct.get(diff.DIFF_NEW_FILE),
|
||||
dct.get(diff.DIFF_NEW_CHECKSUM),
|
||||
)
|
||||
msg += "\n"
|
||||
msg += cls._get_royal_changes(dct)
|
||||
logger.info(msg)
|
||||
return msg
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
msg = self.repo.diff(
|
||||
self.args.a_ref, target=self.args.target, b_ref=self.args.b_ref
|
||||
)
|
||||
self._show(msg)
|
||||
self.repo.diff(self.args.a_ref, self.args.b_ref, self.args.target)
|
||||
except DvcException:
|
||||
msg = "failed to get 'diff {}'"
|
||||
args = " ".join(
|
||||
compact([self.args.target, self.args.a_ref, self.args.b_ref])
|
||||
)
|
||||
msg = msg.format(args)
|
||||
logger.exception(msg)
|
||||
logger.exception("failed to get 'diff {}'")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@@ -147,15 +31,6 @@ def add_parser(subparsers, parent_parser):
|
||||
help=DIFF_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
"-t",
|
||||
"--target",
|
||||
help=(
|
||||
"Source path to a data file or directory. Default None. "
|
||||
"If not specified, compares all files and directories "
|
||||
"that are under DVC control in the current working space."
|
||||
),
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
"a_ref", help="Git reference from which diff calculates"
|
||||
)
|
||||
@@ -167,4 +42,13 @@ def add_parser(subparsers, parent_parser):
|
||||
),
|
||||
nargs="?",
|
||||
)
|
||||
diff_parser.add_argument(
|
||||
"-t",
|
||||
"--target",
|
||||
help=(
|
||||
"Source path to a data file or directory. Default None. "
|
||||
"If not specified, compares all files and directories "
|
||||
"that are under DVC control in the current working space."
|
||||
),
|
||||
)
|
||||
diff_parser.set_defaults(func=CmdDiff)
|
||||
|
||||
@@ -41,6 +41,8 @@ def brancher( # noqa: E302
|
||||
if all_tags:
|
||||
revs.extend(scm.list_tags())
|
||||
|
||||
revs = filter(None, revs)
|
||||
|
||||
try:
|
||||
if revs:
|
||||
for sha, names in group_by(scm.resolve_rev, revs).items():
|
||||
|
||||
+43
-233
@@ -1,246 +1,56 @@
|
||||
import os
|
||||
from errno import ENOENT
|
||||
import collections
|
||||
|
||||
from dvc import logger
|
||||
from dvc.scm.base import FileNotInCommitError
|
||||
from dvc.scm.git import DIFF_A_REF, DIFF_B_REF, DIFF_A_TREE, DIFF_B_TREE
|
||||
from dvc.scm.git import DIFF_EQUAL
|
||||
from . import locked
|
||||
from ..compat import fspath
|
||||
|
||||
|
||||
DIFF_TARGET = "target"
|
||||
DIFF_IS_DIR = "is_dir"
|
||||
DIFF_OLD_FILE = "old_file"
|
||||
DIFF_OLD_CHECKSUM = "old_checksum"
|
||||
DIFF_NEW_FILE = "new_file"
|
||||
DIFF_NEW_CHECKSUM = "new_checksum"
|
||||
DIFF_SIZE = "size_diff"
|
||||
DIFF_DEL = "del"
|
||||
DIFF_IDENT = "ident"
|
||||
DIFF_CHANGE = "changes"
|
||||
DIFF_NEW = "new"
|
||||
DIFF_MOVE = "moves"
|
||||
DIFF_LIST = "diffs"
|
||||
DIFF_SIZE_UNKNOWN = "?"
|
||||
DIFF_A_OUTPUT = "a_output"
|
||||
DIFF_B_OUTPUT = "b_output"
|
||||
DIFF_DELETED = "deleted_file"
|
||||
DIFF_IS_NEW = "created_file"
|
||||
Diffable = collections.namedtuple("Diffable", "filename, checksum, size")
|
||||
Diffable.__doc__ = "Common interface for comparable entries."
|
||||
|
||||
|
||||
def _file_not_exists(error, result):
|
||||
if error.errno == ENOENT:
|
||||
result.update({DIFF_SIZE: DIFF_SIZE_UNKNOWN})
|
||||
else:
|
||||
raise error
|
||||
def diffable_from_output(output):
|
||||
try:
|
||||
size = os.path.getsize(fspath(output.path_info))
|
||||
except OSError:
|
||||
size = None
|
||||
|
||||
|
||||
def _extract_dir(self, dir_not_exists, output):
|
||||
"""Extract the content of dvc tree file
|
||||
Args:
|
||||
self(object) - Repo class instance
|
||||
dir_not_exists(bool) - flag for directory existence
|
||||
output(object) - OutputLOCAL class instance
|
||||
Returns:
|
||||
dict - dictionary with keys - paths to file in .dvc/cache
|
||||
values -checksums for that files
|
||||
"""
|
||||
if not dir_not_exists:
|
||||
lst = output.dir_cache
|
||||
return {i["relpath"]: i["md5"] for i in lst}
|
||||
return {}
|
||||
|
||||
|
||||
def _get_dir_info(dir_not_exists, a_output):
|
||||
if not dir_not_exists:
|
||||
return str(a_output), a_output.checksum
|
||||
return "", ""
|
||||
|
||||
|
||||
def _ident_files(a_entries, b_entries):
|
||||
keys = [
|
||||
key for key in a_entries.keys() if b_entries.get(key) == a_entries[key]
|
||||
]
|
||||
return len(keys)
|
||||
|
||||
|
||||
def _modified_files(self, a_entries, b_entries):
|
||||
keys = [key for key in a_entries.keys() if key in b_entries]
|
||||
diff_size = 0
|
||||
modified_count = 0
|
||||
for key in keys:
|
||||
if a_entries[key] != b_entries[key]:
|
||||
modified_count += 1
|
||||
diff_size += os.path.getsize(
|
||||
self.cache.local.get(b_entries[key])
|
||||
) - os.path.getsize(self.cache.local.get(a_entries[key]))
|
||||
return modified_count, diff_size
|
||||
|
||||
|
||||
def _deleted_files(self, a_entries, b_entries):
|
||||
diff_size = 0
|
||||
deleted_count = 0
|
||||
for key, value in a_entries.items():
|
||||
if key not in b_entries:
|
||||
deleted_count += 1
|
||||
diff_size -= os.path.getsize(self.cache.local.get(a_entries[key]))
|
||||
return deleted_count, diff_size
|
||||
|
||||
|
||||
def _new_files(self, a_entries, b_entries):
|
||||
diff_size = 0
|
||||
new_count = 0
|
||||
for key, value in b_entries.items():
|
||||
if key not in a_entries:
|
||||
new_count += 1
|
||||
diff_size += os.path.getsize(self.cache.local.get(b_entries[key]))
|
||||
return new_count, diff_size
|
||||
|
||||
|
||||
def _get_tree_changes(self, a_entries, b_entries):
|
||||
result = {
|
||||
DIFF_DEL: 0,
|
||||
DIFF_IDENT: 0,
|
||||
DIFF_CHANGE: 0,
|
||||
DIFF_NEW: 0,
|
||||
DIFF_MOVE: 0,
|
||||
}
|
||||
result[DIFF_IDENT] = _ident_files(a_entries, b_entries)
|
||||
result[DIFF_CHANGE], diff_size = _modified_files(
|
||||
self, a_entries, b_entries
|
||||
return Diffable(
|
||||
filename=str(output.path_info), checksum=output.checksum, size=size
|
||||
)
|
||||
result[DIFF_SIZE] = diff_size
|
||||
result[DIFF_DEL], diff_size = _deleted_files(self, a_entries, b_entries)
|
||||
result[DIFF_SIZE] += diff_size
|
||||
result[DIFF_NEW], diff_size = _new_files(self, a_entries, b_entries)
|
||||
result[DIFF_SIZE] += diff_size
|
||||
return result
|
||||
|
||||
|
||||
def _check_local_cache(a_out, is_checked):
|
||||
if a_out is not None and a_out.scheme != "local":
|
||||
is_checked.append(str(a_out))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_dir(path, a_outs, b_outs):
|
||||
if a_outs.get(path):
|
||||
return a_outs[path].is_dir_checksum
|
||||
else:
|
||||
return b_outs[path].is_dir_checksum
|
||||
|
||||
|
||||
def _get_diff_outs(self, diff_dct):
|
||||
self.tree = diff_dct[DIFF_A_TREE]
|
||||
a_outs = {str(out): out for st in self.stages for out in st.outs}
|
||||
self.tree = diff_dct[DIFF_B_TREE]
|
||||
b_outs = {str(out): out for st in self.stages for out in st.outs}
|
||||
outs_paths = set(a_outs.keys())
|
||||
outs_paths.update(b_outs.keys())
|
||||
results = {}
|
||||
non_local_cache = []
|
||||
for path in outs_paths:
|
||||
check1 = _check_local_cache(a_outs.get(path), non_local_cache)
|
||||
check2 = _check_local_cache(b_outs.get(path), non_local_cache)
|
||||
# skip files/directories with non-local cache for now
|
||||
if check1 or check2:
|
||||
continue
|
||||
results[path] = {}
|
||||
results[path][DIFF_A_OUTPUT] = a_outs.get(path)
|
||||
results[path][DIFF_B_OUTPUT] = b_outs.get(path)
|
||||
results[path][DIFF_IS_NEW] = path not in a_outs
|
||||
results[path][DIFF_DELETED] = path not in b_outs
|
||||
results[path][DIFF_IS_DIR] = _is_dir(path, a_outs, b_outs)
|
||||
if non_local_cache:
|
||||
logger.warning(
|
||||
"Diff is not supported for non-local outputs. Ignoring: {}".format(
|
||||
non_local_cache
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _diff_dir(self, target, diff_dct):
|
||||
result = {DIFF_TARGET: target}
|
||||
result[DIFF_IS_DIR] = True
|
||||
a_entries, b_entries = {}, {}
|
||||
try:
|
||||
a_entries = _extract_dir(
|
||||
self, diff_dct[DIFF_IS_NEW], diff_dct[DIFF_A_OUTPUT]
|
||||
)
|
||||
b_entries = _extract_dir(
|
||||
self, diff_dct[DIFF_DELETED], diff_dct[DIFF_B_OUTPUT]
|
||||
)
|
||||
result[DIFF_OLD_FILE], result[DIFF_OLD_CHECKSUM] = _get_dir_info(
|
||||
diff_dct[DIFF_IS_NEW], diff_dct[DIFF_A_OUTPUT]
|
||||
)
|
||||
result[DIFF_NEW_FILE], result[DIFF_NEW_CHECKSUM] = _get_dir_info(
|
||||
diff_dct[DIFF_DELETED], diff_dct[DIFF_B_OUTPUT]
|
||||
)
|
||||
result.update(_get_tree_changes(self, a_entries, b_entries))
|
||||
except IOError as e:
|
||||
_file_not_exists(e, result)
|
||||
return result
|
||||
|
||||
|
||||
def _diff_file(self, target, diff_dct):
|
||||
result = {DIFF_TARGET: target}
|
||||
size = 0
|
||||
try:
|
||||
if not diff_dct[DIFF_IS_NEW]:
|
||||
result[DIFF_OLD_FILE] = target
|
||||
result[DIFF_OLD_CHECKSUM] = diff_dct[DIFF_A_OUTPUT].checksum
|
||||
size -= os.path.getsize(
|
||||
self.cache.local.get(diff_dct[DIFF_A_OUTPUT].checksum)
|
||||
)
|
||||
if not diff_dct[DIFF_DELETED]:
|
||||
result[DIFF_NEW_FILE] = target
|
||||
result[DIFF_NEW_CHECKSUM] = diff_dct[DIFF_B_OUTPUT].checksum
|
||||
size += os.path.getsize(
|
||||
self.cache.local.get(diff_dct[DIFF_B_OUTPUT].checksum)
|
||||
)
|
||||
result[DIFF_SIZE] = size
|
||||
except IOError as e:
|
||||
_file_not_exists(e, result)
|
||||
return result
|
||||
|
||||
|
||||
def _diff_royal(self, target, diff_dct):
|
||||
if diff_dct[DIFF_IS_DIR]:
|
||||
return _diff_dir(self, target, diff_dct)
|
||||
return _diff_file(self, target, diff_dct)
|
||||
|
||||
|
||||
@locked
|
||||
def diff(self, a_ref, target=None, b_ref=None):
|
||||
"""Generates diff message string output
|
||||
|
||||
Args:
|
||||
target(str) - file/directory to check diff of
|
||||
a_ref(str) - first tag
|
||||
(optional) b_ref(str) - second git tag
|
||||
|
||||
Returns:
|
||||
string: string of output message with diff info
|
||||
def diff(self, a_ref=None, b_ref=None, *, target=None):
|
||||
"""
|
||||
result = {}
|
||||
diff_dct = self.scm.get_diff_trees(a_ref, b_ref=b_ref)
|
||||
result[DIFF_A_REF] = diff_dct[DIFF_A_REF]
|
||||
result[DIFF_B_REF] = diff_dct[DIFF_B_REF]
|
||||
if diff_dct[DIFF_EQUAL]:
|
||||
result[DIFF_EQUAL] = True
|
||||
return result
|
||||
result[DIFF_LIST] = []
|
||||
diff_outs = _get_diff_outs(self, diff_dct)
|
||||
if target is None:
|
||||
result[DIFF_LIST] = [
|
||||
_diff_royal(self, path, diff_outs[path]) for path in diff_outs
|
||||
]
|
||||
elif target in diff_outs:
|
||||
result[DIFF_LIST] = [_diff_royal(self, target, diff_outs[target])]
|
||||
else:
|
||||
msg = "Have not found file/directory '{}' in the commits"
|
||||
raise FileNotInCommitError(msg.format(target))
|
||||
return result
|
||||
By default, it compares the working tree with the last commit's tree.
|
||||
|
||||
When a `target` path is given, it only shows that file's comparison.
|
||||
|
||||
This implementation differs from `git diff`, since DVC doesn't have
|
||||
the concept of `index`, `dvc diff` would be the same as `dvc diff HEAD`.
|
||||
"""
|
||||
a_ref = a_ref or "HEAD"
|
||||
outs = {}
|
||||
|
||||
for branch in self.brancher(revs=[a_ref, b_ref]):
|
||||
outs[branch] = set(
|
||||
diffable_from_output(out)
|
||||
for stage in self.stages
|
||||
for out in stage.outs
|
||||
)
|
||||
|
||||
old = outs[a_ref]
|
||||
new = outs[b_ref or "working tree"]
|
||||
delta = old ^ new
|
||||
|
||||
if not delta:
|
||||
return
|
||||
|
||||
old &= delta
|
||||
new &= delta
|
||||
|
||||
return {
|
||||
"old": [entry._asdict() for entry in old],
|
||||
"new": [entry._asdict() for entry in new],
|
||||
}
|
||||
|
||||
+61
-242
@@ -1,267 +1,86 @@
|
||||
import os
|
||||
|
||||
import dvc.repo.diff as diff
|
||||
from dvc.main import main
|
||||
from dvc.scm.base import FileNotInCommitError
|
||||
from tests.basic_env import TestDvcGit
|
||||
import pytest
|
||||
|
||||
|
||||
class TestDiff(TestDvcGit):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
def test_no_scm(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("file", "text")
|
||||
|
||||
self.new_file = "new_test_file"
|
||||
self.create(self.new_file, self.new_file)
|
||||
stage = self.dvc.add(self.new_file)[0]
|
||||
self.a_ref = self.git.git.rev_parse(self.git.head.commit, short=True)
|
||||
self.new_checksum = stage.outs[0].checksum
|
||||
self.git.index.add([self.new_file + ".dvc"])
|
||||
self.git.index.commit("adds new_file")
|
||||
self.test_dct = {
|
||||
diff.DIFF_A_REF: self.a_ref,
|
||||
diff.DIFF_B_REF: self.git.git.rev_parse(
|
||||
self.git.head.commit, short=True
|
||||
),
|
||||
diff.DIFF_LIST: [
|
||||
{
|
||||
diff.DIFF_TARGET: self.new_file,
|
||||
diff.DIFF_NEW_FILE: self.new_file,
|
||||
diff.DIFF_NEW_CHECKSUM: self.new_checksum,
|
||||
diff.DIFF_SIZE: 13,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
def test(self):
|
||||
out = self.dvc.scm.get_diff_trees(self.a_ref)
|
||||
self.assertFalse(out[diff.DIFF_EQUAL])
|
||||
self.assertEqual(self.a_ref, out[diff.DIFF_A_REF])
|
||||
self.assertEqual(
|
||||
self.git.git.rev_parse(self.git.head.commit, short=True),
|
||||
out[diff.DIFF_B_REF],
|
||||
)
|
||||
pytest.skip("TODO: define behavior, should it fail?")
|
||||
|
||||
|
||||
class TestDiffRepo(TestDiff):
|
||||
def test(self):
|
||||
result = self.dvc.diff(self.a_ref, target=self.new_file)
|
||||
self.assertEqual(self.test_dct, result)
|
||||
def test_added(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen("file", "text")
|
||||
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
|
||||
class TestDiffCmdLine(TestDiff):
|
||||
def test(self):
|
||||
ret = main(["diff", "-t", self.new_file, self.a_ref])
|
||||
self.assertEqual(ret, 0)
|
||||
def test_deleted(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen("file", "text", commit="add file")
|
||||
(tmp_dir / "file").unlink()
|
||||
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
|
||||
class TestDiffDir(TestDvcGit):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
def test_modified(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen("file", "first", commit="first version")
|
||||
tmp_dir.dvc_gen("file", "second")
|
||||
|
||||
stage = self.dvc.add(self.DATA_DIR)[0]
|
||||
self.git.index.add([self.DATA_DIR + ".dvc"])
|
||||
self.git.index.commit("adds data_dir")
|
||||
self.a_ref = self.git.git.rev_parse(
|
||||
self.dvc.scm.repo.head.commit, short=True
|
||||
)
|
||||
self.old_checksum = stage.outs[0].checksum
|
||||
self.new_file = os.path.join(self.DATA_SUB_DIR, diff.DIFF_NEW_FILE)
|
||||
self.create(self.new_file, self.new_file)
|
||||
stage = self.dvc.add(self.DATA_DIR)[0]
|
||||
self.git.index.add([self.DATA_DIR + ".dvc"])
|
||||
self.git.index.commit(message="adds data_dir with new_file")
|
||||
self.new_checksum = stage.outs[0].checksum
|
||||
|
||||
def test(self):
|
||||
out = self.dvc.scm.get_diff_trees(self.a_ref)
|
||||
self.assertFalse(out[diff.DIFF_EQUAL])
|
||||
self.assertEqual(self.a_ref, out[diff.DIFF_A_REF])
|
||||
self.assertEqual(
|
||||
self.git.git.rev_parse(self.git.head.commit, short=True),
|
||||
out[diff.DIFF_B_REF],
|
||||
)
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
|
||||
class TestDiffDirRepo(TestDiffDir):
|
||||
maxDiff = None
|
||||
def test_refs(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen("file", "first", commit="first version")
|
||||
tmp_dir.dvc_gen("file", "second", commit="second version")
|
||||
tmp_dir.dvc_gen("file", "third", commit="third version")
|
||||
|
||||
def test(self):
|
||||
result = self.dvc.diff(self.a_ref, target=self.DATA_DIR)
|
||||
test_dct = {
|
||||
diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True),
|
||||
diff.DIFF_B_REF: self.git.git.rev_parse(
|
||||
self.git.head.commit, short=True
|
||||
),
|
||||
diff.DIFF_LIST: [
|
||||
{
|
||||
diff.DIFF_CHANGE: 0,
|
||||
diff.DIFF_DEL: 0,
|
||||
diff.DIFF_IDENT: 2,
|
||||
diff.DIFF_MOVE: 0,
|
||||
diff.DIFF_NEW: 1,
|
||||
diff.DIFF_IS_DIR: True,
|
||||
diff.DIFF_TARGET: self.DATA_DIR,
|
||||
diff.DIFF_NEW_FILE: self.DATA_DIR,
|
||||
diff.DIFF_NEW_CHECKSUM: self.new_checksum,
|
||||
diff.DIFF_OLD_FILE: self.DATA_DIR,
|
||||
diff.DIFF_OLD_CHECKSUM: self.old_checksum,
|
||||
diff.DIFF_SIZE: 30,
|
||||
}
|
||||
],
|
||||
}
|
||||
self.assertEqual(test_dct, result)
|
||||
# dvc.diff("HEAD~1") --> (third, second)
|
||||
# dvc.diff("HEAD~1", "HEAD~2") --> (second, first)
|
||||
# dvc.diff("missing") --> error
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
|
||||
class TestDiffDirRepoDeletedFile(TestDiffDir):
|
||||
maxDiff = None
|
||||
def test_target(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
tmp_dir.dvc_gen("bar", "bar")
|
||||
scm.add([".gitignore", "foo.dvc", "bar.dvc"])
|
||||
scm.commit("lowercase")
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
tmp_dir.dvc_gen("foo", "FOO")
|
||||
tmp_dir.dvc_gen("bar", "BAR")
|
||||
scm.add(["foo.dvc", "bar.dvc"])
|
||||
scm.commit("uppercase")
|
||||
|
||||
self.b_ref = self.a_ref
|
||||
tmp = self.new_checksum
|
||||
self.new_checksum = self.old_checksum
|
||||
self.a_ref = str(self.dvc.scm.repo.head.commit)
|
||||
self.old_checksum = tmp
|
||||
|
||||
def test(self):
|
||||
result = self.dvc.diff(
|
||||
self.a_ref, b_ref=self.b_ref, target=self.DATA_DIR
|
||||
)
|
||||
test_dct = {
|
||||
diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True),
|
||||
diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True),
|
||||
diff.DIFF_LIST: [
|
||||
{
|
||||
diff.DIFF_CHANGE: 0,
|
||||
diff.DIFF_DEL: 1,
|
||||
diff.DIFF_IDENT: 2,
|
||||
diff.DIFF_MOVE: 0,
|
||||
diff.DIFF_NEW: 0,
|
||||
diff.DIFF_IS_DIR: True,
|
||||
diff.DIFF_TARGET: self.DATA_DIR,
|
||||
diff.DIFF_NEW_FILE: self.DATA_DIR,
|
||||
diff.DIFF_NEW_CHECKSUM: self.new_checksum,
|
||||
diff.DIFF_OLD_FILE: self.DATA_DIR,
|
||||
diff.DIFF_OLD_CHECKSUM: self.old_checksum,
|
||||
diff.DIFF_SIZE: -30,
|
||||
}
|
||||
],
|
||||
}
|
||||
self.assertEqual(test_dct, result)
|
||||
# dvc.diff("HEAD~1", target="foo")
|
||||
# dvc.diff("HEAD~1", target="missing") --> error
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
|
||||
class TestDiffFileNotFound(TestDiffDir):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.unknown_file = "unknown_file_" + str(id(self))
|
||||
def test_directories(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen({"dir": {"1": "1", "2": "2"}}, commit="add a directory")
|
||||
tmp_dir.dvc_gen({"dir": {"2": "2"}}, commit="delete a file")
|
||||
tmp_dir.dvc_gen({"dir": {"2": "two"}}, commit="modify a file")
|
||||
tmp_dir.dvc_gen({"dir": {"2": "two", "3": "3"}}, commit="add a file")
|
||||
|
||||
def test(self):
|
||||
with self.assertRaises(FileNotInCommitError):
|
||||
self.dvc.diff(self.a_ref, target=self.unknown_file)
|
||||
# dvc.diff(":/directory", ":/init") --> (add: dir/1, dir/2)
|
||||
# dvc.diff(":/delete", ":/directory") --> (deleted: dir/1)
|
||||
# dvc.diff(":/modify", ":/directory") --> (modified: dir/2)
|
||||
# dvc.diff(":/modify", ":/delete") --> (add: dir/3)
|
||||
# dvc.diff(":/add a file", ":/modify")
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
|
||||
class TestDiffModifiedFile(TestDiff):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.old_checksum = self.new_checksum
|
||||
self.new_file_content = "new_test_file_bigger_content_123456789"
|
||||
self.diff_len = len(self.new_file) + len(self.new_file_content)
|
||||
self.create(self.new_file, self.new_file_content)
|
||||
stage = self.dvc.add(self.new_file)[0]
|
||||
self.git.index.add([self.new_file + ".dvc"])
|
||||
self.git.index.commit("change new_file content to be bigger")
|
||||
self.new_checksum = stage.outs[0].checksum
|
||||
self.b_ref = self.git.git.rev_parse(self.git.head.commit, short=True)
|
||||
|
||||
def test(self):
|
||||
result = self.dvc.diff(
|
||||
self.a_ref, b_ref=self.b_ref, target=self.new_file
|
||||
)
|
||||
test_dct = {
|
||||
diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True),
|
||||
diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True),
|
||||
diff.DIFF_LIST: [
|
||||
{
|
||||
diff.DIFF_NEW_CHECKSUM: self.new_checksum,
|
||||
diff.DIFF_NEW_FILE: self.new_file,
|
||||
diff.DIFF_TARGET: self.new_file,
|
||||
diff.DIFF_SIZE: self.diff_len,
|
||||
}
|
||||
],
|
||||
}
|
||||
self.assertEqual(test_dct, result)
|
||||
def test_cli(tmp_dir, scm, dvc):
|
||||
tmp_dir.dvc_gen("file", "text")
|
||||
# main(["diff"])
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
|
||||
class TestDiffDirWithFile(TestDiffDir):
|
||||
maxDiff = None
|
||||
def test_json(tmp_dir, scm, dvc):
|
||||
# result = {
|
||||
# "added": {...},
|
||||
# "renamed": {...},
|
||||
# "modified": {...},
|
||||
# "deleted": {...},
|
||||
# }
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.a_ref = self.git.git.rev_parse(self.git.head.commit, short=True)
|
||||
self.old_checksum = self.new_checksum
|
||||
self.new_file_content = "new_test_file_bigger_content_123456789"
|
||||
self.diff_len = len(self.new_file_content)
|
||||
self.create(self.new_file, self.new_file_content)
|
||||
stage = self.dvc.add(self.DATA_DIR)[0]
|
||||
self.git.index.add([self.DATA_DIR + ".dvc"])
|
||||
self.git.index.commit(message="modify file in the data dir")
|
||||
self.new_checksum = stage.outs[0].checksum
|
||||
self.b_ref = self.git.git.rev_parse(self.git.head.commit, short=True)
|
||||
|
||||
def test(self):
|
||||
result = self.dvc.diff(self.a_ref, target=self.DATA_DIR)
|
||||
test_dct = {
|
||||
diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True),
|
||||
diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True),
|
||||
diff.DIFF_LIST: [
|
||||
{
|
||||
diff.DIFF_IDENT: 2,
|
||||
diff.DIFF_CHANGE: 1,
|
||||
diff.DIFF_DEL: 0,
|
||||
diff.DIFF_MOVE: 0,
|
||||
diff.DIFF_NEW: 0,
|
||||
diff.DIFF_IS_DIR: True,
|
||||
diff.DIFF_TARGET: self.DATA_DIR,
|
||||
diff.DIFF_NEW_FILE: self.DATA_DIR,
|
||||
diff.DIFF_NEW_CHECKSUM: self.new_checksum,
|
||||
diff.DIFF_OLD_FILE: self.DATA_DIR,
|
||||
diff.DIFF_OLD_CHECKSUM: self.old_checksum,
|
||||
diff.DIFF_SIZE: self.diff_len,
|
||||
}
|
||||
],
|
||||
}
|
||||
self.assertEqual(test_dct, result)
|
||||
|
||||
|
||||
class TestDiffCmdMessage(TestDiff):
|
||||
maxDiff = None
|
||||
|
||||
def test(self):
|
||||
ret = main(
|
||||
[
|
||||
"diff",
|
||||
self.test_dct[diff.DIFF_A_REF],
|
||||
self.test_dct[diff.DIFF_B_REF],
|
||||
]
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
msg1 = "dvc diff from {0} to {1}".format(
|
||||
self.git.git.rev_parse(self.test_dct[diff.DIFF_A_REF], short=True),
|
||||
self.git.git.rev_parse(self.test_dct[diff.DIFF_B_REF], short=True),
|
||||
)
|
||||
msg2 = "diff for '{0}'".format(
|
||||
self.test_dct[diff.DIFF_LIST][0][diff.DIFF_TARGET]
|
||||
)
|
||||
msg3 = "+{0} with md5 {1}".format(
|
||||
self.test_dct[diff.DIFF_LIST][0][diff.DIFF_TARGET],
|
||||
self.test_dct[diff.DIFF_LIST][0][diff.DIFF_NEW_CHECKSUM],
|
||||
)
|
||||
msg4 = "added file with size 13 Bytes"
|
||||
for m in [msg1, msg2, msg3, msg4]:
|
||||
assert m in self._caplog.text
|
||||
# main(["diff", "--json"])
|
||||
pytest.skip("TODO: define output structure")
|
||||
|
||||
Reference in New Issue
Block a user