Repository: iterative/dvc

Issue:
Reconsider gc implementation
As pointed out in discussion in #1691, we should reconsider `gc` implementation.
Currently, if called without any options, dvc will collect current branch dependencies and outputs checksums, and remove everything besides it. We can easily clear history of changes with this command. `gc` should be safer with default options. Straightforward implementation could get all outputs for all revisions in git repo and remove everything that is not on list.

As pointed out by @Suor, this approach might be slow for repository with long history. 

Discussion:

Patch:
diff --git a/.zenodo.json b/.zenodo.json
new file mode 100644
index 000000000..4e110ab04
--- /dev/null
+++ b/.zenodo.json
@@ -0,0 +1,8 @@
+{
+  "title": "DVC: Data Version Control - Git for Data & Models",
+  "keywords": [
+    "data-science", "data-version-control", "machine-learning", "git",
+    "developer-tools", "reproducibility", "collaboration", "ai", "python"],
+  "contributors": [
+    {"name": "DVC team", "type": "Other", "affiliation": "Iterative"}]
+}
diff --git a/README.rst b/README.rst
index 12f974cd9..9c24974bd 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,4 @@
-.. image:: https://dvc.org/static/img/logo-github-readme.png
-  :target: https://dvc.org
-  :alt: DVC logo
+|Banner|
 
 `Website <https://dvc.org>`_
 • `Docs <https://dvc.org/doc>`_
@@ -10,33 +8,7 @@
 • `Tutorial <https://dvc.org/doc/get-started>`_
 • `Mailing List <https://sweedom.us10.list-manage.com/subscribe/post?u=a08bf93caae4063c4e6a351f6&id=24c0ecc49a>`_
 
-.. image:: https://img.shields.io/badge/release-ok-brightgreen
-  :target: https://travis-ci.com/iterative/dvc
-  :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
-  :alt: Code Climate
-
-.. image:: https://codecov.io/gh/iterative/dvc/branch/master/graph/badge.svg
-  :target: https://codecov.io/gh/iterative/dvc
-  :alt: Codecov
-
-.. image:: https://img.shields.io/badge/patreon-donate-green.svg
-  :target: https://www.patreon.com/DVCorg/overview
-  :alt: Donate
-
-.. image:: https://anaconda.org/conda-forge/dvc/badges/version.svg
-  :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
+|Release| |CI| |Maintainability| |Coverage| |Donate| |Conda| |Snap| |DOI|
 
 |
 
@@ -79,9 +51,7 @@ to store data and model files seamlessly out of Git, while preserving almost the
 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
+|Flowchart|
 
 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,
@@ -148,6 +118,8 @@ Homebrew
 Conda (Anaconda)
 ----------------
 
+|Conda|
+
 .. code-block:: bash
 
    conda install -c conda-forge dvc
@@ -157,6 +129,8 @@ Currently, this includes support for Python versions 2.7, 3.6 and 3.7.
 Snap (Snapcraft)
 ----------------
 
+|Snap|
+
 .. code-block:: bash
 
    snap install dvc --classic
@@ -206,40 +180,43 @@ Comparison to related technologies
 
 Contributing
 ============
+
+|Maintainability| |Donate|
+
 Contributions are welcome! Please see our `Contributing Guide <https://dvc.org/doc/user-guide/contributing/core>`_ for more
 details.
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/0
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/0
-  :alt: 0
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/0
+   :alt: 0
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/1
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/1
-  :alt: 1
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/1
+   :alt: 1
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/2
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/2
-  :alt: 2
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/2
+   :alt: 2
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/3
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/3
-  :alt: 3
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/3
+   :alt: 3
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/4
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/4
-  :alt: 4
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/4
+   :alt: 4
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/5
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/5
-  :alt: 5
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/5
+   :alt: 5
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/6
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/6
-  :alt: 6
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/6
+   :alt: 6
 
 .. image:: https://sourcerer.io/fame/efiop/iterative/dvc/images/7
-  :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/7
-  :alt: 7
+   :target: https://sourcerer.io/fame/efiop/iterative/dvc/links/7
+   :alt: 7
 
 Mailing List
 ============
