From 4a534eba97db3c2cfb2926368756fd633d25c056 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Oct 2009 12:24:47 +0200 Subject: Added frame for index implementation and testing --- lib/git/index.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/git/index.py (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py new file mode 100644 index 00000000..1c531712 --- /dev/null +++ b/lib/git/index.py @@ -0,0 +1,17 @@ +# index.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php +""" +Module containing Index implementation, allowing to perform all kinds of index +manipulations such as querying and merging. +""" + +class Index(object): + """ + Implements an Index that can be manipulated using a native implementation in + order to safe git command function calls wherever possible. + + It provides custom merging facilities and to create custom commits. + """ -- cgit v1.2.1 From 50a9920b1bd9e6e8cf452c774c499b0b9014ccef Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Oct 2009 17:04:23 +0200 Subject: Added initial version of the index reading from file - IndexEntry interface is to be improved though, writing needs to be implemented as well --- lib/git/index.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index 1c531712..1e67d6d1 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -7,11 +7,109 @@ Module containing Index implementation, allowing to perform all kinds of index manipulations such as querying and merging. """ +import struct +import binascii +import mmap + +class IndexEntry(tuple): + """ + Allows convenient access to IndexEntry data without completely unpacking it. + + Attributes usully accessed often are cached in the tuple whereas others are + unpacked on demand. + """ class Index(object): """ Implements an Index that can be manipulated using a native implementation in - order to safe git command function calls wherever possible. + order to save git command function calls wherever possible. It provides custom merging facilities and to create custom commits. """ + __slots__ = ( "version", "entries" ) + + def __init__(self, stream = None): + """ + Initialize this Index instance, optionally from the given ``stream`` + + Note + Reading is based on the dulwich project. + """ + self.entries = dict() + self.version = -1 + if stream is not None: + self._read_from_stream(stream) + + def _read_entry(self, stream): + """Return: One entry of the given stream""" + beginoffset = stream.tell() + ctime = struct.unpack(">8s", stream.read(8))[0] + mtime = struct.unpack(">8s", stream.read(8))[0] + (dev, ino, mode, uid, gid, size, sha, flags) = \ + struct.unpack(">LLLLLL20sH", stream.read(20 + 4 * 6 + 2)) + path_size = flags & 0x0fff + path = stream.read(path_size) + + real_size = ((stream.tell() - beginoffset + 8) & ~7) + data = stream.read((beginoffset + real_size) - stream.tell()) + return IndexEntry((path, ctime, mtime, dev, ino, mode, uid, gid, size, + binascii.hexlify(sha), path_size)) + + + def _read_header(self, stream): + """Return tuple(version_long, num_entries) from the given stream""" + type_id = stream.read(4) + if type_id != "DIRC": + raise AssertionError("Invalid index file header: %r" % type_id) + version, num_entries = struct.unpack(">LL", stream.read(4 * 2)) + assert version in (1, 2) + return version, num_entries + + def _read_from_stream(self, stream): + """ + Initialize this instance with index values read from the given stream + """ + self.version, num_entries = self._read_header(stream) + self.entries = dict() + count = 0 + while count < num_entries: + entry = self._read_entry(stream) + self.entries[entry[0]] = entry[1:] + count += 1 + # END for each entry + + @classmethod + def from_file(cls, file_path): + """ + Returns + Index instance as recreated from the given stream. + + ``file_pa `` + File path pointing to git index file + """ + fp = open(file_path, "r") + + # try memory map for speed + stream = fp + try: + stream = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + except Exception: + pass + # END memory mapping + + try: + return cls(stream) + finally: + fp.close() + + def write(self, stream): + """ + Write the current state to the given stream + + ``stream`` + File-like object + + Returns + self + """ + raise NotImplementedError( "TODO" ) -- cgit v1.2.1 From 56823868efddd3bdbc0b624cdc79adc3a2e94a75 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Oct 2009 21:32:00 +0200 Subject: Improved tuple access of EntryIndex class including test, stage and type access still needs to be decoded though --- lib/git/index.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index 1e67d6d1..1ce4183b 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -10,6 +10,7 @@ manipulations such as querying and merging. import struct import binascii import mmap +import objects class IndexEntry(tuple): """ @@ -17,7 +18,61 @@ class IndexEntry(tuple): Attributes usully accessed often are cached in the tuple whereas others are unpacked on demand. + + See the properties for a mapping between names and tuple indices. """ + @property + def path(self): + return self[0] + + @property + def ctime(self): + """ + Returns + Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the + file's creation time + """ + return struct.unpack(">LL", self[1]) + + @property + def mtime(self): + """ + See ctime property, but returns modification time + """ + return struct.unpack(">LL", self[2]) + + @property + def dev(self): + return self[3] + + @property + def inode(self): + return self[4] + + @property + def mode(self): + return self[5] + + @property + def uid(self): + return self[6] + + @property + def gid(self): + return self[7] + + @property + def data_size(self): + return self[8] + + @property + def sha(self): + return self[9] + + @property + def path_size(self): + return self[10] + class Index(object): """ -- cgit v1.2.1 From 152bab7eb64e249122fefab0d5531db1e065f539 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 20 Oct 2009 22:05:51 +0200 Subject: improved IndexEntry type and added test for parsing of the stage --- lib/git/index.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index 1ce4183b..ad581ad4 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -62,7 +62,7 @@ class IndexEntry(tuple): return self[7] @property - def data_size(self): + def size(self): return self[8] @property @@ -70,7 +70,7 @@ class IndexEntry(tuple): return self[9] @property - def path_size(self): + def stage(self): return self[10] @@ -108,7 +108,7 @@ class Index(object): real_size = ((stream.tell() - beginoffset + 8) & ~7) data = stream.read((beginoffset + real_size) - stream.tell()) return IndexEntry((path, ctime, mtime, dev, ino, mode, uid, gid, size, - binascii.hexlify(sha), path_size)) + binascii.hexlify(sha), flags >> 12)) def _read_header(self, stream): @@ -129,7 +129,7 @@ class Index(object): count = 0 while count < num_entries: entry = self._read_entry(stream) - self.entries[entry[0]] = entry[1:] + self.entries[(entry.path,entry.stage)] = entry count += 1 # END for each entry -- cgit v1.2.1 From b9d6494f1075e5370a20e406c3edb102fca12854 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 21 Oct 2009 13:34:43 +0200 Subject: index writing added including simple test, improved docs of IndexEntry --- lib/git/index.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 127 insertions(+), 25 deletions(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index ad581ad4..c137d4da 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -21,10 +21,6 @@ class IndexEntry(tuple): See the properties for a mapping between names and tuple indices. """ - @property - def path(self): - return self[0] - @property def ctime(self): """ @@ -32,45 +28,82 @@ class IndexEntry(tuple): Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the file's creation time """ - return struct.unpack(">LL", self[1]) + return struct.unpack(">LL", self[0]) @property def mtime(self): """ See ctime property, but returns modification time """ - return struct.unpack(">LL", self[2]) + return struct.unpack(">LL", self[1]) @property def dev(self): - return self[3] + """ + Device ID + """ + return self[2] @property def inode(self): - return self[4] + """ + Inode ID + """ + return self[3] @property def mode(self): - return self[5] + """ + File Mode, compatible to stat module constants + """ + return self[4] @property def uid(self): - return self[6] + """ + User ID + """ + return self[5] @property def gid(self): - return self[7] + """ + Group ID + """ + return self[6] @property def size(self): - return self[8] + """ + Uncompressed size of the blob + + Note + Will be 0 if the stage is not 0 ( hence it is an unmerged entry ) + """ + return self[7] @property def sha(self): - return self[9] + """ + hex sha of the blob + """ + return self[8] @property def stage(self): + """ + Stage of the entry, either: + 0 = default stage + 1 = stage before a merge or common ancestor entry in case of a 3 way merge + 2 = stage of entries from the 'left' side of the merge + 3 = stage of entries from the right side of the merge + Note: + For more information, see http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html + """ + return self[9] + + @property + def path(self): return self[10] @@ -80,22 +113,25 @@ class Index(object): order to save git command function calls wherever possible. It provides custom merging facilities and to create custom commits. + + ``Entries`` + The index contains an entries dict whose keys are tuples of """ - __slots__ = ( "version", "entries" ) + __slots__ = ( "version", "entries", "_extension_data" ) + _VERSION = 2 # latest version we support def __init__(self, stream = None): """ Initialize this Index instance, optionally from the given ``stream`` - - Note - Reading is based on the dulwich project. """ self.entries = dict() - self.version = -1 + self.version = self._VERSION + self._extension_data = '' if stream is not None: self._read_from_stream(stream) - def _read_entry(self, stream): + @classmethod + def _read_entry(cls, stream): """Return: One entry of the given stream""" beginoffset = stream.tell() ctime = struct.unpack(">8s", stream.read(8))[0] @@ -107,11 +143,11 @@ class Index(object): real_size = ((stream.tell() - beginoffset + 8) & ~7) data = stream.read((beginoffset + real_size) - stream.tell()) - return IndexEntry((path, ctime, mtime, dev, ino, mode, uid, gid, size, - binascii.hexlify(sha), flags >> 12)) + return IndexEntry((ctime, mtime, dev, ino, mode, uid, gid, size, + binascii.hexlify(sha), flags >> 12, path)) - - def _read_header(self, stream): + @classmethod + def _read_header(cls, stream): """Return tuple(version_long, num_entries) from the given stream""" type_id = stream.read(4) if type_id != "DIRC": @@ -123,15 +159,20 @@ class Index(object): def _read_from_stream(self, stream): """ Initialize this instance with index values read from the given stream + + Note + We explicitly do not clear the entries dict here to allow for reading + multiple chunks from multiple streams into the same Index instance """ self.version, num_entries = self._read_header(stream) - self.entries = dict() count = 0 while count < num_entries: entry = self._read_entry(stream) self.entries[(entry.path,entry.stage)] = entry count += 1 # END for each entry + # this data chunk is the footer of the index, don't yet know what it is for + self._extension_data = stream.read(~0) @classmethod def from_file(cls, file_path): @@ -141,6 +182,9 @@ class Index(object): ``file_pa `` File path pointing to git index file + + Note + Reading is based on the dulwich project. """ fp = open(file_path, "r") @@ -157,6 +201,49 @@ class Index(object): finally: fp.close() + + @classmethod + def to_file(cls, index, file_path): + """ + Write the index data to the given file path. + + ``index`` + Index you wish to write. + + ``file_path`` + Path at which to write the index data. Please note that missing directories + will lead to an exception to be thrown. + + Raise + IOError if the file could not be written + """ + fp = open(file_path, "w") + try: + return index.write(fp) + finally: + fp.close() + # END exception handling + + + @classmethod + def _write_cache_entry(cls, stream, entry): + """ + Write an IndexEntry to a stream + """ + beginoffset = stream.tell() + stream.write(entry[0]) # ctime + stream.write(entry[1]) # mtime + path = entry[10] + plen = len(path) & 0x0fff # path length + assert plen == len(path), "Path %s too long to fit into index" % entry[10] + flags = plen | (entry[9] << 12)# stage and path length are 2 byte flags + stream.write(struct.pack(">LLLLLL20sH", entry[2], entry[3], entry[4], + entry[5], entry[6], entry[7], binascii.unhexlify(entry[8]), flags)) + stream.write(path) + real_size = ((stream.tell() - beginoffset + 8) & ~7) + stream.write("\0" * ((beginoffset + real_size) - stream.tell())) + + def write(self, stream): """ Write the current state to the given stream @@ -166,5 +253,20 @@ class Index(object): Returns self + + Note + Index writing based on the dulwich implementation """ - raise NotImplementedError( "TODO" ) + # header + stream.write("DIRC") + stream.write(struct.pack(">LL", self.version, len(self.entries))) + + # body + entries_sorted = self.entries.values() + entries_sorted.sort(key=lambda e: (e[10], e[9])) # use path/stage as sort key + for entry in entries_sorted: + self._write_cache_entry(stream, entry) + # END for each entry + # write extension_data which we currently cannot interprete + stream.write(self._extension_data) + -- cgit v1.2.1 From babf5765da3e328cc1060cb9b37fbdeb6fd58350 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 21 Oct 2009 16:52:25 +0200 Subject: Initial version of merge including tests for one-way, two-way and tree-way merge --- lib/git/index.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 3 deletions(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index c137d4da..6b51c5c7 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -11,6 +11,8 @@ import struct import binascii import mmap import objects +import tempfile +import os class IndexEntry(tuple): """ @@ -115,7 +117,8 @@ class Index(object): It provides custom merging facilities and to create custom commits. ``Entries`` - The index contains an entries dict whose keys are tuples of + The index contains an entries dict whose keys are tuples of type IndexEntry + to facilitate access. """ __slots__ = ( "version", "entries", "_extension_data" ) _VERSION = 2 # latest version we support @@ -243,7 +246,6 @@ class Index(object): real_size = ((stream.tell() - beginoffset + 8) & ~7) stream.write("\0" * ((beginoffset + real_size) - stream.tell())) - def write(self, stream): """ Write the current state to the given stream @@ -269,4 +271,77 @@ class Index(object): # END for each entry # write extension_data which we currently cannot interprete stream.write(self._extension_data) - + + + @classmethod + def from_tree(cls, repo, *treeish, **kwargs): + """ + Merge the given treeish revisions into a new index which is returned. + The original index will remain unaltered + + ``repo`` + The repository treeish are located in. + + ``*treeish`` + One, two or three Tree Objects or Commits. The result changes according to the + amoutn of trees. + If 1 Tree is given, it will just be read into a new index + If 2 Trees are given, they will be merged into a new index using a + two way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' + one. + If 3 Trees are given, a 3-way merge will be performed with the first tree + being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' tree, + tree 3 is the 'other' one + + ``**kwargs`` + Additional arguments passed to git-read-tree + + Note: + In the three-way merge case, --aggressive will be specified to automatically + resolve more cases in a commonly correct manner. Specify trivial=True as kwarg + to override that. + """ + if len(treeish) == 0 or len(treeish) > 3: + raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish)) + + arg_list = list() + # ignore that working tree and index possibly are out of date + if len(treeish)>1: + # drop unmerged entries when reading our index and merging + arg_list.append("--reset") + # handle non-trivial cases the way a real merge does + arg_list.append("--aggressive") + # END merge handling + + # tmp file created in git home directory to be sure renaming + # works - /tmp/ dirs could be on another device + tmp_index = tempfile.mktemp('','',repo.path) + arg_list.append("--index-output=%s" % tmp_index) + arg_list.extend(treeish) + + # move current index out of the way - otherwise the merge may fail + # as it considers existing entries. moving it essentially clears the index. + # Unfortunately there is no 'soft' way to do it + cur_index = os.path.join(repo.path, 'index') + moved_index = os.path.join(repo.path, 'index_moved'+tempfile.mktemp('','','')) + try: + os.rename(cur_index, moved_index) + repo.git.read_tree(*arg_list, **kwargs) + index = cls.from_file(tmp_index) + finally: + # put back the original index first ! + if os.path.exists(moved_index): + os.rename(moved_index, cur_index) + if os.path.exists(tmp_index): + os.remove(tmp_index) + # END index merge handling + + return index + + def write_tree(self, stream): + """ + Writes the + """ + raise NotImplementedError("TODO") + + -- cgit v1.2.1 From d97afa24ad1ae453002357e5023f3a116f76fb17 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 21 Oct 2009 18:40:35 +0200 Subject: Improved testing of index against trees, tests succeed with next commit --- lib/git/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index 6b51c5c7..1042d7b8 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -288,7 +288,7 @@ class Index(object): If 1 Tree is given, it will just be read into a new index If 2 Trees are given, they will be merged into a new index using a two way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' - one. + one. It behaves like a fast-forward. If 3 Trees are given, a 3-way merge will be performed with the first tree being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' tree, tree 3 is the 'other' one -- cgit v1.2.1 From 2e68d907022c84392597e05afc22d9fe06bf0927 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 21 Oct 2009 18:45:41 +0200 Subject: tree.traverse: Added prune functionality - previously the predciate did both, pruning and preventing to return items --- lib/git/index.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index 1042d7b8..7481f4ce 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -284,7 +284,7 @@ class Index(object): ``*treeish`` One, two or three Tree Objects or Commits. The result changes according to the - amoutn of trees. + amount of trees. If 1 Tree is given, it will just be read into a new index If 2 Trees are given, they will be merged into a new index using a two way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' @@ -300,6 +300,10 @@ class Index(object): In the three-way merge case, --aggressive will be specified to automatically resolve more cases in a commonly correct manner. Specify trivial=True as kwarg to override that. + + As the underlying git-read-tree command takes into account the current index, + it will be temporarily moved out of the way to assure there are no unsuspected + interferences. """ if len(treeish) == 0 or len(treeish) > 3: raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish)) -- cgit v1.2.1 From 6662422ba52753f8b10bc053aba82bac3f2e1b9c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 21 Oct 2009 21:25:52 +0200 Subject: index.iter_blobs method added including tests ( which have been improved generally for more coverage ) --- lib/git/index.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 8 deletions(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index 7481f4ce..e5b2d009 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -13,6 +13,8 @@ import mmap import objects import tempfile import os +import stat +from git.objects import Blob class IndexEntry(tuple): """ @@ -114,19 +116,24 @@ class Index(object): Implements an Index that can be manipulated using a native implementation in order to save git command function calls wherever possible. - It provides custom merging facilities and to create custom commits. + It provides custom merging facilities allowing to merge without actually changing + your index or your working tree. This way you can perform own test-merges based + on the index only without having to deal with the working copy. This is useful + in case of partial working trees. ``Entries`` The index contains an entries dict whose keys are tuples of type IndexEntry to facilitate access. """ - __slots__ = ( "version", "entries", "_extension_data" ) + __slots__ = ( "repo", "version", "entries", "_extension_data" ) _VERSION = 2 # latest version we support + S_IFGITLINK = 0160000 - def __init__(self, stream = None): + def __init__(self, repo, stream = None): """ Initialize this Index instance, optionally from the given ``stream`` """ + self.repo = repo self.entries = dict() self.version = self._VERSION self._extension_data = '' @@ -178,11 +185,14 @@ class Index(object): self._extension_data = stream.read(~0) @classmethod - def from_file(cls, file_path): + def from_file(cls, repo, file_path): """ Returns Index instance as recreated from the given stream. - + + ``repo`` + Repository the index is related to + ``file_pa `` File path pointing to git index file @@ -200,7 +210,7 @@ class Index(object): # END memory mapping try: - return cls(stream) + return cls(repo, stream) finally: fp.close() @@ -331,7 +341,7 @@ class Index(object): try: os.rename(cur_index, moved_index) repo.git.read_tree(*arg_list, **kwargs) - index = cls.from_file(tmp_index) + index = cls.from_file(repo, tmp_index) finally: # put back the original index first ! if os.path.exists(moved_index): @@ -341,7 +351,41 @@ class Index(object): # END index merge handling return index - + + @classmethod + def _index_mode_to_tree_index_mode(cls, index_mode): + """Cleanup a index_mode value. + This will return a index_mode that can be stored in a tree object. + ``index_mode`` + Index_mode to clean up. + """ + if stat.S_ISLNK(index_mode): + return stat.S_IFLNK + elif stat.S_ISDIR(index_mode): + return stat.S_IFDIR + elif stat.S_IFMT(index_mode) == cls.S_IFGITLINK: + return cls.S_IFGITLINK + ret = stat.S_IFREG | 0644 + ret |= (index_mode & 0111) + return ret + + def iter_blobs(self, predicate = lambda t: True): + """ + Returns + Iterator yielding tuples of Blob objects and stages, tuple(stage, Blob) + + ``predicate`` + Function(t) returning True if tuple(stage, Blob) should be yielded by the + iterator + """ + for entry in self.entries.itervalues(): + mode = self._index_mode_to_tree_index_mode(entry.mode) + blob = Blob(self.repo, entry.sha, mode, entry.path) + output = (entry.stage, blob) + if predicate(output): + yield output + # END for each entry + def write_tree(self, stream): """ Writes the -- cgit v1.2.1 From 7b50af0a20bcc7280940ce07593007d17c5acabd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 21 Oct 2009 23:11:40 +0200 Subject: index: Added write_tree method including test --- lib/git/index.py | 53 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index e5b2d009..5bf654fe 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -14,7 +14,24 @@ import objects import tempfile import os import stat -from git.objects import Blob +from git.objects import Blob, Tree + +class _TemporaryFileSwap(object): + """ + Utility class moving a file to a temporary location within the same directory + and moving it back on to where on object deletion. + """ + __slots__ = ("file_path", "tmp_file_path") + + def __init__(self, file_path): + self.file_path = file_path + self.tmp_file_path = self.file_path + tempfile.mktemp('','','') + os.rename(self.file_path, self.tmp_file_path) + + def __del__(self): + if os.path.isfile(self.tmp_file_path): + os.rename(self.tmp_file_path, self.file_path) + class IndexEntry(tuple): """ @@ -335,17 +352,13 @@ class Index(object): # move current index out of the way - otherwise the merge may fail # as it considers existing entries. moving it essentially clears the index. - # Unfortunately there is no 'soft' way to do it - cur_index = os.path.join(repo.path, 'index') - moved_index = os.path.join(repo.path, 'index_moved'+tempfile.mktemp('','','')) + # Unfortunately there is no 'soft' way to do it. + # The _TemporaryFileSwap assure the original file get put back + index_handler = _TemporaryFileSwap(os.path.join(repo.path, 'index')) try: - os.rename(cur_index, moved_index) repo.git.read_tree(*arg_list, **kwargs) index = cls.from_file(repo, tmp_index) finally: - # put back the original index first ! - if os.path.exists(moved_index): - os.rename(moved_index, cur_index) if os.path.exists(tmp_index): os.remove(tmp_index) # END index merge handling @@ -354,8 +367,10 @@ class Index(object): @classmethod def _index_mode_to_tree_index_mode(cls, index_mode): - """Cleanup a index_mode value. + """ + Cleanup a index_mode value. This will return a index_mode that can be stored in a tree object. + ``index_mode`` Index_mode to clean up. """ @@ -381,15 +396,29 @@ class Index(object): for entry in self.entries.itervalues(): mode = self._index_mode_to_tree_index_mode(entry.mode) blob = Blob(self.repo, entry.sha, mode, entry.path) + blob.size = entry.size output = (entry.stage, blob) if predicate(output): yield output # END for each entry - def write_tree(self, stream): + def write_tree(self): """ - Writes the + Writes the Index in self to a corresponding Tree file into the repository + object database and returns it as corresponding Tree object. + + Returns + Tree object representing this index """ - raise NotImplementedError("TODO") + index_path = os.path.join(self.repo.path, "index") + tmp_index_mover = _TemporaryFileSwap(index_path) + + self.to_file(self, index_path) + tree_sha = self.repo.git.write_tree() + + # remove our index file so that the original index can move back into place + # On linux it will silently overwrite, on windows it won't + os.remove(index_path) + return Tree(self.repo, tree_sha, 0, '') -- cgit v1.2.1 From aa921fee6014ef43bb2740240e9663e614e25662 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 22 Oct 2009 00:32:16 +0200 Subject: Implemented merge/resolve handling , but realized that index writing is not yet working properly as it is sha1 checked as well. This explains what my 20 byte 'extension_data' actually is ;) --- lib/git/index.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index 5bf654fe..d8ba6e18 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -126,6 +126,16 @@ class IndexEntry(tuple): @property def path(self): return self[10] + + + @classmethod + def from_blob(cls, blob): + """ + Returns + Minimal entry resembling the given blob objecft + """ + time = struct.pack(">LL", 0, 0) + return IndexEntry((time, time, 0, 0, blob.mode, 0, 0, blob.size, blob.id, 0, blob.path)) class Index(object): @@ -402,6 +412,61 @@ class Index(object): yield output # END for each entry + def unmerged_blobs(self): + """ + Returns + Iterator yielding dict(path : list( tuple( stage, Blob, ...))), being + a dictionary associating a path in the index with a list containing + stage/blob pairs + + Note: + Blobs that have been removed in one side simply do not exist in the + given stage. I.e. a file removed on the 'other' branch whose entries + are at stage 3 will not have a stage 3 entry. + """ + is_unmerged_blob = lambda t: t[0] != 0 + path_map = dict() + for stage, blob in self.iter_blobs(is_unmerged_blob): + path_map.setdefault(blob.path, list()).append((stage, blob)) + # END for each unmerged blob + + return path_map + + def resolve_blobs(self, iter_blobs): + """ + Resolve the blobs given in blob iterator. This will effectively remove the + index entries of the respective path at all non-null stages and add the given + blob as new stage null blob. + + For each path there may only be one blob, otherwise a ValueError will be raised + claiming the path is already at stage 0. + + Raise + ValueError if one of the blobs already existed at stage 0 + + Returns: + self + """ + for blob in iter_blobs: + stage_null_key = (blob.path, 0) + if stage_null_key in self.entries: + raise ValueError( "Blob %r already at stage 0" % blob ) + # END assert blob is not stage 0 already + + # delete all possible stages + for stage in (1, 2, 3): + try: + del( self.entries[(blob.path, stage)] ) + except KeyError: + pass + # END ignore key errors + # END for each possible stage + + self.entries[stage_null_key] = IndexEntry.from_blob(blob) + # END for each blob + + return self + def write_tree(self): """ Writes the Index in self to a corresponding Tree file into the repository @@ -414,11 +479,16 @@ class Index(object): tmp_index_mover = _TemporaryFileSwap(index_path) self.to_file(self, index_path) - tree_sha = self.repo.git.write_tree() - # remove our index file so that the original index can move back into place - # On linux it will silently overwrite, on windows it won't - os.remove(index_path) + try: + tree_sha = self.repo.git.write_tree() + finally: + # remove our index file so that the original index can move back into place + # On linux it will silently overwrite, on windows it won't + if os.path.isfile(index_path): + os.remove(index_path) + # END remove our own index file beforehand + # END write tree handling return Tree(self.repo, tree_sha, 0, '') -- cgit v1.2.1 From 30d822a468dc909aac5c83d078a59bfc85fc27aa Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 22 Oct 2009 10:15:47 +0200 Subject: index writing now creates a sha on the content making it possible to write valid indices after manually removing or altering entriesgst --- lib/git/index.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'lib/git/index.py') diff --git a/lib/git/index.py b/lib/git/index.py index d8ba6e18..9a55da15 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -15,6 +15,7 @@ import tempfile import os import stat from git.objects import Blob, Tree +from git.utils import SHA1Writer class _TemporaryFileSwap(object): """ @@ -205,11 +206,20 @@ class Index(object): count = 0 while count < num_entries: entry = self._read_entry(stream) - self.entries[(entry.path,entry.stage)] = entry + self.entries[(entry.path, entry.stage)] = entry count += 1 # END for each entry - # this data chunk is the footer of the index, don't yet know what it is for + + # the footer contains extension data and a sha on the content so far + # Keep the extension footer,and verify we have a sha in the end self._extension_data = stream.read(~0) + assert len(self._extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(self._extension_data) + + content_sha = self._extension_data[-20:] + + # truncate the sha in the end as we will dynamically create it anyway + self._extension_data = self._extension_data[:-20] + @classmethod def from_file(cls, repo, file_path): @@ -296,6 +306,8 @@ class Index(object): Note Index writing based on the dulwich implementation """ + stream = SHA1Writer(stream) + # header stream.write("DIRC") stream.write(struct.pack(">LL", self.version, len(self.entries))) @@ -306,9 +318,13 @@ class Index(object): for entry in entries_sorted: self._write_cache_entry(stream, entry) # END for each entry - # write extension_data which we currently cannot interprete + + # write previously cached extensions data stream.write(self._extension_data) + # write the sha over the content + stream.write_sha() + @classmethod def from_tree(cls, repo, *treeish, **kwargs): -- cgit v1.2.1