From ab730cb1d45adb164aeb36f0625cb3d9c9154b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 5 Sep 2014 18:57:08 +0200 Subject: [PATCH] Provide a method to write a tree to an archive Add Repository.write_archive() to write a given tree to an archive. As there are many customisation options, we only provide a method to write to an archive which is created by the user. --- docs/repository.rst | 1 + pygit2/repository.py | 73 ++++++++++++++++++++++++++++++++++++++++++- test/test_archive.py | 74 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 test/test_archive.py diff --git a/docs/repository.rst b/docs/repository.rst index d37664f..ce8af39 100644 --- a/docs/repository.rst +++ b/docs/repository.rst @@ -69,3 +69,4 @@ Below there are some general attributes and methods: .. automethod:: pygit2.Repository.write .. automethod:: pygit2.Repository.reset .. automethod:: pygit2.Repository.state_cleanup +.. automethod:: pygit2.Repository.write_archive diff --git a/pygit2/repository.py b/pygit2/repository.py index 27db17a..37b5362 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -35,6 +35,7 @@ from string import hexdigits from _pygit2 import Repository as _Repository from _pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN from _pygit2 import GIT_CHECKOUT_SAFE_CREATE, GIT_DIFF_NORMAL +from _pygit2 import GIT_FILEMODE_LINK from _pygit2 import Reference, Tree, Commit, Blob from .config import Config @@ -43,7 +44,7 @@ from .ffi import ffi, C from .index import Index from .remote import Remote from .blame import Blame -from .utils import to_bytes, to_str +from .utils import to_bytes, to_str, is_string class Repository(_Repository): @@ -488,3 +489,73 @@ class Repository(_Repository): check_error(err, True) return Index.from_c(self, cindex) + + # + # Utility for writing a tree into an archive + # + def write_archive(self, treeish, archive, timestamp=None): + """Write treeish into an archive + + If no timestamp is provided and 'treeish' is a commit, its committer + timestamp will be used. Otherwise the current time will be used. + + Arguments: + + treeish + The treeish to write. + archive + An archive from the 'tarfile' module + timestamp + Timestamp to use for the files in the archive. + + Example:: + + >>> import tarfile, pygit2 + >>>> with tarfile.open('foo.tar', 'w') as archive: + >>>> repo = pygit2.Repsitory('.') + >>>> repo.write_archive(archive, repo.head.target) + """ + + import tarfile, sys + from time import time + if sys.version_info[0] < 3: + from cStringIO import StringIO + else: + from io import BytesIO as StringIO + + # Try to get a tree form whatever we got + if isinstance(treeish, Tree): + tree = treeish + + if isinstance(treeish, Oid) or is_string(treeish): + treeish = self[treeish] + + # if we don't have a timestamp, try to get it from a commit + if not timestamp: + try: + commit = treeish.peel(Commit) + timestamp = commit.committer.time + except: + pass + + # as a last resort, use the current timestamp + if not timestamp: + timestamp = int(time()) + + tree = treeish.peel(Tree) + + index = Index() + index.read_tree(tree) + + for entry in index: + content = self[entry.id].read_raw() + info = tarfile.TarInfo(entry.path) + info.size = len(content) + info.mtime = timestamp + info.uname = info.gname = 'root' # just because git does this + if entry.mode == GIT_FILEMODE_LINK: + info.type = archive.SYMTYPE + info.linkname = content + info.mode = 0o777 # symlinks get placeholder + + archive.addfile(info, StringIO(content)) diff --git a/test/test_archive.py b/test/test_archive.py new file mode 100644 index 0000000..0785fc5 --- /dev/null +++ b/test/test_archive.py @@ -0,0 +1,74 @@ +# -*- coding: UTF-8 -*- +# +# Copyright 2010-2014 The pygit2 contributors +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, +# as published by the Free Software Foundation. +# +# In addition to the permissions in the GNU General Public License, +# the authors give you unlimited permission to link the compiled +# version of this file into combinations with other programs, +# and to distribute those combinations without any restriction +# coming from the use of this file. (The General Public License +# restrictions do apply in other respects; for example, they cover +# modification of the file, and distribution when not linked into +# a combined executable.) +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +"""Tests for Blame objects.""" + +from __future__ import absolute_import +from __future__ import unicode_literals +import unittest +import pygit2 +from pygit2 import Index, Oid, Tree, Object +import tarfile +import os +from . import utils +from time import time + +TREE_HASH = 'fd937514cb799514d4b81bb24c5fcfeb6472b245' +COMMIT_HASH = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' + +class ArchiveTest(utils.RepoTestCase): + + def check_writing(self, treeish, timestamp=None): + archive = tarfile.open('foo.tar', mode='w') + self.repo.write_archive(treeish, archive) + + index = Index() + if isinstance(treeish, Object): + index.read_tree(treeish.peel(Tree)) + else: + index.read_tree(self.repo[treeish].peel(Tree)) + + self.assertEqual(len(index), len(archive.getmembers())) + + if timestamp: + fileinfo = archive.getmembers()[0] + self.assertEqual(timestamp, fileinfo.mtime) + + archive.close() + self.assertTrue(os.path.isfile('foo.tar')) + os.remove('foo.tar') + + def test_write_tree(self): + self.check_writing(TREE_HASH) + self.check_writing(Oid(hex=TREE_HASH)) + self.check_writing(self.repo[TREE_HASH]) + + def test_write_commit(self): + commit_timestamp = self.repo[COMMIT_HASH].committer.time + self.check_writing(COMMIT_HASH, commit_timestamp) + self.check_writing(Oid(hex=COMMIT_HASH), commit_timestamp) + self.check_writing(self.repo[COMMIT_HASH], commit_timestamp)