Compare commits

...
908 Commits
Author SHA1 Message Date
Ruslan Kuprieiev a42d4f2c90 dvc: bump to 0.82.3 2020-01-24 23:36:32 +02:00
Ruslan Kuprieiev 4d5faab19c Merge pull request #3213 from fabiosantoscode/feature/3121-push-no-remote-specified-hint
push: improve 'no remote specified' hint
2020-01-24 22:46:55 +02:00
Ruslan Kuprieiev 0bd0c7a61e Merge pull request #3232 from iterative/2865
Remove usage of google-api-python-client deps
2020-01-24 18:02:24 +02:00
Fábio Santos 2fb6b338bd move message creation outside of exception 2020-01-24 15:13:20 +00:00
Ruslan Kuprieiev c2def08551 Merge pull request #3228 from efiop/3107
install: post-checkout: run only on branches/tags
2020-01-24 17:02:53 +02:00
Max Risuhin 10399e8eb8 Remove usage of google-api-python-client deps 2020-01-24 05:51:33 -08:00
Ruslan Kuprieiev 4457df334b Merge pull request #3225 from pared/2647_no_cache_external_dir
remote: verify cache exists on dir checksum
2020-01-24 15:21:36 +02:00
Paweł Redzyński 6557fed55d remote: verify cache exists on dir checksum 2020-01-24 13:37:33 +01:00
Ruslan Kuprieiev a65b6c7a2b Merge pull request #3231 from pared/link_fixes
some doc link fixes
2020-01-24 13:33:16 +02:00
Paweł Redzyński 44b7216861 some doc link fixes 2020-01-24 11:57:17 +01:00
Ruslan Kuprieiev 6718622549 Merge pull request #3227 from Suor/git-pull
erepo: pull each time
2020-01-24 10:14:43 +02:00
Ruslan Kuprieiev 2f1224b27b setup: update pydrive2 version 2020-01-24 09:47:52 +02:00
Ruslan Kuprieiev a11b5f4377 install: post-checkout: run only on branches/tags
We shouldn't try to automatically checkout in detached HEAD state, as we
might break the rebase process.

Fixes #3107
2020-01-23 23:10:41 +02:00
Alexander Schepanovski 7168a9c50a erepo: pull each time
Fixes #3062.
2020-01-23 23:09:57 +07:00
Ruslan Kuprieiev 1bc39e7e99 dvc: bump to 0.82.2 2020-01-23 16:36:42 +02:00
Ruslan Kuprieiev 4f61932c71 Merge pull request #3224 from pared/fix_import_url_test
import-url: test: prevent in-repo testing
2020-01-23 15:59:20 +02:00
Fábio Santos 84999f590e convert to bool 2020-01-23 12:24:30 +00:00
Fábio Santos 2742a58226 move remote checking option to within NoRemoteError 2020-01-23 12:23:16 +00:00
Paweł Redzyński 48f8f2402a import-url: test: prevent in-repo testing 2020-01-23 12:40:21 +01:00
Ruslan Kuprieiev bbb3a831b5 Merge pull request #3216 from MaxRis/3157
Update Google Project credentials to run tests
2020-01-23 13:25:25 +02:00
Ruslan Kuprieiev 1a7eb8fdec Merge pull request #3222 from skshetry/fallback-external-repo
api: fallback to external_repo if not a DVC repository
2020-01-23 13:24:33 +02:00
Ruslan Kuprieiev a0419173ba Merge pull request #3088 from efiop/3080
dvc: add sanity checks for hard/symlinks
2020-01-23 11:48:19 +02:00
Saugat Pachhai 2e0b143856 api: use current directory for repo if not specified 2020-01-23 15:28:22 +05:45
Saugat Pachhai 003b5c392a api: fallback to external_repo on error 2020-01-23 14:44:38 +05:45
Ruslan Kuprieiev 49039968f7 Merge pull request #3208 from fabiosantoscode/feature/3198-remove-spy-method
tests: replace spy() with pytest-mock spy
2020-01-23 10:52:43 +02:00
Ruslan Kuprieiev ad6e58a457 Merge pull request #3223 from efiop/3107
hooks: checkout: fix incorrect dvc repo detection
2020-01-23 10:41:27 +02:00
Ruslan Kuprieiev f6f931be87 hooks: checkout: fix incorrect dvc repo detection
When running from non-root directory `git ls-files .dvc` will treat it
as a relative path to the current location, so if you run `git checkout`
from a directory that is not a git/dvc root, our hook will think that
it is being run in a non-dvc repo and will do nothing. To fix that, need
to use `--full-name` flag.
2020-01-23 10:21:26 +02:00
Ruslan Kuprieiev da6399dc3c Merge pull request #3220 from jorgeorpinel/master
get: copy edits aroud --show-url, "dvc", et al.
2020-01-23 09:14:00 +02:00
Jorge Orpinel 69c1aa5bbf dvc: revert capitalization of logger.exception strings
per https://github.com/iterative/dvc/pull/3220#pullrequestreview-347083819
2020-01-23 00:50:24 -06:00
Jorge Orpinel 6364c9ee62 get: refine --show-url description
per https://github.com/iterative/dvc/pull/3220#pullrequestreview-347038086
2020-01-23 00:39:10 -06:00
Jorge Orpinel d06bd6abd8 tests: match recent commits and
other misc. language improvements
2020-01-23 00:35:15 -06:00
Jorge Orpinel ec0c39a28e term: review capitalization and backquotes around "dvc"
rel: https://github.com/iterative/dvc/pull/3156#discussion_r369897073
2020-01-22 20:27:01 -06:00
Jorge Orpinel ee0193ccd3 get: rewrite --show-url description
per https://github.com/iterative/dvc.org/pull/936#discussion_r368680473
2020-01-22 20:12:19 -06:00
Jorge Orpinel ae3455141c get: language feedback on --show-url PR
per https://github.com/iterative/dvc/pull/3156#pullrequestreview-345562608
2020-01-22 19:47:57 -06:00
Ruslan Kuprieiev e9caa44b55 Merge pull request #3214 from jorgeorpinel/master
get/imp: update help output descriptions
2020-01-22 23:03:07 +02:00
Max Risuhin cb38b1a0f1 Update Google Project credentials to run tests 2020-01-22 12:19:21 -08:00
Ruslan Kuprieiev 000c524983 Merge pull request #3212 from casperdcl/snap-deploy
another attempt at travis dpl snap
2020-01-22 21:01:56 +02:00
Jorge Orpinel 6e61236fb7 get/imp: update help output descriptions
per https://github.com/iterative/dvc/issues/3196#issuecomment-577306219
2020-01-22 12:00:06 -06:00
Fábio Santos fc92266114 address comments 2020-01-22 17:31:46 +00:00
Fábio Santos abbe29e58e push: improve 'no remote specified' hint
Fixes #3121
2020-01-22 17:09:00 +00:00
Ruslan Kuprieiev d863c46859 dvc: add sanity checks for hard/symlinks
Fixed #3080
2020-01-22 18:47:37 +02:00
Casper da Costa-Luis 66ad722964 Revert "debugging (to revert)"
This reverts commit 555258c75d.
2020-01-22 16:28:43 +00:00
Casper da Costa-Luis 7526eb1a2f fix before_install checks 2020-01-22 16:08:24 +00:00
Casper da Costa-Luis f02c804cad tidy up 2020-01-22 16:04:42 +00:00
Casper da Costa-Luis a4a31b3113 fix missing env vars 2020-01-22 16:00:48 +00:00
Casper da Costa-Luis 555258c75d debugging (to revert) 2020-01-22 15:52:33 +00:00
Casper da Costa-Luis ba2c84bade attempt to fix travis snap dpl 2020-01-22 15:23:57 +00:00
Fábio Santos e41aa00c20 convert tests to pytest function tests 2020-01-22 14:04:52 +00:00
Ruslan Kuprieiev b99c5e4aad Merge pull request #3211 from skshetry/fix-brancher-noscm
brancher: do not get tree if there's no revs
2020-01-22 14:09:53 +02:00
Saugat Pachhai 5f8b1f7f80 brancher: do not get tree if there's no revs 2020-01-22 17:22:54 +05:45
Ruslan Kuprieiev d2d887bb50 Merge pull request #3200 from pared/2676_trust_checksum
fetch: trust remote checksums
2020-01-22 12:36:29 +02:00
Paweł Redzyński 482dfaf998 data cloud: refactor verify checksums test 2020-01-22 10:54:07 +01:00
pawel e36cc8c9d2 data cloud: add explanaiton to pulled checksums save 2020-01-22 10:54:07 +01:00
pawel a148d16a95 data cloud: verify is config option now 2020-01-22 10:54:07 +01:00
pawel f4e0079ec1 data sync: add command unit test 2020-01-22 10:54:07 +01:00
pawel b451de5496 remote: tests: refactor verify test 2020-01-22 10:54:07 +01:00
pawel ef1b06ddf9 test_remote: use mocker.spy instead of spy 2020-01-22 10:54:07 +01:00
pawel 0b1d1f5ce4 data cloud: pull: trust remotes md5 on proper flag 2020-01-22 10:54:07 +01:00
Ruslan Kuprieiev d942542c84 tests: fix index.remove misuse 2020-01-22 10:53:48 +02:00
Ruslan Kuprieiev a99fa4e4e5 Merge pull request #3209 from casperdcl/snap-deploy
fix snap stable & edge release errors
2020-01-22 08:26:02 +02:00
Casper da Costa-Luis a6d55e3252 ensure correct snap version dirtyness 2020-01-21 22:38:09 +00:00
Casper da Costa-Luis 8a560ecaba pin snap version to base version 2020-01-21 22:31:40 +00:00
Fábio Santos bdf665a34a don't use patch.object() 2020-01-21 20:52:48 +00:00
Casper da Costa-Luis c13b24fb40 fix master/edge release provider argument conflicts 2020-01-21 19:50:22 +00:00
Ruslan Kuprieiev aa86eab2cc Merge pull request #3206 from Suor/tests
test: add git_dir
2020-01-21 20:19:35 +02:00
Fábio Santos 5dfcda54f9 tests: replace spy() with pytest-mock spy
Fixes #3198
2020-01-21 14:47:49 +00:00
Alexander Schepanovski 5af7a9d850 test: add git_dir 2020-01-21 20:56:04 +07:00
Ruslan Kuprieiev 6958901d98 Merge pull request #3204 from efiop/3087
erepo: fix DependencyREPO schema
2020-01-21 15:31:22 +02:00
Ruslan Kuprieiev cb64491512 build: don't upgrade pip 2020-01-21 14:41:59 +02:00
Ruslan Kuprieiev 8d8a1c0f0c travis: don't upgrade pip 2020-01-21 14:14:54 +02:00
Ruslan Kuprieiev 7c9e0345a0 Merge pull request #3205 from skshetry/fix-spell
exception: fix spelling
2020-01-21 13:56:59 +02:00
Saugat Pachhai df1ba08b94 exception: fix spelling 2020-01-21 17:24:21 +05:45
Ruslan Kuprieiev 2305549460 erepo: fix DependencyREPO schema
URL should be required and rev or rev_lock should be optional.

Related to #3087
2020-01-21 12:58:33 +02:00
Ruslan Kuprieiev 10a7b9d9a8 readme: add a release label
To avoid accidentally looking like an unstable project.
2020-01-21 10:03:16 +02:00
Ruslan Kuprieiev 04161ad6f2 dvc: bump to 0.82.1 2020-01-21 00:41:45 +02:00
Ruslan Kuprieiev 503401dde5 Merge pull request #3202 from efiop/flatten
dvc: replace flatten_dict with flatten_json
2020-01-21 00:38:19 +02:00
Ruslan Kuprieiev 81029f8f75 Merge pull request #3201 from efiop/traverse_gdrive
gdrive: allow configuring no_traverse
2020-01-21 00:19:11 +02:00
Ruslan Kuprieiev 1eb82ac707 dvc: replace flatten_dict with flatten_json
flatten_json has a conda package, while flatten_dict doesn't and I'm not
willing to maintain yet another conda package :)
2020-01-21 00:14:22 +02:00
Ruslan Kuprieiev e848bcb9d6 gdrive: allow configuring no_traverse 2020-01-20 23:49:49 +02:00
Ruslan Kuprieiev 39bf7c0d43 Merge pull request #3199 from skshetry/remove-use-six
py2: remove use of `six`
2020-01-20 19:19:23 +02:00
Saugat Pachhai 8dd2dbb46e py2: remove use of six 2020-01-20 21:47:56 +05:45
Ruslan Kuprieiev 0be5046a72 Merge pull request #3197 from Suor/refactor-tmpdir
test: refactor tmp_dir/erepo_dir
2020-01-20 16:02:13 +02:00
Alexander Schepanovski 91e024c8fa test: do not commit copy.py to git 2020-01-20 20:35:10 +07:00
Ruslan Kuprieiev b2b3587c0b Merge pull request #3190 from Suor/refactor-erepo
external: refactor external_repo() and its users
2020-01-20 14:49:36 +02:00
Alexander Schepanovski 1d891a6e77 test: provide make_tmp_dir fixture 2020-01-20 11:57:54 +07:00
Alexander Schepanovski 4117206c67 test: drop repo_template fixture 2020-01-20 11:49:13 +07:00
Ruslan Kuprieiev ec2c8ad8bc Merge pull request #2956 from casperdcl/snap
add snap tests
2020-01-19 21:36:47 +02:00
Casper da Costa-Luis f80626b5d0 recompress conditional snap channel 2020-01-19 19:10:25 +00:00
Casper da Costa-Luis de358d661a explicitly spilt snap stable/edge deployments
- (re) fix #3152
2020-01-19 19:10:05 +00:00
Casper da Costa-Luis 22ed9cb5dc Remove travis-incompatible tests
- reverts
  + 205077e3a0.
  + 46e0616614.
2020-01-19 18:27:59 +00:00
Casper da Costa-Luis 205077e3a0 debugging 2020-01-19 18:19:01 +00:00
Casper da Costa-Luis 2fe9b3a967 connectivity debugging 2020-01-19 18:19:01 +00:00
Casper da Costa-Luis 5ac849377a minor comment update 2020-01-19 18:19:01 +00:00
Casper da Costa-Luis 0a0b4e019e debugging 2020-01-19 18:19:01 +00:00
Casper da Costa-Luis d020c3d8b0 test snaps even where tag is undefined 2020-01-19 18:19:01 +00:00
Casper da Costa-Luis b756f77787 revert whitespace changes, but keep stderr errors 2020-01-19 18:19:01 +00:00
Casper da Costa-Luis 46e0616614 promote snap tests for debugging 2020-01-19 18:19:01 +00:00
Casper da Costa-Luis 39ef3dba1a add snap tests
- fixes #2954
2020-01-19 18:17:16 +00:00
Ruslan Kuprieiev 430b9bbdc0 dvc: bump to 0.82.0 2020-01-19 20:04:41 +02:00
Alexander Schepanovski 72812b519e api: fix get_url() non-dvc repo error message 2020-01-19 23:01:53 +07:00
Alexander Schepanovski eb97e0b393 external: do not fail with no remote if cache is available 2020-01-19 21:39:32 +07:00
Alexander Schepanovski ee022eada0 api: adjust get_url requiring dvc repo to the new reality 2020-01-19 21:38:37 +07:00
Alexander Schepanovski 9de0643d97 api: allow rev on local repos 2020-01-19 21:38:30 +07:00
Alexander Schepanovski 033ef88a4e external: refactor external_repo() and its users
Some things are fixed along the way:
- no cache shared between incompatible funcs
- some incosistent exceptions are now consistent
- import updates for git files now works both for dvc and git repos
- "auto-generated-upstream already exists" fixed
- dvc.api.open()/read() works with git repos now

Fixes #3124, #2976. Closes #3172.
2020-01-19 21:38:06 +07:00
Ruslan Kuprieiev 7d65002bf0 Merge pull request #3178 from efiop/1843
logger: remote: use lazy formatting
2020-01-19 12:16:01 +02:00
Ruslan Kuprieiev 19acded31f logger: utils: fs: use lazy formatting 2020-01-19 11:53:29 +02:00
Ruslan Kuprieiev 65c0c7e60d logger: state: use lazy formatting 2020-01-19 11:53:29 +02:00
Ruslan Kuprieiev c2dae4b49c logger: remote: use lazy formatting
Part of #1843

As our previous investigations showed, path stringification and stuff
like relpath are taking a very large chunk of time when working with
giant directories. This patch removes `format()`s and uses lazy
formatting provided by logger instead, so stuff like path_info is not
stringified until actually needed (e.g. on `-v`).
2020-01-19 11:53:29 +02:00
Ruslan Kuprieiev 6f8ac0c1b1 logger: support {} formatting 2020-01-19 11:53:29 +02:00
Ruslan Kuprieiev 3987086275 Merge pull request #3169 from fabiosantoscode/feature/2963-dvc-pull-of-s3-remote-with-https-url
pull: treat HTTP redirects without Location header as error
2020-01-19 10:00:05 +02:00
Ruslan Kuprieiev 74c5af1a84 Merge pull request #3130 from jorgeorpinel/master
api: better code documentation
2020-01-19 09:05:52 +02:00
Ruslan Kuprieiev cf013576d4 Merge pull request #3120 from sharidas/no_remote_issue
Fix import issue without remote config in the target
2020-01-19 09:03:38 +02:00
Ruslan Kuprieiev 1fb79fa6e9 Merge pull request #3188 from mroutis/fix-3184
completion: get --show-url
2020-01-19 08:55:48 +02:00
Ruslan Kuprieiev 3c73e94b4e Merge pull request #3195 from mroutis/fix-boto3-import
s3: import boto3 inside _copy instead of module level
2020-01-19 08:29:52 +02:00
Sujith H 1b7eda2745 Fix import issue without remote config in the target
Fix the import issue when target does not have
remote config.

Signed-off-by: Sujith H <[email protected]>
2020-01-19 11:42:05 +05:30
Mr. Outis de1de68f90 s3: import boto3 inside _copy instead of module level
Kudos to @skshetry for bringing this out during a review
2020-01-18 23:01:50 -06:00
Mr. Outis 6ad55ec3b0 completion: get --show-url 2020-01-17 18:50:08 -06:00
Jorge Orpinel 2b818e203f api: note get_url doesn't chekc file existence
per https://github.com/iterative/dvc/pull/3130#discussion_r367359439
2020-01-17 17:40:02 -06:00
Jorge Orpinel 4d05ad2789 Merge branch 'master' of github.com:iterative/dvc 2020-01-17 17:39:51 -06:00
Ruslan Kuprieiev 7f5e4a9fe0 Merge pull request #3187 from iterative/pull-3186-restyled
Restyle s3: copy large objects without chunking
2020-01-17 20:38:36 +02:00
Restyled.io c3628c87d5 Restyled by black 2020-01-17 18:00:19 +00:00
Andy Sarroff 0bd433d153 s3: copy large objects without chunking
When executing `boto3.s3.Client.Copy`, large files are chunked into
multiple parts. The default chunk threshold and size is 8MB. This
caused an issue when caching an object larger than 8M, where the
original object had not been uploaded in multiple parts. The original
object and the cached object would have different ETags, causing an
exception. This fixes the issue by dynamically setting the chunk
threshold to be one byte larger than the size of the original object
therefore avoiding multipart copies.

Fixes #3174
2020-01-17 12:00:42 -05:00
Ruslan Kuprieiev 4d49b6ba1d Merge pull request #3170 from pared/3105
utils: resolve_output: normpath before extracting basename
2020-01-17 12:41:25 +02:00
Paweł Redzyński cf08529362 utils: add tests for resolve out 2020-01-17 09:51:07 +01:00
Paweł Redzyński 34d3261e58 utils: resolve_output: normpath before extracting basename 2020-01-17 09:29:52 +01:00
Ruslan Kuprieiev bdd1d61803 Merge pull request #3156 from skshetry/get-url-from-cache
get: implement --show-url to display only url/path to remote
2020-01-17 10:15:06 +02:00
Ruslan Kuprieiev 6ebd053c0f Merge pull request #3165 from mroutis/remove-py2-compat
setup: remove py2 backports
2020-01-17 04:28:22 +02:00
Ruslan Kuprieiev 422ff579f4 Merge pull request #3173 from casperdcl/snap-deploy
minor comments
2020-01-17 01:11:49 +02:00
Casper da Costa-Luis d34aa4471b minor comments
- explains review concerns in #3158
2020-01-16 20:36:42 +00:00
Fábio Santos df22dd4def specifically look for Location header in response 2020-01-16 17:58:44 +00:00
Saugat PachhaiandPaweł Redzyński 98f6d56e8c test: refactor, apply suggestion from @pared
Co-Authored-By: Paweł Redzyński <[email protected]>
2020-01-16 22:59:58 +05:45
Saugat Pachhai 02a73a0d9b unit: test: check output of get --show-url command 2020-01-16 22:52:35 +05:45
Ruslan Kuprieiev 74bbc3bc04 Merge pull request #3135 from rxxg/master
Perf improvement for file copies
2020-01-16 18:46:58 +02:00
rxxg 083a630b44 Fallback to standard Python code in case of missing packages 2020-01-16 17:08:07 +01:00
Saugat Pachhai 50206d44dc test: fix test related to path on windows
On windows, `str(exc_info)` is returning escaped path, eg:
'<ExceptionInfo UrlNotDvcRepoError("URL \'C:\\\\Users\\\\travis\\\\AppData\\\\Local\\\\Temp\\\\pytest-of-travis\\\\pytest-0\\\\popen-gw2\\\\test_get_url_git_only_repo_thr0\' is not a dvc repository.") tblen=2>'

This commit just replaces the double slashes with single one. Bit of a hack, I know.
2020-01-16 21:06:52 +05:45
Ruslan Kuprieiev d081627501 Merge pull request #3150 from fabiosantoscode/feature/2959-status-of-imported-files
status: implement support for imported files
2020-01-16 17:11:31 +02:00
Fábio Santos fcb7681683 take exception transformation into account 2020-01-16 14:49:51 +00:00
Fábio Santos cd3ef1850d simplify control flow 2020-01-16 14:44:09 +00:00
Fábio Santos c5eb13c114 update redirect check 2020-01-16 14:34:09 +00:00
Fábio Santos d3f925ae63 add missing return 2020-01-16 14:30:03 +00:00
Fábio Santos 968e74890d pull: treat HTTP redirects without Location header as error
Fixes #2963
2020-01-16 14:21:59 +00:00
Saugat Pachhai aa7119a741 get: test get_url raises exception if not a dvc repo 2020-01-16 19:28:14 +05:45
Saugat Pachhai f130e18e2c get: use new exception from public api 2020-01-16 19:28:14 +05:45
Saugat Pachhai fadf60eea6 api: throw exception by get_url if not a dvc repo 2020-01-16 19:28:14 +05:45
Saugat Pachhai eb252e055b get: split command for getting url and getting files from url functions 2020-01-16 19:28:14 +05:45
Saugat Pachhai 4509f82bc3 get: implement --show-url to display only url/path to remote 2020-01-16 19:28:14 +05:45
Fábio Santos a9bdaec63c rework code path; remove extraneous context generator 2020-01-16 13:37:13 +00:00
Fábio Santos 5883d3dabf reorder imports 2020-01-16 12:54:05 +00:00
Fábio Santos a0bdaa4c9e use checksum facilities 2020-01-16 12:51:20 +00:00
Ruslan Kuprieiev 1f1fb03138 Merge pull request #3142 from pared/remove_fixtures
conftest: remove dvc_repo, erepo, git, git_erepo
2020-01-16 13:20:45 +02:00
Ruslan Kuprieiev d160f83717 Merge pull request #3017 from pared/errno_24_message
point to troubleshooting guide on too many open files error
2020-01-16 12:56:14 +02:00
Paweł Redzyński 36c64c66db test_get_from_non_dvc_master: add comment about git logs 2020-01-16 11:49:03 +01:00
Ruslan Kuprieiev d1f172f332 Merge pull request #3158 from casperdcl/snap-deploy
major deploy tidy
2020-01-16 12:23:22 +02:00
Paweł Redzyński 18446996ab get: tes_get_from_non_dvc_master: capture git logs at INFO 2020-01-16 10:43:48 +01:00
pawel c2f5e35f1d conftest: remove repo_dir 2020-01-16 10:43:48 +01:00
pawel a4b55a00ec conftest: remove dvc_repo, erepo, git, git_erepo 2020-01-16 10:43:48 +01:00
Ruslan Kuprieiev 7d249d23e1 Merge pull request #3166 from mroutis/fix-3111
api: identify local repos with os.path.exists
2020-01-16 05:51:55 +02:00
Mr. Outis 388fcd6d32 api: identify local repos with os.path.exists
Close #3111
2020-01-15 21:31:09 -06:00
Mr. Outis 48326620ec setup: remove py2 backports 2020-01-15 19:13:01 -06:00
Ruslan Kuprieiev 6753e7b1e6 Merge pull request #3133 from JIoJIaJIu/core_checksum
remote: config checksum_jobs
2020-01-15 23:36:33 +02:00
Ruslan Kuprieiev ed0bc38e15 Merge pull request #3163 from MaxRis/3162
Fix gdrive multiple parents file list query
2020-01-15 23:32:57 +02:00
Max Risuhin 2d2ba0addf Fix gdrive multiple parents file list query 2020-01-15 13:10:55 -08:00
Ruslan Kuprieiev d2f81da696 Merge pull request #3161 from efiop/pydrive2
dvc: use pydrive2 instead of pydrive
2020-01-15 20:34:25 +02:00
Ruslan Kuprieiev 97a29a1f7e dvc: use pydrive2 instead of pydrive
PyDrive2 is our maintained fork.
2020-01-15 19:13:29 +02:00
Casper da Costa-Luis d8e6e34c34 minor use of new public API env var 2020-01-15 16:23:59 +00:00
Casper da Costa-Luis 4b16252c48 tidy deploy_condition 2020-01-15 15:41:02 +00:00
Casper da Costa-Luis 7b94181a0d deploy only on push 2020-01-15 15:26:29 +00:00
Casper da Costa-Luis d603048698 capitalisation 2020-01-15 15:20:09 +00:00
Casper da Costa-Luis 930b6cf45e fix pkg deploy condition 2020-01-15 15:16:29 +00:00
Casper da Costa-Luis 5bddfa7a92 major deploy tidy
- fixes #3152
2020-01-15 15:10:56 +00:00
Guro Bokum 4918c56ca3 remote: config checksum_jobs
Close #2920
2020-01-15 22:23:09 +08:00
pawel 0fcbfd04ce main: point to error.dvc.org after too many open files error 2020-01-15 15:14:24 +01:00
pawel d386237d86 main: format link to troubleshooting guide 2020-01-15 15:14:24 +01:00
pawel bc9a94b5ee point to troubleshooting guide on too many open files error 2020-01-15 15:14:24 +01:00
Fábio Santos 4ada384fcd remove trace statement 2020-01-15 12:43:14 +00:00
Fábio Santos 008a531535 support non-DVC repositories 2020-01-15 12:39:15 +00:00
rxxg e170852e97 Merge branch 'master' into master 2020-01-15 12:53:40 +01:00
rxxg 8fca139246 Use released version of speedcopy library 2020-01-15 12:50:43 +01:00
Fábio Santos 289797e7eb can now get whether an item changed, but not when an item changes from non-DVC to DVC 2020-01-15 11:33:26 +00:00
Fábio Santos 799d902c98 add test that checks that a file can be upgraded from git tracking to DVC tracking 2020-01-14 23:41:48 +00:00
Fábio Santos 1a26d3b60f Merge remote-tracking branch 'upstream/master' into feature/2959-status-of-imported-files 2020-01-14 23:08:43 +00:00
Ruslan Kuprieiev 0be0931fcd Merge pull request #3151 from efiop/3087
import: don't forget to set rev_lock for git files
2020-01-15 01:06:04 +02:00
Fábio Santos 3228b476fa compare checksums instead of file bytes 2020-01-14 22:58:11 +00:00
Ruslan Kuprieiev 410a8f8aeb travis: don't deploy snap on PRs 2020-01-15 00:55:34 +02:00
Ruslan Kuprieiev 7d6ecd1ccb import: don't forget to set rev_lock for git files
Related to #3087
2020-01-15 00:38:55 +02:00
Fábio Santos e5d35cb3ce reduce cognitive complexity of _local_status 2020-01-14 20:32:46 +00:00
Fábio Santos cfbdccdbed update test 2020-01-14 19:49:21 +00:00
Fábio Santos 8f464c2a07 status: don't clone more than once for plain files 2020-01-14 19:00:46 +00:00
Fábio Santos c9b0593220 status: enable status of files imported from remote git repos 2020-01-14 18:51:56 +00:00
Ruslan Kuprieiev b9e221a49c Merge pull request #3145 from iterative/efiop-patch-1
travis: build snap in parallel with other packages
2020-01-14 20:29:51 +02:00
Ruslan Kuprieiev 42784816c3 Merge pull request #3149 from skshetry/refactor-remote
test: use helper remotes in external repro
2020-01-14 20:27:25 +02:00
Fábio Santos f949b31976 status: implement support for imported files
Fixes #2959
2020-01-14 17:54:20 +00:00
Saugat Pachhai 7993600019 test: use helper remotes in external repro 2020-01-14 23:31:44 +05:45
Ruslan Kuprieiev fe6d51fa77 Merge pull request #3148 from skshetry/refactor-datacloud-clibase
test: use helper remotes in child classes of TestDataCloudCLIBase
2020-01-14 19:35:03 +02:00
Ruslan Kuprieiev b865a48b92 Merge pull request #3146 from pared/2896_fs_repo
fs: tests: remove repo_dir
2020-01-14 16:49:23 +02:00
Saugat Pachhai 45b97b14c4 test: use helper remotes in child of TestDataCloudCLIBase 2020-01-14 20:29:29 +05:45
Ruslan Kuprieiev 9c934c8abf Merge pull request #3147 from pared/2896_ssh_utils
ssh: test: unit, utils: test: unit: remove repo_dir
2020-01-14 16:38:08 +02:00
pawel cb5b5ed334 ssh: test: unit, utils: test: unit: remove repo_dir 2020-01-14 15:32:48 +01:00
Ruslan Kuprieiev b0f83f1486 travis: deploy snap on tag or master 2020-01-14 16:28:03 +02:00
pawel 144f10d8cb fs: tests: remove repo_dir 2020-01-14 15:25:55 +01:00
Ruslan Kuprieiev 6bd53273da Merge pull request #3143 from pared/2896_connection_unit
connection: test: unit: remove repo_dir
2020-01-14 16:25:11 +02:00
Ruslan Kuprieiev d5290e2a5c travis: build snap in parallel with other packages 2020-01-14 16:21:05 +02:00
Ruslan Kuprieiev 2d1e32748c Merge pull request #3136 from skshetry/refactor-remotes-inherit
test: use helper remotes in TestCases
2020-01-14 16:14:29 +02:00
pawel acd60ac1cf connection: test: unit: remove repo_dir 2020-01-14 14:52:31 +01:00
Saugat Pachhai 37b9d32e04 test: refactor: use helper remotes in TestCases 2020-01-14 19:25:19 +05:45
Ruslan Kuprieiev 88fb097275 Merge pull request #3138 from pared/2896_repro_cmd
tests: repro: unit: migrate to dir helpers
2020-01-14 15:29:03 +02:00
Ruslan Kuprieiev 6869ee65e0 Merge pull request #3139 from pared/2896_run_cmd
tests: run: unit: migrate to dir helpers
2020-01-14 15:28:56 +02:00
Ruslan Kuprieiev 63a3c678cf Merge pull request #3141 from pared/2896_update_cmd
update: tests: unit: migrate to dir helpers
2020-01-14 15:28:47 +02:00
Ruslan Kuprieiev e7626b8500 Merge pull request #3140 from pared/2896_stage_unit
stage: tests: unit: migrate to dir helpers
2020-01-14 15:27:14 +02:00
Ruslan Kuprieiev 6781a534f9 Merge pull request #3137 from pared/2896_unit_local
tests: local: unit: migrate to dir helpers
2020-01-14 15:10:13 +02:00
pawel 26c4845157 tests: run: unit: migrate to dir helpers 2020-01-14 14:02:26 +01:00
pawel ba63900b29 tests: repro: unit: migrate to dir helpers 2020-01-14 13:56:56 +01:00
pawel cabe619d97 update: tests: unit: migrate to dir helpers 2020-01-14 13:55:02 +01:00
pawel 8e756f0452 stage: tests: unit: migrate to dir helpers 2020-01-14 13:51:40 +01:00
pawel de9ff82b9c tests: local: unit: migrate to dir helpers 2020-01-14 13:40:44 +01:00
Ruslan Kuprieiev 9cafef1fdd Merge pull request #3051 from efiop/2995
metrics: introduce `diff`
2020-01-14 14:18:40 +02:00
Ruslan Kuprieiev 482473aa69 dvc: bump to 0.81.3 2020-01-14 11:23:10 +02:00
Ruslan Kuprieiev f18bfdc674 setup: fix typo 2020-01-14 11:22:29 +02:00
Ruslan Kuprieiev 8da898d483 dvc: bump to 0.81.2 2020-01-14 11:15:36 +02:00
Ruslan Kuprieiev ebb0243e87 setup: rollback pydrive dep
pypi doesn't allow direct github deps https://travis-ci.com/iterative/dvc/jobs/275282583
2020-01-14 11:15:11 +02:00
Ruslan Kuprieiev 58cc31cbe4 Merge pull request #3134 from skshetry/refactor-local
test: merge local related function in Local helper class
2020-01-14 10:56:01 +02:00
rxxg 674541f922 Use pyfastcopy for non-Windows systems using Python < 3.8 2020-01-14 09:32:09 +01:00
rxxg 811fc5317c Use speedcopy module with bugfix 2020-01-14 09:32:09 +01:00
rxxg 09223442f5 Use speedcopy module for faster copies across network boundaries 2020-01-14 09:32:09 +01:00
Ruslan Kuprieiev a09bfaf9db dvc: bump to 0.81.1 2020-01-14 10:29:01 +02:00
Ruslan Kuprieiev 876b659c2c remote: config: add a comment about section existence check
https://github.com/iterative/dvc/pull/3132#discussion_r366202037
2020-01-14 10:27:29 +02:00
Ruslan Kuprieiev 69e91f2bf9 Merge pull request #3132 from mroutis/fix-3131
remote: error out when modifying a non-existing remote
2020-01-14 10:26:16 +02:00
Ruslan Kuprieiev 563f75ebf7 Merge pull request #3129 from mroutis/fix-version
version: decode correctly commands output
2020-01-14 10:20:12 +02:00
Saugat Pachhai a27910d71d test: merge local related function in Local helper class 2020-01-14 14:04:02 +05:45
Ruslan Kuprieiev efa655ecf1 Merge pull request #3128 from skshetry/refactor-hdfs-gcp
test: move hdfs and gcp functions into helper classes
2020-01-14 10:16:57 +02:00
Mr. Outis ff094b2560 remote: error out when modifying a non-existing remote
Close #3131
2020-01-13 18:29:40 -06:00
Jorge Orpinel f65237761f api: improve existing docstrings
per https://github.com/iterative/dvc/issues/3092#issuecomment-572394156
2020-01-13 14:27:04 -06:00
Mr. Outis 2c9b763906 version: decode correctly commands output 2020-01-13 12:36:47 -06:00
Saugat Pachhai 26d85f1a52 test: merge gcp related functions to GCP helper class 2020-01-13 23:49:22 +05:45
Ruslan Kuprieiev fe635a5040 dvc: bump to 0.81.0 2020-01-13 16:59:05 +02:00
Ruslan Kuprieiev 0ead08805e Merge pull request #3116 from efiop/2896-scm
test: scm: convert to dir helpers
2020-01-13 16:13:22 +02:00
Ruslan Kuprieiev 0710dc2453 Merge pull request #3114 from iterative/efiop-patch-2
get: refactor handling of git files and git-only repos
2020-01-13 15:38:27 +02:00
Ruslan Kuprieiev e9dee8a103 Merge pull request #3115 from efiop/2896
test: get_url: convert to dir helpers
2020-01-13 15:38:02 +02:00
Ruslan Kuprieiev 92f3f8a624 Merge pull request #3117 from efiop/2896-init
test: init: convert to dir helpers
2020-01-13 15:37:17 +02:00
Ruslan Kuprieiev 7d2fa8885a test: scm: convert to dir helpers
Related to #2896
2020-01-13 15:34:27 +02:00
Ruslan Kuprieiev e833fd22d3 Merge pull request #3118 from efiop/2896-external-repo
tests: external_repo: convert to dir helpers
2020-01-13 15:21:53 +02:00
Ruslan Kuprieiev af821444f7 get: remove unused var 2020-01-13 13:39:45 +02:00
Ruslan Kuprieiev 64431f8930 test: get_url: convert to dir helpers
Related to #2896
2020-01-13 13:34:33 +02:00
Ruslan Kuprieiev 3e3869ffd0 Merge pull request #3126 from pared/fix_import_help
add space for import help text
2020-01-13 12:52:45 +02:00
pawel 28639c1d13 add space for import help text 2020-01-13 11:46:16 +01:00
Saugat Pachhai d55427dbd9 test: refactor hdfs related functions in HDFS test helpers 2020-01-13 16:30:15 +05:45
Ruslan Kuprieiev 2ab6860b90 Update tests/func/test_init.py 2020-01-13 11:25:37 +02:00
Ruslan Kuprieiev 028d30e22d Merge pull request #3123 from skshetry/refactor-ssh
test: refactor ssh related functions in test helper classes
2020-01-13 11:14:26 +02:00
Ruslan Kuprieiev 100dde1c9a Merge pull request #3101 from Suor/prepare-summon
summon: fixes and dvcx prereq
2020-01-13 10:51:27 +02:00
Saugat Pachhai 141f308a6a test: refactor ssh related functions in SSH and SSHMocked test helpers 2020-01-13 14:23:42 +05:45
Alexander Schepanovski be90c38182 summon: nice up summon code
- proper doc-string for prepare_summon()
- summon -> summon_dict in summon() to not overwrite a glo bal name.
This was not a bug, but a bad practice, which might have caused some
issues in the future.
- better name for _get_object_spec()
2020-01-13 15:22:16 +07:00
Alexander Schepanovski d0de9d0ed0 summon: revert dvc.api._invoke_mehtod() considerations
It's now thread-safe, but the rest is still relevant.
2020-01-13 15:19:54 +07:00
Ruslan Kuprieiev e393e29ef5 metrics: introduce diff
This first implementation is based on existing `dvc metrics show`
functionality, but is also able to auto-detect json to properly
show diff for it.
2020-01-13 06:45:48 +02:00
Ruslan Kuprieiev d831044cb6 tests: external_repo: convert to dir helpers
Related to #2896
2020-01-13 04:15:27 +02:00
Ruslan Kuprieiev 537a0af5f7 travis: update mac machine 2020-01-13 03:56:31 +02:00
Ruslan Kuprieiev 21c75502c5 test: init: convert to dir helpers 2020-01-12 06:07:21 +02:00
Ruslan Kuprieiev 5b5810e1c0 Merge pull request #3016 from efiop/3015
gdrive: add support for teamDriveId
2020-01-12 04:52:01 +02:00
Ruslan Kuprieiev a79f04a28c get: refactor handling of git files and git-only repos 2020-01-12 03:05:20 +02:00
Ruslan Kuprieiev f920afd5dd travis: fix aws config condition
Close #3113
2020-01-12 02:23:15 +02:00
Ruslan Kuprieiev 3e97886ccc Merge pull request #3076 from efiop/rwlock_sync
rwlock: flush and fsync
2020-01-12 01:35:13 +02:00
Max Risuhin 57111a7788 gdrive: add support for teamDriveId 2020-01-11 07:41:12 -08:00
Ruslan Kuprieiev b7f9e7365d Merge pull request #3097 from fabiosantoscode/feature/3089-get-non-dvc-repositories
get: handle non-DVC repositories
2020-01-11 07:05:35 +02:00
Ruslan Kuprieiev 9e9f37e06a Merge pull request #3094 from pared/2896_run
run: tests: migrate to dir helpers
2020-01-11 05:36:07 +02:00
Ruslan Kuprieiev d6092c1c95 Merge pull request #3096 from pared/2896_state
state: tests: migrate to dir helpers
2020-01-11 05:35:32 +02:00
Ruslan Kuprieiev 62d0fa1afa Merge pull request #3100 from pared/2896_update
update: tests: migrate to dir helpers
2020-01-11 05:34:52 +02:00
Ruslan Kuprieiev 7f9bbd94a5 Merge pull request #3099 from pared/2896_stage
stage: tests: migrate to dir helpers
2020-01-11 05:33:39 +02:00
Ruslan Kuprieiev 5b85db3e04 Merge pull request #3104 from efiop/ssh_dir_out
tests: remove redundant native ssh tests
2020-01-11 05:20:21 +02:00
Ruslan Kuprieiev bc619f1c41 pyinstaller: remove Crypto hook 2020-01-11 04:55:34 +02:00
Ruslan Kuprieiev c064865c80 build: use build-requirements.txt 2020-01-11 04:55:34 +02:00
Ruslan Kuprieiev 795e4ce6b8 pyinstaller: delete oss hook
It is already available in the standard hooks.
2020-01-11 04:38:57 +02:00
Ruslan Kuprieiev 2a9d19f116 tests: use mocked ssh 2020-01-11 03:14:45 +02:00
Ruslan Kuprieiev f6a00f35ae ssh: fix bug in remove() 2020-01-11 03:14:45 +02:00
Ruslan Kuprieiev 8154c89c3f tests: remove redundant native ssh tests
We already have mocked ones, which are more reliable as they use their
own paramiko server instead of relying on the sshd running on the test
machine. Native tests are also quite flaky and fail sometimes due to
network issues.
2020-01-11 03:14:45 +02:00
Ruslan Kuprieiev 01103585d7 Merge pull request #3090 from mroutis/py3-system
py3: use links from os module
2020-01-11 02:45:10 +02:00
Mr. Outis df97a2a7de don't reimport DvcException 2020-01-10 18:32:34 -06:00
Mr. Outis b8dd4225d2 py3: fspath source and link_name 2020-01-10 17:40:59 -06:00
Mr. Outis 6eac40d226 system: catch OSError instead of Exception 2020-01-10 17:35:54 -06:00
Fábio Santos de303d45ba test removing a file outside a raw git repo 2020-01-10 23:30:18 +00:00
Fábio Santos f3d234ec9c deduplicate code 2020-01-10 23:26:37 +00:00
Fábio Santos 1f2eed80f8 don't manually clean up git repository 2020-01-10 23:16:19 +00:00
Fábio Santos 9f279b3968 forbid absolute paths for plain git repositories 2020-01-10 23:15:03 +00:00
Fábio Santos 0e2ef470e8 remove blank line 2020-01-10 22:53:31 +00:00
Fábio Santos 5fb6640867 run black 2020-01-10 22:29:27 +00:00
Fábio Santos f5e40b2a47 get: leverage external_repo() context manager for DVC repositories 2020-01-10 22:28:24 +00:00
Fábio Santos c16f8825dc import: update documentation
Fixes #3077
2020-01-10 22:28:24 +00:00
Fábio SantosandJorge Orpinel 5b36ffd60e Update dvc/command/get.py
Co-Authored-By: Jorge Orpinel <[email protected]>
2020-01-10 22:28:24 +00:00
Fábio SantosandJorge Orpinel 9a98622020 Update dvc/command/get.py
Co-Authored-By: Jorge Orpinel <[email protected]>
2020-01-10 22:28:24 +00:00
Fábio SantosandJorge Orpinel 04619d559b Update dvc/command/get.py
Co-Authored-By: Jorge Orpinel <[email protected]>
2020-01-10 22:28:24 +00:00
Fábio SantosandJorge Orpinel 51f2fa184f Update dvc/command/get.py
Co-Authored-By: Jorge Orpinel <[email protected]>
2020-01-10 22:28:24 +00:00
Fábio Santos 6a4f4031b0 change command doc string 2020-01-10 22:28:24 +00:00
Fábio Santos 7873abcade remove unused exception class 2020-01-10 22:28:24 +00:00
Fábio Santos 6cb7308cdb Revert "get: recoup cache optimal location"
This reverts commit 8e4922800f6ce47210976764af1c05e31e90deac.
2020-01-10 22:28:24 +00:00
Fábio SantosandRuslan Kuprieiev a01f81ed09 Update dvc/repo/get.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2020-01-10 22:28:24 +00:00
Fábio Santos 86b9a1cfe4 get: recoup cache optimal location 2020-01-10 22:28:24 +00:00
Fábio Santos f126f2483b doc: change get documentation so that it doesn't imply the target must be DVC enabled 2020-01-10 22:28:24 +00:00
Fábio Santos 07637a6f8f style: improve code flow and move comments 2020-01-10 22:28:24 +00:00
Fábio Santos 0dd5647d3c get: handle non-DVC repositories
Allows us to `dvc get` from non-DVC source repositories.

Fixes #3089
2020-01-10 22:28:24 +00:00
Ruslan Kuprieiev 84ec0d49a3 Merge pull request #3091 from pared/2896_repro
repro: tests: migrate to dir helpers
2020-01-10 18:38:30 +02:00
Paweł Redzyński 23288aa568 run: tests: remove deterministic_run fixture 2020-01-10 17:18:12 +01:00
Paweł Redzyński f62aeae6d1 run: tests: clumsy deterministic_run migration 2020-01-10 17:18:11 +01:00
Paweł Redzyński 6d61e627cb run: tests: migrate to helpers all besides deterministic 2020-01-10 17:18:11 +01:00
Alexander Schepanovski 4a3dd63720 summon: separate prepare_summon() 2020-01-10 22:02:58 +07:00
Alexander Schepanovski c548b0792d summon: fix open() shadowing 2020-01-10 21:59:58 +07:00
pawel ab1fab5b3e update: tests: migrate to dir helpers 2020-01-10 15:33:12 +01:00
Alexander Schepanovski afd29e5dad summon: protect chdir/import/call with a thread lock 2020-01-10 20:55:28 +07:00
pawel b6edeb4914 stage: tests: migrate to dir helpers 2020-01-10 14:44:29 +01:00
pawel 57685399a6 state: tests: migrate to dir helpers 2020-01-10 13:06:15 +01:00
Ruslan Kuprieiev 90152ea8b8 Merge pull request #3053 from Suor/is-tracked
dvc: optimize Git.is_tracked()
2020-01-10 06:34:30 +02:00
Ruslan Kuprieiev 095464d851 Merge pull request #3093 from iterative/move-fs-utilities
Move fs utilities to fs.py from __init__.py
2020-01-09 17:25:58 +02:00
Ruslan Kuprieiev 238b4e8e7e Merge pull request #3072 from pared/2896_import_url
import url: test: migrate to dir helpers
2020-01-09 16:33:57 +02:00
Paweł Redzyński 0554cbfe9c repro: tests: use Path-like objects in repro_dir fixture 2020-01-09 11:41:44 +01:00
Paweł Redzyński 93c54763fe repro: tests: yield dict of stages from repro_dir 2020-01-09 11:10:41 +01:00
Aman Sharma 74850fe728 Move walk_files to fs.py 2020-01-09 14:55:49 +05:30
Paweł RedzyńskiandRuslan Kuprieiev ad1450635b Update tests/func/test_import_url.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2020-01-09 10:00:02 +01:00
Aman Sharma a79816920d Move copyfile to fs.py 2020-01-09 04:57:53 +05:30
Aman Sharma eca5d2dde4 Move makedirs to fs.py 2020-01-09 04:30:51 +05:30
Paweł RedzyńskiandRuslan Kuprieiev b9e700105e Update tests/func/test_import_url.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2020-01-08 22:21:02 +01:00
Paweł RedzyńskiandRuslan Kuprieiev 7e12c7104d Update tests/func/test_import_url.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2020-01-08 22:20:50 +01:00
Paweł Redzyński 25437ca600 repro: tests: refactor for readibility 2020-01-08 21:46:39 +01:00
Paweł Redzyński e93504668d repro: tests: migrate to dir helpers 2020-01-08 21:35:29 +01:00
Ruslan Kuprieiev 277d28c6cb Merge pull request #3083 from pared/2896_output
output: tests: migrate to dir helpers
2020-01-08 22:14:07 +02:00
Mr. Outis e5bee243a3 py3: use links from os module 2020-01-08 14:09:22 -06:00
Ruslan Kuprieiev 049367e475 Merge pull request #3034 from mroutis/remove-py2-comments
py3: remove code related to py2 limitations
2020-01-08 20:14:40 +02:00
Ruslan Kuprieiev 82cdce0934 Merge pull request #3086 from pared/2896_remote
remote: test: migrate to dir helpers
2020-01-08 19:16:30 +02:00
Ruslan Kuprieiev 27774705cf Merge pull request #3085 from pared/2896_pipeline
pipeline: test: migrate to dir helpers
2020-01-08 19:14:21 +02:00
Paweł Redzyński 04912f2c86 remote: test: migrate to dir helpers 2020-01-08 12:21:33 +01:00
Paweł Redzyński 945eb181bc pipeline: test: migrate to dir helpers 2020-01-08 11:23:43 +01:00
Paweł Redzyński f074d00269 output: tests: migrate to dir helpers 2020-01-08 11:11:34 +01:00
pawel 62d6f4dbdb import url: test: migrate to dir helpers 2020-01-08 10:30:33 +01:00
Ruslan Kuprieiev 2966924e47 Merge pull request #3074 from pared/2896_install
install: tests: migrate to dir helpers
2020-01-08 06:20:19 +02:00
Ruslan Kuprieiev fb37cd0ab2 Merge pull request #3082 from efiop/reqs
setup: get rid of py2 reqs
2020-01-08 02:42:19 +02:00
Ruslan Kuprieiev ba45c9cd5b setup: get rid of py2 reqs 2020-01-08 02:16:06 +02:00
Ruslan Kuprieiev e8b0e22a6b Merge pull request #3078 from mroutis/py3-logger
py3: use format_exc instead of print_exception
2020-01-08 01:27:07 +02:00
Ruslan Kuprieiev ee0a8fa92d Merge pull request #3075 from efiop/assert_stage
rwlock: check that .dvc/lock is locked
2020-01-08 00:44:32 +02:00
Ruslan Kuprieiev 249f6c5634 Merge pull request #3027 from Suor/status-noop
ui: warn about ignored options for `dvc status` and a grammar fix
2020-01-07 20:30:05 +02:00
Alexander Schepanovski bafac6dec3 ui: fix grammar in the error message 2020-01-07 17:26:01 +07:00
Alexander Schepanovski 1b1f8f9d22 ui: error out for meaningless options for non-cloud dvc status 2020-01-07 17:25:55 +07:00
Paweł Redzyński 1d00560612 fixup 2020-01-07 10:16:32 +01:00
pawel 7f2a196c5c install: tests: migrate to dir helpers 2020-01-07 10:12:01 +01:00
Mr. Outis f942e26453 py3: remove tb_list 2020-01-07 00:13:51 -06:00
Mr. Outis 7ed4fd3840 py3: use format_exc instead of print_exception 2020-01-06 23:40:00 -06:00
Mr. Outis 71b15d259a py3: remove code related to py2 limitations 2020-01-06 23:03:33 -06:00
Ruslan Kuprieiev 11fa9204f8 Merge pull request #3058 from efiop/too_many_files
ui: don't show exception for "Too many files" error
2020-01-07 06:51:29 +02:00
Ruslan Kuprieiev df214fa9d4 Merge pull request #3035 from iterative/test-walk_files
Write test for `walk_files` to check if it accepts Path-like and str objects
2020-01-07 05:33:08 +02:00
Aman Sharma 13ca5bdf28 Write test for walk_files to check if it accepts Path-like and str objects 2020-01-07 05:50:48 +05:30
Ruslan Kuprieiev c1906436c2 rwlock: flush and fsync
It is important that we ensure that rwlocked contents are flushed to
disk, because otherwise other rwlock readers might get outdated state.

Kudos @shcheklein for catching this one.
2020-01-06 22:37:21 +02:00
Ruslan Kuprieiev 1dcce1d8b0 rwlock: check that .dvc/lock is locked
Mostly a sanity check, but we did have a potential bug in `destroy`,
whcih this PR fixes along the way.

Kudos @shcheklein for catching this one.
2020-01-06 22:34:34 +02:00
Ruslan Kuprieiev e2efb7b6e4 Merge pull request #3064 from casperdcl/snap-deploy
fix snap deploy stage
2020-01-06 22:26:00 +02:00
Casper da Costa-Luis 5753ea5dae snap deploy master edge
partially reverts f21931cca0
2020-01-07 00:40:43 +05:30
Ruslan Kuprieiev 498dcabb94 Merge pull request #3054 from sharidas/fix-gc-local
dvc: update gc to remove unpacked dir
2020-01-06 21:05:13 +02:00
Ruslan Kuprieiev f21931cca0 travis: deploy snap only on tags. 2020-01-06 20:53:49 +02:00
Ruslan Kuprieiev aaec1837bb Merge pull request #3073 from skshetry/refactor-remote-s3
test: merge _should_test_aws and _get_aws_url inside S3 test helper
2020-01-06 20:43:18 +02:00
Ruslan Kuprieiev 4f312f69a9 Merge branch 'master' into refactor-remote-s3 2020-01-06 20:43:10 +02:00
Ruslan Kuprieiev 0f08b7ed79 Merge pull request #3071 from skshetry/refactor-remote-oss
test: merge _should_test_oss and _get_oss_url inside OSS test helper
2020-01-06 20:39:20 +02:00
Ruslan Kuprieiev 40449c49b1 Merge pull request #3070 from pared/2896_http_imp
http/ imp tests - migrate do dir helpers
2020-01-06 20:38:37 +02:00
Ruslan Kuprieiev 9f0f1c4212 Merge pull request #3068 from pared/2896_dependency
dependency: test: migrate to dir helpers
2020-01-06 20:37:39 +02:00
Ruslan Kuprieiev e5ac017957 Merge pull request #3067 from pared/2896_cache
cache: test: migrate to dir helpers
2020-01-06 20:37:12 +02:00
Sujith H 1e3abe2f38 dvc: update gc to remove unpacked dir
In local remote its found that gc does not
remove the unpacked dir. This change set
helps to remove it.

Signed-off-by: Sujith H <[email protected]>
2020-01-06 23:58:06 +05:30
Ruslan Kuprieiev cf37fe2c56 travis: deploy snap only on master 2020-01-06 20:16:22 +02:00
Ruslan Kuprieiev ec574a0055 Merge pull request #3066 from pared/2896_analytics
analytics: tests: migrate to dir helpers
2020-01-06 19:42:11 +02:00
Ruslan Kuprieiev 46379c8591 config: update command for setting default remote 2020-01-06 18:58:10 +02:00
Saugat Pachhai 78dd4896ca test: merge _should_test_aws and _get_aws_url inside S3 test helper 2020-01-06 22:08:28 +05:45
pawel e1176bf42d imp/imp_url cmd test: remove unnecessary dvc fixture 2020-01-06 16:40:15 +01:00
pawel a620658884 http/ imp tests - migrate do dir helpers 2020-01-06 16:18:01 +01:00
pawel c2b3921af3 dependency: test: migrate to dir helpers 2020-01-06 16:06:52 +01:00
pawel d2859a311d cache: test: migrate to dir helpers 2020-01-06 16:02:04 +01:00
pawel bb72ce8a50 analytics: tests: migrate to dir helpers 2020-01-06 15:49:26 +01:00
Ruslan Kuprieiev 8041d70552 Merge pull request #3044 from pared/2896_api
tests: api: convert to dir helpers
2020-01-06 16:21:31 +02:00
Ruslan Kuprieiev 0ad3548fca Merge pull request #3063 from casperdcl/snap-classic
Snap classic deployment
2020-01-06 12:47:01 +02:00
Casper da Costa-Luis be762e70c2 remove unneeded stage 2020-01-06 14:56:16 +05:30
Casper da Costa-Luis 55f9a54bff integrate snap deploy 2020-01-06 14:32:39 +05:30
Casper da Costa-Luis 30354f4c8c Snap classic approval!
- approval: https://forum.snapcraft.io/t/classic-confinement-request-for-dvc/14124
- partially reverts commit a0ac129d5f.
2020-01-06 14:14:44 +05:30
Ruslan Kuprieiev 19f408b0e6 Merge pull request #3061 from mroutis/fix-zsh
completion: fix --relink problem
2020-01-06 04:57:21 +02:00
Ruslan Kuprieiev 6411a3bb32 ui: don't show exception for "Too many files" error
https://github.com/iterative/dvc.org/pull/875#discussion_r362920851

Stops showing the exception in non-verbose mode, but shows full
traceback in verbose mode.

Introducing our custom parameter `tb_only` through standard `extra`
logging parameter.
2020-01-06 01:02:59 +02:00
Ruslan Kuprieiev 29afb380fd Merge pull request #3018 from pared/2914_3
local-related methods should verify that WorkingTree is in use
2020-01-06 00:06:35 +02:00
Mr. Outis 8712ecc1e1 completion: fix --relink problem
Close #2983
2020-01-05 02:05:24 -06:00
Ruslan Kuprieiev 7f92cc3be3 Merge pull request #3049 from pared/2896_remote_local
tests: remote: local: migrate to dir fixtures
2020-01-05 07:19:44 +02:00
Ruslan Kuprieiev f93574895f Merge pull request #3020 from chatcannon/dvc-import-regular-git
import: allow importing from non-DVC git repositories
2020-01-05 07:18:03 +02:00
Saugat Pachhai 96c87d5643 test: merge _should_oss_azure and _get_oss_url inside OSS class 2020-01-05 10:25:01 +05:45
Ruslan Kuprieiev 54faf1b795 Merge pull request #3055 from skshetry/refactor-remote-azure
test: Merge _should_test_azure and _get_azure_url inside Azure test helper
2020-01-05 04:25:09 +02:00
Ruslan Kuprieiev 60e512112e Merge branch 'master' into refactor-remote-azure 2020-01-05 04:24:59 +02:00
Ruslan Kuprieiev b6c77f7668 Merge pull request #3056 from skshetry/refactor-gdrive
test: add _should_test_gdrive and _get_gdrive_url inside GDrive
2020-01-04 20:18:49 +02:00
Ruslan Kuprieiev f39d8eb3d1 Merge pull request #3052 from Suor/yield-from
py3: use yield from where appropriate
2020-01-04 18:21:12 +02:00
Ruslan Kuprieiev f3a7ac3756 Merge pull request #3045 from Suor/erepo-template
test: do not bootstrap repo template and version files in erepo_dir
2020-01-04 18:20:55 +02:00
Saugat Pachhai c0616fcee2 test: add _should_test_gdrive and _get_gdrive_url inside GDrive 2020-01-04 22:03:49 +05:45
Saugat Pachhai c4783f4930 test: merge _should_test_azure and _get_azure_url inside Azure class 2020-01-04 21:54:12 +05:45
Alexander Schepanovski a19deb2076 dvc: optimize Git.is_tracked()
Closes #3000
2020-01-04 11:32:16 +07:00
Alexander Schepanovski 0352adc639 py3: use yield from where appropriate 2020-01-04 11:03:30 +07:00
Alexander Schepanovski 5dd2d7b192 test: do not use repo template in erepo_dir 2020-01-04 10:47:13 +07:00
Alexander Schepanovski b194bfa1ff test: do not bootstrap versions in erepo_dir 2020-01-04 10:47:13 +07:00
pawel af425318aa tests: api: roll back erepo usage 2020-01-04 03:10:43 +01:00
pawel 020c0496a6 tests: api: convert to dir helpers 2020-01-04 03:08:51 +01:00
pawel d9eaabe9bf some fixmes 2020-01-04 03:08:51 +01:00
pawel 39f010e50a api: open: fix open scm controlled test 2020-01-04 03:08:51 +01:00
pawel ce0eb08ba6 tests: remote: local: migrate to dir fixtures 2020-01-04 02:47:58 +01:00
Ruslan Kuprieiev 85c35f894c Merge pull request #3048 from Suor/optimize-stages
dvc: optimize and clean up stages collection
2020-01-03 23:26:42 +02:00
Ruslan Kuprieiev bdcc995ae5 Merge pull request #3047 from Suor/tree-autoclean
dvc: autowrap repo tree into CleanTree
2020-01-03 23:23:05 +02:00
Alexander Schepanovski b2d083fd88 dvc: optimize and clean up stages collection 2020-01-03 22:06:52 +07:00
Alexander Schepanovski 5e92a079b8 dvc: autowrap repo tree into CleanTree 2020-01-03 21:46:05 +07:00
Ruslan Kuprieiev 45933113df Merge pull request #3043 from Suor/chdir
test: add @TmpDir.chdir()
2020-01-03 16:06:52 +02:00
Ruslan Kuprieiev 11a627d336 Merge pull request #3046 from Suor/clean-brancher
dvc: clean brancher
2020-01-03 14:42:51 +02:00
Alexander Schepanovski ecd511fc69 dvc: safely return to saved tree in brancher 2020-01-03 19:09:34 +07:00
Alexander Schepanovski 77b937b001 dvc: drop unused params in Repo.brancher() 2020-01-03 19:05:54 +07:00
Alexander Schepanovski 1e5fb102a2 test: add @TmpDir.chdir() 2020-01-03 16:58:32 +07:00
pawel 36427387c0 get_mtime_and_size: move working tree assertion after veryfing dir 2020-01-03 10:32:32 +01:00
pawel 1e1a9cf774 tree: introduce is_working_tree 2020-01-03 10:03:47 +01:00
pawel eb53c030c6 local-related methods should verify that WorkingTree is in use 2020-01-03 09:46:04 +01:00
Ruslan Kuprieiev 705fbd24c4 Merge pull request #3042 from mroutis/refactor-exceptions
py3: refactor exceptions
2020-01-03 05:44:52 +02:00
Mr. Outis 5611ccb43a exceptions: remove cause 2020-01-02 20:43:24 -06:00
Mr. Outis aed04e7cc9 exceptions: remove noop code 2020-01-02 16:59:56 -06:00
Ruslan Kuprieiev 0a2e699115 Merge pull request #3039 from mroutis/refactor-super
py3: use new magic from PEP 3135
2020-01-03 00:53:09 +02:00
Ruslan Kuprieiev ca06698dc2 Merge pull request #3041 from iterative/pull-3040-restyled
Restyle fix for base None value
2020-01-03 00:46:54 +02:00
Restyled.io bb54232921 Restyled by black 2020-01-02 22:43:26 +00:00
msatlihan 70146efc7c fix for None value 2020-01-03 01:39:24 +03:00
Mr. Outis fd82402d34 py3: use new magic from PEP 3135 2020-01-02 16:24:31 -06:00
Ruslan Kuprieiev a377f9e08f Merge pull request #3037 from iterative/test-relpath
Ensure `relpath` accepts str and Path-like objects
2020-01-02 16:46:06 +02:00
Ruslan Kuprieiev 8a2e375d6b Merge pull request #3025 from Suor/dirty-metrics
metrics: don't show working tree if it is clean
2020-01-02 02:14:20 +02:00
Aman Sharma f08c90fc6e Ensure relpath accepts str and Path-like objects 2020-01-02 02:17:02 +05:30
Ruslan Kuprieiev f6162fee47 Merge pull request #3026 from Suor/relink-autocompl
ui: add checkout --relink to autocomplete scripts
2020-01-01 17:34:46 +02:00
Ruslan Kuprieiev a5ba9707a9 Merge pull request #3032 from mroutis/migrate-utils-compat
py3: migrate utils/compat.py to compat.py
2020-01-01 17:09:32 +02:00
Ruslan Kuprieiev cba4c017a7 Merge pull request #3031 from mroutis/remove-imports
py3: remove imports from utils.compat
2020-01-01 17:08:46 +02:00
Ruslan Kuprieiev fd319ab8a9 Merge pull request #3030 from mroutis/remove-utils-compat
py3: remove csv_reader and makedirs from utils/compat.py
2020-01-01 17:06:33 +02:00
Ruslan Kuprieiev a507a19a34 Merge pull request #3029 from mroutis/compat-casts
py3: remove methods for casting bytes in python 2
2020-01-01 17:04:25 +02:00
Mr. Outis 55e90b7bdf py3: ignore_file_not_found -> contextlib.suppress 2019-12-31 23:42:40 -06:00
Mr. Outis 086acf6404 py3: dvc/utils/compat.py -> dvc/compat.py 2019-12-31 23:42:07 -06:00
Mr. Outis 27f581cf4e py3: remove imports from utils.compat 2019-12-31 23:16:11 -06:00
Mr. Outis 9c42809587 py3: _makedirs -> os.makedirs 2019-12-31 22:19:07 -06:00
Mr. Outis 999130b029 py3: remove compat csv_reader 2019-12-31 22:17:02 -06:00
Mr. Outis 1ef466b849 py3: remove convert_to_unicode noop 2019-12-31 22:14:54 -06:00
Alexander Schepanovski b8cb6d72a9 metrics: don't show working tree if it is clean 2020-01-01 10:59:13 +07:00
Mr. Outis b844aea42c py3: remove cast_bytes method 2019-12-31 20:14:25 -06:00
Mr. Outis 326ba21ed0 py3: remove unneeded string casts 2019-12-31 19:43:15 -06:00
Ruslan Kuprieiev 35f4ee8a53 dvc: bump to 0.80.0 2020-01-01 03:24:03 +02:00
Chris Kerr 3ceb1db5a8 import: Allow importing from non-DVC git repos
Fixes #2977

Refactor `_copy_if_git_file` to use `cached_clone` directly, so
that copying still works even if the source repo does not have a
'.dvc' subdirectory.
2019-12-31 13:19:33 +00:00
Chris Kerr fb300e1f9f Refactor cached_clone function out of _external_repo
Since this function does not create a `Repo` class, it can be called
on a git repo with no '.dvc' directory
2019-12-31 13:13:29 +00:00
Chris Kerr c393e8dcbe import: Test for #2977: Handle non-DVC Git repositories 2019-12-31 13:11:28 +00:00
Ruslan Kuprieiev cbc7e21593 utils: don't import colorama again 2019-12-31 13:06:36 +02:00
Ruslan Kuprieiev 32ee19778f Merge pull request #3024 from mroutis/unicode-str
py3: remove string compat related code
2019-12-31 13:05:57 +02:00
Alexander Schepanovski bd0b28d7c2 ui: add checkout --relink to autocomplete scripts 2019-12-31 14:04:51 +07:00
Mr. Outis 6d36b85736 py3: remove string compat related code 2019-12-30 23:49:19 -06:00
Ruslan Kuprieiev dd5e0589db Merge pull request #3021 from mroutis/remove-utils-compat
py3: get rid of is_py checks
2019-12-31 04:11:00 +02:00
Ruslan Kuprieiev 381b279e53 Merge pull request #3022 from mroutis/remove-future-imports
py3: remove future imports
2019-12-31 04:08:22 +02:00
Mr. Outis 2827146e55 py3: adapt TEXT_CHARS on istextfile.py 2019-12-30 19:45:51 -06:00
Ruslan Kuprieiev e599a3ad57 Merge pull request #3019 from mroutis/deprecating-27
py3: deprecate 2.7
2019-12-31 03:36:39 +02:00
Mr. Outis 94a0914bba py3: get rid of future imports 2019-12-30 19:35:48 -06:00
Mr. Outis c61254dc75 py3: get rid of py2 on CI scripts 2019-12-30 19:16:10 -06:00
Mr. Outis 886afdb203 py3: get rid of is_py checks 2019-12-30 18:53:36 -06:00
Mr. Outis d3d6f679be py3: modify python_requires to support 3.5 onwards 2019-12-30 13:10:28 -06:00
Ruslan Kuprieiev 21808112df Merge pull request #3014 from efiop/test
tests: func: convert test_get.py test_utils.py to new fixtures
2019-12-30 18:31:53 +02:00
Ruslan Kuprieiev df5fda272e Merge pull request #2974 from pared/2914
repo: move dvcignore from repo to tree
2019-12-30 18:31:23 +02:00
pawel 1880656579 CleanTree: conform to snake_case naming style 2019-12-30 17:05:45 +01:00
pawel 691d771d56 remove CleanTree from GitTree completely 2019-12-30 16:50:43 +01:00
pawel e1eab9405c tree: check that repo.tree is CleanTree when using walk_files 2019-12-30 14:19:43 +01:00
pawel e225601031 reintroduce dvcignore collection test 2019-12-30 14:19:43 +01:00
pawel cf2d349bd7 tree: move CleanTree to ignore 2019-12-30 14:19:43 +01:00
pawel 56ed5351ef ignore: test: remove ignore collection test 2019-12-30 14:19:43 +01:00
pawel d8813dc5ca remove dvc_walk, skip .dvcignore gathering test 2019-12-30 14:19:43 +01:00
pawel d8c0ae6909 remove most occurences of dvc_walk 2019-12-30 14:19:43 +01:00
pawel cf2fac5787 working tree: adjust walk behaviour to os.walk 2019-12-30 14:19:43 +01:00
pawel aa613f9e6f dvcignore loading back in constructor 2019-12-30 14:19:43 +01:00
pawel f4ee01cffe no need for dvcignore in walk args 2019-12-30 14:19:43 +01:00
Paweł Redzyński 956fec02ce repo: move dvcignore from repo to tree 2019-12-30 14:19:43 +01:00
Ruslan Kuprieiev fe7fa1424b tests: func: convert test_get.py test_utils.py to new fixtures 2019-12-30 09:38:48 +02:00
Ruslan Kuprieiev 0a3a9bf58c Merge pull request #3012 from Suor/simplify-graph
dvc: make graph consist of stages not their paths
2019-12-30 08:07:34 +02:00
Ruslan Kuprieiev 13d9af581f Merge branch 'master' into simplify-graph 2019-12-30 07:29:19 +02:00
Ruslan Kuprieiev ef6018fc2f Merge pull request #3002 from efiop/2458
dvc: support granularity for fetch/pull/push/status/checkout
2019-12-30 07:26:57 +02:00
Alexander Schepanovski b6b07dde8b dvc: make graph consist of stages not their paths 2019-12-30 12:07:52 +07:00
Ruslan Kuprieiev 3fcd758304 travis: fix stages 2019-12-30 00:41:28 +02:00
Ruslan Kuprieiev 054163f850 travis: disable py2 builds 2019-12-30 00:38:57 +02:00
Ruslan Kuprieiev a07bc20c37 Merge pull request #2991 from MaxRis/gdrive-tests
Improve Google Drive auth failure error reporting
2019-12-29 04:35:29 +02:00
Ruslan Kuprieiev c81795c047 dvc: support granularity for fetch/pull/push/status/checkout
Using output path or a subdir/subfile path within an output now works
with `dvc push/pull/fetch/status -c` commands. Other commands don't
support the same logic for now, as there are some questions about what
should commands like `dvc remove` do when given a specific output path.

Example:
    dvc add data
    dvc pull data/subdir # will only pull files within data/subdir

Related to #2458
2019-12-29 01:31:08 +02:00
Max Risuhin 4727cb7893 Improve Google Drive auth failure error reporting 2019-12-28 05:22:37 -08:00
Ruslan Kuprieiev 1038cfc993 Merge pull request #2971 from mroutis/summon
summon: first draft with python specific implementation
2019-12-27 19:57:03 +02:00
Ruslan Kuprieiev a6f023cc4e Merge pull request #3006 from Suor/version-newline
ui: fix extra newline in dvc version
2019-12-27 18:40:10 +02:00
Mr. Outis dad224ef42 summon: os.curdir -> os.getcwd 2019-12-27 10:09:13 -06:00
Mr. Outis e8dd2358f5 summon: fix path popping operation 2019-12-27 10:05:41 -06:00
Alexander Schepanovski 322b354dd5 test: fix and migrate version tests 2019-12-27 22:07:46 +07:00
Ruslan Kuprieiev 5a589c5219 Merge pull request #3003 from Suor/ignore-nuances
test: test ignore nuances
2019-12-27 11:42:13 +02:00
Alexander Schepanovski 5f3e416e60 test: fix test_ignore_external in Python 3.5 2019-12-27 15:51:07 +07:00
Alexander Schepanovski 094482249a ui: fix extra newline in dvc version
Additionally don't try to detect cache fs type unless cache dir exists.
2019-12-27 15:45:41 +07:00
Mr. Outis b97747179b Merge branch 'master' into summon 2019-12-25 14:40:34 -06:00
Mr. Outis 49264b55ac py2: fix problem with ruamel.yaml and unicode 2019-12-25 13:07:40 -06:00
Ruslan Kuprieiev d23ffcf338 dvc: bump to 0.78.1 2019-12-25 16:14:05 +02:00
Ruslan Kuprieiev f1b7f86dbf setup: fix inflect version requirements 2019-12-25 16:04:23 +02:00
Mr. Outis 4a25612282 💅 refactor 2019-12-25 01:04:52 -06:00
Mr. Outis 75ad14aaa2 💅 black 2019-12-25 00:43:33 -06:00
Mr. Outis 0658622ca2 summon: pair-programming adjustments 2019-12-25 00:42:55 -06:00
Mr. Outis 7a58901ec6 💅 black & flake 2019-12-24 20:21:03 -06:00
Mr. Outis 1e65c76ef1 summon: raise SummonError appropiately 2019-12-24 20:20:03 -06:00
Mr. Outis fbb7aebd31 tests/summon: use different args for non-canon.yml 2019-12-24 19:50:43 -06:00
Mr. Outis d45f07cbec summon: make schema more consistent
* Mark optional keywords
* Allow extra entries
* Include objects into the schema
2019-12-24 13:41:57 -06:00
Ruslan Kuprieiev cfa4af15f7 dvc: bump to 0.78.0 2019-12-24 20:20:27 +02:00
Mr. Outis 1c22cd5af4 summon: adjust comments 2019-12-24 02:20:08 -06:00
Mr. Outis 7dd340aeeb summon: 💅 refactoring/reordering 2019-12-24 02:13:11 -06:00
Mr. Outis 542f4c3c18 summon: wrap chdir_and_syspath in a try/finally 2019-12-24 00:42:13 -06:00
Mr. Outis 6fd5eb4185 summon: use slice instead of get 2019-12-24 00:40:07 -06:00
Mr. Outis 7ece84b840 summon: prevent modifying the yaml args 2019-12-24 00:39:24 -06:00
Alexander Schepanovski e8c321e9a3 test: test ignore nuances
- nested files are ignored
- external dirs are not affected
2019-12-24 12:46:32 +07:00
Mr. Outis 4ad7cb3961 summon: define yaml file schema 2019-12-23 23:45:13 -06:00
Ruslan Kuprieiev dd68e9a569 Merge pull request #2986 from jorgeorpinel/output/format/single-quotes
dvc: use ' to mark dynamic command output, et al.
2019-12-21 21:51:53 +02:00
Jorge Orpinel 654525cdde add: fix test with ` around DVC command 2019-12-20 19:29:29 -07:00
Jorge Orpinel 781314dc89 dvc: fix ` to ' in dvc/repo/gc.py 2019-12-20 19:15:27 -07:00
Jorge Orpinel 14ab7c93d3 import-url: revert ' around exception string 2019-12-20 19:09:22 -07:00
Jorge Orpinel 06822a060e dvc: use ' for dynamic output (through dvc/util/) 2019-12-20 19:00:09 -07:00
Jorge Orpinel 655fa956d4 diff: revert ' in string 2019-12-20 18:59:45 -07:00
Jorge Orpinel 3574b0c107 dvc: use ' for dynamic output (up to lock) 2019-12-20 17:48:09 -07:00
Jorge Orpinel e1f755bbdd fix formatting of dvc/analytics.py
based on https://github.com/iterative/dvc/pull/2987/files#diff-60f82c04687a06de3f632551d63de49e
2019-12-20 12:54:31 -07:00
Jorge Orpinel 385674a78b dvc: use ' for dynamic output and ` for DVC names (follow-up)
continues https://github.com/iterative/dvc/pull/2644#discussion_r338417698
2019-12-19 14:23:31 -07:00
Mr. Outis addc56c232 summon: allow different YAML file names 2019-12-18 23:38:52 -06:00
Mr. Outis c852d3316d 💅 black 2019-12-18 23:34:43 -06:00
Mr. Outis ee72280e60 summon: left a comment about chdir and syspath 2019-12-18 23:34:00 -06:00
Mr. Outis 4273ff49b3 summon: params -> args; use summon key in yaml 2019-12-18 23:32:28 -06:00
Mr. Outis 29339d29a2 summon: file & method -> call 2019-12-18 23:25:45 -06:00
Mr. Outis 1b3c771df5 summon: artifacs.yaml -> dvcsummon.yaml; artifact -> object 2019-12-18 22:42:55 -06:00
Mr. Outis 0a54683f91 💅 black 2019-12-18 22:30:22 -06:00
Mr. Outis 32f35ea686 summon: remove scm.add, *_gen already handles it 2019-12-18 22:29:44 -06:00
Mr. Outis 4ce1113ccf summon: use find_out_by_relpath 2019-12-18 22:18:37 -06:00
Mr. Outis fea3d1400a summon: return empty generator for object.deps 2019-12-18 22:15:54 -06:00
Mr. Outis 5d66a572d4 summon: support overriding parameters 2019-12-18 17:20:07 -06:00
Mr. Outis d0344ce975 summon: grimoire/spells -> artifacts 2019-12-18 17:00:39 -06:00
Mr. Outis 789db9169a summon: support checking out dependencies 2019-12-18 16:41:03 -06:00
Mr. Outis b921730f37 summon: safely get a module; change naming 2019-12-18 13:32:23 -06:00
Ruslan Kuprieiev e0313660cf travis: mac: adjust bundler uninstall command 2019-12-18 18:50:03 +02:00
Ruslan Kuprieiev f4daf3bacc travis: retry choco installs 2019-12-18 09:16:49 +02:00
Mr. Outis 3d133186d4 summon: ugly hack 2019-12-18 00:48:10 -06:00
Ruslan Kuprieiev a8b5387e12 build: mac: uninstall bundler
Related to https://github.com/iterative/dvc/issues/2970
2019-12-18 08:33:22 +02:00
Ruslan Kuprieiev 477488ea0a Merge pull request #2950 from mroutis/tests-repo
tests: use dir_helpers on repo
2019-12-17 22:20:31 +02:00
Ruslan Kuprieiev 825e9fc74c Merge pull request #2952 from mroutis/tests-updater
tests: use dir helpers on updater
2019-12-17 22:18:18 +02:00
Mr. Outis e519464aca 💅 black 2019-12-17 12:21:50 -06:00
Mr. Outis 8a14700c12 tests/updater: fix problems with .check 2019-12-17 12:18:43 -06:00
Mr. Outis 7dedd7f518 tests/repo: bring back the old branch logic 2019-12-17 12:12:57 -06:00
Mr. Outis d2469f7a1f tests/repo: str -> fspath 2019-12-17 12:04:37 -06:00
Mr. Outis 1bd66fa859 tests/repo: old params in is_dvc_internal 2019-12-17 12:03:35 -06:00
Mr. Outis a83e440b32 tests/repo: remove unnecessary os.path.join 2019-12-17 11:56:48 -06:00
Mr. Outis 0de183de26 tests/repo: reload cache properly 2019-12-17 11:55:47 -06:00
Ruslan Kuprieiev 7531694f1b Merge pull request #2935 from pared/2896_import
import: test: refactor to dir helpers
2019-12-17 15:38:45 +02:00
Ruslan Kuprieiev 8ee8b9ff14 Merge pull request #2966 from Suor/relink
ui: reorder checkout options to go from common to rare
2019-12-17 15:10:57 +02:00
pawel ffe0071647 import: test: refactor to dir helpers 2019-12-17 13:57:46 +01:00
Ruslan Kuprieiev 85b1721de8 Merge pull request #2866 from pared/2473
remote: s3: adjust jobs number basing on file descriptors number
2019-12-17 14:20:36 +02:00
Ruslan Kuprieiev 032d094239 Merge pull request #2889 from Baranowski/import_regulars.squashed
import: allow downloading regular files/dirs tracked by git
2019-12-17 14:09:58 +02:00
Alexander Schepanovski 6c0cda8c7b ui: reorder checkout options to go from common to rare
This is the order we use in docs. It also makes sense.
2019-12-17 18:40:26 +07:00
Paweł RedzyńskiandRuslan Kuprieiev ab548470f8 Update dvc/main.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-12-17 11:56:22 +01:00
Paweł Redzyński 53163366b6 remote: s3: revert jobs adjustment, pass OSError to main 2019-12-17 11:41:10 +01:00
pawel 6ba4aba419 remote: base: [down/up]load: raise on too many open files 2019-12-17 11:41:03 +01:00
pawel d253f1319d remote: upload/download: handle OSError 2019-12-17 11:41:03 +01:00
pawel a0eda4c884 remote: s3: reading ulimit/handles limit: support windows 2019-12-17 11:41:03 +01:00
pawel 710c4bd32e s3: test: mocker instead of mock annotations 2019-12-17 11:41:03 +01:00
pawel c167eb2675 remote: s3: adjust jobs only on default 2019-12-17 11:41:03 +01:00
pawel 0fdb88a54c remote: s3: adjust jobs number basing on file descriptors number 2019-12-17 11:41:03 +01:00
Ruslan Kuprieiev bdfeba8f9b Merge pull request #2958 from MaxRis/gdrive-tests
Standardize gdrive tests with others remotes
2019-12-16 20:52:46 +02:00
Mr. Outis 5194543007 tests/repo: windows compat 2019-12-16 10:25:07 -06:00
Mr. Outis 59072f4d18 💅 revert black upgrade 2019-12-16 09:44:59 -06:00
Mr. Outis a7525fca64 💅 black and flake 2019-12-16 08:13:54 -06:00
Mr. Outis 576290a3c5 restyled: bump black to v19.10 2019-12-16 08:13:06 -06:00
Mr. Outis 6ed61efc05 tests/repo: refactor some tests 2019-12-16 08:08:23 -06:00
Mr. Outis 08b335d2b5 tests/repo: move unit tests to func tests 2019-12-16 08:04:28 -06:00
Ruslan Kuprieiev f51c222b1c Merge pull request #2937 from pared/2896_add_windows
temporary_windows_drive fixture: move to dir_helpers
2019-12-16 15:55:46 +02:00
Ruslan Kuprieiev 39a9fd8d79 Merge pull request #2951 from mroutis/tests-analytics
tests: use dir helpers on analytics
2019-12-16 14:41:28 +02:00
prd d40588aefa temporary_windows_drive fixture: move to dir_helpers 2019-12-16 13:15:53 +01:00
Mr. Outis f209e1eb6f tests/analytics: explicitly set content for foo 2019-12-15 13:11:40 -06:00
Mr. Outis 4b833f58e5 tests/updater: use skipif for .check 2019-12-15 13:09:23 -06:00
Wojciech Baranowski ad98f0fb50 Use fspath() in tests 2019-12-15 18:21:11 +02:00
Wojciech Baranowski c02c0169cf Revert "DependencyREPO.status: handle git-tracked files"
This reverts commit 48bf4490d5.
2019-12-15 18:14:47 +02:00
Wojciech Baranowski 7461f14845 Revert "_git_status(): use different cache dirs"
This reverts commit 8985759d66.
2019-12-15 18:14:38 +02:00
Max Risuhin 4d4a3fc716 Standardize gdrive tests with others remotes 2019-12-15 05:31:52 -08:00
Wojciech Baranowski 59156021de Revert "test_status_imported_git_file"
This reverts commit 4d59bcddfd.
2019-12-14 19:39:05 +02:00
Wojciech Baranowski 4d59bcddfd test_status_imported_git_file 2019-12-14 19:38:57 +02:00
Wojciech Baranowski 8985759d66 _git_status(): use different cache dirs 2019-12-14 14:42:51 +02:00
Wojciech Baranowski 48bf4490d5 DependencyREPO.status: handle git-tracked files 2019-12-14 14:08:38 +02:00
Wojciech Baranowski 11d890a4fa Wrap tmp_dir in str() 2019-12-14 14:08:38 +02:00
Wojciech Baranowski c8c285536f os.path.isdir(str(...)) 2019-12-14 14:08:38 +02:00
Wojciech Baranowski 5ceaf5a6f0 os.path.isfile(str(...)) 2019-12-14 14:08:38 +02:00
Wojciech Baranowski a36ac0c4ea dependency/repo: fix py2 support 2019-12-14 14:08:38 +02:00
Wojciech Baranowski 0ee6998a29 New tests use tmp_dir 2019-12-14 14:08:38 +02:00
Wojciech Baranowski c04f5a0f9d import: allow downloading regular files/dirs tracked by git
Fixes #2862
2019-12-14 14:08:38 +02:00
Mr. Outis df9f3de231 tests/repo: stringify stage.relpath 2019-12-13 19:15:25 -06:00
Mr. Outis 299fb9022f 💅 black 2019-12-13 18:04:15 -06:00
Mr. Outis 37a2844a74 tests/repo: move .stages to unit 2019-12-13 18:01:11 -06:00
Mr. Outis b3a52515a3 tests/repo: move .collect to unit 2019-12-13 17:54:05 -06:00
Mr. Outis 655b79bf0c tests/repo: move .destroy to unit test 2019-12-13 17:51:54 -06:00
Mr. Outis c178e77ab7 tests/repo: use pytest 2019-12-13 17:51:54 -06:00
Ruslan Kuprieiev 6e22769f3d dvc: bump to 0.77.3 2019-12-14 00:18:04 +02:00
Ruslan Kuprieiev 62495f4f06 Merge pull request #2949 from iterative/pull-2948-restyled
Restyle remote: use absolute from root for gdrive credentials file
2019-12-14 00:08:54 +02:00
Restyled.io a2b398255e Restyled by black 2019-12-13 22:04:20 +00:00
Eamon O'Dea e823828d6c remote: use absolute from root for gdrive credentials file
Fixes #2947
2019-12-13 16:58:11 -05:00
Ruslan Kuprieiev 1ed1a89b9f dvc: bump to 0.77.2 2019-12-13 20:58:13 +02:00
Ruslan Kuprieiev e88856dc16 Merge pull request #2943 from efiop/2942
remote: make `copy` links atomic for local/hdfs/ssh
2019-12-13 20:57:46 +02:00
Ruslan Kuprieiev 8de5286448 Merge pull request #2922 from Suor/relink
dvc: add checkout --relink
2019-12-13 20:35:10 +02:00
Ruslan Kuprieiev da49badbfe Merge branch 'master' into relink 2019-12-13 20:35:02 +02:00
Ruslan Kuprieiev 75ecc9960d dvc: bump to 0.77.1 2019-12-13 20:28:55 +02:00
Ruslan Kuprieiev 3c835909f6 Merge pull request #2931 from rbvesb/dvc-s3-acl
remote: Extend S3 remote support for custom ACL
2019-12-13 19:31:44 +02:00
Ruslan Kuprieiev d823f3b6c3 dvc: bump to 0.77.0 2019-12-13 17:43:26 +02:00
Ruslan Kuprieiev 3ed07cc448 daemon: fix typo
Thanks @pared for catching this one https://github.com/iterative/dvc/pull/2918#discussion_r357574263 . Will add a test on top, need to re-release ASAP
2019-12-13 17:42:08 +02:00
Ruslan Kuprieiev 8d7f20f688 Merge pull request #2584 from efiop/755
dvc: release locks when running a command
2019-12-13 17:35:34 +02:00
Alexander Schepanovski 5d1f01d58f checkout: iron out --relink related messages 2019-12-13 19:00:06 +07:00
Veselin Borisov cd461aec4f fixup! remote: Extend S3 remote support for custom ACL 2019-12-13 10:43:38 +02:00
Ruslan Kuprieiev e384685715 dvc: release locks when running a command
Currently only one instance of dvc could run in a particular repo
because of the `.dvc/lock`. That gets furstrating when you are running
some long-running command and blocked from doing anything else.
This PR solved that issue for `dvc run` by releasing the repo lock
while running the command. To protect against conflicts, we now use
rwlock, which keeps the list of readers and writers for particular
paths. The process looks like this:

1) aquire read locks for deps;
2) aquire write locks for outs;
3) release repo lock;
4) run cmd;
5) aquire repo lock;
6) release write locks for outs;
7) release read locks for outs;

We also take rwlocks for operations such as `import`, `add` and `checkout`
to ensure that we won't hit the same files that are being used by
another `dvc run` in the background.

We don't aquire rwlocks for the stage file itself, as we don't really
care about anyone overwriting a stage file, as there is a very little
difference here between parallel runs and sequential runs that overwrite
the same stage file.

Next steps might be to release repo lock on `import`(when dowloading
stuff) and when computing checksums or copying files.

Fixes #755
2019-12-13 10:38:10 +02:00
Ruslan Kuprieiev b8bcff5e06 remote: make copy links atomic for local/hdfs/ssh
Fixes #2942
2019-12-13 09:37:47 +02:00
Ruslan Kuprieiev ddfa46aedb Merge pull request #2941 from efiop/2559
run: provide a hint if we get output duplication error
2019-12-13 04:19:51 +02:00
Ruslan Kuprieiev 173388b2f8 run: provide a hint if we get output duplication error
Related to #2559
2019-12-13 03:36:23 +02:00
Ruslan Kuprieiev 30f4551283 dvc: bump to 0.76.0 2019-12-13 03:20:43 +02:00
Ruslan Kuprieiev 602a34dad2 Merge pull request #2918 from efiop/lock
dvc: make flufl.lock opt-in and use zc.lockfile
2019-12-13 03:17:25 +02:00
Mr. Outis c09c3aec0c remove empty file 2019-12-12 19:05:31 -06:00
Mr. Outis d0f3559d96 tests: updater, use dir helpers 2019-12-12 18:59:52 -06:00
Ruslan Kuprieiev 25807f2690 dvc: make flufl.lock opt-in and use zc.lockfile
As it turned out (see issue numbers down below), we can't really take
hardlinks for granted, so `flufl.lock` is not a panacea for all
filesystems. Considering that the vast majority of filesystems that our
users use support `zc.lockfile`(flock-based) and it has benefits like
more reliable mechanism, auto-delete when process exits, more sturdy
implementation, etc, it makes more sense to bring it back and use by
default again. For filesystems that don't support `flock()`, users will
be able to manually enable `flufl.lock` use through the config option.
It would be ideal if we could auto-detect that flock is not supported,
but in the real world, it turned out to be non-trivial, as it might hang
forever in a kernel context, which makes the implementation way too
complex for our purposes. So what we're doing instead is showing a
message before locking with `zc.lockfile` that, under normal
circumstances will disappear once the lock is taken or failed, otherwise
it will point users to the related documentation where they can learn
about how to opt-in for `flufl.lock`.

Fixes #2831
Fixes #2897
Related #2860
2019-12-13 02:25:57 +02:00
Mr. Outis 7426a62baa tests: analytics, use dir helpers 2019-12-12 18:16:10 -06:00
Ruslan Kuprieiev 6ab2322cde Merge pull request #2938 from Suor/fix-dir-helpers
test: a couple of dir helpers fixes
2019-12-12 23:20:46 +02:00
Alexander Schepanovski 1143a8c57f test: add checkout parse-cli-api test 2019-12-12 20:16:17 +07:00
Veselin Borisov 4c2bc6bf48 fixup! remote: Extend S3 remote support for custom ACL 2019-12-12 12:41:38 +02:00
Ruslan Kuprieiev fd62ba840d Merge pull request #2934 from pared/2896_checkout
checkout: test: refactor to dir helpers
2019-12-12 04:42:31 +02:00
Ruslan Kuprieiev e2d783aafd Merge pull request #2932 from pared/2896_commit
commit: test: refactor to dir helpers
2019-12-11 20:18:30 +02:00
Ruslan Kuprieiev 08347c06ee Merge pull request #2939 from casperdcl/snap
snap: remove plugs (incompatible with classic)
2019-12-11 20:04:51 +02:00
Casper da Costa-Luis ed95000bb4 remove plugs (incompatible with classic)
Details: <https://launchpad.net/bugs/1655369>
2019-12-11 17:38:09 +00:00
pawel 5f826dd2e6 commit: test: refactor to dir helpers 2019-12-11 14:23:44 +01:00
Alexander Schepanovski b8cf906edb test: stop erepo_dir from undoing parent chdir 2019-12-11 20:19:38 +07:00
Alexander Schepanovski a1643d576d test: fix run_copy fixture when used with scm 2019-12-11 20:19:02 +07:00
pawel 26ef77cf96 checkout: test: refactor to dir helpers 2019-12-11 13:54:44 +01:00
Ruslan Kuprieiev 53c33a22d9 Merge pull request #2915 from maykulkarni/import-external-repo-fix
use cache dir as a default remote when importing from local repo
2019-12-11 08:45:22 +02:00
Mayur Kularni e1e0cbd038 moved default check out of RemoteConfig 2019-12-11 10:54:59 +05:30
Ruslan Kuprieiev 6296e12f48 setup: update pyarrow version
https://github.com/apache/arrow/pull/4907
2019-12-11 06:24:49 +02:00
Ruslan Kuprieiev 5cc92a9b4c Merge pull request #2907 from efiop/import_get
import-url: add directories to examples
2019-12-11 03:23:50 +02:00
Ruslan Kuprieiev d2f40703cd Merge pull request #2926 from pared/2896_test_move
test: move: convert to dir helpers
2019-12-11 02:44:14 +02:00
Ruslan Kuprieiev 8a7cf747e9 dvc: bump to 0.75.1 2019-12-10 23:04:39 +02:00
Ruslan Kuprieiev 6bf8d487da Merge pull request #2921 from pared/2896_add_3
add: test: convert to dir helpers 3
2019-12-10 19:58:15 +02:00
Ruslan Kuprieiev a9e829ae4e Merge pull request #2925 from Suor/dani-cleaup
get: refactor, clean up and fix `dvc get` implementation
2019-12-10 19:20:54 +02:00
Veselin Borisov a160a8ea7a fixup! remote: Extend S3 remote support for custom ACL 2019-12-10 13:44:13 +02:00
Veselin Borisov 9eb1ac2d95 remote: Extend S3 remote support for custom ACL
Besides AWS boto3 extra_arg `ACL` (`x-amz-acl` header), there four more
that could be used to grant specific permissions to specific grantees on
the uploaded object.
These headers are:
  - `x-amz-grant-full-control` with boto3's extra_arg `GrantFullControl`
  - `x-amz-grant-read` with boto3's extra_arg `GrantRead`
  - `x-amz-grant-read-acp` with boto3's extra_arg `GrantReadACP`
  - `x-amz-grant-write-acp` with boto3's extra_arg `GrantWriteACP`

As `ACL` (`x-amz-acl` header) is limited to be used with predefined canned ACLs,
adding support for the aformentioned headers will help in applying
more customized persmissions solutions according to different project
needs.

References:
  - https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#permissions
  - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/s3.html#boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS
  - https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html

Fixes #(2930).
2019-12-10 12:50:34 +02:00
Mayur Kularni e6285f70c5 typo in doc fix 2019-12-10 15:38:54 +05:30
Mayur Kularni 40c392d312 type and doc fix 2019-12-10 15:31:58 +05:30
Mayur Kularni a1ae3578c0 Fix caplog test case and move call to CmdRemoteAdd 2019-12-10 11:49:19 +05:30
Ruslan Kuprieiev 9c2c2b307d s3: dynamically import botocore
https://github.com/iterative/dvc/issues/2927
2019-12-10 01:02:50 +02:00
Paweł Redzyński 267ac9b3ff test: move: convert to dir helpers 2019-12-09 19:27:28 +01:00
Alexander Schepanovski 9607e5f873 get: refactor, clean up and fix dvc get implementation
Things changed:
- both no output and no git file result into PathMissingError now
- fail on missing cache or cache download error loudly
- removed sqlite bypass, since we use nolock now anyway

Refactoring:
- streamlined logic and made it similar to Repo.open() one
- moved exclusive exception classes to dvc.repo.get
2019-12-09 21:02:49 +03:00
Ruslan Kuprieiev b3853b0b2b Merge pull request #2924 from Abrosimov-a-a/master
build_deb: Debian directory appended. Issue #2800
2019-12-09 19:21:40 +02:00
Ruslan Kuprieiev 26a9702a4a Merge pull request #2837 from danihodovic/feat/2515
get: copy/download files tracked by Git
2019-12-09 18:57:52 +02:00
Anton Abrosimov 93280c937b build_deb: Debian directory appended. Issue #2800 2019-12-09 19:14:53 +03:00
Ruslan Kuprieiev 237704ef2b Merge pull request #2908 from Suor/better-fixtures
test: write a usage text for dir_helpers
2019-12-09 18:01:33 +02:00
Ruslan Kuprieiev 8890daf703 Merge pull request #2826 from mroutis/refactor-analytics
analytics: refactor into a module
2019-12-09 17:59:51 +02:00
pawel c4565ce2ac add: test: convert to dir helpers 3
TestAddUnsupportedFile
TestAdd
TestAddDirectory
2019-12-09 16:44:56 +01:00
Ruslan Kuprieiev 24fc8beaeb Update test_ignore.py 2019-12-09 15:27:54 +02:00
Ruslan Kuprieiev 302b016d65 Merge pull request #2913 from Suor/migrate-test-ignore
test: migrate test_ignore
2019-12-09 15:25:46 +02:00
Alexander Schepanovski 3610961144 dvc: warn to run dvc checkout --relink in cache.type change 2019-12-09 13:46:10 +03:00
Alexander Schepanovski 2f79e362f4 dvc: add checkout --relink 2019-12-09 13:25:32 +03:00
Alexander Schepanovski 3b2666e46b remote: remove unused exception class 2019-12-09 12:45:56 +03:00
Alexander Schepanovski 88370e1b00 dvc: reorganize imports in dvc.remote.base 2019-12-09 12:40:56 +03:00
Mayur Kularni b271368845 Moved remote_config_exists into RemoteConfig 2019-12-09 14:14:49 +05:30
Alexander SchepanovskiandMr. Outis 1da133b9ed test: update dir_helpers usage
Co-Authored-By: Mr. Outis <[email protected]>
2019-12-09 11:34:10 +03:00
Alexander SchepanovskiandMr. Outis a59041aeab test: update dir_helpers usage
Co-Authored-By: Mr. Outis <[email protected]>
2019-12-09 11:33:35 +03:00
Alexander SchepanovskiandMr. Outis 0cd6106189 Update tests/dir_helpers.py
Co-Authored-By: Mr. Outis <[email protected]>
2019-12-09 11:24:41 +03:00
Mayur Kularni 5b9daea2b3 fixed test case 2019-12-09 12:11:49 +05:30
Mayur Kularni 84616e8e5e removed commit logic 2019-12-08 20:25:24 +05:30
Mayur Kularni 17d47ce153 merging master 2019-12-08 19:03:25 +05:30
Mayur Kularni 610e43a0ab removed original repo ops 2019-12-08 18:57:19 +05:30
Mayur Kularni c6050efbe3 codeclimate recommendation fix 2019-12-08 18:57:19 +05:30
Mayur Kularni ca20a1c93a incorporated feedback 2019-12-08 18:56:38 +05:30
Mayur Kulkarni e1c5f6c5c0 external_repo: fix for local file import fail
If the URL is local dvc repo, the import/get should fetch from the source project cache.

Fixes #2599
2019-12-08 18:52:35 +05:30
Mayur Kularni 779a0a1b15 codeclimate recommendation fix 2019-12-08 14:45:15 +05:30
Mayur Kularni 9e24be14f0 incorporated feedback 2019-12-08 14:02:44 +05:30
Ruslan Kuprieiev 3796c4182b Merge pull request #2906 from pared/2896_add_2
add: test: convert to dir helpers 2
2019-12-08 05:46:17 +02:00
Mr. Outis 5dd6300878 💅 black 2019-12-07 21:19:54 -06:00
Mr. Outis 6e4c3d6acd tests: require keys on analytics report schema 2019-12-07 21:18:28 -06:00
Mr. Outis 3244f10e7f 💅 add dot to the end of the comment 2019-12-07 21:15:49 -06:00
Mr. Outis c9f958dd00 analytics: AttributeError -> KeyError 2019-12-07 21:15:38 -06:00
Mr. Outis 146e75be3e analytics: raise error when collecting a not supported os 2019-12-07 21:15:11 -06:00
Mr. Outis c50bd17bb4 analytics: collect execution info only when available 2019-12-07 21:14:31 -06:00
Alexander Schepanovski 6c12a2e555 test: fix unicode test_ignore issue under Windows Py2 2019-12-07 17:17:18 +01:00
Mayur Kulkarni 0dd310d387 external_repo: fix for local file import fail
If the URL is local dvc repo, the import/get should fetch from the source project cache.

Fixes #2599
2019-12-07 18:39:32 +05:30
Ruslan Kuprieiev 36336f389d dvc: bump to 0.75.0 2019-12-07 14:54:36 +02:00
Alexander Schepanovski 79902988fc test: fix files set comparisons in dvcignore tests under windows 2019-12-07 13:17:08 +01:00
Alexander Schepanovski 77fcd75b7d test: use utf-8 in tmp_dir.gen() 2019-12-07 13:16:42 +01:00
Alexander Schepanovski 42096c5bfc test: migrate test_ignore to new helpers 2019-12-07 11:09:52 +01:00
Alexander Schepanovski c6c67ace20 test: create empty dirs via tmp_dir.gen() 2019-12-07 10:32:37 +01:00
Ruslan Kuprieiev 82dd1ac7c1 Merge pull request #2815 from xliiv/2807-use-pager
dvc/dagascii: Use pager instead of AsciiCanvas._do_draw
2019-12-07 01:16:16 +02:00
Tymoteusz Jankowski 44df59f96c Fix test_dagascii tests 2019-12-06 23:36:21 +01:00
Mr. Outis 2145daa130 analytics: define private methods 2019-12-06 16:26:10 -06:00
Tymoteusz Jankowski ffefab152e Remove make_pager.cmd attribute 2019-12-06 22:17:44 +01:00
Ruslan Kuprieiev 0a7abf13d5 dvc: bump to 0.74.0 2019-12-06 23:15:53 +02:00
Tymoteusz Jankowski ed4e951f22 Use monkeypatch in test_dagascii.py 2019-12-06 21:58:56 +01:00
Dani Hodovic 29b34bacc2 fixup! fixes 2019-12-06 21:55:54 +01:00
Tymoteusz Jankowski 6cfc831879 Use mocker fixture in test_dagascii.py 2019-12-06 21:54:40 +01:00
Mr. Outis 5a8033415d tests: use str, builtin_str in schema 2019-12-06 13:54:17 -06:00
Tymoteusz Jankowski 8fb754a4f4 Simplify if in find_pager 2019-12-06 20:42:10 +01:00
Ruslan Kuprieiev eb200db442 Merge pull request #2909 from iterative/efiop-patch-1
remote: base: download: don't walk dir twice
2019-12-06 21:37:19 +02:00
Dani Hodovic d59bceb71a fixes 2019-12-06 20:20:25 +01:00
Ruslan Kuprieiev cf171f7ec3 fixup 2019-12-06 21:09:53 +02:00
Ruslan Kuprieiev 52511714c0 remote: base: download: don't walk dir twice 2019-12-06 20:49:45 +02:00
Mr. Outis da064e2ede analytics: remove the report after sending it 2019-12-06 12:28:06 -06:00
Dani Hodovic 811855ade2 fixes 2019-12-06 18:56:19 +01:00
Alexander Schepanovski 5bec700761 test: accept Paths in tmp_dir.gen() 2019-12-06 18:36:17 +01:00
Alexander Schepanovski 659eb04705 test: write a usage text for dir_helpers 2019-12-06 18:34:05 +01:00
Mr. Outis f704e5468b tests: stringify tmp_global_config since it doesnt return a pathlike object 2019-12-06 10:20:30 -06:00
Ruslan Kuprieiev 9e6e430935 import-url: add directories to examples
https://github.com/iterative/dvc/pull/2894
2019-12-06 16:56:12 +02:00
pawel b39ebc8591 add: test: convert to dir helpers
test_should_relink_on_repeated_add
test_should_protect_on_repeated_add
2019-12-06 15:35:32 +01:00
Ruslan Kuprieiev 9b6644a97e Merge pull request #2898 from pared/2896_add_1
add: test: convert to dir helpers 1
2019-12-06 16:25:27 +02:00
Ruslan Kuprieiev 8fa26825ee Merge pull request #2894 from odd-industries/master
import-url: support directories
2019-12-06 16:12:04 +02:00
Vera Sativa 896b08c11d fix: test requirements 2019-12-06 10:55:18 -03:00
Vera Sativa b2e3fd2dbd fix: test imports 2019-12-06 10:20:53 -03:00
Vera Sativa 3603da89ec fix: respect no_progress_bar and remove human else 2019-12-06 10:08:02 -03:00
Vera Sativa 120f087f1c fix: tqdm reading futures 2019-12-06 10:04:05 -03:00
pawel 551f5af561 add: test: convert to dir helpers
test_add_file_in_dir
test_readding_dir_should_not_unprotect_all
test_should_not_checkout_when_adding_cached_copy
2019-12-06 09:07:50 +01:00
Mr. Outis c82d9dcbc1 compat: bring back unicode literals 2019-12-05 23:18:44 -06:00
Mr. Outis bc2471cd30 tests: adjust scm_class schema 2019-12-05 19:56:45 -06:00
Mr. Outis 7e8bdbd603 remove pathlib / fspath changes related to the patch 2019-12-05 19:52:03 -06:00
Ruslan Kuprieiev 13f6101e70 Merge pull request #2902 from mroutis/fix-2570
setup: fix compatibility errors between dependencies
2019-12-06 02:11:27 +02:00
Ruslan Kuprieiev d6ecad0b7c Update version.py 2019-12-06 02:10:34 +02:00
Ruslan Kuprieiev d2faa0f547 travis: don't forget "on" for deployment 2019-12-06 02:10:19 +02:00
Mr. Outis 2237ab6a51 setup: fix only mayor minor version for pyyaml 2019-12-05 17:47:01 -06:00
Vera Sativa 2ea4d26f46 Merge branch 'master' of github.com:odd-industries/dvc 2019-12-05 18:03:13 -03:00
Vera Sativa a0f18696dc refactor: partial into function var 2019-12-05 18:03:03 -03:00
VeraandRuslan Kuprieiev 4e53f17948 Update tests/unit/remote/test_remote_dir.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-12-05 17:41:17 -03:00
Vera Sativa 6f649db7f4 import-url: support directories: code cleanup 2019-12-05 17:39:38 -03:00
Dani Hodovic 7debaca22e fixup! fix error message for git files 2019-12-05 21:05:38 +01:00
Dani Hodovic 9ae4ab88bf fix error message for git files 2019-12-05 21:05:04 +01:00
Dani Hodovic b17d1c5b04 fixup 2019-12-05 21:05:03 +01:00
Dani Hodovic 20e7696da3 return early 2019-12-05 21:05:03 +01:00
Dani Hodovic 3c2633e33f Pass all tests 2019-12-05 21:05:03 +01:00
Dani Hodovic 38bed227c6 fixup 2019-12-05 21:05:03 +01:00
Dani Hodovic 9930e96390 fixup! ensure absolute paths for dvc files work but not for git 2019-12-05 21:05:03 +01:00
Dani Hodovic 97793b4fb3 underscore private function 2019-12-05 21:05:03 +01:00
Dani Hodovic f829338e1e if instead of try / catch 2019-12-05 21:05:02 +01:00
Dani Hodovic 9ece4ed8ad ensure absolute paths for dvc files work but not for git 2019-12-05 21:05:02 +01:00
Dani Hodovic ed10497854 Remove unused var 2019-12-05 21:05:02 +01:00
Dani Hodovic cadbf10e64 dont use absolute paths in tests 2019-12-05 21:05:02 +01:00
Dani Hodovic 74f3851117 Disallow absolute paths in dvc get 2019-12-05 21:05:02 +01:00
Dani Hodovic 1f5da3ffef fix CLI docs 2019-12-05 21:05:01 +01:00
Dani Hodovic 7e1423d13c fix tests 2019-12-05 21:05:01 +01:00
Dani Hodovic 26b62331c7 CLI --help changes 2019-12-05 21:05:01 +01:00
Dani Hodovic 8c3ffe816d fixup! Make tree.walk() output deterministic 2019-12-05 21:05:01 +01:00
Dani Hodovic 97862a3d5f Make tree.walk() output deterministic 2019-12-05 21:05:01 +01:00
Dani Hodovic 4d14256995 fixup! get: copy regular files 2019-12-05 21:05:01 +01:00
Dani Hodovic af2f216c5e get: copy regular files
Allows `dvc get` to copy regular files or directories.

fixes: #2515
2019-12-05 21:05:00 +01:00
Vera Sativa f7e0b575bd tests: add download_dir 2019-12-05 16:51:24 -03:00
VeraandRuslan Kuprieiev 164ded6453 Update dvc/remote/base.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-12-05 16:45:43 -03:00
VeraandRuslan Kuprieiev 3af4f85310 Update dvc/remote/base.py
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
2019-12-05 16:45:13 -03:00
Vera Sativa cf6da7bd8c import-url: support directories: multi-thread working 2019-12-05 16:28:35 -03:00
Mr. Outis 4fd701317b setup: fix compatibility errors between dependencies
fix #2570
2019-12-05 13:19:23 -06:00
Vera Sativa f6c719a6f3 feat: [WIP] dir download working single-thread 2019-12-05 14:53:01 -03:00
Ruslan Kuprieiev 4917a6e818 dvc: bump to 0.72.0 2019-12-05 16:25:14 +02:00
Ruslan Kuprieiev 73e69be067 Merge pull request #2882 from Suor/revert-checkout
dvc: revert checkout to be silly and fast, relink on dvc add though
2019-12-05 16:07:52 +02:00
Vera Sativa 9d0cfcafdf feat: [WIP] remote dir download 2019-12-05 11:05:47 -03:00
Ruslan Kuprieiev c328075c8b Merge pull request #2868 from Suor/better-fixtures
test: refactor tmp dir helper fixtures
2019-12-05 16:00:37 +02:00
Ruslan Kuprieiev baee120729 Merge pull request #2892 from efiop/2885
state: quote % in path
2019-12-05 15:59:24 +02:00
Ruslan Kuprieiev 57b0596bfe state: quote % in path
Fixes #2885
2019-12-05 15:25:21 +02:00
Mr. Outis 71ead3f62e analytics: use a tempfile for inter-process communication 2019-12-04 19:47:06 -06:00
Mr. Outis 05a68684fd tests: isolate global config from analytics tests
fix #2806
2019-12-04 19:44:12 -06:00
Mr. Outis 7a3aae1c7e tests: add unit test for analytics.system_info 2019-12-04 19:43:00 -06:00
Mr. Outis ede010384b py2: use convert_to_unicode instead of str 2019-12-04 19:31:34 -06:00
Tymoteusz Jankowski 128f60ef68 Add test_dagascii.py + make find_pager() use DVC_PAGER 2019-12-04 20:51:24 +01:00
Tymoteusz Jankowski c864ca9384 Move is_exec_found(..) to dvc/dagascii.py 2019-12-04 20:15:49 +01:00
Ruslan Kuprieiev 516e3b14bc Merge pull request #2852 from pared/2848_get_no_dvc_master
external repo: checkout revision before initializing dvc repo
2019-12-04 16:20:00 +02:00
pawel ca852170c1 get: test: from non-dvc master: refactor 2019-12-04 14:34:50 +01:00
Paweł Redzyński f1d59dd64d conftest: empty dir is tmp_path now 2019-12-04 13:10:06 +01:00
Paweł Redzyński 97e4b024c1 external repo: move revision check out of checkout function 2019-12-04 13:10:06 +01:00
pawel 5e5c82652b test: get: inline erepo with non-dvc master 2019-12-04 13:10:06 +01:00
Paweł Redzyński 5c0c4239ec rename TestDirFixtureBase 2019-12-04 13:10:06 +01:00
Paweł Redzyński b9d5933f5d external repo: checkout rev before initializing repo 2019-12-04 13:10:06 +01:00
Paweł Redzyński b788a991f9 conftest: create empty dir fixture 2019-12-04 13:10:06 +01:00
Paweł Redzyński 06d6c2c25c test env: extract empty dir fixture to class 2019-12-04 13:10:06 +01:00
Mr. Outis 02eaa9a14f py35: support for fspath(pathlib.Path()) 2019-12-03 18:38:15 -06:00
Mr. Outis 285620d96e tests: mock analytics.collect while testing daemon 2019-12-03 09:41:23 -06:00
Ruslan Kuprieiev e63bc01e53 Merge pull request #2883 from casperdcl/readme-native
much grammar such fix
2019-12-03 15:24:20 +02:00
Ruslan Kuprieiev 17788790a0 Merge pull request #2857 from casperdcl/short-progress
lengthen bars unless nested/threaded
2019-12-03 14:56:31 +02:00
Ruslan Kuprieiev f142188373 Merge pull request #2875 from casperdcl/stream-progress
Stream progress
2019-12-03 14:55:56 +02:00
Casper da Costa-Luis 5aa2127bfe such syntax much wow 2019-12-03 11:13:43 +00:00
Mr. Outis 624cbb2d91 💅 more specific comment about analytics 2019-12-02 23:46:04 -06:00
Mr. Outis 8a32b28b54 analytics: collect and send on daemon 2019-12-02 22:54:16 -06:00
Mr. Outis 8b5ca253ec analytics: move report schema to tests 2019-12-02 22:48:09 -06:00
Mr. Outis e0d47a1382 💅 formatting 2019-12-02 21:57:06 -06:00
Mr. Outis ca485db240 analytics: define report schema and use it on tests 2019-12-02 21:52:00 -06:00
Mr. Outis 2c8e1495b8 analytics: use fspath instead of str 2019-12-02 21:27:31 -06:00
Casper da Costa-Luis 27d2e71460 fix rst syntax 2019-12-03 01:36:04 +00:00
Casper da Costa-Luis 464bcad82e much grammar such fix 2019-12-03 00:55:26 +00:00
Mr. Outis 55960154c5 analytics: document why tmp_global_config is needed 2019-12-02 17:51:35 -06:00
Mr. Outis 946dfc1400 analytics: send report without loading it to memory
shout-out to @Suor for the suggestion
2019-12-02 17:51:32 -06:00
Jorge Orpinel f91aa0665f Merge pull request #2881 from pared/2711_3
NoRemoteInExternalRepoError: fix message
2019-12-02 17:15:58 -06:00
c27988a042 test: minor fixture refactor fixes from @pared and @efiop
Co-Authored-By: Ruslan Kuprieiev <[email protected]>
Co-Authored-By: Paweł Redzyński <[email protected]>
2019-12-02 23:33:44 +01:00
Alexander Schepanovski 0385810662 test: fix python 2, 3.5 and mac issues with new fixtures 2019-12-02 23:32:11 +01:00
Alexander Schepanovski fcebad821e test: refactor tmp dir helper fixtures
- `tmp_dir` descends from Path
- convenient template methods to generate files and dirs, add them to
dvc and git and commit to git
- independent of old basic_env structures
- can go in any order
- modular: start with empty `tmp_dir`, initialize git with `scm`, dvc
repo with `dvc` add "repo template" with `repo_template`, ...
2019-12-02 23:31:54 +01:00
Alexander Schepanovski d1cf770a3c dvc: don't make an unneeded copy on commit
Fix #2016 without all the complications of #2358.
2019-12-02 23:08:45 +01:00
Ruslan Kuprieiev c2ffff0a0f updater: add an entry for chocolatey package
https://github.com/iterative/chocolatey-dvc/issues/1
2019-12-02 22:52:22 +02:00
Tymoteusz Jankowski 8977958371 Make pager info box to be a simple warning 2019-12-02 20:36:51 +01:00
Tymoteusz Jankowski ab3ccf0243 Use os.devnull + do not check if system exists for os module 2019-12-02 20:27:20 +01:00
Alexander Schepanovski 30d58e14a0 Revert "remote: base: don't checkout existing file (#2358)"
This reverts commit 5ec1d274ae.

Save tests to try make them work, and `_get_cache_type()` to be used
later.
2019-12-02 19:06:35 +01:00
Casper da Costa-Luis c622287cd3 truncate long desc 2019-12-02 17:43:11 +00:00
Casper da Costa-Luis 7318a6d120 safer download notification 2019-12-02 17:35:29 +00:00
Ruslan Kuprieiev d42a04e647 Merge pull request #2778 from casperdcl/snap
Snap
2019-12-02 19:09:25 +02:00
Casper da Costa-Luis 52c48b56a2 add snap approval request to readme 2019-12-02 16:44:12 +00:00
Casper da Costa-Luis 0acb1e8e3e revert devmode
- didn't work
- almost completely reverts c94acdf2a9
2019-12-02 16:39:18 +00:00
Alexander Schepanovski 4ab4e43606 dvc: return to using .changed() shortcut in .checkout() 2019-12-02 14:38:39 +01:00
Paweł Redzyński 9aec435cac NoRemoteInExternalRepoError: fix message 2019-12-02 11:58:09 +01:00
Tymoteusz Jankowski f79711d155 Fix code checks 2019-12-01 19:48:56 +01:00
Tymoteusz Jankowski a6a3cbcffa Reduce nested ifs in find_pager 2019-12-01 19:45:06 +01:00
Tymoteusz Jankowski 3e163f1934 Rename and move DVC_PAGER_ENV_NAME to env.py file 2019-12-01 19:13:56 +01:00
Tymoteusz Jankowski cade3b396c Add is_exec_found to utils and use it in dagascii module 2019-12-01 19:04:04 +01:00
Ruslan Kuprieiev 3d70a1efef Merge pull request #2853 from skshetry/gs-external-deps
Support adding directories in google cloud storage remote
2019-12-01 14:49:35 +02:00
Tymoteusz Jankowski c17cf2b2f7 dvc/dagascii: Use less instead of AsciiCanvas._do_draw
Uses less CLI tool to paging the output in the interactive mode while
doing e.g.  `dvc pipeline show ..`.

Fixes #2807
2019-12-01 13:23:09 +01:00
Saugat Pachhai 6ba7b1d9a3 gs: adjust tests 2019-12-01 17:26:07 +05:45
Ruslan Kuprieiev 24d3a87708 Merge pull request #2873 from skshetry/fix-2871
s3: Check for all files in given path to match
2019-12-01 13:24:50 +02:00
Saugat Pachhai 37fcced4ad s3: raise exception if not 404 status code 2019-12-01 16:28:31 +05:45
Saugat Pachhai 4f011f62c7 use yield and staticmethod 2019-12-01 16:24:22 +05:45
Saugat Pachhai 22c0a644e5 address @efiop's suggestions 2019-12-01 16:08:53 +05:45
Saugat Pachhai 4821aa2efa s3: catch specific exception when head_object() 2019-12-01 14:47:56 +05:45
Saugat Pachhai da0d9e8599 s3: implement isfile() 2019-12-01 10:58:53 +05:45
Saugat Pachhai f4f836e17c Merge branch 'master' into gs-external-deps
* master:
  travis: windows: use python 3.7.5
  test: skip non supported remotes fast in api tests
2019-12-01 09:01:47 +05:45
Saugat Pachhai 7e62a7d7e5 gs: implement isfile 2019-12-01 08:56:07 +05:45
Casper da Costa-Luis f54460f2cb merge unneeded function 2019-12-01 02:54:01 +00:00
Saugat Pachhai 25f0e0765c gs: use Blob.exists() instead of _list_paths to check for blob 2019-12-01 08:25:50 +05:45
Casper da Costa-Luis 75f8b9cd8b start to clean up gdrive 2019-12-01 02:31:57 +00:00
Casper da Costa-Luis ecdc608e13 clean up gs progress using Tqdm.wrapattr
- fixes #2829
2019-12-01 02:22:04 +00:00
Ruslan Kuprieiev f09517ad09 travis: windows: use python 3.7.5 2019-11-30 20:11:11 +02:00
Casper da Costa-Luis c94acdf2a9 use --devmode as it looks less scary 2019-11-30 17:26:48 +00:00
Casper da Costa-Luis a0ac129d5f cleanup things to revert after classic
https://forum.snapcraft.io/t/classic-confinement-request-for-dvc/14124
2019-11-30 17:23:57 +00:00
Casper da Costa-Luis 7fcb4bff9c back to --classic 2019-11-30 17:22:12 +00:00
Saugat Pachhai 2e1f3114cd dvc: write tests 2019-11-30 19:47:30 +05:45
Saugat Pachhai 13e5dbafd0 s3: check for all files in given path 2019-11-30 19:20:32 +05:45
Saugat Pachhai 433d2f8017 test: remove nested contextmanagers, return False on exists() 2019-11-30 19:12:35 +05:45
Ruslan Kuprieiev 30848306e8 Merge pull request #2870 from Suor/test-api-fast-skip
test: skip non supported remotes fast in api tests
2019-11-30 15:15:00 +02:00
Saugat Pachhai 8a62e2f92b gs: check if the path is directory when checking for exists 2019-11-30 18:03:41 +05:45
Saugat Pachhai 42991d3b75 Move more stuff from data_cloud to tests/remotes.py 2019-11-30 10:04:45 +05:45
Saugat Pachhai 63cf117ee9 Move to tests/remotes.py 2019-11-30 08:58:43 +05:45
Saugat Pachhai 4714929b5b Merge branch 'master' into gs-external-deps
* master:
  address @efiop's suggestions
  s3: add support for top level empty directories
  remote: http: calculate length basing on response and not head call
  test: remote: http: clear test file name
  HTTPError: reformat error message
  Update dvc/remote/http.py
  remote: http: rename http error, refactor test
  remote: http: raise exception when download response with error status code
  Support GDrive as remote
  setup: don't forget contextlib2
  remote: refactor ssh ask password code
  remote: protect all remote client/session creation code with locks
  test: refactor & remove redundant test fixtures
  NoRemoteInExternalRepoError: dont pass cause of exception
  perf: optimize cache listing for local, ssh and hdfs
  remote: small .save_info()/.get_checksum() cleanup
2019-11-30 08:39:56 +05:45
Saugat Pachhai caa67c725e dvc: refactor tests 2019-11-30 08:22:39 +05:45
Casper da Costa-Luis ae274cbab7 try devmode 2019-11-30 01:46:48 +00:00
Casper da Costa-Luis f6e92184e3 ready for merge 2019-11-30 01:19:13 +00:00
Casper da Costa-Luis 8fa7d7cfd7 fix git 2019-11-30 01:01:20 +00:00
Casper da Costa-Luis f60d795709 document connecting plugs before approval 2019-11-30 00:35:41 +00:00
Casper da Costa-Luis ebe80cd60a attempt to use layout 2019-11-30 00:28:56 +00:00
Ruslan Kuprieiev 0404a2324e Merge pull request #2551 from MaxRis/gdrive-support
GDrive remote support
2019-11-30 01:21:31 +02:00
Alexander Schepanovski 865ed6f8ea test: skip non supported remotes fast in api tests 2019-11-29 23:42:26 +01:00
Casper da Costa-Luis b36a49120b attempt to fix dvc config --system 2019-11-29 21:49:53 +00:00
Casper da Costa-Luis d0cb359060 try strict confinement 2019-11-29 21:19:44 +00:00
Casper da Costa-Luis b713a62be9 potential git discovery for removable media fix 2019-11-29 21:17:03 +00:00
Casper da Costa-Luis 537d51b535 use strict-confinement-compatible git 2019-11-29 21:16:33 +00:00
Casper da Costa-Luis 577ad548b0 move snapcraft to subdir 2019-11-29 19:41:41 +00:00
Ruslan Kuprieiev 94f803e043 Merge pull request #2683 from mroutis/fix-2678
s3: ignore empty directories while walking files
2019-11-29 21:00:39 +02:00
Casper da Costa-Luis c0058a6f66 actually use PKG to determine package type 2019-11-29 18:46:57 +00:00
Casper da Costa-Luis f616265c95 create dedicated SNAP env var
only for update skipping for now, instead of piggybacking off $CI
2019-11-29 18:39:05 +00:00
Mr. Outis 1f9a0f0e0f address @efiop's suggestions 2019-11-29 12:38:12 -06:00
Mr. Outis 845cd352f7 s3: add support for top level empty directories 2019-11-29 11:46:28 -06:00
Ruslan Kuprieiev 6ad278f54c Merge pull request #2794 from pared/2510
remote: http: raise exception when response with error status code
2019-11-29 16:58:47 +02:00
pawel 03c27192a6 remote: http: calculate length basing on response and not head call 2019-11-29 15:31:04 +01:00
pawel f7846a5bed test: remote: http: clear test file name 2019-11-29 15:30:55 +01:00
pawel c102a781c2 HTTPError: reformat error message 2019-11-29 15:30:55 +01:00
Paweł RedzyńskiandAlexander Schepanovski e084b427e3 Update dvc/remote/http.py
Co-Authored-By: Alexander Schepanovski <[email protected]>
2019-11-29 15:30:55 +01:00
Paweł Redzyński 7218ff202c remote: http: rename http error, refactor test 2019-11-29 15:30:55 +01:00
Paweł Redzyński 72abfb0572 remote: http: raise exception when download response with error status code 2019-11-29 15:30:55 +01:00
Max Risuhin e7227898e4 Support GDrive as remote 2019-11-29 01:21:10 -08:00
Ruslan Kuprieiev 510ff8a9da Merge pull request #2863 from Suor/locks
remote: protect all remote client/session creation code with locks
2019-11-29 11:18:56 +02:00
Saugat Pachhai ee69ac835d gs: make it work with empty dir 2019-11-29 07:24:11 +05:45
Ruslan Kuprieiev 593ab17d25 setup: don't forget contextlib2 2019-11-29 02:14:25 +02:00
Mr. Outis 656f986542 💅 correct wording "disenabled" -> "disabled" 2019-11-28 17:47:14 -06:00
Ruslan Kuprieiev 6b4242a904 Merge pull request #2861 from danihodovic/refactor/remove-redundant-vars
test: refactor & remove redundant test fixtures
2019-11-29 01:24:27 +02:00
Alexander Schepanovski 8af1aeefb4 remote: refactor ssh ask password code 2019-11-28 21:10:35 +01:00
Alexander Schepanovski 73eac3b6cc remote: protect all remote client/session creation code with locks
With @cached_property last write wins, so all threads will use the same
session. However, several might be created concurrently wasting
resources and even potentially asking something from the user.

This change guarantees only one client instance will be created for a
remote.
2019-11-28 21:05:20 +01:00
Dani Hodovic 78c505f6cb test: refactor & remove redundant test fixtures
https://github.com/iterative/dvc/pull/2837#discussion_r351318487
2019-11-28 19:04:56 +01:00
Ruslan Kuprieiev 838efb293f Merge pull request #2836 from Suor/lazy-cache-paths
perf: optimize cache listing for local, ssh and hdfs
2019-11-28 13:23:46 +02:00
Ruslan Kuprieiev 00744bc794 Merge pull request #2835 from Suor/opt-checkout
remote: small .save_info()/.get_checksum() cleanup
2019-11-28 08:55:25 +02:00
Saugat Pachhai 54dd9890dc gs: return relative path in list_cache_paths 2019-11-28 07:37:14 +05:45
Mr. Outis e69fffa7c7 py2 compat issues 🤷 2019-11-27 18:39:01 -06:00
Saugat Pachhai db12416eac remove extra args 2019-11-28 06:05:30 +05:45
Saugat Pachhai fe248aa130 dvc: use replace instead of posixpath 2019-11-28 05:47:04 +05:45
Saugat Pachhai 0c08c09e5d dvc: append slash at the end for dir path_info 2019-11-28 05:45:30 +05:45
Ruslan Kuprieiev 78942ca4da Merge pull request #2854 from pared/2711_2
RemoteNotSpecifiedInExternalRepoError: dont pass cause of exception
2019-11-27 22:30:05 +02:00
Mr. Outis 787ea9cd31 tests: set temporary global config 2019-11-27 12:37:11 -06:00
Saugat Pachhai 5e2150d509 remote/gs: set max_results to improve performance when checking exists/isdir 2019-11-28 00:07:49 +05:45
Saugat Pachhai e750496123 remote/gs: support adding directories 2019-11-28 00:07:49 +05:45
Paweł Redzyński 31aed9b27b NoRemoteInExternalRepoError: dont pass cause of exception 2019-11-27 10:05:16 +01:00
Mr. Outis e9d56a969e 💅 sort imports 2019-11-26 19:39:31 -06:00
Mr. Outis 2a5cac5570 💅 deepsource 2019-11-26 19:34:49 -06:00
Mr. Outis f9952046fd tests: functional and unit tests for analytics 2019-11-26 19:29:20 -06:00
Mr. Outis 6bc24039db 💅 black 2019-11-26 18:33:16 -06:00
Mr. Outis 48b4cbad45 💅 change naming and docstring 2019-11-26 18:32:39 -06:00
Mr. Outis 467b110929 py2: FileNotFoundError -> IOError 2019-11-26 18:32:02 -06:00
Casper da Costa-Luis 36fc2b05ad partiallundo reorder 2019-11-26 21:59:44 +00:00
Casper da Costa-Luis 78c9e32312 reorder desc & bar 2019-11-26 21:56:25 +00:00
Casper da Costa-Luis 857c01d075 lengthen bars unless nested/threaded
Partially fixes #2487
2019-11-26 21:29:24 +00:00
Mr. Outis 5261ed7148 tests: analytics.find_or_create_user_id 2019-11-26 13:40:11 -06:00
Mr. Outis 34d3004779 :face_palm: fix wrong module 2019-11-26 12:07:18 -06:00
Mr. Outis d958870edf analytics: exit_code -> return_code
shout-out to @efiop for the suggestion
2019-11-26 10:45:21 -06:00
Mr. Outis 610d1ee28e analytics: go back to daemon implementation
Lack of a better way to spawn a detached process on both windows an linux
2019-11-25 22:30:16 -06:00
Mr. Outis 5cb437b5ef analytics: move to single file
shout-out to @Suor
2019-11-25 22:30:05 -06:00
Mr. Outis 2e7a6d3fe5 💅 sort methods 2019-11-25 22:30:05 -06:00
Mr. Outis 64d102caaa analytics: move user_id module to a method
shout-out to @Suor for the suggestion
2019-11-25 22:30:03 -06:00
Mr. Outis 7d3cbd789c analytics: collect system info under the same module 2019-11-25 21:41:17 -06:00
Mr. Outis b618d03cfd analytics: return OS when system info not supported 2019-11-25 21:30:25 -06:00
Casper da Costa-Luis ba12e1c457 test eveything apart from gssapi 2019-11-24 00:20:37 +00:00
Casper da Costa-Luis 5f13e2215d bash completion 2019-11-23 23:53:52 +00:00
Casper da Costa-Luis 8e58849f0b suppress update check, add comments 2019-11-23 23:25:42 +00:00
Casper da Costa-Luis d82bb976e6 full installation 2019-11-23 23:24:01 +00:00
Alexander Schepanovski 8681cc3cd1 perf: optimize cache listing for local, ssh and hdfs
- make all three lazy
- simplify local one
- use deque instead of list for hdfs
2019-11-22 14:43:01 +01:00
Alexander Schepanovski 8da0390c57 remote: small .save_info()/.get_checksum() cleanup 2019-11-22 12:58:08 +01:00
Casper da Costa-Luis 18eeb2cd84 fix channel name 2019-11-21 22:18:26 +00:00
Mr. Outis ef1f038c32 analytics: refactor into a module 2019-11-20 13:31:44 -06:00
Casper da Costa-Luis 47e1b49e2a PKG = "snap"
Fixes https://github.com/iterative/dvc/pull/2778#issuecomment-554651335
2019-11-16 21:03:54 +00:00
Casper da Costa-Luis f944ce2ecd documnet --classic 2019-11-12 23:15:16 +00:00
Casper da Costa-Luis c23b8007c4 potential deploy catch 2019-11-12 23:01:32 +00:00
Casper da Costa-Luis 9137ad5f18 add minor comment abous stage tags
Fixes https://github.com/iterative/dvc/pull/2778#discussion_r345424578
2019-11-12 22:48:14 +00:00
Casper da Costa-Luis 31b3bcc0e2 PyPI and GH on tags, Snap on master and tags 2019-11-12 22:43:57 +00:00
Casper da Costa-Luis 73f9f3c9d5 fix connectivity and permissions => back to --classic 2019-11-12 20:12:19 +00:00
Casper da Costa-Luis 8f06e76aac document and add badge 2019-11-12 17:25:34 +00:00
Casper da Costa-Luis 424bdbe286 stable and edge releases 2019-11-12 15:18:30 +00:00
Casper da Costa-Luis c2c98b007a minor tidy 2019-11-12 13:47:52 +00:00
Casper da Costa-Luis d2a6b65711 fix permissions 2019-11-12 02:45:18 +00:00
Casper da Costa-Luis 8212dc7992 travis snap tidy 2019-11-12 01:33:49 +00:00
Casper da Costa-Luis 4c3bf028e9 fix snap build 2019-11-12 01:24:28 +00:00
Casper da Costa-Luis 8c17ec14f9 initial snap support 2019-11-12 00:05:58 +00:00
Casper da Costa-Luis e18fe609cb remove deprecated sudo 2019-11-11 23:43:05 +00:00
Casper da Costa-Luis 226b598dea remove unneeded stage names 2019-11-11 23:42:43 +00:00
226 changed files with 7187 additions and 5355 deletions
+50 -52
View File
@@ -2,7 +2,6 @@ cache:
directories:
- $HOME/.cache/pip
- $HOME/.pyenv
sudo: true
dist: xenial
env:
global:
@@ -14,19 +13,19 @@ stages:
- check
- test
- build
- name: deploy
if: tag is present
# Travis doesn't support testing python on Mac yet, so we need
# to workaround it by installing it directly with brew.
#
# Note that we specify language as ruby-2.4.3 to help us satisfy
# fpm dependencies, as travis runs el capitan with an old ruby
# version, that causes fpm to crash.
#
# Each job will use the previous job's `stage` tag if not specified.
jobs:
include:
# patch formatting checks
- name: "Check patch formatting"
stage: "check"
stage: check
os: linux
language: python
python: 3.7
@@ -34,44 +33,22 @@ jobs:
install:
script: ./scripts/ci/check_patch.sh
# test jobs
- name: "2.7 on Windows"
stage: test
os: windows # Windows 10.0.17134 N/A Build 17134
language: shell # 'language: python' is an error on Travis CI Windows
env:
- PY2=1
- name: "3.7 on Windows"
stage: test
os: windows # Windows 10.0.17134 N/A Build 17134
language: shell # 'language: python' is an error on Travis CI Windows
- name: "2.7 on Linux"
stage: test
language: python
python: 2.7
env:
- PY2=1
- name: "3.5 on Linux"
stage: test
language: python
python: 3.5
- name: "3.6 on Linux"
stage: test
language: python
python: 3.6
- name: "3.7 on Linux"
stage: test
language: python
python: 3.7
- name: "2.7 on Mac"
stage: test
- name: "3.7 on Mac"
os: osx
osx_image: xcode9.4
env:
- PY2=1
- name: "3.6 on Mac"
stage: test
os: osx
osx_image: xcode9.4
osx_image: xcode11.3
# build jobs
- name: "Mac pkg"
stage: build
@@ -82,44 +59,40 @@ jobs:
install:
script: ./scripts/build_posix.sh
- name: "Windows pkg"
stage: build
os: windows
language: shell
install:
script: ./scripts/build_windows.cmd
- name: "Linux pkgs"
stage: build
language: python
python: 3.7
before_install:
install:
script: ./scripts/build_posix.sh
- name: "Pypi pkgs"
stage: build
- name: "PyPI pkgs"
language: python
python: 3.7
script: ./scripts/build_package.sh
# deploy jobs
- name: "Pypi pkgs"
stage: deploy
- name: Snapcraft snap
language: python
python: 3.7
script: ./scripts/build_package.sh
deploy:
- provider: pypi
user:
secure: "IOzNfgPoG3ICg3+s4AuZCCnawgd/KqMzeiAB9R0B9Xb6ProPQJp9O3GBZrtg5OJMHdWQQtwULeOh/LWk4sFdXMcX1iNEgm5Tb7DwO7TDtltW14jUgN9AqfY2twj4UIsW/ON8w0P8v36ZoQq1kO6Pfgjijdm3w+9h6rpps8rJ6jXYpVZVO+SSzzijWddanMkEz4GgctZ2r7U5MWlmm1UogF60kEZIZCQykz0yJnhTpP9nMFDKw6Maki/mcfVwTQ/v3RqjzttHei/aaEMGQXYr3TOX8NPYJXerBpjShDZqcgcCfqPzhi7uCwVYaPvRsMMYH5jgtMz/SO3J6ToeGKT0f1F1ihRyCdusg325TMK7Vv8yHJzL4ngeUbb/oqxWtxhLY7f77Lkx6hBwe6ROpTqiQHIfZad5qyIHihKcDcIHo4FwLHVzxR4y+Ds2js5dXwBkUBaEAx0Dmc6ZdgXqxd815GDwv+VVpzXJhtO6qXmKE36F/Oe6eCNG3dk5MdOK4C2DrM9M8MXllqeQtX6JrM2stWlBalAc9Jn4b0xxAoghX3x3G3hNsLc9Uist1VinY2+IcPozusqj66c+Vbi5YngWSbICOjlIcq2GaOlBi3xioGts396442ztf1eLjMVuvykCI3b4Yz/vJYZOZjUvG/6nLfaFQTJLSXkTvI4do3foeY4="
password:
secure: "ts/3FSPL+w4euXryip9wCJWUqUwuJ2CqHi0nEpYVM10KL0cp9aMkQXN/7C8ih+j/JZ25kaO+tis1nx0JERVnKrg/1F/fJhxHTpakCP4OSIlhKRXDt7yUUHcKKt5QdM2XCKhApptP6RTpWL1vlc36CMrhdvTcMYp7hlDOiWjdhhim24FGQzabL42Ko59ttyyCROqgcnjHqj+WlhJl7iYO+lv2PLOtslpkIYY5EMjgBLuGyOvxTqZsAXtworsASdB2CrsTE6iMRcrM1afgkyBlUqDezoiv9TwfZrZ85oRoAxPB7su4QWK7lEP0wGQV/SDz/RL7c+cNfRaauxUKVcC8I2LfaEpudv8vi205+v/08wzPbS4xSOnf89irUJnyGxUy+bYLoXPPVPMyzcQRAUnrQl+ENCmN62HU+/2pkIyiUJvt0wYYP0vp+3pIbXbYQGmAIMq7nZJd7K3rikfnQIGKI03xZsFe3PEdjzhDI9Ej/tRUbJx4pgg084QuYYca6MSYn/zJ5pqIG4A7ngCSxoxoWZT0duo8PrUWQeT17L73Q86HeEv3OzZhWfEF2fNDN3Qbsu4CllSZnbE7DXgAuysTwTYHS/na6lp7wejOqdRUXrHbidVla9Mwxyc9JdWNWzUDxALvNxAdNbV6l85mO8izy8wjQF6M1O7idqTfjQeR4pc="
skip_existing: true
file_glob: true
file:
- dist/dvc*.whl
- dist/dvc*.tar.gz
skip_cleanup: true
on:
tags: true
repo: iterative/dvc
addons:
snaps:
- name: snapcraft
channel: stable
confinement: classic
- name: lxd
channel: stable
env:
- SNAPCRAFT_IMAGE_INFO: '{"build_url": "$TRAVIS_BUILD_URL"}'
install:
- sudo /snap/bin/lxd.migrate -yes
- sudo /snap/bin/lxd waitready
- sudo /snap/bin/lxd init --auto
script:
- ./scripts/build_snap.sh
after_failure:
- sudo journalctl -u snapd
before_install:
- bash ./scripts/ci/before_install.sh
@@ -131,7 +104,6 @@ script:
- bash ./scripts/ci/script.sh
deploy:
- provider: releases
condition: "[[ $(./scripts/ci/deploy_condition.sh) == 'true' ]]"
api_key:
secure: nyo17gF4Xl6DFL/KVkMewC805Cv+eK99L7SiYCU+WUAYDIzzFL3YJm7fm8bznPeor7DmpO7uSLSveMnJETBtdurW6JWhCTUss4yD66yFzpk6Z5cvHf/1UA5pdMHkkMM54fRaTRt6L2Yal7RPwGEQTBECLXW1Xmf78Y6+0l8lTvXQ9jXOQSRO/63VDKI5Zww2+XytVicUFlr0hLMGKUuaCjjXcQlAPzBgQz3/5Iz6EtzeTf2o+gTPjocbRbSFPikfiBXUn8XlJ8jBj7i114XJRJffw59KxYGDzxp+enfLBULGNoW0Np2hZqkKkPUIdB08C8XVbRXlFYX8cEYBczjqhv8A05p5F5CnltnqQ0KazcoUP2awPVqn68dWNRF+kqrnMBus8euYKIZ4dy/6bVEKJRa84ESJXZVDcwXTAXRqyovSxOlXqg9ZgAImMKBfKn/fpDX2xIvQPaXcnJxKbmcePfdbC1d0HYJ5jkbNaxKE/T15VvhzIho7mJ2WAv1r8QouQUuzqtpzIGbRn/KXHRUzCtyP0k4+eMKMLFOPzg848WsDarP3v8VYiSy4nFY7ZP7DRd3KttvW5bgVbSeO4jPmTgS6TnnpRwual9BJXSG7bjbfpIMii3bNZ7jWL4zpKT9Jfm8w9RGKLcX9HqkZ3+y60+LtYdhEfBvqsIlWQoIJBXg=
file_glob: true
@@ -142,6 +114,32 @@ deploy:
- dvc*.exe
skip_cleanup: true
on:
condition: "$(./scripts/ci/deploy_condition.sh dvc*.rpm dvc*.deb dvc*.pkg dvc*.exe)"
tags: true
repo: iterative/dvc
stage: build
- provider: pypi
user:
secure: "IOzNfgPoG3ICg3+s4AuZCCnawgd/KqMzeiAB9R0B9Xb6ProPQJp9O3GBZrtg5OJMHdWQQtwULeOh/LWk4sFdXMcX1iNEgm5Tb7DwO7TDtltW14jUgN9AqfY2twj4UIsW/ON8w0P8v36ZoQq1kO6Pfgjijdm3w+9h6rpps8rJ6jXYpVZVO+SSzzijWddanMkEz4GgctZ2r7U5MWlmm1UogF60kEZIZCQykz0yJnhTpP9nMFDKw6Maki/mcfVwTQ/v3RqjzttHei/aaEMGQXYr3TOX8NPYJXerBpjShDZqcgcCfqPzhi7uCwVYaPvRsMMYH5jgtMz/SO3J6ToeGKT0f1F1ihRyCdusg325TMK7Vv8yHJzL4ngeUbb/oqxWtxhLY7f77Lkx6hBwe6ROpTqiQHIfZad5qyIHihKcDcIHo4FwLHVzxR4y+Ds2js5dXwBkUBaEAx0Dmc6ZdgXqxd815GDwv+VVpzXJhtO6qXmKE36F/Oe6eCNG3dk5MdOK4C2DrM9M8MXllqeQtX6JrM2stWlBalAc9Jn4b0xxAoghX3x3G3hNsLc9Uist1VinY2+IcPozusqj66c+Vbi5YngWSbICOjlIcq2GaOlBi3xioGts396442ztf1eLjMVuvykCI3b4Yz/vJYZOZjUvG/6nLfaFQTJLSXkTvI4do3foeY4="
password:
secure: "ts/3FSPL+w4euXryip9wCJWUqUwuJ2CqHi0nEpYVM10KL0cp9aMkQXN/7C8ih+j/JZ25kaO+tis1nx0JERVnKrg/1F/fJhxHTpakCP4OSIlhKRXDt7yUUHcKKt5QdM2XCKhApptP6RTpWL1vlc36CMrhdvTcMYp7hlDOiWjdhhim24FGQzabL42Ko59ttyyCROqgcnjHqj+WlhJl7iYO+lv2PLOtslpkIYY5EMjgBLuGyOvxTqZsAXtworsASdB2CrsTE6iMRcrM1afgkyBlUqDezoiv9TwfZrZ85oRoAxPB7su4QWK7lEP0wGQV/SDz/RL7c+cNfRaauxUKVcC8I2LfaEpudv8vi205+v/08wzPbS4xSOnf89irUJnyGxUy+bYLoXPPVPMyzcQRAUnrQl+ENCmN62HU+/2pkIyiUJvt0wYYP0vp+3pIbXbYQGmAIMq7nZJd7K3rikfnQIGKI03xZsFe3PEdjzhDI9Ej/tRUbJx4pgg084QuYYca6MSYn/zJ5pqIG4A7ngCSxoxoWZT0duo8PrUWQeT17L73Q86HeEv3OzZhWfEF2fNDN3Qbsu4CllSZnbE7DXgAuysTwTYHS/na6lp7wejOqdRUXrHbidVla9Mwxyc9JdWNWzUDxALvNxAdNbV6l85mO8izy8wjQF6M1O7idqTfjQeR4pc="
skip_existing: true
file_glob: true
file:
- dist/dvc*.whl
- dist/dvc*.tar.gz
skip_cleanup: true
on:
condition: "$(./scripts/ci/deploy_condition.sh dist/dvc*.whl dist/dvc*.tar.gz)"
tags: true
repo: iterative/dvc
stage: build
- provider: snap
snap: dvc_*.snap
channel: $SNAP_CHANNEL
skip_cleanup: true
on:
all_branches: true
condition: "$(./scripts/ci/deploy_condition.sh dvc_*.snap) && ($TRAVIS_BRANCH = master || -n $TRAVIS_TAG)"
repo: iterative/dvc
stage: build
+62 -44
View File
@@ -10,9 +10,13 @@
• `Tutorial <https://dvc.org/doc/get-started>`_
• `Mailing List <https://sweedom.us10.list-manage.com/subscribe/post?u=a08bf93caae4063c4e6a351f6&id=24c0ecc49a>`_
.. image:: https://travis-ci.com/iterative/dvc.svg?branch=master
.. image:: https://img.shields.io/badge/release-ok-brightgreen
:target: https://travis-ci.com/iterative/dvc
:alt: Travis
:alt: Release
.. image:: https://img.shields.io/travis/com/iterative/dvc/master?label=dev
:target: https://travis-ci.com/iterative/dvc
:alt: Travis dev branch
.. image:: https://codeclimate.com/github/iterative/dvc/badges/gpa.svg
:target: https://codeclimate.com/github/iterative/dvc
@@ -30,25 +34,30 @@
:target: https://anaconda.org/conda-forge/dvc
:alt: Conda-forge
.. image:: https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft
:target: https://snapcraft.io/dvc
:alt: Snapcraft
|
**Data Version Control** or **DVC** is an **open-source** tool for data science and machine
learning projects. Key features:
#. simple **command line** Git-like experience. Does not require installing and maintaining
any databases. Does not depend on any proprietary online services;
#. Simple **command line** Git-like experience. Does not require installing and maintaining
any databases. Does not depend on any proprietary online services.
#. it manages and versions **datasets** and **machine learning models**. Data is saved in
S3, Google cloud, Azure, Alibaba cloud, SSH server, HDFS or even local HDD RAID;
#. Management and versioning of **datasets** and **machine learning models**. Data is saved in
S3, Google cloud, Azure, Alibaba cloud, SSH server, HDFS, or even local HDD RAID.
#. it makes projects **reproducible** and **shareable**, it helps answering question how
the model was build;
#. Makes projects **reproducible** and **shareable**; helping to answer questions about how
a model was built.
#. it helps manage experiments with Git tags or branches and **metrics** tracking;
#. Helps manage experiments with Git tags/branches and **metrics** tracking.
**DVC** aims to replace tools like Excel and Google Docs that are being commonly used as a knowledge repo and
a ledger for the team, ad-hoc scripts to track and move deploy different model versions, ad-hoc
data file suffixes and prefixes.
**DVC** aims to replace spreadsheet and document sharing tools (such as Excel or Google Docs)
which are being used frequently as both knowledge repositories and team ledgers.
DVC also replaces both ad-hoc scripts to track, move, and deploy different model versions;
as well as ad-hoc data file suffixes and prefixes.
.. contents:: **Contents**
:backlinks: none
@@ -56,34 +65,33 @@ data file suffixes and prefixes.
How DVC works
=============
We encourage you to read our `Get Started <https://dvc.org/doc/get-started>`_ to better understand what DVC
is and how does it fit your scenarios.
We encourage you to read our `Get Started <https://dvc.org/doc/get-started>`_ guide to better understand what DVC
is and how it can fit your scenarios.
The easiest (but not perfect!) *analogy* to describe it: DVC is Git (or Git-lfs to be precise) + ``makefiles``
The easiest (but not perfect!) *analogy* to describe it: DVC is Git (or Git-LFS to be precise) & Makefiles
made right and tailored specifically for ML and Data Science scenarios.
#. ``Git/Git-lfs`` part - DVC helps you storing and sharing data artifacts, models. It connects them with your
Git repository.
#. ``Makefiles`` part - DVC describes how one data or model artifact was build from another data.
#. ``Git/Git-LFS`` part - DVC helps store and share data artifacts and models, connecting them with a Git repository.
#. ``Makefile``\ s part - DVC describes how one data or model artifact was built from other data and code.
DVC usually runs along with Git. Git is used as usual to store and version code and DVC meta-files. DVC helps
to store data and model files seamlessly out of Git while preserving almost the same user experience as if they
were stored in Git itself. To store and share data files cache DVC supports remotes - any cloud (S3, Azure,
DVC usually runs along with Git. Git is used as usual to store and version code (including DVC meta-files). DVC helps
to store data and model files seamlessly out of Git, while preserving almost the same user experience as if they
were stored in Git itself. To store and share the data cache, DVC supports multiple remotes - any cloud (S3, Azure,
Google Cloud, etc) or any on-premise network storage (via SSH, for example).
.. image:: https://dvc.org/static/img/flow.gif
:target: https://dvc.org/static/img/flow.gif
:alt: how_dvc_works
DVC pipelines (aka computational graph) feature connects code and data together. In a very explicit way you can
specify, run, and save information that a certain command with certain dependencies needs to be run to produce
a model. See the quick start section below or check `Get Started <https://dvc.org/doc/get-started>`_ tutorial to
learn more.
The DVC pipelines (computational graph) feature connects code and data together. It is possible to explicitly
specify all steps required to produce a model: input dependencies including data, commands to run,
and output information to be saved. See the quick start section below or
the `Get Started <https://dvc.org/doc/get-started>`_ tutorial to learn more.
Quick start
===========
Please read `Get Started <https://dvc.org/doc/get-started>`_ for the full version. Common workflow commands include:
Please read `Get Started <https://dvc.org/doc/get-started>`_ guide for a full version. Common workflow commands include:
+-----------------------------------+-------------------------------------------------------------------+
| Step | Command |
@@ -108,8 +116,8 @@ Please read `Get Started <https://dvc.org/doc/get-started>`_ for the full versio
Installation
============
Read this `instruction <https://dvc.org/doc/get-started/install>`_ to get more details. There are four
options to install DVC: ``pip``, Homebrew, Conda (Anaconda) or an OS-specific package:
There are four options to install DVC: ``pip``, Homebrew, Conda (Anaconda) or an OS-specific package.
Full instructions are `available here <https://dvc.org/doc/get-started/install>`_.
pip (PyPI)
----------
@@ -120,8 +128,8 @@ pip (PyPI)
Depending on the remote storage type you plan to use to keep and share your data, you might need to specify
one of the optional dependencies: ``s3``, ``gs``, ``azure``, ``oss``, ``ssh``. Or ``all`` to include them all.
The command should look like this: ``pip install dvc[s3]`` - it installs the ``boto3`` library along with
DVC to support the AWS S3 storage.
The command should look like this: ``pip install dvc[s3]`` (in this case AWS S3 dependencies such as ``boto3``
will be installed automatically).
To install the development version, run:
@@ -144,13 +152,23 @@ Conda (Anaconda)
conda install -c conda-forge dvc
Currently, it supports only python version 2.7, 3.6 and 3.7.
Currently, this includes support for Python versions 2.7, 3.6 and 3.7.
Snap (Snapcraft)
----------------
.. code-block:: bash
snap install dvc --classic
This corresponds to the latest tagged release.
Add ``--edge`` for the latest ``master`` version.
Package
-------
Self-contained packages for Windows, Linux, Mac are available. The latest version of the packages can be found at
GitHub `releases page <https://github.com/iterative/dvc/releases>`_.
Self-contained packages for Linux, Windows, and Mac are available. The latest version of the packages
can be found on the GitHub `releases page <https://github.com/iterative/dvc/releases>`_.
Ubuntu / Debian (deb)
^^^^^^^^^^^^^^^^^^^^^
@@ -168,23 +186,23 @@ Fedora / CentOS (rpm)
sudo yum update
sudo yum install dvc
Related technologies
====================
Comparison to related technologies
==================================
#. `Git-annex <https://git-annex.branchable.com/>`_ - DVC uses the idea of storing the content of large files (that you
don't want to see in your Git repository) in a local key-value store and uses file hardlinks/symlinks instead of the
copying actual files.
#. `Git-annex <https://git-annex.branchable.com/>`_ - DVC uses the idea of storing the content of large files (which should
not be in a Git repository) in a local key-value store, and uses file hardlinks/symlinks instead of
copying/duplicating files.
#. `Git-LFS <https://git-lfs.github.com/>`_ - DVC is compatible with any remote storage (S3, Google Cloud, Azure, SSH,
etc). DVC utilizes reflinks or hardlinks to avoid copy operation on checkouts which makes much more efficient for
large data files.
etc). DVC also uses reflinks or hardlinks to avoid copy operations on checkouts; thus handling large data files
much more efficiently.
#. *Makefile* (and its analogues). DVC tracks dependencies (DAG).
#. *Makefile* (and analogues including ad-hoc scripts) - DVC tracks dependencies (in a directed acyclic graph).
#. `Workflow Management Systems <https://en.wikipedia.org/wiki/Workflow_management_system>`_. DVC is a workflow
#. `Workflow Management Systems <https://en.wikipedia.org/wiki/Workflow_management_system>`_ - DVC is a workflow
management system designed specifically to manage machine learning experiments. DVC is built on top of Git.
#. `DAGsHub <https://dagshub.com/>`_ Is a Github equivalent for DVC - pushing your Git+DVC based repo to DAGsHub will give you a high level dashboard of your project, including DVC pipeline and metrics visualizations, as well as links to DVC managed files if they are in cloud storage.
#. `DAGsHub <https://dagshub.com/>`_ - This is a Github equivalent for DVC. Pushing Git+DVC based repositories to DAGsHub will produce in a high level project dashboard; including DVC pipelines and metrics visualizations, as well as links to any DVC-managed files present in cloud storage.
Contributing
============
@@ -233,5 +251,5 @@ Copyright
This project is distributed under the Apache license version 2.0 (see the LICENSE file in the project root).
By submitting a pull request for this project, you agree to license your contribution under the Apache license version
By submitting a pull request to this project, you agree to license your contribution under the Apache license version
2.0 to this project.
+5
View File
@@ -0,0 +1,5 @@
dvc (0.75.b1) experimental; urgency=low
* First native Debian package version.
-- Dmitry Petrov <[email protected]> Sat, 07 Dec 2019 09:49:28 +0300
+1
View File
@@ -0,0 +1 @@
10
+33
View File
@@ -0,0 +1,33 @@
Source: dvc
Maintainer: Dmitry Petrov <[email protected]>
Section: python
Priority: optional
Build-Depends: dh-python, python3-setuptools, python3-all, debhelper (>= 9)
Standards-Version: 4.0.0
Homepage: http://dvc.org
Vcs-Git: https://github.com/iterative/dvc.git
Vcs-browser: https://github.com/iterative/dvc
Package: dvc
Architecture: all
Depends: ${misc:Depends}, ${python3:Depends}, python3-flufl.lock (>=3.2)
Recommends: python3-arrow (=0.14.0),
python3-paramiko (>=2.5.0),
python3-gssapi (>= 2.5.0),
python3-boto3 (>=1.9.86),
python3-pydrive (=1.3.1),
python3-azure-storage-blob (=12.1.0),
python3-oss2 (=2.6.1),
python3-google-cloud-storage (=1.19.0)
Description: Git for data scientists - manage your code and data together
Data Version Control or DVC is an open-source tool for data science and machine learning projects.
.
Key features:
.
1. Simple command line Git-like experience. Does not require installing and maintaining any databases. Does not depend on any proprietary online services.
.
2. Management and versioning of datasets and machine learning models. Data is saved in S3, Google cloud, Azure, Alibaba cloud, SSH server, HDFS, or even local HDD RAID.
.
3. Makes projects reproducible and shareable; helping to answer questions about how a model was built.
.
4. Helps manage experiments with Git tags/branches and metrics tracking.
+11
View File
@@ -0,0 +1,11 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: DVC
Upstream-Contact: Dmitry Petrov <[email protected]>
Source: https://github.com/iterative/dvc
Files: *
Copyright: Dmitry Petrov <[email protected]>
License: Apache 2.0
On Debian systems, the full text of the Apache 2.0
License can be found in the file
`/usr/share/common-licenses/Apache-2.0`.
Vendored Executable
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/make -f
export PYBUILD_NAME=dvc
%:
dh $@ --with python3 --buildsystem=pybuild
+1
View File
@@ -0,0 +1 @@
3.0 (native)
+4
View File
@@ -0,0 +1,4 @@
# please also check http://pypi.debian.net/dvc/watch
version=3
opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
http://pypi.debian.net/dvc/dvc-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
-2
View File
@@ -3,8 +3,6 @@ DVC
----
Make your data science projects reproducible and shareable.
"""
from __future__ import unicode_literals
import warnings
import dvc.logger
+1 -3
View File
@@ -1,6 +1,4 @@
"""Main entry point for dvc command line tool."""
from __future__ import unicode_literals
"""Main entry point for DVC command line tool."""
import sys
from dvc.main import main
+137 -224
View File
@@ -1,256 +1,169 @@
"""Collect and send usage analytics"""
from __future__ import unicode_literals
import errno
import json
import logging
import os
import platform
import requests
import sys
import tempfile
import uuid
import distro
from dvc import __version__
from dvc.utils import env2bool
from dvc.utils.compat import str
from dvc.config import Config, to_bool
from dvc.daemon import daemon
from dvc.exceptions import NotDvcRepoError
from dvc.lock import Lock, LockError
from dvc.repo import Repo
from dvc.scm import SCM
from dvc.utils import env2bool, is_binary
from dvc.utils.fs import makedirs
logger = logging.getLogger(__name__)
class Analytics(object):
"""Class for collecting and sending usage analytics.
Args:
info (dict): optional existing analytics report.
def collect_and_send_report(args=None, return_code=None):
"""
Collect information from the runtime/environment and the command
being executed into a report and send it over the network.
URL = "https://analytics.dvc.org"
TIMEOUT_POST = 5
To prevent analytics from blocking the execution of the main thread,
sending the report is done in a separate process.
USER_ID_FILE = "user_id"
The inter-process communication happens through a file containing the
report as a JSON, where the _collector_ generates it and the _sender_
removes it after sending it.
"""
report = _runtime_info()
PARAM_DVC_VERSION = "dvc_version"
PARAM_USER_ID = "user_id"
PARAM_SYSTEM_INFO = "system_info"
# Include command execution information on the report only when available.
if args and hasattr(args, "func"):
report.update({"cmd_class": args.func.__name__})
PARAM_OS = "os"
if return_code is not None:
report.update({"cmd_return_code": return_code})
PARAM_WINDOWS_VERSION_MAJOR = "windows_version_major"
PARAM_WINDOWS_VERSION_MINOR = "windows_version_minor"
PARAM_WINDOWS_VERSION_BUILD = "windows_version_build"
PARAM_WINDOWS_VERSION_SERVICE_PACK = "windows_version_service_pack"
with tempfile.NamedTemporaryFile(delete=False, mode="w") as fobj:
json.dump(report, fobj)
daemon(["analytics", fobj.name])
PARAM_MAC_VERSION = "mac_version"
PARAM_LINUX_DISTRO = "linux_distro"
PARAM_LINUX_DISTRO_VERSION = "linux_distro_version"
PARAM_LINUX_DISTRO_LIKE = "linux_distro_like"
def is_enabled():
if env2bool("DVC_TEST"):
return False
PARAM_SCM_CLASS = "scm_class"
PARAM_IS_BINARY = "is_binary"
PARAM_CMD_CLASS = "cmd_class"
PARAM_CMD_RETURN_CODE = "cmd_return_code"
enabled = to_bool(
Config(validate=False)
.config.get(Config.SECTION_CORE, {})
.get(Config.SECTION_CORE_ANALYTICS, "true")
)
def __init__(self, info=None):
from dvc.config import Config
from dvc.lock import Lock
logger.debug("Analytics is {}abled.".format("en" if enabled else "dis"))
if info is None:
info = {}
return enabled
self.info = info
cdir = Config.get_global_config_dir()
try:
os.makedirs(cdir)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
def send(report):
"""
Side effect: Removes the report after sending it.
self.user_id_file = os.path.join(cdir, self.USER_ID_FILE)
self.user_id_file_lock = Lock(self.user_id_file + ".lock")
The report is generated and stored in a temporary file, see:
`collect_and_send_report`. Sending happens on another process,
thus, the need of removing such file afterwards.
"""
url = "https://analytics.dvc.org"
headers = {"content-type": "application/json"}
@staticmethod
def load(path):
"""Loads analytics report from json file specified by path.
with open(report, "rb") as fobj:
requests.post(url, data=fobj, headers=headers, timeout=5)
Args:
path (str): path to json file with analytics report.
"""
with open(path, "r") as fobj:
analytics = Analytics(info=json.load(fobj))
os.unlink(path)
return analytics
os.remove(report)
def _write_user_id(self):
import uuid
with open(self.user_id_file, "w+") as fobj:
user_id = str(uuid.uuid4())
info = {self.PARAM_USER_ID: user_id}
json.dump(info, fobj)
def _scm_in_use():
try:
scm = SCM(root_dir=Repo.find_root())
return type(scm).__name__
except NotDvcRepoError:
pass
def _runtime_info():
"""
Gather information from the environment where DVC runs to fill a report.
"""
return {
"dvc_version": __version__,
"is_binary": is_binary(),
"scm_class": _scm_in_use(),
"system_info": _system_info(),
"user_id": _find_or_create_user_id(),
}
def _system_info():
system = platform.system()
if system == "Windows":
version = sys.getwindowsversion()
return {
"os": "windows",
"windows_version_build": version.build,
"windows_version_major": version.major,
"windows_version_minor": version.minor,
"windows_version_service_pack": version.service_pack,
}
if system == "Darwin":
return {"os": "mac", "mac_version": platform.mac_ver()[0]}
if system == "Linux":
return {
"os": "linux",
"linux_distro": distro.id(),
"linux_distro_like": distro.like(),
"linux_distro_version": distro.version(),
}
# We don't collect data for any other system.
raise NotImplementedError
def _find_or_create_user_id():
"""
The user's ID is stored on a file under the global config directory.
The file should contain a JSON with a "user_id" key:
{"user_id": "16fd2706-8baf-433b-82eb-8c7fada847da"}
IDs are generated randomly with UUID.
"""
config_dir = Config.get_global_config_dir()
fname = os.path.join(config_dir, "user_id")
lockfile = os.path.join(config_dir, "user_id.lock")
# Since the `fname` and `lockfile` are under the global config,
# we need to make sure such directory exist already.
makedirs(config_dir, exist_ok=True)
try:
with Lock(lockfile):
try:
with open(fname, "r") as fobj:
user_id = json.load(fobj)["user_id"]
except (FileNotFoundError, ValueError, KeyError):
user_id = str(uuid.uuid4())
with open(fname, "w") as fobj:
json.dump({"user_id": user_id}, fobj)
return user_id
def _read_user_id(self):
if not os.path.exists(self.user_id_file):
return None
with open(self.user_id_file, "r") as fobj:
try:
info = json.load(fobj)
except ValueError as exc:
logger.debug("Failed to load user_id: {}".format(exc))
return None
return info[self.PARAM_USER_ID]
def _get_user_id(self):
from dvc.lock import LockError
try:
with self.user_id_file_lock:
user_id = self._read_user_id()
if user_id is None:
user_id = self._write_user_id()
return user_id
except LockError:
msg = "Failed to acquire '{}'"
logger.debug(msg.format(self.user_id_file_lock.lockfile))
def _collect_windows(self):
import sys
version = sys.getwindowsversion() # pylint: disable=no-member
info = {}
info[self.PARAM_OS] = "windows"
info[self.PARAM_WINDOWS_VERSION_MAJOR] = version.major
info[self.PARAM_WINDOWS_VERSION_MINOR] = version.minor
info[self.PARAM_WINDOWS_VERSION_BUILD] = version.build
info[self.PARAM_WINDOWS_VERSION_SERVICE_PACK] = version.service_pack
return info
def _collect_darwin(self):
import platform
info = {}
info[self.PARAM_OS] = "mac"
info[self.PARAM_MAC_VERSION] = platform.mac_ver()[0]
return info
def _collect_linux(self):
import distro
info = {}
info[self.PARAM_OS] = "linux"
info[self.PARAM_LINUX_DISTRO] = distro.id()
info[self.PARAM_LINUX_DISTRO_VERSION] = distro.version()
info[self.PARAM_LINUX_DISTRO_LIKE] = distro.like()
return info
def _collect_system_info(self):
import platform
system = platform.system()
if system == "Windows":
return self._collect_windows()
if system == "Darwin":
return self._collect_darwin()
if system == "Linux":
return self._collect_linux()
raise NotImplementedError
def collect(self):
"""Collect analytics report."""
from dvc.scm import SCM
from dvc.utils import is_binary
from dvc.repo import Repo
from dvc.exceptions import NotDvcRepoError
self.info[self.PARAM_DVC_VERSION] = __version__
self.info[self.PARAM_IS_BINARY] = is_binary()
self.info[self.PARAM_USER_ID] = self._get_user_id()
self.info[self.PARAM_SYSTEM_INFO] = self._collect_system_info()
try:
scm = SCM(root_dir=Repo.find_root())
self.info[self.PARAM_SCM_CLASS] = type(scm).__name__
except NotDvcRepoError:
pass
def collect_cmd(self, args, ret):
"""Collect analytics info from a CLI command."""
from dvc.command.daemon import CmdDaemonAnalytics
assert isinstance(ret, int) or ret is None
if ret is not None:
self.info[self.PARAM_CMD_RETURN_CODE] = ret
if args is not None and hasattr(args, "func"):
assert args.func != CmdDaemonAnalytics
self.info[self.PARAM_CMD_CLASS] = args.func.__name__
def dump(self):
"""Save analytics report to a temporary file.
Returns:
str: path to the temporary file that contains the analytics report.
"""
import tempfile
with tempfile.NamedTemporaryFile(delete=False, mode="w") as fobj:
json.dump(self.info, fobj)
return fobj.name
@staticmethod
def is_enabled(cmd=None):
from dvc.config import Config, to_bool
from dvc.command.daemon import CmdDaemonBase
if env2bool("DVC_TEST"):
return False
if isinstance(cmd, CmdDaemonBase):
return False
core = Config(validate=False).config.get(Config.SECTION_CORE, {})
enabled = to_bool(core.get(Config.SECTION_CORE_ANALYTICS, "true"))
except LockError:
logger.debug(
"Analytics is {}.".format("enabled" if enabled else "disabled")
"Failed to acquire '{lockfile}'".format(lockfile=lockfile)
)
return enabled
@staticmethod
def send_cmd(cmd, args, ret):
"""Collect and send analytics for CLI command.
Args:
args (list): parsed args for the CLI command.
ret (int): return value of the CLI command.
"""
from dvc.daemon import daemon
if not Analytics.is_enabled(cmd):
return
analytics = Analytics()
analytics.collect_cmd(args, ret)
daemon(["analytics", analytics.dump()])
def send(self):
"""Collect and send analytics."""
import requests
if not self.is_enabled():
return
self.collect()
logger.debug("Sending analytics: {}".format(self.info))
try:
requests.post(self.URL, json=self.info, timeout=self.TIMEOUT_POST)
except requests.exceptions.RequestException as exc:
logger.debug("Failed to send analytics: {}".format(str(exc)))
+197 -21
View File
@@ -1,27 +1,74 @@
from builtins import open as builtin_open
import importlib
import os
from contextlib import contextmanager
import sys
from contextlib import contextmanager, _GeneratorContextManager as GCM
import threading
try:
from contextlib import _GeneratorContextManager as GCM
except ImportError:
from contextlib import GeneratorContextManager as GCM
from funcy import wrap_with
import ruamel.yaml
from voluptuous import Schema, Required, Invalid
from dvc.utils.compat import urlparse
from dvc.repo import Repo
from dvc.exceptions import DvcException, NotDvcRepoError
from dvc.external_repo import external_repo
SUMMON_FILE_SCHEMA = Schema(
{
Required("objects"): [
{
Required("name"): str,
"meta": dict,
Required("summon"): {
Required("type"): str,
"deps": [str],
str: object,
},
}
]
}
)
SUMMON_PYTHON_SCHEMA = Schema(
{
Required("type"): "python",
Required("call"): str,
"args": dict,
"deps": [str],
}
)
class SummonError(DvcException):
pass
class UrlNotDvcRepoError(DvcException):
"""Thrown if given url is not a DVC repository.
Args:
url (str): URL to the repository
"""
def __init__(self, url):
super().__init__("'{}' is not a DVC repository.".format(url))
def get_url(path, repo=None, rev=None, remote=None):
"""Returns an url of a resource specified by path in repo"""
"""
Returns the full URL to the data artifact specified by its `path` in a
`repo`.
NOTE: There is no guarantee that the file actually exists in that location.
"""
with _make_repo(repo, rev=rev) as _repo:
abspath = os.path.join(_repo.root_dir, path)
out, = _repo.find_outs_by_path(abspath)
_require_dvc(_repo)
out = _repo.find_out_by_relpath(path)
remote_obj = _repo.cloud.get_remote(remote)
return str(remote_obj.checksum_to_path_info(out.checksum))
def open(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
"""Opens a specified resource as a file descriptor"""
"""Context manager to open a file artifact as a file object."""
args = (path,)
kwargs = {
"repo": repo,
@@ -40,21 +87,20 @@ class _OpenContextManager(GCM):
def __getattr__(self, name):
raise AttributeError(
"dvc.api.open() should be used in a with statement"
"dvc.api.open() should be used in a with statement."
)
def _open(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
with _make_repo(repo, rev=rev) as _repo:
abspath = os.path.join(_repo.root_dir, path)
with _repo.open(
abspath, remote=remote, mode=mode, encoding=encoding
with _repo.open_by_relpath(
path, remote=remote, mode=mode, encoding=encoding
) as fd:
yield fd
def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
"""Read a specified resource into string"""
"""Returns the contents of a file artifact."""
with open(
path, repo=repo, rev=rev, remote=remote, mode=mode, encoding=encoding
) as fd:
@@ -62,10 +108,140 @@ def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
@contextmanager
def _make_repo(repo_url, rev=None):
if not repo_url or urlparse(repo_url).scheme == "":
assert rev is None, "Custom revision is not supported for local repo"
yield Repo(repo_url)
def _make_repo(repo_url=None, rev=None):
repo_url = repo_url or os.getcwd()
if rev is None and os.path.exists(repo_url):
try:
yield Repo(repo_url)
return
except NotDvcRepoError:
pass # fallthrough to external_repo
with external_repo(url=repo_url, rev=rev) as repo:
yield repo
def summon(name, repo=None, rev=None, summon_file="dvcsummon.yaml", args=None):
"""Instantiate an object described in the `summon_file`."""
with prepare_summon(
name, repo=repo, rev=rev, summon_file=summon_file
) as desc:
try:
summon_dict = SUMMON_PYTHON_SCHEMA(desc.obj["summon"])
except Invalid as exc:
raise SummonError(str(exc)) from exc
_args = {**summon_dict.get("args", {}), **(args or {})}
return _invoke_method(summon_dict["call"], _args, desc.repo.root_dir)
@contextmanager
def prepare_summon(name, repo=None, rev=None, summon_file="dvcsummon.yaml"):
"""Does a couple of things every summon needs as a prerequisite:
clones the repo, parses the summon file and pulls the deps.
Calling code is expected to complete the summon logic following
instructions stated in "summon" dict of the object spec.
Returns a SummonDesc instance, which contains references to a Repo object,
named object specification and resolved paths to deps.
"""
with _make_repo(repo, rev=rev) as _repo:
_require_dvc(_repo)
try:
path = os.path.join(_repo.root_dir, summon_file)
obj = _get_object_spec(name, path)
yield SummonDesc(_repo, obj)
except SummonError as exc:
raise SummonError(
str(exc) + " at '{}' in '{}'".format(summon_file, repo)
) from exc.__cause__
class SummonDesc:
def __init__(self, repo, obj):
self.repo = repo
self.obj = obj
self._pull_deps()
@property
def deps(self):
return [os.path.join(self.repo.root_dir, d) for d in self._deps]
@property
def _deps(self):
return self.obj["summon"].get("deps", [])
def _pull_deps(self):
if not self._deps:
return
outs = [self.repo.find_out_by_relpath(d) for d in self._deps]
with self.repo.state:
for out in outs:
self.repo.cloud.pull(out.get_used_cache())
out.checkout()
def _get_object_spec(name, path):
"""
Given a summonable object's name, search for it on the given file
and return its description.
"""
try:
with builtin_open(path, "r") as fobj:
content = SUMMON_FILE_SCHEMA(ruamel.yaml.safe_load(fobj.read()))
objects = [x for x in content["objects"] if x["name"] == name]
if not objects:
raise SummonError("No object with name '{}'".format(name))
elif len(objects) >= 2:
raise SummonError(
"More than one object with name '{}'".format(name)
)
return objects[0]
except FileNotFoundError as exc:
raise SummonError("Summon file not found") from exc
except ruamel.yaml.YAMLError as exc:
raise SummonError("Failed to parse summon file") from exc
except Invalid as exc:
raise SummonError(str(exc)) from exc
@wrap_with(threading.Lock())
def _invoke_method(call, args, path):
# XXX: Some issues with this approach:
# * Import will pollute sys.modules
# * sys.path manipulation is "theoretically" not needed,
# but tests are failing for an unknown reason.
cwd = os.getcwd()
try:
os.chdir(path)
sys.path.insert(0, path)
method = _import_string(call)
return method(**args)
finally:
os.chdir(cwd)
sys.path.pop(0)
def _import_string(import_name):
"""Imports an object based on a string.
Useful to delay import to not load everything on startup.
Use dotted notaion in `import_name`, e.g. 'dvc.remote.gs.RemoteGS'.
:return: imported object
"""
if "." in import_name:
module, obj = import_name.rsplit(".", 1)
else:
with external_repo(url=repo_url, rev=rev) as repo:
yield repo
return importlib.import_module(import_name)
return getattr(importlib.import_module(module), obj)
def _require_dvc(repo):
if not isinstance(repo, Repo):
raise UrlNotDvcRepoError(repo.url)
+3 -6
View File
@@ -1,13 +1,10 @@
"""Manages cache of a dvc repo."""
from __future__ import unicode_literals
"""Manages cache of a DVC repo."""
import os
from collections import defaultdict
from funcy import cached_property
from dvc.config import Config
from dvc.utils.compat import builtin_str
class CacheConfig(object):
@@ -56,12 +53,12 @@ def _make_remote_property(name):
return Remote(self.repo, name=remote)
getter.__name__ = builtin_str(name)
getter.__name__ = name
return cached_property(getter)
class Cache(object):
"""Class that manages cache locations of a dvc repo.
"""Class that manages cache locations of a DVC repo.
Args:
repo (dvc.repo.Repo): repo instance that this cache belongs to.
+2 -5
View File
@@ -1,7 +1,4 @@
"""Command line interface for dvc."""
from __future__ import print_function
from __future__ import unicode_literals
"""DVC command line interface"""
import argparse
import logging
import sys
@@ -173,7 +170,7 @@ def parse_args(argv=None):
title="Available Commands",
metavar="COMMAND",
dest="cmd",
help="Use dvc COMMAND --help for command-specific help.",
help="Use `dvc COMMAND --help` for command-specific help.",
)
fix_subparsers(subparsers)
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
+8 -16
View File
@@ -1,9 +1,5 @@
from __future__ import unicode_literals
import logging
import colorama
logger = logging.getLogger(__name__)
@@ -16,23 +12,18 @@ def fix_subparsers(subparsers):
Args:
subparsers: subparsers to fix.
"""
from dvc.utils.compat import is_py3
if is_py3: # pragma: no cover
subparsers.required = True
subparsers.dest = "cmd"
subparsers.required = True
subparsers.dest = "cmd"
def append_doc_link(help_message, path):
from dvc.utils import format_link
if not path:
return help_message
doc_base = "https://man.dvc.org/"
return "{message}\nDocumentation: <{blue}{base}{path}{nc}>".format(
message=help_message,
base=doc_base,
path=path,
blue=colorama.Fore.CYAN,
nc=colorama.Fore.RESET,
return "{message}\nDocumentation: {link}".format(
message=help_message, link=format_link(doc_base + path)
)
@@ -44,7 +35,8 @@ class CmdBase(object):
self.repo = Repo()
self.config = self.repo.config
self.args = args
updater = Updater(self.repo.dvc_dir)
hardlink_lock = self.config.config["core"].get("hardlink_lock", False)
updater = Updater(self.repo.dvc_dir, hardlink_lock=hardlink_lock)
updater.check()
@property
+3 -4
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
from dvc.cache import CacheConfig
@@ -10,7 +8,7 @@ from dvc.command.config import CmdConfig
class CmdCacheDir(CmdConfig):
def __init__(self, args):
super(CmdCacheDir, self).__init__(args)
super().__init__(args)
self.cache_config = CacheConfig(self.config)
def run(self):
@@ -32,7 +30,8 @@ def add_parser(subparsers, parent_parser):
)
cache_subparsers = cache_parser.add_subparsers(
dest="cmd", help="Use dvc cache CMD --help for command-specific help."
dest="cmd",
help="Use `dvc cache CMD --help` for command-specific " "help.",
)
fix_subparsers(cache_subparsers)
+14 -9
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
from dvc.command.base import append_doc_link
@@ -12,6 +10,7 @@ class CmdCheckout(CmdBase):
targets=self.args.targets,
with_deps=self.args.with_deps,
force=self.args.force,
relink=self.args.relink,
recursive=self.args.recursive,
)
return 0
@@ -27,13 +26,6 @@ def add_parser(subparsers, parent_parser):
help=CHECKOUT_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
checkout_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Do not prompt when removing working directory files.",
)
checkout_parser.add_argument(
"-d",
"--with-deps",
@@ -48,6 +40,19 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Checkout all subdirectories of the specified directory.",
)
checkout_parser.add_argument(
"-f",
"--force",
action="store_true",
default=False,
help="Do not prompt when removing working directory files.",
)
checkout_parser.add_argument(
"--relink",
action="store_true",
default=False,
help="Recreate links or copies from cache to workspace.",
)
checkout_parser.add_argument(
"targets",
nargs="*",
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
+9 -3
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -13,7 +11,7 @@ logger = logging.getLogger(__name__)
class CmdConfig(CmdBaseNoRepo):
def __init__(self, args):
super(CmdConfig, self).__init__(args)
super().__init__(args)
self.config = Config(validate=False)
@@ -29,6 +27,14 @@ class CmdConfig(CmdBaseNoRepo):
section, opt, self.args.value, level=self.args.level
)
is_write = self.args.unset or self.args.value is not None
if is_write and self.args.name == "cache.type":
logger.warning(
"You have changed the 'cache.type' option. This doesn't update"
" any existing workspace file links, but it can be done with:"
"\n dvc checkout --relink"
)
return 0
+10 -7
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.command.base import CmdBaseNoRepo
from dvc.command.base import fix_subparsers
@@ -12,11 +10,16 @@ class CmdDaemonUpdater(CmdDaemonBase):
def run(self):
import os
from dvc.repo import Repo
from dvc.config import Config
from dvc.updater import Updater
root_dir = Repo.find_root()
dvc_dir = os.path.join(root_dir, Repo.DVC_DIR)
updater = Updater(dvc_dir)
config = Config(dvc_dir, validate=False)
hardlink_lock = config.config.get("core", {}).get(
"hardlink_lock", False
)
updater = Updater(dvc_dir, hardlink_lock=hardlink_lock)
updater.fetch(detach=False)
return 0
@@ -24,10 +27,9 @@ class CmdDaemonUpdater(CmdDaemonBase):
class CmdDaemonAnalytics(CmdDaemonBase):
def run(self):
from dvc.analytics import Analytics
from dvc import analytics
analytics = Analytics.load(self.args.target)
analytics.send()
analytics.send(self.args.target)
return 0
@@ -42,7 +44,8 @@ def add_parser(subparsers, parent_parser):
)
daemon_subparsers = daemon_parser.add_subparsers(
dest="cmd", help="Use dvc daemon CMD --help for command-specific help."
dest="cmd",
help="Use `dvc daemon CMD --help` for command-specific " "help.",
)
fix_subparsers(daemon_subparsers)
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
import os
+39 -9
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -12,7 +10,27 @@ logger = logging.getLogger(__name__)
class CmdGet(CmdBaseNoRepo):
def _show_url(self):
from dvc.api import get_url
try:
url = get_url(
self.args.path, repo=self.args.url, rev=self.args.rev
)
logger.info(url)
except DvcException:
logger.exception("failed to show URL")
return 1
return 0
def run(self):
if self.args.show_url:
return self._show_url()
return self._get_file_from_repo()
def _get_file_from_repo(self):
from dvc.repo import Repo
try:
@@ -33,7 +51,9 @@ class CmdGet(CmdBaseNoRepo):
def add_parser(subparsers, parent_parser):
GET_HELP = "Download data from DVC repository."
GET_HELP = (
"Download file or directory from any DVC project or Git repository."
)
get_parser = subparsers.add_parser(
"get",
parents=[parent_parser],
@@ -42,13 +62,23 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawDescriptionHelpFormatter,
)
get_parser.add_argument(
"url", help="URL of Git repository with DVC project to download from."
)
get_parser.add_argument("path", help="Path to data within DVC repository.")
get_parser.add_argument(
"-o", "--out", nargs="?", help="Destination path to put data to."
"url",
help="Location of DVC project or Git repository to download from",
)
get_parser.add_argument(
"--rev", nargs="?", help="DVC repository git revision."
"path",
help="Path to a file or directory within the project or repository",
)
get_parser.add_argument(
"-o", "--out", nargs="?", help="Destination path to download files to"
)
get_parser.add_argument(
"--rev", nargs="?", help="Git revision (e.g. branch, tag, SHA)"
)
get_parser.add_argument(
"--show-url",
action="store_true",
help="Print the storage location (URL) the target data would be "
"downloaded from, and exit.",
)
get_parser.set_defaults(func=CmdGet)
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
+8 -7
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -32,7 +30,8 @@ class CmdImport(CmdBase):
def add_parser(subparsers, parent_parser):
IMPORT_HELP = (
"Download data from DVC repository and take it under DVC control."
"Download file or directory from any DVC project or Git repository "
"into the workspace, and track it."
)
import_parser = subparsers.add_parser(
@@ -43,15 +42,17 @@ def add_parser(subparsers, parent_parser):
formatter_class=argparse.RawTextHelpFormatter,
)
import_parser.add_argument(
"url", help="URL of Git repository with DVC project to download from."
"url",
help="Location of DVC project or Git repository to download from",
)
import_parser.add_argument(
"path", help="Path to data within DVC repository."
"path",
help="Path to a file or directory within the project or repository",
)
import_parser.add_argument(
"-o", "--out", nargs="?", help="Destination path to put data to."
"-o", "--out", nargs="?", help="Destination path to download files to"
)
import_parser.add_argument(
"--rev", nargs="?", help="DVC repository git revision."
"--rev", nargs="?", help="Git revision (e.g. branch, tag, SHA)"
)
import_parser.set_defaults(func=CmdImport)
+5 -2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -44,12 +42,17 @@ def add_parser(subparsers, parent_parser):
"url",
help="Supported urls:\n"
"/path/to/file\n"
"/path/to/directory\n"
"C:\\\\path\\to\\file\n"
"C:\\\\path\\to\\directory\n"
"https://example.com/path/to/file\n"
"s3://bucket/path/to/file\n"
"s3://bucket/path/to/directory\n"
"gs://bucket/path/to/file\n"
"gs://bucket/path/to/directory\n"
"hdfs://example.com/path/to/file\n"
"ssh://example.com:/path/to/file\n"
"ssh://example.com:/path/to/directory\n"
"remote://myremote/path/to/file (see `dvc remote`)",
)
import_parser.add_argument(
+2 -4
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -21,7 +19,7 @@ class CmdInit(CmdBaseNoRepo):
)
self.config = self.repo.config
except InitError:
logger.exception("failed to initiate dvc")
logger.exception("failed to initiate DVC")
return 1
return 0
@@ -45,7 +43,7 @@ def add_parser(subparsers, parent_parser):
"--no-scm",
action="store_true",
default=False,
help="Initiate dvc in directory that is "
help="Initiate DVC in directory that is "
"not tracked by any scm tool (e.g. git).",
)
init_parser.add_argument(
+1 -3
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -15,7 +13,7 @@ class CmdInstall(CmdBase):
try:
self.repo.install()
except Exception:
logger.exception("failed to install dvc hooks")
logger.exception("failed to install DVC Git hooks")
return 1
return 0
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
+122 -4
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -28,7 +26,12 @@ def show_metrics(metrics, all_branches=False, all_tags=False):
logger.info("{branch}:".format(branch=branch))
for fname, metric in val.items():
lines = metric if type(metric) is list else metric.splitlines()
if isinstance(metric, dict):
lines = list(metric.values())
elif isinstance(metric, list):
lines = metric
else:
lines = metric.splitlines()
if len(lines) > 1:
logger.info("\t{fname}:".format(fname=fname))
@@ -102,6 +105,59 @@ class CmdMetricsRemove(CmdBase):
return 0
def _show_diff(diff):
from texttable import Texttable
if not diff:
return "No changes."
table = Texttable()
# remove borders to make it easier for users to copy stuff
table.set_chars(("", "", "", ""))
table.set_deco(0)
rows = [["Path", "Metric", "Value", "Change"]]
for fname, mdiff in diff.items():
for metric, change in mdiff.items():
rows.append(
[
fname,
metric,
change["new"],
change.get("diff", "diff not supported"),
]
)
table.add_rows(rows)
return table.draw()
class CmdMetricsDiff(CmdBase):
def run(self):
try:
diff = self.repo.metrics.diff(
a_ref=self.args.a_ref,
b_ref=self.args.b_ref,
targets=self.args.targets,
typ=self.args.type,
xpath=self.args.xpath,
recursive=self.args.recursive,
)
if self.args.show_json:
import json
logger.info(json.dumps(diff))
else:
logger.info(_show_diff(diff))
except DvcException:
logger.exception("failed to show metrics diff")
return 1
return 0
def add_parser(subparsers, parent_parser):
METRICS_HELP = "Commands to add, manage, collect and display metrics."
@@ -115,7 +171,7 @@ def add_parser(subparsers, parent_parser):
metrics_subparsers = metrics_parser.add_subparsers(
dest="cmd",
help="Use dvc metrics CMD --help to display command-specific help.",
help="Use `dvc metrics CMD --help` to display command-specific help.",
)
fix_subparsers(metrics_subparsers)
@@ -216,3 +272,65 @@ def add_parser(subparsers, parent_parser):
)
metrics_remove_parser.add_argument("path", help="Path to a metric file.")
metrics_remove_parser.set_defaults(func=CmdMetricsRemove)
METRICS_DIFF_HELP = "Output metric values."
metrics_diff_parser = metrics_subparsers.add_parser(
"diff",
parents=[parent_parser],
description=append_doc_link(METRICS_DIFF_HELP, "metrics/diff"),
help=METRICS_DIFF_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
metrics_diff_parser.add_argument(
"a_ref",
nargs="?",
help=(
"Git reference from which diff is calculated. "
"If omitted `HEAD`(latest commit) is used."
),
)
metrics_diff_parser.add_argument(
"b_ref",
nargs="?",
help=(
"Git reference to which diff is calculated. "
"If omitted current working tree is used."
),
)
metrics_diff_parser.add_argument(
"--targets",
nargs="*",
help=(
"Metric files or directories (see -R) to show diff for. "
"Shows diff for all metric files by default."
),
)
metrics_diff_parser.add_argument(
"-t",
"--type",
help=(
"Type of metrics (json/tsv/htsv/csv/hcsv). "
"It can be detected by the file extension automatically. "
"Unsupported types will be treated as raw."
),
)
metrics_diff_parser.add_argument(
"-x", "--xpath", help="json/tsv/htsv/csv/hcsv path."
)
metrics_diff_parser.add_argument(
"-R",
"--recursive",
action="store_true",
default=False,
help=(
"If any target is a directory, recursively search and process "
"metric files."
),
)
metrics_diff_parser.add_argument(
"--show-json",
action="store_true",
default=False,
help="Show output in JSON format.",
)
metrics_diff_parser.set_defaults(func=CmdMetricsDiff)
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
+18 -35
View File
@@ -1,14 +1,8 @@
from __future__ import unicode_literals
import argparse
import logging
from dvc.command.base import append_doc_link
from dvc.command.base import CmdBase
from dvc.command.base import fix_subparsers
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.utils import relpath
from dvc.utils.compat import str
logger = logging.getLogger(__name__)
@@ -21,23 +15,21 @@ class CmdPipelineShow(CmdBase):
stage = Stage.load(self.repo, target)
G = self.repo.graph
stages = networkx.get_node_attributes(G, "stage")
node = relpath(stage.path, self.repo.root_dir)
nodes = networkx.dfs_postorder_nodes(G, node)
stages = networkx.dfs_postorder_nodes(G, stage)
if locked:
nodes = [n for n in nodes if stages[n].locked]
stages = [s for s in stages if s.locked]
for n in nodes:
for stage in stages:
if commands:
if stages[n].cmd is None:
if stage.cmd is None:
continue
logger.info(stages[n].cmd)
logger.info(stage.cmd)
elif outs:
for out in stages[n].outs:
for out in stage.outs:
logger.info(str(out))
else:
logger.info(n)
logger.info(stage.path_in_repo)
def __build_graph(self, target, commands, outs):
import networkx
@@ -45,14 +37,10 @@ class CmdPipelineShow(CmdBase):
from dvc.repo.graph import get_pipeline
stage = Stage.load(self.repo, target)
node = relpath(stage.path, self.repo.root_dir)
G = get_pipeline(self.repo.pipelines, node)
stages = networkx.get_node_attributes(G, "stage")
G = get_pipeline(self.repo.pipelines, stage)
nodes = []
for n in G:
stage = stages[n]
for stage in G:
if commands:
if stage.cmd is None:
continue
@@ -64,9 +52,7 @@ class CmdPipelineShow(CmdBase):
nodes.append(stage.relpath)
edges = []
for e in G.edges():
from_stage = stages[e[0]]
to_stage = stages[e[1]]
for from_stage, to_stage in G.edges():
if commands:
if to_stage.cmd is None:
continue
@@ -115,7 +101,7 @@ class CmdPipelineShow(CmdBase):
tree.show()
def __write_dot(self, target, commands, outs):
from dvc.utils.compat import StringIO
import io
import networkx
from networkx.drawing.nx_pydot import write_dot
@@ -125,7 +111,7 @@ class CmdPipelineShow(CmdBase):
simple_g = networkx.DiGraph()
simple_g.add_edges_from(edges)
dot_file = StringIO()
dot_file = io.StringIO()
write_dot(simple_g, dot_file)
logger.info(dot_file.getvalue())
@@ -163,14 +149,11 @@ class CmdPipelineShow(CmdBase):
class CmdPipelineList(CmdBase):
def run(self):
import networkx
pipelines = self.repo.pipelines
for p in pipelines:
stages = networkx.get_node_attributes(p, "stage")
for stage in stages:
logger.info(stage)
if len(stages) != 0:
for pipeline in pipelines:
for stage in pipeline:
logger.info(stage.relpath)
if len(pipeline) != 0:
logger.info("=" * 80)
logger.info("{} pipelines total".format(len(pipelines)))
@@ -189,7 +172,7 @@ def add_parser(subparsers, parent_parser):
pipeline_subparsers = pipeline_parser.add_subparsers(
dest="cmd",
help="Use dvc pipeline CMD --help for command-specific help.",
help="Use `dvc pipeline CMD --help` for command-specific help.",
)
fix_subparsers(pipeline_subparsers)
+10 -5
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -7,18 +5,23 @@ from dvc.command.base import append_doc_link
from dvc.command.base import fix_subparsers
from dvc.command.config import CmdConfig
from dvc.remote.config import RemoteConfig
from dvc.utils import format_link
logger = logging.getLogger(__name__)
class CmdRemoteConfig(CmdConfig):
def __init__(self, args):
super(CmdRemoteConfig, self).__init__(args)
super().__init__(args)
self.remote_config = RemoteConfig(self.config)
class CmdRemoteAdd(CmdRemoteConfig):
def run(self):
if self.args.default:
logger.info(
"Setting '{}' as a default remote.".format(self.args.name)
)
self.remote_config.add(
self.args.name,
self.args.url,
@@ -81,7 +84,7 @@ def add_parser(subparsers, parent_parser):
remote_subparsers = remote_parser.add_subparsers(
dest="cmd",
help="Use dvc remote CMD --help for " "command-specific help.",
help="Use `dvc remote CMD --help` for " "command-specific help.",
)
fix_subparsers(remote_subparsers)
@@ -97,7 +100,9 @@ def add_parser(subparsers, parent_parser):
remote_add_parser.add_argument("name", help="Name of the remote")
remote_add_parser.add_argument(
"url",
help="URL. See full list of supported urls at " "man.dvc.org/remote",
help="URL. See full list of supported urls at {}".format(
format_link("https://man.dvc.org/remote")
),
)
remote_add_parser.add_argument(
"-d",
+2 -4
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
@@ -20,7 +18,7 @@ class CmdRemove(CmdBase):
if self.args.force:
return False
msg = "Are you sure you want to remove {} with its outputs?".format(
msg = "Are you sure you want to remove '{}' with its outputs?".format(
target
)
@@ -38,7 +36,7 @@ class CmdRemove(CmdBase):
outs_only = self._is_outs_only(target)
self.repo.remove(target, outs_only=outs_only)
except DvcException:
logger.exception("failed to remove {}".format(target))
logger.exception("failed to remove '{}'".format(target))
return 1
return 0
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
import os
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
-3
View File
@@ -1,9 +1,6 @@
from __future__ import unicode_literals
import logging
from dvc.command.data_sync import CmdDataBase
from dvc.utils.compat import str
logger = logging.getLogger(__name__)
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import argparse
import logging
+27 -41
View File
@@ -1,11 +1,10 @@
from __future__ import unicode_literals
import argparse
import itertools
import logging
import os
import platform
import uuid
import pathlib
try:
import psutil
@@ -13,7 +12,6 @@ except ImportError:
psutil = None
from dvc.utils import is_binary, relpath
from dvc.utils.compat import pathlib
from dvc.command.base import CmdBaseNoRepo, append_doc_link
from dvc.version import __version__
from dvc.exceptions import DvcException, NotDvcRepoError
@@ -27,23 +25,13 @@ class CmdVersion(CmdBaseNoRepo):
def run(self):
from dvc.repo import Repo
dvc_version = __version__
python_version = platform.python_version()
platform_type = platform.platform()
binary = is_binary()
info = (
"DVC version: {dvc_version}\n"
"Python version: {python_version}\n"
"Platform: {platform_type}\n"
"Binary: {binary}\n"
"Package: {package}\n"
).format(
dvc_version=dvc_version,
python_version=python_version,
platform_type=platform_type,
binary=binary,
package=PKG,
)
info = [
"DVC version: {}".format(__version__),
"Python version: {}".format(platform.python_version()),
"Platform: {}".format(platform.platform()),
"Binary: {}".format(is_binary()),
"Package: {}".format(PKG),
]
try:
repo = Repo()
@@ -54,9 +42,14 @@ class CmdVersion(CmdBaseNoRepo):
# later decides to enable shared cache mode with
# `dvc config cache.shared group`.
if os.path.exists(repo.cache.local.cache_dir):
info += "Cache: {cache}\n".format(
cache=self.get_linktype_support_info(repo)
info.append(
"Cache: {}".format(self.get_linktype_support_info(repo))
)
if psutil:
fs_type = self.get_fs_type(repo.cache.local.cache_dir)
info.append(
"Filesystem type (cache directory): {}".format(fs_type)
)
else:
logger.warning(
"Unable to detect supported link types, as cache "
@@ -66,19 +59,14 @@ class CmdVersion(CmdBaseNoRepo):
"check.".format(relpath(repo.cache.local.cache_dir))
)
if psutil:
info += (
"Filesystem type (cache directory): {fs_cache}\n"
).format(fs_cache=self.get_fs_type(repo.cache.local.cache_dir))
except NotDvcRepoError:
root_directory = os.getcwd()
if psutil:
info += ("Filesystem type (workspace): {fs_root}").format(
fs_root=self.get_fs_type(os.path.abspath(root_directory))
)
fs_root = self.get_fs_type(os.path.abspath(root_directory))
info.append("Filesystem type (workspace): {}".format(fs_root))
logger.info(info)
logger.info("\n".join(info))
return 0
@staticmethod
@@ -98,9 +86,9 @@ class CmdVersion(CmdBaseNoRepo):
@staticmethod
def get_linktype_support_info(repo):
links = {
"reflink": System.reflink,
"hardlink": System.hardlink,
"symlink": System.symlink,
"reflink": (System.reflink, None),
"hardlink": (System.hardlink, System.is_hardlink),
"symlink": (System.symlink, System.is_symlink),
}
fname = "." + str(uuid.uuid4())
@@ -110,18 +98,16 @@ class CmdVersion(CmdBaseNoRepo):
cache = []
for name, link in links.items():
for name, (link, is_link) in links.items():
try:
link(src, dst)
status = "supported"
if is_link and not is_link(dst):
status = "broken"
os.unlink(dst)
supported = True
except DvcException:
supported = False
cache.append(
"{name} - {supported}".format(
name=name, supported=True if supported else False
)
)
status = "not supported"
cache.append("{name} - {status}".format(name=name, status=status))
os.remove(src)
return ", ".join(cache)
+42
View File
@@ -0,0 +1,42 @@
"""Python version compatibility code"""
# Backport os.fspath() from Python 3.6
try:
from os import fspath # noqa: F821
fspath_py35 = lambda s: s # noqa: E731
except ImportError:
def fspath(path):
"""Return the path representation of a path-like object.
If str or bytes is passed in, it is returned unchanged. Otherwise the
os.PathLike interface is used to get the path representation. If the
path representation is not str or bytes, TypeError is raised. If the
provided path is not str, bytes, or os.PathLike, TypeError is raised.
"""
if isinstance(path, (str, bytes)):
return path
# Work from the object's type to match method resolution of other magic
# methods.
path_type = type(path)
try:
path_repr = path_type.__fspath__(path)
except AttributeError:
if hasattr(path_type, "__fspath__"):
raise
else:
raise TypeError(
"expected str, bytes or os.PathLike object, "
"not " + path_type.__name__
)
if isinstance(path_repr, (str, bytes)):
return path_repr
else:
raise TypeError(
"expected {}.__fspath__() to return str or bytes, "
"not {}".format(path_type.__name__, type(path_repr).__name__)
)
fspath_py35 = fspath
+34 -28
View File
@@ -1,5 +1,4 @@
"""DVC config objects."""
from __future__ import unicode_literals
import copy
import errno
@@ -13,36 +12,19 @@ from voluptuous import All, Any, Lower, Range, Coerce, Match
from dvc.exceptions import DvcException
from dvc.exceptions import NotDvcRepoError
from dvc.utils.compat import open
from dvc.utils.compat import str
logger = logging.getLogger(__name__)
class ConfigError(DvcException):
"""DVC config exception.
"""DVC config exception."""
Args:
msg (str): error message.
cause (Exception): optional exception that has caused this error.
"""
def __init__(self, msg, cause=None):
super(ConfigError, self).__init__(
"config file error: {}".format(msg), cause=cause
)
def __init__(self, msg):
super().__init__("config file error: {}".format(msg))
class NoRemoteError(ConfigError):
def __init__(self, command, cause=None):
msg = (
"no remote specified. Setup default remote with\n"
" dvc config core.remote <name>\n"
"or use:\n"
" dvc {} -r <name>\n".format(command)
)
super(NoRemoteError, self).__init__(msg, cause=cause)
pass
def supported_cache_type(types):
@@ -82,11 +64,11 @@ def Choices(*choices):
*choices: pass allowed values as arguments, or pass a list or
tuple as a single argument
"""
return Any(*choices, msg="expected one of {}".format(",".join(choices)))
return Any(*choices, msg="expected one of {}".format(", ".join(choices)))
class Config(object): # pylint: disable=too-many-instance-attributes
"""Class that manages configuration files for a dvc repo.
"""Class that manages configuration files for a DVC repo.
Args:
dvc_dir (str): optional path to `.dvc` directory, that is used to
@@ -110,6 +92,8 @@ class Config(object): # pylint: disable=too-many-instance-attributes
CONFIG = "config"
CONFIG_LOCAL = "config.local"
CREDENTIALPATH = "credentialpath"
LEVEL_LOCAL = 0
LEVEL_REPO = 1
LEVEL_GLOBAL = 2
@@ -124,6 +108,7 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_CORE_INTERACTIVE = "interactive"
SECTION_CORE_ANALYTICS = "analytics"
SECTION_CORE_CHECKSUM_JOBS = "checksum_jobs"
SECTION_CORE_HARDLINK_LOCK = "hardlink_lock"
SECTION_CACHE = "cache"
SECTION_CACHE_DIR = "dir"
@@ -159,10 +144,11 @@ class Config(object): # pylint: disable=too-many-instance-attributes
Optional(SECTION_CORE_INTERACTIVE, default=False): Bool,
Optional(SECTION_CORE_ANALYTICS, default=True): Bool,
SECTION_CORE_CHECKSUM_JOBS: All(Coerce(int), Range(1)),
Optional(SECTION_CORE_HARDLINK_LOCK, default=False): Bool,
}
# aws specific options
SECTION_AWS_CREDENTIALPATH = "credentialpath"
SECTION_AWS_CREDENTIALPATH = CREDENTIALPATH
SECTION_AWS_ENDPOINT_URL = "endpointurl"
SECTION_AWS_LIST_OBJECTS = "listobjects"
SECTION_AWS_REGION = "region"
@@ -170,9 +156,13 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_AWS_USE_SSL = "use_ssl"
SECTION_AWS_SSE = "sse"
SECTION_AWS_ACL = "acl"
SECTION_AWS_GRANT_READ = "grant_read"
SECTION_AWS_GRANT_READ_ACP = "grant_read_acp"
SECTION_AWS_GRANT_WRITE_ACP = "grant_write_acp"
SECTION_AWS_GRANT_FULL_CONTROL = "grant_full_control"
# gcp specific options
SECTION_GCP_CREDENTIALPATH = SECTION_AWS_CREDENTIALPATH
SECTION_GCP_CREDENTIALPATH = CREDENTIALPATH
SECTION_GCP_PROJECTNAME = "projectname"
# azure specific option
@@ -183,6 +173,12 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_OSS_ACCESS_KEY_SECRET = "oss_key_secret"
SECTION_OSS_ENDPOINT = "oss_endpoint"
# GDrive options
SECTION_GDRIVE_CLIENT_ID = "gdrive_client_id"
SECTION_GDRIVE_CLIENT_SECRET = "gdrive_client_secret"
SECTION_GDRIVE_USER_CREDENTIALS_FILE = "gdrive_user_credentials_file"
SECTION_REMOTE_CHECKSUM_JOBS = "checksum_jobs"
SECTION_REMOTE_REGEX = r'^\s*remote\s*"(?P<name>.*)"\s*$'
SECTION_REMOTE_FMT = 'remote "{}"'
SECTION_REMOTE_URL = "url"
@@ -194,6 +190,7 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_REMOTE_ASK_PASSWORD = "ask_password"
SECTION_REMOTE_GSS_AUTH = "gss_auth"
SECTION_REMOTE_NO_TRAVERSE = "no_traverse"
SECTION_REMOTE_VERIFY = "verify"
SECTION_REMOTE_SCHEMA = {
Required(SECTION_REMOTE_URL): str,
SECTION_AWS_REGION: str,
@@ -204,9 +201,14 @@ class Config(object): # pylint: disable=too-many-instance-attributes
Optional(SECTION_AWS_USE_SSL, default=True): Bool,
SECTION_AWS_SSE: str,
SECTION_AWS_ACL: str,
SECTION_AWS_GRANT_READ: str,
SECTION_AWS_GRANT_READ_ACP: str,
SECTION_AWS_GRANT_WRITE_ACP: str,
SECTION_AWS_GRANT_FULL_CONTROL: str,
SECTION_GCP_PROJECTNAME: str,
SECTION_CACHE_TYPE: supported_cache_type,
Optional(SECTION_CACHE_PROTECTED, default=False): Bool,
SECTION_REMOTE_CHECKSUM_JOBS: All(Coerce(int), Range(1)),
SECTION_REMOTE_USER: str,
SECTION_REMOTE_PORT: Coerce(int),
SECTION_REMOTE_KEY_FILE: str,
@@ -218,8 +220,12 @@ class Config(object): # pylint: disable=too-many-instance-attributes
SECTION_OSS_ACCESS_KEY_ID: str,
SECTION_OSS_ACCESS_KEY_SECRET: str,
SECTION_OSS_ENDPOINT: str,
SECTION_GDRIVE_CLIENT_ID: str,
SECTION_GDRIVE_CLIENT_SECRET: str,
SECTION_GDRIVE_USER_CREDENTIALS_FILE: str,
PRIVATE_CWD: str,
Optional(SECTION_REMOTE_NO_TRAVERSE, default=True): Bool,
SECTION_REMOTE_NO_TRAVERSE: Bool,
SECTION_REMOTE_VERIFY: Bool,
}
SECTION_STATE = "state"
@@ -377,7 +383,7 @@ class Config(object): # pylint: disable=too-many-instance-attributes
try:
d = self.COMPILED_SCHEMA(d)
except Invalid as exc:
raise ConfigError(str(exc), cause=exc)
raise ConfigError(str(exc)) from exc
self.config = configobj.ConfigObj(d, write_empty_values=True)
def save(self, config=None):
+2 -6
View File
@@ -1,5 +1,4 @@
"""Launch `dvc daemon` command in a separate detached process."""
from __future__ import unicode_literals
import inspect
import logging
@@ -10,7 +9,6 @@ from subprocess import Popen
from dvc.env import DVC_DAEMON
from dvc.utils import fix_env
from dvc.utils import is_binary
from dvc.utils.compat import cast_bytes_py2
logger = logging.getLogger(__name__)
@@ -99,9 +97,7 @@ def daemon(args):
env = fix_env()
file_path = os.path.abspath(inspect.stack()[0][1])
env[cast_bytes_py2("PYTHONPATH")] = cast_bytes_py2(
os.path.dirname(os.path.dirname(file_path))
)
env[cast_bytes_py2(DVC_DAEMON)] = cast_bytes_py2("1")
env["PYTHONPATH"] = os.path.dirname(os.path.dirname(file_path))
env[DVC_DAEMON] = "1"
_spawn(cmd, env)
+45 -95
View File
@@ -1,8 +1,9 @@
"""Draws DAG in ASCII."""
from __future__ import print_function
from __future__ import unicode_literals
import logging
import math
import os
import pydoc
import sys
from grandalf.graphs import Edge
@@ -12,6 +13,44 @@ from grandalf.layouts import SugiyamaLayout
from grandalf.routing import EdgeViewer
from grandalf.routing import route_with_lines
from dvc.env import DVC_PAGER
from dvc.utils import format_link
logger = logging.getLogger(__name__)
DEFAULT_PAGER = "less"
DEFAULT_PAGER_FORMATTED = "{} --chop-long-lines --clear-screen".format(
DEFAULT_PAGER
)
def make_pager(cmd):
def pager(text):
return pydoc.tempfilepager(pydoc.plain(text), cmd)
return pager
def find_pager():
if not sys.stdout.isatty():
return pydoc.plainpager
env_pager = os.getenv(DVC_PAGER)
if env_pager:
return make_pager(env_pager)
if os.system("({}) 2>{}".format(DEFAULT_PAGER, os.devnull)) == 0:
return make_pager(DEFAULT_PAGER_FORMATTED)
logger.warning(
"Unable to find `less` in the PATH. Check out "
"{} for more info.".format(
format_link("https://man.dvc.org/pipeline/show")
)
)
return pydoc.plainpager
class VertexViewer(object):
"""Class to define vertex box boundaries that will be accounted for during
@@ -60,99 +99,10 @@ class AsciiCanvas(object):
def draw(self):
"""Draws ASCII canvas on the screen."""
if sys.stdout.isatty(): # pragma: no cover
from asciimatics.screen import Screen
Screen.wrapper(self._do_draw)
else:
for line in self.canvas:
print("".join(line))
def _do_draw(self, screen): # pragma: no cover
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches, too-many-statements
from dvc.system import System
from asciimatics.event import KeyboardEvent
offset_x = 0
offset_y = 0
smaxrow, smaxcol = screen.dimensions
assert smaxrow > 1
assert smaxcol > 1
smaxrow -= 1
smaxcol -= 1
if self.lines + 1 > smaxrow:
max_y = self.lines + 1 - smaxrow
else:
max_y = 0
if self.cols + 1 > smaxcol:
max_x = self.cols + 1 - smaxcol
else:
max_x = 0
while True:
for y in range(smaxrow + 1):
y_index = offset_y + y
line = []
for x in range(smaxcol + 1):
x_index = offset_x + x
if (
len(self.canvas) > y_index
and len(self.canvas[y_index]) > x_index
):
line.append(self.canvas[y_index][x_index])
else:
line.append(" ")
assert len(line) == (smaxcol + 1)
screen.print_at("".join(line), 0, y)
screen.refresh()
# NOTE: get_event() doesn't block by itself,
# so we have to do the blocking ourselves.
#
# NOTE: using this workaround while waiting for PR [1]
# to get merged and released. After that need to adjust
# asciimatics version requirements.
#
# [1] https://github.com/peterbrittain/asciimatics/pull/188
System.wait_for_input(self.TIMEOUT)
event = screen.get_event()
if not isinstance(event, KeyboardEvent):
continue
k = event.key_code
if k == screen.KEY_DOWN or k == ord("s"):
offset_y += 1
elif k == screen.KEY_PAGE_DOWN or k == ord("S"):
offset_y += smaxrow
elif k == screen.KEY_UP or k == ord("w"):
offset_y -= 1
elif k == screen.KEY_PAGE_UP or k == ord("W"):
offset_y -= smaxrow
elif k == screen.KEY_RIGHT or k == ord("d"):
offset_x += 1
elif k == ord("D"):
offset_x += smaxcol
elif k == screen.KEY_LEFT or k == ord("a"):
offset_x -= 1
elif k == ord("A"):
offset_x -= smaxcol
elif k == ord("q") or k == ord("Q"):
break
if offset_y > max_y:
offset_y = max_y
elif offset_y < 0:
offset_y = 0
if offset_x > max_x:
offset_x = max_x
elif offset_x < 0:
offset_x = 0
pager = find_pager()
lines = map("".join, self.canvas)
joined_lines = os.linesep.join(lines)
pager(joined_lines)
def point(self, x, y, char):
"""Create a point on ASCII canvas.
+37 -7
View File
@@ -1,5 +1,4 @@
"""Manages dvc remotes that user can use with push/pull/status commands."""
from __future__ import unicode_literals
import logging
@@ -40,7 +39,26 @@ class DataCloud(object):
if remote:
return self._init_remote(remote)
raise NoRemoteError(command)
has_non_default_remote = bool(
self.repo.config.list_options(
Config.SECTION_REMOTE_REGEX, Config.SECTION_REMOTE_URL
)
)
if has_non_default_remote:
error_msg = (
"no remote specified. Setup default remote with\n"
" dvc remote default <remote name>\n"
"or use:\n"
" dvc {} -r <remote name>".format(command)
)
else:
error_msg = (
"no remote specified. Create a default remote with\n"
" dvc remote add -d <remote name> <remote url>"
)
raise NoRemoteError(error_msg)
def _init_remote(self, remote):
return Remote(self.repo, name=remote)
@@ -74,13 +92,25 @@ class DataCloud(object):
show_checksums (bool): show checksums instead of file names in
information messages.
"""
return self.repo.cache.local.pull(
cache,
jobs=jobs,
remote=self.get_remote(remote, "pull"),
show_checksums=show_checksums,
remote = self.get_remote(remote, "pull")
downloaded_items_num = self.repo.cache.local.pull(
cache, jobs=jobs, remote=remote, show_checksums=show_checksums
)
if not remote.verify:
self._save_pulled_checksums(cache)
return downloaded_items_num
def _save_pulled_checksums(self, cache):
for checksum in cache["local"].keys():
cache_file = self.repo.cache.local.checksum_to_path_info(checksum)
if self.repo.cache.local.exists(cache_file):
# We can safely save here, as existing corrupted files will be
# removed upon status, while files corrupted during download
# will not be moved from tmp_file (see `RemoteBASE.download()`)
self.repo.state.save(cache_file, checksum)
def status(self, cache, jobs=None, remote=None, show_checksums=False):
"""Check status of data items in a cloud-agnostic way.
+1 -3
View File
@@ -1,4 +1,4 @@
from __future__ import unicode_literals
from urllib.parse import urlparse
import dvc.output as output
from dvc.dependency.gs import DependencyGS
@@ -46,8 +46,6 @@ SCHEMA[DependencyREPO.PARAM_REPO] = DependencyREPO.REPO_SCHEMA
def _get(stage, p, info):
from dvc.utils.compat import urlparse
parsed = urlparse(p)
if parsed.scheme == "remote":
+3 -5
View File
@@ -1,23 +1,21 @@
from __future__ import unicode_literals
from dvc.exceptions import DvcException
class DependencyDoesNotExistError(DvcException):
def __init__(self, path):
msg = "dependency '{}' does not exist".format(path)
super(DependencyDoesNotExistError, self).__init__(msg)
super().__init__(msg)
class DependencyIsNotFileOrDirError(DvcException):
def __init__(self, path):
msg = "dependency '{}' is not a file or directory".format(path)
super(DependencyIsNotFileOrDirError, self).__init__(msg)
super().__init__(msg)
class DependencyIsStageFileError(DvcException):
def __init__(self, path):
super(DependencyIsStageFileError, self).__init__(
super().__init__(
"Stage file '{}' cannot be a dependency.".format(path)
)
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.dependency.base import DependencyBase
from dvc.output.gs import OutputGS
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.dependency.base import DependencyBase
from dvc.output.hdfs import OutputHDFS
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.dependency.base import DependencyBase
from dvc.output.base import OutputBase
from dvc.remote.http import RemoteHTTP
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from .http import DependencyHTTP
from dvc.remote.https import RemoteHTTPS
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.dependency.base import DependencyBase
from dvc.output.local import OutputLOCAL
+39 -36
View File
@@ -1,13 +1,10 @@
from __future__ import unicode_literals
import os
import copy
from contextlib import contextmanager
from funcy import merge
from voluptuous import Required
from .local import DependencyLOCAL
from dvc.external_repo import external_repo
from dvc.utils.compat import str
from dvc.exceptions import OutputNotFoundError
from dvc.path_info import PathInfo
class DependencyREPO(DependencyLOCAL):
@@ -16,11 +13,15 @@ class DependencyREPO(DependencyLOCAL):
PARAM_REV = "rev"
PARAM_REV_LOCK = "rev_lock"
REPO_SCHEMA = {PARAM_URL: str, PARAM_REV: str, PARAM_REV_LOCK: str}
REPO_SCHEMA = {
Required(PARAM_URL): str,
PARAM_REV: str,
PARAM_REV_LOCK: str,
}
def __init__(self, def_repo, stage, *args, **kwargs):
self.def_repo = def_repo
super(DependencyREPO, self).__init__(stage, *args, **kwargs)
super().__init__(stage, *args, **kwargs)
def _parse_path(self, remote, path):
return None
@@ -32,24 +33,33 @@ class DependencyREPO(DependencyLOCAL):
@property
def repo_pair(self):
d = self.def_repo
return d[self.PARAM_URL], d[self.PARAM_REV_LOCK] or d[self.PARAM_REV]
rev = d.get(self.PARAM_REV_LOCK) or d.get(self.PARAM_REV)
return d[self.PARAM_URL], rev
def __str__(self):
return "{} ({})".format(self.def_path, self.def_repo[self.PARAM_URL])
@contextmanager
def _make_repo(self, **overrides):
with external_repo(**merge(self.def_repo, overrides)) as repo:
yield repo
def _make_repo(self, *, locked=True):
from dvc.external_repo import external_repo
d = self.def_repo
rev = (d.get("rev_lock") if locked else None) or d.get("rev")
return external_repo(d["url"], rev=rev)
def _get_checksum(self, locked=True):
with self._make_repo(locked=locked) as repo:
try:
return repo.find_out_by_relpath(self.def_path).info["md5"]
except OutputNotFoundError:
path = PathInfo(os.path.join(repo.root_dir, self.def_path))
# We are polluting our repo cache with some dir listing here
return self.repo.cache.local.get_checksum(path)
def status(self):
with self._make_repo() as repo:
current = repo.find_out_by_relpath(self.def_path).info
current_checksum = self._get_checksum(locked=True)
updated_checksum = self._get_checksum(locked=False)
with self._make_repo(rev_lock=None) as repo:
updated = repo.find_out_by_relpath(self.def_path).info
if current != updated:
if current_checksum != updated_checksum:
return {str(self): "update available"}
return {}
@@ -60,23 +70,16 @@ class DependencyREPO(DependencyLOCAL):
def dumpd(self):
return {self.PARAM_PATH: self.def_path, self.PARAM_REPO: self.def_repo}
def fetch(self):
with self._make_repo(
cache_dir=self.repo.cache.local.cache_dir
) as repo:
self.def_repo[self.PARAM_REV_LOCK] = repo.scm.get_rev()
out = repo.find_out_by_relpath(self.def_path)
with repo.state:
repo.cloud.pull(out.get_used_cache())
return out
def download(self, to):
out = self.fetch()
to.info = copy.copy(out.info)
to.checkout()
with self._make_repo() as repo:
if self.def_repo.get(self.PARAM_REV_LOCK) is None:
self.def_repo[self.PARAM_REV_LOCK] = repo.scm.get_rev()
if hasattr(repo, "cache"):
repo.cache.local.cache_dir = self.repo.cache.local.cache_dir
repo.pull_to(self.def_path, to.path_info)
def update(self):
with self._make_repo(rev_lock=None) as repo:
with self._make_repo(locked=False) as repo:
self.def_repo[self.PARAM_REV_LOCK] = repo.scm.get_rev()
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.dependency.base import DependencyBase
from dvc.output.s3 import OutputS3
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.dependency.base import DependencyBase
from dvc.output.ssh import OutputSSH
+1
View File
@@ -1 +1,2 @@
DVC_DAEMON = "DVC_DAEMON"
DVC_PAGER = "DVC_PAGER"
+90 -113
View File
@@ -1,32 +1,10 @@
"""Exceptions raised by the dvc."""
from __future__ import unicode_literals
import traceback
from dvc.utils import relpath
from dvc.utils.compat import builtin_str
from dvc.utils.compat import str
from dvc.utils import relpath, format_link
class DvcException(Exception):
"""Base class for all dvc exceptions.
Args:
msg (unicode): message for this exception.
cause (Exception): optional cause exception.
"""
def __init__(self, msg, cause=None):
# NOTE: unlike python 3, python 2 doesn't have built-in support
# for chained exceptions, so we are using our own implementation.
self.cause = cause
self.cause_tb = None
if cause:
try:
self.cause_tb = traceback.format_exc()
except AttributeError: # pragma: no cover
pass
super(DvcException, self).__init__(msg)
"""Base class for all dvc exceptions."""
class OutputDuplicationError(DvcException):
@@ -39,17 +17,14 @@ class OutputDuplicationError(DvcException):
"""
def __init__(self, output, stages):
assert isinstance(output, str) or isinstance(output, builtin_str)
assert isinstance(stages, list)
assert all(
isinstance(stage, str) or isinstance(stage, builtin_str)
for stage in stages
)
assert isinstance(output, str)
assert all(hasattr(stage, "relpath") for stage in stages)
msg = (
"file/directory '{}' is specified as an output in more than one "
"stage: {}"
).format(output, "\n ".join(stages))
super(OutputDuplicationError, self).__init__(msg)
"stage: {}\n"
"This is not allowed. Consider using a different output name."
).format(output, "\n ".join(s.relpath for s in stages))
super().__init__(msg)
class OutputNotFoundError(DvcException):
@@ -62,8 +37,8 @@ class OutputNotFoundError(DvcException):
def __init__(self, output, repo=None):
self.output = output
self.repo = repo
super(OutputNotFoundError, self).__init__(
"unable to find DVC-file with output '{path}'".format(
super().__init__(
"Unable to find DVC-file with output '{path}'".format(
path=relpath(self.output)
)
)
@@ -74,21 +49,17 @@ class StagePathAsOutputError(DvcException):
an output of another stage.
Args:
cwd (str): path to the directory.
fname (str): path to the DVC-file that has cwd specified as an
output.
stage (Stage): a stage that is in some other stages output
output (str): an output covering the stage above
"""
def __init__(self, wdir, fname):
assert isinstance(wdir, str) or isinstance(wdir, builtin_str)
assert isinstance(fname, str) or isinstance(fname, builtin_str)
msg = (
"current working directory '{cwd}' is specified as an output in "
"'{fname}'. Use another CWD to prevent any data removal.".format(
cwd=wdir, fname=fname
def __init__(self, stage, output):
assert isinstance(output, str)
super().__init__(
"'{stage}' is within an output '{output}' of another stage".format(
stage=stage.relpath, output=output
)
)
super(StagePathAsOutputError, self).__init__(msg)
class CircularDependencyError(DvcException):
@@ -100,13 +71,13 @@ class CircularDependencyError(DvcException):
"""
def __init__(self, dependency):
assert isinstance(dependency, (str, builtin_str))
assert isinstance(dependency, str)
msg = (
"file/directory '{}' is specified as an output and as a "
"dependency."
)
super(CircularDependencyError, self).__init__(msg.format(dependency))
super().__init__(msg.format(dependency))
class ArgumentDuplicationError(DvcException):
@@ -118,9 +89,8 @@ class ArgumentDuplicationError(DvcException):
"""
def __init__(self, path):
assert isinstance(path, (str, builtin_str))
msg = "file '{}' is specified more than once."
super(ArgumentDuplicationError, self).__init__(msg.format(path))
assert isinstance(path, str)
super().__init__("file '{}' is specified more than once.".format(path))
class MoveNotDataSourceError(DvcException):
@@ -138,11 +108,11 @@ class MoveNotDataSourceError(DvcException):
"it by hand, or remove '{path}' and create a new one at the "
"desired location."
)
super(MoveNotDataSourceError, self).__init__(msg.format(path=path))
super().__init__(msg.format(path=path))
class NotDvcRepoError(DvcException):
"""Thrown if a directory is not a dvc repo.
"""Thrown if a directory is not a DVC repo.
Args:
root (str): path to the directory.
@@ -150,56 +120,54 @@ class NotDvcRepoError(DvcException):
def __init__(self, root):
msg = (
"you are not inside of a dvc repository "
"you are not inside of a DVC repository "
"(checked up to mount point '{}')"
)
super(NotDvcRepoError, self).__init__(msg.format(root))
super().__init__(msg.format(root))
class DvcParserError(DvcException):
"""Base class for CLI parser errors."""
def __init__(self):
super(DvcParserError, self).__init__("parser error")
super().__init__("parser error")
class CyclicGraphError(DvcException):
def __init__(self, stages):
assert isinstance(stages, list)
stages = "\n".join("\t- {}".format(stage) for stage in stages)
stages = "\n".join("\t- {}".format(stage.relpath) for stage in stages)
msg = (
"you've introduced a cycle in your pipeline that involves "
"the following stages:"
"\n"
"{stages}".format(stages=stages)
)
super(CyclicGraphError, self).__init__(msg)
super().__init__(msg)
class ConfirmRemoveError(DvcException):
def __init__(self, path):
super(ConfirmRemoveError, self).__init__(
super().__init__(
"unable to remove '{}' without a confirmation from the user. Use "
"`-f` to force.".format(path)
)
class InitError(DvcException):
def __init__(self, msg):
super(InitError, self).__init__(msg)
pass
class ReproductionError(DvcException):
def __init__(self, dvc_file_name, ex):
def __init__(self, dvc_file_name):
self.path = dvc_file_name
msg = "failed to reproduce '{}'".format(dvc_file_name)
super(ReproductionError, self).__init__(msg, cause=ex)
super().__init__("failed to reproduce '{}'".format(dvc_file_name))
class BadMetricError(DvcException):
def __init__(self, paths):
super(BadMetricError, self).__init__(
"the following metrics do not exists, "
super().__init__(
"the following metrics do not exist, "
"are not metric files or are malformed: {paths}".format(
paths=", ".join("'{}'".format(path) for path in paths)
)
@@ -208,32 +176,31 @@ class BadMetricError(DvcException):
class NoMetricsError(DvcException):
def __init__(self):
super(NoMetricsError, self).__init__(
super().__init__(
"no metric files in this repository. "
"Use `dvc metrics add` to add a metric file to track."
)
class StageFileCorruptedError(DvcException):
def __init__(self, path, cause=None):
def __init__(self, path):
path = relpath(path)
super(StageFileCorruptedError, self).__init__(
super().__init__(
"unable to read DVC-file: {} "
"YAML file structure is corrupted".format(path),
cause=cause,
"YAML file structure is corrupted".format(path)
)
class RecursiveAddingWhileUsingFilename(DvcException):
def __init__(self):
super(RecursiveAddingWhileUsingFilename, self).__init__(
super().__init__(
"cannot use `fname` with multiple targets or `-R|--recursive`"
)
class OverlappingOutputPathsError(DvcException):
def __init__(self, out_1, out_2):
super(OverlappingOutputPathsError, self).__init__(
super().__init__(
"Paths for outs:\n'{}'('{}')\n'{}'('{}')\noverlap. To avoid "
"unpredictable behaviour, rerun command with non overlapping outs "
"paths.".format(
@@ -246,57 +213,41 @@ class OverlappingOutputPathsError(DvcException):
class CheckoutErrorSuggestGit(DvcException):
def __init__(self, target, cause):
super(CheckoutErrorSuggestGit, self).__init__(
"Did you mean `git checkout {}`?".format(target), cause=cause
)
def __init__(self, target):
super().__init__("Did you mean `git checkout {}`?".format(target))
class ETagMismatchError(DvcException):
def __init__(self, etag, cached_etag):
super(ETagMismatchError, self).__init__(
super().__init__(
"ETag mismatch detected when copying file to cache! "
"(expected: '{}', actual: '{}')".format(etag, cached_etag)
)
class FileMissingError(DvcException):
def __init__(self, path, cause=None):
super(FileMissingError, self).__init__(
"Can't find '{}' neither locally nor on remote".format(path),
cause=cause,
def __init__(self, path):
self.path = path
super().__init__(
"Can't find '{}' neither locally nor on remote".format(path)
)
class DvcIgnoreInCollectedDirError(DvcException):
def __init__(self, ignore_dirname):
super(DvcIgnoreInCollectedDirError, self).__init__(
super().__init__(
".dvcignore file should not be in collected dir path: "
"'{}'".format(ignore_dirname)
)
class UrlNotDvcRepoError(DvcException):
def __init__(self, url):
super(UrlNotDvcRepoError, self).__init__(
"URL '{}' is not a dvc repository.".format(url)
)
class GetDVCFileError(DvcException):
def __init__(self):
super(GetDVCFileError, self).__init__(
"the given path is a DVC-file, you must specify a data file "
"or a directory"
)
class GitHookAlreadyExistsError(DvcException):
def __init__(self, hook_name):
super(GitHookAlreadyExistsError, self).__init__(
"Hook '{}' already exists. Please refer to "
"https://man.dvc.org/install "
"for more info.".format(hook_name)
super().__init__(
"Hook '{}' already exists. Please refer to {} for more "
"info.".format(
hook_name, format_link("https://man.dvc.org/install")
)
)
@@ -304,7 +255,7 @@ class DownloadError(DvcException):
def __init__(self, amount):
self.amount = amount
super(DownloadError, self).__init__(
super().__init__(
"{amount} files failed to download".format(amount=amount)
)
@@ -313,7 +264,7 @@ class UploadError(DvcException):
def __init__(self, amount):
self.amount = amount
super(UploadError, self).__init__(
super().__init__(
"{amount} files failed to upload".format(amount=amount)
)
@@ -325,27 +276,53 @@ class CheckoutError(DvcException):
"Checkout failed for following targets:\n {}\nDid you "
"forget to fetch?".format("\n".join(targets))
)
super(CheckoutError, self).__init__(m)
super().__init__(m)
class CollectCacheError(DvcException):
pass
class RemoteNotSpecifiedInExternalRepoError(DvcException):
def __init__(self, url, cause=None):
super(RemoteNotSpecifiedInExternalRepoError, self).__init__(
"No DVC remote is specified in the target repository '{}'".format(
url
),
cause=cause,
class NoRemoteInExternalRepoError(DvcException):
def __init__(self, url):
super().__init__(
"No DVC remote is specified in target repository '{}'.".format(url)
)
class NoOutputInExternalRepoError(DvcException):
def __init__(self, path, external_repo_path, external_repo_url):
super(NoOutputInExternalRepoError, self).__init__(
super().__init__(
"Output '{}' not found in target repository '{}'".format(
relpath(path, external_repo_path), external_repo_url
)
)
class HTTPError(DvcException):
def __init__(self, code, reason):
super().__init__("'{} {}'".format(code, reason))
class PathMissingError(DvcException):
def __init__(self, path, repo):
msg = (
"The path '{}' does not exist in the target repository '{}'"
" neither as an output nor a git-handled file."
)
super().__init__(msg.format(path, repo))
class RemoteCacheRequiredError(DvcException):
def __init__(self, path_info):
super().__init__(
(
"Current operation was unsuccessful because '{}' requires "
"existing cache on '{}' remote. See {} for information on how "
"to set up remote cache."
).format(
path_info,
path_info.scheme,
format_link("https://man.dvc.org/config#cache"),
)
)
+168 -48
View File
@@ -1,56 +1,169 @@
from __future__ import unicode_literals
import logging
import os
import tempfile
from contextlib import contextmanager
from distutils.dir_util import copy_tree
import threading
from funcy import retry
from funcy import retry, suppress, wrap_with, cached_property
from dvc.config import NoRemoteError
from dvc.exceptions import RemoteNotSpecifiedInExternalRepoError
from dvc.exceptions import NoOutputInExternalRepoError
from dvc.exceptions import OutputNotFoundError
from dvc.utils.fs import remove
from dvc.compat import fspath
from dvc.repo import Repo
from dvc.config import Config, NoRemoteError, NotDvcRepoError
from dvc.exceptions import NoRemoteInExternalRepoError
from dvc.exceptions import OutputNotFoundError, NoOutputInExternalRepoError
from dvc.exceptions import FileMissingError, PathMissingError
from dvc.remote import RemoteConfig
from dvc.utils.fs import remove, fs_copy
from dvc.scm import SCM
REPO_CACHE = {}
logger = logging.getLogger(__name__)
@contextmanager
def external_repo(url=None, rev=None, rev_lock=None, cache_dir=None):
from dvc.repo import Repo
def external_repo(url, rev=None):
path = _cached_clone(url, rev)
try:
repo = ExternalRepo(path, url)
except NotDvcRepoError:
repo = ExternalGitRepo(path, url)
path = _external_repo(url=url, rev=rev_lock or rev, cache_dir=cache_dir)
repo = Repo(path)
try:
yield repo
except NoRemoteError as exc:
raise RemoteNotSpecifiedInExternalRepoError(url, cause=exc)
except NoRemoteError:
raise NoRemoteInExternalRepoError(url)
except OutputNotFoundError as exc:
if exc.repo is repo:
raise NoOutputInExternalRepoError(exc.output, repo.root_dir, url)
raise
repo.close()
except FileMissingError as exc:
raise PathMissingError(exc.path, url)
finally:
repo.close()
def _external_repo(url=None, rev=None, cache_dir=None):
from dvc.config import Config
from dvc.cache import CacheConfig
from dvc.repo import Repo
def clean_repos():
# Outside code should not see cache while we are removing
repo_paths = list(REPOS_CACHE.values())
REPOS_CACHE.clear()
key = (url, rev, cache_dir)
if key in REPO_CACHE:
return REPO_CACHE[key]
for path in repo_paths:
_remove(path)
class ExternalRepo(Repo):
def __init__(self, root_dir, url):
super().__init__(root_dir)
self.url = url
self._set_upstream()
def pull_to(self, path, to_info):
try:
out = None
with suppress(OutputNotFoundError):
out = self.find_out_by_relpath(path)
if out and out.use_cache:
self._pull_cached(out, to_info)
return
# Git handled files can't have absolute path
if os.path.isabs(path):
raise FileNotFoundError
fs_copy(os.path.join(self.root_dir, path), fspath(to_info))
except FileNotFoundError:
raise PathMissingError(path, self.url)
def _pull_cached(self, out, to_info):
with self.state:
# Only pull unless all needed cache is present
if out.changed_cache():
self.cloud.pull(out.get_used_cache())
out.path_info = to_info
failed = out.checkout()
# This might happen when pull haven't really pulled all the files
if failed:
raise FileNotFoundError
def _set_upstream(self):
# check if the URL is local and no default remote is present
# add default remote pointing to the original repo's cache location
if os.path.isdir(self.url):
rconfig = RemoteConfig(self.config)
if not rconfig.has_default():
src_repo = Repo(self.url)
try:
rconfig.add(
"auto-generated-upstream",
src_repo.cache.local.cache_dir,
default=True,
level=Config.LEVEL_LOCAL,
)
finally:
src_repo.close()
class ExternalGitRepo:
def __init__(self, root_dir, url):
self.root_dir = root_dir
self.url = url
@cached_property
def scm(self):
return SCM(self.root_dir)
def close(self):
if "scm" in self.__dict__:
self.scm.close()
def find_out_by_relpath(self, path):
raise OutputNotFoundError(path, self)
def pull_to(self, path, to_info):
try:
# Git handled files can't have absolute path
if os.path.isabs(path):
raise FileNotFoundError
fs_copy(os.path.join(self.root_dir, path), fspath(to_info))
except FileNotFoundError:
raise PathMissingError(path, self.url)
@contextmanager
def open_by_relpath(self, path, mode="r", encoding=None, **kwargs):
try:
abs_path = os.path.join(self.root_dir, path)
with open(abs_path, mode, encoding=encoding) as fd:
yield fd
except FileNotFoundError:
raise PathMissingError(path, self.url)
REPOS_CACHE = {}
@wrap_with(threading.Lock())
def _cached_clone(url, rev):
"""Clone an external git repo to a temporary directory.
Returns the path to a local temporary directory with the specified
revision checked out.
"""
if (url, rev) in REPOS_CACHE:
path = REPOS_CACHE[url, rev]
_git_pull(path, rev)
return path
new_path = tempfile.mkdtemp("dvc-erepo")
# Copy and adjust existing clone
if (url, None, None) in REPO_CACHE:
old_path = REPO_CACHE[url, None, None]
if url in REPOS_CACHE:
# Copy and an existing clean clone
# This one unlike shutil.copytree() works with an existing dir
copy_tree(old_path, new_path)
copy_tree(REPOS_CACHE[url], new_path)
_git_pull(new_path, None)
else:
# Create a new clone
_clone_repo(url, new_path)
@@ -58,32 +171,39 @@ def _external_repo(url=None, rev=None, cache_dir=None):
# Save clean clone dir so that we will have access to a default branch
clean_clone_path = tempfile.mkdtemp("dvc-erepo")
copy_tree(new_path, clean_clone_path)
REPO_CACHE[url, None, None] = clean_clone_path
REPOS_CACHE[url] = clean_clone_path
# Adjust new clone/copy to fit rev and cache_dir
repo = Repo(new_path)
try:
if rev is not None:
repo.scm.checkout(rev)
# Check out the specified revision
if rev is not None:
_git_checkout(new_path, rev)
if cache_dir is not None:
cache_config = CacheConfig(repo.config)
cache_config.set_dir(cache_dir, level=Config.LEVEL_LOCAL)
finally:
# Need to close/reopen repo to force config reread
repo.close()
REPO_CACHE[key] = new_path
REPOS_CACHE[url, rev] = new_path
return new_path
def clean_repos():
# Outside code should not see cache while we are removing
repo_paths = list(REPO_CACHE.values())
REPO_CACHE.clear()
def _git_checkout(repo_path, rev):
from dvc.scm import Git
for path in repo_paths:
_remove(path)
git = Git(repo_path)
try:
git.checkout(rev)
finally:
git.close()
def _git_pull(repo_path, rev):
import git
# Do not try to pull in a detached mode
if rev and git.Repo.re_hexsha_only.search(rev):
return
git = git.Repo(repo_path)
try:
msg = git.git.pull()
logger.debug("external repo: git pull: {}", msg)
finally:
git.close()
def _remove(path):
+42 -7
View File
@@ -1,11 +1,11 @@
from __future__ import unicode_literals
import logging
import os
from funcy import cached_property
from pathspec import PathSpec
from pathspec.patterns import GitWildMatchPattern
from dvc.scm.tree import BaseTree
from dvc.utils import relpath
logger = logging.getLogger(__name__)
@@ -72,13 +72,12 @@ class DvcIgnoreDirs(DvcIgnore):
class DvcIgnoreFilter(object):
def __init__(self, root_dir, tree):
def __init__(self, tree):
self.tree = tree
self.ignores = {DvcIgnoreDirs([".git", ".hg", ".dvc"])}
self._update(root_dir)
for root, dirs, _ in self.tree.walk(root_dir, dvcignore=self):
for d in dirs:
self._update(os.path.join(root, d))
for root, dirs, files in self.tree.walk(self.tree.tree_root):
self._update(root)
dirs[:], files[:] = self(root, dirs, files)
def _update(self, dirname):
ignore_file_path = os.path.join(dirname, DvcIgnore.DVCIGNORE_FILE)
@@ -90,3 +89,39 @@ class DvcIgnoreFilter(object):
dirs, files = ignore(root, dirs, files)
return dirs, files
class CleanTree(BaseTree):
def __init__(self, tree):
self.tree = tree
@cached_property
def dvcignore(self):
return DvcIgnoreFilter(self.tree)
@property
def tree_root(self):
return self.tree.tree_root
def open(self, path, mode="r", encoding="utf-8"):
return self.tree.open(path, mode, encoding)
def exists(self, path):
return self.tree.exists(path)
def isdir(self, path):
return self.tree.isdir(path)
def isfile(self, path):
return self.tree.isfile(path)
def walk(self, top, topdown=True):
for root, dirs, files in self.tree.walk(top, topdown):
dirs[:], files[:] = self.dvcignore(root, dirs, files)
yield root, dirs, files
def walk_files(self, top):
for root, _, files in self.walk(top):
for file in files:
yield os.path.join(root, file)
+1 -15
View File
@@ -1,24 +1,10 @@
"""Use heuristics to guess if it is a text file or a binary file."""
from __future__ import unicode_literals
from dvc.utils.compat import is_py3
from dvc.utils.compat import open
# Based on https://eli.thegreenplace.net/2011/10/19/
# perls-guess-if-file-is-text-or-binary-implemented-in-python
# A function that takes an integer in the 8-bit range and returns
# a single-character byte object in py3 / a single-character string
# in py2.
#
def _int2byte(i):
if is_py3:
return bytes((i,))
return chr(i)
TEXT_CHARS = b"".join(_int2byte(i) for i in range(32, 127)) + b"\n\r\t\f\b"
TEXT_CHARS = bytes(range(32, 127)) + b"\n\r\t\f\b"
def istextfile(fname, blocksize=512):
+116 -112
View File
@@ -1,17 +1,16 @@
"""Manages dvc lock file."""
from __future__ import unicode_literals
import hashlib
import os
import time
from datetime import timedelta
from funcy.py3 import lkeep
import zc.lockfile
import flufl.lock
from dvc.exceptions import DvcException
from dvc.utils import makedirs
from dvc.utils.compat import is_py3
from dvc.utils import format_link
from dvc.progress import Tqdm
DEFAULT_TIMEOUT = 5
@@ -23,122 +22,127 @@ FAILED_TO_LOCK_MESSAGE = (
class LockError(DvcException):
"""Thrown when unable to acquire the lock for dvc repo."""
"""Thrown when unable to acquire the lock for DVC repo."""
if is_py3:
import flufl.lock
class Lock(object):
"""Class for DVC repo lock.
class Lock(flufl.lock.Lock):
"""Class for dvc repo lock.
Uses zc.lockfile as backend.
"""
Args:
lockfile (str): the lock filename
in.
tmp_dir (str): a directory to store claim files.
"""
def __init__(self, lockfile, friendly=False, **kwargs):
self._friendly = friendly
self.lockfile = lockfile
self._lock = None
def __init__(self, lockfile, tmp_dir=None):
import socket
@property
def files(self):
return [self.lockfile]
self._tmp_dir = tmp_dir
if self._tmp_dir is not None:
makedirs(self._tmp_dir, exist_ok=True)
# NOTE: this is basically Lock.__init__ copy-paste, except that
# instead of using `socket.getfqdn()` we use `socket.gethostname()`
# to speed this up. We've seen [1] `getfqdn()` take ~5sec to return
# anything, which is way too slow. `gethostname()` is actually a
# fallback for `getfqdn()` when it is not able to resolve a
# canonical hostname through network. The claimfile that uses
# `self._hostname` is still usable, as it uses `pid` and random
# number to generate the resulting lock file name, which is unique
# enough for our application.
#
# [1] https://github.com/iterative/dvc/issues/2582
self._hostname = socket.gethostname()
self._lockfile = lockfile
self._lifetime = timedelta(days=365) # Lock for good by default
self._separator = flufl.lock.SEP
self._set_claimfile()
self._owned = True
self._retry_errnos = []
@property
def lockfile(self):
return self._lockfile
@property
def files(self):
return lkeep([self._lockfile, self._tmp_dir])
def lock(self):
try:
super(Lock, self).lock(timedelta(seconds=DEFAULT_TIMEOUT))
except flufl.lock.TimeOutError:
raise LockError(FAILED_TO_LOCK_MESSAGE)
def _set_claimfile(self, pid=None):
super(Lock, self)._set_claimfile(pid)
if self._tmp_dir is not None:
# Under Windows file path length is limited so we hash it
filename = hashlib.md5(self._claimfile.encode()).hexdigest()
self._claimfile = os.path.join(
self._tmp_dir, filename + ".lock"
)
# Fix for __del__ bug in flufl.lock [1] which is causing errors on
# Python shutdown [2].
# [1] https://gitlab.com/warsaw/flufl.lock/issues/7
# [2] https://github.com/iterative/dvc/issues/2573
def __del__(self):
try:
if self._owned:
self.finalize()
except ImportError:
pass
else:
import zc.lockfile
class Lock(object):
"""Class for dvc repo lock.
Uses zc.lockfile as backend.
"""
def __init__(self, lockfile, tmp_dir=None):
self.lockfile = lockfile
self._lock = None
@property
def files(self):
return [self.lockfile]
def _do_lock(self):
try:
def _do_lock(self):
try:
with Tqdm(
bar_format="{desc}",
disable=not self._friendly,
desc=(
"If DVC froze, see `hardlink_lock` in {}".format(
format_link("man.dvc.org/config#core")
)
),
):
self._lock = zc.lockfile.LockFile(self.lockfile)
except zc.lockfile.LockError:
raise LockError(FAILED_TO_LOCK_MESSAGE)
def lock(self):
try:
self._do_lock()
return
except LockError:
time.sleep(DEFAULT_TIMEOUT)
except zc.lockfile.LockError:
raise LockError(FAILED_TO_LOCK_MESSAGE)
def lock(self):
try:
self._do_lock()
return
except LockError:
time.sleep(DEFAULT_TIMEOUT)
def unlock(self):
self._lock.close()
self._lock = None
self._do_lock()
def __enter__(self):
self.lock()
def unlock(self):
self._lock.close()
self._lock = None
def __exit__(self, typ, value, tbck):
self.unlock()
@property
def is_locked(self):
return bool(self._lock)
def __enter__(self):
self.lock()
def __exit__(self, typ, value, tbck):
self.unlock()
class HardlinkLock(flufl.lock.Lock):
"""Class for DVC repo lock.
Args:
lockfile (str): the lock filename
in.
tmp_dir (str): a directory to store claim files.
"""
def __init__(self, lockfile, tmp_dir=None, **kwargs):
import socket
self._tmp_dir = tmp_dir
# NOTE: this is basically Lock.__init__ copy-paste, except that
# instead of using `socket.getfqdn()` we use `socket.gethostname()`
# to speed this up. We've seen [1] `getfqdn()` take ~5sec to return
# anything, which is way too slow. `gethostname()` is actually a
# fallback for `getfqdn()` when it is not able to resolve a
# canonical hostname through network. The claimfile that uses
# `self._hostname` is still usable, as it uses `pid` and random
# number to generate the resulting lock file name, which is unique
# enough for our application.
#
# [1] https://github.com/iterative/dvc/issues/2582
self._hostname = socket.gethostname()
self._lockfile = lockfile
self._lifetime = timedelta(days=365) # Lock for good by default
self._separator = flufl.lock.SEP
self._set_claimfile()
self._owned = True
self._retry_errnos = []
@property
def lockfile(self):
return self._lockfile
def lock(self):
try:
super().lock(timedelta(seconds=DEFAULT_TIMEOUT))
except flufl.lock.TimeOutError:
raise LockError(FAILED_TO_LOCK_MESSAGE)
def _set_claimfile(self, pid=None):
super()._set_claimfile(pid)
if self._tmp_dir is not None:
# Under Windows file path length is limited so we hash it
filename = hashlib.md5(self._claimfile.encode()).hexdigest()
self._claimfile = os.path.join(self._tmp_dir, filename + ".lock")
# Fix for __del__ bug in flufl.lock [1] which is causing errors on
# Python shutdown [2].
# [1] https://gitlab.com/warsaw/flufl.lock/issues/7
# [2] https://github.com/iterative/dvc/issues/2573
def __del__(self):
try:
if self._owned:
self.finalize()
except ImportError:
pass
def make_lock(lockfile, tmp_dir=None, friendly=False, hardlink_lock=False):
cls = HardlinkLock if hardlink_lock else Lock
return cls(lockfile, tmp_dir=tmp_dir, friendly=friendly)
+23 -32
View File
@@ -1,15 +1,12 @@
"""Manages logging configuration for dvc repo."""
from __future__ import unicode_literals
"""Manages logging configuration for DVC repo."""
import traceback
import logging.config
import logging.handlers
import colorama
from dvc.progress import Tqdm
from dvc.utils.compat import RecursionError
from dvc.utils.compat import str
from dvc.utils.compat import StringIO
FOOTER = (
@@ -26,7 +23,7 @@ FOOTER = (
class LoggingException(Exception):
def __init__(self, record):
msg = "failed to log {}".format(str(record))
super(LoggingException, self).__init__(msg)
super().__init__(msg)
class ExcludeErrorsFilter(logging.Filter):
@@ -61,11 +58,13 @@ class ColorFormatter(logging.Formatter):
}
def format(self, record):
msg = record.msg.format(*record.args) if record.args else record.msg
if record.levelname == "INFO":
return record.msg
return msg
if record.levelname == "ERROR" or record.levelname == "CRITICAL":
exception, stack_trace = self._parse_exc(record.exc_info)
exception, stack_trace = self._parse_exc(record)
return (
"{color}{levelname}{nc}: {description}" "{stack_trace}\n"
@@ -73,8 +72,8 @@ class ColorFormatter(logging.Formatter):
color=self.color_code.get(record.levelname, ""),
nc=colorama.Fore.RESET,
levelname=record.levelname,
description=self._description(record.msg, exception),
msg=record.msg,
description=self._description(msg, exception),
msg=msg,
stack_trace=stack_trace,
)
@@ -82,7 +81,7 @@ class ColorFormatter(logging.Formatter):
color=self.color_code.get(record.levelname, ""),
nc=colorama.Fore.RESET,
levelname=record.levelname,
msg=record.msg,
msg=msg,
)
def _current_level(self):
@@ -104,34 +103,26 @@ class ColorFormatter(logging.Formatter):
return description.format(message=message, exception=exception)
def _walk_exc(self, exc_info):
import traceback
buffer = StringIO()
traceback.print_exception(*exc_info, file=buffer)
exc = exc_info[1]
tb = buffer.getvalue()
exc_list = [str(exc)]
tb_list = [tb]
# NOTE: parsing chained exceptions. See dvc/exceptions.py for more info
while hasattr(exc, "cause") and exc.cause:
exc_list.append(str(exc.cause))
if hasattr(exc, "cause_tb") and exc.cause_tb:
tb_list.insert(0, str(exc.cause_tb))
exc = exc.cause
while hasattr(exc, "__cause__") and exc.__cause__:
exc_list.append(str(exc.__cause__))
exc = exc.__cause__
return exc_list, tb_list
return exc_list
def _parse_exc(self, exc_info):
if not exc_info:
def _parse_exc(self, record):
tb_only = getattr(record, "tb_only", False)
if not record.exc_info:
return (None, "")
exc_list, tb_list = self._walk_exc(exc_info)
exc_list = self._walk_exc(record.exc_info)
tb = traceback.format_exception(*record.exc_info)
exception = ": ".join(exc_list)
exception = None if tb_only else ": ".join(exc_list)
if self._current_level() == logging.DEBUG:
stack_trace = (
@@ -140,7 +131,7 @@ class ColorFormatter(logging.Formatter):
red=colorama.Fore.RED,
nc=colorama.Fore.RESET,
line="-" * 60,
stack_trace="\n".join(tb_list),
stack_trace="".join(tb),
)
else:
stack_trace = ""
@@ -150,7 +141,7 @@ class ColorFormatter(logging.Formatter):
class LoggerHandler(logging.StreamHandler):
def handleError(self, record):
super(LoggerHandler, self).handleError(record)
super().handleError(record)
raise LoggingException(record)
def emit(self, record):
+13 -13
View File
@@ -1,22 +1,21 @@
"""Main entry point for dvc CLI."""
from __future__ import unicode_literals
import errno
import logging
from dvc.analytics import Analytics
from dvc import analytics
from dvc.cli import parse_args
from dvc.config import ConfigError
from dvc.exceptions import DvcParserError
from dvc.exceptions import NotDvcRepoError
from dvc.external_repo import clean_repos
from dvc.logger import FOOTER
from dvc.remote.pool import close_pools
from dvc.utils.compat import is_py2
from dvc.utils import format_link
# Workaround for CPython bug. See [1] and [2] for more info.
# [1] https://github.com/aws/aws-cli/blob/1.16.277/awscli/clidriver.py#L55
# [2] https://bugs.python.org/issue29288
"".encode("idna")
@@ -59,10 +58,14 @@ def main(argv=None):
except DvcParserError:
ret = 254
except Exception as exc: # pylint: disable=broad-except
if isinstance(exc, UnicodeError) and is_py2:
if isinstance(exc, OSError) and exc.errno == errno.EMFILE:
logger.exception(
"unicode is not supported in DVC for Python 2 "
"(end-of-life January 1, 2020), please upgrade to Python 3"
"too many open files, please visit "
"{} to see how to handle this "
"problem".format(
format_link("https://error.dvc.org/many-files")
),
extra={"tb_only": True},
)
else:
logger.exception("unexpected error")
@@ -70,10 +73,6 @@ def main(argv=None):
finally:
logger.setLevel(outerLogLevel)
# Python 2 fails to close these clean occasionally and users see
# weird error messages, so we do it manually
close_pools()
# Remove cached repos in the end of the call, these are anonymous
# so won't be reused by any other subsequent run anyway.
clean_repos()
@@ -81,6 +80,7 @@ def main(argv=None):
if ret != 0:
logger.info(FOOTER)
Analytics().send_cmd(cmd, args, ret)
if analytics.is_enabled():
analytics.collect_and_send_report(args, ret)
return ret
+1 -4
View File
@@ -1,5 +1,4 @@
from __future__ import unicode_literals
from urllib.parse import urlparse
from voluptuous import Any, Required
from dvc.output.base import OutputBase
@@ -13,8 +12,6 @@ from dvc.remote.hdfs import RemoteHDFS
from dvc.remote.local import RemoteLOCAL
from dvc.remote.s3 import RemoteS3
from dvc.scheme import Schemes
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
OUTS = [
OutputHDFS,
+38 -31
View File
@@ -1,17 +1,14 @@
from __future__ import unicode_literals
import logging
from urllib.parse import urlparse
from copy import copy
from voluptuous import Any
import dvc.prompt as prompt
from dvc.cache import NamedCache
from dvc.exceptions import CollectCacheError
from dvc.exceptions import CollectCacheError, RemoteCacheRequiredError
from dvc.exceptions import DvcException
from dvc.remote.base import RemoteBASE
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
logger = logging.getLogger(__name__)
@@ -20,26 +17,24 @@ logger = logging.getLogger(__name__)
class OutputDoesNotExistError(DvcException):
def __init__(self, path):
msg = "output '{}' does not exist".format(path)
super(OutputDoesNotExistError, self).__init__(msg)
super().__init__(msg)
class OutputIsNotFileOrDirError(DvcException):
def __init__(self, path):
msg = "output '{}' is not a file or directory".format(path)
super(OutputIsNotFileOrDirError, self).__init__(msg)
super().__init__(msg)
class OutputAlreadyTrackedError(DvcException):
def __init__(self, path):
msg = "output '{}' is already tracked by scm (e.g. git)".format(path)
super(OutputAlreadyTrackedError, self).__init__(msg)
super().__init__(msg)
class OutputIsStageFileError(DvcException):
def __init__(self, path):
super(OutputIsStageFileError, self).__init__(
"Stage file '{}' cannot be an output.".format(path)
)
super().__init__("Stage file '{}' cannot be an output.".format(path))
class OutputBase(object):
@@ -103,14 +98,9 @@ class OutputBase(object):
self.persist = persist
self.tags = None if self.IS_DEPENDENCY else (tags or {})
if self.use_cache and self.cache is None:
raise DvcException(
"no cache location setup for '{}' outputs.".format(
self.REMOTE.scheme
)
)
self.path_info = self._parse_path(remote, path)
if self.use_cache and self.cache is None:
raise RemoteCacheRequiredError(self.path_info)
def _parse_path(self, remote, path):
if remote:
@@ -170,12 +160,7 @@ class OutputBase(object):
return self.remote.exists(self.path_info)
def changed_checksum(self):
return (
self.checksum
!= self.remote.save_info(self.path_info)[
self.remote.PARAM_CHECKSUM
]
)
return self.checksum != self.remote.get_checksum(self.path_info)
def changed_cache(self):
if not self.use_cache or not self.checksum:
@@ -287,10 +272,19 @@ class OutputBase(object):
def download(self, to):
self.remote.download(self.path_info, to.path_info)
def checkout(self, force=False, progress_callback=None, tag=None):
def checkout(
self,
force=False,
progress_callback=None,
tag=None,
relink=False,
filter_info=None,
):
if not self.use_cache:
if progress_callback:
progress_callback(str(self.path_info), self.get_files_number())
progress_callback(
str(self.path_info), self.get_files_number(filter_info)
)
return None
if tag:
@@ -303,6 +297,8 @@ class OutputBase(object):
info,
force=force,
progress_callback=progress_callback,
relink=relink,
filter_info=filter_info,
)
def remove(self, ignore_remove=False):
@@ -326,17 +322,21 @@ class OutputBase(object):
if self.scheme == "local" and self.use_scm_ignore:
self.repo.scm.ignore(self.fspath)
def get_files_number(self):
def get_files_number(self, filter_info=None):
if not self.use_cache:
return 0
return self.cache.get_files_number(self.checksum)
return self.cache.get_files_number(
self.path_info, self.checksum, filter_info
)
def unprotect(self):
if self.exists:
self.remote.unprotect(self.path_info)
def _collect_used_dir_cache(self, remote=None, force=False, jobs=None):
def _collect_used_dir_cache(
self, remote=None, force=False, jobs=None, filter_info=None
):
"""Get a list of `info`s related to the given directory.
- Pull the directory entry from the remote cache if it was changed.
@@ -385,8 +385,9 @@ class OutputBase(object):
for entry in self.dir_cache:
checksum = entry[self.remote.PARAM_CHECKSUM]
path_info = self.path_info / entry[self.remote.PARAM_RELPATH]
cache.add(self.scheme, checksum, str(path_info))
info = self.path_info / entry[self.remote.PARAM_RELPATH]
if not filter_info or info.isin_or_eq(filter_info):
cache.add(self.scheme, checksum, str(info))
return cache
@@ -402,6 +403,12 @@ class OutputBase(object):
if not self.use_cache:
return NamedCache()
if self.stage.is_repo_import:
cache = NamedCache()
dep, = self.stage.deps
cache.external[dep.repo_pair].add(dep.def_path)
return cache
if not self.info:
logger.warning(
"Output '{}'({}) is missing version info. Cache for it will "
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.output.s3 import OutputS3
from dvc.remote.gs import RemoteGS
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.output.base import OutputBase
from dvc.remote.hdfs import RemoteHDFS
+3 -6
View File
@@ -1,16 +1,13 @@
from __future__ import unicode_literals
import logging
import os
from urllib.parse import urlparse
from dvc.exceptions import DvcException
from dvc.istextfile import istextfile
from dvc.output.base import OutputBase
from dvc.remote.local import RemoteLOCAL
from dvc.utils import relpath
from dvc.utils.compat import fspath_py35
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
from dvc.compat import fspath_py35
from dvc.utils.fs import path_isin
@@ -59,7 +56,7 @@ class OutputLOCAL(OutputBase):
return def_scheme != "remote" and not os.path.isabs(self.def_path)
def dumpd(self):
ret = super(OutputLOCAL, self).dumpd()
ret = super().dumpd()
if self.is_in_repo:
path = self.path_info.relpath(self.stage.wdir).as_posix()
else:
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.output.base import OutputBase
from dvc.remote.s3 import RemoteS3
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from dvc.output.base import OutputBase
from dvc.remote.ssh import RemoteSSH
+21 -52
View File
@@ -1,29 +1,26 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import os
import posixpath
import sys
import pathlib
from urllib.parse import urlparse
from funcy import cached_property
from dvc.utils import relpath
from dvc.utils.compat import basestring
from dvc.utils.compat import builtin_str
from dvc.utils.compat import is_py2
from dvc.utils.compat import pathlib
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
# On Python 2.7/Windows sys.getfilesystemencoding() is set to mbcs,
# which is lossy, thus we can't use that,
# see https://github.com/mcmtroffaes/pathlib2/issues/56.
if is_py2:
fs_encoding = "utf-8"
class _BasePath(object):
def overlaps(self, other):
if isinstance(other, (str, bytes)):
other = self.__class__(other)
elif self.__class__ != other.__class__:
return False
return self.isin_or_eq(other) or other.isin(self)
def isin_or_eq(self, other):
return self == other or self.isin(other)
class PathInfo(pathlib.PurePath):
class PathInfo(pathlib.PurePath, _BasePath):
# Use __slots__ in PathInfo objects following PurePath implementation.
# This makes objects smaller and speeds up attribute access.
# We don't add any fields so it's empty.
@@ -54,7 +51,7 @@ class PathInfo(pathlib.PurePath):
return relpath(path)
def __repr__(self):
return builtin_str("{}: '{}'").format(type(self).__name__, self)
return "{}: '{}'".format(type(self).__name__, self)
# This permits passing it to file utils directly in Python 3.6+
# With Python 2.7, Python 3.5+ we are stuck with path_info.fspath for now
@@ -71,7 +68,7 @@ class PathInfo(pathlib.PurePath):
return self.__class__(relpath(self, other))
def isin(self, other):
if isinstance(other, basestring):
if isinstance(other, (str, bytes)):
other = self.__class__(other)
elif self.__class__ != other.__class__:
return False
@@ -79,34 +76,6 @@ class PathInfo(pathlib.PurePath):
n = len(other._cparts)
return len(self._cparts) > n and self._cparts[:n] == other._cparts
# pathlib2 uses bytes internally in Python 2, and we use unicode everywhere
# for paths in both pythons, thus we need this glue.
if is_py2:
__unicode__ = __str__
def __str__(self):
return self.__unicode__().encode(sys.getfilesystemencoding())
@classmethod
def _parse_args(cls, args):
args = [
a.encode(fs_encoding)
if isinstance(a, unicode) # noqa: F821
else a
for a in args
]
return super(PathInfo, cls)._parse_args(args)
@property
def name(self):
return super(PathInfo, self).name.decode(fs_encoding)
def __fspath__(self): # noqa: F811
return pathlib.PurePath.__str__(self).decode(fs_encoding)
def with_name(self, name):
return pathlib.PurePath.with_name(self, name.encode(fs_encoding))
class WindowsPathInfo(PathInfo, pathlib.PureWindowsPath):
pass
@@ -138,7 +107,7 @@ class _URLPathParents(object):
return "<{}.parents>".format(self.src)
class URLInfo(object):
class URLInfo(_BasePath):
DEFAULT_PORTS = {"http": 80, "https": 443, "ssh": 22, "hdfs": 0}
def __init__(self, url):
@@ -163,13 +132,13 @@ class URLInfo(object):
def fill_parts(self, scheme, host, user, port, path):
assert scheme != "remote"
assert isinstance(path, (basestring, _URLPathInfo))
assert isinstance(path, (str, bytes, _URLPathInfo))
self.scheme, self.host, self.user = scheme, host, user
self.port = int(port) if port else self.DEFAULT_PORTS.get(self.scheme)
if isinstance(path, _URLPathInfo):
self._spath = builtin_str(path)
self._spath = str(path)
self._path = path
else:
if path and path[0] != "/":
@@ -198,7 +167,7 @@ class URLInfo(object):
return "{}: '{}'".format(type(self).__name__, self)
def __eq__(self, other):
if isinstance(other, basestring):
if isinstance(other, (str, bytes)):
other = self.__class__(other)
return (
self.__class__ == other.__class__
@@ -248,7 +217,7 @@ class URLInfo(object):
return _URLPathParents(self)
def relative_to(self, other):
if isinstance(other, basestring):
if isinstance(other, (str, bytes)):
other = self.__class__(other)
if self.__class__ != other.__class__:
msg = "'{}' has incompatible class with '{}'".format(self, other)
@@ -259,7 +228,7 @@ class URLInfo(object):
return self._path.relative_to(other._path)
def isin(self, other):
if isinstance(other, basestring):
if isinstance(other, (str, bytes)):
other = self.__class__(other)
elif self.__class__ != other.__class__:
return False
+17 -8
View File
@@ -1,5 +1,4 @@
"""Manages progress bars for dvc repo."""
from __future__ import print_function
"""Manages progress bars for DVC repo."""
import logging
import sys
@@ -20,8 +19,14 @@ class Tqdm(tqdm):
"""
BAR_FMT_DEFAULT = (
"{percentage:3.0f}%|{bar:10}|"
"{desc:{ncols_desc}.{ncols_desc}}{n_fmt}/{total_fmt}"
"{percentage:3.0f}% {desc}|{bar}|"
"{n_fmt}/{total_fmt}"
" [{elapsed}<{remaining}, {rate_fmt:>11}{postfix}]"
)
# nested bars should have fixed bar widths to align nicely
BAR_FMT_DEFAULT_NESTED = (
"{percentage:3.0f}%|{bar:10}|{desc:{ncols_desc}.{ncols_desc}}"
"{n_fmt}/{total_fmt}"
" [{elapsed}<{remaining}, {rate_fmt:>11}{postfix}]"
)
BAR_FMT_NOTOTAL = (
@@ -72,7 +77,7 @@ class Tqdm(tqdm):
and hasattr(file, "isatty")
):
disable = not file.isatty()
super(Tqdm, self).__init__(
super().__init__(
iterable=iterable,
disable=disable,
leave=leave,
@@ -83,7 +88,11 @@ class Tqdm(tqdm):
)
if bar_format is None:
if self.__len__():
self.bar_format = self.BAR_FMT_DEFAULT
self.bar_format = (
self.BAR_FMT_DEFAULT_NESTED
if self.pos
else self.BAR_FMT_DEFAULT
)
else:
self.bar_format = self.BAR_FMT_NOTOTAL
else:
@@ -105,12 +114,12 @@ class Tqdm(tqdm):
def close(self):
if self.desc_persist is not None:
self.set_description_str(self.desc_persist, refresh=False)
super(Tqdm, self).close()
super().close()
@property
def format_dict(self):
"""inject `ncols_desc` to fill the display width (`ncols`)"""
d = super(Tqdm, self).format_dict
d = super().format_dict
ncols = d["ncols"] or 80
ncols_desc = ncols - len(self.format_meter(ncols_desc=1, **d)) + 1
ncols_desc = max(ncols_desc, 0)
-5
View File
@@ -1,14 +1,9 @@
"""Manages user prompts."""
from __future__ import print_function
from __future__ import unicode_literals
import logging
import sys
from getpass import getpass
from dvc.utils.compat import input
logger = logging.getLogger(__name__)
+2 -2
View File
@@ -1,7 +1,6 @@
from __future__ import unicode_literals
from .config import RemoteConfig
from dvc.remote.azure import RemoteAZURE
from dvc.remote.gdrive import RemoteGDrive
from dvc.remote.gs import RemoteGS
from dvc.remote.hdfs import RemoteHDFS
from dvc.remote.http import RemoteHTTP
@@ -14,6 +13,7 @@ from dvc.remote.ssh import RemoteSSH
REMOTES = [
RemoteAZURE,
RemoteGDrive,
RemoteGS,
RemoteHDFS,
RemoteHTTP,
+6 -8
View File
@@ -1,20 +1,17 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import logging
import os
import re
from datetime import datetime
from datetime import timedelta
from datetime import datetime, timedelta
from urllib.parse import urlparse
import threading
from funcy import cached_property
from funcy import cached_property, wrap_prop
from dvc.config import Config
from dvc.path_info import CloudURLInfo
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
from dvc.utils.compat import urlparse
logger = logging.getLogger(__name__)
@@ -34,7 +31,7 @@ class RemoteAZURE(RemoteBASE):
COPY_POLL_SECONDS = 5
def __init__(self, repo, config):
super(RemoteAZURE, self).__init__(repo, config)
super().__init__(repo, config)
url = config.get(Config.SECTION_REMOTE_URL, "azure://")
@@ -64,6 +61,7 @@ class RemoteAZURE(RemoteBASE):
else self.path_cls.from_parts(scheme=self.scheme, netloc=bucket)
)
@wrap_prop(threading.Lock())
@cached_property
def blob_service(self):
from azure.storage.blob import BlockBlobService
+280 -142
View File
@@ -1,34 +1,32 @@
from __future__ import unicode_literals
import errno
import itertools
import json
import logging
import tempfile
from urllib.parse import urlparse
from concurrent.futures import ThreadPoolExecutor
from copy import copy
from functools import partial
from multiprocessing import cpu_count
from operator import itemgetter
from shortuuid import uuid
import dvc.prompt as prompt
from dvc.config import Config
from dvc.exceptions import ConfirmRemoveError
from dvc.exceptions import DvcException
from dvc.exceptions import DvcIgnoreInCollectedDirError
from dvc.exceptions import (
DvcException,
ConfirmRemoveError,
DvcIgnoreInCollectedDirError,
RemoteCacheRequiredError,
)
from dvc.ignore import DvcIgnore
from dvc.path_info import PathInfo
from dvc.path_info import URLInfo
from dvc.path_info import PathInfo, URLInfo
from dvc.progress import Tqdm
from dvc.remote.slow_link_detection import slow_link_guard
from dvc.state import StateNoop
from dvc.utils import makedirs
from dvc.utils import relpath
from dvc.utils import tmp_fname
from dvc.utils.compat import basestring
from dvc.utils.compat import FileNotFoundError
from dvc.utils.compat import str
from dvc.utils.compat import urlparse
from dvc.utils.fs import move
from dvc.utils.fs import move, makedirs
from dvc.utils.http import open_url
logger = logging.getLogger(__name__)
@@ -47,16 +45,9 @@ STATUS_MAP = {
}
class DataCloudError(DvcException):
""" Data Cloud exception """
def __init__(self, msg):
super(DataCloudError, self).__init__("Data sync error: {}".format(msg))
class RemoteCmdError(DvcException):
def __init__(self, remote, cmd, ret, err):
super(RemoteCmdError, self).__init__(
super().__init__(
"{remote} command '{cmd}' finished with non-zero return code"
" {ret}': {err}".format(remote=remote, cmd=cmd, ret=ret, err=err)
)
@@ -64,8 +55,8 @@ class RemoteCmdError(DvcException):
class RemoteActionNotImplemented(DvcException):
def __init__(self, action, scheme):
m = "{} is not supported by {} remote".format(action, scheme)
super(RemoteActionNotImplemented, self).__init__(m)
m = "{} is not supported for {} remotes".format(action, scheme)
super().__init__(m)
class RemoteMissingDepsError(DvcException):
@@ -73,10 +64,9 @@ class RemoteMissingDepsError(DvcException):
class DirCacheError(DvcException):
def __init__(self, checksum, cause=None):
super(DirCacheError, self).__init__(
"Failed to load dir cache for checksum: '{}'.".format(checksum),
cause=cause,
def __init__(self, checksum):
super().__init__(
"Failed to load dir cache for checksum: '{}'.".format(checksum)
)
@@ -90,6 +80,8 @@ class RemoteBASE(object):
CHECKSUM_DIR_SUFFIX = ".dir"
CHECKSUM_JOBS = max(1, min(4, cpu_count() // 2))
DEFAULT_CACHE_TYPES = ["copy"]
DEFAULT_NO_TRAVERSE = True
DEFAULT_VERIFY = False
state = StateNoop()
@@ -97,14 +89,15 @@ class RemoteBASE(object):
self.repo = repo
self._check_requires(config)
core = config.get(Config.SECTION_CORE, {})
self.checksum_jobs = core.get(
Config.SECTION_CORE_CHECKSUM_JOBS, self.CHECKSUM_JOBS
)
self.checksum_jobs = self._get_checksum_jobs(config)
self.protected = False
self.no_traverse = config.get(Config.SECTION_REMOTE_NO_TRAVERSE, True)
self.no_traverse = config.get(
Config.SECTION_REMOTE_NO_TRAVERSE, self.DEFAULT_NO_TRAVERSE
)
self.verify = config.get(
Config.SECTION_REMOTE_VERIFY, self.DEFAULT_VERIFY
)
self._dir_info = {}
types = config.get(Config.SECTION_CACHE_TYPE, None)
@@ -153,6 +146,19 @@ class RemoteBASE(object):
).format(url, missing, " ".join(missing), self.scheme)
raise RemoteMissingDepsError(msg)
def _get_checksum_jobs(self, config):
checksum_jobs = config.get(Config.SECTION_REMOTE_CHECKSUM_JOBS)
if checksum_jobs:
return checksum_jobs
if self.repo:
core = self.repo.config.config.get(Config.SECTION_CORE, {})
return core.get(
Config.SECTION_CORE_CHECKSUM_JOBS, self.CHECKSUM_JOBS
)
return self.CHECKSUM_JOBS
def __repr__(self):
return "{class_name}: '{path_info}'".format(
class_name=type(self).__name__,
@@ -161,7 +167,7 @@ class RemoteBASE(object):
@classmethod
def supported(cls, config):
if isinstance(config, basestring):
if isinstance(config, (str, bytes)):
url = config
else:
url = config[Config.SECTION_REMOTE_URL]
@@ -228,6 +234,9 @@ class RemoteBASE(object):
return sorted(result, key=itemgetter(self.PARAM_RELPATH))
def get_dir_checksum(self, path_info):
if not self.cache:
raise RemoteCacheRequiredError(path_info)
dir_info = self._collect_dir(path_info)
checksum, tmp_info = self._get_dir_info_checksum(dir_info)
new_info = self.cache.checksum_to_path_info(checksum)
@@ -274,11 +283,13 @@ class RemoteBASE(object):
with self.cache.open(path_info, "r") as fobj:
d = json.load(fobj)
except (ValueError, FileNotFoundError) as exc:
raise DirCacheError(checksum, cause=exc)
raise DirCacheError(checksum) from exc
if not isinstance(d, list):
msg = "dir cache file format error '{}' [skipping the file]"
logger.error(msg.format(relpath(path_info)))
logger.error(
"dir cache file format error '{}' [skipping the file]",
path_info,
)
return []
for info in d:
@@ -293,6 +304,8 @@ class RemoteBASE(object):
return checksum.endswith(cls.CHECKSUM_DIR_SUFFIX)
def get_checksum(self, path_info):
assert path_info.scheme == self.scheme
if not self.exists(path_info):
return None
@@ -322,7 +335,6 @@ class RemoteBASE(object):
return checksum
def save_info(self, path_info):
assert path_info.scheme == self.scheme
return {self.PARAM_CHECKSUM: self.get_checksum(path_info)}
def changed(self, path_info, checksum_info):
@@ -343,36 +355,35 @@ class RemoteBASE(object):
"""
logger.debug(
"checking if '{}'('{}') has changed.".format(
path_info, checksum_info
)
"checking if '{}'('{}') has changed.", path_info, checksum_info
)
if not self.exists(path_info):
logger.debug("'{}' doesn't exist.".format(path_info))
logger.debug("'{}' doesn't exist.", path_info)
return True
checksum = checksum_info.get(self.PARAM_CHECKSUM)
if checksum is None:
logger.debug("checksum for '{}' is missing.".format(path_info))
logger.debug("checksum for '{}' is missing.", path_info)
return True
if self.changed_cache(checksum):
logger.debug(
"cache for '{}'('{}') has changed.".format(path_info, checksum)
"cache for '{}'('{}') has changed.", path_info, checksum
)
return True
actual = self.save_info(path_info)[self.PARAM_CHECKSUM]
actual = self.get_checksum(path_info)
if checksum != actual:
logger.debug(
"checksum '{}'(actual '{}') for '{}' has changed.".format(
checksum, actual, path_info
)
"checksum '{}'(actual '{}') for '{}' has changed.",
checksum,
actual,
path_info,
)
return True
logger.debug("'{}' hasn't changed.".format(path_info))
logger.debug("'{}' hasn't changed.", path_info)
return False
def link(self, from_info, to_info):
@@ -385,18 +396,30 @@ class RemoteBASE(object):
self._try_links(from_info, to_info, link_types)
def _verify_link(self, path_info, link_type):
if self.cache_type_confirmed:
return
is_link = getattr(self, "is_{}".format(link_type), None)
if is_link and not is_link(path_info):
self.remove(path_info)
raise DvcException("failed to verify {}".format(link_type))
self.cache_type_confirmed = True
@slow_link_guard
def _try_links(self, from_info, to_info, link_types):
while link_types:
link_method = getattr(self, link_types[0])
try:
self._do_link(from_info, to_info, link_method)
self.cache_type_confirmed = True
self._verify_link(to_info, link_types[0])
return
except DvcException as exc:
msg = "Cache type '{}' is not supported: {}"
logger.debug(msg.format(link_types[0], str(exc)))
logger.debug(
"Cache type '{}' is not supported: {}", link_types[0], exc
)
del link_types[0]
raise DvcException("no possible cache types left to try out.")
@@ -410,13 +433,13 @@ class RemoteBASE(object):
if self.protected:
self.protect(to_info)
msg = "Created {}'{}': {} -> {}".format(
logger.debug(
"Created {}'{}': {} -> {}",
"protected " if self.protected else "",
self.cache_types[0],
from_info,
to_info,
)
logger.debug(msg)
def _save_file(self, path_info, checksum, save_link=True):
assert checksum
@@ -424,10 +447,16 @@ class RemoteBASE(object):
cache_info = self.checksum_to_path_info(checksum)
if self.changed_cache(checksum):
self.move(path_info, cache_info)
self.link(cache_info, path_info)
elif self.iscopy(path_info) and self._cache_is_copy(path_info):
# Default relink procedure involves unneeded copy
if self.protected:
self.protect(path_info)
else:
self.unprotect(path_info)
else:
self.remove(path_info)
self.link(cache_info, path_info)
self.link(cache_info, path_info)
if save_link:
self.state.save_link(path_info)
@@ -438,6 +467,28 @@ class RemoteBASE(object):
self.state.save(path_info, checksum)
self.state.save(cache_info, checksum)
def _cache_is_copy(self, path_info):
"""Checks whether cache uses copies."""
if self.cache_type_confirmed:
return self.cache_types[0] == "copy"
if set(self.cache_types) <= {"copy"}:
return True
workspace_file = path_info.with_name("." + uuid())
test_cache_file = self.path_info / ".cache_type_test_file"
if not self.exists(test_cache_file):
with self.open(test_cache_file, "wb") as fobj:
fobj.write(bytes(1))
try:
self.link(test_cache_file, workspace_file)
finally:
self.remove(workspace_file)
self.remove(test_cache_file)
self.cache_type_confirmed = True
return self.cache_types[0] == "copy"
def _save_dir(self, path_info, checksum):
cache_info = self.checksum_to_path_info(checksum)
dir_info = self.get_dir_cache(checksum)
@@ -466,6 +517,10 @@ class RemoteBASE(object):
"""
return False
def iscopy(self, path_info):
"""Check if this file is an independent copy."""
return False # We can't be sure by default
def walk_files(self, path_info):
"""Return a generator with `PathInfo`s to all the files"""
raise NotImplementedError
@@ -482,20 +537,27 @@ class RemoteBASE(object):
)
checksum = checksum_info[self.PARAM_CHECKSUM]
if not self.changed_cache(checksum):
self._checkout(path_info, checksum)
return
self._save(path_info, checksum)
def _save(self, path_info, checksum):
to_info = self.checksum_to_path_info(checksum)
logger.debug("Saving '{}' to '{}'.".format(path_info, to_info))
logger.debug("Saving '{}' to '{}'.", path_info, to_info)
if self.isdir(path_info):
self._save_dir(path_info, checksum)
return
self._save_file(path_info, checksum)
def _handle_transfer_exception(
self, from_info, to_info, exception, operation
):
if isinstance(exception, OSError) and exception.errno == errno.EMFILE:
raise exception
logger.exception(
"failed to {} '{}' to '{}'", operation, from_info, to_info
)
return 1
def upload(self, from_info, to_info, name=None, no_progress_bar=False):
if not hasattr(self, "_upload"):
raise RemoteActionNotImplemented("upload", self.scheme)
@@ -506,7 +568,7 @@ class RemoteBASE(object):
if from_info.scheme != "local":
raise NotImplementedError
logger.debug("Uploading '{}' to '{}'".format(from_info, to_info))
logger.debug("Uploading '{}' to '{}'", from_info, to_info)
name = name or from_info.name
@@ -517,10 +579,10 @@ class RemoteBASE(object):
name=name,
no_progress_bar=no_progress_bar,
)
except Exception:
msg = "failed to upload '{}' to '{}'"
logger.exception(msg.format(from_info, to_info))
return 1 # 1 fail
except Exception as e:
return self._handle_transfer_exception(
from_info, to_info, e, "upload"
)
return 0
@@ -546,21 +608,58 @@ class RemoteBASE(object):
if to_info.scheme != "local":
raise NotImplementedError
logger.debug("Downloading '{}' to '{}'".format(from_info, to_info))
if self.isdir(from_info):
return self._download_dir(
from_info, to_info, name, no_progress_bar, file_mode, dir_mode
)
return self._download_file(
from_info, to_info, name, no_progress_bar, file_mode, dir_mode
)
def _download_dir(
self, from_info, to_info, name, no_progress_bar, file_mode, dir_mode
):
from_infos = list(self.walk_files(from_info))
to_infos = (
to_info / info.relative_to(from_info) for info in from_infos
)
with ThreadPoolExecutor(max_workers=self.JOBS) as executor:
download_files = partial(
self._download_file,
name=name,
no_progress_bar=True,
file_mode=file_mode,
dir_mode=dir_mode,
)
futures = executor.map(download_files, from_infos, to_infos)
with Tqdm(
futures,
total=len(from_infos),
desc="Downloading directory",
unit="Files",
disable=no_progress_bar,
) as futures:
return sum(futures)
def _download_file(
self, from_info, to_info, name, no_progress_bar, file_mode, dir_mode
):
makedirs(to_info.parent, exist_ok=True, mode=dir_mode)
logger.debug("Downloading '{}' to '{}'", from_info, to_info)
name = name or to_info.name
makedirs(to_info.parent, exist_ok=True, mode=dir_mode)
tmp_file = tmp_fname(to_info)
try:
self._download(
from_info, tmp_file, name=name, no_progress_bar=no_progress_bar
)
except Exception:
msg = "failed to download '{}' to '{}'"
logger.exception(msg.format(from_info, to_info))
return 1 # 1 fail
except Exception as e:
return self._handle_transfer_exception(
from_info, to_info, e, "download"
)
move(tmp_file, to_info, mode=file_mode)
@@ -632,6 +731,8 @@ class RemoteBASE(object):
if checksum in used:
continue
path_info = self.checksum_to_path_info(checksum)
if self.is_dir_checksum(checksum):
self._remove_unpacked_dir(checksum)
self.remove(path_info)
removed = True
return removed
@@ -652,9 +753,10 @@ class RemoteBASE(object):
actual = self.get_checksum(cache_info)
logger.debug(
"cache '{}' expected '{}' actual '{}'".format(
str(cache_info), checksum, actual
)
"cache '{}' expected '{}' actual '{}'",
cache_info,
checksum,
actual,
)
if not checksum or not actual:
@@ -664,29 +766,41 @@ class RemoteBASE(object):
return False
if self.exists(cache_info):
logger.warning("corrupted cache file '{}'.".format(cache_info))
logger.warning("corrupted cache file '{}'.", cache_info)
self.remove(cache_info)
return True
def _changed_dir_cache(self, checksum):
def _changed_dir_cache(self, checksum, path_info=None, filter_info=None):
if self.changed_cache_file(checksum):
return True
if not self._changed_unpacked_dir(checksum):
if not (path_info and filter_info) and not self._changed_unpacked_dir(
checksum
):
return False
for entry in self.get_dir_cache(checksum):
entry_checksum = entry[self.PARAM_CHECKSUM]
if path_info and filter_info:
entry_info = path_info / entry[self.PARAM_RELPATH]
if not entry_info.isin_or_eq(filter_info):
continue
if self.changed_cache_file(entry_checksum):
return True
self._update_unpacked_dir(checksum)
if not (path_info and filter_info):
self._update_unpacked_dir(checksum)
return False
def changed_cache(self, checksum):
def changed_cache(self, checksum, path_info=None, filter_info=None):
if self.is_dir_checksum(checksum):
return self._changed_dir_cache(checksum)
return self._changed_dir_cache(
checksum, path_info=path_info, filter_info=filter_info
)
return self.changed_cache_file(checksum)
def cache_exists(self, checksums, jobs=None, name=None):
@@ -757,45 +871,22 @@ class RemoteBASE(object):
self.remove(path_info)
def _checkout_file(
self,
path_info,
checksum,
force,
progress_callback=None,
save_link=True,
self, path_info, checksum, force, progress_callback=None
):
# NOTE: In case if path_info is already cached and path_info's
# link type matches cache link type, we would like to avoid
# relinking.
if self.changed(
path_info, {self.PARAM_CHECKSUM: checksum}
) or not self._link_matches(path_info):
"""The file is changed we need to checkout a new copy"""
cache_info = self.checksum_to_path_info(checksum)
if self.exists(path_info):
logger.warning(
"data '{}' exists. Removing before checkout.", path_info
)
self.safe_remove(path_info, force=force)
cache_info = self.checksum_to_path_info(checksum)
self.link(cache_info, path_info)
if save_link:
self.state.save_link(path_info)
self.state.save(path_info, checksum)
else:
# NOTE: performing (un)protection costs us +/- the same as checking
# if path_info is protected. Instead of implementing logic,
# just (un)protect according to self.protected.
if self.protected:
self.protect(path_info)
else:
# NOTE dont allow copy, because we checked before that link
# type matches cache, and we don't want data duplication
self.unprotect(path_info, allow_copy=False)
self.link(cache_info, path_info)
self.state.save_link(path_info)
self.state.save(path_info, checksum)
if progress_callback:
progress_callback(str(path_info))
def _link_matches(self, path_info):
return True
def makedirs(self, path_info):
"""Optional: Implement only if the remote needs to create
directories before copying/linking/moving data
@@ -803,7 +894,13 @@ class RemoteBASE(object):
pass
def _checkout_dir(
self, path_info, checksum, force, progress_callback=None
self,
path_info,
checksum,
force,
progress_callback=None,
relink=False,
filter_info=None,
):
# Create dir separately so that dir is created
# even if there are no files in it
@@ -812,19 +909,24 @@ class RemoteBASE(object):
dir_info = self.get_dir_cache(checksum)
logger.debug("Linking directory '{}'.".format(path_info))
logger.debug("Linking directory '{}'.", path_info)
for entry in dir_info:
relative_path = entry[self.PARAM_RELPATH]
entry_checksum = entry[self.PARAM_CHECKSUM]
entry_cache_info = self.checksum_to_path_info(entry_checksum)
entry_info = path_info / relative_path
self._checkout_file(
entry_info,
entry_checksum,
force,
progress_callback,
save_link=False,
)
if filter_info and not entry_info.isin_or_eq(filter_info):
continue
entry_checksum_info = {self.PARAM_CHECKSUM: entry_checksum}
if relink or self.changed(entry_info, entry_checksum_info):
self.safe_remove(entry_info, force=force)
self.link(entry_cache_info, entry_info)
self.state.save(entry_info, entry_checksum)
if progress_callback:
progress_callback(str(entry_info))
self._remove_redundant_files(path_info, dir_info, force)
@@ -842,62 +944,95 @@ class RemoteBASE(object):
self.safe_remove(path, force)
def checkout(
self, path_info, checksum_info, force=False, progress_callback=None
self,
path_info,
checksum_info,
force=False,
progress_callback=None,
relink=False,
filter_info=None,
):
if path_info.scheme not in ["local", self.scheme]:
raise NotImplementedError
checksum = checksum_info.get(self.PARAM_CHECKSUM)
failed = None
skip = False
if not checksum:
logger.warning(
"No checksum info found for '{}'. "
"It won't be created.".format(str(path_info))
"No checksum info found for '{}'. " "It won't be created.",
path_info,
)
self.safe_remove(path_info, force=force)
failed = path_info
elif self.changed_cache(checksum):
msg = "Cache '{}' not found. File '{}' won't be created."
logger.warning(msg.format(checksum, str(path_info)))
elif not relink and not self.changed(path_info, checksum_info):
logger.debug("Data '{}' didn't change.", path_info)
skip = True
elif self.changed_cache(
checksum, path_info=path_info, filter_info=filter_info
):
logger.warning(
"Cache '{}' not found. File '{}' won't be created.",
checksum,
path_info,
)
self.safe_remove(path_info, force=force)
failed = path_info
if failed:
if failed or skip:
if progress_callback:
progress_callback(
str(path_info), self.get_files_number(checksum)
str(path_info),
self.get_files_number(
self.path_info, checksum, filter_info
),
)
return failed
msg = "Checking out '{}' with cache '{}'."
logger.debug(msg.format(str(path_info), checksum))
logger.debug("Checking out '{}' with cache '{}'.", path_info, checksum)
self._checkout(path_info, checksum, force, progress_callback)
return None
self._checkout(
path_info, checksum, force, progress_callback, relink, filter_info
)
def _checkout(
self, path_info, checksum, force=False, progress_callback=None
self,
path_info,
checksum,
force=False,
progress_callback=None,
relink=False,
filter_info=None,
):
if not self.is_dir_checksum(checksum):
return self._checkout_file(
path_info, checksum, force, progress_callback=progress_callback
)
return self._checkout_dir(
path_info, checksum, force, progress_callback=progress_callback
path_info, checksum, force, progress_callback, relink, filter_info
)
def get_files_number(self, checksum):
def get_files_number(self, path_info, checksum, filter_info):
from funcy.py3 import ilen
if not checksum:
return 0
if self.is_dir_checksum(checksum):
if not self.is_dir_checksum(checksum):
return 1
if not filter_info:
return len(self.get_dir_cache(checksum))
return 1
return ilen(
filter_info.isin_or_eq(path_info / entry[self.PARAM_CHECKSUM])
for entry in self.get_dir_cache(checksum)
)
@staticmethod
def unprotect(path_info, allow_copy=True):
def unprotect(path_info):
pass
def _get_unpacked_dir_names(self, checksums):
@@ -913,3 +1048,6 @@ class RemoteBASE(object):
def _update_unpacked_dir(self, checksum):
pass
def _remove_unpacked_dir(self, checksum):
pass
+10 -5
View File
@@ -1,11 +1,10 @@
import logging
import os
import posixpath
from urllib.parse import urlparse
from dvc.config import Config
from dvc.config import ConfigError
from dvc.config import Config, ConfigError
from dvc.utils import relpath
from dvc.utils.compat import urlparse
logger = logging.getLogger(__name__)
@@ -34,7 +33,7 @@ class RemoteConfig(object):
if settings is None:
raise ConfigError(
"unable to find remote section '{}'".format(name)
"Unable to find remote section '{}'".format(name)
)
parsed = urlparse(settings["url"])
@@ -105,7 +104,6 @@ class RemoteConfig(object):
level=level,
)
if default:
logger.info("Setting '{}' as a default remote.".format(name))
self.config.set(
Config.SECTION_CORE,
Config.SECTION_CORE_REMOTE,
@@ -135,6 +133,9 @@ class RemoteConfig(object):
break
def modify(self, name, option, value, level=None):
# try to get remote settings to verify that section exists
self.get_settings(name)
self.config.set(
Config.SECTION_REMOTE_FMT.format(name), option, value, level=level
)
@@ -156,3 +157,7 @@ class RemoteConfig(object):
return self.config.get(
Config.SECTION_CORE, Config.SECTION_CORE_REMOTE, level=level
)
def has_default(self):
core = self.config.config[Config.SECTION_CORE]
return bool(core.get(Config.SECTION_CORE_REMOTE))
+353
View File
@@ -0,0 +1,353 @@
import os
import posixpath
import logging
import threading
import re
from funcy import retry, compose, decorator, wrap_with
from funcy.py3 import cat
from dvc.progress import Tqdm
from dvc.scheme import Schemes
from dvc.path_info import CloudURLInfo
from dvc.remote.base import RemoteBASE
from dvc.config import Config
from dvc.exceptions import DvcException
from dvc.utils import tmp_fname, format_link
logger = logging.getLogger(__name__)
FOLDER_MIME_TYPE = "application/vnd.google-apps.folder"
class GDriveRetriableError(DvcException):
pass
class GDriveAccessTokenRefreshError(DvcException):
pass
class GDriveMissedCredentialKeyError(DvcException):
pass
@decorator
def _wrap_pydrive_retriable(call):
from pydrive2.files import ApiRequestError
try:
result = call()
except ApiRequestError as exception:
retry_codes = ["403", "500", "502", "503", "504"]
if any(
"HttpError {}".format(code) in str(exception)
for code in retry_codes
):
raise GDriveRetriableError("Google API request failed")
raise
return result
gdrive_retry = compose(
# 8 tries, start at 0.5s, multiply by golden ratio, cap at 10s
retry(
8, GDriveRetriableError, timeout=lambda a: min(0.5 * 1.618 ** a, 10)
),
_wrap_pydrive_retriable,
)
class RemoteGDrive(RemoteBASE):
scheme = Schemes.GDRIVE
path_cls = CloudURLInfo
REQUIRES = {"pydrive2": "pydrive2"}
DEFAULT_NO_TRAVERSE = False
DEFAULT_VERIFY = True
GDRIVE_USER_CREDENTIALS_DATA = "GDRIVE_USER_CREDENTIALS_DATA"
DEFAULT_USER_CREDENTIALS_FILE = "gdrive-user-credentials.json"
def __init__(self, repo, config):
super().__init__(repo, config)
self.path_info = self.path_cls(config[Config.SECTION_REMOTE_URL])
bucket = re.search(
"{}://(.*)".format(self.scheme),
config[Config.SECTION_REMOTE_URL],
re.IGNORECASE,
)
self.bucket = (
bucket.group(1).split("/")[0] if bucket else self.path_info.bucket
)
self.config = config
self.init_drive()
def init_drive(self):
self.client_id = self.config.get(Config.SECTION_GDRIVE_CLIENT_ID, None)
self.client_secret = self.config.get(
Config.SECTION_GDRIVE_CLIENT_SECRET, None
)
if not self.client_id or not self.client_secret:
raise DvcException(
"Please specify Google Drive's client id and "
"secret in DVC's config. Learn more at "
"{}.".format(format_link("https://man.dvc.org/remote/add"))
)
self.gdrive_user_credentials_path = (
tmp_fname(os.path.join(self.repo.tmp_dir, ""))
if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA)
else self.config.get(
Config.SECTION_GDRIVE_USER_CREDENTIALS_FILE,
os.path.join(
self.repo.tmp_dir, self.DEFAULT_USER_CREDENTIALS_FILE
),
)
)
@gdrive_retry
def gdrive_upload_file(
self, args, no_progress_bar=True, from_file="", progress_name=""
):
item = self.drive.CreateFile(
{"title": args["title"], "parents": [{"id": args["parent_id"]}]}
)
with open(from_file, "rb") as fobj:
total = os.path.getsize(from_file)
with Tqdm.wrapattr(
fobj,
"read",
desc=progress_name,
total=total,
disable=no_progress_bar,
) as wrapped:
# PyDrive doesn't like content property setting for empty files
# https://github.com/gsuitedevs/PyDrive/issues/121
if total:
item.content = wrapped
item.Upload()
return item
@gdrive_retry
def gdrive_download_file(
self, file_id, to_file, progress_name, no_progress_bar
):
gdrive_file = self.drive.CreateFile({"id": file_id})
bar_format = (
"Donwloading {desc:{ncols_desc}.{ncols_desc}}... "
+ Tqdm.format_sizeof(int(gdrive_file["fileSize"]), "B", 1024)
)
with Tqdm(
bar_format=bar_format, desc=progress_name, disable=no_progress_bar
):
gdrive_file.GetContentFile(to_file)
def gdrive_list_item(self, query):
file_list = self.drive.ListFile({"q": query, "maxResults": 1000})
# Isolate and decorate fetching of remote drive items in pages
get_list = gdrive_retry(lambda: next(file_list, None))
# Fetch pages until None is received, lazily flatten the thing
return cat(iter(get_list, None))
def cache_root_dirs(self):
cached_dirs = {}
cached_ids = {}
for dir1 in self.gdrive_list_item(
"'{}' in parents and trashed=false".format(self.remote_root_id)
):
remote_path = posixpath.join(self.path_info.path, dir1["title"])
cached_dirs.setdefault(remote_path, []).append(dir1["id"])
cached_ids[dir1["id"]] = dir1["title"]
return cached_dirs, cached_ids
@property
def cached_dirs(self):
if not hasattr(self, "_cached_dirs"):
self.drive
return self._cached_dirs
@property
def cached_ids(self):
if not hasattr(self, "_cached_ids"):
self.drive
return self._cached_ids
@property
@wrap_with(threading.RLock())
def drive(self):
from pydrive2.auth import RefreshError
if not hasattr(self, "_gdrive"):
from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive
if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA):
with open(
self.gdrive_user_credentials_path, "w"
) as credentials_file:
credentials_file.write(
os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA)
)
GoogleAuth.DEFAULT_SETTINGS["client_config_backend"] = "settings"
GoogleAuth.DEFAULT_SETTINGS["client_config"] = {
"client_id": self.client_id,
"client_secret": self.client_secret,
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"revoke_uri": "https://oauth2.googleapis.com/revoke",
"redirect_uri": "",
}
GoogleAuth.DEFAULT_SETTINGS["save_credentials"] = True
GoogleAuth.DEFAULT_SETTINGS["save_credentials_backend"] = "file"
GoogleAuth.DEFAULT_SETTINGS[
"save_credentials_file"
] = self.gdrive_user_credentials_path
GoogleAuth.DEFAULT_SETTINGS["get_refresh_token"] = True
GoogleAuth.DEFAULT_SETTINGS["oauth_scope"] = [
"https://www.googleapis.com/auth/drive",
# drive.appdata grants access to appDataFolder GDrive directory
"https://www.googleapis.com/auth/drive.appdata",
]
# Pass non existent settings path to force DEFAULT_SETTINGS loading
gauth = GoogleAuth(settings_file="")
try:
gauth.CommandLineAuth()
except RefreshError as exc:
raise GDriveAccessTokenRefreshError(
"Google Drive's access token refreshment is failed"
) from exc
except KeyError as exc:
raise GDriveMissedCredentialKeyError(
"Google Drive's user credentials file '{}' "
"misses value for key '{}'".format(
self.gdrive_user_credentials_path, str(exc)
)
)
# Handle pydrive2.auth.AuthenticationError and others auth failures
except Exception as exc:
raise DvcException(
"Google Drive authentication failed"
) from exc
finally:
if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA):
os.remove(self.gdrive_user_credentials_path)
self._gdrive = GoogleDrive(gauth)
self.remote_root_id = self.get_remote_id(
self.path_info, create=True
)
self._cached_dirs, self._cached_ids = self.cache_root_dirs()
return self._gdrive
@gdrive_retry
def create_remote_dir(self, parent_id, title):
item = self.drive.CreateFile(
{
"title": title,
"parents": [{"id": parent_id}],
"mimeType": FOLDER_MIME_TYPE,
}
)
item.Upload()
return item
@gdrive_retry
def get_remote_item(self, name, parents_ids):
if not parents_ids:
return None
query = "({})".format(
" or ".join(
"'{}' in parents".format(parent_id)
for parent_id in parents_ids
)
)
query += " and trashed=false and title='{}'".format(name)
# Limit found remote items count to 1 in response
item_list = self.drive.ListFile(
{"q": query, "maxResults": 1}
).GetList()
return next(iter(item_list), None)
def resolve_remote_item_from_path(self, path_parts, create):
parents_ids = [self.bucket]
current_path = ""
for path_part in path_parts:
current_path = posixpath.join(current_path, path_part)
remote_ids = self.get_remote_id_from_cache(current_path)
if remote_ids:
parents_ids = remote_ids
continue
item = self.get_remote_item(path_part, parents_ids)
if not item and create:
item = self.create_remote_dir(parents_ids[0], path_part)
elif not item:
return None
parents_ids = [item["id"]]
return item
def get_remote_id_from_cache(self, remote_path):
if hasattr(self, "_cached_dirs"):
return self.cached_dirs.get(remote_path, [])
return []
def get_remote_id(self, path_info, create=False):
remote_ids = self.get_remote_id_from_cache(path_info.path)
if remote_ids:
return remote_ids[0]
file1 = self.resolve_remote_item_from_path(
path_info.path.split("/"), create
)
return file1["id"] if file1 else ""
def exists(self, path_info):
return self.get_remote_id(path_info) != ""
def _upload(self, from_file, to_info, name, no_progress_bar):
dirname = to_info.parent
if dirname:
parent_id = self.get_remote_id(dirname, True)
else:
parent_id = to_info.bucket
self.gdrive_upload_file(
{"title": to_info.name, "parent_id": parent_id},
no_progress_bar,
from_file,
name,
)
def _download(self, from_info, to_file, name, no_progress_bar):
file_id = self.get_remote_id(from_info)
self.gdrive_download_file(file_id, to_file, name, no_progress_bar)
def all(self):
if not self.cached_ids:
return
query = "({})".format(
" or ".join(
"'{}' in parents".format(dir_id) for dir_id in self.cached_ids
)
)
query += " and trashed=false"
for file1 in self.gdrive_list_item(query):
parent_id = file1["parents"][0]["id"]
path = posixpath.join(self.cached_ids[parent_id], file1["title"])
try:
yield self.path_to_checksum(path)
except ValueError:
# We ignore all the non-cache looking files
logger.debug('Ignoring path as "non-cache looking"')
+56 -42
View File
@@ -1,12 +1,11 @@
from __future__ import unicode_literals, division
import logging
from datetime import timedelta
from functools import wraps
import io
import os.path
import threading
from funcy import cached_property
from funcy import cached_property, wrap_prop
from dvc.config import Config
from dvc.exceptions import DvcException
@@ -14,7 +13,6 @@ from dvc.path_info import CloudURLInfo
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
from dvc.utils.compat import FileNotFoundError # skipcq: PYL-W0622
logger = logging.getLogger(__name__)
@@ -57,23 +55,15 @@ def _upload_to_bucket(
no_progress_bar=True,
):
blob = bucket.blob(to_info.path, chunk_size=chunk_size)
with Tqdm(
desc=name or to_info.path,
total=os.path.getsize(from_file),
bytes=True,
disable=no_progress_bar,
) as pbar:
with io.open(from_file, mode="rb") as fobj:
raw_read = fobj.read
def read(size=chunk_size):
res = raw_read(size)
if res:
pbar.update(len(res))
return res
fobj.read = read
blob.upload_from_file(fobj)
with io.open(from_file, mode="rb") as fobj:
with Tqdm.wrapattr(
fobj,
"read",
desc=name or to_info.path,
total=os.path.getsize(from_file),
disable=no_progress_bar,
) as wrapped:
blob.upload_from_file(wrapped)
class RemoteGS(RemoteBASE):
@@ -83,7 +73,7 @@ class RemoteGS(RemoteBASE):
PARAM_CHECKSUM = "md5"
def __init__(self, repo, config):
super(RemoteGS, self).__init__(repo, config)
super().__init__(repo, config)
url = config.get(Config.SECTION_REMOTE_URL, "gs:///")
self.path_info = self.path_cls(url)
@@ -91,6 +81,7 @@ class RemoteGS(RemoteBASE):
self.projectname = config.get(Config.SECTION_GCP_PROJECTNAME, None)
self.credentialpath = config.get(Config.SECTION_GCP_CREDENTIALPATH)
@wrap_prop(threading.Lock())
@cached_property
def gs(self):
from google.cloud.storage import Client
@@ -136,16 +127,45 @@ class RemoteGS(RemoteBASE):
blob.delete()
def _list_paths(self, bucket, prefix):
for blob in self.gs.bucket(bucket).list_blobs(prefix=prefix):
def _list_paths(self, path_info, max_items=None):
for blob in self.gs.bucket(path_info.bucket).list_blobs(
prefix=path_info.path, max_results=max_items
):
yield blob.name
def list_cache_paths(self):
return self._list_paths(self.path_info.bucket, self.path_info.path)
return self._list_paths(self.path_info)
def walk_files(self, path_info):
for fname in self._list_paths(path_info / ""):
# skip nested empty directories
if fname.endswith("/"):
continue
yield path_info.replace(fname)
def makedirs(self, path_info):
self.gs.bucket(path_info.bucket).blob(
(path_info / "").path
).upload_from_string("")
def isdir(self, path_info):
dir_path = path_info / ""
return bool(list(self._list_paths(dir_path, max_items=1)))
def isfile(self, path_info):
if path_info.path.endswith("/"):
return False
blob = self.gs.bucket(path_info.bucket).blob(path_info.path)
return blob.exists()
def exists(self, path_info):
paths = set(self._list_paths(path_info.bucket, path_info.path))
return any(path_info.path == path for path in paths)
"""Check if the blob exists. If it does not exist,
it could be a part of a directory path.
eg: if `data/file.txt` exists, check for `data` should return True
"""
return self.isfile(path_info) or self.isdir(path_info)
def _upload(self, from_file, to_info, name=None, no_progress_bar=True):
bucket = self.gs.bucket(to_info.bucket)
@@ -160,21 +180,15 @@ class RemoteGS(RemoteBASE):
def _download(self, from_info, to_file, name=None, no_progress_bar=True):
bucket = self.gs.bucket(from_info.bucket)
blob = bucket.get_blob(from_info.path)
with Tqdm(
desc=name or from_info.path,
total=blob.size,
bytes=True,
disable=no_progress_bar,
) as pbar:
with io.open(to_file, mode="wb") as fobj:
raw_write = fobj.write
def write(byte_string):
raw_write(byte_string)
pbar.update(len(byte_string))
fobj.write = write
blob.download_to_file(fobj)
with io.open(to_file, mode="wb") as fobj:
with Tqdm.wrapattr(
fobj,
"write",
desc=name or from_info.path,
total=blob.size,
disable=no_progress_bar,
) as wrapped:
blob.download_to_file(wrapped)
def _generate_download_url(self, path_info, expires=3600):
expiration = timedelta(seconds=int(expires))
+22 -25
View File
@@ -1,24 +1,18 @@
from __future__ import unicode_literals
import io
import logging
import os
import posixpath
import re
from contextlib import closing
from contextlib import contextmanager
from subprocess import PIPE
from subprocess import Popen
import subprocess
from collections import deque
from contextlib import closing, contextmanager
from urllib.parse import urlparse
from .base import RemoteBASE
from .base import RemoteCmdError
from .base import RemoteBASE, RemoteCmdError
from .pool import get_connection
from dvc.config import Config
from dvc.scheme import Schemes
from dvc.utils import fix_env
from dvc.utils import tmp_fname
from dvc.utils.compat import FileNotFoundError
from dvc.utils.compat import urlparse
from dvc.utils import fix_env, tmp_fname
logger = logging.getLogger(__name__)
@@ -30,7 +24,7 @@ class RemoteHDFS(RemoteBASE):
REQUIRES = {"pyarrow": "pyarrow"}
def __init__(self, repo, config):
super(RemoteHDFS, self).__init__(repo, config)
super().__init__(repo, config)
self.path_info = None
url = config.get(Config.SECTION_REMOTE_URL)
if not url:
@@ -68,15 +62,15 @@ class RemoteHDFS(RemoteBASE):
close_fds = os.name != "nt"
executable = os.getenv("SHELL") if os.name != "nt" else None
p = Popen(
p = subprocess.Popen(
cmd,
shell=True,
close_fds=close_fds,
executable=executable,
env=fix_env(os.environ),
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out, err = p.communicate()
if p.returncode != 0:
@@ -104,8 +98,14 @@ class RemoteHDFS(RemoteBASE):
# NOTE: this is how `hadoop fs -cp` works too: it copies through
# your local machine.
with hdfs.open(from_info.path, "rb") as from_fobj:
with hdfs.open(to_info.path, "wb") as to_fobj:
to_fobj.upload(from_fobj)
tmp_info = to_info.parent / tmp_fname(to_info.name)
try:
with hdfs.open(tmp_info.path, "wb") as tmp_fobj:
tmp_fobj.upload(from_fobj)
hdfs.rename(tmp_info.path, to_info.path)
except Exception:
self.remove(tmp_info)
raise
def remove(self, path_info):
if path_info.scheme != "hdfs":
@@ -156,10 +156,9 @@ class RemoteHDFS(RemoteBASE):
def list_cache_paths(self):
if not self.exists(self.path_info):
return []
return
files = []
dirs = [self.path_info.path]
dirs = deque([self.path_info.path])
with self.hdfs(self.path_info) as hdfs:
while dirs:
@@ -167,6 +166,4 @@ class RemoteHDFS(RemoteBASE):
if entry["kind"] == "directory":
dirs.append(urlparse(entry["name"]).path)
elif entry["kind"] == "file":
files.append(urlparse(entry["name"]).path)
return files
yield urlparse(entry["name"]).path
+28 -18
View File
@@ -1,16 +1,14 @@
from __future__ import unicode_literals
import logging
import threading
from funcy import cached_property
from funcy import cached_property, wrap_prop
from dvc.config import Config
from dvc.config import ConfigError
from dvc.exceptions import DvcException
from dvc.exceptions import DvcException, HTTPError
from dvc.progress import Tqdm
from dvc.remote.base import RemoteBASE
from dvc.scheme import Schemes
from dvc.utils.compat import open
logger = logging.getLogger(__name__)
@@ -24,7 +22,7 @@ class RemoteHTTP(RemoteBASE):
PARAM_CHECKSUM = "etag"
def __init__(self, repo, config):
super(RemoteHTTP, self).__init__(repo, config)
super().__init__(repo, config)
url = config.get(Config.SECTION_REMOTE_URL)
self.path_info = self.path_cls(url) if url else None
@@ -36,30 +34,26 @@ class RemoteHTTP(RemoteBASE):
)
def _download(self, from_info, to_file, name=None, no_progress_bar=False):
request = self._request("GET", from_info.url, stream=True)
response = self._request("GET", from_info.url, stream=True)
if response.status_code != 200:
raise HTTPError(response.status_code, response.reason)
with Tqdm(
total=None if no_progress_bar else self._content_length(from_info),
total=None if no_progress_bar else self._content_length(response),
leave=False,
bytes=True,
desc=from_info.url if name is None else name,
disable=no_progress_bar,
) as pbar:
with open(to_file, "wb") as fd:
for chunk in request.iter_content(chunk_size=self.CHUNK_SIZE):
for chunk in response.iter_content(chunk_size=self.CHUNK_SIZE):
fd.write(chunk)
fd.flush()
pbar.update(len(chunk))
def exists(self, path_info):
return bool(self._request("HEAD", path_info.url))
def _content_length(self, url_or_request):
headers = getattr(
url_or_request,
"headers",
self._request("HEAD", url_or_request).headers,
)
res = headers.get("Content-Length")
def _content_length(self, response):
res = response.headers.get("Content-Length")
return int(res) if res else None
def get_file_checksum(self, path_info):
@@ -81,6 +75,7 @@ class RemoteHTTP(RemoteBASE):
return etag
@wrap_prop(threading.Lock())
@cached_property
def _session(self):
import requests
@@ -106,7 +101,22 @@ class RemoteHTTP(RemoteBASE):
kwargs.setdefault("timeout", self.REQUEST_TIMEOUT)
try:
return self._session.request(method, url, **kwargs)
res = self._session.request(method, url, **kwargs)
redirect_no_location = (
kwargs["allow_redirects"]
and res.status_code in (301, 302)
and "location" not in res.headers
)
if redirect_no_location:
# AWS s3 doesn't like to add a location header to its redirects
# from https://s3.amazonaws.com/<bucket name>/* type URLs.
# This should be treated as an error
raise requests.exceptions.RequestException
return res
except requests.exceptions.RequestException:
raise DvcException("could not perform a {} request".format(method))
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from .http import RemoteHTTP
from dvc.scheme import Schemes
+59 -80
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import errno
import logging
import os
@@ -21,18 +19,15 @@ from dvc.remote.base import STATUS_MAP
from dvc.remote.base import STATUS_MISSING
from dvc.remote.base import STATUS_NEW
from dvc.scheme import Schemes
from dvc.scm.tree import is_working_tree
from dvc.system import System
from dvc.utils import copyfile
from dvc.utils.fs import copyfile
from dvc.utils import file_md5
from dvc.utils import makedirs
from dvc.utils import relpath
from dvc.utils import tmp_fname
from dvc.utils import walk_files
from dvc.utils.compat import fspath_py35
from dvc.utils.compat import open
from dvc.utils.compat import str
from dvc.utils.fs import move
from dvc.utils.fs import remove
from dvc.compat import fspath_py35
from dvc.utils.fs import move, makedirs, remove
from dvc.utils.fs import walk_files
logger = logging.getLogger(__name__)
@@ -50,7 +45,7 @@ class RemoteLOCAL(RemoteBASE):
SHARED_MODE_MAP = {None: (0o644, 0o755), "group": (0o664, 0o775)}
def __init__(self, repo, config):
super(RemoteLOCAL, self).__init__(repo, config)
super().__init__(repo, config)
self.protected = config.get(Config.SECTION_CACHE_PROTECTED, False)
shared = config.get(Config.SECTION_CACHE_SHARED)
@@ -66,7 +61,7 @@ class RemoteLOCAL(RemoteBASE):
cwd = config[Config.PRIVATE_CWD]
cache_dir = os.path.abspath(os.path.join(cwd, cache_dir))
self.path_info = PathInfo(cache_dir) if cache_dir else None
self.cache_dir = cache_dir
self._dir_info = {}
@property
@@ -77,23 +72,17 @@ class RemoteLOCAL(RemoteBASE):
def cache_dir(self):
return self.path_info.fspath if self.path_info else None
@cache_dir.setter
def cache_dir(self, value):
self.path_info = PathInfo(value) if value else None
@classmethod
def supported(cls, config):
return True
def list_cache_paths(self):
assert self.path_info is not None
clist = []
for entry in os.listdir(fspath_py35(self.path_info)):
subdir = self.path_info / entry
if not os.path.isdir(fspath_py35(subdir)):
continue
clist.extend(
subdir / cache for cache in os.listdir(fspath_py35(subdir))
)
return clist
return walk_files(self.path_info)
def get(self, md5):
if not md5:
@@ -138,12 +127,19 @@ class RemoteLOCAL(RemoteBASE):
def isdir(path_info):
return os.path.isdir(fspath_py35(path_info))
def iscopy(self, path_info):
return not (
System.is_symlink(path_info) or System.is_hardlink(path_info)
)
@staticmethod
def getsize(path_info):
return os.path.getsize(fspath_py35(path_info))
def walk_files(self, path_info):
for fname in walk_files(path_info, self.repo.dvcignore):
assert is_working_tree(self.repo.tree)
for fname in self.repo.tree.walk_files(path_info):
yield PathInfo(fname)
def get_file_checksum(self, path_info):
@@ -169,14 +165,23 @@ class RemoteLOCAL(RemoteBASE):
move(from_info, to_info, mode=mode)
@staticmethod
def copy(from_info, to_info):
System.copy(from_info, to_info)
def copy(self, from_info, to_info):
tmp_info = to_info.parent / tmp_fname(to_info.name)
try:
System.copy(from_info, tmp_info)
os.rename(fspath_py35(tmp_info), fspath_py35(to_info))
except Exception:
self.remove(tmp_info)
raise
@staticmethod
def symlink(from_info, to_info):
System.symlink(from_info, to_info)
@staticmethod
def is_symlink(path_info):
return System.is_symlink(path_info)
def hardlink(self, from_info, to_info):
# If there are a lot of empty files (which happens a lot in datasets),
# and the cache type is `hardlink`, we might reach link limits and
@@ -203,6 +208,10 @@ class RemoteLOCAL(RemoteBASE):
System.hardlink(from_info, to_info)
@staticmethod
def is_hardlink(path_info):
return System.is_hardlink(path_info)
@staticmethod
def reflink(from_info, to_info):
System.reflink(from_info, to_info)
@@ -404,22 +413,19 @@ class RemoteLOCAL(RemoteBASE):
logger.warning(msg)
@staticmethod
def _unprotect_file(path, allow_copy=True):
def _unprotect_file(path):
if System.is_symlink(path) or System.is_hardlink(path):
if allow_copy:
logger.debug("Unprotecting '{}'".format(path))
tmp = os.path.join(os.path.dirname(path), "." + str(uuid()))
logger.debug("Unprotecting '{}'".format(path))
tmp = os.path.join(os.path.dirname(path), "." + uuid())
# The operations order is important here - if some application
# would access the file during the process of copyfile then it
# would get only the part of file. So, at first, the file
# should be copied with the temporary name, and then
# original file should be replaced by new.
copyfile(
path, tmp, name="Unprotecting '{}'".format(relpath(path))
)
remove(path)
os.rename(tmp, path)
# The operations order is important here - if some application
# would access the file during the process of copyfile then it
# would get only the part of file. So, at first, the file should be
# copied with the temporary name, and then original file should be
# replaced by new.
copyfile(path, tmp, name="Unprotecting '{}'".format(relpath(path)))
remove(path)
os.rename(tmp, path)
else:
logger.debug(
@@ -429,11 +435,13 @@ class RemoteLOCAL(RemoteBASE):
os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
def _unprotect_dir(self, path, allow_copy=True):
for fname in walk_files(path, self.repo.dvcignore):
self._unprotect_file(fname, allow_copy)
def _unprotect_dir(self, path):
assert is_working_tree(self.repo.tree)
def unprotect(self, path_info, allow_copy=True):
for fname in self.repo.tree.walk_files(path):
RemoteLOCAL._unprotect_file(fname)
def unprotect(self, path_info):
path = path_info.fspath
if not os.path.exists(path):
raise DvcException(
@@ -441,9 +449,9 @@ class RemoteLOCAL(RemoteBASE):
)
if os.path.isdir(path):
self._unprotect_dir(path, allow_copy)
self._unprotect_dir(path)
else:
self._unprotect_file(path, allow_copy)
RemoteLOCAL._unprotect_file(path)
@staticmethod
def protect(path_info):
@@ -466,6 +474,10 @@ class RemoteLOCAL(RemoteBASE):
info = self.checksum_to_path_info(checksum)
return info.with_name(info.name + self.UNPACKED_DIR_SUFFIX)
def _remove_unpacked_dir(self, checksum):
path_info = self._get_unpacked_dir_path_info(checksum)
self.remove(path_info)
def _path_info_changed(self, path_info):
if self.exists(path_info) and self.state.get(path_info):
return False
@@ -517,36 +529,3 @@ class RemoteLOCAL(RemoteBASE):
if self.is_dir_checksum(c):
unpacked.add(c + self.UNPACKED_DIR_SUFFIX)
return unpacked
def _get_cache_type(self, path_info):
if self.cache_type_confirmed:
return self.cache_types[0]
workspace_file = path_info.with_name("." + uuid())
test_cache_file = self.path_info / ".cache_type_test_file"
if not self.exists(test_cache_file):
with open(fspath_py35(test_cache_file), "wb") as fobj:
fobj.write(bytes(1))
try:
self.link(test_cache_file, workspace_file)
finally:
self.remove(workspace_file)
self.remove(test_cache_file)
self.cache_type_confirmed = True
return self.cache_types[0]
def _link_matches(self, path_info):
is_hardlink = System.is_hardlink(path_info)
is_symlink = System.is_symlink(path_info)
is_copy_or_reflink = not is_hardlink and not is_symlink
cache_type = self._get_cache_type(path_info)
if cache_type == "symlink":
return is_symlink
if cache_type == "hardlink":
return is_hardlink
return is_copy_or_reflink
+23 -24
View File
@@ -1,8 +1,8 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import logging
import os
import threading
from funcy import cached_property, wrap_prop
from dvc.config import Config
from dvc.path_info import CloudURLInfo
@@ -40,7 +40,7 @@ class RemoteOSS(RemoteBASE):
COPY_POLL_SECONDS = 5
def __init__(self, repo, config):
super(RemoteOSS, self).__init__(repo, config)
super().__init__(repo, config)
url = config.get(Config.SECTION_REMOTE_URL)
self.path_info = self.path_cls(url) if url else None
@@ -61,30 +61,29 @@ class RemoteOSS(RemoteBASE):
or "defaultSecret"
)
self._bucket = None
@property
@wrap_prop(threading.Lock())
@cached_property
def oss_service(self):
import oss2
if self._bucket is None:
logger.debug("URL {}".format(self.path_info))
logger.debug("key id {}".format(self.key_id))
logger.debug("key secret {}".format(self.key_secret))
auth = oss2.Auth(self.key_id, self.key_secret)
self._bucket = oss2.Bucket(
auth, self.endpoint, self.path_info.bucket
logger.debug("URL: {}".format(self.path_info))
logger.debug("key id: {}".format(self.key_id))
logger.debug("key secret: {}".format(self.key_secret))
auth = oss2.Auth(self.key_id, self.key_secret)
bucket = oss2.Bucket(auth, self.endpoint, self.path_info.bucket)
# Ensure bucket exists
try:
bucket.get_bucket_info()
except oss2.exceptions.NoSuchBucket:
bucket.create_bucket(
oss2.BUCKET_ACL_PUBLIC_READ,
oss2.models.BucketCreateConfig(
oss2.BUCKET_STORAGE_CLASS_STANDARD
),
)
try: # verify that bucket exists
self._bucket.get_bucket_info()
except oss2.exceptions.NoSuchBucket:
self._bucket.create_bucket(
oss2.BUCKET_ACL_PUBLIC_READ,
oss2.models.BucketCreateConfig(
oss2.BUCKET_STORAGE_CLASS_STANDARD
),
)
return self._bucket
return bucket
def remove(self, path_info):
if path_info.scheme != self.scheme:
+3 -1
View File
@@ -1,7 +1,8 @@
from collections import deque
from contextlib import contextmanager
import threading
from funcy import memoize
from funcy import memoize, wrap_with
@contextmanager
@@ -17,6 +18,7 @@ def get_connection(conn_func, *args, **kwargs):
pool.release(conn)
@wrap_with(threading.Lock())
@memoize
def get_pool(conn_func, *args, **kwargs):
return Pool(conn_func, *args, **kwargs)
+82 -15
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging
import os
import threading
from funcy import cached_property
from funcy import cached_property, wrap_prop
from dvc.config import Config
from dvc.config import Config, ConfigError
from dvc.exceptions import DvcException
from dvc.exceptions import ETagMismatchError
from dvc.path_info import CloudURLInfo
@@ -24,7 +24,7 @@ class RemoteS3(RemoteBASE):
PARAM_CHECKSUM = "etag"
def __init__(self, repo, config):
super(RemoteS3, self).__init__(repo, config)
super().__init__(repo, config)
url = config.get(Config.SECTION_REMOTE_URL, "s3://")
self.path_info = self.path_cls(url)
@@ -52,10 +52,13 @@ class RemoteS3(RemoteBASE):
if self.acl:
self.extra_args["ACL"] = self.acl
self._append_aws_grants_to_extra_args(config)
shared_creds = config.get(Config.SECTION_AWS_CREDENTIALPATH)
if shared_creds:
os.environ.setdefault("AWS_SHARED_CREDENTIALS_FILE", shared_creds)
@wrap_prop(threading.Lock())
@cached_property
def s3(self):
import boto3
@@ -84,8 +87,8 @@ class RemoteS3(RemoteBASE):
obj = s3.head_object(Bucket=bucket, Key=path, *args, **kwargs)
except Exception as exc:
raise DvcException(
"s3://{}/{} does not exist".format(bucket, path), exc
)
"s3://{}/{} does not exist".format(bucket, path)
) from exc
return obj
@classmethod
@@ -153,6 +156,7 @@ class RemoteS3(RemoteBASE):
# the same throughout the transfer, so it means that in order to
# preserve etag, we need to transfer each part separately, so the
# object is transfered in the same chunks as it was originally.
from boto3.s3.transfer import TransferConfig
obj = cls.get_head_object(s3, from_info.bucket, from_info.path)
etag = obj["ETag"].strip('"')
@@ -166,7 +170,13 @@ class RemoteS3(RemoteBASE):
)
else:
source = {"Bucket": from_info.bucket, "Key": from_info.path}
s3.copy(source, to_info.bucket, to_info.path, ExtraArgs=extra_args)
s3.copy(
source,
to_info.bucket,
to_info.path,
ExtraArgs=extra_args,
Config=TransferConfig(multipart_threshold=size + 1),
)
cached_etag = cls.get_etag(s3, to_info.bucket, to_info.path)
if etag != cached_etag:
@@ -191,11 +201,7 @@ class RemoteS3(RemoteBASE):
}
paginator = self.s3.get_paginator(self.list_objects_api)
for page in paginator.paginate(**kwargs):
contents = page.get("Contents", None)
if not contents:
continue
for item in contents:
yield item
yield from page.get("Contents", ())
def _list_paths(self, path_info, max_items=None):
return (
@@ -205,10 +211,37 @@ class RemoteS3(RemoteBASE):
def list_cache_paths(self):
return self._list_paths(self.path_info)
def isfile(self, path_info):
from botocore.exceptions import ClientError
if path_info.path.endswith("/"):
return False
try:
self.s3.head_object(Bucket=path_info.bucket, Key=path_info.path)
except ClientError as exc:
if exc.response["Error"]["Code"] != "404":
raise
return False
return True
def exists(self, path_info):
"""Check if the blob exists. If it does not exist,
it could be a part of a directory path.
eg: if `data/file.txt` exists, check for `data` should return True
"""
return self.isfile(path_info) or self.isdir(path_info)
def makedirs(self, path_info):
# We need to support creating empty directories, which means
# creating an object with an empty body and a trailing slash `/`.
#
# We are not creating directory objects for every parent prefix,
# as it is not required.
dir_path = path_info / ""
fname = next(self._list_paths(path_info, max_items=1), "")
return path_info.path == fname or fname.startswith(dir_path.path)
self.s3.put_object(Bucket=path_info.bucket, Key=dir_path.path, Body="")
def isdir(self, path_info):
# S3 doesn't have a concept for directories.
@@ -268,5 +301,39 @@ class RemoteS3(RemoteBASE):
)
def walk_files(self, path_info, max_items=None):
for fname in self._list_paths(path_info, max_items):
for fname in self._list_paths(path_info / "", max_items):
if fname.endswith("/"):
continue
yield path_info.replace(path=fname)
def _append_aws_grants_to_extra_args(self, config):
# Keys for extra_args can be one of the following list:
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/s3.html#boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS
"""
ALLOWED_UPLOAD_ARGS = [
'ACL', 'CacheControl', 'ContentDisposition', 'ContentEncoding',
'ContentLanguage', 'ContentType', 'Expires', 'GrantFullControl',
'GrantRead', 'GrantReadACP', 'GrantWriteACP', 'Metadata',
'RequestPayer', 'ServerSideEncryption', 'StorageClass',
'SSECustomerAlgorithm', 'SSECustomerKey', 'SSECustomerKeyMD5',
'SSEKMSKeyId', 'WebsiteRedirectLocation'
]
"""
grants = {
Config.SECTION_AWS_GRANT_FULL_CONTROL: "GrantFullControl",
Config.SECTION_AWS_GRANT_READ: "GrantRead",
Config.SECTION_AWS_GRANT_READ_ACP: "GrantReadACP",
Config.SECTION_AWS_GRANT_WRITE_ACP: "GrantWriteACP",
}
for grant_option, extra_args_key in grants.items():
if config.get(grant_option, ""):
if self.acl:
raise ConfigError(
"`acl` and `grant_*` AWS S3 config options "
"are mutually exclusive"
)
self.extra_args[extra_args_key] = config.get(grant_option)
+21 -25
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import errno
import getpass
import io
@@ -8,8 +6,10 @@ import logging
import os
import threading
from concurrent.futures import ThreadPoolExecutor
from contextlib import closing
from contextlib import contextmanager
from contextlib import closing, contextmanager
from urllib.parse import urlparse
from funcy import memoize, wrap_with
import dvc.prompt as prompt
from dvc.config import Config
@@ -18,14 +18,19 @@ from dvc.remote.base import RemoteBASE
from dvc.remote.pool import get_connection
from dvc.scheme import Schemes
from dvc.utils import to_chunks
from dvc.utils.compat import StringIO
from dvc.utils.compat import urlparse
logger = logging.getLogger(__name__)
saved_passwords = {}
saved_passwords_lock = threading.Lock()
@wrap_with(threading.Lock())
@memoize
def ask_password(host, user, port):
return prompt.password(
"Enter a private key passphrase or a password for "
"host '{host}' port '{port}' user '{user}'".format(
host=host, port=port, user=user
)
)
class RemoteSSH(RemoteBASE):
@@ -44,7 +49,7 @@ class RemoteSSH(RemoteBASE):
DEFAULT_CACHE_TYPES = ["copy"]
def __init__(self, repo, config):
super(RemoteSSH, self).__init__(repo, config)
super().__init__(repo, config)
url = config.get(Config.SECTION_REMOTE_URL)
if url:
parsed = urlparse(url)
@@ -98,7 +103,7 @@ class RemoteSSH(RemoteBASE):
ssh_config = paramiko.SSHConfig()
with open(user_config_file) as f:
# For whatever reason parsing directly from f is unreliable
f_copy = StringIO(f.read())
f_copy = io.StringIO(f.read())
ssh_config.parse(f_copy)
user_ssh_config = ssh_config.lookup(hostname)
return user_ssh_config
@@ -120,21 +125,11 @@ class RemoteSSH(RemoteBASE):
def ensure_credentials(self, path_info=None):
if path_info is None:
path_info = self.path_info
host, user, port = path_info.host, path_info.user, path_info.port
# NOTE: we use the same password regardless of the server :(
if self.ask_password and self.password is None:
with saved_passwords_lock:
server_key = (host, user, port)
password = saved_passwords.get(server_key)
if password is None:
saved_passwords[server_key] = password = prompt.password(
"Enter a private key passphrase or a password for "
"host '{host}' port '{port}' user '{user}'".format(
host=host, port=port, user=user
)
)
self.password = password
host, user, port = path_info.host, path_info.user, path_info.port
self.password = ask_password(host, user, port)
def ssh(self, path_info):
self.ensure_credentials(path_info)
@@ -180,7 +175,7 @@ class RemoteSSH(RemoteBASE):
raise NotImplementedError
with self.ssh(from_info) as ssh:
ssh.copy(from_info.path, to_info.path)
ssh.atomic_copy(from_info.path, to_info.path)
def symlink(self, from_info, to_info):
if not from_info.scheme == to_info.scheme == self.scheme:
@@ -264,7 +259,8 @@ class RemoteSSH(RemoteBASE):
def list_cache_paths(self):
with self.ssh(self.path_info) as ssh:
return list(ssh.walk_files(self.path_info.path))
# If we simply return an iterator then with above closes instantly
yield from ssh.walk_files(self.path_info.path)
def walk_files(self, path_info):
with self.ssh(path_info) as ssh:
+18 -23
View File
@@ -3,6 +3,7 @@ import logging
import os
import posixpath
import stat
from contextlib import suppress
from funcy import cached_property
@@ -12,7 +13,6 @@ except ImportError:
paramiko = None
from dvc.utils import tmp_fname
from dvc.utils.compat import ignore_file_not_found
from dvc.progress import Tqdm
from dvc.exceptions import DvcException
from dvc.remote.base import RemoteCmdError
@@ -60,13 +60,13 @@ class SSHConnection:
self._ssh.close()
def st_mode(self, path):
with ignore_file_not_found():
with suppress(FileNotFoundError):
return self.sftp.lstat(path).st_mode
return 0
def getsize(self, path):
with ignore_file_not_found():
with suppress(FileNotFoundError):
return self.sftp.lstat(path).st_size
return 0
@@ -103,14 +103,13 @@ class SSHConnection:
if tail:
try:
self.sftp.mkdir(path)
except IOError as e:
except IOError as exc:
# Since paramiko errors are very vague we need to recheck
# whether it's because path already exists or something else
if e.errno == errno.EACCES or not self.exists(path):
if exc.errno == errno.EACCES or not self.exists(path):
raise DvcException(
"unable to create remote directory '{}'".format(path),
cause=e,
)
"unable to create remote directory '{}'".format(path)
) from exc
def walk(self, directory, topdown=True):
# NOTE: original os.walk() implementation [1] with default options was
@@ -123,9 +122,8 @@ class SSHConnection:
raise DvcException(
"couldn't get the '{}' remote directory files list".format(
directory
),
cause=exc,
)
)
) from exc
dirs = []
nondirs = []
@@ -141,8 +139,7 @@ class SSHConnection:
for dname in dirs:
newpath = posixpath.join(directory, dname)
for entry in self.walk(newpath, topdown=topdown):
yield entry
yield from self.walk(newpath, topdown=topdown)
if not topdown:
yield directory, dirs, nondirs
@@ -153,22 +150,20 @@ class SSHConnection:
yield posixpath.join(root, fname)
def _remove_file(self, path):
with ignore_file_not_found():
with suppress(FileNotFoundError):
self.sftp.remove(path)
def _remove_dir(self, path):
for root, dirs, files in self.walk(path, topdown=False):
for fname in files:
path = posixpath.join(root, fname)
with ignore_file_not_found():
self._remove_file(path)
with suppress(FileNotFoundError):
self._remove_file(posixpath.join(root, fname))
for dname in dirs:
path = posixpath.join(root, dname)
with ignore_file_not_found():
self.sftp.rmdir(dname)
with suppress(FileNotFoundError):
self.sftp.rmdir(posixpath.join(root, dname))
with ignore_file_not_found():
with suppress(FileNotFoundError):
self.sftp.rmdir(path)
def remove(self, path):
@@ -195,10 +190,10 @@ class SSHConnection:
try:
self.sftp.rename(src, dst)
except OSError:
self._atomic_copy(src, dst)
self.atomic_copy(src, dst)
self.remove(src)
def _atomic_copy(self, src, dst):
def atomic_copy(self, src, dst):
tmp = tmp_fname(dst)
try:
+89 -115
View File
@@ -1,29 +1,24 @@
from __future__ import unicode_literals
import logging
import os
from contextlib import contextmanager
from functools import wraps
from itertools import chain
from funcy import cached_property
from dvc.ignore import CleanTree
from dvc.compat import fspath_py35
from funcy import cached_property, cat
from .graph import check_acyclic
from .graph import get_pipeline
from .graph import get_pipelines
from .graph import get_stages
from dvc.config import Config
from dvc.exceptions import FileMissingError
from dvc.exceptions import NotDvcRepoError
from dvc.exceptions import OutputNotFoundError
from dvc.ignore import DvcIgnoreFilter
from dvc.exceptions import (
FileMissingError,
NotDvcRepoError,
OutputNotFoundError,
)
from dvc.path_info import PathInfo
from dvc.remote.base import RemoteActionNotImplemented
from dvc.utils import relpath
from dvc.utils.fs import path_isin
from dvc.utils.compat import FileNotFoundError
from dvc.utils.compat import fspath_py35
from dvc.utils.compat import open as _open
from .graph import check_acyclic, get_pipeline, get_pipelines
logger = logging.getLogger(__name__)
@@ -69,13 +64,14 @@ class Repo(object):
def __init__(self, root_dir=None):
from dvc.state import State
from dvc.lock import Lock
from dvc.lock import make_lock
from dvc.scm import SCM
from dvc.cache import Cache
from dvc.data_cloud import DataCloud
from dvc.repo.metrics import Metrics
from dvc.scm.tree import WorkingTree
from dvc.repo.tag import Tag
from dvc.utils.fs import makedirs
root_dir = self.find_root(root_dir)
@@ -88,10 +84,17 @@ class Repo(object):
self.tree = WorkingTree(self.root_dir)
self.lock = Lock(
self.tmp_dir = os.path.join(self.dvc_dir, "tmp")
makedirs(self.tmp_dir, exist_ok=True)
hardlink_lock = self.config.config["core"].get("hardlink_lock", False)
self.lock = make_lock(
os.path.join(self.dvc_dir, "lock"),
tmp_dir=os.path.join(self.dvc_dir, "tmp"),
hardlink_lock=hardlink_lock,
friendly=True,
)
# NOTE: storing state and link_state in the repository itself to avoid
# any possible state corruption in 'shared cache dir' scenario.
self.state = State(self, self.config.config)
@@ -116,7 +119,7 @@ class Repo(object):
@tree.setter
def tree(self, tree):
self._tree = tree
self._tree = tree if isinstance(tree, CleanTree) else CleanTree(tree)
# Our graph cache is no longer valid, as it was based on the previous
# tree.
self._reset()
@@ -162,9 +165,8 @@ class Repo(object):
flist = (
[self.config.config_local_file, updater.updater_file]
+ [self.lock.lockfile, updater.lock.lockfile, self.tmp_dir]
+ self.state.files
+ self.lock.files
+ updater.lock.files
)
if path_isin(self.cache.local.cache_dir, self.root_dir):
@@ -183,32 +185,32 @@ class Repo(object):
G = graph or self.graph
if not target:
return get_stages(G)
return list(G)
target = os.path.abspath(target)
if recursive and os.path.isdir(target):
attrs = nx.get_node_attributes(G, "stage")
nodes = [node for node in nx.dfs_postorder_nodes(G)]
ret = []
for node in nodes:
stage = attrs[node]
if path_isin(stage.path, target):
ret.append(stage)
return ret
stages = nx.dfs_postorder_nodes(G)
return [stage for stage in stages if path_isin(stage.path, target)]
stage = Stage.load(self, target)
if not with_deps:
return [stage]
node = relpath(stage.path, self.root_dir)
pipeline = get_pipeline(get_pipelines(G), node)
pipeline = get_pipeline(get_pipelines(G), stage)
return list(nx.dfs_postorder_nodes(pipeline, stage))
return [
pipeline.node[n]["stage"]
for n in nx.dfs_postorder_nodes(pipeline, node)
]
def collect_granular(self, target, *args, **kwargs):
if not target:
return [(stage, None) for stage in self.stages]
try:
out, = self.find_outs_by_path(target, strict=False)
filter_info = PathInfo(os.path.abspath(target))
return [(out.stage, filter_info)]
except OutputNotFoundError:
stages = self.collect(target, *args, **kwargs)
return [(stage, None) for stage in stages]
def used_cache(
self,
@@ -244,28 +246,24 @@ class Repo(object):
all_tags=all_tags,
all_commits=all_commits,
):
if targets:
stages = []
for target in targets:
collected = self.collect(
target, recursive=recursive, with_deps=with_deps
)
stages.extend(collected)
else:
stages = self.stages
targets = targets or [None]
for stage in stages:
if stage.is_repo_import:
dep, = stage.deps
cache.external[dep.repo_pair].add(dep.def_path)
continue
pairs = cat(
self.collect_granular(
target, recursive=recursive, with_deps=with_deps
)
for target in targets
)
for out in stage.outs:
used_cache = out.get_used_cache(
remote=remote, force=force, jobs=jobs
)
suffix = "({})".format(branch) if branch else ""
cache.update(used_cache, suffix=suffix)
suffix = "({})".format(branch) if branch else ""
for stage, filter_info in pairs:
used_cache = stage.get_used_cache(
remote=remote,
force=force,
jobs=jobs,
filter_info=filter_info,
)
cache.update(used_cache, suffix=suffix)
return cache
@@ -316,15 +314,15 @@ class Repo(object):
)
G = nx.DiGraph()
stages = stages or self.collect_stages()
stages = stages or self.stages
stages = [stage for stage in stages if stage]
outs = {}
for stage in stages:
for out in stage.outs:
if out.path_info in outs:
stages = [stage.relpath, outs[out.path_info].stage.relpath]
raise OutputDuplicationError(str(out), stages)
dup_stages = [stage, outs[out.path_info].stage]
raise OutputDuplicationError(str(out), dup_stages)
outs[out.path_info] = out
for stage in stages:
@@ -337,27 +335,19 @@ class Repo(object):
stage_path_info = PathInfo(stage.path)
for p in chain([stage_path_info], stage_path_info.parents):
if p in outs:
raise StagePathAsOutputError(stage.wdir, stage.relpath)
raise StagePathAsOutputError(stage, str(outs[p]))
for stage in stages:
node = relpath(stage.path, self.root_dir)
G.add_node(node, stage=stage)
G.add_node(stage)
for dep in stage.deps:
if dep.path_info is None:
continue
for out in outs:
if (
out == dep.path_info
or dep.path_info.isin(out)
or out.isin(dep.path_info)
):
dep_stage = outs[out].stage
dep_node = relpath(dep_stage.path, self.root_dir)
G.add_node(dep_node, stage=dep_stage)
G.add_edge(node, dep_node)
for out_path_info, out in outs.items():
if out_path_info.overlaps(dep.path_info):
G.add_node(out.stage)
G.add_edge(stage, out.stage)
check_acyclic(G)
@@ -371,18 +361,8 @@ class Repo(object):
def pipelines(self):
return get_pipelines(self.graph)
@staticmethod
def _filter_out_dirs(dirs, outs, root_dir):
def filter_dirs(dname):
path = os.path.join(root_dir, dname)
for out in outs:
if path == os.path.normpath(out):
return False
return True
return list(filter(filter_dirs, dirs))
def collect_stages(self):
@cached_property
def stages(self):
"""
Walks down the root directory looking for Dvcfiles,
skipping the directories that are related with
@@ -395,41 +375,38 @@ class Repo(object):
from dvc.stage import Stage
stages = []
outs = []
outs = set()
for root, dirs, files in self.tree.walk(
self.root_dir, dvcignore=self.dvcignore
):
for root, dirs, files in self.tree.walk(self.root_dir):
for fname in files:
path = os.path.join(root, fname)
if not Stage.is_valid_filename(path):
continue
stage = Stage.load(self, path)
for out in stage.outs:
if out.scheme == "local":
outs.append(out.fspath + out.sep)
stages.append(stage)
dirs[:] = self._filter_out_dirs(dirs, outs, root)
for out in stage.outs:
if out.scheme == "local":
outs.add(out.fspath)
dirs[:] = [d for d in dirs if os.path.join(root, d) not in outs]
return stages
@cached_property
def stages(self):
return get_stages(self.graph)
def find_outs_by_path(self, path, outs=None, recursive=False):
def find_outs_by_path(self, path, outs=None, recursive=False, strict=True):
if not outs:
outs = [out for stage in self.stages for out in stage.outs]
abs_path = os.path.abspath(path)
path_info = PathInfo(abs_path)
is_dir = self.tree.isdir(abs_path)
match = path_info.__eq__ if strict else path_info.isin_or_eq
def func(out):
if out.scheme == "local" and out.fspath == abs_path:
if out.scheme == "local" and match(out.path_info):
return True
if is_dir and recursive and out.path_info.isin(abs_path):
if is_dir and recursive and out.path_info.isin(path_info):
return True
return False
@@ -450,29 +427,30 @@ class Repo(object):
return self.DVC_DIR in path_parts
@contextmanager
def open(self, path, remote=None, mode="r", encoding=None):
def open_by_relpath(self, path, remote=None, mode="r", encoding=None):
"""Opens a specified resource as a file descriptor"""
cause = None
try:
out, = self.find_outs_by_path(path)
except OutputNotFoundError as e:
out = self.find_out_by_relpath(path)
except OutputNotFoundError as exc:
out = None
cause = e
cause = exc
if out and out.use_cache:
try:
with self._open_cached(out, remote, mode, encoding) as fd:
yield fd
return
except FileNotFoundError as e:
raise FileMissingError(relpath(path, self.root_dir), cause=e)
except FileNotFoundError as exc:
raise FileMissingError(path) from exc
if self.tree.exists(path):
with self.tree.open(path, mode, encoding) as fd:
abs_path = os.path.join(self.root_dir, path)
if os.path.exists(abs_path):
with open(abs_path, mode=mode, encoding=encoding) as fd:
yield fd
return
raise FileMissingError(relpath(path, self.root_dir), cause=cause)
raise FileMissingError(path) from cause
def _open_cached(self, out, remote=None, mode="r", encoding=None):
if out.isdir():
@@ -482,7 +460,7 @@ class Repo(object):
cache_file = fspath_py35(cache_file)
if os.path.exists(cache_file):
return _open(cache_file, mode=mode, encoding=encoding)
return open(cache_file, mode=mode, encoding=encoding)
try:
remote_obj = self.cloud.get_remote(remote)
@@ -493,11 +471,7 @@ class Repo(object):
cache_info = out.get_used_cache(remote=remote)
self.cloud.pull(cache_info, remote=remote)
return _open(cache_file, mode=mode, encoding=encoding)
@cached_property
def dvcignore(self):
return DvcIgnoreFilter(self.root_dir, self.tree)
return open(cache_file, mode=mode, encoding=encoding)
def close(self):
self.scm.close()
+3 -7
View File
@@ -1,10 +1,7 @@
from __future__ import unicode_literals
import logging
import os
import colorama
from six import string_types
from . import locked
from dvc.exceptions import RecursiveAddingWhileUsingFilename
@@ -12,7 +9,6 @@ from dvc.progress import Tqdm
from dvc.repo.scm_context import scm_context
from dvc.stage import Stage
from dvc.utils import LARGE_DIR_SIZE
from dvc.utils import walk_files
logger = logging.getLogger(__name__)
@@ -23,7 +19,7 @@ def add(repo, targets, recursive=False, no_commit=False, fname=None):
if recursive and fname:
raise RecursiveAddingWhileUsingFilename()
if isinstance(targets, string_types):
if isinstance(targets, str):
targets = [targets]
stages_list = []
@@ -37,7 +33,7 @@ def add(repo, targets, recursive=False, no_commit=False, fname=None):
"You are adding a large directory '{target}' recursively,"
" consider tracking it as a whole instead.\n"
"{purple}HINT:{nc} Remove the generated DVC-file and then"
" run {cyan}dvc add {target}{nc}".format(
" run `{cyan}dvc add {target}{nc}`".format(
purple=colorama.Fore.MAGENTA,
cyan=colorama.Fore.CYAN,
nc=colorama.Style.RESET_ALL,
@@ -68,7 +64,7 @@ def _find_all_targets(repo, target, recursive):
if os.path.isdir(target) and recursive:
return [
fname
for fname in walk_files(target, repo.dvcignore)
for fname in repo.tree.walk_files(target)
if not repo.is_dvc_internal(fname)
if not Stage.is_stage_file(fname)
if not repo.scm.belongs_to_scm(fname)
+12 -26
View File
@@ -4,12 +4,7 @@ from dvc.scm.tree import WorkingTree
def brancher( # noqa: E302
self,
branches=None,
all_branches=False,
tags=None,
all_tags=False,
all_commits=False,
self, revs=None, all_branches=False, all_tags=False, all_commits=False
):
"""Generator that iterates over specified revisions.
@@ -25,12 +20,12 @@ def brancher( # noqa: E302
- empty string it there is no branches to iterate over
- "Working Tree" if there are uncommitted changes in the SCM repo
"""
if not any([branches, all_branches, tags, all_tags, all_commits]):
if not any([revs, all_branches, all_tags, all_commits]):
yield ""
return
saved_tree = self.tree
revs = []
revs = revs or []
scm = self.scm
@@ -41,24 +36,15 @@ def brancher( # noqa: E302
revs = scm.list_all_commits()
else:
if all_branches:
branches = scm.list_branches()
revs.extend(scm.list_branches())
if all_tags:
tags = scm.list_tags()
revs.extend(scm.list_tags())
if branches is not None:
revs.extend(branches)
if tags is not None:
revs.extend(tags)
# NOTE: it might be a good idea to wrap this loop in try/finally block
# to don't leave the tree on some unexpected branch after the
# `brancher()`, but this could cause problems on exception handling
# code which might expect the tree on which exception was raised to
# stay in place. This behavior is a subject to change.
for sha, names in group_by(scm.resolve_rev, revs).items():
self.tree = scm.get_tree(sha)
yield ", ".join(names)
self.tree = saved_tree
try:
if revs:
for sha, names in group_by(scm.resolve_rev, revs).items():
self.tree = scm.get_tree(sha)
yield ", ".join(names)
finally:
self.tree = saved_tree
+24 -14
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import logging
from dvc.exceptions import CheckoutError
@@ -20,42 +18,54 @@ def _cleanup_unused_links(repo):
repo.state.remove_unused_links(used)
def get_all_files_numbers(stages):
return sum(stage.get_all_files_number() for stage in stages)
def get_all_files_numbers(pairs):
return sum(
stage.get_all_files_number(filter_info) for stage, filter_info in pairs
)
def _checkout(
self, targets=None, with_deps=False, force=False, recursive=False
self,
targets=None,
with_deps=False,
force=False,
relink=False,
recursive=False,
):
from dvc.stage import StageFileDoesNotExistError, StageFileBadNameError
stages = set()
if not targets:
targets = [None]
_cleanup_unused_links(self)
pairs = set()
for target in targets:
try:
new = self.collect(
target, with_deps=with_deps, recursive=recursive
pairs.update(
self.collect_granular(
target, with_deps=with_deps, recursive=recursive
)
)
stages.update(new)
except (StageFileDoesNotExistError, StageFileBadNameError) as exc:
if not target:
raise
raise CheckoutErrorSuggestGit(target, exc)
raise CheckoutErrorSuggestGit(target) from exc
total = get_all_files_numbers(stages)
total = get_all_files_numbers(pairs)
if total == 0:
logger.info("Nothing to do")
failed = []
with Tqdm(
total=total, unit="file", desc="Checkout", disable=total == 0
) as pbar:
for stage in stages:
for stage, filter_info in pairs:
failed.extend(
stage.checkout(force=force, progress_callback=pbar.update_desc)
stage.checkout(
force=force,
progress_callback=pbar.update_desc,
relink=relink,
filter_info=filter_info,
)
)
if failed:
raise CheckoutError(failed)
+10 -3
View File
@@ -1,8 +1,15 @@
from dvc.utils.fs import remove
from . import locked
def destroy(self):
for stage in self.stages:
@locked
def _destroy_stages(repo):
for stage in repo.stages:
stage.remove(remove_outs=False)
remove(self.dvc_dir)
# NOTE: not locking `destroy`, as `remove` will need to delete `.dvc` dir,
# which will cause issues on Windows, as `.dvc/lock` will be busy.
def destroy(repo):
_destroy_stages(repo)
remove(repo.dvc_dir)
+3 -8
View File
@@ -1,16 +1,11 @@
from __future__ import unicode_literals
import os
from errno import ENOENT
import dvc.logger as logger
from . import locked
from dvc import logger
from dvc.scm.base import FileNotInCommitError
from dvc.scm.git import DIFF_A_REF
from dvc.scm.git import DIFF_A_TREE
from dvc.scm.git import DIFF_B_REF
from dvc.scm.git import DIFF_B_TREE
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
DIFF_TARGET = "target"
+5 -6
View File
@@ -1,12 +1,9 @@
from __future__ import unicode_literals
import logging
from dvc.cache import NamedCache
from dvc.config import NoRemoteError
from dvc.exceptions import DownloadError
from dvc.exceptions import OutputNotFoundError
from dvc.external_repo import external_repo
from dvc.scm.base import CloneError
@@ -72,11 +69,13 @@ def _fetch(
def _fetch_external(self, repo_url, repo_rev, files):
failed = 0
from dvc.external_repo import external_repo
cache_dir = self.cache.local.cache_dir
failed = 0
try:
with external_repo(repo_url, repo_rev, cache_dir=cache_dir) as repo:
with external_repo(repo_url, repo_rev) as repo:
repo.cache.local.cache_dir = self.cache.local.cache_dir
with repo.state:
cache = NamedCache()
for name in files:

Some files were not shown because too many files have changed in this diff Show More