############################################################################### # Codestriker: Copyright (c) 2001,2002,2003 David Sitsky. All rights reserved. # sits@users.sourceforge.net # # This program is free software; you can redistribute it and modify it under # the terms of the GPL. # Subversion repository access package. package Codestriker::Repository::Subversion; use IPC::Open3; use strict; use Fatal qw / open close /; # Constructor, which takes as a parameter the repository url. sub new { my ($type, $repository_url, $user, $password) = @_; # Determine if there are additional parameters required for user # authentication. my @userCmdLine = (); if (defined($user) && defined($password)) { push @userCmdLine, '--username'; push @userCmdLine, $user; push @userCmdLine, '--password'; push @userCmdLine, $password; } my $repotag = ''; if ( $repository_url =~ m~(.*)\[(.*)\]$~ ) { $repository_url = $1; $repotag = $2; } # Sanitise the repository URL. $repository_url = sanitise_url_component($repository_url); my $self = {}; $self->{repository_url} = $repository_url; $self->{repotag} = $repotag; $self->{userCmdLine} = \@userCmdLine; $self->{repository_string} = $repository_url; $self->{repository_string} .= ";$user" if defined $user; $self->{repository_string} .= ";$password" if defined $password; if ($self->{repository_string} !~ /^svn:/) { $self->{repository_string} = "svn:" . $self->{repository_string}; } bless $self, $type; } # Sanitise a Subversion URL component, by replacing spaces with %20 and @ # symbols with %40, so that there is no confused with pegged revisions. Also # remove any leading and trailing slashes. sub sanitise_url_component { my $url = shift; $url =~ s/\/$//; $url =~ s/^\///; $url =~ s/ /%20/g; $url =~ s/\@/%40/g; return $url; } # Retrieve the data corresponding to $filename and $revision. Store each line # into $content_array_ref. sub retrieve ($$$\$) { my ($self, $filename, $revision, $content_array_ref) = @_; # # Kludge # If the user has provided a Tag with a PEG, then the filename is # mangled. Eg: # MASS_Dev_Bus/Financial/cpp/Acquirer/tags/Acquirer_0.0.9000.bkk.1@24165/src/SetId/SetAcquirerIdConfig.cpp # # Test Fix # Remove anyhing that looks like @nnnnn/ # $filename =~ s~\@[0-9]+/~/~; # Sanitise the filename. $filename = sanitise_url_component($filename); my $read_data; my $read_stdout_fh = new FileHandle; open($read_stdout_fh, '>', \$read_data); my @args = (); push @args, 'cat'; push @args, '--non-interactive'; push @args, '--no-auth-cache'; push @args, '--trust-server-cert'; push @args, @{ $self->{userCmdLine} }; push @args, '--revision'; push @args, $revision; push @args, $self->{repository_url} . '/' . $filename; Codestriker::execute_command($read_stdout_fh, undef, $Codestriker::svn, @args); # Process the data for the topic. open($read_stdout_fh, '<', \$read_data); for (my $i = 1; <$read_stdout_fh>; $i++) { $_ = Codestriker::decode_topic_text($_); chop; $$content_array_ref[$i] = $_; } } # Retrieve the "root" of this repository. sub getRoot ($) { my ($self) = @_; return $self->{repository_url}; } # Return a URL which views the specified file and revision. sub getViewUrl ($$$) { my ($self, $filename, $revision) = @_; # Lookup the file viewer from the configuration. my $viewer = $Codestriker::file_viewer->{$self->toString()}; if (! (defined $viewer)) { $viewer = $Codestriker::file_viewer->{$self->{repository_string}}; } return (defined $viewer) ? $viewer . "/" . $filename : ""; } # Return a string representation of this repository. sub toString ($) { my ($self) = @_; return "svn:" . $self->getRoot(); } # Given a Subversion URL, determine if it refers to a directory or a file. sub is_file_url { my ($self, $url) = @_; my $file_url; eval { my @args = (); push @args, 'info'; push @args, '--non-interactive'; push @args, '--no-auth-cache'; push @args, '--trust-server-cert'; push @args, @{ $self->{userCmdLine} }; push @args, '--xml'; push @args, $self->{repository_url} . '/' . $url; my $read_data; my $read_stdout_fh = new FileHandle; open($read_stdout_fh, '>', \$read_data); Codestriker::execute_command($read_stdout_fh, undef, $Codestriker::svn, @args); open($read_stdout_fh, '<', \$read_data); while (<$read_stdout_fh>) { if (/kind\s*\=\s*\"(\w+)\"/) { $file_url = $1 eq "File"; last; } } }; if ($@ || !(defined $file_url)) { # The above command failed, try using the older method which only works # in an English locale. This supports Subversion 1.2 or earlier # releases, which don't support the --xml flag for the info command. my @args = (); push @args, 'cat'; push @args, '--non-interactive'; push @args, '--no-auth-cache'; push @args, '--trust-server-cert'; push @args, @{ $self->{userCmdLine} }; push @args, '--revision'; push @args, 'HEAD'; push @args, $self->{repository_url} . '/' . $url; my $read_stdout_data; my $read_stdout_fh = new FileHandle; open($read_stdout_fh, '>', \$read_stdout_data); my $read_stderr_data; my $read_stderr_fh = new FileHandle; open($read_stderr_fh, '>', \$read_stderr_data); Codestriker::execute_command($read_stdout_fh, $read_stderr_fh, $Codestriker::svn, @args); $file_url = 1; open($read_stderr_fh, '<', \$read_stderr_data); while(<$read_stderr_fh>) { if (/^svn:.* refers to a directory/) { $file_url = 0; last; } } } return $file_url; } # The getDiff operation, pull out a change set based on the start and end # revision number, confined to the specified moduled_name. sub getDiff { my ($self, $start_tag, $end_tag, $module_name, $stdout_fh, $stderr_fh) = @_; # # If modulename conatins the repotag - then remove it # if ( $self->{repotag} ) { if ( $module_name =~ m~$self->{repotag}/(.*)~ ) { $module_name = $1; } } # # Check Module Name # my @errors; $self->{getDiffError} = undef; unless ( $module_name =~ m~^(.*)/(tags|branches|trunk|Trunk)(/|$)~ ) { push @errors, "Module does not contain 'tags', 'trunk' or 'branches'", "Module must specify a development branch or trunk", "ModuleName: $module_name" ; } my $moduleRoot = $1; my $moduleTail = $3; if ( $module_name =~ m~^/~ ) { push @errors, "Module must not start with a '/'" } if ( @errors ) { $self->{getDiffError} = join ('
', @errors); return $Codestriker::OK; } # Sanitise the URL, and determine if it refers to a directory or filename. $module_name = sanitise_url_component($module_name); my $directory; if ($self->is_file_url($module_name)) { $module_name =~ /(.*)\/[^\/]+/; $directory = $1; } else { $directory = $module_name; } # # Detect mode of operation # 1) Two known revisions - simple (start and end specified) # 2) Revision to HEAD - simple (only start specified) # 3) Empty to Specified-Revision - tricky (only end specified) # 4) Error - detected before here (no start or end) # my $start_url; my $end_url; # my $moduleRoot = $module_name; # $moduleRoot =~ s~/trunk$~~; # $moduleRoot =~ s~/branches/.*~~; # $moduleRoot =~ s~/tags/.*~~; my $diffOld = $module_name; if ( ! $start_tag ) { # # Dummy start: Root of the empty repository # my $repoName = $module_name; $repoName =~ s~[/\\].*~~; $start_url = $repoName . '@0'; } elsif ( $start_tag =~ m~^\d+$~ ) { # # Pegged version # $start_url = $module_name . '@' . $start_tag; } else { # # Tagged version # $start_url = join ('/', $moduleRoot, 'tags', $start_tag); $diffOld = $start_url; } my $diffNew = $module_name; if ( ! $end_tag ) { # # Dummy end: Head of the branch # $end_url = $module_name; } elsif ( $end_tag =~ m~^\d+$~ ) { # # Pegged version # $end_url = $module_name . '@' . $end_tag; } else { # # Tagged version # $end_url = join ('/', $moduleRoot, 'tags', $end_tag); $diffNew = $end_url; } # Execute the diff command. my $read_stdout_data; my $read_stdout_fh = new FileHandle; open($read_stdout_fh, '>', \$read_stdout_data); # # Determine the command to use # If user provides two numbers, then module is a full path # Otherwise assume that the user has provided named tags # # my @args = (); push @args, 'diff'; push @args, '--non-interactive'; push @args, '--no-auth-cache'; push @args, '--trust-server-cert'; push @args, @{ $self->{userCmdLine} }; push @args, '--old'; push @args, join ('/', $self->{repository_url},$start_url); push @args, '--new'; push @args, join ('/', $self->{repository_url}, $end_url); Codestriker::execute_command($read_stdout_fh, $stderr_fh, $Codestriker::svn, @args); # # DPurdie: 29-Nov-12 # The diff output does not have the full file name # The following code attempts to fix this (bit it didn't work too well # with a mixture of tags, branches and pegs) # # Unified Diff format : # --- /path/to/original # +++ /path/to/new # Svn Extension # Index: path/to/file # # my $rv = open($read_stdout_fh, '<', \$read_stdout_data); if ( $rv ) { while(<$read_stdout_fh>) { my $line = $_; ## If the user specifies a path (a branch in Subversion), the ## diff file does not come back with a path rooted from the ## repository base making it impossible to pull the entire file ## back out. This code attempts to change the diff file on the ## fly to ensure that the full path is present. This is a bug ## against Subversion, so eventually it will be fixed, so this ## code can't break when the diff command starts returning the ## full path. #if ($line =~ /^--- / || $line =~ /^\+\+\+ / || # $line =~ /^Index: /) { # # Check if the bug has been fixed. # if ($line =~ /^\+\+\+ $module_name/ == 0 && # $line =~ /^--- $module_name/ == 0 && # $line =~ /^Index: $module_name/ == 0) { # $line =~ s/^--- /--- $directory\// or # $line =~ s/^Index: /Index: $directory\// or # $line =~ s/^\+\+\+ /\+\+\+ $directory\//; # } #} $line =~ s~^Index: ~Index: $diffNew/~; $line =~ s~^--- ~--- $diffOld/~; $line =~ s~^\+\+\+ ~\+\+\+ $diffNew/~; print $stdout_fh $line; } } return $Codestriker::OK; } 1;