@@ -253,3 +230,51 @@ This project is distributed under the Apache license version 2.0 (see the LICENS
 
 By submitting a pull request to this project, you agree to license your contribution under the Apache license version
 2.0 to this project.
+
+Citation
+========
+
+|DOI|
+
+Iterative, *DVC: Data Version Control - Git for Data & Models* (2020)
+`DOI:10.5281/zenodo.012345 <https://doi.org/10.5281/zenodo.3677553>`_.
+
+.. |Banner| image:: https://dvc.org/static/img/logo-github-readme.png
+   :target: https://dvc.org
+   :alt: DVC logo
+
+.. |Release| image:: https://img.shields.io/badge/release-ok-brightgreen
+   :target: https://travis-ci.com/iterative/dvc
+   :alt: Release
+
+.. |CI| image:: https://img.shields.io/travis/com/iterative/dvc/master?label=dev
+   :target: https://travis-ci.com/iterative/dvc
+   :alt: Travis dev branch
+
+.. |Maintainability| image:: https://codeclimate.com/github/iterative/dvc/badges/gpa.svg
+   :target: https://codeclimate.com/github/iterative/dvc
+   :alt: Code Climate
+
+.. |Coverage| image:: https://codecov.io/gh/iterative/dvc/branch/master/graph/badge.svg
+   :target: https://codecov.io/gh/iterative/dvc
+   :alt: Codecov
+
+.. |Donate| image:: https://img.shields.io/badge/patreon-donate-green.svg
+   :target: https://www.patreon.com/DVCorg/overview
+   :alt: Donate
+
+.. |Conda| image:: https://anaconda.org/conda-forge/dvc/badges/version.svg
+   :target: https://anaconda.org/conda-forge/dvc
+   :alt: Conda-forge
+
+.. |Snap| image:: https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft
+   :target: https://snapcraft.io/dvc
+   :alt: Snapcraft
+
+.. |DOI| image:: https://img.shields.io/badge/DOI-10.5281/zenodo.3677553-blue.svg
+   :target: https://doi.org/10.5281/zenodo.3677553
+   :alt: DOI
+
+.. |Flowchart| image:: https://dvc.org/static/img/flow.gif
+   :target: https://dvc.org/static/img/flow.gif
+   :alt: how_dvc_works
diff --git a/dvc/command/gc.py b/dvc/command/gc.py
index 2ee4979d5..67c0927d2 100644
--- a/dvc/command/gc.py
+++ b/dvc/command/gc.py
@@ -12,6 +12,16 @@ logger = logging.getLogger(__name__)
 
 class CmdGC(CmdBase):
     def run(self):
+        from dvc.repo.gc import _raise_error_if_all_disabled
+
+        _raise_error_if_all_disabled(
+            all_branches=self.args.all_branches,
+            all_tags=self.args.all_tags,
+            all_commits=self.args.all_commits,
+            workspace=self.args.workspace,
+            cloud=self.args.cloud,
+        )
+
         msg = "This will remove all cache except items used in "
 
         msg += "the working tree"
@@ -47,6 +57,7 @@ class CmdGC(CmdBase):
             force=self.args.force,
             jobs=self.args.jobs,
             repos=self.args.repos,
+            workspace=self.args.workspace,
         )
         return 0
 
@@ -64,6 +75,13 @@ def add_parser(subparsers, parent_parser):
         help=GC_HELP,
         formatter_class=argparse.RawDescriptionHelpFormatter,
     )
