Subversion Repositories DevTools

Rev

Rev 1306 | Rev 2559 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1293 dpurdie 1
###############################################################################
2
# Codestriker: Copyright (c) 2001,2002,2003 David Sitsky.  All rights reserved.
3
# sits@users.sourceforge.net
4
#
5
# This program is free software; you can redistribute it and modify it under
6
# the terms of the GPL.
7
 
8
# Subversion repository access package.
9
 
10
package Codestriker::Repository::Subversion;
11
use IPC::Open3;
12
 
13
use strict;
14
use Fatal qw / open close /;
15
 
16
# Constructor, which takes as a parameter the repository url.
17
sub new {
18
    my ($type, $repository_url, $user, $password) = @_;
19
    # Determine if there are additional parameters required for user
20
    # authentication.
1302 dpurdie 21
 
1293 dpurdie 22
    my @userCmdLine = ();
23
    if (defined($user) && defined($password)) {
24
        push @userCmdLine, '--username';
1299 dpurdie 25
        push @userCmdLine, $user;
26
        push @userCmdLine, '--password';
27
        push @userCmdLine, $password;
1293 dpurdie 28
    }
29
 
1302 dpurdie 30
    my $repotag = '';
31
    if ( $repository_url =~ m~(.*)\[(.*)\]$~ )
32
    {
33
        $repository_url = $1;
34
        $repotag = $2;
35
    }
36
 
1293 dpurdie 37
    # Sanitise the repository URL.
38
    $repository_url = sanitise_url_component($repository_url);
39
 
40
    my $self = {};
41
    $self->{repository_url} = $repository_url;
1302 dpurdie 42
    $self->{repotag} = $repotag;
1293 dpurdie 43
    $self->{userCmdLine} = \@userCmdLine;
44
    $self->{repository_string} = $repository_url;
45
    $self->{repository_string} .= ";$user" if defined $user;
46
    $self->{repository_string} .= ";$password" if defined $password;
47
    if ($self->{repository_string} !~ /^svn:/) {
1299 dpurdie 48
        $self->{repository_string} = "svn:" . $self->{repository_string};
1293 dpurdie 49
    }
50
 
51
    bless $self, $type;
52
}
53
 
54
# Sanitise a Subversion URL component, by replacing spaces with %20 and @
55
# symbols with %40, so that there is no confused with pegged revisions.  Also
56
# remove any leading and trailing slashes.
57
sub sanitise_url_component {
58
    my $url = shift;
59
    $url =~ s/\/$//;
60
    $url =~ s/^\///;
61
    $url =~ s/ /%20/g;
62
    $url =~ s/\@/%40/g;
63
    return $url;
64
}
65
 
66
# Retrieve the data corresponding to $filename and $revision.  Store each line
67
# into $content_array_ref.
68
sub retrieve ($$$\$) {
69
    my ($self, $filename, $revision, $content_array_ref) = @_;
70
 
71
    # Sanitise the filename.
72
    $filename = sanitise_url_component($filename);
73
 
74
    my $read_data;
75
    my $read_stdout_fh = new FileHandle;
76
    open($read_stdout_fh, '>', \$read_data);
77
    my @args = ();
78
    push @args, 'cat';
79
    push @args, '--non-interactive';
80
    push @args, '--no-auth-cache';
1299 dpurdie 81
    push @args, '--trust-server-cert';
1293 dpurdie 82
    push @args, @{ $self->{userCmdLine} };
83
    push @args, '--revision';
84
    push @args, $revision;
85
    push @args, $self->{repository_url} . '/' . $filename;
86
    Codestriker::execute_command($read_stdout_fh, undef,
1299 dpurdie 87
                                 $Codestriker::svn, @args);
1293 dpurdie 88
 
89
    # Process the data for the topic.
90
    open($read_stdout_fh, '<', \$read_data);
91
    for (my $i = 1; <$read_stdout_fh>; $i++) {
1299 dpurdie 92
        $_ = Codestriker::decode_topic_text($_);
93
        chop;
94
        $$content_array_ref[$i] = $_;
1293 dpurdie 95
    }
96
}
97
 
98
# Retrieve the "root" of this repository.
99
sub getRoot ($) {
100
    my ($self) = @_;
101
    return $self->{repository_url};
102
}
103
 
104
# Return a URL which views the specified file and revision.
105
sub getViewUrl ($$$) {
106
    my ($self, $filename, $revision) = @_;
107
 
108
    # Lookup the file viewer from the configuration.
109
    my $viewer = $Codestriker::file_viewer->{$self->toString()};
110
    if (! (defined $viewer)) {
1299 dpurdie 111
        $viewer = $Codestriker::file_viewer->{$self->{repository_string}};
1293 dpurdie 112
    }
113
 
114
    return (defined $viewer) ? $viewer . "/" . $filename : "";
115
}
116
 
