Repository: airspeed-velocity/asv

Issue:
Include branch name for the commit
If configuration is setup to test multiple branches it would be great to know what branch(es)  commit being tested belongs to.  Currently I am just getting output like `For XXX commit hash YYYY:`.  Should be quite easy to implement by simply looking at either any given specified branch includes that commit in its history, e.g. whenever `git merge-base COMMIT BRANCH` == `COMMIT` (there could be a smarter way I bet)

Discussion:

Patch:
diff --git a/asv/commands/compare.py b/asv/commands/compare.py
index 7a6e69d..1096249 100644
--- a/asv/commands/compare.py
+++ b/asv/commands/compare.py
@@ -163,14 +163,18 @@ class Compare(Command):
             raise util.UserError(
                 "Results for machine '{0} not found".format(machine))
 
+        commit_names = {hash_1: repo.get_name_from_hash(hash_1),
+                        hash_2: repo.get_name_from_hash(hash_2)}
+
         cls.print_table(conf, hash_1, hash_2, factor=factor, split=split,
                         only_changed=only_changed, sort=sort,
-                        machine=machine, env_names=env_names)
+                        machine=machine, env_names=env_names, commit_names=commit_names)
 
     @classmethod
     def print_table(cls, conf, hash_1, hash_2, factor, split,
                     resultset_1=None, resultset_2=None, machine=None,
-                    only_changed=False, sort='name', use_stats=True, env_names=None):
+                    only_changed=False, sort='name', use_stats=True, env_names=None,
+                    commit_names=None):
         results_1 = {}
         results_2 = {}
         stats_1 = {}
@@ -178,6 +182,9 @@ class Compare(Command):
         versions_1 = {}
         versions_2 = {}
 
+        if commit_names is None:
+            commit_names = {}
+
         def results_default_iter(commit_hash):
             for result in iter_results_for_machine_and_hash(
                     conf.results_dir, machine, commit_hash):
@@ -360,6 +367,21 @@ class Compare(Command):
             color_print("       before           after         ratio")
             color_print("     [{0:8s}]       [{1:8s}]".format(hash_1[:8], hash_2[:8]))
 
+            name_1 = commit_names.get(hash_1)
+            if name_1:
+                name_1 = '<{0}>'.format(name_1)
+            else:
+                name_1 = ''
+
+            name_2 = commit_names.get(hash_2)
+            if name_2:
+                name_2 = '<{0}>'.format(name_2)
+            else:
+                name_2 = ''
+
+            if name_1 or name_2:
+                color_print("     {0:10s}       {1:10s}".format(name_1, name_2))
+
             if sort == 'ratio':
                 bench[key].sort(key=lambda v: v[3], reverse=True)
             elif sort == 'name':
diff --git a/asv/commands/continuous.py b/asv/commands/continuous.py
index 253c775..7d46e08 100644
--- a/asv/commands/continuous.py
+++ b/asv/commands/continuous.py
@@ -126,11 +126,15 @@ class Continuous(Command):
                     stats = result.get_result_stats(name, params)
                     yield name, params, value, stats, version, machine_name, env.name
 
+        commit_names = {parent: repo.get_name_from_hash(parent),
+                        head: repo.get_name_from_hash(head)}
+
         status = Compare.print_table(conf, parent, head,
                                      resultset_1=results_iter(parent),
                                      resultset_2=results_iter(head),
                                      factor=factor, split=split,
-                                     only_changed=only_changed, sort=sort)
+                                     only_changed=only_changed, sort=sort,
+                                     commit_names=commit_names)
         worsened, improved = status
 
         color_print("")
diff --git a/asv/commands/find.py b/asv/commands/find.py
index ee87346..e4a4e7e 100644
--- a/asv/commands/find.py
+++ b/asv/commands/find.py
@@ -119,9 +119,10 @@ class Find(Command):
 
             commit_hash = commit_hashes[i]
 
+            commit_name = repo.get_decorated_hash(commit_hash, 8)
             log.info(
-                "For {0} commit hash {1}:".format(
-                    conf.project, commit_hash[:8]))
+                "For {0} commit {1}:".format(
+                    conf.project, commit_name))
 
             env.install_project(conf, repo, commit_hash)
 
