File Coverage

perllib/Arch/Changeset.pm
Criterion Covered Total %
statement 12 151 7.9
branch 0 70 0.0
condition 0 75 0.0
subroutine 4 11 36.3
pod 7 7 100.0
total 23 314 7.3


line stmt bran cond sub pod time code
1             # Arch Perl library, Copyright (C) 2004 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   59 use 5.005;
  3         9  
  3         117  
18 3     3   15 use strict;
  3         6  
  3         128  
19              
20             package Arch::Changeset;
21              
22 3     3   13 use Arch::Util qw(load_file adjacent_revision);
  3         5  
  3         151  
23 3     3   1446 use Arch::Changes qw(:type);
  3         6  
  3         8356  
24              
25             sub new ($$$) {
26 0     0 1   my $class = shift;
27 0   0       my $revision = shift || die "Arch::Changeset::new: no revision\n";
28 0   0       my $dir = shift || die "Arch::Changeset::new: no dir\n";
29 0 0         die "No changeset dir $dir for revision $revision\n" unless -d $dir;
30              
31 0           $dir =~ s!/$!!;
32              
33 0           my $self = {
34             revision => $revision,
35             dir => $dir,
36             ancestor => undef,
37             index_memo => {},
38             };
39              
40 0           return bless $self, $class;
41             }
42              
43             sub get_patch ($$;$$) {
44 0     0 1   my $self = shift;
45 0           my $filepath = shift;
46 0   0       my $type = shift || 0;
47             # 0 - unknown, 1 - modified (including metadata), 2 - new, 3 - removed
48 0 0 0       $type = { MODIFY => 1, ADD => 2, DELETE => 3 }->{$type} || 0
49             unless $type =~ /^[0123]$/;
50 0   0       my $full_file_asis = shift || 0;
51 0           my $dir = $self->{dir};
52 0           my $change_type = "";
53              
54 0           my $patch_file = "$dir/patches/$filepath.patch";
55 0 0 0       if (!-f $patch_file && $type == 0 || $type == 2) {
      0        
56 0           $patch_file = "$dir/new-files-archive/$filepath";
57 0           $change_type = "new";
58             }
59 0 0 0       if (!-f $patch_file && $type == 0 || $type == 3) {
      0        
60 0           $patch_file = "$dir/removed-files-archive/$filepath";
61 0           $change_type = "removed";
62             }
63              
64 0 0         if (!-f $patch_file) {
65 0           my $patch_content = "*** Currently unsupported patch type, possibly metadata or symlink change ***";
66 0 0         if ($type >= 2) {
67 0 0         if (-l $patch_file) {
68 0           $patch_content = readlink($patch_file);
69             } else {
70 0           die "No file $filepath patch in revision $self->{revision} changeset\n";
71             }
72             } else {
73 0           $patch_file = "/dev/null";
74 0           $change_type = "unsupported";
75             }
76 0 0         return wantarray? ($patch_content, $patch_file, $change_type, 1): $patch_content;
77             }
78 0           my $patch_content = load_file($patch_file);
79              
80             # create fake patch from full file if needed
81 0           my $asis = 0;
82 0 0 0       if ($change_type ne "" && !($asis = $full_file_asis || -B $patch_file)) {
      0        
83 0           my $has_end_line = $patch_content =~ /\n$/;
84 0           my $num_lines = $patch_content =~ s/\n/\n/g;
85 0 0         $num_lines += $has_end_line? 0: 1;
86 0           my $file = $patch_file;
87 0           $file =~ s!^\Q$dir\E/[^/]+/!!s;
88 0           my ($file1, $file2, $line1, $line2, $prefix);
89 0 0         if ($change_type eq "new") {
90 0           $file1 = "/dev/null";
91 0           $file2 = $file;
92 0           $line1 = "-0,0";
93 0           $line2 = "+1,$num_lines";
94 0           $prefix = "+";
95             } else {
96 0           $file1 = $file;
97 0           $file2 = "/dev/null";
98 0           $line1 = "-1,$num_lines";
99 0           $line2 = "+0,0";
100 0           $prefix = "-";
101             }
102 0 0         chop $patch_content if $has_end_line;
103 0           $patch_content =~ s/(^|\012)/$1$prefix/g;
104 0 0         $patch_content .= "\n\\ No newline at end of file" unless $has_end_line;
105 0           $patch_content = "--- $file1\n+++ $file2\n@@ $line1 $line2 @@\n$patch_content\n";
106 0           $change_type = "";
107             }
108              
109 0   0       $change_type ||= "patch";
110 0 0         return wantarray? ($patch_content, $patch_file, $change_type, $asis): $patch_content;
111             }
112              
113             sub ancestor ($) {
114 0     0 1   my $self = shift;
115 0           my $ancestor = $self->{ancestor};
116 0 0         return $ancestor if $ancestor;
117              
118 0 0         if (-f "$self->{dir}/=ancestor") {
119 0           $ancestor = load_file("$self->{dir}/=ancestor");
120 0           chomp($ancestor);
121             }
122 0 0         unless ($ancestor) {
123             # just guess
124 0           my $revision = $self->{revision};
125 0   0       $ancestor = adjacent_revision($revision, -1) || $revision;
126             }
127 0           return $self->{ancestor} = $ancestor;
128             }
129              
130             sub get_index ($$) {
131 0     0 1   my $self = shift;
132 0           my $index = shift;
133              
134 0 0         return %{$self->{index_memo}->{$index}}
  0            
135             if (exists $self->{index_memo}->{$index});
136              
137 0           my $index_hash = {};
138              
139             # TODO: add proper unescaping support
140 0           foreach my $line (split /\n/, load_file($self->{dir} . '/' . $index)) {
141 0           my ($path, $id) = split / /, $line, 2;
142              
143 0           $path =~ s,^\./,,;
144 0           $index_hash->{$id} = $path;
145             }
146              
147 0           $self->{index_memo}->{$index} = $index_hash;
148 0           return %$index_hash;
149             }
150              
151             sub get_changes ($) {
152 0     0 1   my $self = shift;
153              
154 0           my %orig_dirs = $self->get_index('orig-dirs-index');
155 0           my %mod_dirs = $self->get_index('mod-dirs-index');
156              
157 0           my %orig_files = $self->get_index('orig-files-index');
158 0           my %mod_files = $self->get_index('mod-files-index');
159              
160 0           my $changes = Arch::Changes->new;
161              
162             # added dirs
163 0           foreach my $id (keys %mod_dirs) {
164 0 0         $changes->add(ADD, 1, $mod_dirs{$id})
165             unless (exists $orig_dirs{$id});
166             }
167              
168             # added files
169 0           foreach my $id (keys %mod_files) {
170 0 0         $changes->add(ADD, 0, $mod_files{$id})
171             unless (exists $orig_files{$id});
172             }
173              
174             # deleted dirs
175 0           foreach my $id (keys %orig_dirs) {
176 0 0         $changes->add(DELETE, 1, $orig_dirs{$id})
177             unless (exists $mod_dirs{$id});
178             }
179              
180             # deleted files
181 0           foreach my $id (keys %orig_files) {
182 0 0         $changes->add(DELETE, 0, $orig_files{$id})
183             unless (exists $mod_files{$id});
184             }
185              
186             # modified files
187 0           foreach my $id (keys %mod_files) {
188 0 0         $changes->add(MODIFY, 0, $mod_files{$id})
189             if (-f $self->{dir} . '/patches/' . $mod_files{$id} . '.patch');
190             }
191              
192             # dir metadata changes
193 0           foreach my $id (keys %mod_dirs) {
194 0 0         $changes->add(META_MODIFY, 1, $mod_dirs{$id})
195             if (-f $self->{dir} . '/patches/' . $mod_dirs{$id} . '/=dir-meta-mod');
196             }
197              
198             # file metadata changes
199 0           foreach my $id (keys %mod_files) {
200 0 0         $changes->add(META_MODIFY, 0, $mod_files{$id})
201             if (-f $self->{dir} . '/patches/' . $mod_files{$id} . '.meta-mod');
202             }
203              
204 0           my %ren_dirs;
205 0           foreach (keys %orig_dirs) {
206 0 0         $ren_dirs{$orig_dirs{$_}} = $mod_dirs{$_}
207             if exists $mod_dirs{$_};
208             }
209              
210             # moved dirs
211 0           foreach my $id (keys %orig_dirs) {
212 0 0 0       if (
      0        
213             exists $orig_dirs{$id} &&
214             exists $mod_dirs{$id} &&
215             $orig_dirs{$id} ne $mod_dirs{$id}
216             ) {
217 0           (my $parent = $orig_dirs{$id}) =~ s!/?[^/]+$!!;
218 0           my $tail = $&;
219 0           my $found = 0;
220              
221 0   0       while (!$found && $parent) {
222 0   0       $found = exists $ren_dirs{$parent}
223             && (($ren_dirs{$parent} . $tail) eq $mod_dirs{$id});
224              
225 0           $parent =~ s!/?[^/]+$!!;
226 0           $tail = $& . $tail;
227             }
228              
229 0 0         $changes->add(RENAME, 1, $orig_dirs{$id}, $mod_dirs{$id})
230             if !$found;
231             }
232             }
233              
234             # moved files
235 0           foreach my $id (keys %orig_files) {
236 0 0 0       if (
      0        
237             exists $orig_files{$id} &&
238             exists $mod_files{$id} &&
239             $orig_files{$id} ne $mod_files{$id}
240             ) {
241 0           (my $parent = $orig_files{$id}) =~ s!/?[^/]+$!!;
242 0           my $tail = $&;
243 0           my $found = 0;
244              
245 0   0       while (!$found && $parent) {
246 0 0         last if $tail =~ m!^/\.arch-ids/!;
247              
248 0   0       $found = exists $ren_dirs{$parent}
249             && (($ren_dirs{$parent} . $tail) eq $mod_files{$id});
250              
251 0           $parent =~ s!/?[^/]+$!!;
252 0           $tail = $& . $tail;
253             }
254              
255 0 0         $changes->add(RENAME, 0, $orig_files{$id}, $mod_files{$id})
256             if !$found;
257             }
258             }
259              
260 0           return $changes;
261             }
262              
263             sub get_all_diffs ($;%) {
264 0     0 1   my $self = shift;
265 0           my %params = @_;
266              
267 0           my @diffs = ();
268 0           my $changes = $self->get_changes;
269 0           foreach my $change ($changes->get) {
270 0 0         next if $change->{is_dir};
271 0           my $type = $change->{type};
272 0 0 0       next unless $type eq MODIFY
      0        
      0        
273             || !$params{no_new_files} && ($type eq ADD || $type eq DELETE);
274 0           my $filepath = $change->{arguments}->[0];
275 0 0 0       next if $params{no_arch_files} &&
      0        
276             ($filepath =~ m!^{arch}/! || $filepath =~ m!(^|/).arch-ids/!);
277 0   0       push @diffs, scalar $self->get_patch($filepath, $type)
278             || "*** $filepath ***\n*** binary content not displayed ***";
279             }
280              
281 0 0         return wantarray? @diffs: \@diffs;
282             }
283              
284             sub join_all_diffs ($;%) {
285 0     0 1   my $self = shift;
286              
287 0           my $diffs = $self->get_all_diffs(@_);
288              
289 0           return join('', map { "\n$_\n" } @$diffs);
  0            
290             }
291              
292             1;
293              
294             __END__