mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-03 07:18:26 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58a5442d71 | ||
|
|
76ece8c64a | ||
|
|
8b6d655ebe | ||
|
|
f2ba67aece | ||
|
|
19cdef1675 | ||
|
|
dc3fe6fbec | ||
|
|
619c55d399 | ||
|
|
b3bfa346c3 | ||
|
|
0a8927e3d8 | ||
|
|
35597ed83d | ||
|
|
21fc06e947 | ||
|
|
f2b216261e | ||
|
|
6026573860 |
@@ -6,20 +6,30 @@ on:
|
||||
release:
|
||||
types: [released, prereleased]
|
||||
jobs:
|
||||
linux:
|
||||
deb:
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
- name: Set up Python 3.7
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.8
|
||||
- name: Set up Ruby 2.4
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.4
|
||||
- name: Build
|
||||
run: ./scripts/build_posix.sh
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install .[all]
|
||||
pip install -r scripts/build-requirements.txt
|
||||
gem install --no-document fpm
|
||||
- name: Build deb
|
||||
run: python scripts/build.py deb
|
||||
- name: Upload deb artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: scripts/fpm/dvc*.deb
|
||||
if-no-files-found: error
|
||||
- name: Publish deb
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/[email protected]
|
||||
@@ -27,9 +37,33 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: dvc_${{ github.event.release.tag_name }}_amd64.deb
|
||||
asset_path: scripts/fpm/dvc_${{ github.event.release.tag_name }}_amd64.deb
|
||||
asset_name: dvc_${{ github.event.release.tag_name }}_amd64.deb
|
||||
asset_content_type: binary/octet-stream
|
||||
rpm:
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Set up Ruby 2.4
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.4
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install .[all]
|
||||
pip install -r scripts/build-requirements.txt
|
||||
gem install --no-document fpm
|
||||
- name: Build rpm
|
||||
run: python scripts/build.py rpm
|
||||
- name: Upload rpm artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: scripts/fpm/dvc*.rpm
|
||||
if-no-files-found: error
|
||||
- name: Publish rpm
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/[email protected]
|
||||
@@ -37,23 +71,33 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: dvc-${{ github.event.release.tag_name }}-1.x86_64.rpm
|
||||
asset_path: scripts/fpm/dvc-${{ github.event.release.tag_name }}-1.x86_64.rpm
|
||||
asset_name: dvc-${{ github.event.release.tag_name }}-1.x86_64.rpm
|
||||
asset_content_type: binary/octet-stream
|
||||
mac:
|
||||
osxpkg:
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
- name: Set up Python 3.7
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.8
|
||||
- name: Set up Ruby 2.4
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.4
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install .[all]
|
||||
pip install -r scripts/build-requirements.txt
|
||||
gem install --no-document fpm
|
||||
- name: Build
|
||||
run: ./scripts/build_posix.sh
|
||||
run: python scripts/build.py osxpkg
|
||||
- name: Upload osxpkg artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: scripts/fpm/dvc*.pkg
|
||||
if-no-files-found: error
|
||||
- name: Publish
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/[email protected]
|
||||
@@ -61,20 +105,32 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: dvc-${{ github.event.release.tag_name }}.pkg
|
||||
asset_path: scripts/fpm/dvc-${{ github.event.release.tag_name }}.pkg
|
||||
asset_name: dvc-${{ github.event.release.tag_name }}.pkg
|
||||
asset_content_type: binary/octet-stream
|
||||
windows:
|
||||
exe:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
- name: Set up Python 3.7
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.8
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install .[all]
|
||||
pip install -r scripts/build-requirements.txt
|
||||
- name: Pull images
|
||||
run: |
|
||||
dvc pull
|
||||
- name: Build
|
||||
run: scripts\build_windows.cmd
|
||||
run: python scripts\build.py exe
|
||||
shell: cmd
|
||||
- name: Upload exe artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: scripts/innosetup/dvc*.exe
|
||||
if-no-files-found: error
|
||||
- name: Publish
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
@@ -82,10 +138,10 @@ jobs:
|
||||
uses: actions/[email protected]
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: dvc-${{ github.event.release.tag_name }}.exe
|
||||
asset_path: scripts/innosetup/dvc-${{ github.event.release.tag_name }}.exe
|
||||
asset_name: dvc-${{ github.event.release.tag_name }}.exe
|
||||
asset_content_type: binary/octet-stream
|
||||
python:
|
||||
pip:
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/[email protected]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
|
||||
from dvc.command import completion
|
||||
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
|
||||
@@ -23,6 +22,8 @@ class CmdPlots(CmdBase):
|
||||
return {k: v for k, v in props.items() if v is not None}
|
||||
|
||||
def run(self):
|
||||
from pathlib import Path
|
||||
|
||||
if self.args.show_vega:
|
||||
if not self.args.targets:
|
||||
logger.error("please specify a target for `--show-vega`")
|
||||
@@ -41,20 +42,20 @@ class CmdPlots(CmdBase):
|
||||
logger.info(plots[target])
|
||||
return 0
|
||||
|
||||
path = self.args.out or "plots.html"
|
||||
path = os.path.join(os.getcwd(), path)
|
||||
|
||||
rel: str = self.args.out or "plots.html"
|
||||
path = (Path.cwd() / rel).resolve()
|
||||
write(path, plots)
|
||||
url = f"file://{path}"
|
||||
except DvcException:
|
||||
logger.exception("")
|
||||
return 1
|
||||
|
||||
assert path.is_absolute() # as_uri throws ValueError if not absolute
|
||||
url = path.as_uri()
|
||||
logger.info(url)
|
||||
if self.args.open:
|
||||
import webbrowser
|
||||
|
||||
opened = webbrowser.open(url)
|
||||
opened = webbrowser.open(rel)
|
||||
if not opened:
|
||||
logger.error("Failed to open. Please try opening it manually.")
|
||||
return 1
|
||||
|
||||
@@ -44,6 +44,8 @@ class CmdDataStatus(CmdDataBase):
|
||||
self._show(value, indent + 1)
|
||||
|
||||
def run(self):
|
||||
from dvc.ui import ui
|
||||
|
||||
indent = 1 if self.args.cloud else 0
|
||||
try:
|
||||
st = self.repo.status(
|
||||
@@ -68,14 +70,14 @@ class CmdDataStatus(CmdDataBase):
|
||||
elif st:
|
||||
self._show(st, indent)
|
||||
elif not self.repo.stages:
|
||||
logger.info(self.EMPTY_PROJECT_MSG)
|
||||
ui.write(self.EMPTY_PROJECT_MSG)
|
||||
elif self.args.cloud or self.args.remote:
|
||||
remote = self.args.remote or self.repo.config["core"].get(
|
||||
"remote"
|
||||
)
|
||||
logger.info(self.IN_SYNC_MSG.format(remote=remote))
|
||||
ui.write(self.IN_SYNC_MSG.format(remote=remote))
|
||||
else:
|
||||
logger.info(self.UP_TO_DATE_MSG)
|
||||
ui.write(self.UP_TO_DATE_MSG)
|
||||
|
||||
except DvcException:
|
||||
logger.exception("")
|
||||
|
||||
+3
-4
@@ -48,8 +48,6 @@ class AzureFileSystem(FSSpecWrapper):
|
||||
self.path_info = self.PATH_CLS(url)
|
||||
self.bucket = self.path_info.bucket
|
||||
|
||||
self.login_method, self.login_info = self._prepare_credentials(config)
|
||||
|
||||
def _prepare_credentials(self, config):
|
||||
from azure.identity.aio import DefaultAzureCredential
|
||||
|
||||
@@ -90,7 +88,8 @@ class AzureFileSystem(FSSpecWrapper):
|
||||
else:
|
||||
login_method = None
|
||||
|
||||
return login_method, login_info
|
||||
self.login_method = login_method
|
||||
return login_info
|
||||
|
||||
@cached_property
|
||||
def _az_config(self):
|
||||
@@ -111,7 +110,7 @@ class AzureFileSystem(FSSpecWrapper):
|
||||
from azure.core.exceptions import AzureError
|
||||
|
||||
try:
|
||||
file_system = AzureBlobFileSystem(**self.login_info)
|
||||
file_system = AzureBlobFileSystem(**self.fs_args)
|
||||
if self.bucket not in [
|
||||
container.rstrip("/") for container in file_system.ls("/")
|
||||
]:
|
||||
|
||||
@@ -9,6 +9,11 @@ from .base import BaseFileSystem
|
||||
|
||||
# pylint: disable=no-member
|
||||
class FSSpecWrapper(BaseFileSystem):
|
||||
def __init__(self, repo, config):
|
||||
super().__init__(repo, config)
|
||||
self.fs_args = {"skip_instance_cache": True}
|
||||
self.fs_args.update(self._prepare_credentials(config))
|
||||
|
||||
def fs(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -19,8 +24,14 @@ class FSSpecWrapper(BaseFileSystem):
|
||||
return path
|
||||
|
||||
def _strip_bucket(self, entry):
|
||||
_, entry = entry.split("/", 1)
|
||||
return entry
|
||||
try:
|
||||
bucket, path = entry.split("/", 1)
|
||||
except ValueError:
|
||||
# If there is no path attached, only returns
|
||||
# the bucket (top-level).
|
||||
bucket, path = entry, None
|
||||
|
||||
return path or bucket
|
||||
|
||||
def _strip_buckets(self, entries, detail, prefix=None):
|
||||
for entry in entries:
|
||||
@@ -38,6 +49,11 @@ class FSSpecWrapper(BaseFileSystem):
|
||||
entries within info() and ls(detail=True) calls"""
|
||||
return entry
|
||||
|
||||
def _prepare_credentials(self, config): # pylint: disable=unused-argument
|
||||
"""Prepare the arguments for authentication to the
|
||||
host filesystem"""
|
||||
return {}
|
||||
|
||||
def isdir(self, path_info):
|
||||
if not self.exists(path_info):
|
||||
return False
|
||||
|
||||
+2
-4
@@ -22,10 +22,8 @@ class GSFileSystem(FSSpecWrapper):
|
||||
url = config.get("url", "gs://")
|
||||
self.path_info = self.PATH_CLS(url)
|
||||
|
||||
self.login_info = self._prepare_credentials(config)
|
||||
|
||||
def _prepare_credentials(self, config):
|
||||
login_info = {}
|
||||
login_info = {"consistency": None}
|
||||
login_info["project"] = config.get("projectname")
|
||||
login_info["token"] = config.get("credentialpath")
|
||||
return login_info
|
||||
@@ -40,4 +38,4 @@ class GSFileSystem(FSSpecWrapper):
|
||||
def fs(self):
|
||||
from gcsfs import GCSFileSystem
|
||||
|
||||
return GCSFileSystem(**self.login_info, consistency=None)
|
||||
return GCSFileSystem(**self.fs_args)
|
||||
|
||||
+21
-7
@@ -23,14 +23,19 @@ def addLoggingLevel(levelName, levelNum, methodName=None):
|
||||
Adds a new logging level to the `logging` module and the
|
||||
currently configured logging class.
|
||||
|
||||
Uses the existing numeric levelNum if already defined.
|
||||
|
||||
Based on https://stackoverflow.com/questions/2183233
|
||||
"""
|
||||
if methodName is None:
|
||||
methodName = levelName.lower()
|
||||
|
||||
assert not hasattr(logging, levelName)
|
||||
assert not hasattr(logging, methodName)
|
||||
assert not hasattr(logging.getLoggerClass(), methodName)
|
||||
# If the level name is already defined as a top-level `logging`
|
||||
# constant, then adopt the existing numeric level.
|
||||
if hasattr(logging, levelName):
|
||||
existingLevelNum = getattr(logging, levelName)
|
||||
assert isinstance(existingLevelNum, int)
|
||||
levelNum = existingLevelNum
|
||||
|
||||
def logForLevel(self, message, *args, **kwargs):
|
||||
if self.isEnabledFor(levelNum):
|
||||
@@ -40,10 +45,19 @@ def addLoggingLevel(levelName, levelNum, methodName=None):
|
||||
def logToRoot(message, *args, **kwargs):
|
||||
logging.log(levelNum, message, *args, **kwargs)
|
||||
|
||||
logging.addLevelName(levelNum, levelName)
|
||||
setattr(logging, levelName, levelNum)
|
||||
setattr(logging.getLoggerClass(), methodName, logForLevel)
|
||||
setattr(logging, methodName, logToRoot)
|
||||
# getLevelName resolves the numeric log level if already defined,
|
||||
# otherwise returns a string
|
||||
if not isinstance(logging.getLevelName(levelName), int):
|
||||
logging.addLevelName(levelNum, levelName)
|
||||
|
||||
if not hasattr(logging, levelName):
|
||||
setattr(logging, levelName, levelNum)
|
||||
|
||||
if not hasattr(logging.getLoggerClass(), methodName):
|
||||
setattr(logging.getLoggerClass(), methodName, logForLevel)
|
||||
|
||||
if not hasattr(logging, methodName):
|
||||
setattr(logging, methodName, logToRoot)
|
||||
|
||||
|
||||
class LoggingException(Exception):
|
||||
|
||||
@@ -45,6 +45,11 @@ def main(argv=None): # noqa: C901
|
||||
|
||||
logger.trace(args)
|
||||
|
||||
if not args.quiet:
|
||||
from dvc.ui import ui
|
||||
|
||||
ui.enable()
|
||||
|
||||
with debugtools(args):
|
||||
cmd = args.func(args)
|
||||
ret = cmd.run()
|
||||
|
||||
+11
-15
@@ -1,5 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List
|
||||
|
||||
from dvc.exceptions import PathMissingError
|
||||
from dvc.objects.stage import get_file_hash
|
||||
@@ -190,29 +192,23 @@ def _targets_to_path_infos(repo_fs, targets):
|
||||
|
||||
|
||||
def _calculate_renamed(new, old, added, deleted):
|
||||
old_inverted = {}
|
||||
old_inverted: Dict[str, List[str]] = defaultdict(list)
|
||||
# It is needed to be dict of lists to cover cases
|
||||
# when repo has paths with same hash
|
||||
for path, path_hash in old.items():
|
||||
bucket = old_inverted.get(path_hash, None)
|
||||
if bucket is None:
|
||||
old_inverted[path_hash] = [path]
|
||||
else:
|
||||
bucket.append(path)
|
||||
old_inverted[path_hash].append(path)
|
||||
|
||||
renamed = []
|
||||
for path in added:
|
||||
path_hash = new[path]
|
||||
old_paths = old_inverted.get(path_hash, None)
|
||||
if not old_paths:
|
||||
continue
|
||||
old_path = None
|
||||
for tmp_old_path in old_paths:
|
||||
if tmp_old_path in deleted:
|
||||
old_path = tmp_old_path
|
||||
break
|
||||
if not old_path:
|
||||
old_paths = old_inverted[path_hash]
|
||||
try:
|
||||
iterator = enumerate(old_paths)
|
||||
index = next(idx for idx, path in iterator if path in deleted)
|
||||
except StopIteration:
|
||||
continue
|
||||
|
||||
old_path = old_paths.pop(index)
|
||||
renamed.append(
|
||||
{"path": {"old": old_path, "new": path}, "hash": path_hash}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ def _update_import_on_remote(stage, remote, jobs):
|
||||
"be updated with --to-remote"
|
||||
)
|
||||
|
||||
url = stage.deps[0].path_info.url
|
||||
url = stage.deps[0].def_path
|
||||
stage.outs[0].hash_info = stage.repo.cloud.transfer(
|
||||
url, jobs=jobs, remote=remote, command="update"
|
||||
)
|
||||
|
||||
+14
-12
@@ -7,9 +7,6 @@ from funcy import cached_property
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.utils import colorize
|
||||
|
||||
NEWLINE = "\n"
|
||||
SEP = " "
|
||||
|
||||
|
||||
class Formatter:
|
||||
def __init__(self, theme: Dict = None, defaults: Dict = None) -> None:
|
||||
@@ -30,14 +27,17 @@ class Console:
|
||||
formatter: Formatter = None,
|
||||
output: TextIO = None,
|
||||
error: TextIO = None,
|
||||
disable: bool = False,
|
||||
enable: bool = False,
|
||||
) -> None:
|
||||
self._input: TextIO = sys.stdin
|
||||
self._output: TextIO = output or sys.stdout
|
||||
self._error: TextIO = error or sys.stderr
|
||||
|
||||
self.formatter: Formatter = formatter or Formatter()
|
||||
self.disabled: bool = disable
|
||||
self._enabled: bool = enable
|
||||
|
||||
def enable(self):
|
||||
self._enabled = True
|
||||
|
||||
def success(self, message: str) -> None:
|
||||
self.write(message, style="success")
|
||||
@@ -52,8 +52,8 @@ class Console:
|
||||
self,
|
||||
*objects: Any,
|
||||
style: str = None,
|
||||
sep: str = SEP,
|
||||
end: str = NEWLINE,
|
||||
sep: str = None,
|
||||
end: str = None,
|
||||
flush: bool = False,
|
||||
) -> None:
|
||||
return self.write(
|
||||
@@ -69,12 +69,12 @@ class Console:
|
||||
self,
|
||||
*objects: Any,
|
||||
style: str = None,
|
||||
sep: str = SEP,
|
||||
end: str = NEWLINE,
|
||||
sep: str = None,
|
||||
end: str = None,
|
||||
file: TextIO = None,
|
||||
flush: bool = False,
|
||||
) -> None:
|
||||
if self.disabled:
|
||||
if not self._enabled:
|
||||
return
|
||||
|
||||
file = file or self._output
|
||||
@@ -152,9 +152,11 @@ class Console:
|
||||
self.write(ret)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ui = Console()
|
||||
ui = Console()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ui.enable()
|
||||
ui.write("No default remote set")
|
||||
ui.success("Everything is up to date.")
|
||||
ui.warn("Run queued experiments will be removed.")
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
_BASE_VERSION = "2.0.6"
|
||||
_BASE_VERSION = "2.0.8"
|
||||
|
||||
|
||||
def _generate_version(base_version):
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
PyInstaller==3.5
|
||||
PyInstaller==4.2
|
||||
numpy==1.19.3
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import argparse
|
||||
import importlib
|
||||
import pathlib
|
||||
import textwrap
|
||||
from subprocess import STDOUT, check_call
|
||||
|
||||
path = pathlib.Path(__file__).parent.absolute()
|
||||
dvc = path.parent / "dvc"
|
||||
pyinstaller = path / "pyinstaller"
|
||||
innosetup = path / "innosetup"
|
||||
fpm = path / "fpm"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"pkg", choices=["deb", "rpm", "osxpkg", "exe"], help="package type"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
(dvc / "utils" / "build.py").write_text(f'PKG = "{args.pkg}"')
|
||||
|
||||
# Autogenerate version, similar to what we do in setup.py
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"dvc.version", dvc / "version.py"
|
||||
)
|
||||
dvc_version = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(dvc_version)
|
||||
version = dvc_version.__version__
|
||||
|
||||
(dvc / "version.py").write_text(
|
||||
textwrap.dedent(
|
||||
f"""\
|
||||
# AUTOGENERATED at build time by scripts/build.py
|
||||
__version__ = "{version}"
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
check_call(
|
||||
["python", "build.py"], cwd=pyinstaller, stderr=STDOUT,
|
||||
)
|
||||
|
||||
if args.pkg == "exe":
|
||||
check_call(
|
||||
["python", "build.py"], cwd=innosetup, stderr=STDOUT,
|
||||
)
|
||||
else:
|
||||
check_call(
|
||||
["python", "build.py", args.pkg], cwd=fpm, stderr=STDOUT,
|
||||
)
|
||||
@@ -1,139 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
FPM_FLAGS='--description "Data Version Control - datasets, models, and experiments versioning for ML or data science projects"'
|
||||
FPM_FLAGS+=' -n dvc -s dir -f'
|
||||
FPM_FLAGS+=' --url https://dvc.org'
|
||||
FPM_FLAGS+=' --license "Apache License 2.0"'
|
||||
|
||||
if [[ "$(uname)" == 'Linux' ]]; then
|
||||
INSTALL_DIR=usr
|
||||
FPM_FLAGS+=' --depends "git >= 1.7.0"' # needed for gitpython
|
||||
else
|
||||
INSTALL_DIR=usr/local
|
||||
FPM_FLAGS+=' --osxpkg-identifier-prefix com.iterative'
|
||||
FPM_FLAGS+=' --after-install scripts/fpm/after-install.sh'
|
||||
FPM_FLAGS+=' --after-remove scripts/fpm/after-remove.sh'
|
||||
fi
|
||||
|
||||
BUILD_DIR=build
|
||||
BIN_DIR=$BUILD_DIR/$INSTALL_DIR/bin
|
||||
LIB_DIR=$BUILD_DIR/$INSTALL_DIR/lib
|
||||
|
||||
FPM_PACKAGE_DIRS="usr"
|
||||
ZSH_CMPLT_DIR=usr/share/zsh/site-functions/_dvc
|
||||
if [[ "$(uname)" == 'Linux' ]]; then
|
||||
BASH_CMPLT_DIR=etc/bash_completion.d
|
||||
FPM_PACKAGE_DIRS="$FPM_PACKAGE_DIRS etc"
|
||||
else
|
||||
BASH_CMPLT_DIR=usr/local/etc/bash_completion.d
|
||||
fi
|
||||
|
||||
print_error() {
|
||||
echo -e "\e[31m$1\e[0m" >&2
|
||||
}
|
||||
|
||||
if [ ! -d "dvc" ]; then
|
||||
print_error "Please run this script from repository root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
trap 'print_error "FAIL"; exit 1' ERR
|
||||
|
||||
print_info() {
|
||||
echo -e "\e[32m$1\e[0m"
|
||||
}
|
||||
|
||||
command_exists() {
|
||||
command -v $1 >/dev/null 2>&1
|
||||
}
|
||||
|
||||
fpm_build() {
|
||||
print_info "Building $1..."
|
||||
VERSION=$(python -c "import dvc; from dvc import __version__; print(str(__version__))")
|
||||
eval "fpm -t $1 $FPM_FLAGS -v $VERSION -C $BUILD_DIR $FPM_PACKAGE_DIRS"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
print_info "Cleaning up..."
|
||||
rm -rf build
|
||||
}
|
||||
|
||||
install_dependencies() {
|
||||
print_info "Installing fpm..."
|
||||
if command_exists dnf; then
|
||||
sudo dnf install ruby-devel gcc make rpm-build
|
||||
elif command_exists yum; then
|
||||
sudo yum install ruby-devel gcc make rpm-build
|
||||
elif command_exists apt-get; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install ruby-dev build-essential rpm python-pip python-dev
|
||||
elif command_exists brew; then
|
||||
brew install ruby
|
||||
else
|
||||
echo "Unable to install fpm dependencies" && exit 1
|
||||
fi
|
||||
|
||||
gem install --no-document fpm
|
||||
|
||||
# https://github.com/iterative/dvc/issues/2970
|
||||
gem uninstall -i /Users/travis/.rvm/gems/ruby-2.4.3@global rubygems-bundler
|
||||
|
||||
print_info "Installing requirements..."
|
||||
pip install .[all]
|
||||
pip install -r scripts/build-requirements.txt
|
||||
}
|
||||
|
||||
build_dvc() {
|
||||
print_info "Building dvc binary..."
|
||||
pyinstaller \
|
||||
--additional-hooks-dir $(pwd)/scripts/hooks dvc/__main__.py \
|
||||
--name dvc \
|
||||
--distpath $LIB_DIR \
|
||||
--specpath $BUILD_DIR
|
||||
|
||||
$LIB_DIR/dvc/dvc --help
|
||||
|
||||
# NOTE: in osxpkg fpm replaces symlinks with actual file that it
|
||||
# points to, so we need to use after-install hook. See FPM_FLAGS
|
||||
# above.
|
||||
if [[ "$(uname)" == 'Linux' ]]; then
|
||||
mkdir -p $BIN_DIR
|
||||
pushd $BIN_DIR
|
||||
ln -s ../lib/dvc/dvc dvc
|
||||
popd
|
||||
$BIN_DIR/dvc --help
|
||||
fi
|
||||
|
||||
# NOTE: temporarily not adding scripts to mac package. See [1]
|
||||
# [1] https://github.com/iterative/dvc/issues/2585
|
||||
if [[ "$(uname)" == 'Linux' ]]; then
|
||||
mkdir -p $BUILD_DIR/$BASH_CMPLT_DIR
|
||||
$LIB_DIR/dvc/dvc completion -s bash > $BUILD_DIR/$BASH_CMPLT_DIR/dvc
|
||||
|
||||
mkdir -p $BUILD_DIR/$ZSH_CMPLT_DIR
|
||||
$LIB_DIR/dvc/dvc completion -s zsh > $BUILD_DIR/$ZSH_CMPLT_DIR/_dvc
|
||||
fi
|
||||
}
|
||||
|
||||
build() {
|
||||
cleanup
|
||||
echo "PKG = \"$1\"" >dvc/utils/build.py
|
||||
build_dvc
|
||||
fpm_build $1
|
||||
}
|
||||
|
||||
install_dependencies
|
||||
|
||||
if [[ "$(uname)" == 'Linux' ]]; then
|
||||
build rpm
|
||||
build deb
|
||||
else
|
||||
build osxpkg
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
print_info "Successfully built dvc packages"
|
||||
@@ -1,48 +0,0 @@
|
||||
@echo Off
|
||||
|
||||
echo ====== Starting to build dvc installer for Windows... ======
|
||||
|
||||
if not exist dvc\NUL (echo Error: Please run this script from repository root && goto :error)
|
||||
|
||||
rmdir /Q /S build
|
||||
rmdir /Q /S dist
|
||||
del /Q /S dvc.spec
|
||||
del /Q /S "dvc-*.exe"
|
||||
|
||||
where pip
|
||||
if %errorlevel% neq 0 (echo Error: pip not found && goto :error)
|
||||
|
||||
where choco
|
||||
if %errorlevel% neq 0 (echo Error: choco not found && goto :error)
|
||||
|
||||
choco install InnoSetup
|
||||
call refreshenv
|
||||
where iscc
|
||||
if %errorlevel% neq 0 (echo Error: Couldn't find Inno Setup compiler. && goto :error)
|
||||
|
||||
echo ====== Installing requirements... ======
|
||||
echo PKG = "exe" > dvc\utils\build.py
|
||||
call pip install .[all] || goto :error
|
||||
call pip install -r scripts\build-requirements.txt || goto :error
|
||||
call dvc pull || goto :error
|
||||
|
||||
echo ====== Building dvc binary... ======
|
||||
call pyinstaller --additional-hooks-dir scripts\hooks dvc/__main__.py --name dvc --specpath build
|
||||
|
||||
echo ====== Testing dvc binary... ======
|
||||
call dist\dvc\dvc.exe version || goto :error
|
||||
|
||||
echo ====== Copying additional files... ======
|
||||
copy scripts\innosetup\addSymLinkPermissions.ps1 dist\ || goto :error
|
||||
|
||||
echo ====== Building dvc installer... ======
|
||||
set PYTHONPATH=%cd%
|
||||
call python scripts\innosetup\config_gen.py || goto :error
|
||||
call iscc scripts\innosetup\setup.iss || goto :error
|
||||
|
||||
echo ====== DONE ======
|
||||
goto :EOF
|
||||
|
||||
:error
|
||||
echo ====== FAIL ======
|
||||
exit /b 1
|
||||
@@ -0,0 +1,4 @@
|
||||
/build
|
||||
/*.deb
|
||||
/*.rpm
|
||||
/*.pkg
|
||||
@@ -0,0 +1,96 @@
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
from subprocess import STDOUT, check_call, check_output
|
||||
|
||||
path = pathlib.Path(__file__).parent.absolute()
|
||||
dvc = path.parent.parent / "dvc"
|
||||
pyinstaller = path.parent / "pyinstaller"
|
||||
|
||||
build = path / "build"
|
||||
install = build / "usr"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"pkg", choices=["deb", "rpm", "osxpkg"], help="package type"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
flags = [
|
||||
"--description",
|
||||
'"Data Version Control | Git for Data & Models"',
|
||||
"-n",
|
||||
"dvc",
|
||||
"-s",
|
||||
"dir",
|
||||
"-f",
|
||||
"--license",
|
||||
'"Apache License 2.0"',
|
||||
]
|
||||
|
||||
if args.pkg == "osxpkg":
|
||||
install /= "local"
|
||||
bash_dir = install / "etc" / "bash_completion.d"
|
||||
dirs = ["usr"]
|
||||
flags.extend(
|
||||
[
|
||||
"--osxpkg-identifier-prefix",
|
||||
"com.iterative",
|
||||
"--after-install",
|
||||
path / "after-install.sh",
|
||||
"--after-remove",
|
||||
path / "after-remove.sh",
|
||||
]
|
||||
)
|
||||
else:
|
||||
bash_dir = build / "etc" / "bash_completion.d"
|
||||
dirs = ["usr", "etc"]
|
||||
flags.extend(["--depends", "git >= 1.7.0"]) # needed for gitpython
|
||||
|
||||
try:
|
||||
shutil.rmtree(build)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
lib = install / "lib"
|
||||
lib.mkdir(parents=True)
|
||||
shutil.copytree(pyinstaller / "dist" / "dvc", lib / "dvc")
|
||||
|
||||
if args.pkg != "osxpkg":
|
||||
# NOTE: in osxpkg fpm replaces symlinks with actual file that it
|
||||
# points to, so we need to use after-install hook.
|
||||
(install / "bin").mkdir()
|
||||
os.symlink("../lib/dvc/dvc", install / "bin" / "dvc")
|
||||
|
||||
bash_dir.mkdir(parents=True)
|
||||
bash_completion = check_output(
|
||||
[lib / "dvc" / "dvc", "completion", "-s", "bash"], text=True
|
||||
)
|
||||
(bash_dir / "dvc").write_text(bash_completion)
|
||||
|
||||
zsh_dir = build / "usr" / "share" / "zsh" / "site-funtions"
|
||||
zsh_dir.mkdir(parents=True)
|
||||
zsh_completion = check_output(
|
||||
[lib / "dvc" / "dvc", "completion", "-s", "zsh"], text=True
|
||||
)
|
||||
(zsh_dir / "_dvc").write_text(zsh_completion)
|
||||
|
||||
version = check_output([lib / "dvc" / "dvc", "--version"], text=True).strip()
|
||||
|
||||
check_call(
|
||||
[
|
||||
"fpm",
|
||||
"--verbose",
|
||||
"-t",
|
||||
args.pkg,
|
||||
*flags,
|
||||
"-v",
|
||||
version,
|
||||
"-C",
|
||||
build,
|
||||
*dirs,
|
||||
],
|
||||
cwd=path,
|
||||
stderr=STDOUT,
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
"""Temporary fix for https://github.com/iterative/dvc/issues/5618."""
|
||||
from PyInstaller.utils.hooks import ( # pylint:disable=import-error
|
||||
collect_data_files,
|
||||
copy_metadata,
|
||||
)
|
||||
|
||||
datas = collect_data_files("googleapiclient.discovery")
|
||||
datas += copy_metadata("google_api_python_client")
|
||||
@@ -1,4 +1,6 @@
|
||||
dvc.ico
|
||||
dvc_left.bmp
|
||||
dvc_up.bmp
|
||||
/dvc.ico
|
||||
/dvc_left.bmp
|
||||
/dvc_up.bmp
|
||||
/config.ini
|
||||
/build
|
||||
/dvc*.exe
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import argparse
|
||||
import configparser
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
from subprocess import STDOUT, check_call, check_output
|
||||
|
||||
path = pathlib.Path(__file__).parent.absolute()
|
||||
config = path / "config.ini"
|
||||
|
||||
dvc = path.parent.parent / "dvc"
|
||||
pyinstaller = path.parent / "pyinstaller"
|
||||
|
||||
build = path / "build"
|
||||
install = build / "usr"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
shutil.rmtree(build)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
build.mkdir()
|
||||
shutil.copytree(pyinstaller / "dist" / "dvc", build / "dvc")
|
||||
shutil.copy(path / "addSymLinkPermissions.ps1", build)
|
||||
|
||||
version = check_output(
|
||||
[os.fspath(build / "dvc" / "dvc"), "--version"], text=True
|
||||
).strip()
|
||||
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.add_section("Version")
|
||||
cfg.set("Version", "version", version)
|
||||
|
||||
with (path / "config.ini").open("w") as fobj:
|
||||
cfg.write(fobj)
|
||||
|
||||
check_call(
|
||||
["iscc", "setup.iss"], cwd=path, stderr=STDOUT,
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
# This script generates config.ini for setup.iss script
|
||||
import configparser
|
||||
|
||||
from dvc import __version__
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.add_section("Version")
|
||||
config.set("Version", "Version", __version__)
|
||||
|
||||
with open("scripts/innosetup/config.ini", "w") as f:
|
||||
config.write(f)
|
||||
@@ -1,8 +1,8 @@
|
||||
#define MyAppName "DVC (Data Version Control)"
|
||||
#define MyAppVersion ReadIni(".\scripts\innosetup\config.ini", "Version", "version", "unknown")
|
||||
#define MyAppVersion ReadIni(SourcePath + "\config.ini", "Version", "version", "unknown")
|
||||
#define MyAppPublisher "Dmitry Petrov"
|
||||
#define MyAppURL "https://dataversioncontrol.com/"
|
||||
#define MyAppDir "..\..\dist\dvc"
|
||||
#define MyAppURL "https://dvc.org"
|
||||
#define MyAppDir SourcePath + "\build\dvc"
|
||||
|
||||
[Setup]
|
||||
AppId={{8258CE8A-110E-4E0D-AE60-FEE00B15F041}
|
||||
@@ -19,7 +19,7 @@ LicenseFile=..\..\LICENSE
|
||||
OutputBaseFilename=dvc-{#MyAppVersion}
|
||||
Compression=lzma2/max
|
||||
SolidCompression=yes
|
||||
OutputDir=..\..\
|
||||
OutputDir=.
|
||||
ChangesEnvironment=yes
|
||||
SetupIconFile=dvc.ico
|
||||
WizardSmallImageFile=dvc_up.bmp
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/build
|
||||
/dist
|
||||
/dvc.spec
|
||||
/dvc
|
||||
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
import pathlib
|
||||
from subprocess import STDOUT, check_call
|
||||
|
||||
path = pathlib.Path(__file__).parent.absolute()
|
||||
hooks = path / "hooks"
|
||||
dvc = path.parent.parent / "dvc"
|
||||
entry = dvc / "__main__.py"
|
||||
|
||||
check_call(
|
||||
[
|
||||
"pyinstaller",
|
||||
"--additional-hooks-dir",
|
||||
os.fspath(hooks),
|
||||
"--name",
|
||||
"dvc",
|
||||
"-y",
|
||||
os.fspath(entry),
|
||||
],
|
||||
cwd=path,
|
||||
stderr=STDOUT,
|
||||
)
|
||||
@@ -145,7 +145,7 @@ tests_requirements = [
|
||||
"rangehttpserver==1.2.0",
|
||||
"beautifulsoup4==4.4.0",
|
||||
"pylint==2.7.2",
|
||||
"pylint-pytest==0.3.0",
|
||||
"pylint-pytest==1.0.3",
|
||||
"pylint-plugin-utils==0.6",
|
||||
"wget",
|
||||
"filelock",
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.utils import relpath
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
@@ -18,132 +14,6 @@ def test_diff_no_changes(tmp_dir, scm, dvc):
|
||||
assert dvc.params.diff() == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("commit_last", [True, False])
|
||||
def test_diff_add_similar_files(tmp_dir, scm, dvc, commit_last):
|
||||
if commit_last:
|
||||
last_commit_msg = "commit #2"
|
||||
a_rev = "HEAD~1"
|
||||
else:
|
||||
last_commit_msg = None
|
||||
a_rev = "HEAD"
|
||||
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir": {"file": "text1", "subdir": {"file2": "text2"}}},
|
||||
commit="commit #1",
|
||||
)
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir2": {"file": "text1", "subdir": {"file2": "text2"}}},
|
||||
commit=last_commit_msg,
|
||||
)
|
||||
assert dvc.diff(a_rev) == {
|
||||
"added": [
|
||||
{
|
||||
"path": os.path.join("dir2", ""),
|
||||
"hash": "cb58ee07cb01044db229e4d6121a0dfc.dir",
|
||||
},
|
||||
{
|
||||
"path": os.path.join("dir2", "file"),
|
||||
"hash": "cef7ccd89dacf1ced6f5ec91d759953f",
|
||||
},
|
||||
{
|
||||
"path": os.path.join("dir2", "subdir", "file2"),
|
||||
"hash": "fe6123a759017e4a2af4a2d19961ed71",
|
||||
},
|
||||
],
|
||||
"deleted": [],
|
||||
"modified": [],
|
||||
"renamed": [],
|
||||
"not in cache": [],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("commit_last", [True, False])
|
||||
def test_diff_rename_folder(tmp_dir, scm, dvc, commit_last):
|
||||
if commit_last:
|
||||
last_commit_msg = "commit #2"
|
||||
a_rev = "HEAD~1"
|
||||
else:
|
||||
last_commit_msg = None
|
||||
a_rev = "HEAD"
|
||||
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir": {"file": "text1", "subdir": {"file2": "text2"}}},
|
||||
commit="commit #1",
|
||||
)
|
||||
(tmp_dir / "dir").replace(tmp_dir / "dir2")
|
||||
tmp_dir.dvc_add("dir2", commit=last_commit_msg)
|
||||
assert dvc.diff(a_rev) == {
|
||||
"added": [],
|
||||
"deleted": [],
|
||||
"modified": [],
|
||||
"renamed": [
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", ""),
|
||||
"new": os.path.join("dir2", ""),
|
||||
},
|
||||
"hash": "cb58ee07cb01044db229e4d6121a0dfc.dir",
|
||||
},
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", "file"),
|
||||
"new": os.path.join("dir2", "file"),
|
||||
},
|
||||
"hash": "cef7ccd89dacf1ced6f5ec91d759953f",
|
||||
},
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", "subdir", "file2"),
|
||||
"new": os.path.join("dir2", "subdir", "file2"),
|
||||
},
|
||||
"hash": "fe6123a759017e4a2af4a2d19961ed71",
|
||||
},
|
||||
],
|
||||
"not in cache": [],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("commit_last", [True, False])
|
||||
def test_diff_rename_file(tmp_dir, scm, dvc, commit_last):
|
||||
if commit_last:
|
||||
last_commit_msg = "commit #2"
|
||||
a_rev = "HEAD~1"
|
||||
else:
|
||||
last_commit_msg = None
|
||||
a_rev = "HEAD"
|
||||
|
||||
paths = tmp_dir.gen(
|
||||
{"dir": {"file": "text1", "subdir": {"file2": "text2"}}}
|
||||
)
|
||||
tmp_dir.dvc_add(paths, commit="commit #1")
|
||||
(tmp_dir / "dir" / "file").replace(tmp_dir / "dir" / "subdir" / "file3")
|
||||
|
||||
tmp_dir.dvc_add(paths, commit=last_commit_msg)
|
||||
assert dvc.diff(a_rev) == {
|
||||
"added": [],
|
||||
"deleted": [],
|
||||
"modified": [
|
||||
{
|
||||
"path": os.path.join("dir", ""),
|
||||
"hash": {
|
||||
"old": "cb58ee07cb01044db229e4d6121a0dfc.dir",
|
||||
"new": "a4ac9c339aacc60b6a3152e362c319c8.dir",
|
||||
},
|
||||
}
|
||||
],
|
||||
"renamed": [
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", "file"),
|
||||
"new": os.path.join("dir", "subdir", "file3"),
|
||||
},
|
||||
"hash": "cef7ccd89dacf1ced6f5ec91d759953f",
|
||||
}
|
||||
],
|
||||
"not in cache": [],
|
||||
}
|
||||
|
||||
|
||||
def test_diff(tmp_dir, scm, dvc):
|
||||
tmp_dir.gen("params.yaml", "foo: bar")
|
||||
dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True)
|
||||
|
||||
@@ -708,7 +708,7 @@ def test_show_from_subdir(tmp_dir, dvc, caplog):
|
||||
with subdir.chdir(), caplog.at_level(logging.INFO, "dvc"):
|
||||
assert main(["plots", "show", "metric.json"]) == 0
|
||||
|
||||
assert f"file://{str(subdir)}" in caplog.text
|
||||
assert subdir.as_uri() in caplog.text
|
||||
assert (subdir / "plots.html").exists()
|
||||
|
||||
|
||||
|
||||
@@ -423,3 +423,175 @@ def test_targets_single_file_in_dir_with_file(tmp_dir, scm, dvc):
|
||||
"not in cache": [],
|
||||
"renamed": [],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("commit_last", [True, False])
|
||||
def test_diff_add_similar_files(tmp_dir, scm, dvc, commit_last):
|
||||
if commit_last:
|
||||
last_commit_msg = "commit #2"
|
||||
a_rev = "HEAD~1"
|
||||
else:
|
||||
last_commit_msg = None
|
||||
a_rev = "HEAD"
|
||||
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir": {"file": "text1", "subdir": {"file2": "text2"}}},
|
||||
commit="commit #1",
|
||||
)
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir2": {"file": "text1", "subdir": {"file2": "text2"}}},
|
||||
commit=last_commit_msg,
|
||||
)
|
||||
assert dvc.diff(a_rev) == {
|
||||
"added": [
|
||||
{
|
||||
"path": os.path.join("dir2", ""),
|
||||
"hash": "cb58ee07cb01044db229e4d6121a0dfc.dir",
|
||||
},
|
||||
{
|
||||
"path": os.path.join("dir2", "file"),
|
||||
"hash": "cef7ccd89dacf1ced6f5ec91d759953f",
|
||||
},
|
||||
{
|
||||
"path": os.path.join("dir2", "subdir", "file2"),
|
||||
"hash": "fe6123a759017e4a2af4a2d19961ed71",
|
||||
},
|
||||
],
|
||||
"deleted": [],
|
||||
"modified": [],
|
||||
"renamed": [],
|
||||
"not in cache": [],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("commit_last", [True, False])
|
||||
def test_diff_rename_folder(tmp_dir, scm, dvc, commit_last):
|
||||
if commit_last:
|
||||
last_commit_msg = "commit #2"
|
||||
a_rev = "HEAD~1"
|
||||
else:
|
||||
last_commit_msg = None
|
||||
a_rev = "HEAD"
|
||||
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir": {"file": "text1", "subdir": {"file2": "text2"}}},
|
||||
commit="commit #1",
|
||||
)
|
||||
(tmp_dir / "dir").replace(tmp_dir / "dir2")
|
||||
tmp_dir.dvc_add("dir2", commit=last_commit_msg)
|
||||
assert dvc.diff(a_rev) == {
|
||||
"added": [],
|
||||
"deleted": [],
|
||||
"modified": [],
|
||||
"renamed": [
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", ""),
|
||||
"new": os.path.join("dir2", ""),
|
||||
},
|
||||
"hash": "cb58ee07cb01044db229e4d6121a0dfc.dir",
|
||||
},
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", "file"),
|
||||
"new": os.path.join("dir2", "file"),
|
||||
},
|
||||
"hash": "cef7ccd89dacf1ced6f5ec91d759953f",
|
||||
},
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", "subdir", "file2"),
|
||||
"new": os.path.join("dir2", "subdir", "file2"),
|
||||
},
|
||||
"hash": "fe6123a759017e4a2af4a2d19961ed71",
|
||||
},
|
||||
],
|
||||
"not in cache": [],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("commit_last", [True, False])
|
||||
def test_diff_rename_file(tmp_dir, scm, dvc, commit_last):
|
||||
if commit_last:
|
||||
last_commit_msg = "commit #2"
|
||||
a_rev = "HEAD~1"
|
||||
else:
|
||||
last_commit_msg = None
|
||||
a_rev = "HEAD"
|
||||
|
||||
paths = tmp_dir.gen(
|
||||
{"dir": {"file": "text1", "subdir": {"file2": "text2"}}}
|
||||
)
|
||||
tmp_dir.dvc_add(paths, commit="commit #1")
|
||||
(tmp_dir / "dir" / "file").replace(tmp_dir / "dir" / "subdir" / "file3")
|
||||
|
||||
tmp_dir.dvc_add(paths, commit=last_commit_msg)
|
||||
assert dvc.diff(a_rev) == {
|
||||
"added": [],
|
||||
"deleted": [],
|
||||
"modified": [
|
||||
{
|
||||
"path": os.path.join("dir", ""),
|
||||
"hash": {
|
||||
"old": "cb58ee07cb01044db229e4d6121a0dfc.dir",
|
||||
"new": "a4ac9c339aacc60b6a3152e362c319c8.dir",
|
||||
},
|
||||
}
|
||||
],
|
||||
"renamed": [
|
||||
{
|
||||
"path": {
|
||||
"old": os.path.join("dir", "file"),
|
||||
"new": os.path.join("dir", "subdir", "file3"),
|
||||
},
|
||||
"hash": "cef7ccd89dacf1ced6f5ec91d759953f",
|
||||
}
|
||||
],
|
||||
"not in cache": [],
|
||||
}
|
||||
|
||||
|
||||
def test_rename_multiple_files_same_hashes(tmp_dir, scm, dvc):
|
||||
"""Test diff by renaming >=2 instances of file with same hashes.
|
||||
|
||||
DVC should be able to detect that they are renames, and should not include
|
||||
them in either of the `added` or the `deleted` section.
|
||||
"""
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir": {"foo": "foo", "subdir": {"foo": "foo"}}}, commit="commit #1",
|
||||
)
|
||||
remove(tmp_dir / "dir")
|
||||
# changing foo and subdir/foo to bar and subdir/bar respectively
|
||||
tmp_dir.dvc_gen(
|
||||
{"dir": {"bar": "foo", "subdir": {"bar": "foo"}}}, commit="commit #2"
|
||||
)
|
||||
assert dvc.diff("HEAD~") == {
|
||||
"added": [],
|
||||
"deleted": [],
|
||||
"modified": [
|
||||
{
|
||||
"hash": {
|
||||
"new": "31b36b3ea5f4485e27f10578c47183b0.dir",
|
||||
"old": "c7684c8b3b0d28cf80d5305e2d856bfc.dir",
|
||||
},
|
||||
"path": os.path.join("dir", ""),
|
||||
}
|
||||
],
|
||||
"not in cache": [],
|
||||
"renamed": [
|
||||
{
|
||||
"hash": "acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
"path": {
|
||||
"new": os.path.join("dir", "bar"),
|
||||
"old": os.path.join("dir", "foo"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"hash": "acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
"path": {
|
||||
"new": os.path.join("dir", "subdir", "bar"),
|
||||
"old": os.path.join("dir", "subdir", "foo"),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -349,3 +349,23 @@ def test_fs_ls_with_etag(dvc, cloud):
|
||||
fs.info(path_info.replace(path=details["name"]))["etag"]
|
||||
== details["etag"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cloud", [pytest.lazy_fixture("azure"), pytest.lazy_fixture("gs")]
|
||||
)
|
||||
def test_fs_fsspec_path_management(dvc, cloud):
|
||||
cloud.gen({"foo": "foo", "data": {"bar": "bar", "baz": {"foo": "foo"}}})
|
||||
fs = get_cloud_fs(dvc, **cloud.config)
|
||||
|
||||
root = cloud.parents[len(cloud.parents) - 1]
|
||||
bucket_details = fs.info(root)
|
||||
|
||||
# special conditions: name always points to the bucket name
|
||||
assert bucket_details["name"] == root.bucket
|
||||
assert bucket_details["type"] == "directory"
|
||||
|
||||
data = cloud / "data"
|
||||
data_details = fs.info(data)
|
||||
assert data_details["name"].rstrip("/") == data.path
|
||||
assert data_details["type"] == "directory"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.cli import parse_args
|
||||
from dvc.command.plots import CmdPlotsDiff, CmdPlotsShow
|
||||
@@ -116,12 +120,11 @@ def test_plots_diff_open(tmp_dir, dvc, mocker, caplog):
|
||||
)
|
||||
|
||||
assert cmd.run() == 0
|
||||
mocked_open.assert_called_once_with("plots.html")
|
||||
|
||||
expected_url = f"file://{tmp_dir / 'plots.html'}"
|
||||
expected_url = posixpath.join(tmp_dir.as_uri(), "plots.html")
|
||||
assert expected_url in caplog.text
|
||||
|
||||
mocked_open.assert_called_once_with(expected_url)
|
||||
|
||||
|
||||
def test_plots_diff_open_failed(tmp_dir, dvc, mocker, caplog):
|
||||
mocked_open = mocker.patch("webbrowser.open", return_value=False)
|
||||
@@ -132,12 +135,36 @@ def test_plots_diff_open_failed(tmp_dir, dvc, mocker, caplog):
|
||||
)
|
||||
|
||||
assert cmd.run() == 1
|
||||
|
||||
expected_url = f"file://{tmp_dir / 'plots.html'}"
|
||||
mocked_open.assert_called_once_with(expected_url)
|
||||
mocked_open.assert_called_once_with("plots.html")
|
||||
|
||||
error_message = "Failed to open. Please try opening it manually."
|
||||
expected_url = posixpath.join(tmp_dir.as_uri(), "plots.html")
|
||||
|
||||
assert caplog.record_tuples == [
|
||||
("dvc.command.plots", logging.INFO, expected_url),
|
||||
("dvc.command.plots", logging.ERROR, error_message),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"output, expected_url_path",
|
||||
[
|
||||
("plots file with spaces.html", "plots%20file%20with%20spaces.html"),
|
||||
(os.path.join("dir", "..", "plots.html"), "plots.html"),
|
||||
],
|
||||
ids=["quote", "resolve"],
|
||||
)
|
||||
def test_plots_path_is_quoted_and_resolved_properly(
|
||||
tmp_dir, dvc, mocker, caplog, output, expected_url_path
|
||||
):
|
||||
cli_args = parse_args(
|
||||
["plots", "diff", "--targets", "datafile", "--out", output]
|
||||
)
|
||||
cmd = cli_args.func(cli_args)
|
||||
mocker.patch(
|
||||
"dvc.repo.plots.diff.diff", return_value={"datafile": "filledtemplate"}
|
||||
)
|
||||
|
||||
assert cmd.run() == 0
|
||||
expected_url = posixpath.join(tmp_dir.as_uri(), expected_url_path)
|
||||
assert expected_url in caplog.text
|
||||
|
||||
@@ -248,3 +248,31 @@ def test_info_with_debug_loglevel_shows_no_datetime(caplog, dt):
|
||||
logger.info("message")
|
||||
|
||||
assert "message" == formatter.format(caplog.records[0])
|
||||
|
||||
|
||||
def test_add_existing_level(caplog, dt):
|
||||
# Common pattern to configure logging level in external libraries
|
||||
# eg:
|
||||
# https://github.com/bokeh/bokeh/blob/04bb30fef2e72e64baaa8b2f330806d5bfdd3b11/
|
||||
# bokeh/util/logconfig.py#L79-L85
|
||||
TRACE2 = 4
|
||||
logging.addLevelName(TRACE2, "TRACE2")
|
||||
logging.TRACE2 = TRACE2
|
||||
|
||||
dvc.logger.addLoggingLevel("TRACE2", 2)
|
||||
|
||||
# DVC sets all expected entrypoints, but doesn't override the level
|
||||
assert logging.TRACE2 == 4
|
||||
assert hasattr(logging, "trace2")
|
||||
assert hasattr(logger, "trace2")
|
||||
assert logging.getLevelName("TRACE2") == 4
|
||||
|
||||
# The TRACE2 logging level uses the original, higher logging level
|
||||
with caplog.at_level(logging.TRACE2, logger="dvc"):
|
||||
logger.trace2("TRACE2")
|
||||
assert len(caplog.records) == 1
|
||||
|
||||
(record,) = caplog.records
|
||||
assert record.levelno == 4
|
||||
assert record.levelname == "TRACE2"
|
||||
assert record.message == "TRACE2"
|
||||
|
||||
Reference in New Issue
Block a user