117
# Return a string representation of this repository.
118
sub toString ($) {
119
    my ($self) = @_;
120
    return "svn:" . $self->getRoot();
121
}
122
 
123
# Given a Subversion URL, determine if it refers to a directory or a file.
124
sub is_file_url {
125
    my ($self, $url) = @_;
126
    my $file_url;
127
 
128
    eval {
1299 dpurdie 129
        my @args = ();
130
        push @args, 'info';
131
        push @args, '--non-interactive';
132
        push @args, '--no-auth-cache';
133
        push @args, '--trust-server-cert';
134
        push @args, @{ $self->{userCmdLine} };
135
        push @args, '--xml';
136
        push @args, $self->{repository_url} . '/' . $url;
137
        my $read_data;
138
        my $read_stdout_fh = new FileHandle;
139
        open($read_stdout_fh, '>', \$read_data);
1293 dpurdie 140
 
1299 dpurdie 141
        Codestriker::execute_command($read_stdout_fh, undef,
142
                                     $Codestriker::svn, @args);
143
        open($read_stdout_fh, '<', \$read_data);
144
        while (<$read_stdout_fh>) {
145
            if (/kind\s*\=\s*\"(\w+)\"/) {
146
                $file_url = $1 eq "File";
147
                last;
148
            }
149
        }
1293 dpurdie 150
    };
151
    if ($@ || !(defined $file_url)) {
1299 dpurdie 152
        # The above command failed, try using the older method which only works
153
        # in an English locale.  This supports Subversion 1.2 or earlier
154
        # releases, which don't support the --xml flag for the info command.
155
        my @args = ();
156
        push @args, 'cat';
157
        push @args, '--non-interactive';
158
        push @args, '--no-auth-cache';
159
        push @args, '--trust-server-cert';
160
        push @args, @{ $self->{userCmdLine} };
161
        push @args, '--revision';
162
        push @args, 'HEAD';
163
        push @args, $self->{repository_url} . '/' . $url;
1293 dpurdie 164
 
1299 dpurdie 165
        my $read_stdout_data;
166
        my $read_stdout_fh = new FileHandle;
167
        open($read_stdout_fh, '>', \$read_stdout_data);
1293 dpurdie 168
 
1299 dpurdie 169
        my $read_stderr_data;
170
        my $read_stderr_fh = new FileHandle;
171
        open($read_stderr_fh, '>', \$read_stderr_data);
1293 dpurdie 172
 
1299 dpurdie 173
        Codestriker::execute_command($read_stdout_fh, $read_stderr_fh,
174
                                     $Codestriker::svn, @args);
175
        $file_url = 1;
176
        open($read_stderr_fh, '<', \$read_stderr_data);
177
        while(<$read_stderr_fh>) {
178
            if (/^svn:.* refers to a directory/) {
179
                $file_url = 0;
180
                last;
181
            }
182
        }
1293 dpurdie 183
    }
184
 
185
    return $file_url;
186
}
187
 
