Bugzilla Version 2.17.1
Diff Between Patch v1.7.0 (#111309) and Patch v1.8.1 (#122880) for Bug #174942
 
Raw Unified
Collapse All Expand All
(+) mozilla/webtools/bugzilla/attachment.cgi (-234 / +261 lines)
Lines 1-18 (Link Here)
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 2003 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): John Keiser <jkeiser@netscape.com>
package CVSClient;
package CVSClient;
use strict;
use strict;
sub parse_cvsroot {
sub parse_cvsroot {
    my $cvsroot = $_[0];
    my $cvsroot = $_[0];
    # Format: :method:[user[:password]@]server[:port]/path
    # Format: :method:[user[:password]@]server[:port]:/path
    if ($cvsroot =~ /^:([^:]*):(.*)(\/.*)$/) {
    if ($cvsroot =~ /^:([^:]*):(.*?)(\/.*)$/) {
        my %retval;
        my %retval;
        $retval{protocol} = $1;
        $retval{protocol} = $1;
        $retval{rootdir} = $3;
        $retval{rootdir} = $3;
        my $remote = $2;
        my $remote = $2;
        print "Hmm $remote\n";
        if ($remote =~ /^(([^\@:]*)(:([^\@]*))?\@)?([^:]*)(:(.*))?$/) {
        if ($remote =~ /^(([^\@:]*)(:([^\@]*))?\@)?([^:]*)(:(.*))?$/) {
            print "$1!$2!$3!$4!$5!$6!$7\n";
            $retval{user} = $2;
            $retval{user} = $2;
            $retval{password} = $4;
            $retval{password} = $4;
            $retval{server} = $5;
            $retval{server} = $5;
Lines 68-88 (Link Here)
  validateID();
  validateID();
  view(); 
  view(); 
}
}
elsif ($action eq "interdiff")
{
  validateID('oldid');
  validateID('newid');
  validateFormat("html", "raw");
  validateContext();
  interdiff();
}
elsif ($action eq "prettyview")
{
  validateID();
  validateFormat("html", "raw");
  validateContext();
  prettyview();
}
elsif ($action eq "viewall") 
elsif ($action eq "viewall") 
{ 
{ 
  ValidateBugID($::FORM{'bugid'});
  ValidateBugID($::FORM{'bugid'});
Lines 147-164 (Link Here)
sub validateID
sub validateID
{
{
    my $id = @_ ? $_[0] : 'id';
    # Validate the value of the "id" form field, which must contain an
    # Validate the value of the "id" form field, which must contain an
    # integer that is the ID of an existing attachment.
    # integer that is the ID of an existing attachment.
    $vars->{'attach_id'} = $::FORM{$id};
    $vars->{'attach_id'} = $::FORM{'id'};
    
    
    detaint_natural($::FORM{$id}) 
    detaint_natural($::FORM{'id'}) 
     || ThrowUserError("invalid_attach_id");
     || ThrowUserError("invalid_attach_id");
  
  
    # Make sure the attachment exists in the database.
    # Make sure the attachment exists in the database.
    SendSQL("SELECT bug_id, isprivate FROM attachments WHERE attach_id = $::FORM{$id}");
    SendSQL("SELECT bug_id, isprivate FROM attachments WHERE attach_id = $::FORM{'id'}");
    MoreSQLData()
    MoreSQLData()
      || ThrowUserError("invalid_attach_id");
      || ThrowUserError("invalid_attach_id");
Lines 170-197 (Link Here)
    }
    }
}
}
sub validateFormat
{
  $::FORM{'format'} ||= $_[0];
  if (! grep { $_ eq $::FORM{'format'} } @_)
  {
     $vars->{'format'} = $::FORM{'format'};
     $vars->{'formats'} = \@_;
     ThrowUserError("invalid_format");
  }
}
sub validateContext
{
  $::FORM{'context'} ||= "patch";
  if ($::FORM{'context'} ne "file" && $::FORM{'context'} ne "patch") {
    $vars->{'context'} = $::FORM{'context'};
    detaint_natural($::FORM{'context'})
      || ThrowUserError("invalid_context");
    delete $vars->{'context'};
  }
}
sub validateCanEdit
sub validateCanEdit
{
{
    my ($attach_id) = (@_);
    my ($attach_id) = (@_);
Lines 423-613 (Link Here)
}
}
sub interdiff
{
  # Get old patch data
  my ($old_bugid, $old_filename) = get_unified_diff($::FORM{'oldid'});
  # Get old patch data
  my ($new_bugid, $new_filename) = get_unified_diff($::FORM{'newid'});
  #
  # send through interdiff, send output directly to template
  #
  # Must hack path so that interdiff will work.
  #
  $ENV{'PATH'} = $::diffpath;
  $ENV{'PATH'} = $::diffpath; # Intentionally done twice to suppress a warning
  open my $interdiff_fh, "$::interdiffbin $old_filename $new_filename|";
  binmode $interdiff_fh;
  my ($iter, $last_iter) = setup_iterators("");
  if ($::FORM{'format'} eq "raw")
  {
    require DiffPrinter::raw;
    $last_iter->target(new DiffPrinter::raw());
    # Actually print out the patch
    print $cgi->header(-type => 'text/plain',
                            -expires => '+3M');
  }
  else
  {
    $vars->{bugid} = $new_bugid;
    $vars->{oldid} = $::FORM{'oldid'};
    $vars->{newid} = $::FORM{'newid'};
    delete $vars->{attachid};
    delete $vars->{do_context};
    delete $vars->{context};
    setup_template_iterator($iter, $last_iter);
  }
  $iter->iterate_fh($interdiff_fh, "interdiff #$::FORM{'oldid'} #$::FORM{'newid'}");
  close $interdiff_fh;
  $ENV{'PATH'} = '';
  #
  # Delete temporary files
  #
  unlink($old_filename) or die "Could not unlink $old_filename: $!";
  unlink($new_filename) or die "Could not unlink $new_filename: $!";
}
sub get_unified_diff
{
  my ($id) = @_;
  # Bring in the modules we need
  require PatchIterator::RawPatchIterator;
  require PatchIterator::FixPatchRootIterator;
  require DiffPrinter::raw;
  require File::Temp;
  # Get the patch
  SendSQL("SELECT bug_id, ispatch, thedata FROM attachments WHERE attach_id = $id");
  my ($bugid, $ispatch, $thedata) = FetchSQLData();
  if (!$ispatch) {
    $vars->{'attach_id'} = $id;
    ThrowUserError("must_be_patch");
  }
  # Reads in the patch, converting to unified diff in a temp file
  my $iter = new PatchIterator::RawPatchIterator;
  # fixes patch root (makes canonical if possible)
  $iter->target(new PatchIterator::FixPatchRootIterator(Param('cvsroot')));
  # Prints out to temporary file
  my ($fh, $filename) = File::Temp::tempfile();
  $iter->target->target(new DiffPrinter::raw($fh));
  # Iterate!
  $iter->iterate_string($id, $thedata);
  return ($bugid, $filename);
}
sub setup_iterators {
  my ($diff_root) = @_;
  #
  # Parameters:
  # format=raw|html
  # context=patch|file|0-n
  # collapsed=0|1
  # headers=0|1
  #
  # Define the iterators
  # The iterator that reads the patch in (whatever its format)
  require PatchIterator::RawPatchIterator;
  my $iter = new PatchIterator::RawPatchIterator;
  my $last_iter = $iter;
  # Fix the patch root if we have a cvs root
  if (Param('cvsroot'))
  {
    require PatchIterator::FixPatchRootIterator;
    $last_iter->target(new PatchIterator::FixPatchRootIterator(Param('cvsroot')));
    $last_iter->target->diff_root($diff_root) if defined($diff_root);
    $last_iter = $last_iter->target;
  }
  # Add in cvs context if we have the necessary info to do it
  if ($::FORM{'context'} ne "patch" && $::cvsbin && Param('cvsroot_get'))
  {
    require PatchIterator::AddCVSContextIterator;
    $last_iter->target(
        new PatchIterator::AddCVSContextIterator($::FORM{'context'},
                                                 Param('cvsroot_get')));
    $last_iter = $last_iter->target;
  }
  return ($iter, $last_iter);
}
sub setup_template_iterator
{
  my ($iter, $last_iter) = @_;
  require DiffPrinter::template;
  my $format = $::FORM{'format'};
  # Define the vars for templates
  $vars->{headers} = $::FORM{'headers'};
  $vars->{collapsed} = $::FORM{'collapsed'};
  $vars->{context} = $::FORM{'context'};
  $vars->{do_context} = $::cvsbin && Param('cvsroot_get') && !$vars->{'newid'};
  # Print everything out
  print $cgi->header(-type => 'text/html',
                     -expires => '+3M');
  $last_iter->target(new DiffPrinter::template($template,
                             "attachment/prettyview-header.$format.tmpl",
                             "attachment/prettyview-file.$format.tmpl",
                             "attachment/prettyview-footer.$format.tmpl",
                             { %{$vars},
                               bonsai_url => Param('bonsai_url'),
                               lxr_url => Param('lxr_url'),
                               lxr_root => Param('lxr_root'),
                             }));
}
sub prettyview
{
  # Get patch data
  SendSQL("SELECT bug_id, ispatch, thedata FROM attachments WHERE attach_id = $::FORM{'id'}");
  my ($bugid, $ispatch, $thedata) = FetchSQLData();
  # If it is not a patch, view normally
  if (!$ispatch)
  {
    view();
    return;
  }
  my ($iter, $last_iter) = setup_iterators();
  if ($::FORM{'format'} eq "raw")
  {
    require DiffPrinter::raw;
    $last_iter->target(new DiffPrinter::raw());
    # Actually print out the patch
    use vars qw($cgi);
    print $cgi->header(-type => 'text/plain',
                       -expires => '+3M');
    $iter->iterate_string("Attachment " . $::FORM{'id'}, $thedata);
  }
  else
  {
    $vars->{other_patches} = [];
    if ($::interdiffbin && $::diffpath) {
      # Get list of attachments on this bug
      SendSQL("SELECT attach_id, description FROM attachments WHERE bug_id = $bugid AND attach_id <> $::FORM{'id'} AND ispatch = 1");
      while (my ($other_id, $other_desc) = FetchSQLData()) {
        push @{$vars->{other_patches}}, { id => $other_id, desc => $other_desc };
      }
    }
    $vars->{bugid} = $bugid;
    $vars->{attachid} = $::FORM{'id'};
    setup_template_iterator($iter, $last_iter);
    # Actually print out the patch
    $iter->iterate_string("Attachment " . $::FORM{'id'}, $thedata);
  }
}
sub viewall
sub viewall
{
{
Lines 80-85 (Link Here)
  validateID();
  validateID();
  view(); 
  view(); 
}
}
elsif ($action eq "interdiff")
{
  validateID('oldid');
  validateID('newid');
  validateFormat("html", "raw");
  validateContext();
  interdiff();
}
elsif ($action eq "diff")
{
  validateID();
  validateFormat("html", "raw");
  validateContext();
  diff();
}
elsif ($action eq "viewall") 
elsif ($action eq "viewall") 
{ 
{ 
  ValidateBugID($::FORM{'bugid'});
  ValidateBugID($::FORM{'bugid'});
Lines 149-164 (Link Here)
sub validateID
sub validateID
{
{
    my $param = @_ ? $_[0] : 'id';
    # Validate the value of the "id" form field, which must contain an
    # Validate the value of the "id" form field, which must contain an
    # integer that is the ID of an existing attachment.
    # integer that is the ID of an existing attachment.
    $vars->{'attach_id'} = $::FORM{'id'};
    $vars->{'attach_id'} = $::FORM{$param};
    
    
    detaint_natural($::FORM{'id'}) 
    detaint_natural($::FORM{$param}) 
     || ThrowUserError("invalid_attach_id");
     || ThrowUserError("invalid_attach_id");
  
  
    # Make sure the attachment exists in the database.
    # Make sure the attachment exists in the database.
    SendSQL("SELECT bug_id, isprivate FROM attachments WHERE attach_id = $::FORM{'id'}");
    SendSQL("SELECT bug_id, isprivate FROM attachments WHERE attach_id = $::FORM{$param}");
    MoreSQLData()
    MoreSQLData()
      || ThrowUserError("invalid_attach_id");
      || ThrowUserError("invalid_attach_id");
Lines 170-175 (Link Here)
    }
    }
}
}
sub validateFormat
{
  $::FORM{'format'} ||= $_[0];
  if (! grep { $_ eq $::FORM{'format'} } @_)
  {
     $vars->{'format'} = $::FORM{'format'};
     $vars->{'formats'} = \@_;
     ThrowUserError("invalid_format");
  }
}
sub validateContext
{
  $::FORM{'context'} ||= "patch";
  if ($::FORM{'context'} ne "file" && $::FORM{'context'} ne "patch") {
    $vars->{'context'} = $::FORM{'context'};
    detaint_natural($::FORM{'context'})
      || ThrowUserError("invalid_context");
    delete $vars->{'context'};
  }
}
sub validateCanEdit
sub validateCanEdit
{
{
    my ($attach_id) = (@_);
    my ($attach_id) = (@_);
Lines 408-413 (Link Here)
    print $thedata;
    print $thedata;
}
}
sub interdiff
{
  # Get old patch data
  my ($old_bugid, $old_description, $old_filename) =
      get_unified_diff($::FORM{'oldid'});
  # Get new patch data
  my ($new_bugid, $new_description, $new_filename) =
      get_unified_diff($::FORM{'newid'});
  #
  # send through interdiff, send output directly to template
  #
  # Must hack path so that interdiff will work.
  #
  $ENV{'PATH'} = $::diffpath;
  open my $interdiff_fh, "$::interdiffbin $old_filename $new_filename|";
  binmode $interdiff_fh;
  my ($iter, $last_iter) = setup_iterators("");
  if ($::FORM{'format'} eq "raw")
  {
    require PatchIterator::DiffPrinter::raw;
    $last_iter->target(new PatchIterator::DiffPrinter::raw());
    # Actually print out the patch
    print $cgi->header(-type => 'text/plain',
                       -expires => '+3M');
  }
  else
  {
    $vars->{bugid} = $new_bugid;
    $vars->{oldid} = $::FORM{'oldid'};
    $vars->{old_desc} = $old_description;
    $vars->{newid} = $::FORM{'newid'};
    $vars->{new_desc} = $new_description;
    delete $vars->{attachid};
    delete $vars->{do_context};
    delete $vars->{context};
    setup_template_iterator($iter, $last_iter);
  }
  $iter->iterate_fh($interdiff_fh, "interdiff #$::FORM{'oldid'} #$::FORM{'newid'}");
  close $interdiff_fh;
  $ENV{'PATH'} = '';
  #
  # Delete temporary files
  #
  unlink($old_filename) or warn "Could not unlink $old_filename: $!";
  unlink($new_filename) or warn "Could not unlink $new_filename: $!";
}
sub get_unified_diff
{
  my ($id) = @_;
  # Bring in the modules we need
  require PatchIterator::Raw;
  require PatchIterator::FixPatchRoot;
  require PatchIterator::DiffPrinter::raw;
  require File::Temp;
  # Get the patch
  SendSQL("SELECT bug_id, description, ispatch, thedata FROM attachments WHERE attach_id = $id");
  my ($bugid, $description, $ispatch, $thedata) = FetchSQLData();
  if (!$ispatch) {
    $vars->{'attach_id'} = $id;
    ThrowCodeError("must_be_patch");
  }
  # Reads in the patch, converting to unified diff in a temp file
  my $iter = new PatchIterator::Raw;
  # fixes patch root (makes canonical if possible)
  $iter->target(new PatchIterator::FixPatchRoot(Param('cvsroot')));
  # Prints out to temporary file
  my ($fh, $filename) = File::Temp::tempfile();
  $iter->target->target(new PatchIterator::DiffPrinter::raw($fh));
  # Iterate!
  $iter->iterate_string($id, $thedata);
  return ($bugid, $description, $filename);
}
sub setup_iterators {
  my ($diff_root) = @_;
  #
  # Parameters:
  # format=raw|html
  # context=patch|file|0-n
  # collapsed=0|1
  # headers=0|1
  #
  # Define the iterators
  # The iterator that reads the patch in (whatever its format)
  require PatchIterator::Raw;
  my $iter = new PatchIterator::Raw;
  my $last_iter = $iter;
  # Fix the patch root if we have a cvs root
  if (Param('cvsroot'))
  {
    require PatchIterator::FixPatchRoot;
    $last_iter->target(new PatchIterator::FixPatchRoot(Param('cvsroot')));
    $last_iter->target->diff_root($diff_root) if defined($diff_root);
    $last_iter = $last_iter->target;
  }
  # Add in cvs context if we have the necessary info to do it
  if ($::FORM{'context'} ne "patch" && $::cvsbin && Param('cvsroot_get'))
  {
    require PatchIterator::AddCVSContext;
    $last_iter->target(
        new PatchIterator::AddCVSContext($::FORM{'context'},
                                         Param('cvsroot_get')));
    $last_iter = $last_iter->target;
  }
  return ($iter, $last_iter);
}
sub setup_template_iterator
{
  my ($iter, $last_iter) = @_;
  require PatchIterator::DiffPrinter::template;
  my $format = $::FORM{'format'};
  # Define the vars for templates
  if (defined($::FORM{'headers'})) {
    $vars->{headers} = $::FORM{'headers'};
  } else {
    $vars->{headers} = 1 if !defined($::FORM{'headers'});
  }
  $vars->{collapsed} = $::FORM{'collapsed'};
  $vars->{context} = $::FORM{'context'};
  $vars->{do_context} = $::cvsbin && Param('cvsroot_get') && !$vars->{'newid'};
  # Print everything out
  print $cgi->header(-type => 'text/html',
                     -expires => '+3M');
  $last_iter->target(new PatchIterator::DiffPrinter::template($template,
                             "attachment/diff-header.$format.tmpl",
                             "attachment/diff-file.$format.tmpl",
                             "attachment/diff-footer.$format.tmpl",
                             { %{$vars},
                               bonsai_url => Param('bonsai_url'),
                               lxr_url => Param('lxr_url'),
                               lxr_root => Param('lxr_root'),
                             }));
}
sub diff
{
  # Get patch data
  SendSQL("SELECT bug_id, description, ispatch, thedata FROM attachments WHERE attach_id = $::FORM{'id'}");
  my ($bugid, $description, $ispatch, $thedata) = FetchSQLData();
  # If it is not a patch, view normally
  if (!$ispatch)
  {
    view();
    return;
  }
  my ($iter, $last_iter) = setup_iterators();
  if ($::FORM{'format'} eq "raw")
  {
    require PatchIterator::DiffPrinter::raw;
    $last_iter->target(new PatchIterator::DiffPrinter::raw());
    # Actually print out the patch
    use vars qw($cgi);
    print $cgi->header(-type => 'text/plain',
                       -expires => '+3M');
    $iter->iterate_string("Attachment " . $::FORM{'id'}, $thedata);
  }
  else
  {
    $vars->{other_patches} = [];
    if ($::interdiffbin && $::diffpath) {
      # Get list of attachments on this bug
      SendSQL("SELECT attach_id, description FROM attachments WHERE bug_id = $bugid AND attach_id <> $::FORM{'id'} AND ispatch = 1");
      while (my ($other_id, $other_desc) = FetchSQLData()) {
        push @{$vars->{other_patches}}, { id => $other_id, desc => $other_desc };
      }
    }
    $vars->{bugid} = $bugid;
    $vars->{attachid} = $::FORM{'id'};
    $vars->{description} = $description;
    setup_template_iterator($iter, $last_iter);
    # Actually print out the patch
    $iter->iterate_string("Attachment " . $::FORM{'id'}, $thedata);
  }
}
sub viewall
sub viewall
{
{
(+) mozilla/webtools/bugzilla/checksetup.pl (-1 / +1 lines)
Lines 450-456 (Link Here)
    # If which didn't find it, set to blank
    # If which didn't find it, set to blank
    $interdiff_executable = "";
    $interdiff_executable = "";
} else {
} else {
    $interdiff_executable =~ s/\r?\n$//;
    chomp $interdiff_executable;
}
}
LocalVar('interdiffbin', <<"END");
LocalVar('interdiffbin', <<"END");
(+) mozilla/webtools/bugzilla/PatchIterator/RawPatchIterator.pm (-1255 / +67 lines)
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
(+) mozilla/webtools/bugzilla/template/en/default/attachment/edit.html.tmpl (-2 / +2 lines)
Lines 95-101 (Link Here)
      // If we have not viewed as diff before, set the view diff frame URL
      // If we have not viewed as diff before, set the view diff frame URL
      if (!has_viewed_as_diff) {
      if (!has_viewed_as_diff) {
        var viewDiffFrame = document.getElementById('viewDiffFrame');
        var viewDiffFrame = document.getElementById('viewDiffFrame');
        viewDiffFrame.src = 'attachment.cgi?id=[% attachid %]&action=prettyview&headers=0';
        viewDiffFrame.src = 'attachment.cgi?id=[% attachid %]&action=diff&headers=0';
        has_viewed_as_diff = 1;
        has_viewed_as_diff = 1;
      }
      }
    }
    }
Lines 225-231 (Link Here)
        <input type="submit" value="Submit"><br><br>
        <input type="submit" value="Submit"><br><br>
        <strong>Actions:</strong> <a href="attachment.cgi?id=[% attachid %]">View</a>
        <strong>Actions:</strong> <a href="attachment.cgi?id=[% attachid %]">View</a>
        [% IF ispatch %]
        [% IF ispatch %]
         | <a href="attachment.cgi?id=[% attachid %]&action=prettyview">Diff</a>
         | <a href="attachment.cgi?id=[% attachid %]&action=diff">Diff</a>
        [% END %]
        [% END %]
        </small>
        </small>
      </td>
      </td>
(+) mozilla/webtools/bugzilla/template/en/default/attachment/prettyview-header.html.tmpl (-351 / +1 lines)
Removed (Link Here)
	  [% IF attachment.canedit %]
	  [% IF attachment.canedit %]
	    |
	    |
	  [% END %]
	  [% END %]
          <a href="attachment.cgi?id=[% attachment.attachid %]&amp;action=prettyview">Diff</a>
          <a href="attachment.cgi?id=[% attachment.attachid %]&amp;action=diff">Diff</a>
	[% END %]
	[% END %]
      </td>
      </td>
    </tr>
    </tr>
Removed (Link Here)
<!-- 1.0@bugzilla.org -->
[%# The contents of this file are subject to the Mozilla Public
  # License Version 1.1 (the "License"); you may not use this file
  # except in compliance with the License. You may obtain a copy of
  # the License at http://www.mozilla.org/MPL/
  #
  # Software distributed under the License is distributed on an "AS
  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  # implied. See the License for the specific language governing
  # rights and limitations under the License.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): John Keiser <jkeiser@netscape.com>
  #%]
<table class="file_table"><thead><tr><td class="file_head" colspan="2"><a href="#" onclick="return twisty_click(this)">[% collapsed ? '(+)' : '(-)' %]</a><input type="checkbox" name="[% file.filename %]"[% collapsed ? '' : ' checked' %] style="display: none"> 
  [% IF lxr_prefix && !file.is_add %]
    <a href="[% lxr_prefix %]">[% file.filename %]</a>
  [% ELSE %]
    [% file.filename %]
  [% END %]
  [% IF file.plus_lines %]
    [% IF file.minus_lines %]
      (-[% file.minus_lines %]&nbsp;/&nbsp;+[% file.plus_lines %]&nbsp;lines)
    [% ELSE %]
      (+[% file.plus_lines %]&nbsp;lines)
    [% END %]
  [% ELSE %]
    [% IF file.minus_lines %]
      (-[% file.minus_lines %]&nbsp;lines)
    [% END %]
  [% END %]
</td></tr></thead><tbody class="[% collapsed ? 'file_collapse' : 'file' %]">
<script type="application/x-javascript" language="JavaScript">incremental_restore()</script>
[% section_num = 0 %]
[% FOREACH section = sections %]
  [% section_num = section_num + 1 %]
  <tr><th class="section_head" colspan="2">
  [% IF file.is_add %]
    Added
  [% ELSIF file.is_remove %]
    [% IF bonsai_prefix %]
      <a href="[% bonsai_prefix %]">Removed</a>
    [% ELSE %]
      Removed
    [% END %]
  [% ELSE %]
    [% IF bonsai_prefix %]
      <a href="[% bonsai_prefix %]#[% section.old_start %]">
    [% END %]
    [% IF section.old_lines > 1 %]
      Lines [% section.old_start %]-[% section.old_start + section.old_lines - 1 %]
    [% ELSE %]
      Line [% section.old_start %]
    [% END %]
    [% IF bonsai_prefix %]
      </a>
    [% END %]
  [% END %] 
  (<a name="[% file.filename %]_sec[% section_num %]"><a href="#[% file.filename %]_sec[% section_num %]">Link Here</a></a>)
  </th></tr>
  [% FOREACH group = section.groups %]
    [% IF group.context %]
      [% FOREACH line = group.context %]
        <tr><td><pre>[% line FILTER html %]</pre></td><td><pre>[% line FILTER html %]</pre></td></tr>
      [% END %]
    [% END %]
    [% IF group.plus.size %]
      [% IF group.minus.size %]
        [% i = 0 %]
        [% WHILE (i < group.plus.size || i < group.minus.size) %]
          [% currentloop = 0 %]
          [% WHILE currentloop < 500 && (i < group.plus.size || i < group.minus.size) %]
            <tr class=changed><td><pre>[% group.minus.$i FILTER html %]</pre></td><td><pre>[% group.plus.$i FILTER html %]</pre></td></tr>
            [% currentloop = currentloop + 1 %]
            [% i = i + 1 %]
          [% END %]
        [% END %]
      [% ELSE %]
        [% FOREACH line = group.plus %]
          [% IF file.is_add %]
            <tr><td class=added colspan=2><pre>[% line FILTER html %]</pre></td></tr>
          [% ELSE %]
            <tr><td></td><td class=added><pre>[% line FILTER html %]</pre></td></tr>
          [% END %]
        [% END %]
      [% END %]
    [% ELSE %]
      [% IF group.minus.size %]
        [% FOREACH line = group.minus %]
          [% IF file.is_remove %]
            <tr><td class=removed colspan=2><pre>[% line FILTER html %]</pre></td></tr>
          [% ELSE %]
            <tr><td class=removed><pre>[% line FILTER html %]</pre></td><td></td></tr>
          [% END %]
        [% END %]
      [% END %]
    [% END %]
  [% END %]
[% END %]
</table>
Removed (Link Here)
<!-- 1.0@bugzilla.org -->
[%# The contents of this file are subject to the Mozilla Public
  # License Version 1.1 (the "License"); you may not use this file
  # except in compliance with the License. You may obtain a copy of
  # the License at http://www.mozilla.org/MPL/
  #
  # Software distributed under the License is distributed on an "AS
  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  # implied. See the License for the specific language governing
  # rights and limitations under the License.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): John Keiser <jkeiser@netscape.com>
  #%]
</form>
[% IF headers != 0 %]
  [% headers = 1 %]
[% END %]
[% IF headers %]
  <br>
  [% PROCESS global/footer.html.tmpl %]
 
[% ELSE %]
</body>
</html>
[% END %]
Removed (Link Here)
<!-- 1.0@bugzilla.org -->
[%# The contents of this file are subject to the Mozilla Public
  # License Version 1.1 (the "License"); you may not use this file
  # except in compliance with the License. You may obtain a copy of
  # the License at http://www.mozilla.org/MPL/
  #
  # Software distributed under the License is distributed on an "AS
  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  # implied. See the License for the specific language governing
  # rights and limitations under the License.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): John Keiser <jkeiser@netscape.com>
  #%]
[%# Define strings that will serve as the title and header of this page %]
[% title = BLOCK %]Attachment #[% attachid %] for Bug #[% bugid %][% END %]
[% style = BLOCK %]
.file_head { font-size: x-large; font-weight: bold; background-color: #d3d3d3; border: 1px solid black; width: 100% }
.file_collapse { display: none }
.section_head { width: 100%; font-weight: bold; background-color: #d3d3d3; border: 1px solid black; text-align: left }
table.file_table { table-layout: fixed; width: 100%; empty-cells: show; border-spacing: 0px; border-collapse: collapse }
tbody.file td { border-left: 1px dashed black; border-right: 1px dashed black; width: 50%; }
tbody.file pre { display: inline; white-space: -moz-pre-wrap; font-size: 0.9em; }
tbody.file pre:empty { display: block; height: 1em }
.changed { background-color: lightblue }
.added { background-color: lightgreen }
.removed { background-color: #FFCC99 }
[% END %]
[%# SCRIPT FUNCTIONS %]
[% javascript = BLOCK %]
  function collapse_all(expand) {
    var elem = document.checkboxform.firstChild;
    while (elem != null) {
      if (elem.firstChild != null) {
        if (expand) {
          var tbody = elem.firstChild.nextSibling;
          if (tbody.className == 'file_collapse') {
            tbody.className = 'file';
            twisty = get_twisty_from_tbody(tbody);
            twisty.firstChild.nodeValue = '(-)';
            twisty.nextSibling.checked = true;
          }
        } else {
          var tbody = elem.firstChild.nextSibling;
          if (tbody.className == 'file') {
            tbody.className = 'file_collapse';
            twisty = get_twisty_from_tbody(tbody);
            twisty.firstChild.nodeValue = '(+)';
            twisty.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;
      if (current_restore_elem.firstChild != null) {
        restore_elem(current_restore_elem.firstChild.nextSibling);
      }
      next_restore_elem = current_restore_elem.nextSibling;
    }
  }
  function restore_elem(elem, alertme) {
    if (elem.className == 'file_collapse') {
      twisty = get_twisty_from_tbody(elem);
      if (twisty.nextSibling.checked) {
        elem.className = 'file';
        twisty.firstChild.nodeValue = '(-)';
      }
    } else if (elem.className == 'file') {
      twisty = get_twisty_from_tbody(elem);
      if (!twisty.nextSibling.checked) {
        elem.className = 'file_collapse';
        twisty.firstChild.nodeValue = '(+)';
      }
    }
  }
  function twisty_click(twisty) {
    tbody = get_tbody_from_twisty(twisty);
    if (tbody.className == 'file') {
      tbody.className = 'file_collapse';
      twisty.firstChild.nodeValue = '(+)';
      twisty.nextSibling.checked = false;
    } else {
      tbody.className = 'file';
      twisty.firstChild.nodeValue = '(-)';
      twisty.nextSibling.checked = true;
    }
    return false;
  }
  function get_tbody_from_twisty(twisty) {
    return twisty.parentNode.parentNode.parentNode.nextSibling;
  }
  function get_twisty_from_tbody(tbody) {
    return tbody.previousSibling.firstChild.firstChild.firstChild;
  }
[% END %]
[% onload = 'restore_all(); document.checkboxform.restore_indicator.checked = true' %]
[% IF headers != 0 %]
  [% headers = 1 %]
[% END %]
[% IF headers %]
  [% h1 = BLOCK %][% IF attachid %]Attachment #[% attachid %][% ELSE %]Diff Between Attachments #<a href="attachment.cgi?action=prettyview&id=[% oldid %]">[% oldid %]</a> and <a href="attachment.cgi?action=prettyview&id=[% newid %]">[% newid %]</a>[% END %] for <a href="show_bug.cgi?id=[% bugid %]">Bug #[% bugid %]</a>[% END %]
  [% h2 = BLOCK %][% bugsummary FILTER html %][% END %]
  [% PROCESS global/header.html.tmpl 
    title = title
    h1 = h1
    h2 = h2
    style = style
    onload = onload
    javascript = javascript
  %]
[% ELSE %]
<html>
<head>
<style type="text/css">
[% style %]
</style>
<script type="text/javascript" language="JavaScript">
<!--
[% javascript %]
</script>
</head>
<body onload="[% onload FILTER html %]">
[% END %]
[%# If we have attachid, we are in prettyview, otherwise we're in interdiff %]
[% IF attachid %]
  [%# HEADER %]
  [% IF headers %]
    [% USE url('attachment.cgi', id = attachid) %]
    <a href="[% url() %]">View</a>
    | <a href="[% url(action = 'edit') %]">Edit</a>
    [% USE url('attachment.cgi', id = attachid, context = context, collapsed = collapsed, headers = headers, action = 'prettyview') %]
    | <a href="[% url(format = 'raw') %]">Raw Unified</a>
  [% END %]
  [% IF other_patches %]
    [% IF headers %]|[%END%] Differences between <form style="display: inline"><select name="oldid">[% FOREACH patch = other_patches %]
      <option value="[% patch.id %]">[% patch.desc %]</option>
    [% END %]
    </select> and this patch <input type="submit" value="Diff"><input type="hidden" name="action" value="interdiff"><input type="hidden" name="newid" value="[% attachid %]"><input type="hidden" name="headers" value="[% headers FILTER html %]"></form>
  [% END %]
  <br>
[% ELSE %]
  [% USE url('attachment.cgi', newid = newid, oldid = oldid, action = 'interdiff') %]
  <a href="[% url(format = 'raw') %]">Raw Unified</a>
  <br>
[% END %]
  
[%# Collapse / Expand %]
<a href="#" onclick="return collapse_all(false)">Collapse All</a>
| <a href="#" onclick="return collapse_all(true)">Expand All</a>
[% IF do_context %]
  | <span style='font-weight: bold'>Context:</span>
  (<a href="[% url(context = '') %]">Patch</a> / 
  <a href="[% url(context = 'file') %]">File</a> / 
  [% IF context == "patch" || context == "file" %]
    [% context = 3 %]
  [% END %]
  [%# textbox for context %]
  <form style="display: inline"><input type="hidden" name="action" value="prettyview"><input type="hidden" name="id" value="[% attachid %]"><input type="hidden" name="collapsed" value="[% collapsed FILTER html %]"><input type="hidden" name="headers" value="[% headers FILTER html %]"><input type="text" name="context" value="[% context FILTER html %]" size="3"></form>)
[% END %]
[%# Restore Stuff %]
<form name="checkboxform">
<input type="checkbox" name="restore_indicator" style="display: none">
(-) mozilla/webtools/bugzilla/globals.pl (-24 / +379 lines)
Added (Link Here)
    is either <em>application, audio, image, message, model, multipart, 
    is either <em>application, audio, image, message, model, multipart, 
    text,</em> or <em>video</em>.
    text,</em> or <em>video</em>.
    
    
  [% ELSIF error == "invalid_context" %]
    [% title = "Invalid Context" %]
    The context [% context FILTER html %] is invalid (must be a number,
    "file" or "patch").
  [% ELSIF error == "invalid_format" %]
    [% title = "Invalid Format" %]
    The format "[% format FILTER html %]" is invalid (must be one of
    [% FOREACH my_format = formats %]
      "[% my_format FILTER html %]"
    [% END %]
    ).
  [% ELSIF error == "invalid_maxrow" %]
  [% ELSIF error == "invalid_maxrow" %]
    [% title = "Invalid Max Rows" %]
    [% title = "Invalid Max Rows" %]
    The maximum number of rows, '[% maxrows FILTER html %]', must be a positive
    The maximum number of rows, '[% maxrows FILTER html %]', must be a positive
Added (Link Here)
    is either <em>application, audio, image, message, model, multipart, 
    is either <em>application, audio, image, message, model, multipart, 
    text,</em> or <em>video</em>.
    text,</em> or <em>video</em>.
    
    
  [% ELSIF error == "invalid_context" %]
    [% title = "Invalid Context" %]
    The context [% context FILTER html %] is invalid (must be a number,
    "file" or "patch").
  [% ELSIF error == "invalid_format" %]
    [% title = "Invalid Format" %]
    The format "[% format FILTER html %]" is invalid (must be one of
    [% FOREACH my_format = formats %]
      "[% my_format FILTER html %]"
    [% END %]
    ).
  [% ELSIF error == "invalid_maxrow" %]
  [% ELSIF error == "invalid_maxrow" %]
    [% title = "Invalid Max Rows" %]
    [% title = "Invalid Max Rows" %]
    The maximum number of rows, '[% maxrows FILTER html %]', must be a positive
    The maximum number of rows, '[% maxrows FILTER html %]', must be a positive
Added (Link Here)
    The query named <em>[% queryname FILTER html %]</em> seems to no longer
    The query named <em>[% queryname FILTER html %]</em> seems to no longer
    exist.
    exist.
        
        
  [% ELSIF error == "must_be_patch" %]
    [% title = "Attachment Must Be Patch" %]
    Attachment #[% attach_id FILTER html %] must be a patch.
  [% ELSIF error == "need_component" %]
  [% ELSIF error == "need_component" %]
    [% title = "Component Required" %]
    [% title = "Component Required" %]
    You must specify a component to help determine the new owner of these bugs.                            
    You must specify a component to help determine the new owner of these bugs.                            
Added (Link Here)
    The query named <em>[% queryname FILTER html %]</em> does not
    The query named <em>[% queryname FILTER html %]</em> does not
    exist.
    exist.
        
        
  [% ELSIF error == "must_be_patch" %]
    [% title = "Attachment Must Be Patch" %]
    Attachment #[% attach_id FILTER html %] must be a patch.
  [% ELSIF error == "need_component" %]
  [% ELSIF error == "need_component" %]
    [% title = "Component Required" %]
    [% title = "Component Required" %]
    You must specify a component to help determine the new owner of these bugs.                            
    You must specify a component to help determine the new owner of these bugs.                            
Added (Link Here)
<!-- 1.0@bugzilla.org -->
[%# The contents of this file are subject to the Mozilla Public
  # License Version 1.1 (the "License"); you may not use this file
  # except in compliance with the License. You may obtain a copy of
  # the License at http://www.mozilla.org/MPL/
  #
  # Software distributed under the License is distributed on an "AS
  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  # implied. See the License for the specific language governing
  # rights and limitations under the License.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): John Keiser <jkeiser@netscape.com>
  #%]
[%# Define strings that will serve as the title and header of this page %]
[% title = BLOCK %]Attachment #[% attachid %] for Bug #[% bugid %][% END %]
[% style = BLOCK %]
.file_head { font-size: x-large; font-weight: bold; background-color: #d3d3d3; border: 1px solid black; width: 100% }
.file_collapse { display: none }
.section_head { width: 100%; font-weight: bold; background-color: #d3d3d3; border: 1px solid black; text-align: left }
table.file_table { table-layout: fixed; width: 100%; empty-cells: show; border-spacing: 0px; border-collapse: collapse }
tbody.file td { border-left: 1px dashed black; border-right: 1px dashed black; width: 50%; }
tbody.file pre { display: inline; white-space: -moz-pre-wrap; font-size: 0.9em; }
tbody.file pre:empty { display: block; height: 1em }
.changed { background-color: lightblue }
.added { background-color: lightgreen }
.removed { background-color: #FFCC99 }
[% END %]
[%# SCRIPT FUNCTIONS %]
[% javascript = BLOCK %]
  function collapse_all() {
    var elem = document.checkboxform.firstChild;
    while (elem != null) {
      if (elem.firstChild != null) {
        var tbody = elem.firstChild.nextSibling;
        if (tbody.className == 'file') {
          tbody.className = 'file_collapse';
          twisty = get_twisty_from_tbody(tbody);
          twisty.firstChild.nodeValue = '(+)';
          twisty.nextSibling.checked = false;
        }
      }
      elem = elem.nextSibling;
    }
    return false;
  }
  function expand_all() {
    var elem = document.checkboxform.firstChild;
    while (elem != null) {
      if (elem.firstChild != null) {
        var tbody = elem.firstChild.nextSibling;
        if (tbody.className == 'file_collapse') {
          tbody.className = 'file';
          twisty = get_twisty_from_tbody(tbody);
          twisty.firstChild.nodeValue = '(-)';
          twisty.nextSibling.checked = true;
        }
      }
      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;
      if (current_restore_elem.firstChild != null) {
        restore_elem(current_restore_elem.firstChild.nextSibling);
      }
      next_restore_elem = current_restore_elem.nextSibling;
    }
  }
  function restore_elem(elem, alertme) {
    if (elem.className == 'file_collapse') {
      twisty = get_twisty_from_tbody(elem);
      if (twisty.nextSibling.checked) {
        elem.className = 'file';
        twisty.firstChild.nodeValue = '(-)';
      }
    } else if (elem.className == 'file') {
      twisty = get_twisty_from_tbody(elem);
      if (!twisty.nextSibling.checked) {
        elem.className = 'file_collapse';
        twisty.firstChild.nodeValue = '(+)';
      }
    }
  }
  function twisty_click(twisty) {
    tbody = get_tbody_from_twisty(twisty);
    if (tbody.className == 'file') {
      tbody.className = 'file_collapse';
      twisty.firstChild.nodeValue = '(+)';
      twisty.nextSibling.checked = false;
    } else {
      tbody.className = 'file';
      twisty.firstChild.nodeValue = '(-)';
      twisty.nextSibling.checked = true;
    }
    return false;
  }
  function get_tbody_from_twisty(twisty) {
    return twisty.parentNode.parentNode.parentNode.nextSibling;
  }
  function get_twisty_from_tbody(tbody) {
    return tbody.previousSibling.firstChild.firstChild.firstChild;
  }
[% END %]
[% onload = 'restore_all(); document.checkboxform.restore_indicator.checked = true' %]
[% IF headers %]
  [% h1 = BLOCK %][% IF attachid %][% description FILTER html %] (Attachment #[% attachid %])[% ELSE %]Diff Between [% old_desc FILTER html %] (#<a href="attachment.cgi?action=diff&amp;id=[% oldid %]">[% oldid %]</a>) and [% new_desc FILTER html %] (#<a href="attachment.cgi?action=diff&amp;id=[% newid %]">[% newid %]</a>)[% END %] for <a href="show_bug.cgi?id=[% bugid %]">Bug #[% bugid %]</a>[% END %]
  [% h2 = BLOCK %][% bugsummary FILTER html %][% END %]
  [% PROCESS global/header.html.tmpl %]
[% ELSE %]
  <html>
  <head>
  <style type="text/css">
  [% style %]
  </style>
  <script type="text/javascript" language="JavaScript">
  <!--
  [% javascript %]
  -->
  </script>
  </head>
  <body onload="[% onload FILTER html %]">
[% END %]
  
[%# If we have attachid, we are in diff, otherwise we're in interdiff %]
[% IF attachid %]
  [%# HEADER %]
  [% IF headers %]
    [% USE url('attachment.cgi', id = attachid) %]
    <a href="[% url() %]">View</a>
    | <a href="[% url(action = 'edit') %]">Edit</a>
    [% USE url('attachment.cgi', id = attachid, context = context, collapsed = collapsed, headers = headers, action = 'diff') %]
    | <a href="[% url(format = 'raw') %]">Raw Unified</a>
  [% END %]
  [% IF other_patches %]
    [% IF headers %]|[%END%] Differences between <form style="display: inline"><select name="oldid">[% FOREACH patch = other_patches %]
      <option value="[% patch.id %]">[% patch.desc FILTER html %]</option>
    [% END %]
    </select> and this patch <input type="submit" value="Diff"><input type="hidden" name="action" value="interdiff"><input type="hidden" name="newid" value="[% attachid %]"><input type="hidden" name="headers" value="[% headers FILTER html %]"></form>
  [% END %]
  <br>
[% ELSE %]
  [% IF headers %]
    [% USE url('attachment.cgi', newid = newid, oldid = oldid, action = 'interdiff') %]
    <a href="[% url(format = 'raw') %]">Raw Unified</a>
    <br>
  [% END %]
[% END %]
  
[%# Collapse / Expand %]
<a href="#" onmouseover="lastStatus = window.status; window.status='Collapse All'; return true" onmouseout="window.status = lastStatus; return true" onclick="return collapse_all()">Collapse All</a>
<a href="#" onmouseover="lastStatus = window.status; window.status='Expand All'; return true" onmouseout="window.status = lastStatus; return true" onclick="return expand_all()">Expand All</a>
[% IF do_context %]
  | <span style='font-weight: bold'>Context:</span>
  [% IF context == "patch" %]
    (<strong>Patch</strong> / 
  [% ELSE %]
    (<a href="[% url(context = '') %]">Patch</a> / 
  [% END %]
  [% IF context == "file" %]
    <strong>File</strong> /
  [% ELSE %]
    <a href="[% url(context = 'file') %]">File</a> / 
  [% END %]
  [% IF context == "patch" || context == "file" %]
    [% context = 3 %]
  [% END %]
  [%# textbox for context %]
  <form style="display: inline"><input type="hidden" name="action" value="diff"><input type="hidden" name="id" value="[% attachid %]"><input type="hidden" name="collapsed" value="[% collapsed FILTER html %]"><input type="hidden" name="headers" value="[% headers FILTER html %]"><input type="text" name="context" value="[% context FILTER html %]" size="3"></form>)
[% END %]
[%# Restore Stuff %]
<form name="checkboxform">
<input type="checkbox" name="restore_indicator" style="display: none">
Added (Link Here)
<!-- 1.0@bugzilla.org -->
[%# The contents of this file are subject to the Mozilla Public
  # License Version 1.1 (the "License"); you may not use this file
  # except in compliance with the License. You may obtain a copy of
  # the License at http://www.mozilla.org/MPL/
  #
  # Software distributed under the License is distributed on an "AS
  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  # implied. See the License for the specific language governing
  # rights and limitations under the License.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): John Keiser <jkeiser@netscape.com>
  #%]
</form>
[% IF headers %]
  <br>
  [% PROCESS global/footer.html.tmpl %]
 
[% ELSE %]
</body>
</html>
[% END %]
Added (Link Here)
<!-- 1.0@bugzilla.org -->
[%# The contents of this file are subject to the Mozilla Public
  # License Version 1.1 (the "License"); you may not use this file
  # except in compliance with the License. You may obtain a copy of
  # the License at http://www.mozilla.org/MPL/
  #
  # Software distributed under the License is distributed on an "AS
  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  # implied. See the License for the specific language governing
  # rights and limitations under the License.
  #
  # The Original Code is the Bugzilla Bug Tracking System.
  #
  # The Initial Developer of the Original Code is Netscape Communications
  # Corporation. Portions created by Netscape are
  # Copyright (C) 1998 Netscape Communications Corporation. All
  # Rights Reserved.
  #
  # Contributor(s): John Keiser <jkeiser@netscape.com>
  #%]
<table class="file_table"><thead><tr><td class="file_head" colspan="2"><a href="#" onclick="return twisty_click(this)">[% collapsed ? '(+)' : '(-)' %]</a><input type="checkbox" name="[% file.filename FILTER html %]"[% collapsed ? '' : ' checked' %] style="display: none"> 
  [% IF lxr_prefix && !file.is_add %]
    <a href="[% lxr_prefix %]">[% file.filename FILTER html %]</a>
  [% ELSE %]
    [% file.filename FILTER html %]
  [% END %]
  [% IF file.plus_lines %]
    [% IF file.minus_lines %]
      (-[% file.minus_lines %]&nbsp;/&nbsp;+[% file.plus_lines %]&nbsp;lines)
    [% ELSE %]
      (+[% file.plus_lines %]&nbsp;lines)
    [% END %]
  [% ELSE %]
    [% IF file.minus_lines %]
      (-[% file.minus_lines %]&nbsp;lines)
    [% END %]
  [% END %]
</td></tr></thead><tbody class="[% collapsed ? 'file_collapse' : 'file' %]">
<script type="application/x-javascript" language="JavaScript">incremental_restore()</script>
[% section_num = 0 %]
[% FOREACH section = sections %]
  [% section_num = section_num + 1 %]
  <tr><th class="section_head" colspan="2">
  [% IF file.is_add %]
    Added
  [% ELSIF file.is_remove %]
    [% IF bonsai_prefix %]
      <a href="[% bonsai_prefix %]">Removed</a>
    [% ELSE %]
      Removed
    [% END %]
  [% ELSE %]
    [% IF bonsai_prefix %]
      <a href="[% bonsai_prefix %]#[% section.old_start %]">
    [% END %]
    [% IF section.old_lines > 1 %]
      Lines [% section.old_start %]-[% section.old_start + section.old_lines - 1 %]
    [% ELSE %]
      Line [% section.old_start %]
    [% END %]
    [% IF bonsai_prefix %]
      </a>
    [% END %]
  [% END %] 
  (<a name="[% file.filename FILTER html %]_sec[% section_num %]"><a href="#[% file.filename FILTER html %]_sec[% section_num %]">Link Here</a></a>)
  </th></tr>
  [% FOREACH group = section.groups %]
    [% IF group.context %]
      [% FOREACH line = group.context %]
        <tr><td><pre>[% line FILTER html %]</pre></td><td><pre>[% line FILTER html %]</pre></td></tr>
      [% END %]
    [% END %]
    [% IF group.plus.size %]
      [% IF group.minus.size %]
        [% i = 0 %]
        [% WHILE (i < group.plus.size || i < group.minus.size) %]
          [% currentloop = 0 %]
          [% WHILE currentloop < 500 && (i < group.plus.size || i < group.minus.size) %]
            <tr class="changed"><td><pre>[% group.minus.$i FILTER html %]</pre></td><td><pre>[% group.plus.$i FILTER html %]</pre></td></tr>
            [% currentloop = currentloop + 1 %]
            [% i = i + 1 %]
          [% END %]
        [% END %]
      [% ELSE %]
        [% FOREACH line = group.plus %]
          [% IF file.is_add %]
            <tr><td class="added" colspan="2"><pre>[% line FILTER html %]</pre></td></tr>
          [% ELSE %]
            <tr><td></td><td class="added"><pre>[% line FILTER html %]</pre></td></tr>
          [% END %]
        [% END %]
      [% END %]
    [% ELSE %]
      [% IF group.minus.size %]
        [% FOREACH line = group.minus %]
          [% IF file.is_remove %]
            <tr><td class="removed" colspan="2"><pre>[% line FILTER html %]</pre></td></tr>
          [% ELSE %]
            <tr><td class="removed"><pre>[% line FILTER html %]</pre></td><td></td></tr>
          [% END %]
        [% END %]
      [% END %]
    [% END %]
  [% END %]
[% END %]
</table>
Added (Link Here)
use Date::Format;               # For time2str().
use Date::Format;               # For time2str().
use Date::Parse;               # For str2time().
use Date::Parse;               # For str2time().
#use Carp;                       # for confess
use Carp;                       # for confess
use RelationSet;
use RelationSet;
# Use standard Perl libraries for cross-platform file/directory manipulation.
# Use standard Perl libraries for cross-platform file/directory manipulation.
Added (Link Here)
$::defaultqueryname = "(Default query)"; # This string not exposed in UI
$::defaultqueryname = "(Default query)"; # This string not exposed in UI
$::unconfirmedstate = "UNCONFIRMED";
$::unconfirmedstate = "UNCONFIRMED";
#sub die_with_dignity {
sub die_with_dignity {
#    my ($err_msg) = @_;
    my ($err_msg) = @_;
#    print $err_msg;
    print $err_msg;
#    confess($err_msg);
    confess($err_msg);
#}
}
#$::SIG{__DIE__} = \&die_with_dignity;
$::SIG{__DIE__} = \&die_with_dignity;
@::default_column_list = ("bug_severity", "priority", "rep_platform", 
@::default_column_list = ("bug_severity", "priority", "rep_platform", 
                          "assigned_to", "bug_status", "resolution",
                          "assigned_to", "bug_status", "resolution",

Actions: New | Query | bug # | Reports | Requests   New Account | Log In