Repository: airspeed-velocity/asv

Issue:
`git rev-parse master` can fail
I'm trying to run benchmarks on Travis-CI. Unfortunately, "asv run" uses "git rev-parse master" to discover benchmarks, which fails as "master" doesn't necessary exist:
https://travis-ci.org/pitrou/arrow/builds/364763507#L866

The command being run is:
```
asv run --no-pull --show-stderr --quick ${TRAVIS_COMMIT}^!
```

Discussion:

Patch:
diff --git a/asv/benchmarks.py b/asv/benchmarks.py
index 40379f9..8dacfbb 100644
--- a/asv/benchmarks.py
+++ b/asv/benchmarks.py
@@ -19,6 +19,7 @@ import six
 from .console import log, truncate_left
 from . import util
 from . import statistics
+from .repo import NoSuchNameError
 
 
 WIN = (os.name == "nt")
@@ -410,7 +411,11 @@ class Benchmarks(dict):
         # discovery usually succeeds
         commit_hashes = list(commit_hashes)
         for branch in conf.branches:
-            branch_hash = repo.get_hash_from_name(branch)
+            try:
+                branch_hash = repo.get_hash_from_name(branch)
+            except NoSuchNameError:
+                continue
+
             if branch_hash not in commit_hashes:
                 commit_hashes.append(branch_hash)
 
diff --git a/asv/plugins/git.py b/asv/plugins/git.py
index bc9eb4d..04ee9d5 100644
--- a/asv/plugins/git.py
+++ b/asv/plugins/git.py
@@ -12,7 +12,7 @@ import os
 import re
 
 from ..console import log
-from ..repo import Repo
+from ..repo import Repo, NoSuchNameError
 from .. import util
 
 
@@ -127,8 +127,15 @@ class Git(Repo):
     def get_hash_from_name(self, name):
         if name is None:
             name = self.get_branch_name()
-        return self._run_git(['rev-parse', name],
-                             dots=False).strip().split()[0]
+
+        try:
+            return self._run_git(['rev-parse', name],
+                                 dots=False).strip().split()[0]
+        except util.ProcessError as err:
+            if err.stdout.strip() == name:
+                # Name does not exist
+                raise NoSuchNameError(name)
+            raise
 
     def get_hash_from_parent(self, name):
         return self.get_hash_from_name(name + '^')
diff --git a/asv/plugins/mercurial.py b/asv/plugins/mercurial.py
index e723e51..20cb626 100644
--- a/asv/plugins/mercurial.py
+++ b/asv/plugins/mercurial.py
@@ -17,7 +17,7 @@ except ImportError as exc:
     hglib = None
 
 from ..console import log
-from ..repo import Repo
+from ..repo import Repo, NoSuchNameError
 from .. import util
 
 
@@ -142,7 +142,12 @@ class Hg(Repo):
     def get_hash_from_name(self, name):
         if name is None:
             name = self.get_branch_name()
-        return self._decode(self._repo.log(self._encode(name))[0].node)
+        try:
+            return self._decode(self._repo.log(self._encode(name))[0].node)
+        except hglib.error.CommandError as err:
+            if b'unknown revision' in err.err:
+                raise NoSuchNameError(name)
+            raise
 
     def get_hash_from_parent(self, name):
         return self.get_hash_from_name('p1({0})'.format(name))
diff --git a/asv/repo.py b/asv/repo.py
index 8b7f98a..43e2f16 100644
--- a/asv/repo.py
+++ b/asv/repo.py
@@ -8,6 +8,13 @@ from __future__ import (absolute_import, division, print_function,
 from . import util
 
 
+class NoSuchNameError(RuntimeError):
+    """
+    Exception raised if requested branch or commit does not exist.
+    """
+    pass
+
+
 class Repo(object):
     """
     Base class for repository handlers.


Test patch:
diff --git a/test/test_benchmarks.py b/test/test_benchmarks.py
index 0a35fcb..4fa7a18 100644
--- a/test/test_benchmarks.py
+++ b/test/test_benchmarks.py
@@ -52,6 +52,7 @@ def test_find_benchmarks(tmpdir):
     d['env_dir'] = "env"
     d['benchmark_dir'] = 'benchmark'
     d['repo'] = tools.generate_test_repo(tmpdir, [0]).path
+    d['branches'] = ["master", "some-missing-branch"]  # missing branches ignored
     conf = config.Config.from_json(d)
 
     repo = get_repo(conf)
diff --git a/test/test_repo.py b/test/test_repo.py
index 88a498c..b10cb35 100644
--- a/test/test_repo.py
+++ b/test/test_repo.py
@@ -434,3 +434,35 @@ def test_root_ceiling(dvcs_type, tmpdir):
     # parent repository
     with pytest.raises(Exception):
         r.checkout(workcopy_dir, commit2)
+
+
+@pytest.mark.parametrize('dvcs_type', [
+    "git",
+    pytest.mark.skipif(hglib is None, reason="needs hglib")("hg")
+])
+def test_no_such_name_error(dvcs_type, tmpdir):
+    tmpdir = six.text_type(tmpdir)
+    dvcs = tools.generate_test_repo(tmpdir, values=[0], dvcs_type=dvcs_type)
+
+    conf = config.Config()
+    conf.branches = []
+    conf.dvcs = dvcs_type
+    conf.project = "project"
+    conf.repo = dvcs.path
+
+    r = repo.get_repo(conf)
+
+    # Check that NoSuchNameError error gets raised correctly
+    assert r.get_hash_from_name(None) == dvcs.get_hash(r._default_branch)
+    with pytest.raises(repo.NoSuchNameError):
+        r.get_hash_from_name("badbranch")
+
+    if dvcs_type == "git":
+        # Corrupted repository/etc should not give NoSuchNameError
+        util.long_path_rmtree(join(dvcs.path, ".git"))
+        with pytest.raises(Exception) as excinfo:
+            r.get_hash_from_name(None)
+        assert excinfo.type not in (AssertionError, repo.NoSuchNameError)
+    elif dvcs_type == "hg":
+        # hglib seems to do some caching, so this doesn't work
+        pass