1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
4 /* Subversion SVN browsing */
5 require_once 'MTrack/SCMFile.php';
6 require_once 'MTrack/SCMWorkingCopy.php';
8 class MTrackSCMFileSVN extends MTrackSCMFile {
14 function __construct(MTrackSCM $repo, $name, $rev, $is_dir = false)
19 $this->is_dir = $is_dir;
22 public function _determineFileChangeEvent($reponame, $filename, $rev)
24 $repo = MTrack_Repo::loadByName($reponame);
25 list($ent) = $repo->history($filename, 1, 'rev', $rev);
29 public function getChangeEvent()
31 //FIXME - this should do something similar to git,
32 // and use the database rather than caching with expiry..
33 return MTrackSCMFileSVN::_determineFileChangeEvent($this->repo->getBrowseRootName(), $this->name, $this->rev);
34 //return mtrack_cache(
35 // array('MTrack_SCM_Hg_File', '_determineFileChangeEvent'),
36 // array($this->repo->id, $this->name, $this->rev),
44 return $this->repo->svn('cat', '-r', $this->rev,
45 'file://' . $this->repo->repopath . '/' . $this->name . "@$this->rev");
48 function annotate($include_line_content = false)
50 $xml = stream_get_contents($this->repo->svn('annotate', '--xml',
51 'file://' . $this->repo->repopath . '/' . $this->name . "@$this->rev"));
53 $xml = @simplexml_load_string($xml);
54 if (!is_object($xml)) {
57 if ($include_line_content) {
60 foreach ($xml->target->entry as $ent) {
61 $A = new MTrackSCMAnnotation;
62 $A->rev = (int)$ent->commit['revision'];
63 $A->changeby = (string)$ent->commit->author;
64 if ($include_line_content) {
65 $A->line = fgets($cat);
67 $ann[(int)$ent['line-number']] = $A;
74 class MTrackWCSVN extends MTrackSCMWorkingCopy {
77 function __construct(MTrack_Repo $repo) {
78 $this->dir = mtrack_make_temp_dir();
81 stream_get_contents($this->repo->svn('checkout',
82 'file://' . $this->repo->repopath . '/trunk',
86 function getFile($path)
88 return $this->repo->file('trunk/' . $path);
92 function addFile($path)
95 $this->repo->svn('add', $this->dir . DIRECTORY_SEPARATOR . $path));
98 function delFile($path)
101 $this->repo->svn('rm', $this->dir . DIRECTORY_SEPARATOR . $path));
104 function commit(MTrackChangeset $CS)
106 list($proc, $pipes) = MTrackSCM::run('svn', 'proc',
107 array('ci', '--non-interactive', '--username', $CS->who,
108 '-m', $CS->reason, $this->dir));
110 $svn = MTrackConfig::get('tools', 'svn');
111 if (!strlen($svn)) $svn = 'svn';
113 "$svn ci --non-interactive " .
114 ' --username ' . escapeshellarg($CS->who) .
115 ' -m ' . escapeshellarg($CS->reason) .
118 0 => array('pipe', 'r'),
119 1 => array('pipe', 'w'),
120 2 => array('pipe', 'w'),
121 ), $pipes, $this->dir);
124 $output = stream_get_contents($pipes[1]);
125 $err = stream_get_contents($pipes[2]);
128 throw new Exception($err);
131 if (preg_match("/Committed revision (\d+)/", $output, $M)) {
134 $this->repo->svn('propset', 'svn:date',
136 '-r', $rev, $CS->when, $this->dir
142 class MTrackSCMSVN extends MTrack_Repo {
143 protected $svn = 'svn';
144 static $debug = false;
146 public function getSCMMetaData() {
148 'name' => 'Subversion',
149 'tools' => array('svn', 'svnlook', 'svnadmin'),
153 function getServerURL() {
154 $url = parent::getServerURL();
155 if ($url) return $url;
156 $url = MTrackConfig::get('repos', 'serverurl');
158 return "svn+ssh://$url/" . $this->getBrowseRootName() . '/BRANCHNAME';
164 public function reconcileRepoSettings(MTrackSCM $r = null) {
168 if (!is_dir($r->repopath)) {
169 $stm = MTrackSCM::run('svnadmin', 'read', array('create', $r->repopath));
170 $out = stream_get_contents($stm);
172 throw new Exception("failed to create repo: $out");
174 file_put_contents("$r->repopath/hooks/pre-revprop-change",
175 "#!/bin/sh\nexit 0\n");
176 chmod("$r->repopath/hooks/pre-revprop-change", 0755);
177 $me = mtrack_canon_username(MTrackAuth::whoami());
178 $stm = MTrackSCM::run('svn', 'read', array('mkdir', '-m', 'init',
179 '--username', $me, "file://$r->repopath/trunk"));
180 $out = stream_get_contents($stm);
182 throw new Exception("failed to create trunk: $out");
184 system("chmod -R 02777 $r->repopath/db $r->repopath/locks");
186 $authzname = MTrackConfig::get('core', 'vardir') . '/svn.authz';
187 $svnserve = "[general]\nauthz-db = $authzname\n";
188 file_put_contents("$r->repopath/conf/svnserve.conf", $svnserve);
192 public function getDefaultRoot() {
196 public function getBranches()
201 public function getTags()
206 public function readdir($path, $object = null, $ident = null)
210 if ($object === null) {
214 $rev = $this->resolveRevision(null, $object, $ident);
216 $rpath = $this->repopath;
221 $fp = $this->svn('ls', '--xml', '-r', $rev,
224 $ls = stream_get_contents($fp);
225 $doc = simplexml_load_string($ls);
226 if (!is_object($doc)) {
227 echo '<pre>', htmlentities($ls, ENT_QUOTES, 'utf-8'), '</pre>';
229 if (isset($doc->list)) foreach ($doc->list->entry as $le) {
233 if ($name[0] == '/') {
234 $name = substr($name, 1);
236 /* Use the revision passed in to readdir rather than the revision
237 * in the entry, as svn can return a revision number that pre-dates
238 * that of the containing tag, and this causes the subsequent
239 * lookup of commit data to fail */
240 $res[] = new MTrackSCMFileSVN($this, $name,
241 //$le->commit['revision'],
243 $le['kind'] == 'dir');
248 public function file($path, $object = null, $ident = null)
250 if ($object == null) {
254 $rev = $this->resolveRevision(null, $object, $ident);
255 return new MTrackSCMFileSVN($this, $path, $rev);
258 public function history($path, $limit = null, $object = null, $ident = null)
264 if ($limit !== null) {
265 if (!is_int($limit)) {
266 $limit_date = strtotime($limit);
268 $limit_date = date('c', $limit_date);
273 if ($object !== null) {
274 $rev = $this->resolveRevision(null, $object, $ident);
275 if ($limit_date != null) {
277 $args[] = $rev . ':{' . $limit_date . '}';
278 } else if ($rev == 'HEAD') {
285 if ($limit !== null) {
288 } else if ($limit_date !== null) {
290 $args[] = '{' . $limit_date . '}:head';
293 $rpath = $this->repopath;
295 if ($path[0] != '/') {
306 $fp = $this->svn('log', '--xml', '-v', $args, "file://$spath");
308 $xml = stream_get_contents($fp);
309 $doc = @simplexml_load_string($xml);
310 if (!is_object($doc)) {
311 /* try looking at the parent */
312 $spath = dirname($spath);
316 $fp = $this->svn('log', '--xml', '-v', $args, "file://$spath");
317 $xml = stream_get_contents($fp);
318 $doc = @simplexml_load_string($xml);
321 if (!is_object($doc)) {
322 // echo '<pre>', htmlentities($xml, ENT_QUOTES, 'utf-8'), '</pre>';
326 if (php_sapi_name() == 'cli') {
329 echo htmlentities(var_export($xml, true)) . "<br>";
333 if ($origpath[0] != '/') {
334 $origpath = '/' . $origpath;
336 if ($doc->logentry) foreach ($doc->logentry as $le) {
338 $ent = new MTrackSCMEvent;
340 $ent->rev = (int)$le['revision'];
341 $ent->branches = array();
342 $ent->tags = array();
344 $ent->files = array();
345 foreach ($le->paths->path as $path) {
346 if (strncmp($path, $origpath, strlen($origpath))) {
350 $f = new MTrackSCMFileEvent;
351 $f->name = (string)$path;
352 $f->status = (string)$path['action'];
357 $ent->changeby = (string)$le->author;
358 $ent->ctime = MTrackDB::unixtime(strtotime($le->date));
359 $ent->changelog = (string)$le->msg;
365 if (count($res) == 0) {
371 function getCheckoutCommand() {
372 $url = $this->getServerURL();
374 return $this->scmtype . ' checkout ' . $this->getServerURL();
379 public function diff($path, $from = null, $to = null)
383 if ($path instanceof MTrackSCMFile) {
384 $is_file = !$path->is_dir;
385 if ($from === null) {
389 } elseif ($path instanceof MTrackSCMFileEvent) {
392 // http://subversion.tigris.org/issues/show_bug.cgi?id=2873
393 // Essentially, if there are files added in a changeset, you cannot use
394 // diff to show the diff of those newly added files if you explicitly
395 // request the file itself. So we need to assess whether $path represents
396 // a file and dance around by diffing the parent path.
399 $info = $this->svn('info', "file://$this->repopath$path", '-r', $from);
401 while (($line = fgets($info)) !== false) {
403 if (preg_match("/^Node Kind:\s+file/", $line)) {
409 // no data returned; path doesn't exist at that revision
417 $diffpath = dirname($path);
423 $diff = $this->svn('diff', '-r', $from, '-r', $to,
424 "file://$this->repopath$diffpath");
426 $diff = $this->svn('diff', '-c', $from,
427 "file://$this->repopath$diffpath");
433 $wanted = basename($path);
435 // search in the diffstream for the file that was originally requested
436 // and copy that through to the tmpfile we're using for the diff we're
437 // returning to the caller
438 while (($line = fgets($dir)) !== false) {
439 if (preg_match("/^Index: $wanted$/", $line)) {
441 fwrite($diff, $line);
443 } else if (preg_match("/^Index: /", $line)) {
449 fwrite($diff, $line);
457 public function getWorkingCopy()
459 return new MTrackWCSVN($this);
462 public function getRelatedChanges($revision)
469 $args = func_get_args();
470 return MTrackSCM::run('svn', 'read', $args);