Subversion Repositories DevTools

Rev

Rev 2615 | 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
 
2880 dpurdie 71
    #
72
    #   Kludge
73
    #   If the user has provided a Tag with a PEG, then the filename is
74
    #   mangled. Eg:
75
    #       MASS_Dev_Bus/Financial/cpp/Acquirer/tags/Acquirer_0.0.9000.bkk.1@24165/src/SetId/SetAcquirerIdConfig.cpp
76
    #
77
    #   Test Fix
78
    #   Remove anyhing that looks like @nnnnn/
79
    #
80
    $filename =~ s~\@[0-9]+/~/~;
81
 
82
 
1293 dpurdie 83
    # Sanitise the filename.
84
    $filename = sanitise_url_component($filename);
85
 
86
    my $read_data;
87
    my $read_stdout_fh = new FileHandle;
88
    open($read_stdout_fh, '>', \$read_data);
89
    my @args = ();
90
    push @args, 'cat';
91
    push @args, '--non-interactive';
92
    push @args, '--no-auth-cache';
1299 dpurdie 93
    push @args, '--trust-server-cert';
1293 dpurdie 94
    push @args, @{ $self->{userCmdLine} };
95
    push @args, '--revision';
96
    push @args, $revision;
97
    push @args, $self->{repository_url} . '/' . $filename;
98
    Codestriker::execute_command($read_stdout_fh, undef,
1299 dpurdie 99
                                 $Codestriker::svn, @args);
1293 dpurdie 100
 
101
    # Process the data for the topic.
102
    open($read_stdout_fh, '<', \$read_data);
103
    for (my $i = 1; <$read_stdout_fh>; $i++) {
1299 dpurdie 104
        $_ = Codestriker::decode_topic_text($_);
105
        chop;
106
        $$content_array_ref[$i] = $_;
1293 dpurdie 107
    }
108
}
109
 
110
# Retrieve the "root" of this repository.
111
sub getRoot ($) {
112
    my ($self) = @_;
113
    return $self->{repository_url};
114
}
115
 
116
# Return a URL which views the specified file and revision.
117
sub getViewUrl ($$$) {
118
    my ($self, $filename, $revision) = @_;
119
 
120
    # Lookup the file viewer from the configuration.
121
    my $viewer = $Codestriker::file_viewer->{$self->toString()};
122
    if (! (defined $viewer)) {
1299 dpurdie 123
        $viewer = $Codestriker::file_viewer->{$self->{repository_string}};
1293 dpurdie 124
    }
125
 
126
    return (defined $viewer) ? $viewer . "/" . $filename : "";
127
}
128
 
129
# Return a string representation of this repository.
130
sub toString ($) {
131
    my ($self) = @_;
132
    return "svn:" . $self->getRoot();
133
}
134
 
135
# Given a Subversion URL, determine if it refers to a directory or a file.
136
sub is_file_url {
137
    my ($self, $url) = @_;
138
    my $file_url;
139
 
140
    eval {
1299 dpurdie 141
        my @args = ();
142
        push @args, 'info';
143
        push @args, '--non-interactive';
144
        push @args, '--no-auth-cache';
145
        push @args, '--trust-server-cert';
146
        push @args, @{ $self->{userCmdLine} };
147
        push @args, '--xml';
148
        push @args, $self->{repository_url} . '/' . $url;
149
        my $read_data;
150
        my $read_stdout_fh = new FileHandle;
151
        open($read_stdout_fh, '>', \$read_data);
1293 dpurdie 152
 
1299 dpurdie 153
        Codestriker::execute_command($read_stdout_fh, undef,
154
                                     $Codestriker::svn, @args);
155
        open($read_stdout_fh, '<', \$read_data);
156
        while (<$read_stdout_fh>) {
157
            if (/kind\s*\=\s*\"(\w+)\"/) {
158
                $file_url = $1 eq "File";
159
                last;
160
            }
161
        }
1293 dpurdie 162
    };
163
    if ($@ || !(defined $file_url)) {
1299 dpurdie 164
        # The above command failed, try using the older method which only works
165
        # in an English locale.  This supports Subversion 1.2 or earlier
166
        # releases, which don't support the --xml flag for the info command.
167
        my @args = ();
168
        push @args, 'cat';
169
        push @args, '--non-interactive';
170
        push @args, '--no-auth-cache';
171
        push @args, '--trust-server-cert';
172
        push @args, @{ $self->{userCmdLine} };
173
        push @args, '--revision';
174
        push @args, 'HEAD';
175
        push @args, $self->{repository_url} . '/' . $url;
1293 dpurdie 176
 
1299 dpurdie 177
        my $read_stdout_data;
178
        my $read_stdout_fh = new FileHandle;
179
        open($read_stdout_fh, '>', \$read_stdout_data);
1293 dpurdie 180
 
1299 dpurdie 181
        my $read_stderr_data;
182
        my $read_stderr_fh = new FileHandle;
183
        open($read_stderr_fh, '>', \$read_stderr_data);
1293 dpurdie 184
 
1299 dpurdie 185
        Codestriker::execute_command($read_stdout_fh, $read_stderr_fh,
186
                                     $Codestriker::svn, @args);
187
        $file_url = 1;
188
        open($read_stderr_fh, '<', \$read_stderr_data);
189
        while(<$read_stderr_fh>) {
190
            if (/^svn:.* refers to a directory/) {
191
                $file_url = 0;
192
                last;
193
            }
194
        }
1293 dpurdie 195
    }
196
 
197
    return $file_url;
198
}
199
 
