Files
dvc/dvc/repo/diff.py
T

76 lines
2.0 KiB
Python

import os
import collections
from dvc.repo import locked
from dvc.compat import fspath
Diffable = collections.namedtuple("Diffable", "filename, checksum, size")
Diffable.__doc__ = "Common interface to compare outputs."
Diffable._asdict = lambda x: {"checksum": x.checksum, "size": x.size}
def diffable_from_output(output):
try:
size = os.path.getsize(fspath(output.path_info))
except FileNotFoundError:
size = None
return Diffable(filename=str(output), checksum=output.checksum, size=size)
def compare_states(old, new):
if old and new:
try:
size = new["size"] - old["size"]
except KeyError:
size = "unknown"
return {"status": "modified", "size": size}
if old and not new:
return {"status": "deleted", "size": old.get("size", "unknown")}
if not old and new:
return {"status": "added", "size": new.get("size", "unknown")}
@locked
def diff(self, a_ref="HEAD", b_ref=None, *, target=None):
"""
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`.
"""
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
result = collections.defaultdict(dict)
for entry in delta:
states = result[entry.filename]
states.update(
old=states.get("old") or (entry._asdict() if entry in old else {}),
new=states.get("new") or (entry._asdict() if entry in new else {}),
)
for filename, entry in result.items():
entry.update({"diff": compare_states(entry["old"], entry["new"])})
return result