def test_emit_commit(self): b = Blob() b.data = b"FOO" t = Tree() t.add(b"foo", stat.S_IFREG | 0o644, b.id) c = Commit() c.committer = c.author = b"Jelmer <jelmer@host>" c.author_time = c.commit_time = 1271345553 c.author_timezone = c.commit_timezone = 0 c.message = b"msg" c.tree = t.id self.store.add_objects([(b, None), (t, None), (c, None)]) self.fastexporter.emit_commit(c, b"refs/heads/master") self.assertEqual( b"""blob mark :1 data 3 FOO commit refs/heads/master mark :2 author Jelmer <jelmer@host> 1271345553 +0000 committer Jelmer <jelmer@host> 1271345553 +0000 data 3 msg M 644 :1 foo """, self.stream.getvalue())
def test_symlink(self): repo_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, repo_dir) with Repo.init(repo_dir) as repo: # Populate repo filed = Blob.from_string(b'file d') filee = Blob.from_string(b'd') tree = Tree() tree[b'c/d'] = (stat.S_IFREG | 0o644, filed.id) tree[b'c/e'] = (stat.S_IFLNK, filee.id) # symlink repo.object_store.add_objects([(o, None) for o in [filed, filee, tree]]) build_index_from_tree(repo.path, repo.index_path(), repo.object_store, tree.id) # Verify index entries index = repo.open_index() # symlink to d epath = os.path.join(repo.path, 'c', 'e') self.assertTrue(os.path.exists(epath)) self.assertReasonableIndexEntry( index[b'c/e'], stat.S_IFLNK, 0 if sys.platform == 'win32' else 1, filee.id) self.assertFileContents(epath, 'd', symlink=True)
def test_object_diff_bin_blob_force(self): f = BytesIO() # Prepare two slightly different PNG headers b1 = Blob.from_string(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" b"\x00\x00\x00\x0d\x49\x48\x44\x52" b"\x00\x00\x01\xd5\x00\x00\x00\x9f" b"\x08\x04\x00\x00\x00\x05\x04\x8b") b2 = Blob.from_string(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" b"\x00\x00\x00\x0d\x49\x48\x44\x52" b"\x00\x00\x01\xd5\x00\x00\x00\x9f" b"\x08\x03\x00\x00\x00\x98\xd3\xb3") store = MemoryObjectStore() store.add_objects([(b1, None), (b2, None)]) write_object_diff(f, store, (b'foo.png', 0o644, b1.id), (b'bar.png', 0o644, b2.id), diff_binary=True) self.assertEqual([ b'diff --git a/foo.png b/bar.png', b'index f73e47d..06364b7 644', b'--- a/foo.png', b'+++ b/bar.png', b'@@ -1,4 +1,4 @@', b' \x89PNG', b' \x1a', b' \x00\x00\x00', b'-IHDR\x00\x00\x01\xd5\x00\x00\x00' b'\x9f\x08\x04\x00\x00\x00\x05\x04\x8b', b'\\ No newline at end of file', b'+IHDR\x00\x00\x01\xd5\x00\x00\x00\x9f' b'\x08\x03\x00\x00\x00\x98\xd3\xb3', b'\\ No newline at end of file' ], f.getvalue().splitlines())
def test_git_dir(self): repo_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, repo_dir) with Repo.init(repo_dir) as repo: # Populate repo filea = Blob.from_string(b'file a') filee = Blob.from_string(b'd') tree = Tree() tree[b'.git/a'] = (stat.S_IFREG | 0o644, filea.id) tree[b'c/e'] = (stat.S_IFREG | 0o644, filee.id) repo.object_store.add_objects([(o, None) for o in [filea, filee, tree]]) build_index_from_tree(repo.path, repo.index_path(), repo.object_store, tree.id) # Verify index entries index = repo.open_index() self.assertEqual(len(index), 1) # filea apath = os.path.join(repo.path, '.git', 'a') self.assertFalse(os.path.exists(apath)) # filee epath = os.path.join(repo.path, 'c', 'e') self.assertTrue(os.path.exists(epath)) self.assertReasonableIndexEntry(index[b'c/e'], stat.S_IFREG | 0o644, 1, filee.id) self.assertFileContents(epath, b'd')
def test_set_chunks(self): b = Blob() b.chunked = [b'te', b'st', b' 5\n'] self.assertEqual(b'test 5\n', b.data) b.chunked = [b'te', b'st', b' 6\n'] self.assertEqual(b'test 6\n', b.as_raw_string()) self.assertEqual(b'test 6\n', bytes(b))
def content(mode, hexsha): if hexsha is None: return Blob.from_string(b'') elif S_ISGITLINK(mode): return Blob.from_string(b"Submodule commit " + hexsha + b"\n") else: return store[hexsha]
def test_simple_delta(self): b1 = Blob.from_string(b"a" * 101) b2 = Blob.from_string(b"a" * 100) delta = create_delta(b1.as_raw_string(), b2.as_raw_string()) self.assertEqual( [(b1.type_num, b1.sha().digest(), None, b1.as_raw_string()), (b2.type_num, b2.sha().digest(), b1.sha().digest(), delta)], list(deltify_pack_objects([(b1, b""), (b2, b"")])))
def test_single_blob(self): blob = Blob() blob.data = b"foo" self.store.add_object(blob) blobs = [(b"bla", blob.id, stat.S_IFREG)] rootid = commit_tree(self.store, blobs) self.assertEqual(rootid, b"1a1e80437220f9312e855c37ac4398b68e5c1d50") self.assertEqual((stat.S_IFREG, blob.id), self.store[rootid][b"bla"]) self.assertEqual(set([rootid, blob.id]), set(self.store._data.keys()))
def test_blob_diff(self): f = BytesIO() write_blob_diff(f, (b"foo.txt", 0o644, Blob.from_string(b"old\nsame\n")), (b"bar.txt", 0o644, Blob.from_string(b"new\nsame\n"))) self.assertEqual([ b"diff --git a/foo.txt b/bar.txt", b"index 3b0f961..a116b51 644", b"--- a/foo.txt", b"+++ b/bar.txt", b"@@ -1,2 +1,2 @@", b"-old", b"+new", b" same" ], f.getvalue().splitlines())
def commit_diff(self, commit): """Return the list of changes introduced by `commit`.""" from my_klaus.utils import guess_is_binary if commit.parents: parent_tree = self[commit.parents[0]].tree else: parent_tree = None summary = {'nfiles': 0, 'nadditions': 0, 'ndeletions': 0} file_changes = [] # the changes in detail dulwich_changes = self.object_store.tree_changes( parent_tree, commit.tree) for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in dulwich_changes: summary['nfiles'] += 1 try: oldblob = self.object_store[ oldsha] if oldsha else Blob.from_string(b'') newblob = self.object_store[ newsha] if newsha else Blob.from_string(b'') except KeyError: # newsha/oldsha are probably related to submodules. # Dulwich will handle that. pass # Check for binary files -- can't show diffs for these if guess_is_binary(newblob) or \ guess_is_binary(oldblob): file_changes.append({ 'is_binary': True, 'old_filename': oldpath or '/dev/null', 'new_filename': newpath or '/dev/null', 'chunks': None }) continue additions, deletions, chunks = render_diff(oldblob.splitlines(), newblob.splitlines()) change = { 'is_binary': False, 'old_filename': oldpath or '/dev/null', 'new_filename': newpath or '/dev/null', 'chunks': chunks, 'additions': additions, 'deletions': deletions, } summary['nadditions'] += additions summary['ndeletions'] += deletions file_changes.append(change) return summary, file_changes
def test_nonempty(self): repo_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, repo_dir) with Repo.init(repo_dir) as repo: # Populate repo filea = Blob.from_string(b'file a') fileb = Blob.from_string(b'file b') filed = Blob.from_string(b'file d') tree = Tree() tree[b'a'] = (stat.S_IFREG | 0o644, filea.id) tree[b'b'] = (stat.S_IFREG | 0o644, fileb.id) tree[b'c/d'] = (stat.S_IFREG | 0o644, filed.id) repo.object_store.add_objects([ (o, None) for o in [filea, fileb, filed, tree] ]) build_index_from_tree(repo.path, repo.index_path(), repo.object_store, tree.id) # Verify index entries index = repo.open_index() self.assertEqual(len(index), 3) # filea apath = os.path.join(repo.path, 'a') self.assertTrue(os.path.exists(apath)) self.assertReasonableIndexEntry(index[b'a'], stat.S_IFREG | 0o644, 6, filea.id) self.assertFileContents(apath, b'file a') # fileb bpath = os.path.join(repo.path, 'b') self.assertTrue(os.path.exists(bpath)) self.assertReasonableIndexEntry(index[b'b'], stat.S_IFREG | 0o644, 6, fileb.id) self.assertFileContents(bpath, b'file b') # filed dpath = os.path.join(repo.path, 'c', 'd') self.assertTrue(os.path.exists(dpath)) self.assertReasonableIndexEntry(index[b'c/d'], stat.S_IFREG | 0o644, 6, filed.id) self.assertFileContents(dpath, b'file d') # Verify no extra files self.assertEqual(['.git', 'a', 'b', 'c'], sorted(os.listdir(repo.path))) self.assertEqual(['d'], sorted(os.listdir(os.path.join(repo.path, 'c'))))
def test_nested(self): blob = Blob() blob.data = b"foo" self.store.add_object(blob) blobs = [(b"bla/bar", blob.id, stat.S_IFREG)] rootid = commit_tree(self.store, blobs) self.assertEqual(rootid, b"d92b959b216ad0d044671981196781b3258fa537") dirid = self.store[rootid][b"bla"][1] self.assertEqual(dirid, b"c1a1deb9788150829579a8b4efa6311e7b638650") self.assertEqual((stat.S_IFDIR, dirid), self.store[rootid][b"bla"]) self.assertEqual((stat.S_IFREG, blob.id), self.store[dirid][b"bar"]) self.assertEqual(set([rootid, dirid, blob.id]), set(self.store._data.keys()))
def test_object_diff_blob(self): f = BytesIO() b1 = Blob.from_string(b"old\nsame\n") b2 = Blob.from_string(b"new\nsame\n") store = MemoryObjectStore() store.add_objects([(b1, None), (b2, None)]) write_object_diff(f, store, (b"foo.txt", 0o644, b1.id), (b"bar.txt", 0o644, b2.id)) self.assertEqual([ b"diff --git a/foo.txt b/bar.txt", b"index 3b0f961..a116b51 644", b"--- a/foo.txt", b"+++ b/bar.txt", b"@@ -1,2 +1,2 @@", b"-old", b"+new", b" same" ], f.getvalue().splitlines())
def test_splitlines(self): for case in [ [], [b'foo\nbar\n'], [b'bl\na', b'blie'], [b'bl\na', b'blie', b'bloe\n'], [b'', b'bl\na', b'blie', b'bloe\n'], [b'', b'', b'', b'bla\n'], [b'', b'', b'', b'bla\n', b''], [b'bl', b'', b'a\naaa'], [b'a\naaa', b'a'], ]: b = Blob() b.chunked = case self.assertEqual(b.data.splitlines(True), b.splitlines())
def test_tree_diff(self): f = BytesIO() store = MemoryObjectStore() added = Blob.from_string(b"add\n") removed = Blob.from_string(b"removed\n") changed1 = Blob.from_string(b"unchanged\nremoved\n") changed2 = Blob.from_string(b"unchanged\nadded\n") unchanged = Blob.from_string(b"unchanged\n") tree1 = Tree() tree1.add(b"removed.txt", 0o644, removed.id) tree1.add(b"changed.txt", 0o644, changed1.id) tree1.add(b"unchanged.txt", 0o644, changed1.id) tree2 = Tree() tree2.add(b"added.txt", 0o644, added.id) tree2.add(b"changed.txt", 0o644, changed2.id) tree2.add(b"unchanged.txt", 0o644, changed1.id) store.add_objects([ (o, None) for o in [tree1, tree2, added, removed, changed1, changed2, unchanged] ]) write_tree_diff(f, store, tree1.id, tree2.id) self.assertEqual([ b'diff --git /dev/null b/added.txt', b'new mode 644', b'index 0000000..76d4bb8 644', b'--- /dev/null', b'+++ b/added.txt', b'@@ -0,0 +1 @@', b'+add', b'diff --git a/changed.txt b/changed.txt', b'index bf84e48..1be2436 644', b'--- a/changed.txt', b'+++ b/changed.txt', b'@@ -1,2 +1,2 @@', b' unchanged', b'-removed', b'+added', b'diff --git a/removed.txt /dev/null', b'deleted mode 644', b'index 2c3f0b3..0000000', b'--- a/removed.txt', b'+++ /dev/null', b'@@ -1 +0,0 @@', b'-removed', ], f.getvalue().splitlines())
def test_full_tree(self): c = self.make_commit(commit_time=30) t = Tree() t.add(b'data-x', 0o644, Blob().id) c.tree = t c1 = Commit() c1.set_raw_string(c.as_raw_string()) self.assertEqual(t.id, c1.tree) self.assertEqual(c.as_raw_string(), c1.as_raw_string())
def test_deltas_work(self): with self.get_pack(pack1_sha) as orig_pack: orig_blob = orig_pack[a_sha] new_blob = Blob() new_blob.data = orig_blob.data + b'x' all_to_pack = list(orig_pack.pack_tuples()) + [(new_blob, None)] pack_path = os.path.join(self._tempdir, 'pack_with_deltas') write_pack(pack_path, all_to_pack, deltify=True) output = run_git_or_fail(['verify-pack', '-v', pack_path]) self.assertEqual(set(x[0].id for x in all_to_pack), _git_verify_pack_object_list(output)) # We specifically made a new blob that should be a delta # against the blob a_sha, so make sure we really got only 3 # non-delta objects: got_non_delta = int(_NON_DELTA_RE.search(output).group('non_delta')) self.assertEqual( 3, got_non_delta, 'Expected 3 non-delta objects, got %d' % got_non_delta)
def commit_handler(self, cmd): """Process a CommitCommand.""" commit = Commit() if cmd.author is not None: author = cmd.author else: author = cmd.committer (author_name, author_email, author_timestamp, author_timezone) = author (committer_name, committer_email, commit_timestamp, commit_timezone) = cmd.committer commit.author = author_name + b" <" + author_email + b">" commit.author_timezone = author_timezone commit.author_time = int(author_timestamp) commit.committer = committer_name + b" <" + committer_email + b">" commit.commit_timezone = commit_timezone commit.commit_time = int(commit_timestamp) commit.message = cmd.message commit.parents = [] if cmd.from_: cmd.from_ = self.lookup_object(cmd.from_) self._reset_base(cmd.from_) for filecmd in cmd.iter_files(): if filecmd.name == b"filemodify": if filecmd.data is not None: blob = Blob.from_string(filecmd.data) self.repo.object_store.add(blob) blob_id = blob.id else: blob_id = self.lookup_object(filecmd.dataref) self._contents[filecmd.path] = (filecmd.mode, blob_id) elif filecmd.name == b"filedelete": del self._contents[filecmd.path] elif filecmd.name == b"filecopy": self._contents[filecmd.dest_path] = self._contents[ filecmd.src_path] elif filecmd.name == b"filerename": self._contents[filecmd.new_path] = self._contents[ filecmd.old_path] del self._contents[filecmd.old_path] elif filecmd.name == b"filedeleteall": self._contents = {} else: raise Exception("Command %s not supported" % filecmd.name) commit.tree = commit_tree( self.repo.object_store, ((path, hexsha, mode) for (path, (mode, hexsha)) in self._contents.items())) if self.last_commit != ZERO_SHA: commit.parents.append(self.last_commit) for merge in cmd.merges: commit.parents.append(self.lookup_object(merge)) self.repo.object_store.add_object(commit) self.repo[cmd.ref] = commit.id self.last_commit = commit.id if cmd.mark: self.markers[cmd.mark] = commit.id
def test_blob_add(self): f = BytesIO() write_blob_diff(f, (None, None, None), (b"bar.txt", 0o644, Blob.from_string(b"new\nsame\n"))) self.assertEqual([ b'diff --git /dev/null b/bar.txt', b'new mode 644', b'index 0000000..a116b51 644', b'--- /dev/null', b'+++ b/bar.txt', b'@@ -0,0 +1,2 @@', b'+new', b'+same' ], f.getvalue().splitlines())
def _get_example_tar_stream(self, *tar_stream_args, **tar_stream_kwargs): store = MemoryObjectStore() b1 = Blob.from_string(b"somedata") store.add_object(b1) t1 = Tree() t1.add(b"somename", 0o100644, b1.id) store.add_object(t1) stream = b''.join( tar_stream(store, t1, *tar_stream_args, **tar_stream_kwargs)) return BytesIO(stream)
def test_blob_remove(self): f = BytesIO() write_blob_diff(f, (b"bar.txt", 0o644, Blob.from_string(b"new\nsame\n")), (None, None, None)) self.assertEqual([ b'diff --git a/bar.txt /dev/null', b'deleted mode 644', b'index a116b51..0000000', b'--- a/bar.txt', b'+++ /dev/null', b'@@ -1,2 +0,0 @@', b'-new', b'-same' ], f.getvalue().splitlines())
def test_object_diff_bin_blob(self): f = BytesIO() # Prepare two slightly different PNG headers b1 = Blob.from_string(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" b"\x00\x00\x00\x0d\x49\x48\x44\x52" b"\x00\x00\x01\xd5\x00\x00\x00\x9f" b"\x08\x04\x00\x00\x00\x05\x04\x8b") b2 = Blob.from_string(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" b"\x00\x00\x00\x0d\x49\x48\x44\x52" b"\x00\x00\x01\xd5\x00\x00\x00\x9f" b"\x08\x03\x00\x00\x00\x98\xd3\xb3") store = MemoryObjectStore() store.add_objects([(b1, None), (b2, None)]) write_object_diff(f, store, (b'foo.png', 0o644, b1.id), (b'bar.png', 0o644, b2.id)) self.assertEqual([ b'diff --git a/foo.png b/bar.png', b'index f73e47d..06364b7 644', b'Binary files a/foo.png and b/bar.png differ' ], f.getvalue().splitlines())
def create_commit(marker=None): blob = Blob.from_string(b'The blob content ' + marker) tree = Tree() tree.add(b"thefile " + marker, 0o100644, blob.id) cmt = Commit() cmt.tree = tree.id cmt.author = cmt.committer = b"John Doe <*****@*****.**>" cmt.message = marker tz = parse_timezone(b'-0200')[0] cmt.commit_time = cmt.author_time = int(time.time()) cmt.commit_timezone = cmt.author_timezone = tz return cmt, tree, blob
def test_delta_medium_object(self): # This tests an object set that will have a copy operation # 2**20 in size. with self.get_pack(pack1_sha) as orig_pack: orig_blob = orig_pack[a_sha] new_blob = Blob() new_blob.data = orig_blob.data + (b'x' * 2**20) new_blob_2 = Blob() new_blob_2.data = new_blob.data + b'y' all_to_pack = list(orig_pack.pack_tuples()) + [(new_blob, None), (new_blob_2, None)] pack_path = os.path.join(self._tempdir, 'pack_with_deltas') write_pack(pack_path, all_to_pack, deltify=True) output = run_git_or_fail(['verify-pack', '-v', pack_path]) self.assertEqual(set(x[0].id for x in all_to_pack), _git_verify_pack_object_list(output)) # We specifically made a new blob that should be a delta # against the blob a_sha, so make sure we really got only 3 # non-delta objects: got_non_delta = int(_NON_DELTA_RE.search(output).group('non_delta')) self.assertEqual( 3, got_non_delta, 'Expected 3 non-delta objects, got %d' % got_non_delta) # We expect one object to have a delta chain length of two # (new_blob_2), so let's verify that actually happens: self.assertIn(b'chain length = 2', output)
def blob_from_path_and_stat(fs_path, st): """Create a blob from a path and a stat object. :param fs_path: Full file system path to file :param st: A stat object :return: A `Blob` object """ assert isinstance(fs_path, bytes) blob = Blob() if not stat.S_ISLNK(st.st_mode): with open(fs_path, 'rb') as f: blob.data = f.read() else: if sys.platform == 'win32' and sys.version_info[0] == 3: # os.readlink on Python3 on Windows requires a unicode string. # TODO(jelmer): Don't assume tree_encoding == fs_encoding tree_encoding = sys.getfilesystemencoding() fs_path = fs_path.decode(tree_encoding) blob.data = os.readlink(fs_path).encode(tree_encoding) else: blob.data = os.readlink(fs_path) return blob
def test_tree_copy_after_update(self): """Check Tree.id is correctly updated when the tree is copied after updated. """ shas = [] tree = Tree() shas.append(tree.id) tree.add(b'data', 0o644, Blob().id) copied = tree.copy() shas.append(tree.id) shas.append(copied.id) self.assertNotIn(shas[0], shas[1:]) self.assertEqual(shas[1], shas[2])
def test_object_diff_remove_blob(self): f = BytesIO() b1 = Blob.from_string(b"new\nsame\n") store = MemoryObjectStore() store.add_object(b1) write_object_diff(f, store, (b"bar.txt", 0o644, b1.id), (None, None, None)) self.assertEqual([ b'diff --git a/bar.txt /dev/null', b'deleted mode 644', b'index a116b51..0000000', b'--- a/bar.txt', b'+++ /dev/null', b'@@ -1,2 +0,0 @@', b'-new', b'-same' ], f.getvalue().splitlines())
def test_object_diff_add_blob(self): f = BytesIO() store = MemoryObjectStore() b2 = Blob.from_string(b"new\nsame\n") store.add_object(b2) write_object_diff(f, store, (None, None, None), (b"bar.txt", 0o644, b2.id)) self.assertEqual([ b'diff --git /dev/null b/bar.txt', b'new mode 644', b'index 0000000..a116b51 644', b'--- /dev/null', b'+++ b/bar.txt', b'@@ -0,0 +1,2 @@', b'+new', b'+same' ], f.getvalue().splitlines())
def test_object_diff_remove_bin_blob(self): f = BytesIO() b1 = Blob.from_string(b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a' b'\x00\x00\x00\x0d\x49\x48\x44\x52' b'\x00\x00\x01\xd5\x00\x00\x00\x9f' b'\x08\x04\x00\x00\x00\x05\x04\x8b') store = MemoryObjectStore() store.add_object(b1) write_object_diff(f, store, (b'foo.png', 0o644, b1.id), (None, None, None)) self.assertEqual([ b'diff --git a/foo.png /dev/null', b'deleted mode 644', b'index f73e47d..0000000', b'Binary files a/foo.png and /dev/null differ' ], f.getvalue().splitlines())
def test_object_diff_add_bin_blob(self): f = BytesIO() b2 = Blob.from_string(b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a' b'\x00\x00\x00\x0d\x49\x48\x44\x52' b'\x00\x00\x01\xd5\x00\x00\x00\x9f' b'\x08\x03\x00\x00\x00\x98\xd3\xb3') store = MemoryObjectStore() store.add_object(b2) write_object_diff(f, store, (None, None, None), (b'bar.png', 0o644, b2.id)) self.assertEqual([ b'diff --git /dev/null b/bar.png', b'new mode 644', b'index 0000000..06364b7 644', b'Binary files /dev/null and b/bar.png differ' ], f.getvalue().splitlines())