200
# The getDiff operation, pull out a change set based on the start and end 
201
# revision number, confined to the specified moduled_name.
202
sub getDiff {
203
    my ($self, $start_tag, $end_tag, $module_name, $stdout_fh, $stderr_fh) = @_;
204
 
1300 dpurdie 205
 
206
    #
1302 dpurdie 207
    #   If modulename conatins the repotag - then remove it
208
    #
209
    if ( $self->{repotag} )
210
    {
211
        if ( $module_name =~ m~$self->{repotag}/(.*)~ )
212
        {
213
            $module_name = $1;
214
        }
215
    }
216
 
217
    #
1300 dpurdie 218
    #   Check Module Name
219
    #
220
    my @errors;
221
    $self->{getDiffError} = undef;
2615 dpurdie 222
    unless ( $module_name =~ m~^(.*)/(tags|branches|trunk|Trunk)(/|$)~ )
1300 dpurdie 223
    {
224
        push @errors,
225
            "Module does not contain 'tags', 'trunk' or 'branches'",
2559 dpurdie 226
            "Module must specify a development branch or trunk",
227
            "ModuleName: $module_name"
228
            ;
1300 dpurdie 229
    }
2559 dpurdie 230
    my $moduleRoot = $1;
231
    my $moduleTail = $3;
232
 
1300 dpurdie 233
 
234
    if ( $module_name =~ m~^/~ )
235
    {
236
        push @errors, "Module must not start with a '/'"
237
    }
238
 
239
    if ( @errors )
240
    {
241
        $self->{getDiffError} = join ('<br>', @errors);
242
        return $Codestriker::OK;
243
    }
244
 
245
 
1293 dpurdie 246
    # Sanitise the URL, and determine if it refers to a directory or filename.
247
    $module_name = sanitise_url_component($module_name);
248
    my $directory;
249
    if ($self->is_file_url($module_name)) {
1299 dpurdie 250
        $module_name =~ /(.*)\/[^\/]+/;
251
        $directory = $1;
1293 dpurdie 252
    } else {
1299 dpurdie 253
        $directory = $module_name;
1293 dpurdie 254
    }
255
 
1306 dpurdie 256
    #
257
    #   Detect mode of operation
258
    #       1) Two known revisions - simple (start and end specified)
259
    #       2) Revision to HEAD - simple (only start specified)
260
    #       3) Empty to Specified-Revision - tricky (only end specified)
261
    #       4) Error - detected before here (no start or end)
262
    #
263
    my $start_url;
264
    my $end_url;
265
 
2559 dpurdie 266
#    my $moduleRoot = $module_name;
267
#    $moduleRoot =~ s~/trunk$~~;
268
#    $moduleRoot =~ s~/branches/.*~~;
269
#    $moduleRoot =~ s~/tags/.*~~;
1306 dpurdie 270
 
2559 dpurdie 271
    my $diffOld = $module_name;
1306 dpurdie 272
    if ( ! $start_tag )
273
    {
274
        #
275
        #   Dummy start: Root of the empty repository
276
        #
277
        my $repoName = $module_name;
278
        $repoName =~ s~[/\\].*~~;
279
        $start_url = $repoName . '@0';
280
    }
281
    elsif ( $start_tag =~ m~^\d+$~ )
282
    {
283
        #
284
        #   Pegged version
285
        #
286
        $start_url = $module_name  . '@' . $start_tag;
287
    }
288
    else
289
    {
290
        #
291
        #   Tagged version
292
        #
293
        $start_url =  join ('/', $moduleRoot, 'tags', $start_tag);
2559 dpurdie 294
        $diffOld = $start_url;
1306 dpurdie 295
    }
296
 
2559 dpurdie 297
    my $diffNew = $module_name;
1306 dpurdie 298
    if ( ! $end_tag )
299
    {
300
        #
301
        #   Dummy end: Head of the branch
302
        #
303
        $end_url = $module_name;
304
    }
305
    elsif ( $end_tag =~ m~^\d+$~ )
306
    {
307
        #
308
        #   Pegged version
309
        #
310
        $end_url = $module_name  . '@' . $end_tag;
311
    }
312
    else
313
    {
314
        #
315
        #   Tagged version
316
        #
317
        $end_url =  join ('/', $moduleRoot, 'tags', $end_tag);
2559 dpurdie 318
        $diffNew = $end_url;
1306 dpurdie 319
    }
320
 
321
 
322
 
1293 dpurdie 323
    # Execute the diff command.
324
    my $read_stdout_data;
325
    my $read_stdout_fh = new FileHandle;
326
    open($read_stdout_fh, '>', \$read_stdout_data);
327
 
1300 dpurdie 328
    #
2559 dpurdie 329
    #   Determine the command to use
1300 dpurdie 330
    #   If user provides two numbers, then module is a full path
331
    #   Otherwise assume that the user has provided named tags
332
    #
333
    #
1293 dpurdie 334
    my @args = ();
335
    push @args, 'diff';
336
    push @args, '--non-interactive';
337
    push @args, '--no-auth-cache';
1299 dpurdie 338
    push @args, '--trust-server-cert';
1293 dpurdie 339
    push @args, @{ $self->{userCmdLine} };
1300 dpurdie 340
 
1306 dpurdie 341
    push @args, '--old';
342
    push @args, join ('/', $self->{repository_url},$start_url);
343
    push @args, '--new';
344
    push @args, join ('/', $self->{repository_url}, $end_url);
1300 dpurdie 345
 
1306 dpurdie 346
 
1293 dpurdie 347
    Codestriker::execute_command($read_stdout_fh, $stderr_fh,
1299 dpurdie 348
                                 $Codestriker::svn, @args);
1293 dpurdie 349
 
2559 dpurdie 350
 
351
    #
352
    #   DPurdie: 29-Nov-12
353
    #   The diff output does not have the full file name
354
    #   The following code attempts to fix this (bit it didn't work too well
355
    #   with a mixture of tags, branches and pegs)
356
    #
357
    #   Unified Diff format :
358
    #       --- /path/to/original
359
    #       +++ /path/to/new
360
    #   Svn Extension
361
    #       Index: path/to/file
362
    #
363
    #
364
 
1300 dpurdie 365
    my $rv = open($read_stdout_fh, '<', \$read_stdout_data);
366
    if ( $rv ) {
367
        while(<$read_stdout_fh>) {
368
            my $line = $_;
1299 dpurdie 369
 
2559 dpurdie 370
            ## If the user specifies a path (a branch in Subversion), the
371
            ## diff file does not come back with a path rooted from the
372
            ## repository base making it impossible to pull the entire file
373
            ## back out. This code attempts to change the diff file on the
374
            ## fly to ensure that the full path is present. This is a bug
375
            ## against Subversion, so eventually it will be fixed, so this
376
            ## code can't break when the diff command starts returning the
377
            ## full path.
378
            #if ($line =~ /^--- / || $line =~ /^\+\+\+ / ||
379
            #    $line =~ /^Index: /) {
380
            #    # Check if the bug has been fixed.
381
            #    if ($line =~ /^\+\+\+ $module_name/ == 0 &&
382
            #        $line =~ /^--- $module_name/ == 0 &&
383
            #        $line =~ /^Index: $module_name/ == 0) {
384
            #            $line =~ s/^--- /--- $directory\// or
385
            #            $line =~ s/^Index: /Index: $directory\// or
386
            #            $line =~ s/^\+\+\+ /\+\+\+ $directory\//;
387
            #    }
388
            #}
1300 dpurdie 389
 
2559 dpurdie 390
            $line =~ s~^Index: ~Index: $diffNew/~;
391
            $line =~ s~^--- ~--- $diffOld/~;
392
            $line =~ s~^\+\+\+ ~\+\+\+ $diffNew/~;
393
 
1300 dpurdie 394
            print $stdout_fh $line;
1299 dpurdie 395
        }
1293 dpurdie 396
    }
397
    return $Codestriker::OK;
398
}
399
 
400
1;