浏览代码

Merge addition of dulwich.index.build_index_from_tree from milki.

Jelmer Vernooij 13 年之前
父节点
当前提交
8e6aaa2851
共有 4 个文件被更改,包括 203 次插入16 次删除
  1. 2 0
      NEWS
  2. 48 9
      dulwich/index.py
  3. 19 7
      dulwich/repo.py
  4. 134 0
      dulwich/tests/test_index.py

+ 2 - 0
NEWS

@@ -29,6 +29,8 @@
     using Content-Encoding: gzip.
     (David Blewett, Jelmer Vernooij)
 
+  * Add dulwich.index.build_index_from_tree(). (milki)
+
 0.8.3	2012-01-21
 
  FEATURES

+ 48 - 9
dulwich/index.py

@@ -59,7 +59,7 @@ def pathjoin(*args):
 
 def read_cache_time(f):
     """Read a cache time.
-    
+
     :param f: File-like object to read from
     :return: Tuple with seconds and nanoseconds
     """
@@ -68,7 +68,7 @@ def read_cache_time(f):
 
 def write_cache_time(f, t):
     """Write a cache time.
-    
+
     :param f: File-like object to write to
     :param t: Time to write (as int, float or tuple with secs and nsecs)
     """
@@ -97,7 +97,7 @@ def read_cache_entry(f):
     # Padding:
     real_size = ((f.tell() - beginoffset + 8) & ~7)
     data = f.read((beginoffset + real_size) - f.tell())
