Jelajahi Sumber

Add support for getting the revision graph from a head.

Ask a repo for the revision_history from a commit sha, and it will oblige by 
returning a list of commits that are reverse chronologically sorted.

Doing this required storing the commit time. The author time and the timezones
are currently discarded, but could be parsed.

Also ShaFile now implements __eq__ so that two objects are the same if their
sha value matches.
James Westby 18 tahun lalu
induk
melakukan
c28e9eea8d
25 mengubah file dengan 108 tambahan dan 12 penghapusan
  1. 33 6
      git/objects.py
  2. 31 0
      git/repository.py
  3. 1 0
      git/tests/data/repos/simple_merge/.git/HEAD
  4. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/index
  5. 2 0
      git/tests/data/repos/simple_merge/.git/objects/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
  6. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/1b/6318f651a534b38f9c7aedeebbd56c1e896853
  7. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b
  8. 2 0
      git/tests/data/repos/simple_merge/.git/objects/4c/ffe90e0a41ad3f5190079d7c8f036bde29cbe6
  9. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc
  10. 2 0
      git/tests/data/repos/simple_merge/.git/objects/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
  11. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8
  12. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
  13. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870
  14. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349
  15. 2 0
      git/tests/data/repos/simple_merge/.git/objects/ab/64bbdcc51b170d21588e5c5d391ee5c0c96dfd
  16. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/d4/bdad6549dfedf25d3b89d21f506aff575b28a7
  17. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/d8/0c186a03f423a81b39df39dc87fd269736ca86
  18. TEMPAT SAMPAH
      git/tests/data/repos/simple_merge/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
  19. 1 0
      git/tests/data/repos/simple_merge/.git/refs/heads/master
  20. 1 0
      git/tests/data/repos/simple_merge/a
  21. 1 0
      git/tests/data/repos/simple_merge/b
  22. 0 0
      git/tests/data/repos/simple_merge/d
  23. 0 0
      git/tests/data/repos/simple_merge/e
  24. 14 6
      git/tests/test_objects.py
  25. 18 0
      git/tests/test_repository.py

+ 33 - 6
git/objects.py

@@ -46,10 +46,7 @@ def sha_to_hex(sha):
   """Takes a string and returns the hex of the sha within"""
   hexsha = ''
   for c in sha:
-    if ord(c) < 16:
-      hexsha += "0%x" % ord(c)
-    else:
-      hexsha += "%x" % ord(c)
+    hexsha += "%02x" % ord(c)
   assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % \
          len(hexsha)
   return hexsha
@@ -149,6 +146,14 @@ class ShaFile(object):
     ressha.update(self._text)
     return ressha
 
+  def __eq__(self, other):
+    """Return true id the sha of the two objects match.
+
+    The __le__ etc methods aren't overriden as they make no sense,
+    certainly at this level.
+    """
+    return self.sha().digest() == other.sha().digest()
+
 class Blob(ShaFile):
   """A Git Blob object."""
 
@@ -259,9 +264,14 @@ class Commit(ShaFile):
            "%s must be followed by space not %s" % (author_id, text[count])
       count += 1
       self._author = ''
-      while text[count] != '\n':
+      while text[count] != '>':
+        assert text[count] != '\n', "Malformed author information"
         self._author += text[count]
         count += 1
+      self._author += text[count]
+      count += 1
+      while text[count] != '\n':
+        count += 1
       count += 1
     self._committer = None
     if text[count:].startswith(committer_id):
@@ -270,12 +280,22 @@ class Commit(ShaFile):
            "%s must be followed by space not %s" % (committer_id, text[count])
       count += 1
       self._committer = ''
-      while text[count] != '\n':
+      while text[count] != '>':
+        assert text[count] != '\n', "Malformed committer information"
         self._committer += text[count]
         count += 1
+      self._committer += text[count]
+      count += 1
+      assert text[count] == ' ', "Invalid commit object, " \
+           "commiter information must be followed by space not %s" % text[count]
+      count += 1
+      self._commit_time = int(text[count:count+10])
+      while text[count] != '\n':
+        count += 1
       count += 1
     assert text[count] == '\n', "There must be a new line after the headers"
     count += 1
+    # XXX: There can be an encoding field.
     self._message = text[count:]
 
   def tree(self):
@@ -298,6 +318,13 @@ class Commit(ShaFile):
     """Returns the commit message"""
     return self._message
 
