Removed
(Link Here)
|
default => 1, | default => 1, |
}, | }, |
| |
# Added for Patch Viewer stuff (attachment.cgi?action=prettyview) |
{ |
name => 'cvsroot', |
desc => 'The <a href="http://www.cvshome.org">CVS</a> root that most ' . |
'users of your system will be using for "cvs diff". Used in ' . |
'Patch Viewer ("Diff" option on patches) to figure out where ' . |
'patches are rooted even if users did the "cvs diff" from ' . |
'different places in the directory structure. (NOTE: if your ' . |
'cvs repository is remote and requires a password, you must ' . |
'either ensure the bugzilla user has done a "cvs login" or ' . |
'specify the password ' . |
'<a href="http://www.cvshome.org/docs/manual/cvs_2.html#SEC26">as ' . |
'part of the cvs root.</a>) Leave this blank if you have no ' . |
'cvs repository.', |
type => 't', |
default => '', |
}, |
|
{ |
name => 'cvsroot_get', |
desc => 'The CVS root bugzilla will be using to get patches from. ' . |
'Some installations may want to mirror their cvs repository on ' . |
'the bugzilla server or even have it on that same server, and ' . |
'thus the repository can be the local file system (and much ' . |
'faster). Make this the same as cvsroot if you don\'t ' . |
'understand what this is (if cvsroot is blank, make this blank ' . |
'too).', |
type => 't', |
default => '', |
}, |
|
{ |
name => 'bonsai_url', |
desc => 'The URL to a ' . |
'<a href="http://www.mozilla.org/bonsai.html">Bonsai</a> ' . |
'server containing information about your cvs repository. ' . |
'Patch Viewer will use this information to create links to ' . |
'bonsai\'s blame for each section of a patch (it will append ' . |
'"/cvsblame.cgi?..." to this url). Leave this blank if you ' . |
'don\'t understand what this is.', |
type => 't', |
default => '' |
}, |
|
{ |
name => 'lxr_url', |
desc => 'The URL to an ' . |
'<a href="http://sourceforge.net/projects/lxr">LXR</a> server ' . |
'that indexes your cvs repository. Patch Viewer will use this ' . |
'information to create links to LXR for each file in a patch. ' . |
'Leave this blank if you don\'t understand what this is.', |
type => 't', |
default => '' |
}, |
|
{ |
name => 'lxr_root', |
desc => 'Some LXR installations do not index the cvs repository from ' . |
'the root--' . |
'<a href="http://lxr.mozilla.org/mozilla">Mozilla\'s</a>, for ' . |
'example, starts indexing under <code>mozilla/</code>. This ' . |
'means URLs are relative to that extra path under the root. ' . |
'Enter this if you have a similar situation. Leave it blank ' . |
'if you don\'t know what this is.', |
type => 't', |
default => '', |
}, |
); | ); |
| |
1; | 1; |
Removed
(Link Here)
|
default => 1, | default => 1, |
}, | }, |
| |
| # Added for Patch Viewer stuff (attachment.cgi?action=diff) |
| { |
| name => 'cvsroot', |
| desc => 'The <a href="http://www.cvshome.org">CVS</a> root that most ' . |
| 'users of your system will be using for "cvs diff". Used in ' . |
| 'Patch Viewer ("Diff" option on patches) to figure out where ' . |
| 'patches are rooted even if users did the "cvs diff" from ' . |
| 'different places in the directory structure. (NOTE: if your ' . |
| 'CVS repository is remote and requires a password, you must ' . |
| 'either ensure the Bugzill user has done a "cvs login" or ' . |
| 'specify the password ' . |
| '<a href="http://www.cvshome.org/docs/manual/cvs_2.html#SEC26">as ' . |
| 'part of the CVS root.</a>) Leave this blank if you have no ' . |
| 'CVS repository.', |
| type => 't', |
| default => '', |
| }, |
| |
| { |
| name => 'cvsroot_get', |
| desc => 'The CVS root Bugzill will be using to get patches from. ' . |
| 'Some installations may want to mirror their CVS repository on ' . |
| 'the Bugzill server or even have it on that same server, and ' . |
| 'thus the repository can be the local file system (and much ' . |
| 'faster). Make this the same as cvsroot if you don\'t ' . |
| 'understand what this is (if cvsroot is blank, make this blank ' . |
| 'too).', |
| type => 't', |
| default => '', |
| }, |
| |
| { |
| name => 'bonsai_url', |
| desc => 'The URL to a ' . |
| '<a href="http://www.mozilla.org/bonsai.html">Bonsai</a> ' . |
| 'server containing information about your CVS repository. ' . |
| 'Patch Viewer will use this information to create links to ' . |
| 'bonsai\'s blame for each section of a patch (it will append ' . |
| '"/cvsblame.cgi?..." to this url). Leave this blank if you ' . |
| 'don\'t understand what this is.', |
| type => 't', |
| default => '' |
| }, |
| |
| { |
| name => 'lxr_url', |
| desc => 'The URL to an ' . |
| '<a href="http://sourceforge.net/projects/lxr">LXR</a> server ' . |
| 'that indexes your CVS repository. Patch Viewer will use this ' . |
| 'information to create links to LXR for each file in a patch. ' . |
| 'Leave this blank if you don\'t understand what this is.', |
| type => 't', |
| default => '' |
| }, |
| |
| { |
| name => 'lxr_root', |
| desc => 'Some LXR installations do not index the CVS repository from ' . |
| 'the root--' . |
| '<a href="http://lxr.mozilla.org/mozilla">Mozilla\'s</a>, for ' . |
| 'example, starts indexing under <code>mozilla/</code>. This ' . |
| 'means URLs are relative to that extra path under the root. ' . |
| 'Enter this if you have a similar situation. Leave it blank ' . |
| 'if you don\'t know what this is.', |
| type => 't', |
| default => '', |
| }, |
); | ); |
| |
1; | 1; |
Removed
(Link Here)
|
package DiffPrinter::html; |
|
use strict; |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = {}; |
bless $this, $class; |
|
$this->{COLLAPSED} = $_[0]; |
$this->{BONSAI_URL} = $_[1]; |
$this->{LXR_URL} = $_[2]; |
$this->{LXR_ROOT} = $_[3]; |
|
return $this; |
} |
|
sub start_patch { |
} |
|
sub end_patch { |
} |
|
sub start_file { |
my $this = shift; |
my ($file) = @_; |
|
if ($file->{canonical} && $file->{old_revision} && $this->{BONSAI_URL}) { |
$this->{BONSAI_PREFIX} = "$this->{BONSAI_URL}/cvsblame.cgi?file=$file->{filename}&rev=$file->{old_revision}"; |
} |
if ($file->{canonical} && $this->{LXR_URL}) { |
# Cut off the lxr root, if any |
my $filename = $file->{filename}; |
$filename = substr($filename, length($this->{LXR_ROOT})); |
$this->{LXR_PREFIX} = "$this->{LXR_URL}/source/$filename"; |
} |
|
my $twisty = $this->{COLLAPSED} ? "(+)" : "(-)"; |
my $file_class = $this->{COLLAPSED} ? "file_collapse" : "file"; |
my $initial_checked = $this->{COLLAPSED} ? "" : " checked"; |
my $filename = $file->{filename} ? $file->{filename} : ""; |
print '<div class="file_head"><a href="#" onclick="return twisty_click(this)">', |
$twisty, '</a><input type="checkbox" name="', $filename, '"', |
$initial_checked, ' style="display: none"> '; |
if ($file->{canonical} && $this->{LXR_URL}) { |
print "<a href=\"$this->{LXR_PREFIX}\">$filename</a>"; |
} else { |
print $filename; |
} |
print "</div>"; |
print "<div class=\"$file_class\">\n"; |
print "<script>incremental_restore()</script>\n"; |
} |
|
sub end_file { |
print "</div>\n"; |
} |
|
sub section { |
my $this = shift; |
my ($section) = @_; |
|
my $old_end = $section->{old_lines} ? |
$section->{old_start} + $section->{old_lines} - 1 : 0; |
print '<div class="section_head">Line '; |
if ($this->{BONSAI_PREFIX}) { |
print "<a href=\"$this->{BONSAI_PREFIX}#$section->{old_start}\">"; |
} |
if ($this->{BONSAI_PREFIX}) { |
print "</a>"; |
} |
print "</div>\n"; |
print "<div class=\"section\">\n"; |
|
# Get groups of lines and print them |
my $last_line_char = ''; |
my @context_lines; |
my @plus_lines; |
my @minus_lines; |
foreach my $line (@{$section->{lines}}) { |
$line =~ s/\r?\n?$//; |
if ($line =~ /^ /) { |
if ($last_line_char ne ' ') { |
print_lines(\@context_lines, \@plus_lines, \@minus_lines); |
$last_line_char = ''; |
} |
$last_line_char = ' '; |
push @context_lines, substr($line, 1); |
} elsif ($line =~ /^\+/) { |
if ($last_line_char eq ' ' || $last_line_char eq '-' && @plus_lines) { |
print_lines(\@context_lines, \@plus_lines, \@minus_lines); |
$last_line_char = ''; |
} |
$last_line_char = '+'; |
push @plus_lines, substr($line, 1); |
} elsif ($line =~ /^-/) { |
if ($last_line_char eq '+' && @minus_lines) { |
print_lines(\@context_lines, \@plus_lines, \@minus_lines); |
$last_line_char = ''; |
} |
$last_line_char = '-'; |
push @minus_lines, substr($line, 1); |
} |
} |
|
print_lines(\@context_lines, \@plus_lines, \@minus_lines); |
print "</div>\n"; |
} |
|
sub print_lines { |
my ($context_lines, $plus_lines, $minus_lines) = @_; |
if (@{$context_lines}) { |
print "<table>\n"; |
foreach my $line (@{$context_lines}) { |
print_line(undef, undef, $line, undef, $line); |
} |
print "</table>\n"; |
@{$context_lines} = (); |
} |
return if !@{$plus_lines} && !@{$minus_lines}; |
if (@{$plus_lines}) { |
if (@{$minus_lines}) { |
# Plus and minus (change) |
print "<table class=\"changed\">\n"; |
for (my $i=0; $i < @{$plus_lines} || $i < @{$minus_lines}; $i++) { |
print_line(undef, undef, $minus_lines->[$i], undef, $plus_lines->[$i]); |
} |
print "</table>\n"; |
} else { |
# Plus only |
print "<table>\n"; |
foreach my $line (@{$plus_lines}) { |
print_line(undef, undef, undef, "added", $line); |
} |
print "</table>\n"; |
} |
} else { |
if (@{$minus_lines}) { |
# Minus only |
print "<table>\n"; |
foreach my $line (@{$minus_lines}) { |
print_line(undef, "removed", $line, undef, undef); |
} |
print "</table>\n"; |
} else { |
# Nothing |
return; |
} |
} |
@{$plus_lines} = (); |
@{$minus_lines} = (); |
} |
|
sub print_line { |
my ($mainclass, $class1, $line1, $class2, $line2) = @_; |
print "<tr" . ($mainclass ? " class=\"$mainclass\"" : "") . ">"; |
print "<td" . ($class1 ? " class=\"$class1\"" : "") . ">" . ($line1 ? $line1 : "") . "</td>"; |
print "<td" . ($class2 ? " class=\"$class2\"" : "") . ">" . ($line2 ? $line2 : "") . "</td>"; |
print "</tr>\n"; |
} |
|
sub head_section { |
return <<EOM; |
<style> |
.file_head { font-size: x-large; font-weight: bold; background-color: #d3d3d3; border-top: 1px solid black; border-left: 1px solid black; border-right: 1px solid black; width: 100% } |
.file_collapse { display: none } |
.section_head { width: 100%; font-weight: bold; background-color: #d3d3d3; border: 1px solid black } |
.section { font-family: monospace } |
div.section table { width: 100%; empty-cells: show; border-spacing: 0px; border-collapse: collapse } |
div.section td { width: 50%; height: 1em; font-size: 0.9em } |
.changed { background-color: lightblue } |
.added { background-color: lightgreen } |
.removed { background-color: lightgreen } |
</style> |
<script> |
function collapse_all(expand) { |
var elem = document.checkboxform.firstChild; |
while (elem != null) { |
if (expand) { |
if (elem.className == 'file_collapse') { |
elem.className = 'file'; |
elem.previousSibling.firstChild.firstChild.nodeValue = '(-)'; |
elem.previousSibling.firstChild.nextSibling.checked = true; |
} |
} else { |
if (elem.className == 'file') { |
elem.className = 'file_collapse'; |
elem.previousSibling.firstChild.firstChild.nodeValue = '(+)'; |
elem.previousSibling.firstChild.nextSibling.checked = false; |
} |
} |
elem = elem.nextSibling; |
} |
return false; |
} |
|
var current_restore_elem; |
|
function restore_all() { |
current_restore_elem = null; |
incremental_restore(); |
} |
|
function incremental_restore() { |
if (!document.checkboxform.restore_indicator.checked) { |
return; |
} |
var next_restore_elem; |
if (current_restore_elem) { |
next_restore_elem = current_restore_elem.nextSibling; |
} else { |
next_restore_elem = document.checkboxform.firstChild; |
} |
while (next_restore_elem != null) { |
current_restore_elem = next_restore_elem; |
restore_elem(current_restore_elem); |
next_restore_elem = current_restore_elem.nextSibling; |
} |
} |
|
function restore_elem(elem, alertme) { |
if (elem.className == 'file_collapse') { |
if (elem.previousSibling.firstChild.nextSibling.checked) { |
elem.className = 'file'; |
elem.previousSibling.firstChild.firstChild.nodeValue = '(-)'; |
} |
} else if (elem.className == 'file') { |
if (!elem.previousSibling.firstChild.nextSibling.checked) { |
elem.className = 'file_collapse'; |
elem.previousSibling.firstChild.firstChild.nodeValue = '(+)'; |
} |
} |
} |
|
function twisty_click(twisty) { |
if (twisty.parentNode.nextSibling.className == 'file') { |
twisty.parentNode.nextSibling.className = 'file_collapse'; |
twisty.firstChild.nodeValue = '(+)'; |
twisty.nextSibling.checked = false; |
} else { |
twisty.parentNode.nextSibling.className = 'file'; |
twisty.firstChild.nodeValue = '(-)'; |
twisty.nextSibling.checked = true; |
} |
return false; |
} |
</script> |
EOM |
} |
|
sub onload { |
return "restore_all(); document.checkboxform.restore_indicator.checked = true"; |
} |
|
1 |
Removed
(Link Here)
|
package DiffPrinter::raw; |
|
use strict; |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = {}; |
bless $this, $class; |
|
$this->{OUTFILE} = @_ ? $_[0] : *STDOUT; |
my $fh = $this->{OUTFILE}; |
|
return $this; |
} |
|
sub start_patch { |
} |
|
sub end_patch { |
} |
|
sub start_file { |
my $this = shift; |
my ($file) = @_; |
|
my $fh = $this->{OUTFILE}; |
if ($file->{rcs_filename}) { |
print $fh "Index: $file->{filename}\n"; |
print $fh "===================================================================\n"; |
print $fh "RCS file: $file->{rcs_filename}\n"; |
} |
my $old_file = $file->{is_add} ? "/dev/null" : $file->{filename}; |
my $old_date = $file->{old_date_str} || ""; |
print $fh "--- $old_file\t$old_date"; |
print $fh "\t$file->{old_revision}" if $file->{old_revision}; |
print $fh "\n"; |
my $new_file = $file->{is_remove} ? "/dev/null" : $file->{filename}; |
my $new_date = $file->{new_date_str} || ""; |
print $fh "+++ $new_file\t$new_date"; |
print $fh "\t$file->{new_revision}" if $file->{new_revision}; |
print $fh "\n"; |
} |
|
sub end_file { |
} |
|
sub section { |
my $this = shift; |
my ($section) = @_; |
|
my $fh = $this->{OUTFILE}; |
print $fh "@@ -$section->{old_start},$section->{old_lines} +$section->{new_start},$section->{new_lines} @@\n"; |
foreach my $line (@{$section->{lines}}) { |
$line =~ s/(\r?\n?)$/\n/; |
print $fh $line; |
} |
} |
|
1 |
Removed
(Link Here)
|
package DiffPrinter::template; |
|
use strict; |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = {}; |
bless $this, $class; |
|
$this->{TEMPLATE_PROCESSOR} = $_[0]; |
$this->{HEADER_TEMPLATE} = $_[1]; |
$this->{FILE_TEMPLATE} = $_[2]; |
$this->{FOOTER_TEMPLATE} = $_[3]; |
$this->{ARGS} = $_[4] || {}; |
|
return $this; |
} |
|
sub start_patch { |
my $this = shift; |
$this->{TEMPLATE_PROCESSOR}->process($this->{HEADER_TEMPLATE}, $this->{ARGS}) |
|| ::ThrowTemplateError($this->{TEMPLATE_PROCESSOR}->error()); |
} |
|
sub end_patch { |
my $this = shift; |
$this->{TEMPLATE_PROCESSOR}->process($this->{FOOTER_TEMPLATE}, $this->{ARGS}) |
|| ::ThrowTemplateError($this->{TEMPLATE_PROCESSOR}->error()); |
} |
|
sub start_file { |
my $this = shift; |
$this->{ARGS}{file} = shift; |
$this->{ARGS}{file}{plus_lines} = 0; |
$this->{ARGS}{file}{minus_lines} = 0; |
@{$this->{ARGS}{sections}} = (); |
} |
|
sub end_file { |
my $this = shift; |
my $file = $this->{ARGS}{file}; |
if ($file->{canonical} && $file->{old_revision} && $this->{ARGS}{bonsai_url}) { |
$this->{ARGS}{bonsai_prefix} = "$this->{ARGS}{bonsai_url}/cvsblame.cgi?file=$file->{filename}&rev=$file->{old_revision}"; |
} |
if ($file->{canonical} && $this->{ARGS}{lxr_url}) { |
# Cut off the lxr root, if any |
my $filename = $file->{filename}; |
$filename = substr($filename, length($this->{ARGS}{lxr_root})); |
$this->{ARGS}{lxr_prefix} = "$this->{ARGS}{lxr_url}/source/$filename"; |
} |
|
$this->{TEMPLATE_PROCESSOR}->process($this->{FILE_TEMPLATE}, $this->{ARGS}) |
|| ::ThrowTemplateError($this->{TEMPLATE_PROCESSOR}->error()); |
@{$this->{ARGS}{sections}} = (); |
delete $this->{ARGS}{file}; |
} |
|
sub section { |
my $this = shift; |
my ($section) = @_; |
|
$this->{ARGS}{file}{plus_lines} += $section->{plus_lines}; |
$this->{ARGS}{file}{minus_lines} += $section->{minus_lines}; |
|
# Get groups of lines and print them |
my $last_line_char = ''; |
my $context_lines = []; |
my $plus_lines = []; |
my $minus_lines = []; |
foreach my $line (@{$section->{lines}}) { |
$line =~ s/\r?\n?$//; |
if ($line =~ /^ /) { |
if ($last_line_char ne ' ') { |
push @{$section->{groups}}, {context => $context_lines, |
plus => $plus_lines, |
minus => $minus_lines}; |
$context_lines = []; |
$plus_lines = []; |
$minus_lines = []; |
$last_line_char = ''; |
} |
$last_line_char = ' '; |
push @{$context_lines}, substr($line, 1); |
} elsif ($line =~ /^\+/) { |
if ($last_line_char eq ' ' || $last_line_char eq '-' && @{$plus_lines}) { |
push @{$section->{groups}}, {context => $context_lines, |
plus => $plus_lines, |
minus => $minus_lines}; |
$context_lines = []; |
$plus_lines = []; |
$minus_lines = []; |
$last_line_char = ''; |
} |
$last_line_char = '+'; |
push @{$plus_lines}, substr($line, 1); |
} elsif ($line =~ /^-/) { |
if ($last_line_char eq '+' && @{$minus_lines}) { |
push @{$section->{groups}}, {context => $context_lines, |
plus => $plus_lines, |
minus => $minus_lines}; |
$context_lines = []; |
$plus_lines = []; |
$minus_lines = []; |
$last_line_char = ''; |
} |
$last_line_char = '-'; |
push @{$minus_lines}, substr($line, 1); |
} |
} |
|
push @{$section->{groups}}, {context => $context_lines, |
plus => $plus_lines, |
minus => $minus_lines}; |
push @{$this->{ARGS}{sections}}, $section; |
} |
|
1 |
Removed
(Link Here)
|
package PatchIterator::AddCVSContextIterator; |
|
use PatchIterator::FilterPatchIterator; |
use CVSClient; |
use Cwd; |
use File::Temp; |
|
use strict; |
|
@PatchIterator::AddCVSContextIterator::ISA = qw(PatchIterator::FilterPatchIterator); |
|
# XXX If you need to, get the entire patch worth of files and do a single |
# cvs update of all files as soon as you find a file where you need to do a |
# cvs update, to avoid the significant connect overhead |
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = $class->SUPER::new(); |
bless $this, $class; |
|
$this->{CONTEXT} = $_[0]; |
$this->{CVSROOT} = $_[1]; |
|
return $this; |
} |
|
sub my_rmtree { |
my ($this, $dir) = @_; |
foreach my $file (glob("$dir/*")) { |
if (-d $file) { |
$this->my_rmtree($file); |
} else { |
trick_taint($file); |
unlink $file; |
} |
} |
trick_taint($dir); |
rmdir $dir; |
} |
|
sub end_patch { |
my $this = shift; |
if (exists($this->{TMPDIR})) { |
# Set as variable to get rid of taint |
# One would like to use rmtree here, but that is not taint-safe. |
$this->my_rmtree($this->{TMPDIR}); |
} |
} |
|
sub start_file { |
my $this = shift; |
my ($file) = @_; |
$this->{HAS_CVS_CONTEXT} = !$file->{is_add} && !$file->{is_remove}; |
$this->{REVISION} = $file->{old_revision}; |
$this->{FILENAME} = $file->{filename}; |
$this->{SECTION_END} = -1; |
$this->{TARGET}->start_file(@_); |
} |
|
sub end_file { |
my $this = shift; |
$this->flush_section(); |
|
if ($this->{FILE}) { |
close $this->{FILE}; |
unlink $this->{FILE}; # If it fails, it fails ... |
delete $this->{FILE}; |
} |
$this->{TARGET}->end_file(@_); |
} |
|
sub section { |
my $this = shift; |
my ($section) = @_; |
$this->{NEXT_PATCH_LINE} = $section->{old_start}; |
$this->{NEXT_NEW_LINE} = $section->{new_start}; |
foreach my $line (@{$section->{lines}}) { |
# If this is a line requiring context ... |
if ($line =~ /^[-\+]/) { |
# Determine how much context is needed for both the previous section line |
# and this one: |
# - If there is no old line, start new section |
# - If this is file context, add (old section end to new line) context to |
# the existing section |
# - If old end context line + 1 < new start context line, there is an empty |
# space and therefore we end the old section and start the new one |
# - Else we add (old start context line through new line) context to |
# existing section |
if (! exists($this->{SECTION})) { |
$this->_start_section(); |
} elsif ($this->{CONTEXT} eq "file") { |
$this->push_context_lines($this->{SECTION_END} + 1, |
$this->{NEXT_PATCH_LINE} - 1); |
} else { |
my $start_context = $this->{NEXT_PATCH_LINE} - $this->{CONTEXT}; |
$start_context = $start_context > 0 ? $start_context : 0; |
if (($this->{SECTION_END} + $this->{CONTEXT} + 1) < $start_context) { |
$this->flush_section(); |
$this->_start_section(); |
} else { |
$this->push_context_lines($this->{SECTION_END} + 1, |
$this->{NEXT_PATCH_LINE} - 1); |
} |
} |
push @{$this->{SECTION}{lines}}, $line; |
if (substr($line, 0, 1) eq "+") { |
$this->{SECTION}{plus_lines}++; |
$this->{SECTION}{new_lines}++; |
$this->{NEXT_NEW_LINE}++; |
} else { |
$this->{SECTION_END}++; |
$this->{SECTION}{minus_lines}++; |
$this->{SECTION}{old_lines}++; |
$this->{NEXT_PATCH_LINE}++; |
} |
} else { |
$this->{NEXT_PATCH_LINE}++; |
$this->{NEXT_NEW_LINE}++; |
} |
# If this is context, for now lose it (later we should try and determine if |
# we can just use it instead of pulling the file all the time) |
} |
} |
|
sub determine_start { |
my ($this, $line) = @_; |
return 0 if $line < 0; |
if ($this->{CONTEXT} eq "file") { |
return 1; |
} else { |
my $start = $line - $this->{CONTEXT}; |
$start = $start > 0 ? $start : 1; |
return $start; |
} |
} |
|
sub _start_section { |
my $this = shift; |
|
# Add the context to the beginning |
$this->{SECTION}{old_start} = $this->determine_start($this->{NEXT_PATCH_LINE}); |
$this->{SECTION}{new_start} = $this->determine_start($this->{NEXT_NEW_LINE}); |
$this->{SECTION}{old_lines} = 0; |
$this->{SECTION}{new_lines} = 0; |
$this->{SECTION}{minus_lines} = 0; |
$this->{SECTION}{plus_lines} = 0; |
$this->{SECTION_END} = $this->{SECTION}{old_start} - 1; |
$this->push_context_lines($this->{SECTION}{old_start}, |
$this->{NEXT_PATCH_LINE} - 1); |
} |
|
sub flush_section { |
my $this = shift; |
|
if ($this->{SECTION}) { |
# Add the necessary context to the end |
if ($this->{CONTEXT} eq "file") { |
$this->push_context_lines($this->{SECTION_END} + 1, "file"); |
} else { |
$this->push_context_lines($this->{SECTION_END} + 1, |
$this->{SECTION_END} + $this->{CONTEXT}); |
} |
# Send the section and line notifications |
$this->{TARGET}->section($this->{SECTION}); |
delete $this->{SECTION}; |
$this->{SECTION_END} = 0; |
} |
} |
|
sub push_context_lines { |
my $this = shift; |
# Grab from start to end |
my ($start, $end) = @_; |
return if $end ne "file" && $start > $end; |
|
# If it's an added / removed file, don't do anything |
return if ! $this->{HAS_CVS_CONTEXT} || ! $this->{REVISION}; |
|
# Get and open the file if necessary |
if (!$this->{FILE}) { |
my $olddir = getcwd(); |
if (! exists($this->{TMPDIR})) { |
$this->{TMPDIR} = File::Temp::tempdir(); |
if (! -d $this->{TMPDIR}) { |
die "Could not get temporary directory"; |
} |
} |
chdir($this->{TMPDIR}) or die "Could not cd $this->{TMPDIR}"; |
CVSClient::cvs_co_rev($this->{CVSROOT}, $this->{REVISION}, $this->{FILENAME}) and return; |
open my $fh, $this->{FILENAME} or die "Could not open $this->{FILENAME}"; |
$this->{FILE} = $fh; |
$this->{NEXT_FILE_LINE} = 1; |
trick_taint($olddir); # $olddir comes from getcwd() |
chdir($olddir) or die "Could not cd back to $olddir"; |
} |
|
# Read through the file to reach the line we need |
die "File read too far!" if $this->{FILE_LINE} && $this->{FILE_LINE} > $start; |
my $fh = $this->{FILE}; |
while ($this->{NEXT_FILE_LINE} < $start) { |
my $dummy = <$fh>; |
$this->{NEXT_FILE_LINE}++; |
} |
my $i = $start; |
for (; $end eq "file" || $i <= $end; $i++) { |
my $line = <$fh>; |
last if !defined($line); |
$line =~ s/\r\n/\n/g; |
push @{$this->{SECTION}{lines}}, " $line"; |
$this->{NEXT_FILE_LINE}++; |
$this->{SECTION}{old_lines}++; |
$this->{SECTION}{new_lines}++; |
} |
$this->{SECTION_END} = $i - 1; |
} |
|
sub trick_taint { |
$_[0] =~ /^(.*)$/s; |
$_[0] = $1; |
return (defined($_[0])); |
} |
|
1 |
Removed
(Link Here)
|
package PatchIterator::BasePatchIterator; |
|
use strict; |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = {}; |
bless $this, $class; |
|
return $this; |
} |
|
sub target { |
my $this = shift; |
if (defined($_[0])) { |
$this->{TARGET} = $_[0]; |
} else { |
return $this->{TARGET}; |
} |
} |
|
1 |
Removed
(Link Here)
|
package PatchIterator::FilterPatchIterator; |
|
use strict; |
|
use PatchIterator::BasePatchIterator; |
|
@PatchIterator::FilterPatchIterator::ISA = qw(PatchIterator::BasePatchIterator); |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = $class->SUPER::new(); |
bless $this, $class; |
|
return $this; |
} |
|
sub start_patch { |
my $this = shift; |
$this->{TARGET}->start_patch(@_); |
} |
|
sub end_patch { |
my $this = shift; |
$this->{TARGET}->end_patch(@_); |
} |
|
sub start_file { |
my $this = shift; |
$this->{TARGET}->start_file(@_); |
} |
|
sub end_file { |
my $this = shift; |
$this->{TARGET}->end_file(@_); |
} |
|
sub section { |
my $this = shift; |
$this->{TARGET}->section(@_); |
} |
|
1 |
Removed
(Link Here)
|
package PatchIterator::FixPatchRootIterator; |
|
use PatchIterator::FilterPatchIterator; |
use CVSClient; |
|
use strict; |
|
@PatchIterator::FixPatchRootIterator::ISA = qw(PatchIterator::FilterPatchIterator); |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = $class->SUPER::new(); |
bless $this, $class; |
|
my %parsed = CVSClient::parse_cvsroot($_[0]); |
$this->{REPOSITORY_ROOT} = $parsed{rootdir}; |
$this->{REPOSITORY_ROOT} .= "/" if substr($this->{REPOSITORY_ROOT}, -1) ne "/"; |
|
return $this; |
} |
|
sub diff_root { |
my $this = shift; |
if (@_) { |
$this->{DIFF_ROOT} = $_[0]; |
} else { |
return $this->{DIFF_ROOT}; |
} |
} |
|
sub flush_delayed_commands { |
my $this = shift; |
return if ! $this->{DELAYED_COMMANDS}; |
|
my $commands = $this->{DELAYED_COMMANDS}; |
delete $this->{DELAYED_COMMANDS}; |
$this->{FORCE_COMMANDS} = 1; |
foreach my $command_arr (@{$commands}) { |
my $command = $command_arr->[0]; |
my $arg = $command_arr->[1]; |
if ($command eq "start_file") { |
$this->start_file($arg); |
} elsif ($command eq "end_file") { |
$this->end_file($arg); |
} elsif ($command eq "section") { |
$this->section($arg); |
} |
} |
} |
|
sub end_patch { |
my $this = shift; |
$this->flush_delayed_commands(); |
$this->{TARGET}->end_patch(@_); |
} |
|
sub start_file { |
my $this = shift; |
my ($file) = @_; |
# If the file is new, it will not have a filename that fits the repository |
# root and therefore needs to be fixed up to have the same root as everyone |
# else. At the same time we need to fix DIFF_ROOT too. |
if (exists($this->{DIFF_ROOT})) { |
# XXX Return error if there are multiple roots in the patch by verifying |
# that the DIFF_ROOT is not different from the calculated diff root on this |
# filename |
|
$file->{filename} = $this->{DIFF_ROOT} . $file->{filename}; |
|
$file->{canonical} = 1; |
} elsif ($file->{rcs_filename} && |
substr($file->{rcs_filename}, 0, length($this->{REPOSITORY_ROOT})) eq |
$this->{REPOSITORY_ROOT}) { |
# Since we know the repository we can determine where the user was in the |
# repository when they did the diff by chopping off the repository root |
# from the rcs filename |
$this->{DIFF_ROOT} = substr($file->{rcs_filename}, |
length($this->{REPOSITORY_ROOT})); |
$this->{DIFF_ROOT} =~ s/,v$//; |
# XXX More error checking--that filename exists and that it is in fact |
# part of the rcs filename |
$this->{DIFF_ROOT} = substr($this->{DIFF_ROOT}, 0, |
-length($file->{filename})); |
$this->flush_delayed_commands(); |
|
$file->{filename} = $this->{DIFF_ROOT} . $file->{filename}; |
|
$file->{canonical} = 1; |
} else { |
# DANGER Will Robinson. The first file in the patch is new. We will try |
# "delayed command mode" |
# |
# (if force commands is on we are already in delayed command mode, and sadly |
# this means the entire patch was unintelligible to us, so we just output |
# whatever the hell was in the patch) |
|
if (!$this->{FORCE_COMMANDS}) { |
push @{$this->{DELAYED_COMMANDS}}, [ "start_file", { %{$file} } ]; |
return; |
} |
} |
$this->{TARGET}->start_file($file); |
} |
|
sub end_file { |
my $this = shift; |
if (exists($this->{DELAYED_COMMANDS})) { |
push @{$this->{DELAYED_COMMANDS}}, [ "end_file", { %{$_[0]} } ]; |
} else { |
$this->{TARGET}->end_file(@_); |
} |
} |
|
sub section { |
my $this = shift; |
if (exists($this->{DELAYED_COMMANDS})) { |
push @{$this->{DELAYED_COMMANDS}}, [ "section", { %{$_[0]} } ]; |
} else { |
$this->{TARGET}->section(@_); |
} |
} |
|
1 |
Removed
(Link Here)
|
package PatchIterator::NarrowPatchIterator; |
|
use PatchIterator::BasePatchIterator; |
|
use strict; |
|
@PatchIterator::NarrowPatchIterator::ISA = qw(PatchIterator::FilterPatchIterator); |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = $class->SUPER::new(); |
bless $this, $class; |
|
$this->{INCLUDE_FILES} = [@_]; |
|
return $this; |
} |
|
sub start_file { |
my $this = shift; |
my ($file) = @_; |
if (grep { $_ eq substr($file->{filename}, 0, length($_)) } @{$this->{INCLUDE_FILES}}) { |
$this->{IS_INCLUDED} = 1; |
$this->{TARGET}->start_file(@_); |
} |
} |
|
sub end_file { |
my $this = shift; |
if ($this->{IS_INCLUDED}) { |
$this->{TARGET}->end_file(@_); |
$this->{IS_INCLUDED} = 0; |
} |
} |
|
sub section { |
my $this = shift; |
if ($this->{IS_INCLUDED}) { |
$this->{TARGET}->section(@_); |
} |
} |
|
1 |
Removed
(Link Here)
|
package PatchIterator::PatchInfoGrabber; |
|
use PatchIterator::FilterPatchIterator; |
|
use strict; |
|
@PatchIterator::PatchInfoGrabber::ISA = qw(PatchIterator::FilterPatchIterator); |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = $class->SUPER::new(); |
bless $this, $class; |
|
$this->{INCLUDE_FILES} = [@_]; |
|
return $this; |
} |
|
sub patch_info { |
my $this = shift; |
return $this->{PATCH_INFO}; |
} |
|
sub start_patch { |
my $this = shift; |
$this->{PATCH_INFO} = {}; |
$this->{TARGET}->start_patch(@_); |
} |
|
sub start_file { |
my $this = shift; |
my ($file) = @_; |
$this->{PATCH_INFO}{files}{$file->{filename}} = $file; |
$this->{FILE} = $file; |
$this->{TARGET}->start_file(@_); |
} |
|
sub section { |
my $this = shift; |
my ($section) = @_; |
$this->{PATCH_INFO}{files}{$this->{FILE}{filename}}{plus_lines} += $section->{plus_lines}; |
$this->{PATCH_INFO}{files}{$this->{FILE}{filename}}{minus_lines} += $section->{minus_lines}; |
$this->{TARGET}->section(@_); |
} |
|
1 |
Removed
(Link Here)
|
package PatchIterator::RawPatchIterator; |
|
# |
# USAGE: |
# use PatchIterator::RawPatchIterator; |
# my $parser = new PatchIterator::RawPatchIterator(); |
# $parser->target($my_target); |
# $parser->startlines(); |
# open FILE, "mypatch.patch"; |
# while (<FILE>) { |
# $parser->rawline($_); |
# } |
# $parser->endlines(); |
# |
# See also PatchIterator::PatchFileParser, which does exactly that |
# |
|
use strict; |
|
use PatchIterator::BasePatchIterator; |
|
@PatchIterator::RawPatchIterator::ISA = qw(PatchIterator::BasePatchIterator); |
|
sub new { |
my $class = shift; |
$class = ref($class) || $class; |
my $this = $class->SUPER::new(); |
bless $this, $class; |
|
return $this; |
} |
|
# We send these notifications: |
# start_patch({ patchname }) |
# start_file({ filename, rcs_filename, old_revision, old_date_str, new_revision, new_date_str, is_add, is_remove }) |
# section({ old_start, new_start, old_lines, new_lines, @lines }) |
# end_patch |
# end_file |
sub rawline { |
my $this = shift; |
my ($line) = @_; |
|
return if $line =~ /^\?/; |
|
# patch header parsing |
if ($line =~ /^---\s*(\S+)\s*\t([^\t\r\n]*)\s*(\S*)/) { |
if ($1 eq "/dev/null") { |
$this->{FILE_STATE}{is_add} = 1; |
} else { |
$this->{FILE_STATE}{filename} = $1; |
} |
$this->{FILE_STATE}{old_date_str} = $2; |
$this->{FILE_STATE}{old_revision} = $3 if $3; |
|
$this->{IN_HEADER} = 1; |
|
} elsif ($line =~ /^\+\+\+\s*(\S+)\s*\t([^\t\r\n]*)(\S*)/) { |
if ($1 eq "/dev/null") { |
$this->{FILE_STATE}{is_remove} = 1; |
} |
$this->{FILE_STATE}{new_date_str} = $2; |
$this->{FILE_STATE}{new_revision} = $3 if $3; |
|
$this->{IN_HEADER} = 1; |
|
} elsif ($line =~ /^RCS file: (\S+)/) { |
$this->{FILE_STATE}{rcs_filename} = $1; |
|
$this->{IN_HEADER} = 1; |
|
} elsif ($line =~ /^retrieving revision (\S+)/) { |
$this->{FILE_STATE}{old_revision} = $1; |
|
$this->{IN_HEADER} = 1; |
|
} elsif ($line =~ /^Index:\s*(\S+)/) { |
$this->_maybe_end_file(); |
|
$this->{FILE_STATE}{filename} = $1; |
|
$this->{IN_HEADER} = 1; |
|
} elsif ($line =~ /^diff\s*(-\S+\s*)*(\S+)\s*(\S*)/ && $3) { |
# Simple diff <dir> <dir> |
$this->_maybe_end_file(); |
$this->{FILE_STATE}{filename} = $2; |
|
$this->{IN_HEADER} = 1; |
|
# section parsing |
} elsif ($line =~ /^@@\s*-(\d+),?(\d*)\s*\+(\d+),?(\d*)/) { |
$this->{IN_HEADER} = 0; |
|
$this->_maybe_start_file(); |
$this->_maybe_end_section(); |
$2 = 0 if !defined($2); |
$4 = 0 if !defined($4); |
$this->{SECTION_STATE} = { old_start => $1, old_lines => $2, |
new_start => $3, new_lines => $4, |
minus_lines => 0, plus_lines => 0 }; |
|
} elsif ($line =~ /^(\d+),?(\d*)([acd])(\d+),?(\d*)/) { |
# Non-universal diff. Calculate as though it were universal. |
$this->{IN_HEADER} = 0; |
|
$this->_maybe_start_file(); |
$this->_maybe_end_section(); |
|
my $old_start; |
my $old_lines; |
my $new_start; |
my $new_lines; |
if ($3 eq 'a') { |
# 'a' has the old number one off from diff -u ("insert after this line") |
# vs. "insert at this line") |
$old_start = $1 + 1; |
$old_lines = 0; |
} else { |
$old_start = $1; |
$old_lines = $2 ? ($2 - $1 + 1) : 1; |
} |
if ($3 eq 'd') { |
# 'd' has the new number one off from diff -u ("delete after this line") |
# vs. "delete at this line") |
$new_start = $4 + 1; |
$new_lines = 0; |
} else { |
$new_start = $4; |
$new_lines = $5 ? ($5 - $4 + 1) : 1; |
} |
|
$this->{SECTION_STATE} = { old_start => $old_start, old_lines => $old_lines, |
new_start => $new_start, new_lines => $new_lines, |
minus_lines => 0, plus_lines => 0 |
}; |
|
# line parsing |
} elsif ($line =~ /^ /) { |
push @{$this->{SECTION_STATE}{lines}}, $line; |
} elsif ($line =~ /^-($|[^-])/) { |
$this->{SECTION_STATE}{minus_lines}++; |
push @{$this->{SECTION_STATE}{lines}}, $line; |
} elsif ($line =~ /^\+($|[^+])/) { |
$this->{SECTION_STATE}{plus_lines}++; |
push @{$this->{SECTION_STATE}{lines}}, $line; |
} elsif ($line =~ /^< /) { |
$this->{SECTION_STATE}{minus_lines}++; |
push @{$this->{SECTION_STATE}{lines}}, "-" . substr($line, 2); |
} elsif ($line =~ /^> /) { |
$this->{SECTION_STATE}{plus_lines}++; |
push @{$this->{SECTION_STATE}{lines}}, "+" . substr($line, 2); |
} |
} |
|
sub startlines { |
my $this = shift; |
delete $this->{FILE_STARTED}; |
delete $this->{FILE_STATE}; |
delete $this->{SECTION_STATE}; |
$this->{FILE_NEVER_STARTED} = 1; |
|
$this->{TARGET}->start_patch(@_); |
} |
|
sub endlines { |
my $this = shift; |
$this->_maybe_end_file(); |
$this->{TARGET}->end_patch(@_); |
} |
|
sub _maybe_start_file { |
my $this = shift; |
if (exists($this->{FILE_STATE}) && !$this->{FILE_STARTED} || |
$this->{FILE_NEVER_STARTED}) { |
$this->_start_file(); |
} |
} |
|
sub _maybe_end_file { |
my $this = shift; |
return if $this->{IN_HEADER}; |
|
$this->_maybe_end_section(); |
if (exists($this->{FILE_STATE})) { |
# Handle empty patch sections (if the file has not been started and we're |
# already trying to end it, start it first!) |
if (!$this->{FILE_STARTED}) { |
$this->_start_file(); |
} |
|
# Send end notification and set state |
$this->{TARGET}->end_file($this->{FILE_STATE}); |
delete $this->{FILE_STATE}; |
delete $this->{FILE_STARTED}; |
} |
} |
|
sub _start_file { |
my $this = shift; |
|
# Send start notification and set state |
$this->{TARGET}->start_file($this->{FILE_STATE}); |
$this->{FILE_STARTED} = 1; |
delete $this->{FILE_NEVER_STARTED}; |
} |
|
sub _maybe_end_section { |
my $this = shift; |
if (exists($this->{SECTION_STATE})) { |
$this->{TARGET}->section($this->{SECTION_STATE}); |
delete $this->{SECTION_STATE}; |
} |
} |
|
sub iterate_file { |
my $this = shift; |
my ($filename) = @_; |
|
open FILE, $filename or die "Could not open $this->{FILENAME}: $!"; |
$this->startlines($filename); |
while (<FILE>) { |
$this->rawline($_); |
} |
$this->endlines($filename); |
close FILE; |
} |
|
sub iterate_fh { |
my $this = shift; |
my ($fh, $filename) = @_; |
|
$this->startlines($filename); |
while (<$fh>) { |
$this->rawline($_); |
} |
$this->endlines($filename); |
} |
|
sub iterate_string { |
my $this = shift; |
my ($id, $data) = @_; |
|
$this->startlines($id); |
while ($data =~ /([^\n]*(\n|$))/g) { |
$this->rawline($1); |
} |
$this->endlines($id); |
} |
|
1 |