@@ -210,6 +211,7 @@ class Find(Command):
 
         result = do_search(0, len(commit_hashes) - 1)
 
-        log.info("Greatest regression found: {0}".format(commit_hashes[result][:8]))
+        commit_name = repo.get_decorated_hash(commit_hashes[result], 8)
+        log.info("Greatest regression found: {0}".format(commit_name))
 
         return 0
diff --git a/asv/commands/run.py b/asv/commands/run.py
index c65a78e..45950bc 100644
--- a/asv/commands/run.py
+++ b/asv/commands/run.py
@@ -325,9 +325,10 @@ class Run(Command):
                 else:
                     round_info = ""
 
+                commit_name = repo.get_decorated_hash(commit_hash, 8)
                 log.info(
-                    "For {0} commit hash {1}{2}:".format(
-                        conf.project, commit_hash[:8], round_info))
+                    "For {0} commit {1}{2}:".format(
+                        conf.project, commit_name, round_info))
 
             with log.indent():
 
diff --git a/asv/environment.py b/asv/environment.py
index 139766c..c58ace4 100644
--- a/asv/environment.py
+++ b/asv/environment.py
@@ -509,7 +509,10 @@ class Environment(object):
 
     def build_project(self, repo, commit_hash):
         self.checkout_project(repo, commit_hash)
-        log.info("Building {0} for {1}".format(commit_hash[:8], self.name))
+
+        commit_name = repo.get_decorated_hash(commit_hash, 8)
+        log.info("Building {0} for {1}".format(commit_name, self.name))
+
         if self._repo_subdir:
             build_dir = os.path.join(self._build_root, self._repo_subdir)
         else:
diff --git a/asv/plugins/git.py b/asv/plugins/git.py
index dc32389..5d99b89 100644
--- a/asv/plugins/git.py
+++ b/asv/plugins/git.py
@@ -128,12 +128,15 @@ class Git(Repo):
         if name is None:
             name = self.get_branch_name()
 
+        # In case of annotated tags, return the hash for the commit
+        lookup_name = name + '^{commit}'
+
         try:
