Removed /usr/local from CDPATH
[clearscm.git] / lib / Perforce.pm
1 package Perforce;
2
3 use strict;
4 use warnings;
5
6 use Carp;
7 use File::Basename;
8 use File::Temp;
9
10 use P4;
11 use Authen::Simple::LDAP;
12
13 use Display;
14 use GetConfig;
15 use Utils;
16
17 our $VERSION  = '$Revision: 2.23 $';
18    ($VERSION) = ($VERSION =~ /\$Revision: (.*) /);
19
20 my $p4config   = $ENV{P4_CONF}   || dirname (__FILE__) . '/../etc/p4.conf';
21 my $ldapconfig = $ENV{LDAP_CONF} || dirname (__FILE__) . '/../etc/LDAP.conf';
22
23 my %P4OPTS   = GetConfig $p4config   if -r $p4config;
24 my %LDAPOPTS = GetConfig $ldapconfig if -r $ldapconfig;
25
26 my $serviceUser = 'shared';
27 my ($domain, $password);
28 my $defaultPort = 'perforce:1666';
29 my $p4tickets   = $^O =~ /win/i ? 'C:/Program Files/Devops/Perforce/p4tickets'
30                                 : '/opt/audience/perforce/p4tickets';
31                                 
32 my $keys;
33
34 # If USERDOMAIN is set and equal to audience then set $domain to ''. This will
35 # use the Audience domain settings in LDAP.conf.
36 if ($ENV{USERDOMAIN}) {
37   if (lc $ENV{USERDOMAIN} eq 'audience') {
38     $domain = '';
39   } else {
40     $domain = $ENV{USERDOMAIN}
41   } # if
42 } # if
43
44 sub new (;%) {
45   my ($class, %parms) = @_;
46   
47   my $self = bless {}, $class;
48   
49   $self->{P4USER}   = $parms{username} || $P4OPTS{P4USER}   || $ENV{P4USER}   || $serviceUser;
50   $self->{P4PASSWD} = $parms{password} || $P4OPTS{P4PASSWD} || $ENV{P4PASSWD} || undef;
51   $self->{P4CLIENT} = $parms{p4client} || $P4OPTS{P4CLIENT} || $ENV{P4CLIENT} || undef;
52   $self->{P4PORT}   = $parms{p4port}   || $ENV{P4PORT}    || $defaultPort;
53
54   $self->{P4}       = $self->connect (%parms);
55   
56   return $self; 
57 } # new
58
59 sub errors ($;$) {
60   my ($self, $cmd, $exit) = @_;
61
62   my $msg    = "Unable to run \"p4 $cmd\"";
63   my $errors = $self->{P4}->ErrorCount;
64
65   error "$msg\n" . $self->{P4}->Errors, $exit if $errors; 
66
67   return $errors;
68 } # errors
69
70 sub connect () {
71   my ($self) = @_;
72   
73   $self->{P4} = P4->new;
74   
75   $self->{P4}->SetUser     ($self->{P4USER});
76   $self->{P4}->SetClient   ($self->{P4CLIENT}) if $self->{P4CLIENT};
77   $self->{P4}->SetPort     ($self->{P4PORT});
78   $self->{P4}->SetPassword ($self->{P4PASSWD}) unless $self->{P4USER} eq $serviceUser;
79
80   verbose_nolf "Connecting to Perforce server $self->{P4PORT}...";
81   $self->{P4}->Connect or croak "Unable to connect to Perforce Server\n";
82   verbose 'done';
83   
84   verbose_nolf "Logging in as $self->{P4USER}\@$self->{P4PORT}...";
85
86   unless ($self->{P4USER} eq $serviceUser) {
87     $self->{P4}->RunLogin;
88
89     $self->errors ('login', $self->{P4}->ErrorCount);
90   } else {
91     $ENV{P4TICKETS} = $p4tickets if $self->{P4USER} eq $serviceUser;
92   } # unless
93
94   verbose 'done';
95
96   return $self->{P4};
97 } # connect
98
99 sub authenticateUser ($;$) {
100   my ($self, $username, $p4client) = @_;
101   
102   # Connect to LDAP
103   my $ad = Authen::Simple::LDAP->new (
104     host   => $LDAPOPTS{AD_HOST},
105     basedn => $LDAPOPTS{AD_BASEDN},
106     port   => $LDAPOPTS{AD_PORT},
107     filter => $LDAPOPTS{AD_FILTER},
108   ) or croak $@;
109   
110   # Read the password from <stdin> and truncate the newline - unless we already
111   # read in the password
112   unless ($password) {
113     if (-t STDIN) {
114       $password = GetPassword;
115     } else {
116       $password = <STDIN>;
117       
118       chomp $password;
119     } # if
120   } # unless
121   
122   # Special handling of "shared" user
123   if ($username eq 'shared') {
124     my $sharedAcl = "$FindBin::Bin/sharedAcl.txt";
125     
126     croak "Unable to find file $sharedAcl" unless -f $sharedAcl;
127     
128     open my $sharedAcls, '<', $sharedAcl
129       or croak "Unable to open $sharedAcl - $!";
130       
131     chomp (my @acls = <$sharedAcls>);
132     
133     close $sharedAcls;
134     
135     for (@acls) {
136       if (/\*$/) {
137         chop;
138         
139         exit if $p4client =~ /$_/;
140       } else {
141         exit if $_ eq $p4client;
142       } # if
143     } # for
144   } # if
145
146   # Connect to Perforce
147   $self->connect unless $self->{P4};
148   
149   # Must be a valid Perforce user  
150   return unless $self->getUser ($username);
151   
152   # And supply a valid username/password
153   return $ad->authenticate ($username, $password);  
154 } # authenticateUser
155
156 sub change ($) {
157   my ($self, $changelist) = @_;
158   
159   my $change = $self->{P4}->Run ('change', '-o', $changelist);
160   
161   return $change->[0];
162 } # change
163
164 sub changelist ($) {
165   my ($self, $changelist) = @_;
166   
167   goto change;
168 } # changelist
169
170 sub changes (;$%) {
171   my ($self, $args, %opts) = @_;
172
173   my $cmd = 'changes';
174
175   for (keys %opts) {
176     if (/from/i and $opts{to}) {
177         $args .= " $opts{$_},$opts{to}";
178         
179         delete $opts{to};
180     } else {
181       $args .= " $opts{$_}";
182     } # if
183   } # for
184   
185   my $changes = $self->{P4}->Run ($cmd, $args);
186   
187   return $self->errors ("$cmd $args") || $changes;
188 } # changes
189
190 sub job ($) {
191   my ($self, $job) = @_;
192   
193   my $jobs = $self->{P4}->IterateJobs ("-e $job");
194   
195   return $self->errors ("jobs -e $job") || $job;
196 } # job
197
198 sub comments ($) {
199   my ($self, $changelist) = @_;
200   
201   my $change = $self->{P4}->FetchChange ($changelist);
202   
203   return $self->errors ("change $changelist") || $change;
204 } # comments
205
206 sub files ($) {
207   my ($self, $changelist) = @_;
208   
209   my $files = $self->{P4}->Run ('files', "\@=$changelist");
210   
211   return $self->errors ("files \@=$changelist") || $files;
212 } # files
213
214 sub filelog ($;%) {
215   my ($self, $fileSpec, %opts) = @_;
216   
217   return $self->{P4}->RunFilelog ($fileSpec, %opts);
218 } # filelog
219
220 sub getRevision ($;$) {
221   my ($self, $filename, $revision) = @_;
222   
223   unless ($revision) {
224     if ($filename =~ /#/) {
225       ($filename, $revision) = split $filename, '#';
226     } else {
227       error "No revision specified in $filename";
228     
229       return;
230     } # if
231   } # unlessf
232
233   my @contents = $self->{P4}->RunPrint ("$filename#$revision");
234   
235   if ($self->{P4}->ErrorCount) {
236     $self->errors ("Print $filename#$revision");
237     
238     return;
239   } else {
240     return @contents;
241   } # if
242 } # getRevision
243
244 sub getUser (;$) {
245   my ($self, $user) = @_;
246   
247   $user //= $ENV{P4USER} || $ENV{USER};
248   
249   my $cmd  = 'user';
250   my @args = ('-o', $user);
251   
252   my $userRecs = $self->{P4}->Run ($cmd, @args);
253   
254   # Perforce returns an array of qualifying users. We only care about the first
255   # one. However if the username is invalid, Perforce still returns something 
256   # that looks like a user. We look to see if there is a Type field here which
257   # indicates that it's a valid user
258   if ($userRecs->[0]{Type}) {
259     return %{$userRecs->[0]};
260   } else {
261     return;
262   } # if
263 } # getUser
264
265 sub renameSwarmUser ($$) {
266   my ($self, $oldusername, $newusername) = @_;
267   
268   # We are turning this off because Perforce support says that just modifying
269   # the keys we do not update the indexing done in the Perforce Server/Database.
270   # So instead we have a PHP script (renameUser.php) which goes through the
271   # official, but still unsupported, "Swarm Record API" to change the usernames
272   # and call the object's method "save" which should perform the necessary
273   # reindexing... Stay tuned! :-)
274   #
275   # BTW One needs to run renameUser.php by hand as we do not do that here. 
276   return;
277   
278   $keys = $self->getKeys ('swarm-*') unless $keys;
279   
280   for (@$keys) {
281     my %key = %$_;
282     
283     if ($key{value} =~ /$oldusername/) {
284       $key{value} =~ s/\"$oldusername\"/\"$newusername\"/g;
285       $key{value} =~ s/\@$oldusername /\@$newusername /g;
286       $key{value} =~ s/\@$oldusername\./\@$newusername\./g;
287       $key{value} =~ s/\@$oldusername,/\@$newusername,/g;
288       $key{value} =~ s/ $oldusername / $newusername /g;
289       $key{value} =~ s/ $oldusername,/ $newusername,/g;
290       $key{value} =~ s/ $oldusername\./ $newusername\./g;
291       $key{value} =~ s/-$oldusername\"/-$newusername\"/g;
292       
293       my $cmd = 'key';
294       
295       display "Correcting key $key{key}";
296
297       my @result = $self->{P4}->Run ($cmd, $key{key}, $key{value});
298       
299       $self->errors ($cmd, $result[0]->{key} || 1);
300     } # if
301   } # for
302   
303   return;
304 } # renameSwarmUser
305
306 sub renameUser ($$) {
307   my ($self, $old, $new) = @_;
308   
309   my $cmd  = 'renameuser';
310   my @args = ("--from=$old", "--to=$new");
311   
312   $self->{P4}->Run ($cmd, @args);
313   
314   my $status = $self->errors (join ' ', $cmd, @args);
315   
316   return $status if $status;
317   
318 #  return $self->renameSwarmUser ($old, $new);
319 } # renameUser
320
321 sub updateUser (%) {
322   my ($self, %user) = @_;
323   
324   # Trying to do this with P4Perl is difficult. First off the structure needs
325   # to be AOH and secondly you need to call SetUser to be the other user. That
326   # said you need to also specify -f to force the update (which means you must
327   # a admin (or superuser?) and I found no way to specify -f so I've reverted
328   # back to using p4 from the command line. I also don't like having to use
329   # a file here...
330   my $tmpfile     = File::Temp->new;
331   my $tmpfilename = $tmpfile->filename;
332   
333   print $tmpfile "User: $user{User}\n";
334   print $tmpfile "Email: $user{Email}\n";
335   print $tmpfile "Update: $user{Update}\n";
336   print $tmpfile "FullName: $user{FullName}\n";
337   
338   close $tmpfile;
339   
340   my @lines  = `p4 -p $self->{P4PORT} user -f -i < $tmpfilename`;
341   my $status = $?;
342
343   return wantarray ? @lines : join '', @lines;  
344 } # updateUser
345
346 sub getKeys (;$) {
347   my ($self, $filter) = @_;
348   
349   my $cmd = 'keys';
350   my @args;
351   
352   if ($filter) {
353     push @args, '-e';
354     push @args, $filter;
355   } # if
356   
357   my $keys = $self->{P4}->Run ($cmd, @args);
358   
359   $self->errors ($cmd . join (' ', @args), 1);
360   
361   return $keys; 
362 } # getKeys
363
364 sub key ($$) {
365   my ($self, $name, $value) = @_;
366   
367   my $cmd = 'key';
368   my @args = ($name, $value);
369   
370   $self->{P4}->Run ($cmd, @args);
371   
372   return $self->errors (join ' ', $cmd, @args);
373 } # key
374
375 1;