-    return (name, ctime, mtime, dev, ino, mode, uid, gid, size, 
+    return (name, ctime, mtime, dev, ino, mode, uid, gid, size,
             sha_to_hex(sha), flags & ~0x0fff)
 
 
@@ -105,7 +105,7 @@ def write_cache_entry(f, entry):
     """Write an index entry to a file.
 
     :param f: File object
-    :param entry: Entry to write, tuple with: 
+    :param entry: Entry to write, tuple with:
         (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
     """
     beginoffset = f.tell()
@@ -132,7 +132,7 @@ def read_index(f):
 
 def read_index_dict(f):
     """Read an index file and return it as a dictionary.
-    
+
     :param f: File object to read from
     """
     ret = {}
@@ -143,7 +143,7 @@ def read_index_dict(f):
 
 def write_index(f, entries):
     """Write an index file.
-    
+
     :param f: File-like object to write to
     :param entries: Iterable over the entries to write
     """
@@ -167,7 +167,7 @@ def cleanup_mode(mode):
     """Cleanup a mode value.
 
     This will return a mode that can be stored in a tree object.
-    
+
     :param mode: Mode to clean up.
     """
     if stat.S_ISLNK(mode):
@@ -186,7 +186,7 @@ class Index(object):
 
     def __init__(self, filename):
         """Open an index file.
-        
+
         :param filename: Path to the index file
         """
         self._filename = filename
@@ -226,7 +226,7 @@ class Index(object):
 
     def __getitem__(self, name):
         """Retrieve entry by relative path.
-        
+
         :return: tuple with (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
         """
         return self._byname[name]
@@ -302,7 +302,9 @@ def commit_tree(object_store, blobs):
     :param blobs: Iterable over blob path, sha, mode entries
     :return: SHA1 of the created tree.
     """
+
     trees = {"": {}}
+
     def add_tree(path):
         if path in trees:
             return trees[path]
@@ -387,3 +389,40 @@ def index_entry_from_stat(stat_val, hex_sha, flags, mode=None):
     return (stat_val.st_ctime, stat_val.st_mtime, stat_val.st_dev,
             stat_val.st_ino, mode, stat_val.st_uid,
             stat_val.st_gid, stat_val.st_size, hex_sha, flags)
+
+
+def build_index_from_tree(prefix, index_path, object_store, tree_id):
+    """Generate and materialize index from a tree
+
+    :param tree_id: Tree to materialize
+    :param prefix: Target dir for materialized index files
+    :param index_path: Target path for generated index
+    :param object_store: Non-empty object store holding tree contents
+
+    :note:: existing index is wiped and contents are not merged
+        in a working dir. Suiteable only for fresh clones.
+    """
+
+    index = Index(index_path)
+
+    for entry in object_store.iter_tree_contents(tree_id):
+        full_path = os.path.join(prefix, entry.path)
+
+        if not os.path.exists(os.path.dirname(full_path)):
+            os.makedirs(os.path.dirname(full_path))
+
+        # FIXME: Merge new index into working tree
+        if stat.S_ISLNK(entry.mode):
+            os.symlink(object_store[entry.sha].as_raw_string(), full_path)
+        else:
+            with open(full_path, 'wb') as file:
+                # Write out file
+                file.write(object_store[entry.sha].as_raw_string())
+
+            os.chmod(full_path, entry.mode)
+
+        # Add file to index
+        st = os.lstat(full_path)
+        index[entry.path] = index_entry_from_stat(st, entry.sha, 0)
+
+    index.write()

+ 19 - 7
dulwich/repo.py

@@ -22,7 +22,7 @@
 """Repository access.
 
 This module contains the base class for git repositories
-(BaseRepo) and an implementation which uses a repository on 
+(BaseRepo) and an implementation which uses a repository on
 local disk (Repo).
 
 """
@@ -201,7 +201,7 @@ class RefsContainer(object):
             try:
                 ret[key] = self[("%s/%s" % (base, key)).strip("/")]
             except KeyError:
-                continue # Unable to resolve
+                continue  # Unable to resolve
 
         return ret
 
@@ -553,7 +553,7 @@ class DiskRefsContainer(RefsContainer):
                     return header + iter(f).next().rstrip("\r\n")
                 else:
                     # Read only the first 40 bytes
-                    return header + f.read(40-len(SYMREF))
+                    return header + f.read(40 - len(SYMREF))
             finally:
                 f.close()
         except IOError, e:
@@ -635,7 +635,7 @@ class DiskRefsContainer(RefsContainer):
                     f.abort()
                     raise
             try:
-                f.write(new_ref+"\n")
+                f.write(new_ref + "\n")
             except (OSError, IOError):
                 f.abort()
                 raise
@@ -668,7 +668,7 @@ class DiskRefsContainer(RefsContainer):
                 f.abort()
                 return False
             try:
-                f.write(ref+"\n")
+                f.write(ref + "\n")
             except (OSError, IOError):
                 f.abort()
                 raise
@@ -1334,7 +1334,7 @@ class Repo(BaseRepo):
                 try:
                     del index[path]
                 except KeyError:
-                    pass # already removed
+                    pass  # already removed
             else:
                 blob = Blob()
                 f = open(full_path, 'rb')
@@ -1363,7 +1363,7 @@ class Repo(BaseRepo):
             target = self.init_bare(target_path)
         self.fetch(target)
         target.refs.import_refs(
-            'refs/remotes/'+origin, self.refs.as_dict('refs/heads'))
+            'refs/remotes/' + origin, self.refs.as_dict('refs/heads'))
         target.refs.import_refs(
             'refs/tags', self.refs.as_dict('refs/tags'))
         try:
@@ -1372,6 +1372,18 @@ class Repo(BaseRepo):
                 self.refs['refs/heads/master'])
         except KeyError:
             pass
+
+        # Update target head
+        head, head_sha = self.refs._follow('HEAD')
+        target.refs.set_symbolic_ref('HEAD', head)
+        target['HEAD'] = head_sha
+
+        if not bare:
+            # Checkout HEAD to target dir
+            from dulwich.index import build_index_from_tree
+            build_index_from_tree(target.path, target.index_path(),
+                    target.object_store, target['HEAD'].tree)
+
         return target
 
     def __repr__(self):

+ 134 - 0
dulwich/tests/test_index.py

@@ -30,6 +30,7 @@ import tempfile
 
 from dulwich.index import (
     Index,
+    build_index_from_tree,
     cleanup_mode,
     commit_tree,
     index_entry_from_stat,
@@ -42,7 +43,9 @@ from dulwich.object_store import (
     )
 from dulwich.objects import (
     Blob,
+    Tree,
     )
+from dulwich.repo import Repo
 from dulwich.tests import TestCase
 
 
@@ -208,3 +211,134 @@ class IndexEntryFromStatTests(TestCase):
             12288,
             '2222222222222222222222222222222222222222',
             0))
+
+
+class BuildIndexTests(TestCase):
+
+    def assertReasonableIndexEntry(self, index_entry, values):
+        delta = 1000000
+        self.assertEquals(index_entry[0], index_entry[1])  # ctime and atime
+        self.assertTrue(index_entry[0] > values[0] - delta)
+        self.assertEquals(index_entry[4], values[4])  # mode
+        self.assertEquals(index_entry[5], values[5])  # uid
+        self.assertTrue(index_entry[6] in values[6])  # gid
+        self.assertEquals(index_entry[7], values[7])  # filesize
+        self.assertEquals(index_entry[8], values[8])  # sha
+
+    def assertFileContents(self, path, contents, symlink=False):
+        if symlink:
+            self.assertEquals(os.readlink(path), contents)
+        else:
+            f = open(path, 'rb')
+            try:
+                self.assertEquals(f.read(), contents)
+            finally:
+                f.close()
+
+    def test_empty(self):
+        repo_dir = tempfile.mkdtemp()
+        repo = Repo.init(repo_dir)
+        self.addCleanup(shutil.rmtree, repo_dir)
+
+        tree = Tree()
+        repo.object_store.add_object(tree)
+
+        build_index_from_tree(repo.path, repo.index_path(),
+                repo.object_store, tree.id)
+
+        # Verify index entries
+        index = repo.open_index()
+        self.assertEquals(len(index), 0)
+
+        # Verify no files
+        self.assertEquals(['.git'], os.listdir(repo.path))
+
+    def test_nonempty(self):
+        if os.name != 'posix':
+            self.skip("test depends on POSIX shell")
+
+        repo_dir = tempfile.mkdtemp()
+        repo = Repo.init(repo_dir)
+        self.addCleanup(shutil.rmtree, repo_dir)
+
+        # Populate repo
+        filea = Blob.from_string('file a')
+        fileb = Blob.from_string('file b')
+        filed = Blob.from_string('file d')
+        filee = Blob.from_string('d')
+
+        tree = Tree()
+        tree['a'] = (stat.S_IFREG | 0644, filea.id)
+        tree['b'] = (stat.S_IFREG | 0644, fileb.id)
+        tree['c/d'] = (stat.S_IFREG | 0644, filed.id)
+        tree['c/e'] = (stat.S_IFLNK, filee.id)  # symlink
+
+        repo.object_store.add_objects([(o, None)
+            for o in [filea, fileb, filed, filee, tree]])
+
+        build_index_from_tree(repo.path, repo.index_path(),
+                repo.object_store, tree.id)
+
+        # Verify index entries
+        import time
+        ctime = time.time()
+        index = repo.open_index()
+        self.assertEquals(len(index), 4)
+
+        # filea
+        apath = os.path.join(repo.path, 'a')
+        self.assertTrue(os.path.exists(apath))
+        self.assertReasonableIndexEntry(index['a'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFREG | 0644,
+            os.getuid(), os.getgroups(),
+            6,
+            filea.id,
+            None))
+        self.assertFileContents(apath, 'file a')
+
+        # fileb
+        bpath = os.path.join(repo.path, 'b')
+        self.assertTrue(os.path.exists(bpath))
+        self.assertReasonableIndexEntry(index['b'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFREG | 0644,
+            os.getuid(), os.getgroups(),
+            6,
+            fileb.id,
+            None))
+        self.assertFileContents(bpath, 'file b')
+
+        # filed
+        dpath = os.path.join(repo.path, 'c', 'd')
+        self.assertTrue(os.path.exists(dpath))
+        self.assertReasonableIndexEntry(index['c/d'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFREG | 0644,
+            os.getuid(), os.getgroups(),
+            6,
+            filed.id,
+            None))
+        self.assertFileContents(dpath, 'file d')
+
+        # symlink to d
+        epath = os.path.join(repo.path, 'c', 'e')
+        self.assertTrue(os.path.exists(epath))
+        self.assertReasonableIndexEntry(index['c/e'], (
+            ctime, ctime,
+            None, None,
+            stat.S_IFLNK,
+            os.getuid(), os.getgroups(),
+            1,
+            filee.id,
+            None))
+        self.assertFileContents(epath, 'd', symlink=True)
+
+        # Verify no extra files
+        self.assertEquals(['.git', 'a', 'b', 'c'],
+            sorted(os.listdir(repo.path)))
+        self.assertEquals(['d', 'e'], 
+            sorted(os.listdir(os.path.join(repo.path, 'c'))))