Skip to content

Commit 5d775fb

Browse files
authored
Merge pull request #1048 from jeremywestwood/packbuilder
Add support for PackBuilder methods
2 parents 16fae6e + 8b8a0b5 commit 5d775fb

File tree

7 files changed

+243
-1
lines changed

7 files changed

+243
-1
lines changed

pygit2/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from .index import Index, IndexEntry
4141
from .remote import Remote
4242
from .repository import Repository
43+
from .packbuilder import PackBuilder
4344
from .settings import Settings
4445
from .submodule import Submodule
4546
from .utils import to_bytes, to_str

pygit2/_run.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@
6161
'strarray.h',
6262
'diff.h',
6363
'checkout.h',
64-
'pack.h',
6564
'transport.h',
6665
'proxy.h',
6766
'indexer.h',
67+
'pack.h',
6868
'remote.h',
6969
'clone.h',
7070
'common.h',

pygit2/decl/pack.h

+13
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,16 @@ typedef int (*git_packbuilder_progress)(
33
uint32_t current,
44
uint32_t total,
55
void *payload);
6+
7+
int git_packbuilder_new(git_packbuilder **out, git_repository *repo);
8+
void git_packbuilder_free(git_packbuilder *pb);
9+
10+
int git_packbuilder_insert(git_packbuilder *pb, const git_oid *id, const char *name);
11+
int git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name);
12+
13+
size_t git_packbuilder_object_count(git_packbuilder *pb);
14+
15+
int git_packbuilder_write(git_packbuilder *pb, const char *path, unsigned int mode, git_indexer_progress_cb progress_cb, void *progress_cb_payload);
16+
uint32_t git_packbuilder_written(git_packbuilder *pb);
17+
18+
unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n);

pygit2/decl/types.h

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ typedef struct git_repository git_repository;
1010
typedef struct git_submodule git_submodule;
1111
typedef struct git_transport git_transport;
1212
typedef struct git_tree git_tree;
13+
typedef struct git_packbuilder git_packbuilder;
1314

1415
typedef int64_t git_off_t;
1516
typedef int64_t git_time_t;

pygit2/packbuilder.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright 2010-2020 The pygit2 contributors
2+
#
3+
# This file is free software; you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License, version 2,
5+
# as published by the Free Software Foundation.
6+
#
7+
# In addition to the permissions in the GNU General Public License,
8+
# the authors give you unlimited permission to link the compiled
9+
# version of this file into combinations with other programs,
10+
# and to distribute those combinations without any restriction
11+
# coming from the use of this file. (The General Public License
12+
# restrictions do apply in other respects; for example, they cover
13+
# modification of the file, and distribution when not linked into
14+
# a combined executable.)
15+
#
16+
# This file is distributed in the hope that it will be useful, but
17+
# WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
# General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with this program; see the file COPYING. If not, write to
23+
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
24+
# Boston, MA 02110-1301, USA.
25+
26+
27+
# Import from pygit2
28+
from .errors import check_error
29+
from .ffi import ffi, C
30+
from .utils import to_bytes
31+
32+
33+
class PackBuilder:
34+
35+
def __init__(self, repo):
36+
37+
cpackbuilder = ffi.new('git_packbuilder **')
38+
err = C.git_packbuilder_new(cpackbuilder, repo._repo)
39+
check_error(err)
40+
41+
self._repo = repo
42+
self._packbuilder = cpackbuilder[0]
43+
self._cpackbuilder = cpackbuilder
44+
45+
@property
46+
def _pointer(self):
47+
return bytes(ffi.buffer(self._packbuilder)[:])
48+
49+
def __del__(self):
50+
C.git_packbuilder_free(self._packbuilder)
51+
52+
def __len__(self):
53+
return C.git_packbuilder_object_count(self._packbuilder)
54+
55+
@staticmethod
56+
def convert_object_to_oid(oid):
57+
git_oid = ffi.new('git_oid *')
58+
ffi.buffer(git_oid)[:] = oid.raw[:]
59+
return git_oid
60+
61+
def add(self, oid):
62+
git_oid = self.convert_object_to_oid(oid)
63+
err = C.git_packbuilder_insert(self._packbuilder, git_oid, ffi.NULL)
64+
check_error(err)
65+
66+
def add_recur(self, oid):
67+
git_oid = self.convert_object_to_oid(oid)
68+
err = C.git_packbuilder_insert_recur(self._packbuilder, git_oid, ffi.NULL)
69+
check_error(err)
70+
71+
def set_threads(self, n_threads):
72+
return C.git_packbuilder_set_threads(self._packbuilder, n_threads)
73+
74+
def write(self, path=None):
75+
path = ffi.NULL if path is None else to_bytes(path)
76+
err = C.git_packbuilder_write(self._packbuilder, path, 0, ffi.NULL, ffi.NULL)
77+
check_error(err)
78+
79+
@property
80+
def written_objects_count(self):
81+
return C.git_packbuilder_written(self._packbuilder)

pygit2/repository.py

+34
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from .blame import Blame
5050
from .utils import to_bytes, StrArray
5151
from .submodule import Submodule
52+
from .packbuilder import PackBuilder
5253

5354

5455
class BaseRepository(_Repository):
@@ -83,6 +84,39 @@ def write(self, *args, **kwargs):
8384
object."""
8485
return self.odb.write(*args, **kwargs)
8586

87+
def pack(self, path=None, pack_delegate=None, n_threads=None):
88+
"""Pack the objects in the odb chosen by the pack_delegate function
89+
and write .pack and .idx files for them.
90+
91+
Returns: the number of objects written to the pack
92+
93+
Parameters:
94+
95+
path
96+
The path to which the .pack and .idx files should be written. None will write to the default location.
97+
98+
pack_delegate
99+
The method which will provide add the objects to the pack builder. Defaults to all objects.
100+
101+
n_threads
102+
The number of threads the PackBuilder will spawn. If set to 0 libgit2 will autodetect the number of CPUs.
103+
"""
104+
105+
def pack_all_objects(pack_builder):
106+
for obj in self.odb:
107+
pack_builder.add(obj)
108+
109+
pack_delegate = pack_delegate or pack_all_objects
110+
111+
builder = PackBuilder(self)
112+
if n_threads is not None:
113+
builder.set_threads(n_threads)
114+
pack_delegate(builder)
115+
builder.write(path=path)
116+
117+
return builder.written_objects_count
118+
119+
86120
def __iter__(self):
87121
return iter(self.odb)
88122

test/test_packbuilder.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 2010-2020 The pygit2 contributors
2+
#
3+
# This file is free software; you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License, version 2,
5+
# as published by the Free Software Foundation.
6+
#
7+
# In addition to the permissions in the GNU General Public License,
8+
# the authors give you unlimited permission to link the compiled
9+
# version of this file into combinations with other programs,
10+
# and to distribute those combinations without any restriction
11+
# coming from the use of this file. (The General Public License
12+
# restrictions do apply in other respects; for example, they cover
13+
# modification of the file, and distribution when not linked into
14+
# a combined executable.)
15+
#
16+
# This file is distributed in the hope that it will be useful, but
17+
# WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
# General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with this program; see the file COPYING. If not, write to
23+
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
24+
# Boston, MA 02110-1301, USA.
25+
26+
"""Tests for Index files."""
27+
28+
import os
29+
30+
import pytest
31+
32+
import pygit2
33+
from pygit2 import PackBuilder
34+
from . import utils
35+
from .utils import rmtree
36+
37+
38+
def test_create_packbuilder(testrepo):
39+
# simple test of PackBuilder creation
40+
packbuilder = PackBuilder(testrepo)
41+
assert len(packbuilder) == 0
42+
43+
44+
def test_add(testrepo):
45+
# Add a few objects and confirm that the count is correct
46+
packbuilder = PackBuilder(testrepo)
47+
objects_to_add = [obj for obj in testrepo]
48+
packbuilder.add(objects_to_add[0])
49+
assert len(packbuilder) == 1
50+
packbuilder.add(objects_to_add[1])
51+
assert len(packbuilder) == 2
52+
53+
54+
def test_add_recursively(testrepo):
55+
# Add the head object and referenced objects recursively and confirm that the count is correct
56+
packbuilder = PackBuilder(testrepo)
57+
packbuilder.add_recur(testrepo.head.target)
58+
59+
#expect a count of 4 made up of the following referenced objects:
60+
# Commit
61+
# Tree
62+
# Blob: hello.txt
63+
# Blob: .gitignore
64+
65+
assert len(packbuilder) == 4
66+
67+
68+
def test_repo_pack(testrepo, tmp_path):
69+
# pack the repo with the default strategy
70+
confirm_same_repo_after_packing(testrepo, tmp_path, None)
71+
72+
73+
def test_pack_with_delegate(testrepo, tmp_path):
74+
# loop through all branches and add each commit to the packbuilder
75+
def pack_delegate(pb):
76+
for branch in pb._repo.branches:
77+
br = pb._repo.branches.get(branch)
78+
for commit in br.log():
79+
pb.add_recur(commit.oid_new)
80+
confirm_same_repo_after_packing(testrepo, tmp_path, pack_delegate)
81+
82+
83+
def setup_second_repo(tmp_path):
84+
# helper method to set up a second repo for comparison
85+
tmp_path_2 = os.path.join(tmp_path, 'test_repo2')
86+
with utils.TemporaryRepository('testrepo.tar', tmp_path_2) as path:
87+
testrepo = pygit2.Repository(path)
88+
return testrepo
89+
90+
def confirm_same_repo_after_packing(testrepo, tmp_path, pack_delegate):
91+
# Helper method to confirm the contents of two repos before and after packing
92+
pack_repo = setup_second_repo(tmp_path)
93+
94+
objects_dir = os.path.join(pack_repo.path, 'objects')
95+
rmtree(objects_dir)
96+
pack_path = os.path.join(pack_repo.path, 'objects', 'pack')
97+
os.makedirs(pack_path)
98+
99+
# assert that the number of written objects is the same as the number of objects in the repo
100+
written_objects = testrepo.pack(pack_path, pack_delegate=pack_delegate)
101+
assert written_objects == len([obj for obj in testrepo])
102+
103+
104+
# assert that the number of objects in the pack repo is the same as the original repo
105+
orig_objects = [obj for obj in testrepo.odb]
106+
packed_objects = [obj for obj in pack_repo.odb]
107+
assert len(packed_objects) == len(orig_objects)
108+
109+
# assert that the objects in the packed repo are the same objects as the original repo
110+
for i, obj in enumerate(orig_objects):
111+
assert pack_repo[obj].type == testrepo[obj].type
112+
assert pack_repo[obj].read_raw() == testrepo[obj].read_raw()

0 commit comments

Comments
 (0)