1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
21: class Horde_Vcs_Cvs extends Horde_Vcs_Rcs
22: {
23: 24: 25: 26: 27:
28: protected $_driver = 'Cvs';
29:
30: 31: 32: 33: 34:
35: protected $_features = array(
36: 'deleted' => true,
37: 'patchsets' => true,
38: 'branches' => true,
39: 'snapshots' => false);
40:
41: 42: 43:
44: public function __construct($params = array())
45: {
46: parent::__construct($params);
47: if (!$this->getPath('cvsps')) {
48: $this->_features['patchsets'] = false;
49: }
50: }
51:
52: 53: 54:
55: public function getFile($filename, $opts = array())
56: {
57: $filename = ltrim($filename, '/');
58: $fname = $filename . ',v';
59:
60:
61: if (!@is_file($this->sourceroot . '/' . $fname)) {
62: $fname = dirname($filename) . '/Attic/' . basename($filename) . ',v';
63: }
64:
65: if (!@is_file($this->sourceroot . '/' . $fname)) {
66: throw new Horde_Vcs_Exception(sprintf('File "%s" not found', $filename));
67: }
68:
69: return Horde_Vcs_Base::getFile($fname, $opts);
70: }
71:
72: 73: 74:
75: public function isFile($where)
76: {
77: return @is_file($where . ',v') ||
78: @is_file(dirname($where) . '/Attic/' . basename($where) . ',v');
79: }
80:
81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95:
96: protected function _diff(Horde_Vcs_File_Base $file, $rev1, $rev2, $opts)
97: {
98: $fullName = $file->getPath();
99: $diff = array();
100: $flags = '-kk ';
101:
102: if (!$opts['ws']) {
103: $flags .= ' -bB ';
104: }
105:
106: switch ($opts['type']) {
107: case 'context':
108: $flags .= '-p --context=' . escapeshellarg((int)$opts['num']);
109: break;
110:
111: case 'unified':
112: $flags .= '-p --unified=' . escapeshellarg((int)$opts['num']);
113: break;
114:
115: case 'column':
116: $flags .= '--side-by-side --width=120';
117: break;
118:
119: case 'ed':
120: $flags .= '-e';
121: break;
122: }
123:
124:
125: if (VC_WINDOWS) {
126: $fullName = str_replace(DIRECTORY_SEPARATOR, '/', $fullName);
127: }
128:
129:
130:
131: $command = escapeshellcmd($this->getPath('rcsdiff')) . ' ' . $flags . ' -r' . escapeshellarg($rev1) . ' -r' . escapeshellarg($rev2) . ' ' . escapeshellarg($fullName) . ' 2>&1';
132: if (VC_WINDOWS) {
133: $command .= ' < ' . escapeshellarg(__FILE__);
134: }
135:
136: exec($command, $diff, $retval);
137: return ($retval > 0) ? $diff : array();
138: }
139:
140: 141: 142: 143: 144:
145: public function annotate($fileob, $rev)
146: {
147: $this->assertValidRevision($rev);
148:
149: $tmpfile = Horde_Util::getTempFile('vc', true, $this->_paths['temp']);
150: $where = $fileob->getSourcerootPath();
151:
152: $pipe = popen(escapeshellcmd($this->getPath('cvs')) . ' -n server > ' . escapeshellarg($tmpfile), VC_WINDOWS ? 'wb' : 'w');
153:
154: $out = array(
155: 'Root ' . $this->sourceroot,
156: 'Valid-responses ok error Valid-requests Checked-in Updated Merged Removed M E',
157: 'UseUnchanged',
158: 'Argument -r',
159: 'Argument ' . $rev,
160: 'Argument ' . $where
161: );
162:
163: $dirs = explode('/', dirname($where));
164: while (count($dirs)) {
165: $out[] = 'Directory ' . implode('/', $dirs);
166: $out[] = $this->sourceroot . '/' . implode('/', $dirs);
167: array_pop($dirs);
168: }
169:
170: $out[] = 'Directory .';
171: $out[] = $this->sourceroot;
172: $out[] = 'annotate';
173:
174: foreach ($out as $line) {
175: fwrite($pipe, "$line\n");
176: }
177: pclose($pipe);
178:
179: if (!($fl = fopen($tmpfile, VC_WINDOWS ? 'rb' : 'r'))) {
180: return false;
181: }
182:
183: $lines = array();
184: $line = fgets($fl, 4096);
185:
186:
187: if (VC_WINDOWS) {
188: $where = str_replace(DIRECTORY_SEPARATOR, '/', $where);
189: }
190:
191: while ($line && !preg_match("|^E\s+Annotations for $where|", $line)) {
192: $line = fgets($fl, 4096);
193: }
194:
195: if (!$line) {
196: throw new Horde_Vcs_Exception('Unable to annotate; server said: ' . $line);
197: }
198:
199: $lineno = 1;
200: while ($line = fgets($fl, 4096)) {
201: if (preg_match('/^M\s+([\d\.]+)\s+\((.+)\s+(\d+-\w+-\d+)\):.(.*)$/', $line, $regs)) {
202: $lines[] = array(
203: 'rev' => $regs[1],
204: 'author' => trim($regs[2]),
205: 'date' => $regs[3],
206: 'line' => $regs[4],
207: 'lineno' => $lineno++
208: );
209: }
210: }
211:
212: fclose($fl);
213: return $lines;
214: }
215:
216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226:
227: public function checkout($fullname, $rev)
228: {
229: $this->assertValidRevision($rev);
230:
231: if (!($RCS = popen(escapeshellcmd($this->getPath('co')) . ' ' . escapeshellarg('-p' . $rev) . ' ' . escapeshellarg($fullname) . " 2>&1", VC_WINDOWS ? 'rb' : 'r'))) {
232: throw new Horde_Vcs_Exception('Couldn\'t perform checkout of the requested file');
233: }
234:
235: 236: 237: 238:
239: $co = fgets($RCS, 1024);
240: if (!preg_match('/^([\S ]+),v\s+-->\s+st(andar)?d ?out(put)?\s*$/', $co, $regs) ||
241: ($regs[1] != $fullname)) {
242: throw new Horde_Vcs_Exception('Unexpected output from checkout: ' . $co);
243: }
244:
245: 246: 247: 248: 249:
250: $co = fgets($RCS, 1024);
251:
252: return $RCS;
253: }
254:
255: }
256: