File Coverage

blib/lib/Git/ObjectStore.pm
Criterion Covered Total %
statement 189 203 93.1
branch 43 64 67.1
condition 2 3 66.6
subroutine 20 22 90.9
pod 14 14 100.0
total 268 306 87.5


line stmt bran cond sub pod time code
1             package Git::ObjectStore;
2             $Git::ObjectStore::VERSION = '0.006';
3 1     1   12882 use strict;
  1         1  
  1         21  
4 1     1   3 use warnings;
  1         1  
  1         18  
5              
6 1     1   443 use Git::Raw;
  1         17073  
  1         24  
7 1     1   5 use Git::Raw::Object;
  1         1  
  1         14  
8 1     1   3 use Carp;
  1         1  
  1         45  
9 1     1   415 use File::Spec::Functions qw(catfile);
  1         522  
  1         1302  
10              
11              
12             # ABSTRACT: abstraction layer for Git::Raw and libgit2
13              
14              
15              
16              
17             sub new
18             {
19 3     3 1 1107 my ($class, %args) = @_;
20 3         5 my $self = bless {}, $class;
21              
22 3         9 $self->{'author_name'} = 'ObjectStore';
23 3         4 $self->{'author_email'} = 'ObjectStore@localhost';
24              
25 3         5 foreach my $arg (qw(repodir branchname)) {
26 6 50       10 if ( defined( $args{$arg} ) ) {
27 6         10 $self->{$arg} = $args{$arg};
28             } else {
29 0         0 croak('Mandatory argument missing: ' . $arg);
30             }
31             }
32              
33 3         5 foreach my $arg (qw(writer author_name author_email)) {
34 9 100       15 if ( defined( $args{$arg} ) ) {
35 1         2 $self->{$arg} = $args{$arg};
36             }
37             }
38              
39 3 50 66     10 if ( $self->{'writer'} and $args{'goto'} ) {
40 0         0 croak('Cannot use goto in writer mode');
41             }
42              
43 3         4 my $branchname = $self->{'branchname'};
44 3         3 my $repodir = $self->{'repodir'};
45              
46 3 100       49 if ( not -e $repodir . '/config' ) {
47 1 50       3 if( $self->{'writer'} ) {
48 1         1496 Git::Raw::Repository->init($repodir, 1);
49             } else {
50 0         0 croak($repodir . ' does not contain a bare Git repository');
51             }
52             }
53              
54 3         374 my $repo = $self->{'repo'} = Git::Raw::Repository->open($repodir);
55 3         17 my $objdir = catfile($repodir, 'objects');
56              
57             # We do not use loose backend, so we explicitly exclude it from ODB.
58             {
59 3         4 my $odb = Git::Raw::Odb->new();
  3         18  
60 3         349 $odb->add_backend(Git::Raw::Odb::Backend::Pack->new($objdir), 10);
61 3         42 $repo->odb($odb);
62             }
63              
64 3 100       13 if ( $self->{'writer'} ) {
65              
66             # in-memory store that will write a single pack file for all objects
67 1         5 $self->{'packdir'} = catfile($objdir, 'pack');
68 1         10 my $mempack = $self->{'mempack'} = Git::Raw::Mempack->new;
69 1         9 $repo->odb->add_backend($mempack, 99);
70              
71 1         117 my $branch = Git::Raw::Branch->lookup($repo, $branchname, 1);
72              
73 1 50       5 if ( not defined($branch) ) {
74             # This is a fresh repo, create the branch
75 1         12 my $builder = Git::Raw::Tree::Builder->new($repo);
76 1         64 my $tree = $builder->write();
77 1         6 my $me = $self->_signature();
78 1         3 my $refname = 'refs/heads/' . $branchname;
79 1         8 my $commit = $repo->commit("Initial empty commit in $branchname",
80             $me, $me, [], $tree, $refname);
81 1         270 $self->{'created_init_commit'} = $commit;
82 1         46 $branch = Git::Raw::Branch->lookup($repo, $branchname, 1);
83             }
84              
85 1 50       3 croak('expected a branch') unless defined($branch);
86              
87             # in-memory index for preparing a commit
88 1         13 my $index = Git::Raw::Index->new();
89              
90             # assign the index to our repo
91 1         8 $repo->index($index);
92              
93             # initiate the index with the top of the branch
94 1         17 my $commit = $branch->peel('commit');
95 1         24 $index->read_tree($commit->tree());
96              
97             # memorize the index for quick write access
98 1         4 $self->{'gitindex'} = $index;
99              
100 1         12 $self->{'current_commit_id'} = $commit->id();
101              
102             } else {
103             # open the repo for read-only access
104 2         1 my $commit;
105 2 50       6 if ( defined($args{'goto'}) ) {
106             # read from a specified commit
107 0         0 $commit = Git::Raw::Commit->lookup($repo, $args{'goto'});
108 0 0       0 croak('Cannot lookup commit ' . $args{'goto'})
109             unless defined($commit);
110             } else {
111             # read from the top of the branch
112 2         152 my $branch = Git::Raw::Branch->lookup($repo, $branchname, 1);
113 2         144 $commit = $branch->peel('commit');
114             }
115              
116             # memorize the tree that we will read
117 2         21 $self->{'gittree'} = $commit->tree();
118              
119 2         11 $self->{'current_commit_id'} = $commit->id();
120             }
121              
122 3         12 return $self;
123             }
124              
125              
126             sub _signature
127             {
128 3     3   5 my $self = shift;
129             return Git::Raw::Signature->now
130 3         116 ($self->{'author_name'}, $self->{'author_email'});
131             }
132              
133              
134              
135             sub created_init_commit
136             {
137 0     0 1 0 my $self = shift;
138 0         0 return $self->{'created_init_commit'};
139             }
140              
141              
142              
143              
144             sub repo
145             {
146 0     0 1 0 my $self = shift;
147 0         0 return $self->{'repo'};
148             }
149              
150              
151              
152              
153              
154             sub read_file
155             {
156 6     6 1 1159 my $self = shift;
157 6         5 my $filename = shift;
158              
159 6 100       12 if ( $self->{'writer'} ) {
160 2         19 my $entry = $self->{'gitindex'}->find($filename);
161 2 100       4 if ( defined($entry) ) {
162 1         11 return $entry->blob()->content();
163             } else {
164 1         13 return undef;
165             }
166             } else {
167 4         57 my $entry = $self->{'gittree'}->entry_bypath($filename);
168 4 50       8 if ( defined($entry) ) {
169 4         56 return $entry->object()->content();
170             } else {
171 0         0 return undef;
172             }
173             }
174             }
175              
176              
177              
178             sub file_exists
179             {
180 2     2 1 183 my $self = shift;
181 2         2 my $filename = shift;
182              
183 2 50       6 if ( $self->{'writer'} ) {
184 2         18 return defined($self->{'gitindex'}->find($filename));
185             } else {
186 0         0 return defined($self->{'gittree'}->entry_bypath($filename));
187             }
188             }
189              
190              
191             sub current_commit_id
192             {
193 4     4 1 784 my $self = shift;
194 4         14 return $self->{'current_commit_id'};
195             }
196              
197              
198              
199             sub write_and_check
200             {
201 2     2 1 189 my $self = shift;
202 2         3 my $filename = shift;
203 2         2 my $data = shift;
204              
205             croak('write_and_check() is called for a read-only ObjectStore object')
206 2 50       5 unless $self->{'writer'};
207              
208 2         2 my $prev_blob_id = '';
209 2 100       18 if( defined(my $entry = $self->{'gitindex'}->find($filename)) ) {
210 1         14 $prev_blob_id = $entry->blob()->id();
211             }
212              
213 2         164 my $entry = $self->{'gitindex'}->add_frombuffer($filename, $data);
214 2         32 my $new_blob_id = $entry->blob()->id();
215              
216 2         37 return ($new_blob_id ne $prev_blob_id);
217             }
218              
219              
220              
221             sub write_file
222             {
223 7     7 1 1644 my $self = shift;
224 7         5 my $filename = shift;
225 7         5 my $data = shift;
226              
227             croak('write_file() is called for a read-only ObjectStore object')
228 7 50       16 unless $self->{'writer'};
229              
230 7         277 $self->{'gitindex'}->add_frombuffer($filename, $data);
231 7         44 return;
232             }
233              
234              
235              
236             sub delete_file
237             {
238 2     2 1 276 my $self = shift;
239 2         3 my $path = shift;
240              
241             croak('delete_file() is called for a read-only ObjectStore object')
242 2 50       6 unless $self->{'writer'};
243 2         17 $self->{'gitindex'}->remove($path);
244 2         4 return;
245             }
246              
247              
248              
249             sub create_commit
250             {
251 3     3 1 2 my $self = shift;
252 3         3 my $msg = shift;
253              
254             croak('create_commit() is called for a read-only ObjectStore object')
255 3 50       7 unless $self->{'writer'};
256              
257 3 50       4 if( not defined($msg) ) {
258 3         71 $msg = scalar(localtime(time()));
259             }
260              
261 3         4 my $branchname = $self->{'branchname'};
262 3         1 my $repo = $self->{'repo'};
263 3         3 my $index = $self->{'gitindex'};
264              
265 3         104 my $branch = Git::Raw::Branch->lookup($self->{'repo'}, $branchname, 1);
266 3         18 my $parent = $branch->peel('commit');
267              
268             # this creates a new tree object from changes in the index
269 3         333 my $tree = $index->write_tree();
270              
271 3 100       32 if( $tree->id() eq $parent->tree()->id() ) {
272             # The tree identifier has not changed, hence there are no
273             # changes in content
274 1         11 return 0;
275             }
276              
277 2         9 my $me = $self->_signature();
278 2         16 my $commit = $repo->commit
279             ($msg, $me, $me, [$parent], $tree, $branch->name());
280              
281             # re-initialize the index
282 2         549 $index->clear();
283 2         28 $index->read_tree($tree);
284              
285 2         8 $self->{'current_commit_id'} = $commit->id();
286              
287 2         25 return 1;
288             }
289              
290              
291              
292             sub write_packfile
293             {
294 3     3 1 3 my $self = shift;
295              
296             croak('write_packfile() is called for a read-only ObjectStore object')
297 3 50       9 unless $self->{'writer'};
298              
299 3         4 my $repo = $self->{'repo'};
300 3         11 my $tp = Git::Raw::TransferProgress->new();
301 3         218 my $indexer = Git::Raw::Indexer->new($self->{'packdir'}, $repo->odb());
302              
303 3         1230 $indexer->append($self->{'mempack'}->dump($repo), $tp);
304 3         537 $indexer->commit($tp);
305 3         16 $self->{'mempack'}->reset;
306 3         38 return;
307             }
308              
309              
310              
311             sub create_commit_and_packfile
312             {
313 3     3 1 558 my $self = shift;
314 3         3 my $msg = shift;
315              
316 3 100       7 if( $self->create_commit($msg) ) {
    50          
317 2         5 $self->write_packfile();
318 2         6 return 1;
319             } elsif ( defined($self->{'created_init_commit'}) ) {
320 1         3 $self->write_packfile();
321             }
322              
323 1         6 return 0;
324             }
325              
326              
327              
328             sub recursive_read
329             {
330 4     4 1 2190 my $self = shift;
331 4         6 my $path = shift;
332 4         2 my $callback = shift;
333 4         4 my $no_content = shift;
334            
335             croak('recursive_read() is called for a read-write ObjectStore object')
336 4 50       10 if $self->{'writer'};
337              
338 4 100       9 if( $path eq '' )
339             {
340 2         17 foreach my $entry ($self->{'gittree'}->entries()) {
341 9         31 $self->_do_recursive_read
342             ($entry, $entry->name(), $callback, $no_content);
343             }
344             } else {
345 2         21 my $entry = $self->{'gittree'}->entry_bypath($path);
346 2 50       6 if( defined($entry) ) {
347 2         3 $self->_do_recursive_read($entry, $path, $callback, $no_content);
348             }
349             else
350             {
351 0         0 croak("No such path in the branch: $path");
352             }
353             }
354 4         14 return;
355             }
356              
357              
358             sub _do_recursive_read
359             {
360 26     26   19 my $self = shift;
361 26         17 my $entry = shift; # Git::Raw::Tree::Entry object
362 26         23 my $path = shift;
363 26         14 my $callback = shift;
364 26         18 my $no_content = shift;
365              
366 26         35 $entry->type();
367              
368 26 100       71 if( $entry->type() == Git::Raw::Object->TREE ) {
369             # this is a subtree, we read it recursively
370 15         127 foreach my $child_entry ($entry->object()->entries()) {
371 15         52 $self->_do_recursive_read
372             ($child_entry, $path . '/' . $child_entry->name(), $callback);
373             }
374             } else {
375 11 50       49 if( $no_content ) {
376 0         0 &{$callback}($path);
  0         0  
377             } else {
378 11         115 &{$callback}($path, $entry->object()->content());
  11         21  
379             }
380             }
381              
382 26         147 return;
383             }
384              
385              
386              
387              
388              
389             sub read_updates
390             {
391 2     2 1 466 my $self = shift;
392 2         2 my $old_commit_id = shift;
393 2         2 my $cb_updated = shift;
394 2         2 my $cb_deleted = shift;
395 2         2 my $no_content = shift;
396              
397 2         36 my $old_commit = Git::Raw::Commit->lookup($self->{'repo'}, $old_commit_id);
398 2 50       6 croak("Cannot lookup commit $old_commit_id") unless defined($old_commit);
399 2         14 my $old_tree = $old_commit->tree();
400              
401 2         3 my $new_tree = $self->{'gittree'};
402              
403 2         197 my $diff = $old_tree->diff
404             (
405             {
406             'tree' => $new_tree,
407             'flags' => {
408             'skip_binary_check' => 1,
409             },
410             }
411             );
412              
413 2         28 my @deltas = $diff->deltas();
414 2         6 foreach my $delta (@deltas) {
415              
416 10         71 my $path = $delta->new_file()->path();
417              
418 10 100       33 if( $delta->status() eq 'deleted') {
419 4         2 &{$cb_deleted}($path);
  4         9  
420             } else {
421 6 100       25 if( $no_content ) {
422 3         3 &{$cb_updated}($path);
  3         5  
423             } else {
424 3         12 my $entry = $new_tree->entry_bypath($path);
425 3         21 &{$cb_updated}($path, $entry->object()->content());
  3         6  
426             }
427             }
428             }
429              
430 2         27 return;
431             }
432              
433              
434              
435              
436              
437              
438              
439             1;
440              
441             # Local Variables:
442             # mode: cperl
443             # indent-tabs-mode: nil
444             # cperl-indent-level: 4
445             # cperl-continued-statement-offset: 4
446             # cperl-continued-brace-offset: -4
447             # cperl-brace-offset: 0
448             # cperl-label-offset: -2
449             # End:
450              
451             __END__