+  def commit_time(self):
+    """Returns the timestamp of the commit.
+    
+    Returns it as the number of seconds since the epoch.
+    """
+    return self._commit_time
+
 type_map = {
   blob_id : Blob,
   tree_id : Tree,

+ 31 - 0
git/repository.py

@@ -84,3 +84,34 @@ class Repository(object):
   def get_blob(self, sha):
     return self._get_object(sha, Blob)
 
+  def revision_history(self, head):
+    """Returns a list of the commits reachable from head.
+
+    Returns a list of commit objects. the first of which will be the commit
+    of head, then following theat will be the parents.
+
+    Raises NotCommitError if any no commits are referenced, including if the
+    head parameter isn't the sha of a commit.
+
+    XXX: work out how to handle merges.
+    """
+    commit = self.get_commit(head)
+    history = [commit]
+    parents = commit.parents()
+    parent_commits = [[]] * len(parents)
+    i = 0
+    for parent in parents:
+      parent_commits[i] = self.revision_history(parent)
+      i += 1
+    for commit_list in parent_commits:
+      for parent_commit in commit_list:
+        if parent_commit in history:
+          continue
+        j = 0
+        for main_commit in history:
+          if main_commit.commit_time() < parent_commit.commit_time():
+            break
+          j += 1
+        history.insert(j, parent_commit)
+    return history
+

+ 1 - 0
git/tests/data/repos/simple_merge/.git/HEAD

@@ -0,0 +1 @@
+ref: refs/heads/master

TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/index


+ 2 - 0
git/tests/data/repos/simple_merge/.git/objects/0d/89f20333fbb1d2f3a94da77f4981373d8f4310

@@ -0,0 +1,2 @@
+x°╔█K
+б@]о)z░Ы53в╝в==-&░▓√ЮМ█Я╬e=╗Бi:вЗ┐н"пZ≈=╒ГH)╒╟╖░╘r┬Х┼■З░°╛║≈>╖╝4хwY╢╪Ат╞M∙ряxИ©|щЯq=┐s)&л6DhЛ6ц{YЕ┤╧m/ЭLФXg?╚

TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/1b/6318f651a534b38f9c7aedeebbd56c1e896853


TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b


+ 2 - 0
git/tests/data/repos/simple_merge/.git/objects/4c/ffe90e0a41ad3f5190079d7c8f036bde29cbe6

@@ -0,0 +1,2 @@
+xœ¥�K
+1D]ç½�L~=Á�pÝ�î ÂŒ2FoïïÔæ-Uå:Žç.„U›UAIŠ!KU©.Šç!‹ëk´‰j�Ù
„æF³N
’*RÐ{QofÄwSQ[ÙÖÌ!”Œ)«¡G;]g8Шw8ê½ñ6—¥å3M»Ë‡/_¼ž´m¡ï1 ú”töS¾K›þã0ûß]ži*'pæúCOÒ

TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc


+ 2 - 0
git/tests/data/repos/simple_merge/.git/objects/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e

@@ -0,0 +1,2 @@
+xœ¥ŽË
+Â0E]ç+f/ÈäÑN"®]®g’	¶Ð*5"þ½¯Oð.Ï…ÃÉ—i¸€«¶¨a¶	UB¬E¤ÃRrŸ[’²P´\©öæÊ‹Î
°ÄTz靖-®zN¡0Q
)ZO¾Ä¼EÃ÷v¾,pàIopÒ[“'lÇǺ¨<ïÇ|ñfÖ¶k)P—œGXã{&K›þã0Ç÷?“y´MQ

TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8


TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6


TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870


TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349


+ 2 - 0
git/tests/data/repos/simple_merge/.git/objects/ab/64bbdcc51b170d21588e5c5d391ee5c0c96dfd

@@ -0,0 +1,2 @@
+xœ¥�M
+Â0…]ç³$cÒL"‚;/à:3™bm¥�oïß|ð6ßâã=™†¡«°÷~SgU@cÌ�óìb›„²UæÒA�)ÄÆ™{žu¬lÉR„œ+êld&z7ˆÚ–m›Ø{I’šü¨·i†Kt�«.•Ÿpè×mQîòxê?|ýâݨõˆä‰!ÂÖ¾cä»´ê?sþÝå9�r4/mËO+

TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/d4/bdad6549dfedf25d3b89d21f506aff575b28a7


TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/d8/0c186a03f423a81b39df39dc87fd269736ca86


TEMPAT SAMPAH
git/tests/data/repos/simple_merge/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391


+ 1 - 0
git/tests/data/repos/simple_merge/.git/refs/heads/master

@@ -0,0 +1 @@
+5dac377bdded4c9aeb8dff595f0faeebcc8498cc

+ 1 - 0
git/tests/data/repos/simple_merge/a

@@ -0,0 +1 @@
+test 1

+ 1 - 0
git/tests/data/repos/simple_merge/b

@@ -0,0 +1 @@
+test 2

+ 0 - 0
git/tests/data/repos/simple_merge/d


+ 0 - 0
git/tests/data/repos/simple_merge/e


+ 14 - 6
git/tests/test_objects.py

@@ -69,6 +69,11 @@ class BlobReadTests(unittest.TestCase):
     self.assertEqual(b.text(), string)
     self.assertEqual(b.sha().hexdigest(), c_sha)
 
+  def test_eq(self):
+    blob1 = self.get_blob(a_sha)
+    blob2 = self.get_blob(a_sha)
+    self.assertEqual(blob1, blob2)
+
   def test_read_tree_from_file(self):
     t = self.get_tree(tree_sha)
     self.assertEqual(t.entries()[0], (33188, 'a', a_sha))
@@ -80,9 +85,10 @@ class BlobReadTests(unittest.TestCase):
     self.assertEqual(c.tree(), tree_sha)
     self.assertEqual(c.parents(), ['0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
     self.assertEqual(c.author(),
-        'James Westby <jw+debian@jameswestby.net> 1174759230 +0000')
+        'James Westby <jw+debian@jameswestby.net>')
     self.assertEqual(c.committer(),
-        'James Westby <jw+debian@jameswestby.net> 1174759230 +0000')
+        'James Westby <jw+debian@jameswestby.net>')
+    self.assertEqual(c.commit_time(), 1174759230)
     self.assertEqual(c.message(), 'Test commit\n')
 
   def test_read_commit_no_parents(self):
@@ -91,9 +97,10 @@ class BlobReadTests(unittest.TestCase):
     self.assertEqual(c.tree(), '90182552c4a85a45ec2a835cadc3451bebdfe870')
     self.assertEqual(c.parents(), [])
     self.assertEqual(c.author(),
-        'James Westby <jw+debian@jameswestby.net> 1174758034 +0000')
+        'James Westby <jw+debian@jameswestby.net>')
     self.assertEqual(c.committer(),
-        'James Westby <jw+debian@jameswestby.net> 1174758034 +0000')
+        'James Westby <jw+debian@jameswestby.net>')
+    self.assertEqual(c.commit_time(), 1174758034)
     self.assertEqual(c.message(), 'Test commit\n')
 
   def test_read_commit_two_parents(self):
@@ -103,8 +110,9 @@ class BlobReadTests(unittest.TestCase):
     self.assertEqual(c.parents(), ['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
                                    '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'])
     self.assertEqual(c.author(),
-        'James Westby <jw+debian@jameswestby.net> 1174773719 +0000')
+        'James Westby <jw+debian@jameswestby.net>')
     self.assertEqual(c.committer(),
-        'James Westby <jw+debian@jameswestby.net> 1174773719 +0000')
+        'James Westby <jw+debian@jameswestby.net>')
+    self.assertEqual(c.commit_time(), 1174773719)
     self.assertEqual(c.message(), 'Merge ../b\n')
 

+ 18 - 0
git/tests/test_repository.py

@@ -90,3 +90,21 @@ class RepositoryTests(unittest.TestCase):
     r = self.open_repo('a')
     self.assertRaises(NotBlobError, r.get_blob, r.head())
 
+  def test_linear_history(self):
+    r = self.open_repo('a')
+    history = r.revision_history(r.head())
+    shas = [c.sha().hexdigest() for c in history]
+    self.assertEqual(shas, [r.head(),
+                            '2a72d929692c41d8554c07f6301757ba18a65d91'])
+
+  def test_merge_history(self):
+    r = self.open_repo('simple_merge')
+    history = r.revision_history(r.head())
+    shas = [c.sha().hexdigest() for c in history]
+    self.assertEqual(shas, ['5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
+                            'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
+                            '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
+                            '60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
+                            '0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
+
+