codecov[bot]: # [Codecov](https://codecov.io/gh/iterative/dvc/pull/3428?src=pr&el=h1) Report
> Merging [#3428](https://codecov.io/gh/iterative/dvc/pull/3428?src=pr&el=desc) into [master](https://codecov.io/gh/iterative/dvc/commit/05cc023164766fbbec91642d7d8af513c8d4079b?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/iterative/dvc/pull/3428/graphs/tree.svg?width=650&token=VOfa1EJWC4&height=150&src=pr)](https://codecov.io/gh/iterative/dvc/pull/3428?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #3428   +/-   ##
=======================================
  Coverage   93.08%   93.08%           
=======================================
  Files         140      140           
  Lines        8515     8515           
=======================================
  Hits         7926     7926           
  Misses        589      589
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/iterative/dvc/pull/3428?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/iterative/dvc/pull/3428?src=pr&el=footer). Last update [05cc023...4e9289f](https://codecov.io/gh/iterative/dvc/pull/3428?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

skshetry: We (@efiop and I) discussed this on 1o1. A better way to fix this would be to try to create a temporary file to check if the **System** supports `{ref,hard,sym}`link. Though, it'll need some refactor, so, I'll create an issue for working on this later. 
diff --git a/dvc/api.py b/dvc/api.py
index d1312baaf..98763d16a 100644
--- a/dvc/api.py
+++ b/dvc/api.py
@@ -7,31 +7,44 @@ from dvc.external_repo import external_repo
 
 
 class UrlNotDvcRepoError(DvcException):
-    """Thrown if given URL is not a DVC repository.
-
-    Args:
-        url (str): URL to the repository
-    """
+    """Thrown if the given URL is not a DVC 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 URL to the storage location of a data artifact tracked
-    by DVC, specified by its path in a repo.
+    """
+    Returns the URL to the storage location of a data file or directory tracked
+    in a DVC repo. For Git repos, HEAD is used unless a rev argument is
+    supplied. The default remote is tried unless a remote argument is supplied.
 
-    NOTE: There's no guarantee that the file actually exists in that location.
+    Raises UrlNotDvcRepoError if repo is not a DVC project.
+
+    NOTE: This function does not check for the actual existence of the file or
+    directory in the remote storage.
     """
     with _make_repo(repo, rev=rev) as _repo:
-        _require_dvc(_repo)
+        if not isinstance(_repo, Repo):
+            raise UrlNotDvcRepoError(_repo.url)
         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):
-    """Context manager to open a tracked file as a file object."""
+    """
+    Open file in the supplied path tracked in a repo (both DVC projects and
+    plain Git repos are supported). For Git repos, HEAD is used unless a rev
+    argument is supplied. The default remote is tried unless a remote argument
+    is supplied. It may only be used as a context manager:
+
+        with dvc.api.open(
+                'path/to/file',
+                repo='https://example.com/url/to/repo'
+                ) as fd:
+            # ... Handle file object fd
+    """
     args = (path,)
     kwargs = {
         "repo": repo,
@@ -63,7 +76,11 @@ def _open(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
 
 
 def read(path, repo=None, rev=None, remote=None, mode="r", encoding=None):
-    """Returns the contents of a tracked file."""
+    """
+    Returns the contents of a tracked file (by DVC or Git). For Git repos, HEAD
+    is used unless a rev argument is supplied. The default remote is tried
+    unless a remote argument is supplied.
+    """
     with open(
         path, repo=repo, rev=rev, remote=remote, mode=mode, encoding=encoding
     ) as fd:
@@ -81,8 +98,3 @@ def _make_repo(repo_url=None, rev=None):
             pass  # fallthrough to external_repo
     with external_repo(url=repo_url, rev=rev) as repo:
         yield repo
-
-
-def _require_dvc(repo):
-    if not isinstance(repo, Repo):
-        raise UrlNotDvcRepoError(repo.url)
diff --git a/dvc/config.py b/dvc/config.py
index 1d6118ec4..0907c66f3 100644
--- a/dvc/config.py
+++ b/dvc/config.py
@@ -172,9 +172,13 @@ SCHEMA = {
                     **REMOTE_COMMON,
                 },
                 "gdrive": {
+                    "gdrive_use_service_account": Bool,
                     "gdrive_client_id": str,
                     "gdrive_client_secret": str,
                     "gdrive_user_credentials_file": str,
+                    "gdrive_service_account_email": str,
+                    "gdrive_service_account_user_email": str,
+                    "gdrive_service_account_p12_file_path": str,
                     **REMOTE_COMMON,
                 },
                 "http": {**HTTP_COMMON, **REMOTE_COMMON},
@@ -201,9 +205,8 @@ class Config(dict):
         validate (bool): optional flag to tell dvc if it should validate the
             config or just load it as is. 'True' by default.
 
-
     Raises:
-        ConfigError: thrown when config has an invalid format.
+        ConfigError: thrown if config has an invalid format.
     """
 
     APPNAME = "dvc"
@@ -272,7 +275,7 @@ class Config(dict):
         """Loads config from all the config files.
 
         Raises:
-            dvc.config.ConfigError: thrown if config has invalid format.
+            ConfigError: thrown if config has an invalid format.
         """
         conf = {}
         for level in self.LEVELS:
@@ -332,7 +335,7 @@ class Config(dict):
     @contextmanager
     def edit(self, level="repo"):
         if level in {"repo", "local"} and self.dvc_dir is None:
-            raise ConfigError("Not inside a dvc repo")
+            raise ConfigError("Not inside a DVC repo")
 
         conf = self.load_one(level)
         yield conf
diff --git a/dvc/remote/gdrive.py b/dvc/remote/gdrive.py
index 80b4261bb..56499e853 100644
--- a/dvc/remote/gdrive.py
+++ b/dvc/remote/gdrive.py
@@ -76,7 +76,7 @@ class RemoteGDrive(RemoteBASE):
     DEFAULT_NO_TRAVERSE = False
     DEFAULT_VERIFY = True
 
-    GDRIVE_USER_CREDENTIALS_DATA = "GDRIVE_USER_CREDENTIALS_DATA"
+    GDRIVE_CREDENTIALS_DATA = "GDRIVE_CREDENTIALS_DATA"
     DEFAULT_USER_CREDENTIALS_FILE = "gdrive-user-credentials.json"
 
     def __init__(self, repo, config):
@@ -93,17 +93,22 @@ class RemoteGDrive(RemoteBASE):
             )
 
         self._bucket = self.path_info.bucket
