summaryrefslogtreecommitdiffstats
path: root/Bugzilla/PatchReader/Raw.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/PatchReader/Raw.pm')
-rw-r--r--Bugzilla/PatchReader/Raw.pm268
1 files changed, 268 insertions, 0 deletions
diff --git a/Bugzilla/PatchReader/Raw.pm b/Bugzilla/PatchReader/Raw.pm
new file mode 100644
index 000000000..b58ed3a2d
--- /dev/null
+++ b/Bugzilla/PatchReader/Raw.pm
@@ -0,0 +1,268 @@
+package Bugzilla::PatchReader::Raw;
+
+#
+# USAGE:
+# use PatchReader::Raw;
+# my $parser = new PatchReader::Raw();
+# $parser->sends_data_to($my_target);
+# $parser->start_lines();
+# open FILE, "mypatch.patch";
+# while (<FILE>) {
+# $parser->next_line($_);
+# }
+# $parser->end_lines();
+#
+
+use strict;
+
+use Bugzilla::PatchReader::Base;
+
+@Bugzilla::PatchReader::Raw::ISA = qw(Bugzilla::PatchReader::Base);
+
+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 })
+# next_section({ old_start, new_start, old_lines, new_lines, @lines })
+# end_patch
+# end_file
+sub next_line {
+ my $this = shift;
+ my ($line) = @_;
+
+ return if $line =~ /^\?/;
+
+ # patch header parsing
+ if ($line =~ /^---\s*([\S ]+)\s*\t([^\t\r\n]*)\s*(\S*)/) {
+ $this->_maybe_end_file();
+
+ 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*)\s*(?:@@\s*(.*))?/) {
+ $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,
+ func_info => $5,
+ 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 (only when inside a section)
+ return if $this->{IN_HEADER};
+ if ($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 start_lines {
+ my $this = shift;
+ die "No target specified: call sends_data_to!" if !$this->{TARGET};
+ delete $this->{FILE_STARTED};
+ delete $this->{FILE_STATE};
+ delete $this->{SECTION_STATE};
+ $this->{FILE_NEVER_STARTED} = 1;
+
+ $this->{TARGET}->start_patch(@_);
+}
+
+sub end_lines {
+ my $this = shift;
+ $this->_maybe_end_file();
+ $this->{TARGET}->end_patch(@_);
+}
+
+sub _init_state {
+ my $this = shift;
+ $this->{SECTION_STATE}{minus_lines} ||= 0;
+ $this->{SECTION_STATE}{plus_lines} ||= 0;
+}
+
+sub _maybe_start_file {
+ my $this = shift;
+ $this->_init_state();
+ if (exists($this->{FILE_STATE}) && !$this->{FILE_STARTED} ||
+ $this->{FILE_NEVER_STARTED}) {
+ $this->_start_file();
+ }
+}
+
+sub _maybe_end_file {
+ my $this = shift;
+ $this->_init_state();
+ 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
+ if (!$this->{FILE_STATE}) {
+ $this->{FILE_STATE} = { filename => "file_not_specified_in_diff" };
+ }
+
+ # 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}->next_section($this->{SECTION_STATE});
+ delete $this->{SECTION_STATE};
+ }
+}
+
+sub iterate_file {
+ my $this = shift;
+ my ($filename) = @_;
+
+ open FILE, $filename or die "Could not open $filename: $!";
+ $this->start_lines($filename);
+ while (<FILE>) {
+ $this->next_line($_);
+ }
+ $this->end_lines($filename);
+ close FILE;
+}
+
+sub iterate_fh {
+ my $this = shift;
+ my ($fh, $filename) = @_;
+
+ $this->start_lines($filename);
+ while (<$fh>) {
+ $this->next_line($_);
+ }
+ $this->end_lines($filename);
+}
+
+sub iterate_string {
+ my $this = shift;
+ my ($id, $data) = @_;
+
+ $this->start_lines($id);
+ while ($data =~ /([^\n]*(\n|$))/g) {
+ $this->next_line($1);
+ }
+ $this->end_lines($id);
+}
+
+1