reduce webserver logging of RCS operations
[diogenes.git] / include / diogenes.rcs.inc.php
CommitLineData
6855525e
JL
1<?php
2/*
3 * Copyright (C) 2003-2004 Polytechnique.org
4 * http://opensource.polytechnique.org/
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21
22require_once 'diogenes.spool.inc.php';
23require_once 'diogenes.icons.inc.php';
24// dependency on PEAR
25require_once 'System.php';
26
27/** This class handles Diogenes RCS operations.
28 */
29class DiogenesRcs extends DiogenesSpool {
30 /** Absolute directory location for the barrel's RCS files. */
31 var $rcsdir;
32
33 /** The username */
34 var $login;
35 /** The constructor.
36 *
37 * @param caller
38 * @param alias
39 * @param login the current user's login
40 * @param init should create this module?
41 */
42 function DiogenesRcs(&$caller,$alias,$login,$init = false) {
43 global $globals;
44 $this->DiogenesSpool($caller,$alias);
45 $this->rcsdir = "{$globals->rcsroot}/$alias";
46 $this->login = $login;
47
48 // if we were asked to, created directories
49 if ($init) {
50 if (!is_dir($this->rcsdir))
51 mkdir($this->rcsdir, 0700);
52 if (!is_dir($this->datadir))
53 mkdir($this->datadir, 0700);
54 }
55
56 // check RCS directory
57 if (!is_dir($this->rcsdir) || !is_writable($this->rcsdir))
58 $this->kill("'{$this->rcsdir}' is not a writable directory");
59
60 // check spool directory
61 if (!is_dir($this->datadir) || !is_writable($this->datadir))
62 $this->kill("'{$this->datadir}' is not a writable directory");
63 }
64
65
66 /** Return the path of an RCS "item" (file or directory).
67 *
68 * @param parent parent directory (optional)
69 * @param entry the item
70 */
71 function rcsPath($parent="",$entry="") {
72 $this->checkPath($parent,$entry);
73 return $this->rcsdir.($parent ? "/$parent": "") . ($entry ? "/$entry" : "");
74 }
75
76
77 /** Return the path of an RCS file (something,v).
78 *
79 * @param dir parent directory
80 * @param file the RCS entry
81 */
82 function rcsFile($dir,$file) {
83 return $this->rcsPath($dir,$file).",v";
84 }
85
86
87 /** Check whether a file is registered in RCS.
88 *
89 * @param dir parent directory
90 * @param file the RCS entry
91 */
92 function checkFile($dir, $file) {
93 return is_file($this->rcsFile($dir, $file));
94 }
95
96
97 /** Perform sanity check on an RCS directory
98 * and the corresponding checkout in the spool
99 *
100 * @param dir
101 */
102 function checkDir($dir) {
103 return is_dir($this->rcsPath($dir))
104 && is_writable($this->rcsPath($dir))
105 && is_dir($this->spoolPath($dir))
106 && is_writable($this->spoolPath($dir));
107 }
108
109
110 /** Do a checkout of an RCS item to a given location.
111 *
112 * @param dir parent directory
113 * @param file the RCS entry
114 * @param rev the revision to check out
115 * @param output the directory to which we want to perform the checkout
116 */
117 function checkout($dir,$file,$rev,$output)
118 {
119 $this->info("RCS : checkout out $file ($rev)..");
120 $rfile = $this->rcsFile($dir,$file);
9d2cbe83 121 if ($this->cmdExec("co -q -r".escapeshellarg($rev)." ".escapeshellarg($rfile)." ".escapeshellarg("$output/$file")))
6855525e
JL
122 {
123 $this->info("RCS : Error, checkout failed!");
124 $this->info($this->cmdStatus());
125 return;
126 }
127 return "$output/$file";
128 }
129
130
131 /** Commit an RCS item. Returns true for succes, false for an error.
132 *
133 * @param dir parent directory
134 * @param file the RCS entry
135 * @param content the contents of the new revision
136 * @param message the log message for this revision
137 */
138 function commit($dir,$file,$content,$message="")
139 {
140 $this->info("RCS : checking in '$file'..");
141
142 // check directories
143 if (!$this->checkDir($dir)) {
144 // error
145 $this->info("RCS : Error, RCS sanity check for '$dir' failed!");
146 return false;
147 }
148
149 // log commit attempt
150 $this->log("rcs_commit","{$this->alias}:$dir/$file:$message");
151
152 $sfile = $this->spoolPath($dir,$file);
153 $rfile = $this->rcsFile($dir,$file);
154
155 // if the RCS file does not exist, create it
156 if (!file_exists($rfile)) {
9d2cbe83 157 if ($this->cmdExec("echo '' | rcs -q -i ".escapeshellarg($rfile)))
6855525e
JL
158 {
159 // error
160 $this->info("RCS : Error, could not initialise RCS file '$rfile'!");
161 $this->info($this->cmdStatus());
162 return false;
163 }
164 }
165
166 // lock the spool file
9d2cbe83 167 if ($this->cmdExec("co -q -l ".escapeshellarg($rfile)." ".escapeshellarg($sfile)))
6855525e
JL
168 {
169 // error
170 $this->info("RCS : Error, could not get RCS lock on file '$file'!");
171 $this->info($this->cmdStatus());
172 return false;
173 }
174 if ($fp = fopen($sfile,"w")) {
175 fwrite($fp,$content);
176 fclose($fp);
177
9d2cbe83 178 if ($this->cmdExec("ci -q -w".escapeshellarg($this->login). ($message ? " -m".escapeshellarg($message) : "").
6855525e
JL
179 " ". escapeshellarg($sfile). " ". escapeshellarg($rfile)))
180 {
181 // error
182 $this->info("RCS : Error, checkin failed!");
183 $this->info($this->cmdStatus());
184 return false;
185 }
186
9d2cbe83 187 if ($this->cmdExec("co -q ".escapeshellarg($rfile)." ".escapeshellarg($sfile)))
6855525e
JL
188 {
189 // error
190 $this->info("RCS : Error, checkout after checkin failed!");
191 $this->info($this->cmdStatus());
192 return false;
193 }
194 }
195 return true;
196 }
197
198
199
200 /** Make a copy of an RCS item to a given location.
201 *
202 * @param sdir the source directory
203 * @param sfile the source RCS entry
204 * @param ddir the destination directory
205 * @param dfile the destination RCS entry
206 */
207 function copy($sdir,$sfile,$ddir,$dfile)
208 {
209 $this->info("RCS : copying '$sfile' to '$ddir/$dfile'..");
210
211 $spath = $this->spoolPath($sdir, $sfile);
212 if (!is_file($spath)) {
213 $this->info("Error: source file '$spath' does not exist!");
214 return false;
215 }
216 if (!$this->checkDir($ddir))
217 {
218 $this->info("Error: directory '$ddir' does not exist!");
219 return false;
220 }
221 if ($this->checkFile($ddir, $dfile))
222 {
223 $this->info("Error: file '$dfile' already exists in '$ddir'!");
224 return false;
225 }
226 return $this->commit($ddir,$dfile,
227 file_get_contents($spath),
228 "copied from '$ddir/$sfile'");
229 }
230
231
232 /** Delete an RCS file and its corresponding spool entry.
233 *
234 * @param dir parent directory
235 * @param file the RCS entry
236 */
237 function del($dir,$file) {
238 $this->info("RCS : deleting '$file'..");
239 $this->log("rcs_delete","{$this->alias}:$dir/$file");
240 @unlink($this->spoolPath($dir,$file));
241 @unlink($this->rcsFile($dir,$file));
242 }
243
244
245 /** Retrieve differences between two version of a file.
246 *
247 * @param dir parent directory
248 * @param file the RCS entry
249 * @param r1 the first revision
250 * @param r2 the second revision
251 */
252 function diff($dir,$file,$r1,$r2)
253 {
254 $rfile = $this->rcsFile($dir,$file);
255 $this->info("RCS : diffing '$file' ($r1 to $r2)..");
256 $this->cmdExec("rcsdiff -r".escapeshellarg($r1). " -r".escapeshellarg($r2)." ".escapeshellarg($rfile));
257 return $this->cmd_output;
258 }
259
260
261 /** Converts a Word document to HTML and commits the resulting
262 * HTML and images.
263 *
264 * @param dir
265 * @param htmlfile
266 * @param wordfile
267 */
268 function importWordFile($dir,$htmlfile,$wordfile)
269 {
270 global $globals;
271
272 if (!$globals->word_import) {
273 $this->info("Error : support for word import is disabled!");
274 return false;
275 }
276
277 $func = "importWordFile_{$globals->word_import}";
278
279 if (!method_exists($this, $func))
280 {
281 $this->info("Error : the utility '$globals->word_import' is not supported!");
282 return false;
283 }
284
285 return $this->$func($dir, $htmlfile, $wordfile);
286 }
287
288
289 /** Converts a Word document to HTML using wvHtml and commits the resulting
290 * HTML and images.
291 *
292 * @param dir
293 * @param htmlfile
294 * @param wordfile
295 */
296 function importWordFile_wvHtml($dir,$htmlfile,$wordfile)
297 {
298 $tmphtmlfile = "importWord.html";
299 if (($tmpdir = System::mktemp('-d')) == false) {
300 $this->info("Error : could not create temporary directory!");
301 return false;
302 }
303
304 if ($this->cmdExec("wvHtml --targetdir=".escapeshellarg($tmpdir).
305 " --charset=iso-8859-15 ".
306 escapeshellarg($wordfile)." ".escapeshellarg($tmphtmlfile)))
307 {
308 $this->info("Error : wvHtml returned an error!");
309 $this->info($this->cmdStatus());
310 return false;
311 }
312
313 if (!$dh = opendir($tmpdir)) {
314 $this->info("Error : could not find temporary directory '$tmpdir'!");
315 return false;
316 }
317
318 // process the files generated by wvHtml
319 $ok = true;
320 while (($myentry = readdir($dh)) != false) {
321 if (is_file($myfile = "$tmpdir/$myentry")) {
322 if ($myentry == $tmphtmlfile) {
323 $ok = $ok &&
324 $this->commit($dir,$htmlfile,
325 $this->importHtmlString(file_get_contents($myfile)),
326 "Word file import");
327 } else {
328 $ok = $ok &&
329 $this->commit($dir,$myentry,file_get_contents($myfile),
330 "Word file import");
331 }
332 }
333 }
334 closedir($dh);
335
336 return $ok;
337 }
338
339
340 /** Returns raw log entries for an RCS item.
341 *
342 * @param dir parent directory
343 * @param file the RCS entry
344 */
345 function logEntries($dir,$file) {
346 $rfile = $this->rcsFile($dir,$file);
347 $this->cmdExec("rlog ".escapeshellarg($rfile));
348 return $this->cmd_output;
349 }
350
351
352 /** Parse the log entries for an RCS item into an array.
353 *
354 * @param dir parent directory
355 * @param file the RCS entry
356 */
357 function logParse($dir,$file)
358 {
359 // get the log, drop last 2 lines
360 $lines = $this->logEntries($dir,$file);
361 array_pop($lines);
362 array_pop($lines);
363
364 // split into revision, drop first block
365 $revs = split("----------------------------\n", implode("\n",$lines));
366 array_shift($revs);
367
368 // parse info about the revisions
369 $revinfo = array();
370 foreach ($revs as $rev) {
371 $myrev = array();
372 $lines = explode("\n",$rev);
373 preg_match("/^revision (.+)$/",array_shift($lines),$res);
374 $myrev['rev'] = $res[1];
375 preg_match("/^date: ([^;]+); author: ([^;]+); .*$/",array_shift($lines),$res);
376 $myrev['date'] = $res[1];
377 $myrev['author'] = $res[2];
378 $myrev['log'] = implode("\n",$lines);
379 array_push($revinfo,$myrev);
380 }
381 return $revinfo;
382 }
383
384
385 /** Move an RCS item to a given location.
386 *
387 * @param sdir the source directory
388 * @param sfile the source RCS entry
389 * @param ddir the destination directory
390 * @param dfile the destination RCS entry
391 */
392 function move($sdir,$sfile,$ddir,$dfile)
393 {
394 $this->info("RCS : moving '$sfile' to '$ddir/$dfile'..");
395
396
397 // check source files
398 $spath = $this->spoolPath($sdir, $sfile);
399 $srpath = $this->rcsFile($sdir, $sfile);
400
401 if (!is_file($spath)) {
402 $this->info("Error: source file '$spath' does not exist!");
403 return false;
404 }
405
406 if (!is_file($srpath)) {
407 $this->info("Error: source RCS file '$srpath' does not exist!");
408 return false;
409 }
410
411 // check destination
412 $dpath = $this->spoolPath($ddir, $dfile);
413 $drpath = $this->rcsFile($ddir, $dfile);
414
415 if (!$this->checkDir($ddir))
416 {
417 $this->info("Error: directory '$ddir' does not exist!");
418 return false;
419 }
420
421 if (file_exists($dpath)) {
422 $this->info("Error: file '$dfile' already exists in '$ddir'!");
423 return false;
424 }
425
426 if (file_exists($drpath)) {
427 $this->info("Error: file '".basename($drpath)."' already exists in '$ddir'!");
428 return false;
429 }
430
431 if (!rename($spath, $dpath))
432 {
433 $this->info("Error: failed to move '".basename($spath)."' to '".basename($dpath)."' in '$ddir'!");
434 return false;
435 }
436
437 if (!rename($srpath, $drpath))
438 {
439 $this->info("Error: failed to move '".basename($srpath)."' to '".basename($drpath)."' in '$ddir'!");
440 return false;
441 }
442
443
444 //$this->log("rcs_move","{$this->alias}:$dir/$file");
445
446 return true;
447 }
448
449
450 /** Add a new RCS-managed directory.
451 *
452 * @param parent the parent directory
453 * @param dir the directory to add
454 */
455 function newdir($parent,$dir)
456 {
457 @mkdir($this->rcsPath($parent,$dir),0700);
458 @mkdir($this->spoolPath($parent,$dir),0700);
459 }
460
461
462 /** Retrieve differences between two version of a file and prepare for output.
463 *
464 * @param dir parent directory
465 * @param file the RCS entry
466 * @param r1 the first revision
467 * @param r2 the second revision
468 */
469 function dispDiff($dir,$file,$r1,$r2)
470 {
471 $lns = "[0-9]+|[0-9]+,[0-9]+";
472
473 // get diff, strip any leading comments
474 $lines = $this->diff($dir,$file,$r1,$r2);
475 $line = "";
476 while (!preg_match("/^($lns)([acd])($lns)/",$line))
477 $line = array_shift($lines);
478 array_unshift($lines,$line);
479 $raw = implode("\n",$lines);
480
481 $blocks = preg_split("/($lns)([acd])($lns)\n/",$raw,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
482 $out = array();
483
484 $lold = array_shift($blocks);
485 while ($lold!='')
486 {
487 $type = array_shift($blocks);
488 $lnew = array_shift($blocks);
489 $diff = array_shift($blocks);
490
491 switch ($type) {
492 case 'c':
493 list($a,$b) = split("---\n",$diff,2);
494 break;
495 case 'a':
496 $a = "";
497 $b = $diff;
498 break;
499 case 'd':
500 $a = $diff;
501 $b = "";
502 break;
503 }
504 array_push($out,array($lold,$type,$lnew,$a,$b));
505 $lold = array_shift($blocks);
506 }
507 //array_push($out,array($type,$a,$b));
508 return $out;
509 }
510
511
512 /** Return an RCS-managed directory.
513 *
514 * @param dir the directory
515 * @param loc the visible location for the directory
516 * @param canedit can we edit files in this directory?
517 */
518 function dispDir($dir,$loc,$canedit) {
519 $entries = array();
520
521 if ($pdir = @opendir($this->rcsPath($dir))) {
522 while ( ($file = readdir($pdir)) !== false) {
523 if ( ($file != ".") && ($file != "..") )
524 {
525 $entry = $this->dispEntry($dir,$loc,$file,$canedit);
526 if (!empty($entry))
527 array_push($entries, $entry);
528 }
529 }
530 closedir($pdir);
531 }
532 return $entries;
533 }
534
535
536 /** Returns an RCS "item" (file or directory).
537 *
538 * @param dir parent directory
539 * @param loc visible location for parent directory
540 * @param file the RCS "item"
541 * @param canedit can we edit files this entry?
542 */
543 function dispEntry($dir,$loc,$file,$canedit) {
544 global $globals;
545
546 $view = $edit = $del = $size = $rev = "";
547
548 $myitem = $this->rcsPath($dir,$file);
549
550 // check the RCS entry exists
551 if (!file_exists($myitem)) {
552 $this->info("RCS entry '$myitem' does not exist!");
553 return;
554 }
555
556 if (is_dir($myitem))
557 {
558 // this is a directory, this should not happen!
559 $this->info("Unexpected directory in RCS, skipping : '$myitem'");
560 return;
561 }
562 else if (substr($file,-2) == ",v")
563 {
564 // this is an RCS file
565 $file = substr($file,0,-2);
566
567 // check we have a working copy of this item
568 $spoolitem = $this->spoolPath($dir,$file);
569 if (!is_file($spoolitem))
570 {
571 $this->info("Could not find working copy '$spoolitem'!");
572 $size = "";
573 $icon = "";
574 } else {
575 $size = $this->dispSize(filesize($spoolitem));
576 $icon = $globals->icons->get_mime_icon($spoolitem);
577 }
578
579 // revision info
580 $myrev = array_shift($tmparr = $this->logParse($dir,$file));
581
582 return array(
583 "icon" => $icon,
584 "file" => $file,
585 "rev" => array($myrev['rev'],$rev),
586 "date" => $myrev['date'],
587 "author" => $myrev['author'],
588 "size" => $size
589 );
590
591 }
592 else
593 {
594 $this->info("Unknown RCS entry type : '$myitem'");
595 return;
596 }
597
598 }
599
600
601 /** Format a file size for display.
602 *
603 * @param size the size, in bytes
604 */
605 function dispSize($size)
606 {
607 if ($size < 1000)
608 return "$size B";
609 else if ($size < 1000000)
610 return floor($size/1000)." kB";
611 else
612 return floor($size/1000000)." MB";
613 }
614
615}
616
617?>