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 {}
 

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)
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"):

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-rerunfailures
apt-get update apt-get install -y gcc
3.6
pip_requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
-e git+https://github.com/airspeed-velocity/asv.git@b6d369758a59cdae600b1cb91bb7dc59be3f0531#egg=asv
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
coverage==6.2
docutils==0.18.1
execnet==1.9.0
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.0.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
pytest-rerunfailures==10.3
pytest-xdist==3.0.2
pytz==2025.2
requests==2.27.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-bootstrap-theme==0.8.1
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: asv
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - attrs==22.2.0
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - coverage==6.2
      - docutils==0.18.1
      - execnet==1.9.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-rerunfailures==10.3
      - pytest-xdist==3.0.2
      - pytz==2025.2
      - requests==2.27.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-bootstrap-theme==0.8.1
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/asv

test/test_compare.py::test_compare test/test_repo.py::test_repo_git
test/test_compare.py::test_compare_name_lookup[dvcs_type1] test/test_repo.py::test_git_submodule test/test_repo.py::test_root_ceiling[dvcs_type1] test/test_repo.py::test_no_such_name_error[dvcs_type1]
test/test_compare.py::test_compare_name_lookup[git] test/test_repo.py::test_repo_git_annotated_tag_date test/test_repo.py::test_get_branch_commits[git] test/test_repo.py::test_get_new_branch_commits[git-all] test/test_repo.py::test_get_new_branch_commits[git-new] test/test_repo.py::test_get_new_branch_commits[git-no-new] test/test_repo.py::test_get_new_branch_commits[git-new-branch-added-in-config] test/test_repo.py::test_root_ceiling[git] test/test_repo.py::test_no_such_name_error[git]
asv/commands/continuous.py asv/commands/run.py asv/commands/compare.py asv/repo.py asv/environment.py asv/commands/find.py asv/plugins/mercurial.py asv/plugins/git.py
asv/commands/continuous.py asv/commands/run.py asv/commands/compare.py asv/repo.py asv/environment.py asv/commands/find.py asv/plugins/mercurial.py asv/plugins/git.py