-            return self._run_git(['rev-parse', name],
+            return self._run_git(['rev-parse', lookup_name],
                                  display_error=False,
                                  dots=False).strip().split()[0]
         except util.ProcessError as err:
-            if err.stdout.strip() == name:
+            if err.stdout.strip() == lookup_name:
                 # Name does not exist
                 raise NoSuchNameError(name)
             raise
@@ -141,6 +144,27 @@ class Git(Repo):
     def get_hash_from_parent(self, name):
         return self.get_hash_from_name(name + '^')
 
+    def get_name_from_hash(self, commit):
+        try:
+            name = self._run_git(["name-rev", "--name-only",
+                                  "--exclude=remotes/*",
+                                  "--no-undefined", commit],
+                                 display_error=False).strip()
+            if not name:
+                return None
+        except util.ProcessError as err:
+            if err.retcode == 128:
+                # Nothing found
+                return None
+            raise
+ 
+        # Return tags without prefix
+        for prefix in ['tags/']:
+            if name.startswith(prefix):
+                return name[len(prefix):]
+
+        return name
+
     def get_tags(self):
         tags = {}
         for tag in self._run_git(["tag", "-l"]).splitlines():
diff --git a/asv/plugins/mercurial.py b/asv/plugins/mercurial.py
index 20cb626..9f1da86 100644
--- a/asv/plugins/mercurial.py
+++ b/asv/plugins/mercurial.py
@@ -152,6 +152,10 @@ class Hg(Repo):
     def get_hash_from_parent(self, name):
         return self.get_hash_from_name('p1({0})'.format(name))
 
+    def get_name_from_hash(self, commit):
+        # XXX: implement
+        return None
+
     def get_tags(self):
         tags = {}
         for item in self._repo.log(b"tag()"):
diff --git a/asv/repo.py b/asv/repo.py
index 035de23..5d12f60 100644
--- a/asv/repo.py
+++ b/asv/repo.py
@@ -129,6 +129,25 @@ class Repo(object):
         """
         raise NotImplementedError()
 
+    def get_name_from_hash(self, commit):
+        """
+        Get a symbolic name for a commit, if it exists.
+        The name is resolvable back to the has via get_hash_form_name.
+        If there is no symbolic name, returns None.
+        """
+        raise NotImplementedError()
+
+    def get_decorated_hash(self, commit, hash_length=8):
+        """
+        Return commit hash with possible branch/tag information added,
+        for displaying to the user.
+        """
+        name = self.get_name_from_hash(commit)
+        if name is not None:
+            return "{0} <{1}>".format(commit[:hash_length], name)
+        else:
+            return commit[:hash_length]
+
     def get_tags(self):
         """
         Get a dict of all of the tags defined in the repo and their
@@ -207,6 +226,9 @@ class NoRepository(Repo):
     def get_hash_from_parent(self, name):
         self._raise_error()
 
+    def get_name_from_hash(self, commit):
+        return None
+
     def get_tags(self):
         return {}
 


Test patch:
diff --git a/test/test_compare.py b/test/test_compare.py
index 3674c31..0584855 100644
--- a/test/test_compare.py
+++ b/test/test_compare.py
@@ -105,6 +105,7 @@ x           1.00s            3.00s     3.00  time_with_version_mismatch_other
 REFERENCE_ONLY_CHANGED = """
        before           after         ratio
      [22b920c6]       [fcf8c079]
+     <name1>          <name2>   
 !             n/a           failed      n/a  params_examples.ParamSuite.track_value
 !           454μs           failed      n/a  time_coordinates.time_latitude
 !           3.00s           failed      n/a  time_other.time_parameterized(3)
@@ -161,7 +162,8 @@ def test_compare(capsys, tmpdir):
     # Check print_table output as called from Continuous
     status = Compare.print_table(conf, '22b920c6', 'fcf8c079', factor=2, machine='cheetah',
                                  split=False, only_changed=True, sort='ratio',
-                                 env_names=["py2.7-numpy1.8"])
+                                 env_names=["py2.7-numpy1.8"],
+                                 commit_names={'22b920c6': 'name1', 'fcf8c079': 'name2'})
     worsened, improved = status
     assert worsened
     assert improved
diff --git a/test/test_repo.py b/test/test_repo.py
index b10cb35..d9ea56e 100644
--- a/test/test_repo.py
+++ b/test/test_repo.py
@@ -76,7 +76,7 @@ def _test_generic_repo(conf, tmpdir, hash_range, master, branch, is_remote=False
         r.get_date_from_name(tag)
 
 
-def _test_branches(conf, branch_commits):
+def _test_branches(conf, branch_commits, require_describe=False):
     r = repo.get_repo(conf)
 
     assert len(conf.branches) == 2
@@ -87,6 +87,13 @@ def _test_branches(conf, branch_commits):
         for commit in branch_commits[branch]:
             assert commit in commits
 
+            name = r.get_name_from_hash(commit)
+            if require_describe:
+                assert name is not None
+            if name is not None:
+                assert r.get_hash_from_name(name) == commit
+                assert name in r.get_decorated_hash(commit)
+
 
 def test_repo_git(tmpdir):
     tmpdir = six.text_type(tmpdir)
@@ -109,7 +116,7 @@ def test_repo_git(tmpdir):
             'master': [dvcs.get_hash('master'), dvcs.get_hash('master~6')],
             'some-branch': [dvcs.get_hash('some-branch'), dvcs.get_hash('some-branch~6')]
         }
-        _test_branches(conf, branch_commits)
+        _test_branches(conf, branch_commits, require_describe=True)
 
     test_it()
 
diff --git a/test/test_workflow.py b/test/test_workflow.py
index fbb72a7..f7c5b02 100644
--- a/test/test_workflow.py
+++ b/test/test_workflow.py
@@ -193,7 +193,7 @@ def test_continuous(capfd, basic_conf):
     assert "params_examples.ClassOne" in text
 
     # Check processes were interleaved (timing benchmark was run twice)
-    assert re.search(r"For.*commit hash [a-f0-9]+ \(round 1/2\)", text, re.M)
+    assert re.search(r"For.*commit [a-f0-9]+ (<[a-z0-9~^]+> )?\(round 1/2\)", text, re.M), text
 
     result_found = False
     for results in iter_results_for_machine(conf.results_dir, "orangutan"):