File Coverage

perllib/Arch/Tree.pm
Criterion Covered Total %
statement 65 433 15.0
branch 15 244 6.1
condition 21 114 18.4
subroutine 15 49 30.6
pod 30 32 93.7
total 146 872 16.7


line stmt bran cond sub pod time code
1             # Arch Perl library, Copyright (C) 2004-2005 Mikhael Goikhman
2             #
3             # This program is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation; either version 2 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program; if not, write to the Free Software
15             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16              
17 3     3   72 use 5.005;
  3         11  
  3         122  
18 3     3   17 use strict;
  3         6  
  3         158  
19              
20             package Arch::Tree;
21              
22 3     3   620 use Arch::Util qw(run_tla load_file _parse_revision_descs adjacent_revision);
  3         7  
  3         308  
23 3         221 use Arch::Backend qw(
24             is_baz has_tree_version_dir_opt has_tree_id_cmd has_set_tree_version_cmd
25             has_file_diffs_cmd has_commit_version_arg has_commit_files_separator
26 3     3   18 );
  3         5  
27 3     3   768 use Arch::Session;
  3         7  
  3         93  
28 3     3   21 use Arch::Name;
  3         8  
  3         169  
29 3     3   16 use Arch::Log;
  3         6  
  3         63  
30 3     3   1774 use Arch::Inventory;
  3         6  
  3         156  
31 3     3   21 use Arch::Changes qw(:type);
  3         5  
  3         590  
32 3     3   18 use Arch::Changeset;
  3         6  
  3         63  
33              
34 3     3   14 use Cwd;
  3         8  
  3         23825  
