File Coverage

lib/PatchReader/AddCVSContext.pm
Criterion Covered Total %
statement 22 136 16.1
branch 0 56 0.0
condition 1 15 6.6
subroutine 6 16 37.5
pod 0 10 0.0
total 29 233 12.4


line stmt bran cond sub pod time code
1             package PatchReader::AddCVSContext;
2              
3 1     1   1277 use PatchReader::FilterPatch;
  1         2  
  1         25  
4 1     1   359 use PatchReader::CVSClient;
  1         2  
  1         25  
5 1     1   5 use Cwd;
  1         2  
  1         82  
6 1     1   1239 use File::Temp;
  1         20783  
  1         80  
7              
8 1     1   8 use strict;
  1         2  
  1         1502  
9              
10             @PatchReader::AddCVSContext::ISA = qw(PatchReader::FilterPatch);
11              
12             # XXX If you need to, get the entire patch worth of files and do a single
13             # cvs update of all files as soon as you find a file where you need to do a
14             # cvs update, to avoid the significant connect overhead
15             sub new {
16 1     1 0 733 my $class = shift;
17 1   33     10 $class = ref($class) || $class;
18 1         9 my $this = $class->SUPER::new();
19 1         3 bless $this, $class;
20              
21 1         8 $this->{CONTEXT} = $_[0];
22 1         3 $this->{CVSROOT} = $_[1];
23              
24 1         3 return $this;
25             }
26              
27             sub my_rmtree {
28 0     0 0   my ($this, $dir) = @_;
29 0           foreach my $file (glob("$dir/*")) {
30 0 0         if (-d $file) {
31 0           $this->my_rmtree($file);
32             } else {
33 0           trick_taint($file);
34 0           unlink $file;
35             }
36             }
37 0           trick_taint($dir);
38 0           rmdir $dir;
39             }
40              
41             sub end_patch {
42 0     0 0   my $this = shift;
43 0 0         if (exists($this->{TMPDIR})) {
44             # Set as variable to get rid of taint
45             # One would like to use rmtree here, but that is not taint-safe.
46 0           $this->my_rmtree($this->{TMPDIR});
47             }
48             }
49              
50             sub start_file {
51 0     0 0   my $this = shift;
52 0           my ($file) = @_;
53 0   0       $this->{HAS_CVS_CONTEXT} = !$file->{is_add} && !$file->{is_remove} &&
54             $file->{old_revision};
55 0           $this->{REVISION} = $file->{old_revision};
56 0           $this->{FILENAME} = $file->{filename};
57 0           $this->{SECTION_END} = -1;
58 0 0         $this->{TARGET}->start_file(@_) if $this->{TARGET};
59             }
60              
61             sub end_file {
62 0     0 0   my $this = shift;
63 0           $this->flush_section();
64              
65 0 0         if ($this->{FILE}) {
66 0           close $this->{FILE};
67 0           unlink $this->{FILE}; # If it fails, it fails ...
68 0           delete $this->{FILE};
69             }
70 0 0         $this->{TARGET}->end_file(@_) if $this->{TARGET};
71             }
72              
73             sub next_section {
74 0     0 0   my $this = shift;
75 0           my ($section) = @_;
76 0           $this->{NEXT_PATCH_LINE} = $section->{old_start};
77 0           $this->{NEXT_NEW_LINE} = $section->{new_start};
78 0           foreach my $line (@{$section->{lines}}) {
  0            
79             # If this is a line requiring context ...
80 0 0         if ($line =~ /^[-\+]/) {
81             # Determine how much context is needed for both the previous section line
82             # and this one:
83             # - If there is no old line, start new section
84             # - If this is file context, add (old section end to new line) context to
85             # the existing section
86             # - If old end context line + 1 < new start context line, there is an empty
87             # space and therefore we end the old section and start the new one
88             # - Else we add (old start context line through new line) context to
89             # existing section
90 0 0         if (! exists($this->{SECTION})) {
    0          
91 0           $this->_start_section();
92             } elsif ($this->{CONTEXT} eq "file") {
93 0           $this->push_context_lines($this->{SECTION_END} + 1,
94             $this->{NEXT_PATCH_LINE} - 1);
95             } else {
96 0           my $start_context = $this->{NEXT_PATCH_LINE} - $this->{CONTEXT};
97 0 0         $start_context = $start_context > 0 ? $start_context : 0;
98 0 0         if (($this->{SECTION_END} + $this->{CONTEXT} + 1) < $start_context) {
99 0           $this->flush_section();
100 0           $this->_start_section();
101             } else {
102 0           $this->push_context_lines($this->{SECTION_END} + 1,
103             $this->{NEXT_PATCH_LINE} - 1);
104             }
105             }
106 0           push @{$this->{SECTION}{lines}}, $line;
  0            
107 0 0         if (substr($line, 0, 1) eq "+") {
108 0           $this->{SECTION}{plus_lines}++;
109 0           $this->{SECTION}{new_lines}++;
110 0           $this->{NEXT_NEW_LINE}++;
111             } else {
112 0           $this->{SECTION_END}++;
113 0           $this->{SECTION}{minus_lines}++;
114 0           $this->{SECTION}{old_lines}++;
115 0           $this->{NEXT_PATCH_LINE}++;
116             }
117             } else {
118 0           $this->{NEXT_PATCH_LINE}++;
119 0           $this->{NEXT_NEW_LINE}++;
120             }
121             # If this is context, for now lose it (later we should try and determine if
122             # we can just use it instead of pulling the file all the time)
123             }
124             }
125              
126             sub determine_start {
127 0     0 0   my ($this, $line) = @_;
128 0 0         return 0 if $line < 0;
129 0 0         if ($this->{CONTEXT} eq "file") {
130 0           return 1;
131             } else {
132 0           my $start = $line - $this->{CONTEXT};
133 0 0         $start = $start > 0 ? $start : 1;
134 0           return $start;
135             }
136             }
137              
138             sub _start_section {
139 0     0     my $this = shift;
140              
141             # Add the context to the beginning
142 0           $this->{SECTION}{old_start} = $this->determine_start($this->{NEXT_PATCH_LINE});
143 0           $this->{SECTION}{new_start} = $this->determine_start($this->{NEXT_NEW_LINE});
144 0           $this->{SECTION}{old_lines} = 0;
145 0           $this->{SECTION}{new_lines} = 0;
146 0           $this->{SECTION}{minus_lines} = 0;
147 0           $this->{SECTION}{plus_lines} = 0;
148 0           $this->{SECTION_END} = $this->{SECTION}{old_start} - 1;
149 0           $this->push_context_lines($this->{SECTION}{old_start},
150             $this->{NEXT_PATCH_LINE} - 1);
151             }
152              
153             sub flush_section {
154 0     0 0   my $this = shift;
155              
156 0 0         if ($this->{SECTION}) {
157             # Add the necessary context to the end
158 0 0         if ($this->{CONTEXT} eq "file") {
159 0           $this->push_context_lines($this->{SECTION_END} + 1, "file");
160             } else {
161 0           $this->push_context_lines($this->{SECTION_END} + 1,
162             $this->{SECTION_END} + $this->{CONTEXT});
163             }
164             # Send the section and line notifications
165 0 0         $this->{TARGET}->next_section($this->{SECTION}) if $this->{TARGET};
166 0           delete $this->{SECTION};
167 0           $this->{SECTION_END} = 0;
168             }
169             }
170              
171             sub push_context_lines {
172 0     0 0   my $this = shift;
173             # Grab from start to end
174 0           my ($start, $end) = @_;
175 0 0 0       return if $end ne "file" && $start > $end;
176              
177             # If it's an added / removed file, don't do anything
178 0 0         return if ! $this->{HAS_CVS_CONTEXT};
179              
180             # Get and open the file if necessary
181 0 0         if (!$this->{FILE}) {
182 0           my $olddir = getcwd();
183 0 0         if (! exists($this->{TMPDIR})) {
184 0           $this->{TMPDIR} = File::Temp::tempdir();
185 0 0         if (! -d $this->{TMPDIR}) {
186 0           die "Could not get temporary directory";
187             }
188             }
189 0 0         chdir($this->{TMPDIR}) or die "Could not cd $this->{TMPDIR}";
190 0 0         if (PatchReader::CVSClient::cvs_co_rev($this->{CVSROOT}, $this->{REVISION}, $this->{FILENAME})) {
191 0           die "Could not check out $this->{FILENAME} r$this->{REVISION} from $this->{CVSROOT}";
192             }
193 0 0         open my $fh, $this->{FILENAME} or die "Could not open $this->{FILENAME}";
194 0           $this->{FILE} = $fh;
195 0           $this->{NEXT_FILE_LINE} = 1;
196 0           trick_taint($olddir); # $olddir comes from getcwd()
197 0 0         chdir($olddir) or die "Could not cd back to $olddir";
198             }
199              
200             # Read through the file to reach the line we need
201 0 0 0       die "File read too far!" if $this->{NEXT_FILE_LINE} && $this->{NEXT_FILE_LINE} > $start;
202 0           my $fh = $this->{FILE};
203 0           while ($this->{NEXT_FILE_LINE} < $start) {
204 0           my $dummy = <$fh>;
205 0           $this->{NEXT_FILE_LINE}++;
206             }
207 0           my $i = $start;
208 0   0       for (; $end eq "file" || $i <= $end; $i++) {
209 0           my $line = <$fh>;
210 0 0         last if !defined($line);
211 0           $line =~ s/\r\n/\n/g;
212 0           push @{$this->{SECTION}{lines}}, " $line";
  0            
213 0           $this->{NEXT_FILE_LINE}++;
214 0           $this->{SECTION}{old_lines}++;
215 0           $this->{SECTION}{new_lines}++;
216             }
217 0           $this->{SECTION_END} = $i - 1;
218             }
219              
220             sub trick_taint {
221 0     0 0   $_[0] =~ /^(.*)$/s;
222 0           $_[0] = $1;
223 0           return (defined($_[0]));
224             }
225              
226             1;