+    gc_parser.add_argument(
+        "-w",
+        "--workspace",
+        action="store_true",
+        default=False,
+        help="Keep data files used in the current workspace.",
+    )
     gc_parser.add_argument(
         "-a",
         "--all-branches",
diff --git a/dvc/config.py b/dvc/config.py
index d893855f7..6a59fff1c 100644
--- a/dvc/config.py
+++ b/dvc/config.py
@@ -101,6 +101,13 @@ LOCAL_COMMON = {
     "shared": All(Lower, Choices("group")),
     Optional("slow_link_warning", default=True): Bool,
 }
+HTTP_COMMON = {
+    "auth": All(Lower, Choices("basic", "digest", "custom")),
+    "custom_auth_header": str,
+    "user": str,
+    "password": str,
+    "ask_password": Bool,
+}
 SCHEMA = {
     "core": {
         "remote": Lower,
@@ -169,8 +176,8 @@ SCHEMA = {
                     "gdrive_user_credentials_file": str,
                     **REMOTE_COMMON,
                 },
-                "http": REMOTE_COMMON,
-                "https": REMOTE_COMMON,
+                "http": {**HTTP_COMMON, **REMOTE_COMMON},
+                "https": {**HTTP_COMMON, **REMOTE_COMMON},
                 "remote": {str: object},  # Any of the above options are valid
             }
         )
diff --git a/dvc/exceptions.py b/dvc/exceptions.py
index b91fd1424..2f6c25a47 100644
--- a/dvc/exceptions.py
+++ b/dvc/exceptions.py
@@ -7,6 +7,10 @@ class DvcException(Exception):
     """Base class for all dvc exceptions."""
 
 
+class InvalidArgumentError(ValueError, DvcException):
+    """Thrown if arguments are invalid."""
+
+
 class OutputDuplicationError(DvcException):
     """Thrown if a file/directory is specified as an output in more than one
     stage.
diff --git a/dvc/output/base.py b/dvc/output/base.py
index d59276da7..d07010ad6 100644
--- a/dvc/output/base.py
+++ b/dvc/output/base.py
@@ -409,12 +409,23 @@ class OutputBase(object):
             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 "
-                "not be collected. Use `dvc repro` to get your pipeline up to "
-                "date.".format(self, self.stage)
+        if not self.checksum:
+            msg = (
+                "Output '{}'({}) is missing version info. "
+                "Cache for it will not be collected. "
+                "Use `dvc repro` to get your pipeline up to date.".format(
+                    self, self.stage
+                )
             )
+            if self.exists:
+                msg += (
+                    "\n"
+                    "You can also use `dvc commit {stage}` to associate "
+                    "existing '{out}' with '{stage}'.".format(
+                        out=self, stage=self.stage.relpath
+                    )
+                )
+            logger.warning(msg)
             return NamedCache()
 
         ret = NamedCache.make(self.scheme, self.checksum, str(self))
diff --git a/dvc/remote/http.py b/dvc/remote/http.py
index f3b3fb5a5..d0f35029b 100644
--- a/dvc/remote/http.py
+++ b/dvc/remote/http.py
@@ -1,8 +1,10 @@
 import logging
+import os.path
 import threading
 
-from funcy import cached_property, wrap_prop
+from funcy import cached_property, memoize, wrap_prop, wrap_with
 
+import dvc.prompt as prompt
 from dvc.config import ConfigError
 from dvc.exceptions import DvcException, HTTPError
 from dvc.progress import Tqdm
@@ -12,6 +14,15 @@ from dvc.scheme import Schemes
 logger = logging.getLogger(__name__)
 
 
+@wrap_with(threading.Lock())
+@memoize
+def ask_password(host, user):
+    return prompt.password(
+        "Enter a password for "
+        "host '{host}' user '{user}'".format(host=host, user=user)
+    )
+
+
 class RemoteHTTP(RemoteBASE):
     scheme = Schemes.HTTP
     SESSION_RETRIES = 5
@@ -24,7 +35,13 @@ class RemoteHTTP(RemoteBASE):
         super().__init__(repo, config)
 
         url = config.get("url")
-        self.path_info = self.path_cls(url) if url else None
+        if url:
+            self.path_info = self.path_cls(url)
+            user = config.get("user", None)
+            if user:
+                self.path_info.user = user
+        else:
+            self.path_info = None
 
         if not self.no_traverse:
             raise ConfigError(
@@ -32,6 +49,12 @@ class RemoteHTTP(RemoteBASE):
                 "files. Use: `dvc remote modify <name> no_traverse true`"
             )
 
+        self.auth = config.get("auth", None)
+        self.custom_auth_header = config.get("custom_auth_header", None)
+        self.password = config.get("password", None)
+        self.ask_password = config.get("ask_password", False)
+        self.headers = {}
+
     def _download(self, from_info, to_file, name=None, no_progress_bar=False):
         response = self._request("GET", from_info.url, stream=True)
         if response.status_code != 200:
@@ -48,6 +71,28 @@ class RemoteHTTP(RemoteBASE):
                     fd.write(chunk)
                     pbar.update(len(chunk))
 
+    def _upload(self, from_file, to_info, name=None, no_progress_bar=False):
+        with Tqdm(
+            total=None if no_progress_bar else os.path.getsize(from_file),
+            leave=False,
+            bytes=True,
+            desc=to_info.url if name is None else name,
+            disable=no_progress_bar,
+        ) as pbar:
+
+            def chunks():
+                with open(from_file, "rb") as fd:
+                    while True:
+                        chunk = fd.read(self.CHUNK_SIZE)
+                        if not chunk:
+                            break
+                        pbar.update(len(chunk))
+                        yield chunk
+
+            response = self._request("POST", to_info.url, data=chunks())
+            if response.status_code not in (200, 201):
+                raise HTTPError(response.status_code, response.reason)
+
     def exists(self, path_info):
         return bool(self._request("HEAD", path_info.url))
 
@@ -74,6 +119,24 @@ class RemoteHTTP(RemoteBASE):
 
         return etag
 
+    def auth_method(self, path_info=None):
+        from requests.auth import HTTPBasicAuth, HTTPDigestAuth
+
+        if path_info is None:
+            path_info = self.path_info
+
+        if self.auth:
+            if self.ask_password and self.password is None:
+                host, user = path_info.host, path_info.user
+                self.password = ask_password(host, user)
+            if self.auth == "basic":
+                return HTTPBasicAuth(path_info.user, self.password)
+            if self.auth == "digest":
+                return HTTPDigestAuth(path_info.user, self.password)
+            if self.auth == "custom" and self.custom_auth_header:
+                self.headers.update({self.custom_auth_header: self.password})
+        return None
+
     @wrap_prop(threading.Lock())
     @cached_property
     def _session(self):
@@ -100,7 +163,13 @@ class RemoteHTTP(RemoteBASE):
         kwargs.setdefault("timeout", self.REQUEST_TIMEOUT)
 
         try:
-            res = self._session.request(method, url, **kwargs)
+            res = self._session.request(
+                method,
+                url,
+                auth=self.auth_method(),
+                headers=self.headers,
+                **kwargs,
+            )
 
             redirect_no_location = (
                 kwargs["allow_redirects"]
diff --git a/dvc/repo/gc.py b/dvc/repo/gc.py
index e55a34dc3..a19c3932b 100644
--- a/dvc/repo/gc.py
+++ b/dvc/repo/gc.py
@@ -2,6 +2,7 @@ import logging
 
 from . import locked
 from dvc.cache import NamedCache
+from dvc.exceptions import InvalidArgumentError
 
 
 logger = logging.getLogger(__name__)
@@ -13,6 +14,15 @@ def _do_gc(typ, func, clist):
         logger.info("No unused '{}' cache to remove.".format(typ))
 
 
+def _raise_error_if_all_disabled(**kwargs):
+    if not any(kwargs.values()):
+        raise InvalidArgumentError(
+            "Invalid Arguments. Either of ({}) needs to be enabled.".format(
+                ", ".join(kwargs.keys())
+            )
+        )
+
+
 @locked
 def gc(
     self,
@@ -25,7 +35,20 @@ def gc(
     force=False,
     jobs=None,
     repos=None,
+    workspace=False,
 ):
+
+    # require `workspace` to be true to come into effect.
+    # assume `workspace` to be enabled if any of `all_tags`, `all_commits`,
+    # or `all_branches` are enabled.
+    _raise_error_if_all_disabled(
+        workspace=workspace,
+        all_tags=all_tags,
+        all_commits=all_commits,
+        all_branches=all_branches,
+        cloud=cloud,
+    )
+
     from contextlib import ExitStack
     from dvc.repo import Repo
 
diff --git a/dvc/version.py b/dvc/version.py
index cd51300a0..9cd7ed8c1 100644
--- a/dvc/version.py
+++ b/dvc/version.py
@@ -7,7 +7,7 @@ import os
 import subprocess
 
 
-_BASE_VERSION = "0.86.4"
+_BASE_VERSION = "0.86.5"
 
 
 def _generate_version(base_version):
diff --git a/scripts/completion/dvc.bash b/scripts/completion/dvc.bash
index 72737b216..1fab7e1bb 100644
--- a/scripts/completion/dvc.bash
+++ b/scripts/completion/dvc.bash
@@ -2,6 +2,7 @@
 # References:
 #   - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
 #   - https://opensource.com/article/18/3/creating-bash-completion-script
+#   - https://stackoverflow.com/questions/12933362
 
 _dvc_commands='add cache checkout commit config destroy diff fetch get-url get gc \
   import-url import init install lock list metrics move pipeline pull push \
@@ -10,15 +11,19 @@ _dvc_commands='add cache checkout commit config destroy diff fetch get-url get g
 _dvc_options='-h --help -V --version'
 _dvc_global_options='-h --help -q --quiet -v --verbose'
 
-_dvc_add='-R --recursive -f --file --no-commit $(compgen -f)'
+_dvc_add='-R --recursive -f --file --no-commit'
+_dvc_add_COMPGEN=_dvc_compgen_files
 _dvc_cache='dir'
 _dvc_cache_dir=' --global --system --local -u --unset'
-_dvc_checkout='-d --with-deps -R --recursive -f --force --relink $(compgen -f -X \!*?.dvc)'
-_dvc_commit='-f --force -d --with-deps -R --recursive $(compgen -f -X \!*?.dvc)'
+_dvc_checkout='-d --with-deps -R --recursive -f --force --relink'
+_dvc_checkout_COMPGEN=_dvc_compgen_DVCFiles
+_dvc_commit='-f --force -d --with-deps -R --recursive'
+_dvc_commit_COMPGEN=_dvc_compgen_DVCFiles
 _dvc_config='-u --unset --local --system --global'
 _dvc_destroy='-f --force'
 _dvc_diff='-t --show-json --checksums'
-_dvc_fetch='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -R --recursive $(compgen -f -X \!*?.dvc)'
+_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 -T --all-tags -c --cloud -r --remote -f --force -p --projects -j --jobs'
@@ -26,60 +31,85 @@ _dvc_import_url='-f --file'
 _dvc_import='-o --out --rev'
 _dvc_init='--no-scm -f --force'
 _dvc_install=''
-_dvc_list='-R --recursive --outs-only --rev $(compgen -f)'
+_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_metrics_add='-t --type -x --xpath $(compgen -f)'
-_dvc_metrics_show='$(-t --type -x --xpath -a --all-branches -T --all-tags -R --recursive $(compgen -f)'
+_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 $(compgen -f)'
+_dvc_metrics_modify='-t --type -x --xpath'
+_dvc_metrics_modify_COMPGEN=_dvc_compgen_files
 _dvc_metrics_remove='$(compgen -f)'
 _dvc_move='$(compgen -f)'
 _dvc_pipeline='list show'
 _dvc_pipeline_list=''
-_dvc_pipeline_show='-c --commands -o --outs --ascii --dot --tree -l --locked $(compgen -f -X \!*?.dvc)'
-_dvc_pull='-j --jobs -r --remote -a --all-branches -T --all-tags -f --force -d --with-deps -R --recursive $(compgen -f -X \!*?.dvc)'
-_dvc_push='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -R --recursive $(compgen -f -X \!*?.dvc)'
+_dvc_pipeline_show='-c --commands -o --outs --ascii --dot --tree -l --locked'
+_dvc_pipeline_show_COMPGEN=_dvc_compgen_DVCFiles
+_dvc_pull='-j --jobs -r --remote -a --all-branches -T --all-tags -f --force -d --with-deps -R --recursive'
+_dvc_pull_COMPGEN=_dvc_compgen_DVCFiles
+_dvc_push='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -R --recursive'
+_dvc_push_COMPGEN=_dvc_compgen_DVCFiles
 _dvc_remote='add default list modify remove'
 _dvc_remote_add='--global --system --local -d --default -f --force'
 _dvc_remote_default='--global --system --local -u --unset'
 _dvc_remote_list='--global --system --local'
 _dvc_remote_modify='--global --system --local -u --unset'
 _dvc_remote_remove='--global --system --local'
-_dvc_remove='-o --outs -p --purge -f --force $(compgen -f -X \!*?.dvc)'
-_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 $(compgen -f -X \!*?.dvc)'
+_dvc_remove='-o --outs -p --purge -f --force'
+_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 $(compgen -f -X \!*?.dvc)'
-_dvc_unlock='$(compgen -f -X \!*?.dvc)'
-_dvc_unprotect='$(compgen -f)'
-_dvc_update='--rev $(compgen -f -X \!*?.dvc)'
+_dvc_status='-j --jobs -r --remote -a --all-branches -T --all-tags -d --with-deps -c --cloud'
+_dvc_run_COMPGEN=_dvc_compgen_DVCFiles
+_dvc_unlock_COMPGEN=_dvc_compgen_DVCFiles
+_dvc_unprotect_COMPGEN=_dvc_compgen_files
+_dvc_update='--rev'
+_dvc_update_COMPGEN=_dvc_compgen_DVCFiles
 _dvc_version=''
 
-# Params
-# $1 - COMP_WORDS[1]
+# $1=COMP_WORDS[1]
+_dvc_compgen_DVCFiles() {
+  compgen -f -X '!*?.dvc' -- $1
+  compgen -d -S '/' -- $1  # recurse into subdirs
+  # Note that the recurse into dirs is only for looking for DVC-files.
+  # Since dirs themselves are not required, we need `-o nospace` at the bottom
+  # unfortunately :(
+}
+
+# $1=COMP_WORDS[1]
+_dvc_compgen_files() {
+  compgen -f -- $1
+  compgen -d -S '/' -- $1  # recurse into subdirs
+}
+
+# $1=COMP_WORDS[1]
 _dvc_replace_hyphen() {
   echo $1 | sed 's/-/_/g'
 }
 
-# Params
-# $1 - COMP_WORDS[1]
-_dvc_comp_command() {
-  local options_list="_dvc_$(_dvc_replace_hyphen $1)"
-
-  COMPREPLY=( $(compgen -W "$_dvc_global_options ${!options_list}" -- "$word") )
+# $1=COMP_WORDS[1]
+_dvc_compgen_command() {
+  local flags_list="_dvc_$(_dvc_replace_hyphen $1)"
+  local args_gen="${flags_list}_COMPGEN"
+  COMPREPLY=( $(compgen -W "$_dvc_global_options ${!flags_list}" -- "$word"; [ -n "${!args_gen}" ] && ${!args_gen} "$word") )
 }
 
-# Params
-# $1 - COMP_WORDS[1]
-# $1 - COMP_WORDS[2]
-_dvc_comp_subcommand() {
-  local options_list="_dvc_$(_dvc_replace_hyphen $1)_$(_dvc_replace_hyphen $2)"
-  local _dvc_opts="${!options_list}"
-  if [ -z "$_dvc_opts" ]; then
-    _dvc_comp_command $1
+# $1=COMP_WORDS[1]
+# $2=COMP_WORDS[2]
+_dvc_compgen_subcommand() {
+  local flags_list="_dvc_$(_dvc_replace_hyphen $1)_$(_dvc_replace_hyphen $2)"
+  local args_gen="${flags_list}_COMPGEN"
+  [ -n "${!args_gen}" ] && local opts_more="$(${!args_gen} "$word")"
+  local opts="${!flags_list}"
+  if [ -z "$opts$opts_more" ]; then
+    _dvc_compgen_command $1
   else
-    COMPREPLY=( $(compgen -W "$_dvc_global_options $_dvc_opts" -- "$word") )
+    COMPREPLY=( $(compgen -W "$_dvc_global_options $opts" -- "$word"; [ -n "$opts_more" ] && echo "$opts_more") )
   fi
 }
 
@@ -101,12 +131,12 @@ _dvc() {
       *) COMPREPLY=($(compgen -W "$_dvc_commands" -- "$word")) ;;
     esac
   elif [ "${COMP_CWORD}" -eq 2 ]; then
-    _dvc_comp_command ${COMP_WORDS[1]}
+    _dvc_compgen_command ${COMP_WORDS[1]}
   elif [ "${COMP_CWORD}" -ge 3 ]; then
-    _dvc_comp_subcommand ${COMP_WORDS[1]} ${COMP_WORDS[2]}
+    _dvc_compgen_subcommand ${COMP_WORDS[1]} ${COMP_WORDS[2]}
   fi
 
   return 0
 }
 
-complete -F _dvc dvc
+complete -o nospace -F _dvc dvc
diff --git a/setup.py b/setup.py
index b39a1f6e9..88b111960 100644
--- a/setup.py
+++ b/setup.py
@@ -148,7 +148,8 @@ setup(
         "hdfs": hdfs,
         "tests": tests_requirements,
     },
-    keywords="data science, data version control, machine learning",
+    keywords="data-science data-version-control machine-learning git"
+    " developer-tools reproducibility collaboration ai",
     python_requires=">=3.5",
     classifiers=[
         "Development Status :: 4 - Beta",


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6e01739aa..a6a4c9ba8 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,6 +4,7 @@ import mockssh
 import pytest
 
 from dvc.remote.ssh.connection import SSHConnection
+from tests.utils.httpd import PushRequestHandler, StaticFileServer
 from .dir_helpers import *  # noqa
 
 
@@ -57,3 +58,9 @@ def _close_pools():
 
     yield
     close_pools()
+
+
+@pytest.fixture
+def http_server(tmp_dir):
+    with StaticFileServer(handler_class=PushRequestHandler) as httpd:
+        yield httpd
diff --git a/tests/func/test_data_cloud.py b/tests/func/test_data_cloud.py
index 39df54bbb..b28c2e438 100644
--- a/tests/func/test_data_cloud.py
+++ b/tests/func/test_data_cloud.py
@@ -30,6 +30,7 @@ from tests.remotes import (
     GCP,
     GDrive,
     HDFS,
+    HTTP,
     Local,
     S3,
     SSHMocked,
@@ -290,6 +291,20 @@ class TestRemoteHDFS(HDFS, TestDataCloudBase):
         return RemoteHDFS
 
 
+@pytest.mark.usefixtures("http_server")
+class TestRemoteHTTP(HTTP, TestDataCloudBase):
+    @pytest.fixture(autouse=True)
+    def setup_method_fixture(self, request, http_server):
+        self.http_server = http_server
+        self.method_name = request.function.__name__
+
+    def get_url(self):
+        return super().get_url(self.http_server.server_port)
+
+    def _get_cloud_class(self):
+        return RemoteHTTP
+
+
 class TestDataCloudCLIBase(TestDvc):
     def main(self, args):
         ret = main(args)
diff --git a/tests/func/test_gc.py b/tests/func/test_gc.py
index f1aa4012a..bb395669a 100644
--- a/tests/func/test_gc.py
+++ b/tests/func/test_gc.py
@@ -1,9 +1,11 @@
+import logging
 import os
 
 import configobj
 import pytest
 from git import Repo
 
+from dvc.compat import fspath
 from dvc.exceptions import CollectCacheError
 from dvc.main import main
 from dvc.repo import Repo as DvcRepo
@@ -28,11 +30,11 @@ class TestGC(TestDvcGit):
             self.bad_cache.append(path)
 
     def test_api(self):
-        self.dvc.gc()
+        self.dvc.gc(workspace=True)
         self._test_gc()
 
     def test_cli(self):
-        ret = main(["gc", "-f"])
+        ret = main(["gc", "-wf"])
         self.assertEqual(ret, 0)
         self._test_gc()
 
@@ -169,10 +171,10 @@ class TestGCMultipleDvcRepos(TestDvcGit):
 
         self._check_cache(3)
 
-        self.dvc.gc(repos=[self.additional_path])
+        self.dvc.gc(repos=[self.additional_path], workspace=True)
         self._check_cache(3)
 
-        self.dvc.gc()
+        self.dvc.gc(workspace=True)
         self._check_cache(2)
 
 
@@ -196,10 +198,10 @@ def test_gc_no_dir_cache(tmp_dir, dvc):
     os.unlink(dir_stage.outs[0].cache_path)
 
     with pytest.raises(CollectCacheError):
-        dvc.gc()
+        dvc.gc(workspace=True)
 
     assert _count_files(dvc.cache.local.cache_dir) == 4
-    dvc.gc(force=True)
+    dvc.gc(force=True, workspace=True)
     assert _count_files(dvc.cache.local.cache_dir) == 2
 
 
@@ -218,5 +220,59 @@ def test_gc_no_unpacked_dir(tmp_dir, dvc):
 
     assert os.path.exists(unpackeddir)
 
-    dvc.gc(force=True)
+    dvc.gc(force=True, workspace=True)
     assert not os.path.exists(unpackeddir)
+
+
+def test_gc_without_workspace_raises_error(tmp_dir, dvc):
+    dvc.gc(force=True, workspace=True)  # works without error
+
+    from dvc.exceptions import InvalidArgumentError
+
+    with pytest.raises(InvalidArgumentError):
+        dvc.gc(force=True)
+
+    with pytest.raises(InvalidArgumentError):
+        dvc.gc(force=True, workspace=False)
+
+
+def test_gc_without_workspace_on_tags_branches_commits(tmp_dir, dvc):
+    dvc.gc(force=True, all_tags=True)
+    dvc.gc(force=True, all_commits=True)
+    dvc.gc(force=False, all_branches=True)
+
+    # even if workspace is disabled, and others are enabled, assume as if
+    # workspace is enabled.
+    dvc.gc(force=False, all_branches=True, all_commits=False, workspace=False)
+
+
+def test_gc_without_workspace(tmp_dir, dvc, caplog):
+    with caplog.at_level(logging.WARNING, logger="dvc"):
+        assert main(["gc", "-vf"]) == 255
+
+    assert "Invalid Arguments" in caplog.text
+
+
+def test_gc_with_possible_args_positive(tmp_dir, dvc):
+    for flag in [
+        "-w",
+        "-a",
+        "-T",
+        "--all-commits",
+        "-aT",
+        "-wa",
+        "-waT",
+    ]:
+        assert main(["gc", "-vf", flag]) == 0
+
+
+def test_gc_cloud_positive(tmp_dir, dvc, tmp_path_factory):
+    with dvc.config.edit() as conf:
+        storage = fspath(tmp_path_factory.mktemp("test_remote_base"))
+        conf["remote"]["local_remote"] = {"url": storage}
+        conf["core"]["remote"] = "local_remote"
+
+    dvc.push()
+
+    for flag in ["-c", "-ca", "-cT", "-caT", "-cwT"]:
+        assert main(["gc", "-vf", flag]) == 0
diff --git a/tests/func/test_repro.py b/tests/func/test_repro.py
index 99eb24d34..d084de26b 100644
--- a/tests/func/test_repro.py
+++ b/tests/func/test_repro.py
@@ -950,7 +950,7 @@ class TestReproExternalBase(TestDvc):
         self.assertEqual(self.dvc.status([cmd_stage.path]), {})
 
         self.assertEqual(self.dvc.status(), {})
-        self.dvc.gc()
+        self.dvc.gc(workspace=True)
         self.assertEqual(self.dvc.status(), {})
 
         self.dvc.remove(cmd_stage.path, outs_only=True)
diff --git a/tests/func/test_stage.py b/tests/func/test_stage.py
index ce4991fd8..949e8b567 100644
--- a/tests/func/test_stage.py
+++ b/tests/func/test_stage.py
@@ -1,6 +1,7 @@
 import os
 import tempfile
 
+
 from dvc.main import main
 from dvc.output.local import OutputLOCAL
 from dvc.remote.local import RemoteLOCAL
diff --git a/tests/remotes.py b/tests/remotes.py
index 2102ad963..6964fc8a7 100644
--- a/tests/remotes.py
+++ b/tests/remotes.py
@@ -275,3 +275,11 @@ class HDFS:
         return "hdfs://{}@127.0.0.1{}".format(
             getpass.getuser(), Local.get_storagepath()
         )
+
+
+class HTTP:
+    should_test = always_test
+
+    @staticmethod
+    def get_url(port):
+        return "http://127.0.0.1:{}".format(port)
diff --git a/tests/unit/output/test_output.py b/tests/unit/output/test_output.py
index 2cf30e968..fb9325471 100644
--- a/tests/unit/output/test_output.py
+++ b/tests/unit/output/test_output.py
@@ -1,8 +1,12 @@
+import logging
+
 import pytest
+from funcy import first
 
 from voluptuous import Schema, MultipleInvalid
 
-from dvc.output import CHECKSUM_SCHEMA
+from dvc.cache import NamedCache
+from dvc.output import CHECKSUM_SCHEMA, OutputBase
 
 
 @pytest.mark.parametrize(
@@ -40,3 +44,47 @@ def test_checksum_schema(value, expected):
 def test_checksum_schema_fail(value):
     with pytest.raises(MultipleInvalid):
         Schema(CHECKSUM_SCHEMA)(value)["md5"]
+
+
+@pytest.mark.parametrize(
+    "exists, expected_message",
+    [
+        (
+            False,
+            (
+                "Output 'path'(Stage stage.dvc) is missing version info. "
+                "Cache for it will not be collected. "
+                "Use `dvc repro` to get your pipeline up to date."
+            ),
+        ),
+        (
+            True,
+            (
+                "Output 'path'(Stage stage.dvc) is missing version info. "
+                "Cache for it will not be collected. "
+                "Use `dvc repro` to get your pipeline up to date.\n"
+                "You can also use `dvc commit stage.dvc` to associate "
+                "existing 'path' with 'stage.dvc'."
+            ),
+        ),
+    ],
+)
+def test_get_used_cache(exists, expected_message, mocker, caplog):
+    stage = mocker.MagicMock()
+    mocker.patch.object(stage, "__str__", return_value="Stage stage.dvc")
+    mocker.patch.object(stage, "relpath", "stage.dvc")
+
+    output = OutputBase(stage, "path")
+
+    mocker.patch.object(output, "use_cache", True)
+    mocker.patch.object(stage, "is_repo_import", False)
+    mocker.patch.object(
+        OutputBase, "checksum", new_callable=mocker.PropertyMock
+    ).return_value = None
+    mocker.patch.object(
+        OutputBase, "exists", new_callable=mocker.PropertyMock
+    ).return_value = exists
+
+    with caplog.at_level(logging.WARNING, logger="dvc"):
+        assert isinstance(output.get_used_cache(), NamedCache)
+    assert first(caplog.messages) == expected_message
diff --git a/tests/unit/remote/test_http.py b/tests/unit/remote/test_http.py
index 20c8854fc..65ad2d2e8 100644
--- a/tests/unit/remote/test_http.py
+++ b/tests/unit/remote/test_http.py
@@ -27,3 +27,74 @@ def test_download_fails_on_error_code(dvc):
 
         with pytest.raises(HTTPError):
             remote._download(URLInfo(url) / "missing.txt", "missing.txt")
+
+
+def test_public_auth_method(dvc):
+    config = {
+        "url": "http://example.com/",
+        "path_info": "file.html",
+        "user": "",
+        "password": "",
+    }
+
+    remote = RemoteHTTP(dvc, config)
+
+    assert remote.auth_method() is None
+
+
+def test_basic_auth_method(dvc):
+    from requests.auth import HTTPBasicAuth
+
+    user = "username"
+    password = "password"
+    auth = HTTPBasicAuth(user, password)
+    config = {
+        "url": "http://example.com/",
+        "path_info": "file.html",
+        "auth": "basic",
+        "user": user,
+        "password": password,
+    }
+
+    remote = RemoteHTTP(dvc, config)
+
+    assert remote.auth_method() == auth
+    assert isinstance(remote.auth_method(), HTTPBasicAuth)
+
+
+def test_digest_auth_method(dvc):
+    from requests.auth import HTTPDigestAuth
+
+    user = "username"
+    password = "password"
+    auth = HTTPDigestAuth(user, password)
+    config = {
+        "url": "http://example.com/",
+        "path_info": "file.html",
+        "auth": "digest",
+        "user": user,
+        "password": password,
+    }
+
+    remote = RemoteHTTP(dvc, config)
+
+    assert remote.auth_method() == auth
+    assert isinstance(remote.auth_method(), HTTPDigestAuth)
+
+
+def test_custom_auth_method(dvc):
+    header = "Custom-Header"
+    password = "password"
+    config = {
+        "url": "http://example.com/",
+        "path_info": "file.html",
+        "auth": "custom",
+        "custom_auth_header": header,
+        "password": password,
+    }
+
+    remote = RemoteHTTP(dvc, config)
+
+    assert remote.auth_method() is None
+    assert header in remote.headers
+    assert remote.headers[header] == password
diff --git a/tests/utils/httpd.py b/tests/utils/httpd.py
index 2a3091eb3..378bb75b3 100644
--- a/tests/utils/httpd.py
+++ b/tests/utils/httpd.py
@@ -1,7 +1,8 @@
 import hashlib
 import os
 import threading
-from http.server import HTTPServer
+from http import HTTPStatus
+from http.server import HTTPServer, SimpleHTTPRequestHandler
 from RangeHTTPServer import RangeRequestHandler
 
 
@@ -35,6 +36,35 @@ class ContentMD5Handler(TestRequestHandler):
     checksum_header = "Content-MD5"
 
 
+class PushRequestHandler(SimpleHTTPRequestHandler):
+    def _chunks(self):
+        while True:
+            data = self.rfile.readline(65537)
+            chunk_size = int(data[:-2], 16)
+            if chunk_size == 0:
+                return
+            data = self.rfile.read(chunk_size)
+            yield data
+            self.rfile.read(2)
+
+    def do_POST(self):
+        chunked = self.headers.get("Transfer-Encoding", "") == "chunked"
+        path = self.translate_path(self.path)
+        try:
+            os.makedirs(os.path.dirname(path), exist_ok=True)
+            with open(path, "wb") as fd:
+                if chunked:
+                    for chunk in self._chunks():
+                        fd.write(chunk)
+                else:
+                    size = int(self.headers.get("Content-Length", 0))
+                    fd.write(self.rfile.read(size))
+        except OSError as e:
+            self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
+        self.send_response(HTTPStatus.OK)
+        self.end_headers()
+
+
 class StaticFileServer:
     _lock = threading.Lock()