Repository: iterative/dvc

Issue:
gdrive: failed to pull empty file
CC @imflash217  and @we-taper 

https://github.com/iterative/dvc/issues/1376#issuecomment-664004339

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/iterative/dvc/pull/4287?src=pr&el=h1) Report
> Merging [#4287](https://codecov.io/gh/iterative/dvc/pull/4287?src=pr&el=desc) into [master](https://codecov.io/gh/iterative/dvc/commit/e2b1fb1f64d30e593a45883ef7cfa92b74a26b88&el=desc) will **decrease** coverage by `0.48%`.
> The diff coverage is `96.42%`.

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

```diff
@@            Coverage Diff             @@
##           master    #4287      +/-   ##
==========================================
- Coverage   91.72%   91.23%   -0.49%     
==========================================
  Files         173      173              
  Lines       11745    11751       +6     
==========================================
- Hits        10773    10721      -52     
- Misses        972     1030      +58     
```


| [Impacted Files](https://codecov.io/gh/iterative/dvc/pull/4287?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [dvc/tree/gdrive.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL3RyZWUvZ2RyaXZlLnB5) | `81.75% <80.00%> (-0.10%)` | :arrow_down: |
| [dvc/ignore.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL2lnbm9yZS5weQ==) | `94.40% <100.00%> (-0.21%)` | :arrow_down: |
| [dvc/system.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL3N5c3RlbS5weQ==) | `62.39% <0.00%> (-26.50%)` | :arrow_down: |
| [dvc/utils/fs.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL3V0aWxzL2ZzLnB5) | `90.40% <0.00%> (-4.05%)` | :arrow_down: |
| [dvc/analytics.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL2FuYWx5dGljcy5weQ==) | `92.59% <0.00%> (-3.71%)` | :arrow_down: |
| [dvc/stage/run.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL3N0YWdlL3J1bi5weQ==) | `94.02% <0.00%> (-2.99%)` | :arrow_down: |
| [dvc/tree/ssh/connection.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL3RyZWUvc3NoL2Nvbm5lY3Rpb24ucHk=) | `80.19% <0.00%> (-2.02%)` | :arrow_down: |
| [dvc/daemon.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL2RhZW1vbi5weQ==) | `47.45% <0.00%> (-1.70%)` | :arrow_down: |
| [dvc/prompt.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL3Byb21wdC5weQ==) | `73.68% <0.00%> (-1.32%)` | :arrow_down: |
| [dvc/tree/local.py](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree#diff-ZHZjL3RyZWUvbG9jYWwucHk=) | `93.83% <0.00%> (-0.95%)` | :arrow_down: |
| ... and [14 more](https://codecov.io/gh/iterative/dvc/pull/4287/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/iterative/dvc/pull/4287?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/4287?src=pr&el=footer). Last update [fe9ae2c...edddcdb](https://codecov.io/gh/iterative/dvc/pull/4287?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/dvc/command/experiments.py b/dvc/command/experiments.py
index 3ec817bc7..ab1daa3c7 100644
--- a/dvc/command/experiments.py
+++ b/dvc/command/experiments.py
@@ -82,29 +82,25 @@ def _show_experiments(all_experiments, console, precision=None):
 
     metric_names, param_names = _collect_names(all_experiments)
 
-    table = Table(row_styles=["white", "bright_white"])
-    table.add_column("Experiment", header_style="black on grey93")
+    table = Table()
+    table.add_column("Experiment")
     for name in metric_names:
-        table.add_column(
-            name, justify="right", header_style="black on cornsilk1"
-        )
+        table.add_column(name, justify="right")
     for name in param_names:
-        table.add_column(
-            name, justify="left", header_style="black on light_cyan1"
-        )
+        table.add_column(name, justify="left")
 
     for base_rev, experiments in all_experiments.items():
         if Git.is_sha(base_rev):
             base_rev = base_rev[:7]
 
-        for row, style, in _collect_rows(
+        for row, _, in _collect_rows(
             base_rev,
             experiments,
             metric_names,
             param_names,
             precision=precision,
         ):
-            table.add_row(*row, style=style)
+            table.add_row(*row)
 
     console.print(table)
 
diff --git a/dvc/command/version.py b/dvc/command/version.py
index 589eb548d..79452c6d3 100644
--- a/dvc/command/version.py
+++ b/dvc/command/version.py
@@ -10,7 +10,7 @@ from dvc.command.base import CmdBaseNoRepo, append_doc_link
 from dvc.exceptions import DvcException, NotDvcRepoError
 from dvc.scm.base import SCMError
 from dvc.system import System
-from dvc.utils import is_binary, relpath
+from dvc.utils import relpath
 from dvc.utils.pkg import PKG
 from dvc.version import __version__
 
@@ -27,13 +27,18 @@ class CmdVersion(CmdBaseNoRepo):
     def run(self):
         from dvc.repo import Repo
 
+        package = PKG
+        if PKG is None:
+            package = ""
+        else:
+            package = f"({PKG})"
+
         info = [
-            f"DVC version: {__version__}",
-            f"Python version: {platform.python_version()}",
-            f"Platform: {platform.platform()}",
-            f"Binary: {is_binary()}",
-            f"Package: {PKG}",
-            f"Supported remotes: {self.get_supported_remotes()}",
+            f"DVC version: {__version__} {package}",
+            "---------------------------------",
+            f"Platform: Python {platform.python_version()} on "
+            f"{platform.platform()}",
+            f"Supports: {self.get_supported_remotes()}",
         ]
 
         try:
@@ -46,13 +51,13 @@ class CmdVersion(CmdBaseNoRepo):
             # `dvc config cache.shared group`.
             if os.path.exists(repo.cache.local.cache_dir):
                 info.append(
-                    "Cache: {}".format(self.get_linktype_support_info(repo))
+                    "Cache types: {}".format(
+                        self.get_linktype_support_info(repo)
+                    )
                 )
                 if psutil:
                     fs_type = self.get_fs_type(repo.cache.local.cache_dir)
-                    info.append(
-                        f"Filesystem type (cache directory): {fs_type}"
-                    )
+                    info.append(f"Cache directory: {fs_type}")
             else:
                 logger.warning(
                     "Unable to detect supported link types, as cache "
@@ -68,11 +73,11 @@ class CmdVersion(CmdBaseNoRepo):
             root_directory = os.getcwd()
             info.append("Repo: dvc, git (broken)")
         else:
-            info.append("Repo: {}".format(_get_dvc_repo_info(repo)))
+            if psutil:
+                fs_root = self.get_fs_type(os.path.abspath(root_directory))
+                info.append(f"Workspace directory: {fs_root}")
 
-        if psutil:
-            fs_root = self.get_fs_type(os.path.abspath(root_directory))
-            info.append(f"Filesystem type (workspace): {fs_root}")
+            info.append("Repo: {}".format(_get_dvc_repo_info(repo)))
 
         logger.info("\n".join(info))
         return 0
@@ -80,7 +85,7 @@ class CmdVersion(CmdBaseNoRepo):
     @staticmethod
     def get_fs_type(path):
         partition = {
-            pathlib.Path(part.mountpoint): (part.fstype, part.device)
+            pathlib.Path(part.mountpoint): (part.fstype + " on " + part.device)
             for part in psutil.disk_partitions(all=True)
         }
 
@@ -115,7 +120,9 @@ class CmdVersion(CmdBaseNoRepo):
                 os.unlink(dst)
             except DvcException:
                 status = "not supported"
-            cache.append(f"{name} - {status}")
+
+            if status == "supported":
+                cache.append(name)
         os.remove(src)
 
         return ", ".join(cache)
@@ -129,6 +136,12 @@ class CmdVersion(CmdBaseNoRepo):
             if not tree_cls.get_missing_deps():
                 supported_remotes.append(tree_cls.scheme)
 
+        if len(supported_remotes) == len(TREES):
+            return "All remotes"
+
+        if len(supported_remotes) == 1:
+            return supported_remotes
+
         return ", ".join(supported_remotes)
 
 
diff --git a/dvc/daemon.py b/dvc/daemon.py
index 666f0b413..0251fb1f2 100644
--- a/dvc/daemon.py
+++ b/dvc/daemon.py
@@ -3,6 +3,7 @@
 import inspect
 import logging
 import os
+import platform
 import sys
 from subprocess import Popen
 
@@ -15,25 +16,24 @@ CREATE_NEW_PROCESS_GROUP = 0x00000200
 DETACHED_PROCESS = 0x00000008
 
 
-def _spawn_windows(cmd, env):
-    from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
-
+def _popen(cmd, **kwargs):
     prefix = [sys.executable]
     if not is_binary():
         prefix += [sys.argv[0]]
 
+    return Popen(prefix + cmd, close_fds=True, shell=False, **kwargs)
+
+
+def _spawn_windows(cmd, env):
+    from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
+
     creationflags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
 
     startupinfo = STARTUPINFO()
     startupinfo.dwFlags |= STARTF_USESHOWWINDOW
 
-    Popen(
-        prefix + cmd,
-        env=env,
-        close_fds=True,
-        shell=False,
-        creationflags=creationflags,
-        startupinfo=startupinfo,
+    _popen(
+        cmd, env=env, creationflags=creationflags, startupinfo=startupinfo,
     ).communicate()
 
 
@@ -65,8 +65,13 @@ def _spawn_posix(cmd, env):
     sys.stdout.close()
     sys.stderr.close()
 
-    os.environ.update(env)
-    main(cmd)
+    if platform.system() == "Darwin":
+        # workaround for MacOS bug
+        # https://github.com/iterative/dvc/issues/4294
+        _popen(cmd, env=env).communicate()
+    else:
+        os.environ.update(env)
+        main(cmd)
 
     os._exit(0)  # pylint: disable=protected-access
 
diff --git a/dvc/tree/gdrive.py b/dvc/tree/gdrive.py
index 5e34fa455..abf48d17a 100644
--- a/dvc/tree/gdrive.py
+++ b/dvc/tree/gdrive.py
@@ -381,6 +381,9 @@ class GDriveTree(BaseTree):
         # it does not create a file on the remote
         gdrive_file = self._drive.CreateFile(param)
 
+        gdrive_file.FetchMetadata(fields="fileSize")
+        size = gdrive_file["fileSize"]
+
         with Tqdm(
             desc=progress_desc,
             disable=no_progress_bar,
@@ -388,7 +391,13 @@ class GDriveTree(BaseTree):
             # explicit `bar_format` as `total` will be set by `update_to`
             bar_format=Tqdm.BAR_FMT_DEFAULT,
         ) as pbar:
-            gdrive_file.GetContentFile(to_file, callback=pbar.update_to)
+            if size:
+                gdrive_file.GetContentFile(to_file, callback=pbar.update_to)
+            else:
+                # PyDrive doesn't like downloading empty files
+                # https://github.com/iterative/dvc/issues/4286
+                with open(to_file, "w"):
+                    pass
 
     @contextmanager
     @_gdrive_retry
diff --git a/dvc/version.py b/dvc/version.py
index 0f14fd2a9..d779b1977 100644
--- a/dvc/version.py
+++ b/dvc/version.py
@@ -6,7 +6,7 @@
 import os
 import subprocess
 
-_BASE_VERSION = "1.2.0"
+_BASE_VERSION = "1.2.2"
 
 
 def _generate_version(base_version):
diff --git a/setup.py b/setup.py
index 875ab04d1..064cb1261 100644
--- a/setup.py
+++ b/setup.py
@@ -75,7 +75,7 @@ install_requires = [
     "networkx>=2.1,<2.5",
     "pydot>=1.2.4",
     "speedcopy>=2.0.1; python_version < '3.8' and sys_platform == 'win32'",
-    "flatten_json>=0.1.6",
+    "flatten_json>=0.1.6,<0.1.8",
     "tabulate>=0.8.7",
     "pygtrie==2.3.2",
     "dpath>=2.0.1,<3",


Test patch:
diff --git a/tests/func/test_version.py b/tests/func/test_version.py
index 9c31b3f84..c55955db4 100644
--- a/tests/func/test_version.py
+++ b/tests/func/test_version.py
@@ -16,15 +16,10 @@ def test_info_in_repo(scm_init, tmp_dir, caplog):
 
     assert main(["version"]) == 0
 
-    assert re.search(r"DVC version: \d+\.\d+\.\d+", caplog.text)
-    assert re.search(r"Python version: \d\.\d\.\d", caplog.text)
-    assert re.search(r"Platform: .*", caplog.text)
-    assert re.search(r"Binary: (True|False)", caplog.text)
-    assert re.search(r"Package: .*", caplog.text)
-    assert re.search(r"Supported remotes: .*", caplog.text)
-    assert re.search(
-        r"(Cache: (.*link - (not )?supported(,\s)?){3})", caplog.text
-    )
+    assert re.search(r"DVC version: \d+\.\d+\.\d+\+.*", caplog.text)
+    assert re.search(r"Platform: Python \d\.\d\.\d on .*", caplog.text)
+    assert re.search(r"Supports: .*", caplog.text)
+    assert re.search(r"Cache types: .*", caplog.text)
 
     if scm_init:
         assert "Repo: dvc, git" in caplog.text
@@ -58,26 +53,20 @@ def test_fs_info_in_repo(tmp_dir, dvc, caplog):
     os.mkdir(dvc.cache.local.cache_dir)
     assert main(["version"]) == 0
 
-    assert "Filesystem type (cache directory): " in caplog.text
-    assert "Filesystem type (workspace): " in caplog.text
+    assert re.search(r"Cache directory: .* on .*", caplog.text)
+    assert re.search(r"Workspace directory: .* on .*", caplog.text)
 
 
 def test_info_outside_of_repo(tmp_dir, caplog):
     assert main(["version"]) == 0
 
-    assert re.search(r"DVC version: \d+\.\d+\.\d+", caplog.text)
-    assert re.search(r"Python version: \d\.\d\.\d", caplog.text)
-    assert re.search(r"Platform: .*", caplog.text)
-    assert re.search(r"Binary: (True|False)", caplog.text)
-    assert re.search(r"Package: .*", caplog.text)
-    assert re.search(r"Supported remotes: .*", caplog.text)
-    assert not re.search(r"(Cache: (.*link - (not )?(,\s)?){3})", caplog.text)
+    assert re.search(r"DVC version: \d+\.\d+\.\d+\+.*", caplog.text)
+    assert re.search(r"Platform: Python \d\.\d\.\d on .*", caplog.text)
+    assert re.search(r"Supports: .*", caplog.text)
+    assert not re.search(r"Cache types: .*", caplog.text)
     assert "Repo:" not in caplog.text
 
 
 @pytest.mark.skipif(psutil is None, reason="No psutil.")
 def test_fs_info_outside_of_repo(tmp_dir, caplog):
     assert main(["version"]) == 0
-
-    assert "Filesystem type (cache directory): " not in caplog.text
-    assert "Filesystem type (workspace): " in caplog.text