Repository: valohai/valohai-cli

Issue:
Incorrect error message when commit is not yet fetched
Valohai-cli should ask if user want to fetch after Roi fails to find the commit. 

### User story:
When trying to run execution, got:

(research) MacBook-Pro-9:language\_modeling\_research $ vh execution run train  
😿 Warning: Commit e2780cc5ef7c54c4b56fb69cb2ca21cd06f81791 is not known for the project. Have you pushed it?

...  
but I had actually pushed the commit. Actually I had to click "Fetch repository" on the app page. I wonder why it cannot automatically fetch the repo? gz#1194

Discussion:

Patch:
diff --git a/valohai_cli/api.py b/valohai_cli/api.py
index 1ad2851..3c006b9 100644
--- a/valohai_cli/api.py
+++ b/valohai_cli/api.py
@@ -72,10 +72,10 @@ class APISession(requests.Session):
         try:
             resp = super().request(method, url, **kwargs)
         except requests.ConnectionError as ce:
-            host = urlparse(ce.request.url).netloc
+            host = urlparse(ce.request.url).netloc if ce.request else self.base_netloc
             if 'Connection refused' in str(ce):
                 raise CLIException(
-                    f'Unable to connect to {host} (connection refused); try again soon.'
+                    f'Unable to connect to {host!r} (connection refused); try again soon.'
                 ) from ce
             raise
 
diff --git a/valohai_cli/utils/commits.py b/valohai_cli/utils/commits.py
index 9c0e73c..3932f56 100644
--- a/valohai_cli/utils/commits.py
+++ b/valohai_cli/utils/commits.py
@@ -1,9 +1,12 @@
-from typing import Optional, Union
+from typing import List, Optional, Union
 
 import click
+from click import get_current_context
 
 from valohai_cli import git as git
 from valohai_cli.adhoc import package_adhoc_commit
+from valohai_cli.commands.project.fetch import fetch
+from valohai_cli.ctx import get_project
 from valohai_cli.exceptions import NoGitRepo
 from valohai_cli.messages import warn
 from valohai_cli.models.project import Project
@@ -45,6 +48,7 @@ def get_git_commit(project: Project) -> Optional[str]:
 
 
 def resolve_commit(commit_identifier: Optional[str], project: Project) -> str:
+    matching_commits = []
     if commit_identifier and commit_identifier.startswith('~'):
         # Assume ad-hoc commits are qualified already
         return commit_identifier
@@ -52,10 +56,13 @@ def resolve_commit(commit_identifier: Optional[str], project: Project) -> str:
     try:
         matching_commits = project.resolve_commits(commit_identifier=commit_identifier)
     except KeyError:
-        warn(f'Commit {commit_identifier} is not known for the project. Have you pushed it?')
-        raise click.Abort()
+        warn(f'Commit {commit_identifier} is not known for the project')
+        if click.confirm('Would you like to fetch new commits?', default=True):
+            matching_commits = fetch_latest_commits(commit_identifier)
     except IndexError:
         warn('No commits are known for the project.')
+
+    if not matching_commits:
         raise click.Abort()
 
     commit_obj = matching_commits[0]
@@ -70,3 +77,10 @@ def resolve_commit(commit_identifier: Optional[str], project: Project) -> str:
         click.echo(f'Resolved to commit {resolved_commit_identifier}', err=True)
 
     return resolved_commit_identifier
+
+
+def fetch_latest_commits(commit_identifier: Optional[str]) -> List[dict]:
+    ctx = get_current_context(silent=True)
+    ctx.invoke(fetch)
+    project = get_project(require=True)
+    return project.resolve_commits(commit_identifier=commit_identifier)


Test patch:
diff --git a/tests/test_utils_commits.py b/tests/test_utils_commits.py
index 4edd45a..68ef773 100644
--- a/tests/test_utils_commits.py
+++ b/tests/test_utils_commits.py
@@ -78,14 +78,29 @@ class TestResolveCommit:
         out, err = capsys.readouterr()
         assert 'which is ambiguous with' in err
 
-    def test_fails_if_not_found(self, logged_in, project, commits):
+
+    @pytest.mark.parametrize('confirm', [True, False])
+    def test_asks_to_fetch_latest_if_not_found(self, logged_in, project, commits, monkeypatch, confirm):
+        new_commit = '94c9d70834f53ce765f3b86bac50f015278d8bd4'
         with requests_mock.mock() as m:
             m.get(self.commit_api_url, json={'results': commits})
-            with pytest.raises(click.exceptions.Abort):
-                resolve_commit('94c9d70834f53ce765f3b86bac50f015278d8bd4', project)
 
-    def test_fails_if_no_commits(self, logged_in, project):
+            # When the commit is not found, it should ask to fetch latest commits
+            monkeypatch.setattr(click, 'confirm', lambda *args, **kwargs: confirm)
+
+            if confirm:
+                # When the latest commit is fetched, it should return the new commit
+                monkeypatch.setattr('valohai_cli.utils.commits.fetch_latest_commits', lambda unknown_commit: [{'identifier': new_commit}])
+                assert resolve_commit(new_commit, project) == new_commit
+            else:
+                with pytest.raises(click.exceptions.Abort):
+                    resolve_commit(new_commit, project)
+
+    def test_fails_if_no_commits(self, logged_in, project, monkeypatch):
         with requests_mock.mock() as m:
             m.get(self.commit_api_url, json={'results': []})
+            monkeypatch.setattr(click, 'confirm', lambda *args, **kwargs: True)
+            monkeypatch.setattr('valohai_cli.utils.commits.fetch_latest_commits',
+                                lambda unknown_commit: [])
             with pytest.raises(click.exceptions.Abort):
                 resolve_commit('c243d40ff7feb63aa7e15e8eda1d8859010ebc53', project)