diff --git a/pytools/__init__.py b/pytools/__init__.py
index 25881d5a708c5fafe8570133470ee71a3039ec44..70eb53051cd18b37ff5aa72f6620d6c5f61fbf0b 100644
--- a/pytools/__init__.py
+++ b/pytools/__init__.py
@@ -1978,6 +1978,49 @@ def download_from_web_if_not_present(url, local_name=None):
 # }}}
 
 
+# {{{ find git revisions
+
+def find_git_revision(tree_root):
+    # Keep this routine self-contained so that it can be copy-pasted into
+    # setup.py.
+
+    from os.path import join, exists, abspath
+    tree_root = abspath(tree_root)
+
+    if not exists(join(tree_root, ".git")):
+        return None
+
+    from subprocess import Popen, PIPE, STDOUT
+    p = Popen(["git", "rev-parse", "HEAD"], shell=False,
+              stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True,
+              cwd=tree_root)
+    (git_rev, _) = p.communicate()
+
+    import sys
+    if sys.version_info >= (3,):
+        git_rev = git_rev.decode()
+
+    git_rev = git_rev.rstrip()
+
+    retcode = p.returncode
+    assert retcode is not None
+    if retcode != 0:
+        from warnings import warn
+        warn("unable to find git revision")
+        return None
+
+    return git_rev
+
+
+def find_module_git_revision(module_file, n_levels_up):
+    from os.path import dirname, join
+    tree_root = join(*([dirname(module_file)] + [".." * n_levels_up]))
+
+    return find_git_revision(tree_root)
+
+# }}}
+
+
 def _test():
     import doctest
     doctest.testmod()
diff --git a/test/test_pytools.py b/test/test_pytools.py
index fbd7cf78bb0859268a72894cf53b3d78e986104b..51d64999c0b62a7a41ceb35d17a3c88f204bb770 100644
--- a/test/test_pytools.py
+++ b/test/test_pytools.py
@@ -206,6 +206,11 @@ def test_generate_numbered_unique_names():
     assert next(gen) == (7, "b_6")
 
 
+def test_find_module_git_revision():
+    import pytools
+    print(pytools.find_module_git_revision(pytools.__file__, n_levels_up=1))
+
+
 if __name__ == "__main__":
     if len(sys.argv) > 1:
         exec(sys.argv[1])