1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
4 /* Subversion SVN browsing */
6 class MTrackSCMFileSVN extends MTrackSCMFile {
12 function __construct(MTrackSCM $repo, $name, $rev, $is_dir = false)
17 $this->is_dir = $is_dir;
20 public function _determineFileChangeEvent($reponame, $filename, $rev)
22 $repo = MTrackRepo::loadByName($reponame);
23 list($ent) = $repo->history($filename, 1, 'rev', $rev);
27 public function getChangeEvent()
30 array('MTrackSCMFileSVN', '_determineFileChangeEvent'),
31 array($this->repo->getBrowseRootName(), $this->name, $this->rev),
37 return $this->repo->svn('cat', '-r', $this->rev,
38 'file://' . $this->repo->repopath . '/' . $this->name . "@$this->rev");
41 function annotate($include_line_content = false)
43 $xml = stream_get_contents($this->repo->svn('annotate', '--xml',
44 'file://' . $this->repo->repopath . '/' . $this->name . "@$this->rev"));
46 $xml = @simplexml_load_string($xml);
47 if (!is_object($xml)) {
50 if ($include_line_content) {
53 foreach ($xml->target->entry as $ent) {
54 $A = new MTrackSCMAnnotation;
55 $A->rev = (int)$ent->commit['revision'];
56 $A->changeby = (string)$ent->commit->author;
57 if ($include_line_content) {
58 $A->line = fgets($cat);
60 $ann[(int)$ent['line-number']] = $A;
67 class MTrackWCSVN extends MTrackSCMWorkingCopy {
70 function __construct(MTrackRepo $repo) {
71 $this->dir = mtrack_make_temp_dir();
74 stream_get_contents($this->repo->svn('checkout',
75 'file://' . $this->repo->repopath . '/trunk',
79 function getFile($path)
81 return $this->repo->file('trunk/' . $path);
85 function addFile($path)
88 $this->repo->svn('add', $this->dir . DIRECTORY_SEPARATOR . $path));
91 function delFile($path)
94 $this->repo->svn('rm', $this->dir . DIRECTORY_SEPARATOR . $path));
97 function commit(MTrackChangeset $CS)
99 list($proc, $pipes) = mtrack_run_tool('svn', 'proc',
100 array('ci', '--non-interactive', '--username', $CS->who,
101 '-m', $CS->reason, $this->dir));
103 $svn = MTrackConfig::get('tools', 'svn');
104 if (!strlen($svn)) $svn = 'svn';
106 "$svn ci --non-interactive " .
107 ' --username ' . escapeshellarg($CS->who) .
108 ' -m ' . escapeshellarg($CS->reason) .
111 0 => array('pipe', 'r'),
112 1 => array('pipe', 'w'),
113 2 => array('pipe', 'w'),
114 ), $pipes, $this->dir);
117 $output = stream_get_contents($pipes[1]);
118 $err = stream_get_contents($pipes[2]);
121 throw new Exception($err);
124 if (preg_match("/Committed revision (\d+)/", $output, $M)) {
127 $this->repo->svn('propset', 'svn:date',
129 '-r', $rev, $CS->when, $this->dir
135 class MTrackSCMSVN extends MTrackRepo {
136 protected $svn = 'svn';
137 static $debug = false;
139 public function getSCMMetaData() {
141 'name' => 'Subversion',
142 'tools' => array('svn', 'svnlook', 'svnadmin'),
146 function getServerURL() {
147 $url = parent::getServerURL();
148 if ($url) return $url;
149 $url = MTrackConfig::get('repos', 'serverurl');
151 return "svn+ssh://$url/" . $this->getBrowseRootName() . '/BRANCHNAME';
157 public function reconcileRepoSettings(MTrackSCM $r = null) {
161 if (!is_dir($r->repopath)) {
162 $stm = mtrack_run_tool('svnadmin', 'read', array('create', $r->repopath));
163 $out = stream_get_contents($stm);
165 throw new Exception("failed to create repo: $out");
167 file_put_contents("$r->repopath/hooks/pre-revprop-change",
168 "#!/bin/sh\nexit 0\n");
169 chmod("$r->repopath/hooks/pre-revprop-change", 0755);
170 $me = mtrack_canon_username(MTrackAuth::whoami());
171 $stm = mtrack_run_tool('svn', 'read', array('mkdir', '-m', 'init',
172 '--username', $me, "file://$r->repopath/trunk"));
173 $out = stream_get_contents($stm);
175 throw new Exception("failed to create trunk: $out");
177 system("chmod -R 02777 $r->repopath/db $r->repopath/locks");
179 $authzname = MTrackConfig::get('core', 'vardir') . '/svn.authz';
180 $svnserve = "[general]\nauthz-db = $authzname\n";
181 file_put_contents("$r->repopath/conf/svnserve.conf", $svnserve);
185 public function getDefaultRoot() {
189 public function getBranches()
194 public function getTags()
199 public function readdir($path, $object = null, $ident = null)
203 if ($object === null) {
207 $rev = $this->resolveRevision(null, $object, $ident);
209 $rpath = $this->repopath;
214 $fp = $this->svn('ls', '--xml', '-r', $rev,
217 $ls = stream_get_contents($fp);
218 $doc = simplexml_load_string($ls);
219 if (!is_object($doc)) {
220 echo '<pre>', htmlentities($ls, ENT_QUOTES, 'utf-8'), '</pre>';
222 if (isset($doc->list)) foreach ($doc->list->entry as $le) {
226 if ($name[0] == '/') {
227 $name = substr($name, 1);
229 /* Use the revision passed in to readdir rather than the revision
230 * in the entry, as svn can return a revision number that pre-dates
231 * that of the containing tag, and this causes the subsequent
232 * lookup of commit data to fail */
233 $res[] = new MTrackSCMFileSVN($this, $name,
234 //$le->commit['revision'],
236 $le['kind'] == 'dir');
241 public function file($path, $object = null, $ident = null)
243 if ($object == null) {
247 $rev = $this->resolveRevision(null, $object, $ident);
248 return new MTrackSCMFileSVN($this, $path, $rev);
251 public function history($path, $limit = null, $object = null, $ident = null)
257 if ($limit !== null) {
258 if (!is_int($limit)) {
259 $limit_date = strtotime($limit);
261 $limit_date = date('c', $limit_date);
266 if ($object !== null) {
267 $rev = $this->resolveRevision(null, $object, $ident);
268 if ($limit_date != null) {
270 $args[] = $rev . ':{' . $limit_date . '}';
271 } else if ($rev == 'HEAD') {
278 if ($limit !== null) {
281 } else if ($limit_date !== null) {
283 $args[] = '{' . $limit_date . '}:head';
286 $rpath = $this->repopath;
288 if ($path[0] != '/') {
299 $fp = $this->svn('log', '--xml', '-v', $args, "file://$spath");
301 $xml = stream_get_contents($fp);
302 $doc = @simplexml_load_string($xml);
303 if (!is_object($doc)) {
304 /* try looking at the parent */
305 $spath = dirname($spath);
309 $fp = $this->svn('log', '--xml', '-v', $args, "file://$spath");
310 $xml = stream_get_contents($fp);
311 $doc = @simplexml_load_string($xml);
314 if (!is_object($doc)) {
315 // echo '<pre>', htmlentities($xml, ENT_QUOTES, 'utf-8'), '</pre>';
319 if (php_sapi_name() == 'cli') {
322 echo htmlentities(var_export($xml, true)) . "<br>";
326 if ($origpath[0] != '/') {
327 $origpath = '/' . $origpath;
329 if ($doc->logentry) foreach ($doc->logentry as $le) {
331 $ent = new MTrackSCMEvent;
332 $ent->rev = (int)$le['revision'];
333 $ent->branches = array();
334 $ent->tags = array();
336 $ent->files = array();
337 foreach ($le->paths->path as $path) {
338 if (strncmp($path, $origpath, strlen($origpath))) {
342 $f = new MTrackSCMFileEvent;
343 $f->name = (string)$path;
344 $f->status = (string)$path['action'];
349 $ent->changeby = (string)$le->author;
350 $ent->ctime = MTrackDB::unixtime(strtotime($le->date));
351 $ent->changelog = (string)$le->msg;
357 if (count($res) == 0) {
363 function getCheckoutCommand() {
364 $url = $this->getServerURL();
366 return $this->scmtype . ' checkout ' . $this->getServerURL();
371 public function diff($path, $from = null, $to = null)
375 if ($path instanceof MTrackSCMFile) {
376 $is_file = !$path->is_dir;
377 if ($from === null) {
381 } elseif ($path instanceof MTrackSCMFileEvent) {
384 // http://subversion.tigris.org/issues/show_bug.cgi?id=2873
385 // Essentially, if there are files added in a changeset, you cannot use
386 // diff to show the diff of those newly added files if you explicitly
387 // request the file itself. So we need to assess whether $path represents
388 // a file and dance around by diffing the parent path.
391 $info = $this->svn('info', "file://$this->repopath$path", '-r', $from);
393 while (($line = fgets($info)) !== false) {
395 if (preg_match("/^Node Kind:\s+file/", $line)) {
401 // no data returned; path doesn't exist at that revision
409 $diffpath = dirname($path);
415 $diff = $this->svn('diff', '-r', $from, '-r', $to,
416 "file://$this->repopath$diffpath");
418 $diff = $this->svn('diff', '-c', $from,
419 "file://$this->repopath$diffpath");
425 $wanted = basename($path);
427 // search in the diffstream for the file that was originally requested
428 // and copy that through to the tmpfile we're using for the diff we're
429 // returning to the caller
430 while (($line = fgets($dir)) !== false) {
431 if (preg_match("/^Index: $wanted$/", $line)) {
433 fwrite($diff, $line);
435 } else if (preg_match("/^Index: /", $line)) {
441 fwrite($diff, $line);
449 public function getWorkingCopy()
451 return new MTrackWCSVN($this);
454 public function getRelatedChanges($revision)
461 $args = func_get_args();
462 return mtrack_run_tool('svn', 'read', $args);
466 MTrackRepo::registerSCM('svn', 'MTrackSCMSVN');