188
# The getDiff operation, pull out a change set based on the start and end 
189
# revision number, confined to the specified moduled_name.
190
sub getDiff {
191
    my ($self, $start_tag, $end_tag, $module_name, $stdout_fh, $stderr_fh) = @_;
192
 
1300 dpurdie 193
 
194
    #
1302 dpurdie 195
    #   If modulename conatins the repotag - then remove it
196
    #
197
    if ( $self->{repotag} )
198
    {
199
        if ( $module_name =~ m~$self->{repotag}/(.*)~ )
200
        {
201
            $module_name = $1;
202
        }
203
    }
204
 
205
    #
1300 dpurdie 206
    #   Check Module Name
207
    #
208
    my @errors;
209
    $self->{getDiffError} = undef;
210
    unless ( $module_name =~ m~/(tags|branches|trunk)(/|$)~ )
211
    {
212
        push @errors,
213
            "Module does not contain 'tags', 'trunk' or 'branches'",
214
            "Module must specify a development branch or trunk";
215
    }
216
 
217
    if ( $module_name =~ m~^/~ )
218
    {
219
        push @errors, "Module must not start with a '/'"
220
    }
221
 
222
    if ( @errors )
223
    {
224
        $self->{getDiffError} = join ('<br>', @errors);
225
        return $Codestriker::OK;
226
    }
227
 
228
 
1293 dpurdie 229
    # Sanitise the URL, and determine if it refers to a directory or filename.
230
    $module_name = sanitise_url_component($module_name);
231
    my $directory;
232
    if ($self->is_file_url($module_name)) {
1299 dpurdie 233
        $module_name =~ /(.*)\/[^\/]+/;
234
        $directory = $1;
1293 dpurdie 235
    } else {
1299 dpurdie 236
        $directory = $module_name;
1293 dpurdie 237
    }
238
 
1306 dpurdie 239
    #
240
    #   Detect mode of operation
241
    #       1) Two known revisions - simple (start and end specified)
242
    #       2) Revision to HEAD - simple (only start specified)
243
    #       3) Empty to Specified-Revision - tricky (only end specified)
244
    #       4) Error - detected before here (no start or end)
245
    #
246
    my $start_url;
247
    my $end_url;
248
 
249
    my $moduleRoot = $module_name;
250
    $moduleRoot =~ s~/trunk$~~;
251
    $moduleRoot =~ s~/branches/.*~~;
252
    $moduleRoot =~ s~/tags/.*~~;
253
 
254
    if ( ! $start_tag )
255
    {
256
        #
257
        #   Dummy start: Root of the empty repository
258
        #
259
        my $repoName = $module_name;
260
        $repoName =~ s~[/\\].*~~;
261
        $start_url = $repoName . '@0';
262
    }
263
    elsif ( $start_tag =~ m~^\d+$~ )
264
    {
265
        #
266
        #   Pegged version
267
        #
268
        $start_url = $module_name  . '@' . $start_tag;
269
    }
270
    else
271
    {
272
        #
273
        #   Tagged version
274
        #
275
        $start_url =  join ('/', $moduleRoot, 'tags', $start_tag);
276
    }
277
 
278
    if ( ! $end_tag )
279
    {
280
        #
281
        #   Dummy end: Head of the branch
282
        #
283
        $end_url = $module_name;
284
    }
285
    elsif ( $end_tag =~ m~^\d+$~ )
286
    {
287
        #
288
        #   Pegged version
289
        #
290
        $end_url = $module_name  . '@' . $end_tag;
291
    }
292
    else
293
    {
294
        #
295
        #   Tagged version
296
        #
297
        $end_url =  join ('/', $moduleRoot, 'tags', $end_tag);
298
    }
299
 
300
 
301
 
1293 dpurdie 302
    # Execute the diff command.
303
    my $read_stdout_data;
304
    my $read_stdout_fh = new FileHandle;
305
    open($read_stdout_fh, '>', \$read_stdout_data);
306
 
1300 dpurdie 307
    #
308
    #   Determine the commad to use
309
    #   If user provides two numbers, then module is a full path
310
    #   Otherwise assume that the user has provided named tags
311
    #
312
    #
1293 dpurdie 313
    my @args = ();
314
    push @args, 'diff';
315
    push @args, '--non-interactive';
316
    push @args, '--no-auth-cache';
1299 dpurdie 317
    push @args, '--trust-server-cert';
1293 dpurdie 318
    push @args, @{ $self->{userCmdLine} };
1300 dpurdie 319
 
1306 dpurdie 320
    push @args, '--old';
321
    push @args, join ('/', $self->{repository_url},$start_url);
322
    push @args, '--new';
323
    push @args, join ('/', $self->{repository_url}, $end_url);
1300 dpurdie 324
 
1306 dpurdie 325
 
1293 dpurdie 326
    Codestriker::execute_command($read_stdout_fh, $stderr_fh,
1299 dpurdie 327
                                 $Codestriker::svn, @args);
1293 dpurdie 328
 
1300 dpurdie 329
    my $rv = open($read_stdout_fh, '<', \$read_stdout_data);
330
    if ( $rv ) {
331
        while(<$read_stdout_fh>) {
332
            my $line = $_;
1299 dpurdie 333
 
1300 dpurdie 334
            # If the user specifies a path (a branch in Subversion), the
335
            # diff file does not come back with a path rooted from the
336
            # repository base making it impossible to pull the entire file
337
            # back out. This code attempts to change the diff file on the
338
            # fly to ensure that the full path is present. This is a bug
339
            # against Subversion, so eventually it will be fixed, so this
340
            # code can't break when the diff command starts returning the
341
            # full path.
342
            if ($line =~ /^--- / || $line =~ /^\+\+\+ / ||
343
                $line =~ /^Index: /) {
344
                # Check if the bug has been fixed.
345
                if ($line =~ /^\+\+\+ $module_name/ == 0 && 
346
                    $line =~ /^--- $module_name/ == 0 &&
347
                    $line =~ /^Index: $module_name/ == 0) {
348
                        $line =~ s/^--- /--- $directory\// or
349
                        $line =~ s/^Index: /Index: $directory\// or
350
                        $line =~ s/^\+\+\+ /\+\+\+ $directory\//;
351
                }
1299 dpurdie 352
            }
1300 dpurdie 353
 
354
            print $stdout_fh $line;
1299 dpurdie 355
        }
1293 dpurdie 356
    }
357
 
358
    return $Codestriker::OK;
359
}
360
 
361
1;