Repository: iterative/dvc

Issue:
dvc diff ERROR: unexpected error - 'NoneType' object has no attribute 'endswith' 
If given rev is not tracked fails. I.E 2f05868ebe3693a7e7afbdd8923c15ce327b179d does not have metrics:
```
dvc diff  --show-json 2f05868ebe3693a7e7afbdd8923c15ce327b179d 19b33235917f6b3732de8486b35235ba200e74a2
ERROR: unexpected error - 'NoneType' object has no attribute 'endswith' 
```

I would expect at least the difference of files added 

**Please provide information about your setup**
dvc 0.87.0
Python 3.6.9
ubuntu 10.04



Discussion:

Patch:
diff --git a/dvc/command/remote.py b/dvc/command/remote.py
index 74fa2b677..5a0506c34 100644
--- a/dvc/command/remote.py
+++ b/dvc/command/remote.py
@@ -104,6 +104,35 @@ class CmdRemoteList(CmdRemote):
         return 0
 
 
+class CmdRemoteRename(CmdRemote):
+    def _rename_default(self, conf):
+        if conf["core"].get("remote") == self.args.name:
+            conf["core"]["remote"] = self.args.new
+
+    def run(self):
+        all_config = self.config.load_config_to_level(None)
+        if self.args.new in all_config.get("remote", {}):
+            raise ConfigError(
+                "Rename failed. Remote name '{}' already exists.".format(
+                    {self.args.new}
+                )
+            )
+
+        with self.config.edit(self.args.level) as conf:
+            self._check_exists(conf)
+            conf["remote"][self.args.new] = conf["remote"][self.args.name]
+            del conf["remote"][self.args.name]
+            self._rename_default(conf)
+
+        for level in reversed(self.config.LEVELS):
+            if level == self.args.level:
+                break
+            with self.config.edit(level) as level_conf:
+                self._rename_default(level_conf)
+
+        return 0
+
+
 def add_parser(subparsers, parent_parser):
     from dvc.command.config import parent_config_parser
 
@@ -220,3 +249,14 @@ def add_parser(subparsers, parent_parser):
         "name", help="Name of the remote to remove."
     )
     remote_remove_parser.set_defaults(func=CmdRemoteRemove)
+    REMOTE_RENAME_HELP = "Rename a DVC remote"
+    remote_rename_parser = remote_subparsers.add_parser(
+        "rename",
+        parents=[parent_config_parser, parent_parser],
+        description=append_doc_link(REMOTE_RENAME_HELP, "remote/rename"),
+        help=REMOTE_RENAME_HELP,
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+    )
+    remote_rename_parser.add_argument("name", help="Remote to be renamed")
+    remote_rename_parser.add_argument("new", help="New name of the remote")
+    remote_rename_parser.set_defaults(func=CmdRemoteRename)
diff --git a/dvc/remote/base.py b/dvc/remote/base.py
index 039339100..ec6c6a0dc 100644
--- a/dvc/remote/base.py
+++ b/dvc/remote/base.py
@@ -319,6 +319,8 @@ class BaseRemote:
 
     @classmethod
     def is_dir_checksum(cls, checksum):
+        if not checksum:
+            return False
         return checksum.endswith(cls.CHECKSUM_DIR_SUFFIX)
 
     def get_checksum(self, path_info):


Test patch:
diff --git a/tests/func/test_remote.py b/tests/func/test_remote.py
index d8217eda0..6a031742a 100644
--- a/tests/func/test_remote.py
+++ b/tests/func/test_remote.py
@@ -284,3 +284,74 @@ def test_remote_modify_default(dvc):
 
     assert repo_config["core"]["remote"] == remote_repo
     assert local_config["core"]["remote"] == remote_local
+
+
+def test_remote_rename(dvc):
+    remote_name = "drive"
+    remote_url = "gdrive://test/test"
+    new_name = "new"
+    other_name = "other"
+    # prepare
+    assert main(["remote", "add", remote_name, remote_url]) == 0
+    config = dvc.config.load_one("repo")
+    assert config["remote"][remote_name]["url"] == remote_url
+    assert new_name not in config.get("remote", {})
+
+    # rename failed
+    assert main(["remote", "rename", remote_name]) == 254
+    assert main(["remote", "rename", new_name, other_name]) == 251
+    config = dvc.config.load_one("repo")
+    assert config["remote"][remote_name]["url"] == remote_url
+    assert new_name not in config.get("remote", {})
+
+    # rename success
+    assert main(["remote", "rename", remote_name, new_name]) == 0
+    config = dvc.config.load_one("repo")
+    assert remote_name not in config.get("remote", {})
+    assert config["remote"][new_name]["url"] == remote_url
+
+
+def test_remote_duplicated(dvc):
+    remote_name = "drive"
+    remote_url = "gdrive://test/test"
+    used_name = "overlap"
+    another_url = "gdrive://test/test1"
+    # prepare
+    assert main(["remote", "add", remote_name, remote_url]) == 0
+    assert main(["remote", "add", "--local", used_name, another_url]) == 0
+    config = dvc.config.load_one("repo")
+    assert config["remote"][remote_name]["url"] == remote_url
+    local_config = dvc.config.load_one("local")
+    assert local_config["remote"][used_name]["url"] == another_url
+
+    # rename duplicated
+    assert main(["remote", "rename", remote_name, used_name]) == 251
+    config = dvc.config.load_one("repo")
+    assert config["remote"][remote_name]["url"] == remote_url
+    local_config = dvc.config.load_one("local")
+    assert local_config["remote"][used_name]["url"] == another_url
+
+
+def test_remote_default(dvc):
+    remote_name = "drive"
+    remote_url = "gdrive://test/test"
+    new_name = "new"
+    # prepare
+    assert main(["remote", "add", "-d", remote_name, remote_url]) == 0
+    assert main(["remote", "default", "--local", remote_name]) == 0
+    config = dvc.config.load_one("repo")
+    assert config["core"]["remote"] == remote_name
+    assert config["remote"][remote_name]["url"] == remote_url
+    assert new_name not in config.get("remote", {})
+    local_config = dvc.config.load_one("local")
+    assert local_config["core"]["remote"] == remote_name
+
+    # rename success
+    assert main(["remote", "rename", remote_name, new_name]) == 0
+    config = dvc.config.load_one("repo")
+    assert remote_name not in config.get("remote", {})
+    assert config["core"]["remote"] == new_name
+    assert config["remote"][new_name]["url"] == remote_url
+    assert remote_name not in config.get("remote", {})
+    local_config = dvc.config.load_one("local")
+    assert local_config["core"]["remote"] == new_name
diff --git a/tests/unit/remote/test_base.py b/tests/unit/remote/test_base.py
index 1e07589df..c89b0474f 100644
--- a/tests/unit/remote/test_base.py
+++ b/tests/unit/remote/test_base.py
@@ -136,3 +136,11 @@ def test_cache_checksums(dvc):
     ):
         checksums = list(remote.cache_checksums())
         assert checksums == ["123456"]
+
+
+@pytest.mark.parametrize(
+    "checksum, result",
+    [(None, False), ("", False), ("3456.dir", True), ("3456", False)],
+)
+def test_is_dir_checksum(checksum, result):
+    assert BaseRemote.is_dir_checksum(checksum) == result