+        self._use_service_account = config.get("gdrive_use_service_account")
+        self._service_account_email = config.get(
+            "gdrive_service_account_email"
+        )
+        self._service_account_user_email = config.get(
+            "gdrive_service_account_user_email"
+        )
+        self._service_account_p12_file_path = config.get(
+            "gdrive_service_account_p12_file_path"
+        )
         self._client_id = config.get("gdrive_client_id")
         self._client_secret = config.get("gdrive_client_secret")
-        if not self._client_id or not self._client_secret:
-            raise DvcException(
-                "Please specify Google Drive's client id and "
-                "secret in DVC config. Learn more at "
-                "{}.".format(format_link("https://man.dvc.org/remote/add"))
-            )
+        self._validate_config()
         self._gdrive_user_credentials_path = (
             tmp_fname(os.path.join(self.repo.tmp_dir, ""))
-            if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA)
+            if os.getenv(RemoteGDrive.GDRIVE_CREDENTIALS_DATA)
             else config.get(
                 "gdrive_user_credentials_file",
                 os.path.join(
@@ -114,6 +119,33 @@ class RemoteGDrive(RemoteBASE):
 
         self._list_params = None
 
+    def _validate_config(self):
+        # Validate Service Account configuration
+        if self._use_service_account and (
+            not self._service_account_email
+            or not self._service_account_p12_file_path
+        ):
+            raise DvcException(
+                "To use service account please specify {}, {} and "
+                "{} in DVC config. Learn more at "
+                "{}.".format(
+                    "gdrive_service_account_email",
+                    "gdrive_service_account_p12_file_path",
+                    "gdrive_service_account_user_email (optional)",
+                    format_link("https://man.dvc.org/remote/modify"),
+                )
+            )
+
+        # Validate OAuth 2.0 Client ID configuration
+        if not self._use_service_account and (
+            not self._client_id or not self._client_secret
+        ):
+            raise DvcException(
+                "Please specify Google Drive's client id and "
+                "secret in DVC config. Learn more at "
+                "{}.".format(format_link("https://man.dvc.org/remote/modify"))
+            )
+
     @wrap_prop(threading.RLock())
     @cached_property
     def drive(self):
@@ -121,23 +153,30 @@ class RemoteGDrive(RemoteBASE):
         from pydrive2.auth import GoogleAuth
         from pydrive2.drive import GoogleDrive
 
-        if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA):
+        if os.getenv(RemoteGDrive.GDRIVE_CREDENTIALS_DATA):
             with open(
                 self._gdrive_user_credentials_path, "w"
             ) as credentials_file:
                 credentials_file.write(
-                    os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA)
+                    os.getenv(RemoteGDrive.GDRIVE_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": "",
-        }
+        if self._use_service_account:
+            GoogleAuth.DEFAULT_SETTINGS["service_config"] = {
+                "client_service_email": self._service_account_email,
+                "client_user_email": self._service_account_user_email,
+                "client_pkcs12_file_path": self._service_account_p12_file_path,
+            }
+        else:
+            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[
@@ -153,7 +192,10 @@ class RemoteGDrive(RemoteBASE):
         gauth = GoogleAuth(settings_file="")
 
         try:
-            gauth.CommandLineAuth()
+            if self._use_service_account:
+                gauth.ServiceAuth()
+            else:
+                gauth.CommandLineAuth()
         except RefreshError as exc:
             raise GDriveAccessTokenRefreshError from exc
         except KeyError as exc:
@@ -164,7 +206,7 @@ class RemoteGDrive(RemoteBASE):
         except Exception as exc:
             raise DvcException("Google Drive authentication failed") from exc
         finally:
-            if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA):
+            if os.getenv(RemoteGDrive.GDRIVE_CREDENTIALS_DATA):
                 os.remove(self._gdrive_user_credentials_path)
 
         return GoogleDrive(gauth)
diff --git a/dvc/remote/local.py b/dvc/remote/local.py
index 24725c526..3e62e0628 100644
--- a/dvc/remote/local.py
+++ b/dvc/remote/local.py
@@ -92,6 +92,12 @@ class RemoteLOCAL(RemoteBASE):
 
         return not self.changed_cache(current_md5)
 
+    def _verify_link(self, path_info, link_type):
+        if link_type == "hardlink" and self.getsize(path_info) == 0:
+            return
+
+        super()._verify_link(path_info, link_type)
+
     def is_empty(self, path_info):
         path = path_info.fspath
 
diff --git a/dvc/scm/__init__.py b/dvc/scm/__init__.py
index 62f106314..15a75796f 100644
--- a/dvc/scm/__init__.py
+++ b/dvc/scm/__init__.py
@@ -4,8 +4,8 @@ from dvc.scm.base import Base
 from dvc.scm.git import Git
 
 
-# just a sugar to point that this is an actual implementation for a dvc
-# project under no SCM control
+# Syntactic sugar to signal that this is an actual implementation for a DVC
+# project under no SCM control.
 class NoSCM(Base):
     pass
 
diff --git a/dvc/version.py b/dvc/version.py
index 48b5ae25e..b9e405ff3 100644
--- a/dvc/version.py
+++ b/dvc/version.py
@@ -7,7 +7,7 @@ import os
 import subprocess
 
 
-_BASE_VERSION = "0.87.0"
+_BASE_VERSION = "0.88.0"
 
 
 def _generate_version(base_version):
diff --git a/scripts/ci/install_hadoop.sh b/scripts/ci/install_hadoop.sh
index eaf50e977..10a42eea5 100755
--- a/scripts/ci/install_hadoop.sh
+++ b/scripts/ci/install_hadoop.sh
@@ -11,7 +11,7 @@ sudo apt-get install openjdk-7-jdk
 java -version
 
 pushd /usr/local
-sudo wget https://s3-us-west-2.amazonaws.com/dvc-share/test/hadoop-2.6.5.tar.gz
+sudo wget https://s3-us-east-2.amazonaws.com/dvc-public/travis-test/hadoop-2.6.5.tar.gz
 sudo tar xzf hadoop-2.6.5.tar.gz
 sudo mkdir hadoop
 sudo mv hadoop-2.6.5/* hadoop/
diff --git a/scripts/completion/dvc.bash b/scripts/completion/dvc.bash
index 135f70661..61b1a93eb 100644
--- a/scripts/completion/dvc.bash
+++ b/scripts/completion/dvc.bash
@@ -24,26 +24,26 @@ _dvc_destroy='-f --force'
 _dvc_diff='-t --show-json --show-hash'
 _dvc_fetch='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -R --recursive'
 _dvc_fetch_COMPGEN=_dvc_compgen_DVCFiles
-_dvc_get_url=''
-_dvc_get='-o --out --rev --show-url'
 _dvc_gc='-a --all-branches --all-commits -T --all-tags -w --workspace -c --cloud -r --remote -f --force -p --projects -j --jobs'
-_dvc_import_url='-f --file'
+_dvc_get='-o --out --rev --show-url'
+_dvc_get_url=''
 _dvc_import='-o --out --rev'
+_dvc_import_url='-f --file'
 _dvc_init='--no-scm -f --force'
 _dvc_install=''
 _dvc_list='-R --recursive --outs-only --rev'
 _dvc_list_COMPGEN=_dvc_compgen_files
-_dvc_lock='$(compgen -f -X \!*?.dvc)'
-_dvc_metrics='add modify rmeove show'
+_dvc_lock=_dvc_compgen_DVCFiles
+_dvc_metrics='add diff modify remove show'
 _dvc_metrics_add='-t --type -x --xpath'
 _dvc_metrics_add_COMPGEN=_dvc_compgen_files
-_dvc_metrics_show='$(-t --type -x --xpath -a --all-branches -T --all-tags -R --recursive'
-_dvc_metrics_show_COMPGEN=_dvc_compgen_files
 _dvc_metrics_diff='--targets -t --type -x --xpath -R --show-json'
 _dvc_metrics_modify='-t --type -x --xpath'
 _dvc_metrics_modify_COMPGEN=_dvc_compgen_files
-_dvc_metrics_remove='$(compgen -f)'
-_dvc_move='$(compgen -f)'
+_dvc_metrics_remove=_dvc_compgen_files
+_dvc_metrics_show='-t --type -x --xpath -a --all-branches -T --all-tags -R --recursive'
+_dvc_metrics_show_COMPGEN=_dvc_compgen_files
+_dvc_move=_dvc_compgen_files
 _dvc_pipeline='list show'
 _dvc_pipeline_list=''
 _dvc_pipeline_show='-c --commands -o --outs --ascii --dot --tree -l --locked'
@@ -63,9 +63,10 @@ _dvc_remove_COMPGEN=_dvc_compgen_DVCFiles
 _dvc_repro='-f --force -s --single-item -c --cwd -m --metrics --dry -i --interactive -p --pipeline -P --all-pipelines --ignore-build-cache --no-commit -R --recursive --downstream'
 _dvc_repro_COMPGEN=_dvc_compgen_DVCFiles
 _dvc_root=''
-_dvc_run='--no-exec -f --file -c --cwd -d --deps -o --outs -O --outs-no-cache --outs-persist --outs-persist-no-cache -m --metrics -M --metrics-no-cache -y --yes --overwrite-dvcfile --ignore-build-cache --remove-outs --no-commit -w --wdir'
-_dvc_status='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -c --cloud'
+_dvc_run='--no-exec -f --file -d --deps -o --outs -O --outs-no-cache --outs-persist --outs-persist-no-cache -m --metrics -M --metrics-no-cache --overwrite-dvcfile --ignore-build-cache --no-commit -w --wdir'
 _dvc_run_COMPGEN=_dvc_compgen_DVCFiles
+_dvc_status='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -c --cloud'
+_dvc_status_COMPGEN=_dvc_compgen_DVCFiles
 _dvc_unlock_COMPGEN=_dvc_compgen_DVCFiles
 _dvc_unprotect_COMPGEN=_dvc_compgen_files
 _dvc_update='--rev'
diff --git a/setup.py b/setup.py
index 1f7f67e84..5930136a7 100644
--- a/setup.py
+++ b/setup.py
@@ -53,7 +53,7 @@ install_requires = [
     "nanotime>=0.5.2",
     "pyasn1>=0.4.1",
     "voluptuous>=0.11.7",
-    "jsonpath-ng>=1.4.3",
+    "jsonpath-ng>=1.5.1",
     "requests>=2.22.0",
     "grandalf==0.6",
     "distro>=1.3.0",
@@ -84,7 +84,7 @@ install_requires = [
 # Extra dependencies for remote integrations
 
 gs = ["google-cloud-storage==1.19.0"]
-gdrive = ["pydrive2>=1.4.5"]
+gdrive = ["pydrive2>=1.4.6"]
 s3 = ["boto3>=1.9.201"]
 azure = ["azure-storage-blob==2.1.0"]
 oss = ["oss2==2.6.1"]

add: empty files add broken when cache mode is hardlinks
## DVC Version


```
DVC version: 0.86.5+f67314.mod
Python version: 3.7.6
Platform: Darwin-18.2.0-x86_64-i386-64bit
Binary: False
Package: None
Cache: reflink - supported, hardlink - supported, symlink - supported
Filesystem type (cache directory): ('apfs', '/dev/disk1s1')
Filesystem type (workspace): ('apfs', '/dev/disk1s1')
```

## Reproduce

```
dvc config cache.type hardlink
touch foo
dvc add foo
```

outputs:

```
ERROR: no possible cache types left to try out.
```

diff --git a/tests/func/test_add.py b/tests/func/test_add.py
index 95407ee7d..9d966452d 100644
--- a/tests/func/test_add.py
+++ b/tests/func/test_add.py
@@ -7,7 +7,7 @@ import time
 
 import colorama
 import pytest
-from mock import patch
+from mock import patch, call
 
 import dvc as dvc_module
 from dvc.cache import Cache
@@ -662,3 +662,36 @@ def test_not_raises_on_re_add(tmp_dir, dvc):
 
     tmp_dir.gen({"file2": "file2 content", "file": "modified file"})
     dvc.add(["file2", "file"])
+
+
+@pytest.mark.parametrize("link", ["hardlink", "symlink", "copy"])
+def test_add_empty_files(tmp_dir, dvc, link):
+    file = "foo"
+    dvc.cache.local.cache_types = [link]
+    stages = tmp_dir.dvc_gen(file, "")
+
+    assert (tmp_dir / file).exists()
+    assert (tmp_dir / (file + Stage.STAGE_FILE_SUFFIX)).exists()
+    assert os.path.exists(stages[0].outs[0].cache_path)
+
+
+def test_add_optimization_for_hardlink_on_empty_files(tmp_dir, dvc, mocker):
+    dvc.cache.local.cache_types = ["hardlink"]
+    tmp_dir.gen({"foo": "", "bar": "", "lorem": "lorem", "ipsum": "ipsum"})
+    m = mocker.spy(RemoteLOCAL, "is_hardlink")
+    stages = dvc.add(["foo", "bar", "lorem", "ipsum"])
+
+    assert m.call_count == 1
+    assert m.call_args != call(tmp_dir / "foo")
+    assert m.call_args != call(tmp_dir / "bar")
+
+    for stage in stages[:2]:
+        # hardlinks are not created for empty files
+        assert not System.is_hardlink(stage.outs[0].path_info)
+
+    for stage in stages[2:]:
+        assert System.is_hardlink(stage.outs[0].path_info)
+
+    for stage in stages:
+        assert os.path.exists(stage.path)
+        assert os.path.exists(stage.outs[0].cache_path)
diff --git a/tests/func/test_data_cloud.py b/tests/func/test_data_cloud.py
index b28c2e438..aaf1a12c5 100644
--- a/tests/func/test_data_cloud.py
+++ b/tests/func/test_data_cloud.py
@@ -37,8 +37,6 @@ from tests.remotes import (
     OSS,
     TEST_CONFIG,
     TEST_GCP_CREDS_FILE,
-    TEST_GDRIVE_CLIENT_ID,
-    TEST_GDRIVE_CLIENT_SECRET,
     TEST_REMOTE,
 )
 
@@ -193,8 +191,9 @@ def setup_gdrive_cloud(remote_url, dvc):
     config = copy.deepcopy(TEST_CONFIG)
     config["remote"][TEST_REMOTE] = {
         "url": remote_url,
-        "gdrive_client_id": TEST_GDRIVE_CLIENT_ID,
-        "gdrive_client_secret": TEST_GDRIVE_CLIENT_SECRET,
+        "gdrive_service_account_email": "test",
+        "gdrive_service_account_p12_file_path": "test.p12",
+        "gdrive_use_service_account": True,
     }
 
     dvc.config = config
@@ -434,8 +433,8 @@ class TestRemoteGDriveCLI(GDrive, TestDataCloudCLIBase):
                 "remote",
                 "modify",
                 TEST_REMOTE,
-                "gdrive_client_id",
-                TEST_GDRIVE_CLIENT_ID,
+                "gdrive_service_account_email",
+                "modified",
             ]
         )
         self.main(
@@ -443,8 +442,17 @@ class TestRemoteGDriveCLI(GDrive, TestDataCloudCLIBase):
                 "remote",
                 "modify",
                 TEST_REMOTE,
-                "gdrive_client_secret",
-                TEST_GDRIVE_CLIENT_SECRET,
+                "gdrive_service_account_p12_file_path",
+                "modified.p12",
+            ]
+        )
+        self.main(
+            [
+                "remote",
+                "modify",
+                TEST_REMOTE,
+                "gdrive_use_service_account",
+                "True",
             ]
         )
 
diff --git a/tests/remotes.py b/tests/remotes.py
index 55029312d..a80619b17 100644
--- a/tests/remotes.py
+++ b/tests/remotes.py
@@ -35,11 +35,6 @@ TEST_GCP_CREDS_FILE = os.path.abspath(
 # Ensure that absolute path is used
 os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = TEST_GCP_CREDS_FILE
 
-TEST_GDRIVE_CLIENT_ID = (
-    "217948389181-rs7it4a635b3qrf8dnmklmoj2kimun9n.apps.googleusercontent.com"
-)
-TEST_GDRIVE_CLIENT_SECRET = "LNg9n_cK7bohI8gEHn4bUeMX"
-
 always_test = staticmethod(lambda: True)
 
 
@@ -146,7 +141,7 @@ class GCP:
 class GDrive:
     @staticmethod
     def should_test():
-        return os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA) is not None
+        return os.getenv(RemoteGDrive.GDRIVE_CREDENTIALS_DATA) is not None
 
     def get_url(self):
         if not getattr(self, "_remote_url", None):
diff --git a/tests/unit/remote/test_gdrive.py b/tests/unit/remote/test_gdrive.py
index 9d7c511e1..e165903cc 100644
--- a/tests/unit/remote/test_gdrive.py
+++ b/tests/unit/remote/test_gdrive.py
@@ -33,15 +33,15 @@ class TestRemoteGDrive(object):
     def test_drive(self):
         remote = RemoteGDrive(Repo(), self.CONFIG)
         os.environ[
-            RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA
+            RemoteGDrive.GDRIVE_CREDENTIALS_DATA
         ] = USER_CREDS_TOKEN_REFRESH_ERROR
         with pytest.raises(GDriveAccessTokenRefreshError):
             remote.drive
 
-        os.environ[RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA] = ""
+        os.environ[RemoteGDrive.GDRIVE_CREDENTIALS_DATA] = ""
         remote = RemoteGDrive(Repo(), self.CONFIG)
         os.environ[
-            RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA
+            RemoteGDrive.GDRIVE_CREDENTIALS_DATA
         ] = USER_CREDS_MISSED_KEY_ERROR
         with pytest.raises(GDriveMissedCredentialKeyError):
             remote.drive

pip install -e .[all]
parse_log_pytest
pytest
pytest pytest-timeout pytest-cov pytest-xdist pytest-mock flaky mock xmltodict awscli google-compute-engine Pygments collective.checkdocs flake8 psutil flake8-docstrings pydocstyle jaraco.windows mock-ssh-server moto rangehttpserver
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aliyun-python-sdk-core==2.16.0
aliyun-python-sdk-core-v3==2.13.33
aliyun-python-sdk-kms==2.16.5
appdirs==1.4.4
atpublic==3.1.2
attrs @ file:///croot/attrs_1668696182826/work
autocommand==2.2.2
awscli==1.31.13
azure-common==1.1.28
azure-storage-blob==2.1.0
azure-storage-common==2.1.0
bcrypt==4.2.1
boto==2.49.0
boto3==1.33.13
botocore==1.33.13
cachetools==4.2.4
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
collective.checkdocs==0.2
colorama==0.4.4
configobj==5.0.9
coverage==7.2.7
crcmod==1.7
cryptography==44.0.2
decorator==5.1.1
distro==1.9.0
docutils==0.16
-e git+https://github.com/iterative/dvc.git@ea981ae955c1212eaed52dc84f225731bc516785#egg=dvc
execnet==2.0.2
flake8==5.0.4
flake8-docstrings==1.7.0
flaky==3.8.1
flatten-json==0.1.14
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
flufl.lock==7.1.1
funcy==2.0
future==1.0.0
gitdb==4.0.12
GitPython==3.1.44
google-api-core==2.10.2
google-api-python-client==2.166.0
google-auth==1.35.0
google-auth-httplib2==0.2.0
google-cloud-core==1.7.3
google-cloud-storage==1.19.0
google-compute-engine==2.8.13
google-crc32c==1.5.0
google-resumable-media==2.7.2
googleapis-common-protos==1.69.2
grandalf==0.6
httplib2==0.22.0
humanize==4.6.0
idna==3.10
importlib-metadata==4.2.0
importlib-resources==5.12.0
inflect==3.0.2
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
jaraco.classes==3.2.3
jaraco.collections==4.2.0
jaraco.context==4.3.0
jaraco.functools==3.7.0
jaraco.structures==2.1.0
jaraco.text==3.11.1
jaraco.ui==2.3.0
jaraco.windows==5.7.0
Jinja2==3.1.6
jmespath==0.10.0
jsonpath-ng==1.7.0
MarkupSafe==2.1.5
mccabe==0.7.0
mock==5.2.0
mock-ssh-server==0.9.1
more-itertools==9.1.0
moto==4.2.14
nanotime==0.5.2
networkx==2.3
numpy==1.21.6
oauth2client==4.1.3
oss2==2.6.1
packaging @ file:///croot/packaging_1671697413597/work
paramiko==3.5.1
path==16.6.0
pathspec==0.11.2
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
ply==3.11
protobuf==4.24.4
psutil==7.0.0
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyarrow==0.15.1
pyasn1==0.5.1
pyasn1-modules==0.3.0
pycodestyle==2.9.1
pycparser==2.21
pycryptodome==3.22.0
pydantic==1.10.21
pydocstyle==6.3.0
pydot==2.0.0
PyDrive2==1.15.4
pyflakes==2.5.0
Pygments==2.17.2
pygtrie==2.3.2
PyNaCl==1.5.0
pyOpenSSL==25.0.0
pyparsing==3.1.4
pytest==7.1.2
pytest-cov==4.1.0
pytest-mock==3.11.1
pytest-timeout==2.3.1
pytest-xdist==3.5.0
python-dateutil==2.8.0
PyYAML==5.1.2
rangehttpserver==1.4.0
requests==2.31.0
responses==0.23.3
rsa==4.7.2
ruamel.yaml==0.18.10
ruamel.yaml.clib==0.2.8
s3transfer==0.8.2
shortuuid==1.0.13
six==1.17.0
smmap==5.0.2
snowballstemmer==2.2.0
texttable==1.7.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
tqdm==4.67.1
treelib==1.7.1
types-PyYAML==6.0.12.12
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
uritemplate==4.1.1
urllib3==1.26.20
voluptuous==0.14.1
Werkzeug==2.2.3
xmltodict==0.14.2
zc.lockfile==3.0.post1
zipp @ file:///croot/zipp_1672387121353/work

name: dvc
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aliyun-python-sdk-core==2.16.0
      - aliyun-python-sdk-core-v3==2.13.33
      - aliyun-python-sdk-kms==2.16.5
      - appdirs==1.4.4
      - atpublic==3.1.2
      - autocommand==2.2.2
      - awscli==1.31.13
      - azure-common==1.1.28
      - azure-storage-blob==2.1.0
      - azure-storage-common==2.1.0
      - bcrypt==4.2.1
      - boto==2.49.0
      - boto3==1.33.13
      - botocore==1.33.13
      - cachetools==4.2.4
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - collective-checkdocs==0.2
      - colorama==0.4.4
      - configobj==5.0.9
      - coverage==7.2.7
      - crcmod==1.7
      - cryptography==44.0.2
      - decorator==5.1.1
      - distro==1.9.0
      - docutils==0.16
      - dvc==0.87.0+ea981a
      - execnet==2.0.2
      - flake8==5.0.4
      - flake8-docstrings==1.7.0
      - flaky==3.8.1
      - flatten-json==0.1.14
      - flufl-lock==7.1.1
      - funcy==2.0
      - future==1.0.0
      - gitdb==4.0.12
      - gitpython==3.1.44
      - google-api-core==2.10.2
      - google-api-python-client==2.166.0
      - google-auth==1.35.0
      - google-auth-httplib2==0.2.0
      - google-cloud-core==1.7.3
      - google-cloud-storage==1.19.0
      - google-compute-engine==2.8.13
      - google-crc32c==1.5.0
      - google-resumable-media==2.7.2
      - googleapis-common-protos==1.69.2
      - grandalf==0.6
      - httplib2==0.22.0
      - humanize==4.6.0
      - idna==3.10
      - importlib-metadata==4.2.0
      - importlib-resources==5.12.0
      - inflect==3.0.2
      - jaraco-classes==3.2.3
      - jaraco-collections==4.2.0
      - jaraco-context==4.3.0
      - jaraco-functools==3.7.0
      - jaraco-structures==2.1.0
      - jaraco-text==3.11.1
      - jaraco-ui==2.3.0
      - jaraco-windows==5.7.0
      - jinja2==3.1.6
      - jmespath==0.10.0
      - jsonpath-ng==1.7.0
      - markupsafe==2.1.5
      - mccabe==0.7.0
      - mock==5.2.0
      - mock-ssh-server==0.9.1
      - more-itertools==9.1.0
      - moto==4.2.14
      - nanotime==0.5.2
      - networkx==2.3
      - numpy==1.21.6
      - oauth2client==4.1.3
      - oss2==2.6.1
      - paramiko==3.5.1
      - path==16.6.0
      - pathspec==0.11.2
      - ply==3.11
      - protobuf==4.24.4
      - psutil==7.0.0
      - pyarrow==0.15.1
      - pyasn1==0.5.1
      - pyasn1-modules==0.3.0
      - pycodestyle==2.9.1
      - pycparser==2.21
      - pycryptodome==3.22.0
      - pydantic==1.10.21
      - pydocstyle==6.3.0
      - pydot==2.0.0
      - pydrive2==1.15.4
      - pyflakes==2.5.0
      - pygments==2.17.2
      - pygtrie==2.3.2
      - pynacl==1.5.0
      - pyopenssl==25.0.0
      - pyparsing==3.1.4
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - pytest-timeout==2.3.1
      - pytest-xdist==3.5.0
      - python-dateutil==2.8.0
      - pyyaml==5.1.2
      - rangehttpserver==1.4.0
      - requests==2.31.0
      - responses==0.23.3
      - rsa==4.7.2
      - ruamel-yaml==0.18.10
      - ruamel-yaml-clib==0.2.8
      - s3transfer==0.8.2
      - shortuuid==1.0.13
      - six==1.17.0
      - smmap==5.0.2
      - snowballstemmer==2.2.0
      - texttable==1.7.0
      - tqdm==4.67.1
      - treelib==1.7.1
      - types-pyyaml==6.0.12.12
      - uritemplate==4.1.1
      - urllib3==1.26.20
      - voluptuous==0.14.1
      - werkzeug==2.2.3
      - xmltodict==0.14.2
      - zc-lockfile==3.0.post1
prefix: /opt/conda/envs/dvc

tests/func/test_add.py::test_add_empty_files[hardlink] tests/func/test_add.py::test_add_optimization_for_hardlink_on_empty_files tests/unit/remote/test_gdrive.py::TestRemoteGDrive::test_drive
tests/func/test_add.py::TestAddUnprotected::test tests/func/test_add.py::test_should_protect_on_repeated_add[hardlink] tests/func/test_add.py::test_should_protect_on_repeated_add[symlink] tests/func/test_add.py::test_should_protect_on_repeated_add[copy] tests/func/test_data_cloud.py::TestRemoteLOCALCLI::test
tests/func/test_add.py::test_add tests/func/test_add.py::test_add_unicode tests/func/test_add.py::test_add_unsupported_file tests/func/test_add.py::test_add_directory tests/func/test_add.py::TestAddDirectoryRecursive::test tests/func/test_add.py::TestAddCmdDirectoryRecursive::test tests/func/test_add.py::TestAddCmdDirectoryRecursive::test_warn_about_large_directories tests/func/test_add.py::TestAddDirectoryWithForwardSlash::test tests/func/test_add.py::test_add_tracked_file tests/func/test_add.py::TestAddDirWithExistingCache::test tests/func/test_add.py::TestAddModifiedDir::test tests/func/test_add.py::test_add_file_in_dir tests/func/test_add.py::TestAddExternalLocalFile::test tests/func/test_add.py::TestAddLocalRemoteFile::test tests/func/test_add.py::TestCmdAdd::test tests/func/test_add.py::TestDoubleAddUnchanged::test_dir tests/func/test_add.py::TestDoubleAddUnchanged::test_file tests/func/test_add.py::test_should_update_state_entry_for_file_after_add tests/func/test_add.py::test_should_update_state_entry_for_directory_after_add tests/func/test_add.py::TestAddCommit::test tests/func/test_add.py::test_should_collect_dir_cache_only_once tests/func/test_add.py::TestShouldAddDataFromExternalSymlink::test tests/func/test_add.py::TestShouldAddDataFromInternalSymlink::test tests/func/test_add.py::TestShouldPlaceStageInDataDirIfRepositoryBelowSymlink::test tests/func/test_add.py::TestShouldThrowProperExceptionOnCorruptedStageFile::test tests/func/test_add.py::TestAddFilename::test tests/func/test_add.py::test_failed_add_cleanup tests/func/test_add.py::test_should_not_track_git_internal_files tests/func/test_add.py::test_readding_dir_should_not_unprotect_all tests/func/test_add.py::test_should_not_checkout_when_adding_cached_copy tests/func/test_add.py::test_should_relink_on_repeated_add[hardlink-copy-<lambda>] tests/func/test_add.py::test_should_relink_on_repeated_add[symlink-copy-<lambda>] tests/func/test_add.py::test_should_relink_on_repeated_add[copy-hardlink-is_hardlink] tests/func/test_add.py::test_should_relink_on_repeated_add[copy-symlink-is_symlink] tests/func/test_add.py::test_escape_gitignore_entries tests/func/test_add.py::test_add_from_data_dir tests/func/test_add.py::test_not_raises_on_re_add tests/func/test_add.py::test_add_empty_files[symlink] tests/func/test_add.py::test_add_empty_files[copy] tests/func/test_data_cloud.py::TestDataCloud::test tests/func/test_data_cloud.py::TestRemoteLOCAL::test tests/func/test_data_cloud.py::TestRemoteSSHMocked::test tests/func/test_data_cloud.py::TestRemoteHTTP::test tests/func/test_data_cloud.py::TestDataCloudErrorCLI::test_error tests/func/test_data_cloud.py::TestWarnOnOutdatedStage::test tests/func/test_data_cloud.py::TestRecursiveSyncOperations::test tests/func/test_data_cloud.py::test_checksum_recalculation tests/func/test_data_cloud.py::TestShouldWarnOnNoChecksumInLocalAndRemoteCache::test tests/func/test_data_cloud.py::test_verify_checksums tests/unit/remote/test_gdrive.py::TestRemoteGDrive::test_init
dvc/config.py dvc/version.py dvc/remote/gdrive.py setup.py scripts/completion/dvc.bash dvc/api.py dvc/scm/__init__.py dvc/remote/local.py scripts/ci/install_hadoop.sh
dvc/config.py dvc/version.py dvc/remote/gdrive.py setup.py scripts/completion/dvc.bash dvc/api.py dvc/scm/__init__.py dvc/remote/local.py scripts/ci/install_hadoop.sh