Repository: albertyw/git-browse

Issue:
Support git browsing a commit hash

Discussion:

Patch:
diff --git a/git_browse/browse.py b/git_browse/browse.py
index 08673cd..80c7886 100755
--- a/git_browse/browse.py
+++ b/git_browse/browse.py
@@ -36,12 +36,21 @@ class GithubHost(object):
             self.user,
             self.repository
         )
+        if focus_object.is_commit_hash():
+            return self.commit_hash_url(repository_url, focus_object)
         if focus_object.is_root():
             return self.root_url(repository_url, focus_object)
         if focus_object.is_directory():
             return self.directory_url(repository_url, focus_object)
         return self.file_url(repository_url, focus_object)
 
+    def commit_hash_url(self, repository_url, focus_hash):
+        repository_url = "%s/commit/%s" % (
+            repository_url,
+            focus_hash.commit_hash
+        )
+        return repository_url
+
     def root_url(self, repository_url, focus_object):
         return repository_url
 
@@ -69,10 +78,13 @@ class UberPhabricatorHost(object):
         return UberPhabricatorHost(None, None)
 
     def get_url(self, focus_object):
-        path = focus_object.path
-        # arc browse requires an object, provide the root object by default
-        if focus_object.is_root():
-            path = '.'
+        if focus_object.is_commit_hash():
+            path = focus_object.commit_hash
+        else:
+            path = focus_object.path
+            # arc browse requires an object, provide the root object by default
+            if focus_object.is_root():
+                path = '.'
         command = ['arc', 'browse']
         if path:
             command.append(path)
@@ -90,6 +102,9 @@ class FocusObject(object):
     def __init__(self, path):
         self.path = path
 
+    def is_commit_hash(self):
+        return False
+
     def is_root(self):
         return self.path == os.sep
 
@@ -101,6 +116,14 @@ class FocusObject(object):
         return FocusObject(os.sep)
 
 
+class FocusHash(object):
+    def __init__(self, commit_hash):
+        self.commit_hash = commit_hash
+
+    def is_commit_hash(self):
+        return True
+
+
 def get_repository_root():
     current_directory = ''
     new_directory = os.getcwd()
@@ -166,6 +189,9 @@ def get_focus_object(sys_argv, path):
     object_path = os.path.join(directory, focus_object[0])
     object_path = os.path.normpath(object_path)
     if not os.path.exists(object_path):
+        focus_hash = get_commit_hash(focus_object[0])
+        if focus_hash:
+            return focus_hash
         error = "specified file does not exist: %s" % object_path
         raise FileNotFoundError(error)
     is_dir = os.path.isdir(object_path) and object_path[-1] != os.sep
@@ -175,6 +201,21 @@ def get_focus_object(sys_argv, path):
     return FocusObject(object_path)
 
 
+def get_commit_hash(focus_object):
+    command = ['git', 'show', focus_object]
+    process = subprocess.Popen(
+        command,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        universal_newlines=True
+    )
+    out, err = process.communicate()
+    if process.returncode != 0:
+        return None
+    commit_hash = out.split("\n")[0].split(" ")[1]
+    return FocusHash(commit_hash)
+
+
 def open_url(url):
     print(url)
     if url.__class__ is list:


Test patch:
diff --git a/git_browse/tests/test.py b/git_browse/tests/test.py
index a96ba45..75ae39b 100644
--- a/git_browse/tests/test.py
+++ b/git_browse/tests/test.py
@@ -15,6 +15,7 @@ class TestGithubHost(unittest.TestCase):
         self.github_host = browse.GithubHost('albertyw', 'git-browse')
         self.repository_url = 'https://github.com/albertyw/git-browse'
         self.focus_object = browse.FocusObject('/')
+        self.focus_hash = browse.FocusHash('v2.0.0')
 
     def test_init(self):
         host = browse.GithubHost('user', 'repository')
@@ -48,6 +49,16 @@ class TestGithubHost(unittest.TestCase):
             'https://github.com/albertyw/git-browse/blob/master/README.md'
         )
 
+    def test_commit_hash_url(self):
+        url = self.github_host.commit_hash_url(
+            self.repository_url,
+            self.focus_hash
+        )
+        self.assertEqual(
+            url,
+            'https://github.com/albertyw/git-browse/commit/v2.0.0'
+        )
+
 
 class FocusObject(unittest.TestCase):
     def test_init(self):
@@ -75,6 +86,16 @@ class FocusObject(unittest.TestCase):
         self.assertTrue(obj.is_root())
 
 
+class FocusHash(unittest.TestCase):
+    def test_init(self):
+        obj = browse.FocusHash('abcde')
+        self.assertEqual(obj.commit_hash, 'abcde')
+
+    def test_is_commit_hash(self):
+        obj = browse.FocusHash('abcde')
+        self.assertTrue(obj.is_commit_hash())
+
+
 class GetRepositoryRoot(unittest.TestCase):
     def test_get(self):
         os.chdir(BASE_DIRECTORY)
@@ -179,12 +200,30 @@ class TestGetFocusObject(unittest.TestCase):
         self.assertFalse(focus_object.is_root())
         self.assertTrue(focus_object.is_directory())
 
+    def test_get_focus_hash(self):
+        sys_argv = ['asdf', 'v2.0.0']
+        focus_object = browse.get_focus_object(sys_argv, os.getcwd())
+        self.assertTrue(focus_object.__class__ is browse.FocusHash)
+
     def test_nonexistend_focus_object(self):
         sys_argv = ['asdf', 'asdf']
         with self.assertRaises(FileNotFoundError):
             browse.get_focus_object(sys_argv, os.getcwd())
 
 
+class TestGetCommitHash(unittest.TestCase):
+    def test_get_unknown_hash(self):
+        focus_object = '!@#$'
+        focus_hash = browse.get_commit_hash(focus_object)
+        self.assertEqual(focus_hash, None)
+
+    def test_get_hash(self):
+        focus_object = 'v2.0.0'
+        focus_hash = browse.get_commit_hash(focus_object)
+        self.assertTrue(focus_hash.__class__ is browse.FocusHash)
+        self.assertTrue(focus_hash.commit_hash)
+
+
 class TestOpenURL(unittest.TestCase):
     @patch("builtins.print", autospec=True)
     def test_open_url(self, mock_print):
diff --git a/git_browse/tests/test_git_urls.py b/git_browse/tests/test_git_urls.py
index e570b67..75d62e3 100644
--- a/git_browse/tests/test_git_urls.py
+++ b/git_browse/tests/test_git_urls.py
@@ -49,6 +49,12 @@ GIT_URLS = [
         TEST_DIR,
         'https://github.com/albertyw/git-browse/tree/master/testdir/'
     ),
+    (
+        'git@github.com:albertyw/git-browse',
+        'v2.0.0',
+        'https://github.com/albertyw/git-browse/commit/' +
+        'f5631b4c423f2fa5c9c4b64853607f1727d4b7a9'
+    ),
     (
         'gitolite@code.uber.internal:a/b',
         None,
@@ -63,6 +69,11 @@ GIT_URLS = [
         'gitolite@code.uber.internal:a/b',
         TEST_DIR,
         ['arc', 'browse', TEST_DIR+'/']
+    ),
+    (
+        'gitolite@code.uber.internal:a/b',
+        'v2.0.0',
+        ['arc', 'browse', 'f5631b4c423f2fa5c9c4b64853607f1727d4b7a9']
     )
 ]