35              
36             sub new ($;$%) {
37 0     0 1 0 my $class = shift;
38 0   0     0 my $dir = shift || ".";
39 0 0       0 die "No tree dir $dir\n" unless -d $dir;
40 0         0 my ($root) = run_tla("tree-root", $dir);
41 0 0       0 die "No tree root for dir $dir\n" unless $root;
42 0         0 my %init = @_;
43              
44 0         0 my $self = {
45             dir => $root,
46             own_logs => $init{own_logs},
47             hide_ids => $init{hide_ids},
48             cache_logs => $init{cache_logs},
49             };
50              
51 0         0 bless $self, $class;
52 0         0 $self->clear_cache;
53 0         0 return $self;
54             }
55              
56             sub root ($) {
57 0     0 1 0 my $self = shift;
58              
59 0         0 return $self->{dir};
60             }
61              
62             sub get_id_tagging_method ($) {
63 0     0 0 0 my $self = shift;
64              
65 0 0       0 ($self->{id_tagging_method}) = run_tla("id-tagging-method", "-d", $self->{dir})
66             unless $self->{id_tagging_method};
67              
68 0         0 return $self->{id_tagging_method};
69             }
70              
71             sub get_version ($) {
72 0     0 1 0 my $self = shift;
73 0 0       0 return $self->{version} if $self->{version};
74 0 0       0 my @add_params = has_tree_version_dir_opt()? ("-d"): ();
75 0         0 my ($version) = run_tla("tree-version", @add_params, $self->{dir});
76 0         0 return $self->{version} = $version;
77             }
78              
79             sub get_revision ($) {
80 0     0 1 0 my $self = shift;
81             #return $self->{revision} if $self->{revision};
82 0 0       0 my $cmd = has_tree_id_cmd()? "tree-id": "logs -frd";
83 0         0 my ($revision) = run_tla($cmd, $self->{dir});
84 0         0 return $self->{revision} = $revision;
85             }
86              
87             sub set_version ($$) {
88 0     0 1 0 my $self = shift;
89 0         0 my $version = shift;
90              
91 0         0 delete $self->{version};
92 0 0       0 my $cmd = has_set_tree_version_cmd()? "set-tree-version": "tree-version";
93 0         0 run_tla($cmd, "-d", $self->{dir}, $version);
94              
95 0         0 return $?;
96             }
97              
98             sub get_log_versions ($) {
99 0     0 1 0 my $self = shift;
100 0         0 my @versions = run_tla("log-versions", "-d", $self->{dir});
101 0 0       0 return wantarray? @versions: \@versions;
102             }
103              
104             sub add_log_version ($$) {
105 0     0 1 0 my $self = shift;
106 0         0 my $version = shift;
107              
108 0         0 run_tla("add-log-version", "-d", $self->{dir}, $version);
109              
110 0         0 return $?;
111             }
112              
113             sub get_log_revisions ($;$) {
114 0     0 1 0 my $self = shift;
115 0   0     0 my $version = shift || $self->get_version;
116 0         0 $version =~ s!-(SOURCE|MIRROR)/!/!;
117 0         0 my @revisions = run_tla("logs", "-f", "-d", $self->{dir}, $version);
118 0 0       0 return wantarray? @revisions: \@revisions;
119             }
120              
121             sub get_log ($$) {
122 0     0 1 0 my $self = shift;
123 0   0     0 my $revision = shift || die;
124              
125 0 0       0 return $self->{cached_logs}->{$revision}
126             if $self->{cached_logs}->{$revision};
127              
128 0         0 my $message;
129 0 0       0 if ($self->{own_logs}) {
130 0         0 my $name = Arch::Name->new($revision);
131 0 0       0 $name->is_valid('revision') or die "Invalid revision $revision\n";
132 0         0 my @n = $name->get;
133 0 0       0 my $version_subdir = $n[2] ne ""?
134             "$n[1]--$n[2]/$n[1]--$n[2]--$n[3]": "$n[1]/$n[1]--$n[3]";
135 0         0 my $subdir = "{arch}/$n[1]/$version_subdir/$n[0]/patch-log/$n[4]";
136 0         0 my $file = "$self->{dir}/$subdir";
137 0 0       0 $message = load_file($file) if -f $file;
138             } else {
139 0         0 $message = run_tla("cat-log", "-d", $self->{dir}, $revision);
140             }
141 0 0       0 return undef unless $message;
142              
143 0         0 my $log = Arch::Log->new($message, hide_ids => $self->{hide_ids});
144 0 0       0 $self->{cached_logs}->{$revision} = $log
145             if $self->{cache_logs};
146 0         0 return $log;
147             }
148              
149             sub get_logs ($;$) {
150 0     0 1 0 my $self = shift;
151 0   0     0 my $version = shift || $self->get_version;
152 0 0       0 my $versions = ref($version) eq 'ARRAY'? $version:
    0          
153             $version eq '*'? $self->get_log_versions: [ $version ];
154              
155 0         0 my @logs = ();
156 0         0 foreach (@$versions) {
157 0         0 my $revisions = $self->get_log_revisions($_);
158 0         0 foreach my $revision (@$revisions) {
159 0         0 push @logs, $self->get_log($revision);
160             }
161             }
162 0 0       0 return wantarray? @logs: \@logs;
163             }
164              
165             sub get_log_revision_descs ($;$) {
166 0     0 1 0 my $self = shift;
167 0         0 my $version = shift;
168              
169 0         0 my $logs = $self->get_logs($version);
170 0         0 my $revision_descs = [];
171 0         0 foreach my $log (@$logs) {
172 0         0 push @$revision_descs, $log->get_revision_desc;
173             }
174 0         0 return $revision_descs;
175             }
176              
177             sub get_inventory ($) {
178 0     0 1 0 my $self = shift;
179              
180 0         0 return Arch::Inventory->new($self->root);
181             }
182              
183             # TODO: properly support file name escaping
184             sub get_changes ($) {
185 0     0 1 0 my $self = shift;
186 0         0 my $is_baz = is_baz();
187 0 0       0 my @args = $is_baz ? qw(status) : qw(changes -d);
188 0         0 my @lines = run_tla(@args, $self->{dir});
189              
190             return undef
191 0 0       0 if ($? >> 8) == 2;
192              
193 0         0 my $baz_1_1_conversion_table;
194 0 0       0 $baz_1_1_conversion_table = {
195             'A ' => [ 'A ', 'A/' ],
196             'D ' => [ 'D ', 'D/' ],
197             'R ' => [ '=>', '/>' ],
198             ' M' => [ 'M ', '??' ],
199             ' P' => [ '--', '-/' ],
200             } if $is_baz;
201              
202 0         0 my $changes = Arch::Changes->new;
203 0         0 foreach my $line (@lines) {
204 0 0       0 next if $line =~ /^\*/;
205 0 0       0 next if $line eq "";
206              
207             # work around baz-1.1 tree-lint messages
208 0 0       0 last if $line =~ /^These files would be source but lack inventory ids/;
209              
210             # support baz
211 0 0 0     0 if ($is_baz && $line =~ /^([ADR ][ MP]) (.+?)(?: => (.+))?$/) {
212 0         0 my $tla_prefix = $baz_1_1_conversion_table->{$1};
213 0 0       0 die "Unknown 'baz status' line: $line\n" unless $tla_prefix;
214             # baz-1.1 lacks info about dirs, so stat file (may not work)
215 0 0       0 my $is_dir = $1 eq 'R '
216             ? -d "$self->{dir}/$3"
217             : -d "$self->{dir}/$2";
218 0 0       0 $line = $tla_prefix->[$is_dir ? 1 : 0] . " $2";
219 0 0       0 $line .= "\t$3" if $3;
220             }
221              
222 0 0       0 $line =~ m!^([ADM=/-])([ />b-]) ([^\t]+)(?:\t([^\t]+))?$!
223             or die("Unrecognized changes line: $line\n");
224              
225 0         0 my $type = $1;
226 0   0     0 my $is_dir = ($1 eq '/') || ($2 eq '/');
227 0         0 my @args = ($3, $4);
228              
229             # fix tla changes inconsistency with renamed directories ('/>' vs '=/')
230 0 0       0 $type = '=' if $type eq '/';
231              
232 0         0 $changes->add($type, $is_dir, @args);
233             }
234              
235 0         0 return $changes;
236             }
237              
238             sub get_changeset ($$) {
239 0     0 1 0 my $self = shift;
240 0         0 my $dir = shift;
241              
242 0 0       0 die("Directory already exists: $dir\n")
243             if (-d $dir);
244              
245 0 0       0 my $cmd = is_baz()? "diff": "changes";
246 0         0 run_tla($cmd, "-d", $self->{dir}, "-o", $dir);
247              
248 0 0       0 return -f "$dir/mod-dirs-index"
249             ? Arch::Changeset->new("changes.".$self->get_version(), $dir)
250             : undef;
251             }
252              
253             sub get_merged_log_text ($) {
254 0     0 1 0 my $self = shift;
255 0         0 my $text = run_tla("log-for-merge", "-d", $self->{dir});
256 0         0 return $text;
257             }
258              
259             sub get_merged_revision_summaries ($) {
260 0     0 1 0 my $self = shift;
261 0         0 my $text = $self->get_merged_log_text;
262 0         0 my @hash = ();
263              
264 0 0 0     0 $text eq "" or $text =~ s/^Patches applied:\n\n//
265             or die "Unexpected merged log output:\n$text\n";
266              
267 0         0 while ($text =~ s/^ \* (.*)\n(.+\n)*\n//) {
268 0         0 push @hash, $1;
269 0         0 my $summary = $2;
270 0         0 $summary =~ s/^ //g;
271 0         0 $summary =~ s/\n$//;
272 0         0 push @hash, $summary;
273             }
274 0 0       0 die "Unexpected merged log sub-output:\n$text\n" if $text ne "";
275              
276 0 0       0 return @hash if wantarray;
277 0         0 my %hash = @hash;
278 0         0 return \%hash;
279             }
280              
281             sub get_merged_revisions ($) {
282 0     0 1 0 my $self = shift;
283              
284 0         0 my $revision_summaries = $self->get_merged_revision_summaries;
285 0         0 my @array = sort keys %$revision_summaries;
286 0 0       0 return wantarray ? @array : \@array;
287             }
288              
289             sub get_missing_revisions ($;$) {
290 0     0 1 0 my $self = shift;
291 0   0     0 my $version = shift || $self->get_version;
292              
293 0   0     0 $self->{missing_revisions}->{$version} ||= [
294             run_tla("missing", "-d", $self->{dir}, $version)
295             ];
296 0         0 my $array = $self->{missing_revisions}->{$version};
297 0 0       0 return wantarray ? @$array : $array;
298             }
299              
300             sub get_missing_revision_descs ($;$) {
301 0     0 1 0 my $self = shift;
302 0   0     0 my $version = shift || $self->get_version;
303              
304 0 0       0 unless ($self->{missing_revision_descs}->{$version}) {
305 0 0       0 my @revision_lines =
306 0         0 map { /^\S/? (undef, $_): $_ }
307             run_tla("missing -scD", "-d", $self->{dir}, $version);
308 0         0 shift @revision_lines; # throw away first undef
309              
310 0         0 my $revision_descs = _parse_revision_descs(4, \@revision_lines);
311 0         0 $self->{missing_revision_descs}->{$version} = $revision_descs;
312 0         0 $self->{missing_revisions}->{$version} =
313 0         0 [ map { $_->{name} } @$revision_descs ];
314             }
315 0         0 return $self->{missing_revision_descs}->{$version};
316             }
317              
318             # for compatibility only, may be removed after summer 2005
319             *get_missing_revision_details = *get_missing_revision_descs;
320             *get_missing_revision_details = *get_missing_revision_details;
321              
322             sub get_previous_revision ($;$) {
323 0     0 1 0 my $self = shift;
324 0   0     0 my $revision = shift || $self->get_revision;
325              
326 0 0       0 return adjacent_revision($revision, -1)
327             unless $revision =~ /^(.*)--version-0$/;
328              
329             # handle version-0 case specially, can't be guessed from the name alone
330 0         0 my $revisions = $self->get_log_revisions($1);
331 0         0 until (pop @$revisions eq $revision) {
332             }
333 0         0 return $revisions->[-1];
334             }
335              
336             sub get_ancestry_logs ($%) {
337 0     0 1 0 my $self = shift;
338 0         0 my %args = @_;
339              
340 0   0     0 my $limit = $args{limit} || 0;
341 0         0 my $callback = $args{callback};
342 0   0     0 my $one_version = $args{one_version} || 0;
343 0   0     0 my $no_continuation = $args{no_continuation} || 0;
344              
345 0         0 my @collected = ();
346 0 0       0 my $version = $self->get_version if $one_version;
347 0         0 my $revision = $self->get_revision;
348 0         0 while ($revision) {
349 0         0 my $log = $self->get_log($revision);
350              
351             # handle removed logs
352 0 0       0 unless ($log) {
353 0         0 $revision = $self->get_previous_revision($revision);
354 0         0 next;
355             }
356              
357 0         0 my $kind = $log->get_revision_kind;
358 0 0       0 if ($kind eq 'import') {
    0          
359 0         0 $revision = undef;
360             } elsif ($kind eq 'tag') {
361 0 0       0 $revision = $no_continuation
362             ? undef
363             : $log->continuation_of;
364 0 0 0     0 $revision &&= undef
      0        
365             if $one_version && $revision !~ /^\Q$version--/;
366             } else {
367 0         0 $revision = $self->get_previous_revision($revision);
368             }
369 0 0       0 push @collected, $callback? $callback->($log): $log;
370 0 0 0     0 last unless --$limit && $log; # undefined by callback
371             }
372 0         0 return \@collected;
373             }
374              
375             # for compatibility only, may be removed after summer 2005
376             sub iterate_ancestry_logs ($;$$) {
377 0     0 0 0 my $self = shift;
378 0         0 my $cb = shift;
379 0   0     0 my $nc = shift || 0;
380 0         0 return $self->get_ancestry_logs(callback => $cb, no_continuation => $nc);
381             }
382              
383             sub get_history_revision_descs ($;$%) {
384 0     0 1 0 my $self = shift;
385 0         0 my $filepath = shift;
386 0 0       0 @_ = (one_version => $_[0]) if @_ == 1; # be compatible until summer 2005
387 0         0 my %args = @_;
388              
389 0   0     0 my $limit = delete $args{limit} || 0;
390 0         0 my $callback = delete $args{callback};
391              
392 0         0 my ($is_dir, $changed);
393 0 0       0 if (defined $filepath) {
394 0         0 my $full_filepath = "$self->{dir}/$filepath";
395             # currently stat the existing tree file/dir
396 0 0       0 $is_dir = -l $full_filepath? 0: -d _? 1: -f _? 0:
    0          
    0          
397             die "No tree file or dir ($full_filepath)\n";
398 0         0 $filepath =~ s!/{2,}!/!g;
399 0         0 $filepath =~ s!^/|/$!!g;
400 0 0       0 $filepath = "." if $filepath eq ""; # avoid invalid input die
401             }
402              
403             return $self->get_ancestry_logs(%args, callback => sub {
404 0     0   0 my $log = $_[0];
405 0 0       0 if (defined $filepath) {
406 0         0 $changed = $log->get_changes->is_changed("to", $filepath, $is_dir);
407 0 0       0 return unless defined $changed;
408             }
409 0         0 my $revision_desc = $log->get_revision_desc;
410              
411 0 0       0 if (defined $filepath) {
412 0         0 $revision_desc->{filepath} = $filepath;
413 0 0       0 $revision_desc->{is_filepath_added} = $changed->{&ADD}? 1: 0;
414 0 0       0 $revision_desc->{is_filepath_renamed} = $changed->{&RENAME}? 1: 0;
415 0 0       0 $revision_desc->{is_filepath_modified} = $changed->{&MODIFY}? 1: 0;
416              
417 0 0       0 $revision_desc->{orig_filepath} = $filepath = $changed->{&RENAME}
418             if $revision_desc->{is_filepath_renamed};
419 0 0       0 $_[0] = undef
420             if $revision_desc->{is_filepath_added};
421             }
422              
423 0 0       0 my @returned = $callback
424             ? $callback->($revision_desc, $log)
425             : $revision_desc;
426              
427 0 0 0     0 $_[0] = undef unless --$limit && $revision_desc; # undefined by callback
428 0         0 return @returned;
429 0         0 });
430             }
431              
432             # for compatibility only, may be removed after 2005
433             *get_ancestry_revision_descs = *get_history_revision_descs;
434             *get_ancestry_revision_descs = *get_ancestry_revision_descs;
435              
436             # parse input like "3-5,8" or [ 3..5, 8 ] or { 3 => 1, 4 => 1, 5 => 1, 8 => 1 }
437             sub _get_skip_hash_from_linenums ($$) {
438 13     13   25 my $linenums = shift;
439 13         15 my $max_linenum = shift;
440              
441 13         21 my %skip_linenums = ();
442 13 100       37 if (defined $linenums) {
443 12         29 %skip_linenums = map { $_ => 1 } 1 .. $max_linenum;
  74         166  
444 12 100       46 if (!ref($linenums)) {
445 15 50       78 $linenums = [ map {
446 8         23 die "Invalid line range ($_)\n" unless /^(\d+)?(-|\.\.)?(\d+)?$/;
447 15 100 100     126 $2? ($1 || 1) .. ($3 || $max_linenum): $1
      66        
448             } split(',', $linenums) ];
449             }
450 12 100       41 if (ref($linenums) eq 'ARRAY') {
451 10         16 $linenums = { map { $_ => 1 } @$linenums };
  36         81  
452             }
453 12 50       39 if (ref($linenums) eq 'HASH') {
454 12         68 delete $skip_linenums{$_} foreach keys %$linenums;
455             }
456             }
457 13         53 return \%skip_linenums;
458             }
459              
460             sub _eq ($$) {
461 84     84   98 my $value1 = shift;
462 84         86 my $value2 = shift;
463 84   100     911 return defined $value1 && defined $value2 && $value1 == $value2
464             || !defined $value1 && !defined $value2;
465             }
466              
467             # see tests/tree-annotate-1 to understand input and output
468             sub _group_annotated_lines ($$) {
469 6     6   13 my $lines = shift;
470 6         8 my $line_rd_indexes = shift;
471              
472 6         7 my $last_line_index = undef;
473 6         8 my $last_rd_index = -1;
474 6   100     51 for (my $i = @$lines; @$lines && $i >= 0; $i--) {
475 27 100 100     83 if ($i == 0 || !_eq($last_rd_index, -1) && !_eq($line_rd_indexes->[$i - 1], $last_rd_index)) {
      100        
476 15         29 splice(@$line_rd_indexes, $i + 1, $last_line_index - $i);
477 15         58 splice(@$lines, $i, $last_line_index - $i + 1, [ @$lines[$i .. $last_line_index] ]);
478             }
479 27 100 100     123 if ($i > 0 && (_eq($last_rd_index, -1) || !_eq($line_rd_indexes->[$i - 1], $last_rd_index))) {
      66        
480 15         19 $last_line_index = $i - 1;
481 15         80 $last_rd_index = $line_rd_indexes->[$i - 1];
482             }
483             }
484             }
485              
486             sub get_annotate_revision_descs ($$;%) {
487 0     0 1 0 my $self = shift;
488 0   0     0 my $filepath = shift || die "No file to annotate\n";
489 0         0 my %args = @_;
490              
491 0         0 my $prefetch_callback = delete $args{prefetch_callback};
492 0         0 my $callback = delete $args{callback};
493 0         0 my $linenums = delete $args{linenums};
494 0         0 my $match_re = delete $args{match_re};
495 0         0 my $highlight = delete $args{highlight};
496 0         0 my $full_history = delete $args{full_history};
497 0 0 0     0 $linenums ||= [] if $match_re; # no lines by default if regexp given
498              
499 0         0 my $full_filepath = "$self->{dir}/$filepath";
500 0 0       0 die "No file $full_filepath to annotate\n" unless -f $full_filepath;
501              
502 0         0 require Arch::DiffParser;
503 0         0 my $diff_parser = Arch::DiffParser->new;
504 0         0 my @lines;
505 0         0 load_file($full_filepath, \@lines);
506              
507 0 0       0 if ($highlight) {
508 0         0 require Arch::FileHighlighter;
509 0         0 my $fh = Arch::FileHighlighter->instance;
510 0         0 my $html_ref = $fh->highlight($full_filepath);
511 0         0 chomp($$html_ref);
512 0         0 @lines = split(/\n/, $$html_ref, -1);
513             }
514              
515 0         0 my @line_rd_indexes = (undef) x @lines;
516 0         0 my @line_rd_index_refs = map { \$_ } @line_rd_indexes;
  0         0  
517              
518 0         0 my $num_unannotated_lines = @lines;
519 0         0 my $num_revision_descs = 0;
520 0         0 my $session = Arch::Session->instance;
521              
522             # limit to certain lines only if requested, like "12-24,50-75,100-"
523 0         0 my $skip_linenums = _get_skip_hash_from_linenums($linenums, 0 + @lines);
524 0 0       0 if ($match_re) {
525 0         0 my $re = eval { qr/$match_re/ };
  0         0  
526 0 0       0 die "get_annotate_revision_descs: invalid regexp /$match_re/: $@" unless defined $re;
527 0   0     0 $lines[$_ - 1] =~ $re && delete $skip_linenums->{$_} for 1 .. @lines;
528             }
529 0         0 $num_unannotated_lines -= keys %$skip_linenums;
530 0         0 $line_rd_index_refs[$_ - 1] = undef foreach keys %$skip_linenums;
531              
532             my $revision_descs = $num_unannotated_lines == 0? []:
533             $self->get_history_revision_descs($filepath, %args, callback => sub {
534 0     0   0 my ($revision_desc, $log) = @_;
535              
536 0 0       0 goto FINISH if $num_unannotated_lines == 0;
537 0         0 my $old_num_unannotated_lines = $num_unannotated_lines;
538              
539             # there is no diff on import, so include all lines manually
540 0 0       0 if ($log->get_revision_kind eq 'import') {
541 0         0 for (my $i = 1; $i <= @line_rd_index_refs; $i++) {
542 0         0 my $ref = $line_rd_index_refs[$i - 1];
543 0 0 0     0 if ($ref && !$$ref) {
544 0         0 $$ref = $num_revision_descs;
545 0         0 $num_unannotated_lines--;
546             }
547             }
548 0         0 goto FINISH;
549             }
550              
551             # only interested in file addition and modification
552 0 0 0     0 goto FINISH unless $revision_desc->{is_filepath_modified}
553             || $revision_desc->{is_filepath_added};
554              
555             # fetch changeset first
556 0         0 my $revision = Arch::Name->new($revision_desc->{version})
557             ->apply($revision_desc->{name});
558 0         0 my $filepath = $revision_desc->{filepath};
559 0 0       0 $prefetch_callback->($revision, $filepath) if $prefetch_callback;
560 0         0 my $changeset = eval {
561 0         0 $session->get_revision_changeset($revision);
562             };
563             # stop if some ancestry archive is not registered or accessible
564 0 0       0 unless ($changeset) {
565 0         0 $_[0] = undef;
566 0         0 return ();
567             }
568             # get file diff if any
569 0         0 my $diff = $changeset->get_patch($filepath);
570             # ignore metadata modification
571 0 0       0 goto FINISH if $diff =~ /^\*/;
572              
573             # calculate annotate data for file lines affected in diff
574 0         0 my $changes = $diff_parser->parse($diff)->changes;
575 0         0 foreach my $change (reverse @$changes) {
576 0         0 my ($ln1, $size1, $ln2, $size2) = @$change;
577 0         0 for (my $i = $ln2; $i < $ln2 + $size2; $i++) {
578 0 0       0 die "get_annotate_revision_descs: inconsistent source line #$i in diff:\n"
579             . " $revision\n $filepath\n"
580             . " ($ln1, $size1, $ln2, $size2)\n"
581             if $i > @line_rd_index_refs;
582 0         0 my $ref = $line_rd_index_refs[$i - 1];
583 0 0 0     0 if ($ref && !$$ref) {
584 0         0 $$ref = $num_revision_descs;
585 0         0 $num_unannotated_lines--;
586             }
587             }
588 0         0 splice(@line_rd_index_refs, $ln2 - 1, $size2, (undef) x $size1);
589             }
590              
591             FINISH:
592 0 0 0     0 die "get_annotate_revision_descs: inconsistency (some lines left)\n"
593             if $revision_desc->{is_filepath_added} && $num_unannotated_lines > 0;
594 0 0       0 die "get_annotate_revision_descs: inconsistency (got extra lines)\n"
595             if $num_unannotated_lines < 0;
596              
597             # stop "history" processing if all lines are annotated
598 0 0 0     0 $_[0] = undef
599             if !$full_history && $num_unannotated_lines == 0;
600              
601             # skip "history" revision that does not belong to "annotate"
602 0 0 0     0 return () if !$full_history
603             && $old_num_unannotated_lines == $num_unannotated_lines;
604              
605 0         0 $num_revision_descs++;
606              
607 0 0       0 my @returned = $callback
608             ? $callback->($revision_desc, $log)
609             : $revision_desc;
610              
611 0 0       0 $_[0] = undef unless $revision_desc; # undefined by callback
612 0         0 return @returned;
613 0 0       0 });
614              
615 0 0       0 return $revision_descs unless wantarray;
616              
617 0 0       0 _group_annotated_lines(\@lines, \@line_rd_indexes) if $args{group};
618 0         0 return (\@lines, \@line_rd_indexes, $revision_descs);
619             }
620              
621             sub clear_cache ($;@) {
622 0     0 1 0 my $self = shift;
623 0         0 my @keys = @_;
624              
625 0 0       0 @keys = qw(missing_revision_descs missing_revisions cached_logs)
626             unless @keys;
627              
628 0         0 foreach (@keys) {
629 0 0 0     0 if (@_ && !exist $self->{$_}) {
630 0         0 warn __PACKAGE__ . "::clear_cache: unknown key ($_), ignoring\n";
631 0         0 next;
632             }
633 0         0 $self->{$_} = {};
634             }
635              
636 0         0 return $self;
637             }
638              
639             sub get_file_diff ($$) {
640 0     0 1 0 my $self = shift;
641 0         0 my $path = shift;
642              
643 0         0 my $cwd = getcwd;
644 0         0 chdir($self->{dir});
645 0 0       0 my $cmd = has_file_diffs_cmd()? "file-diffs": "file-diff";
646 0         0 my $diff = run_tla($cmd, "-N", $path);
647 0         0 chdir($cwd);
648              
649 0         0 return $diff;
650             }
651              
652             sub add ($;@) {
653 0     0 1 0 my $self = shift;
654 0 0       0 my $opts = shift if ref($_[0]) eq 'HASH';
655 0         0 my @files = @_;
656              
657 0         0 my @args = ();
658 0 0       0 push @args, "--id", $opts->{id} if $opts->{id};
659 0         0 push @args, @files;
660              
661 0         0 my $cwd = getcwd();
662 0 0       0 chdir($self->{dir}) && run_tla("add-id", @args);
663 0         0 chdir($cwd);
664              
665 0         0 return $?;
666             }
667              
668             sub delete ($;@) {
669 0     0 1 0 my $self = shift;
670 0         0 my @files = @_;
671              
672 0         0 my $cwd = getcwd();
673 0 0       0 chdir($self->{dir}) && run_tla("delete-id", @files);
674 0         0 chdir($cwd);
675              
676 0         0 return $?;
677             }
678              
679             sub move ($;@) {
680 0     0 1 0 my $self = shift;
681 0         0 my @files = @_;
682              
683 0         0 my $cwd = getcwd();
684 0 0       0 chdir($self->{dir}) && run_tla("move-id", @files);
685 0         0 chdir($cwd);
686              
687 0         0 return $?;
688             }
689              
690             sub make_log ($) {
691 0     0 1 0 my $self = shift;
692              
693 0         0 my ($file) = run_tla("make-log", "-d", $self->{dir});
694 0         0 return $file;
695             }
696              
697             sub import ($;$@) {
698 4     4   13 my $self = shift;
699 4 50       102 return unless ref($self); # ignore perl's import() method
700 0 0         my $opts = shift if ref($_[0]) eq 'HASH';
701 0   0       my $version = shift || $self->get_version;
702              
703 0           my $is_baz = is_baz();
704 0           my @args = ();
705 0           foreach my $opt (qw(archive log summary log-message)) {
706 0 0         push @args, "--$opt", $opts->{$opt} if $opts->{$opt};
707             }
708 0 0 0       push @args, "--setup" unless $is_baz || $opts->{nosetup};
709 0 0         push @args, "--dir" unless $is_baz;
710 0   0       push @args, $opts->{dir} || $self->{dir};
711              
712             # baz-1.2 advertizes but does not actually support directory argument
713             # this block may be deleted later (the bug is fixed in baz-1.3)
714 0 0         if ($is_baz) {
715 0           my $cwd = getcwd();
716 0           my $dir = pop @args;
717 0 0         chdir($dir) && run_tla("import", @args, $version);
718 0           chdir($cwd);
719 0           return $?;
720             }
721              
722 0           run_tla("import", @args, $version);
723              
724 0           return $?;
725             }
726              
727             sub commit ($;$) {
728 0     0 1   my $self = shift;
729 0 0         my $opts = shift if ref($_[0]) eq 'HASH';
730 0           my $version = shift;
731              
732 0           my @args = ();
733 0 0         push @args, "--dir", $self->{dir} unless $opts->{dir};
734 0           foreach my $opt (qw(archive dir log summary log-message file-list)) {
735 0           my $_opt = $opt; $_opt =~ s/-/_/g;
  0            
736 0 0         push @args, "--$opt", $opts->{$_opt} if $opts->{$_opt};
737             }
738 0           foreach my $opt (qw(strict seal fix out-of-date-ok)) {
739 0           my $_opt = $opt; $_opt =~ s/-/_/g;
  0            
740 0 0         push @args, "--$opt" if $opts->{$_opt};
741             }
742              
743 0 0         if (has_commit_version_arg()) {
    0          
744 0   0       push @args, $version || $self->get_version;
745             } elsif ($version) {
746 0           warn "This arch backend's commit does not support version arg\n";
747             }
748              
749 0           my $files = $opts->{files};
750 0 0         if ($files) {
751 0 0         die "commit: files is not ARRAY ($files)\n"
752             unless ref($files) eq 'ARRAY';
753 0 0         push @args, "--" if has_commit_files_separator();
754 0           push @args, @$files;
755             }
756              
757 0           run_tla("commit", @args);
758              
759 0           return $?;
760             }
761              
762             1;
763              
764             __END__