1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-12-02 03:32:41 +01:00
phorge-arcanist/src/repository/api/ArcanistSubversionAPI.php

696 lines
20 KiB
PHP
Raw Normal View History

2011-01-10 00:22:25 +01:00
<?php
/**
* Interfaces with Subversion working copies.
*
* @group workingcopy
*/
final class ArcanistSubversionAPI extends ArcanistRepositoryAPI {
2011-01-10 00:22:25 +01:00
protected $svnStatus;
protected $svnBaseRevisions;
protected $svnInfo = array();
protected $svnInfoRaw = array();
protected $svnDiffRaw = array();
private $svnBaseRevisionNumber;
private $statusPaths = array();
2011-01-10 00:22:25 +01:00
public function getSourceControlSystemName() {
return 'svn';
}
public function getMetadataPath() {
[Arcanist] fix scratch dir for svn >= 1.7 Summary: svn 1.7 got rid of the per-direcotry .svn dirs in favor of a single .svn directory under the root of the working copy. Unfortunately, we relied on ther being a .svn directory at the same level as .arcconfig, which may or may not be at the root of the working copy. We now walk up the directory tree until we find a .svn directory that we can use for scratch files. Test Plan: [16:27:52 Mon Jun 25 2012] dschleimer@dev4022.snc6 ~/data/admin/facebook/scripts/db db 21298 $ ls -la .arcconfig .svn ../../../.svn/arc/config ls: .svn: No such file or directory ls: ../../../.svn/arc/config: No such file or directory -rw-r--r-- 1 dschleimer users 239 Jun 25 16:15 .arcconfig [16:27:54 Mon Jun 25 2012] dschleimer@dev4022.snc6 ~/data/admin/facebook/scripts/db db 21298 $ ~/devtools/arcanist/bin/arc set-config --local foo bar Set key 'foo' = 'bar' in local config. [16:29:40 Mon Jun 25 2012] dschleimer@dev4022.snc6 ~/data/admin/facebook/scripts/db db 21298 $ ls -la .arcconfig .svn ../../../.svn/arc/config ls: .svn: No such file or directory -rw-r--r-- 1 dschleimer users 239 Jun 25 16:15 .arcconfig -rw-r--r-- 1 dschleimer users 20 Jun 25 16:29 ../../../.svn/arc/config [16:29:43 Mon Jun 25 2012] dschleimer@dev4022.snc6 ~/data/admin/facebook/scripts/db db 21298 $ cat ../../../.svn/arc/config { "foo" : "bar" } [16:29:48 Mon Jun 25 2012] dschleimer@dev4022.snc6 ~/data/admin/facebook/scripts/db db 21299 $ ~/devtools/arcanist/bin/arc get-config foo (global) foo = (local) foo = bar Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1411 Differential Revision: https://secure.phabricator.com/D2856
2012-06-26 02:13:29 +02:00
static $svn_dir = null;
if ($svn_dir === null) {
// from svn 1.7, subversion keeps a single .svn directly under
// the working copy root. However, we allow .arcconfigs that
// aren't at the working copy root.
foreach (Filesystem::walkToRoot($this->getPath()) as $parent) {
$possible_svn_dir = Filesystem::resolvePath('.svn', $parent);
if (Filesystem::pathExists($possible_svn_dir)) {
$svn_dir = $possible_svn_dir;
break;
}
}
}
return $svn_dir;
}
protected function buildLocalFuture(array $argv) {
$argv[0] = 'svn '.$argv[0];
$future = newv('ExecFuture', $argv);
$future->setCWD($this->getPath());
return $future;
}
Refactor getWorkingCopyStatus() into getUncommittedStatus() and getCommitRangeStatus() Summary: See discussion in D4049. The getWorkingCopyStatus() method gets called from requireCleanWorkingCopy() in a lot of places, which triggers resolution of the base of the commit range. This is unnecessary; we do not need to examine the base commit in order to determine whether the working copy is dirty or not. This causes problems, in D4049 and elsewhere (we currently have a lot of fluff calls to setDefaultBaseCommit() in workflows which need to call requireCleanWorkingCopy() but do not ever use commit ranges, such as `arc patch`). This is mostly an artifact of SVN, where the "commit range" and "uncommitted stuff in the working copy" are always the same. - Split the method into two status methods: getUncommittedStatus() (uncommitted stuff in the working copy, required by requireCleanWorkingCopy()) and getCommitRangeStatus() (committed stuff in the commit range). - Lift caching out of the implementations into the base class. - Dirty the cache after we commit. This doesn't do anything useful on its own and creates one caching problem (`commitRangeStatusCache` is not invalidated when the commit range changes because of `setBaseCommit()` or similar) but I wanted to break things apart here. I won't land it until there's a more complete picture. This creates a minor performance regression in git and hg (we run less stuff in parallel than previously) but all the commands should be disk-bound anyway and the regression should be minor. It prevents a larger regression in `hg` in D4049, and lets us do less work to arrive at common error states (dirty working copy). We can examine perf at the end of this change sequence. Test Plan: Ran unit tests, various `arc` commands. Reviewers: vrana Reviewed By: vrana CC: aran Differential Revision: https://secure.phabricator.com/D4095
2012-12-17 21:53:28 +01:00
protected function buildCommitRangeStatus() {
// In SVN, the commit range is always "uncommitted changes", so these
// statuses are equivalent.
return $this->getUncommittedStatus();
}
protected function buildUncommittedStatus() {
2011-01-10 00:22:25 +01:00
return $this->getSVNStatus();
}
public function getSVNBaseRevisions() {
if ($this->svnBaseRevisions === null) {
$this->getSVNStatus();
}
return $this->svnBaseRevisions;
}
public function limitStatusToPaths(array $paths) {
$this->statusPaths = $paths;
return $this;
}
2011-01-10 00:22:25 +01:00
public function getSVNStatus($with_externals = false) {
if ($this->svnStatus === null) {
if ($this->statusPaths) {
list($status) = $this->execxLocal(
'--xml status %Ls',
$this->statusPaths);
} else {
list($status) = $this->execxLocal('--xml status');
}
2011-01-10 00:22:25 +01:00
$xml = new SimpleXMLElement($status);
$externals = array();
$files = array();
foreach ($xml->target as $target) {
$this->svnBaseRevisions = array();
foreach ($target->entry as $entry) {
$path = (string)$entry['path'];
// On Windows, we get paths with backslash directory separators here.
// Normalize them to the format everything else expects and generates.
if (phutil_is_windows()) {
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
$mask = 0;
$props = (string)($entry->{'wc-status'}[0]['props']);
$item = (string)($entry->{'wc-status'}[0]['item']);
$base = (string)($entry->{'wc-status'}[0]['revision']);
$this->svnBaseRevisions[$path] = $base;
switch ($props) {
case 'none':
case 'normal':
break;
case 'modified':
$mask |= self::FLAG_MODIFIED;
break;
default:
throw new Exception("Unrecognized property status '{$props}'.");
}
2011-01-10 00:22:25 +01:00
$mask |= $this->parseSVNStatus($item);
if ($item == 'external') {
$externals[] = $path;
}
2011-01-10 00:22:25 +01:00
// This is new in or around Subversion 1.6.
$tree_conflicts = ($entry->{'wc-status'}[0]['tree-conflicted']);
if ((string)$tree_conflicts) {
$mask |= self::FLAG_CONFLICT;
}
2011-03-20 23:06:55 +01:00
$files[$path] = $mask;
}
2011-01-10 00:22:25 +01:00
}
foreach ($files as $path => $mask) {
foreach ($externals as $external) {
if (!strncmp($path.'/', $external.'/', strlen($external) + 1)) {
2011-01-10 00:22:25 +01:00
$files[$path] |= self::FLAG_EXTERNALS;
}
}
}
$this->svnStatus = $files;
}
$status = $this->svnStatus;
if (!$with_externals) {
foreach ($status as $path => $mask) {
if ($mask & ArcanistRepositoryAPI::FLAG_EXTERNALS) {
unset($status[$path]);
}
}
}
return $status;
}
private function parseSVNStatus($item) {
switch ($item) {
case 'none':
// We can get 'none' for property changes on a directory.
case 'normal':
return 0;
case 'external':
return self::FLAG_EXTERNALS;
case 'unversioned':
return self::FLAG_UNTRACKED;
case 'obstructed':
return self::FLAG_OBSTRUCTED;
case 'missing':
return self::FLAG_MISSING;
case 'added':
return self::FLAG_ADDED;
case 'replaced':
// This is the result of "svn rm"-ing a file, putting another one
// in place of it, and then "svn add"-ing the new file. Just treat
// this as equivalent to "modified".
return self::FLAG_MODIFIED;
case 'modified':
return self::FLAG_MODIFIED;
case 'deleted':
return self::FLAG_DELETED;
case 'conflicted':
return self::FLAG_CONFLICT;
case 'incomplete':
return self::FLAG_INCOMPLETE;
default:
throw new Exception("Unrecognized item status '{$item}'.");
}
}
public function addToCommit(array $paths) {
$add = array_filter($paths, 'Filesystem::pathExists');
if ($add) {
$this->execxLocal(
'add -- %Ls',
$add);
}
if ($add != $paths) {
$this->execxLocal(
'delete -- %Ls',
array_diff($paths, $add));
}
$this->svnStatus = null;
}
2011-01-10 00:22:25 +01:00
public function getSVNProperty($path, $property) {
list($stdout) = execx(
'svn propget %s %s@',
$property,
$this->getPath($path));
return trim($stdout);
}
public function getSourceControlPath() {
return idx($this->getSVNInfo('/'), 'URL');
}
public function getSourceControlBaseRevision() {
$info = $this->getSVNInfo('/');
return $info['URL'].'@'.$this->getSVNBaseRevisionNumber();
}
public function getCanonicalRevisionName($string) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getSVNBaseRevisionNumber() {
if ($this->svnBaseRevisionNumber) {
return $this->svnBaseRevisionNumber;
}
$info = $this->getSVNInfo('/');
return $info['Revision'];
}
public function overrideSVNBaseRevisionNumber($effective_base_revision) {
$this->svnBaseRevisionNumber = $effective_base_revision;
return $this;
2011-01-10 00:22:25 +01:00
}
public function getBranchName() {
Improve arc commit with SVN branches Summary: If you are trying to commit someone else's diff, arc commit gives warnings about path mismatch. This changes the path comparison to be based on the repo url rather than the local working directory. E.g. if both the author and committer are working in branches/release/2013_08_07 despite being checked out in ~/dev/2013_08_07 (system user being different, of course) it no longer warns that the WC path is different Original behavior: eric@Eric-MBP ~/dev/2013_07_31: arc commit --revision 21 You are not the author of 'D21: WeMerge Automatic Request'. Commit this revision anyway? [y/N] y Revision 'D21: WeMerge Automatic Request' was generated from '', but current working copy root is '/Users/eric/dev/2013_07_31/'. Commit this revision anyway? [y/N] y Committing 'D21: WeMerge Automatic Request'... Adding test Transmitting file data . Committed revision 52676. Closing revision D21 'WeMerge Automatic Request'... Exception ERR-CONDUIT-CORE: You can not mark a revision you don't own as closed. (Run with --trace for a full exception trace.) New behavior: eric@Eric-MBP ~/dev/2013_07_31: arc commit --revision 24 You are not the author of 'D24: WeMerge Automatic Request'. Commit this revision anyway? [y/N] y Committing 'D24: WeMerge Automatic Request'... Adding test Transmitting file data . Committed revision 52679. Closing revision D24 'WeMerge Automatic Request'... Exception ERR-CONDUIT-CORE: You can not mark a revision you don't own as closed. (Run with --trace for a full exception trace.) Test Plan: 'arc diff' changes with one user. 'arc patch Dxx' on a different working copy by a different user to review and test changes. accept review. 'arc commit --revision xx' as reviewer to land the patch. complaint goes away. Reviewers: epriestley, ghostwriter78 Reviewed By: epriestley CC: aran, epriestley, Korvin Differential Revision: https://secure.phabricator.com/D6665
2013-08-04 18:01:22 +02:00
$info = $this->getSVNInfo('/');
$repo_root = idx($info, 'Repository Root');
$repo_root_length = strlen($repo_root);
$url = idx($info, 'URL');
if (substr($url, 0, $repo_root_length) == $repo_root) {
return substr($url, $repo_root_length);
}
2011-01-10 00:22:25 +01:00
return 'svn';
}
Allow `arc` to identify repositories without "project_id" Summary: Ref T4343. Continues the process of reducing the prominence of Arcanist Projects. Primarily: - Query Phabricator to identify the working copy based on explicit configuration, or guess based on heuristics. - Enhance `arc which` to explain the process to the user. - The `project_id` key is no longer required in `.arcconfig`. Minor/cleanup changes: - Rename `project_id` to `project.name` (consistency, clarity). - Rename `conduit_uri` to `phabricator.uri` (consistency, clairty). - These both need documentation updates. - Add `repository.callsign` to explicitly bind to a repository. - Updated `.arcconfig` for the new values. - Fix a unit test which broke a while ago when we fixed a rare definition of "unstaged". - Make `getRepositoryUUID()` generic so we can get rid of one `instanceof`. Test Plan: - Ran `arc which`. - Ran `arc diff`. - This doesn't really change anything, so the only real risk is version compatibility breaks. This //does// introduce such a break, but the window is very narrow: if you upgrade `arc` after this commit, and try to diff against a Phabricator which was updated after yesterday (D8068) but before D8072 lands, the lookup will work so we'll add `repositoryPHID` to the `differential.creatediff` call, but it won't exist in Phabricator yet. This window is so narrow that I'm not going to try to fix it, as I'd guess there is a significant chance that no users will be affected. I don't see a clever way to fix it that doesn't involve a lot of work, either. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T4343 Differential Revision: https://secure.phabricator.com/D8073
2014-01-27 00:31:30 +01:00
public function getRemoteURI() {
return idx($this->getSVNInfo('/'), 'Repository Root');
}
2011-01-10 00:22:25 +01:00
public function buildInfoFuture($path) {
if ($path == '/') {
// When the root of a working copy is referenced by a symlink and you
// execute 'svn info' on that symlink, svn fails. This is a longstanding
// bug in svn:
//
// See http://subversion.tigris.org/issues/show_bug.cgi?id=2305
//
// To reproduce, do:
//
// $ ln -s working_copy working_link
// $ svn info working_copy # ok
// $ svn info working_link # fails
//
// Work around this by cd-ing into the directory before executing
// 'svn info'.
return $this->buildLocalFuture(array('info .'));
} else {
// Note: here and elsewhere we need to append "@" to the path because if
// a file has a literal "@" in it, everything after that will be
// interpreted as a revision. By appending "@" with no argument, SVN
// parses it properly.
return $this->buildLocalFuture(array('info %s@', $this->getPath($path)));
}
2011-01-10 00:22:25 +01:00
}
public function buildDiffFuture($path) {
// The "--depth empty" flag prevents us from picking up changes in
// children when we run 'diff' against a directory. Specifically, when a
// user has added or modified some directory "example/", we want to return
// ONLY changes to that directory when given it as a path. If we run
// without "--depth empty", svn will give us changes to the directory
// itself (such as property changes) and also give us changes to any
// files within the directory (basically, implicit recursion). We don't
// want that, so prevent recursive diffing.
$root = phutil_get_library_root('arcanist');
if (phutil_is_windows()) {
// TODO: Provide a binary_safe_diff script for Windows.
// TODO: Provide a diff command which can take lines of context somehow.
return $this->buildLocalFuture(
array(
'diff --depth empty %s',
$path,
));
} else {
$diff_bin = $root.'/../scripts/repository/binary_safe_diff.sh';
$diff_cmd = Filesystem::resolvePath($diff_bin);
return $this->buildLocalFuture(
array(
'diff --depth empty --diff-cmd %s -x -U%d %s',
$diff_cmd,
$this->getDiffLinesOfContext(),
$path,
));
}
2011-01-10 00:22:25 +01:00
}
public function primeSVNInfoResult($path, $result) {
$this->svnInfoRaw[$path] = $result;
return $this;
}
public function primeSVNDiffResult($path, $result) {
$this->svnDiffRaw[$path] = $result;
return $this;
}
public function getSVNInfo($path) {
if (empty($this->svnInfo[$path])) {
if (empty($this->svnInfoRaw[$path])) {
$this->svnInfoRaw[$path] = $this->buildInfoFuture($path)->resolve();
}
list($err, $stdout) = $this->svnInfoRaw[$path];
if ($err) {
throw new Exception(
"Error #{$err} executing svn info against '{$path}'.");
}
// TODO: Hack for Windows.
$stdout = str_replace("\r\n", "\n", $stdout);
2011-01-10 00:22:25 +01:00
$patterns = array(
'/^(URL): (\S+)$/m',
'/^(Revision): (\d+)$/m',
'/^(Last Changed Author): (\S+)$/m',
'/^(Last Changed Rev): (\d+)$/m',
'/^(Last Changed Date): (.+) \(.+\)$/m',
'/^(Copied From URL): (\S+)$/m',
'/^(Copied From Rev): (\d+)$/m',
Improve arc commit with SVN branches Summary: If you are trying to commit someone else's diff, arc commit gives warnings about path mismatch. This changes the path comparison to be based on the repo url rather than the local working directory. E.g. if both the author and committer are working in branches/release/2013_08_07 despite being checked out in ~/dev/2013_08_07 (system user being different, of course) it no longer warns that the WC path is different Original behavior: eric@Eric-MBP ~/dev/2013_07_31: arc commit --revision 21 You are not the author of 'D21: WeMerge Automatic Request'. Commit this revision anyway? [y/N] y Revision 'D21: WeMerge Automatic Request' was generated from '', but current working copy root is '/Users/eric/dev/2013_07_31/'. Commit this revision anyway? [y/N] y Committing 'D21: WeMerge Automatic Request'... Adding test Transmitting file data . Committed revision 52676. Closing revision D21 'WeMerge Automatic Request'... Exception ERR-CONDUIT-CORE: You can not mark a revision you don't own as closed. (Run with --trace for a full exception trace.) New behavior: eric@Eric-MBP ~/dev/2013_07_31: arc commit --revision 24 You are not the author of 'D24: WeMerge Automatic Request'. Commit this revision anyway? [y/N] y Committing 'D24: WeMerge Automatic Request'... Adding test Transmitting file data . Committed revision 52679. Closing revision D24 'WeMerge Automatic Request'... Exception ERR-CONDUIT-CORE: You can not mark a revision you don't own as closed. (Run with --trace for a full exception trace.) Test Plan: 'arc diff' changes with one user. 'arc patch Dxx' on a different working copy by a different user to review and test changes. accept review. 'arc commit --revision xx' as reviewer to land the patch. complaint goes away. Reviewers: epriestley, ghostwriter78 Reviewed By: epriestley CC: aran, epriestley, Korvin Differential Revision: https://secure.phabricator.com/D6665
2013-08-04 18:01:22 +02:00
'/^(Repository Root): (\S+)$/m',
2011-04-06 07:27:32 +02:00
'/^(Repository UUID): (\S+)$/m',
'/^(Node Kind): (\S+)$/m',
2011-01-10 00:22:25 +01:00
);
$result = array();
foreach ($patterns as $pattern) {
$matches = null;
if (preg_match($pattern, $stdout, $matches)) {
$result[$matches[1]] = $matches[2];
}
}
if (isset($result['Last Changed Date'])) {
$result['Last Changed Date'] = strtotime($result['Last Changed Date']);
}
if (empty($result)) {
throw new Exception('Unable to parse SVN info.');
}
$this->svnInfo[$path] = $result;
}
return $this->svnInfo[$path];
}
public function getRawDiffText($path) {
$status = $this->getSVNStatus();
if (!isset($status[$path])) {
return null;
}
$status = $status[$path];
// Build meaningful diff text for "svn copy" operations.
if ($status & ArcanistRepositoryAPI::FLAG_ADDED) {
$info = $this->getSVNInfo($path);
if (!empty($info['Copied From URL'])) {
return $this->buildSyntheticAdditionDiff(
$path,
$info['Copied From URL'],
$info['Copied From Rev']);
}
}
// If we run "diff" on a binary file which doesn't have the "svn:mime-type"
// of "application/octet-stream", `diff' will explode in a rain of
// unhelpful hellfire as it tries to build a textual diff of the two
// files. We just fix this inline since it's pretty unambiguous.
// TODO: Move this to configuration?
$matches = null;
if (preg_match('/\.(gif|png|jpe?g|swf|pdf|ico)$/i', $path, $matches)) {
// Check if the file is deleted first; SVN will complain if we try to
// get properties of a deleted file.
if ($status & ArcanistRepositoryAPI::FLAG_DELETED) {
return <<<EODIFF
Index: {$path}
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
EODIFF;
}
$mime = $this->getSVNProperty($path, 'svn:mime-type');
2011-01-10 00:22:25 +01:00
if ($mime != 'application/octet-stream') {
execx(
'svn propset svn:mime-type application/octet-stream %s',
self::escapeFileNameForSVN($this->getPath($path)));
2011-01-10 00:22:25 +01:00
}
}
if (empty($this->svnDiffRaw[$path])) {
$this->svnDiffRaw[$path] = $this->buildDiffFuture($path)->resolve();
}
list($err, $stdout, $stderr) = $this->svnDiffRaw[$path];
// Note: GNU Diff returns 2 when SVN hands it binary files to diff and they
// differ. This is not an error; it is documented behavior. But SVN isn't
// happy about it. SVN will exit with code 1 and return the string below.
if ($err != 0 && $stderr !== "svn: 'diff' returned 2\n") {
throw new Exception(
"svn diff returned unexpected error code: $err\n".
"stdout: $stdout\n".
"stderr: $stderr");
}
if ($err == 0 && empty($stdout)) {
// If there are no changes, 'diff' exits with no output, but that means
// we can not distinguish between empty and unmodified files. Build a
// synthetic "diff" without any changes in it.
return $this->buildSyntheticUnchangedDiff($path);
}
return $stdout;
}
protected function buildSyntheticAdditionDiff($path, $source, $rev) {
$type = $this->getSVNProperty($path, 'svn:mime-type');
if ($type == 'application/octet-stream') {
return <<<EODIFF
Index: {$path}
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
EODIFF;
}
if (is_dir($this->getPath($path))) {
return null;
}
$data = Filesystem::readFile($this->getPath($path));
list($orig) = execx('svn cat %s@%s', $source, $rev);
$src = new TempFile();
$dst = new TempFile();
Filesystem::writeFile($src, $orig);
Filesystem::writeFile($dst, $data);
list($err, $diff) = exec_manual(
'diff -L a/%s -L b/%s -U%d %s %s',
str_replace($this->getSourceControlPath().'/', '', $source),
$path,
$this->getDiffLinesOfContext(),
$src,
$dst);
if ($err == 1) { // 1 means there are differences.
return <<<EODIFF
Index: {$path}
===================================================================
{$diff}
EODIFF;
} else {
return $this->buildSyntheticUnchangedDiff($path);
}
}
protected function buildSyntheticUnchangedDiff($path) {
$full_path = $this->getPath($path);
if (is_dir($full_path)) {
return null;
}
if (!file_exists($full_path)) {
return null;
}
2011-01-10 00:22:25 +01:00
$data = Filesystem::readFile($full_path);
$lines = explode("\n", $data);
$len = count($lines);
foreach ($lines as $key => $line) {
$lines[$key] = ' '.$line;
}
$lines = implode("\n", $lines);
return <<<EODIFF
Index: {$path}
===================================================================
--- {$path} (synthetic)
+++ {$path} (synthetic)
@@ -1,{$len} +1,{$len} @@
{$lines}
EODIFF;
}
public function getAllFiles() {
// TODO: Handle paths with newlines.
$future = $this->buildLocalFuture(array('list -R'));
return new PhutilCallbackFilterIterator(
new LinesOfALargeExecFuture($future),
array($this, 'filterFiles'));
}
public function getChangedFiles($since_commit) {
$url = '';
$match = null;
if (preg_match('/(.*)@(.*)/', $since_commit, $match)) {
list(, $url, $since_commit) = $match;
}
// TODO: Handle paths with newlines.
list($stdout) = $this->execxLocal(
'--xml diff --revision %s:HEAD --summarize %s',
$since_commit,
$url);
$xml = new SimpleXMLElement($stdout);
$return = array();
foreach ($xml->paths[0]->path as $path) {
$return[(string)$path] = $this->parseSVNStatus($path['item']);
}
return $return;
}
public function filterFiles($path) {
// NOTE: SVN uses '/' also on Windows.
if ($path == '' || substr($path, -1) == '/') {
return null;
}
return $path;
}
2011-01-10 00:22:25 +01:00
public function getBlame($path) {
$blame = array();
list($stdout) = $this->execxLocal('blame %s', $path);
2011-01-10 00:22:25 +01:00
$stdout = trim($stdout);
if (!strlen($stdout)) {
// Empty file.
return $blame;
}
foreach (explode("\n", $stdout) as $line) {
$m = array();
if (!preg_match('/^\s*(\d+)\s+(\S+)/', $line, $m)) {
throw new Exception("Bad blame? `{$line}'");
}
$revision = $m[1];
$author = $m[2];
$blame[] = array($author, $revision);
}
return $blame;
}
public function getOriginalFileData($path) {
// SVN issues warnings for nonexistent paths, directories, etc., but still
// returns no error code. However, for new paths in the working copy it
// fails. Assume that failure means the original file does not exist.
list($err, $stdout) = $this->execManualLocal('cat %s@', $path);
if ($err) {
return null;
}
return $stdout;
}
public function getCurrentFileData($path) {
$full_path = $this->getPath($path);
if (Filesystem::pathExists($full_path)) {
return Filesystem::readFile($full_path);
}
return null;
}
2011-01-10 00:22:25 +01:00
Allow `arc` to identify repositories without "project_id" Summary: Ref T4343. Continues the process of reducing the prominence of Arcanist Projects. Primarily: - Query Phabricator to identify the working copy based on explicit configuration, or guess based on heuristics. - Enhance `arc which` to explain the process to the user. - The `project_id` key is no longer required in `.arcconfig`. Minor/cleanup changes: - Rename `project_id` to `project.name` (consistency, clarity). - Rename `conduit_uri` to `phabricator.uri` (consistency, clairty). - These both need documentation updates. - Add `repository.callsign` to explicitly bind to a repository. - Updated `.arcconfig` for the new values. - Fix a unit test which broke a while ago when we fixed a rare definition of "unstaged". - Make `getRepositoryUUID()` generic so we can get rid of one `instanceof`. Test Plan: - Ran `arc which`. - Ran `arc diff`. - This doesn't really change anything, so the only real risk is version compatibility breaks. This //does// introduce such a break, but the window is very narrow: if you upgrade `arc` after this commit, and try to diff against a Phabricator which was updated after yesterday (D8068) but before D8072 lands, the lookup will work so we'll add `repositoryPHID` to the `differential.creatediff` call, but it won't exist in Phabricator yet. This window is so narrow that I'm not going to try to fix it, as I'd guess there is a significant chance that no users will be affected. I don't see a clever way to fix it that doesn't involve a lot of work, either. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T4343 Differential Revision: https://secure.phabricator.com/D8073
2014-01-27 00:31:30 +01:00
public function getRepositoryUUID() {
2011-04-06 05:12:37 +02:00
$info = $this->getSVNInfo('/');
return $info['Repository UUID'];
}
public function getLocalCommitInformation() {
return null;
}
public function isHistoryDefaultImmutable() {
return true;
}
public function supportsAmend() {
return false;
}
public function supportsCommitRanges() {
return false;
}
public function supportsLocalCommits() {
return false;
}
public function hasLocalCommit($commit) {
return false;
}
public function getWorkingCopyRevision() {
return $this->getSourceControlBaseRevision();
}
public function supportsLocalBranchMerge() {
return false;
}
public function getFinalizedRevisionMessage() {
// In other VCSes we give push instructions here, but it never makes sense
// in SVN.
return 'Done.';
}
public function loadWorkingCopyDifferentialRevisions(
ConduitClient $conduit,
array $query) {
// We don't have much to go on in SVN, look for revisions that came from
// this directory and belong to the same project.
$project = $this->getWorkingCopyIdentity()->getProjectID();
if (!$project) {
return array();
}
$results = $conduit->callMethodSynchronous(
'differential.query',
$query + array(
'arcanistProjects' => array($project),
));
foreach ($results as $key => $result) {
if ($result['sourcePath'] != $this->getPath()) {
unset($results[$key]);
}
}
foreach ($results as $key => $result) {
$results[$key]['why'] =
'Matching arcanist project name and working copy directory path.';
}
return $results;
}
public function updateWorkingCopy() {
$this->execxLocal('up');
}
public static function escapeFileNamesForSVN(array $files) {
foreach ($files as $k => $file) {
$files[$k] = self::escapeFileNameForSVN($file);
}
return $files;
}
public static function escapeFileNameForSVN($file) {
// SVN interprets "x@1" as meaning "file x at revision 1", which is not
// intended for files named "sprite@2x.png" or similar. For files with an
// "@" in their names, escape them by adding "@" at the end, which SVN
// interprets as "at the working copy revision". There is a special case
// where ".@" means "fail with an error" instead of ". at the working copy
// revision", so avoid escaping "." into ".@".
if (strpos($file, '@') !== false) {
$file = $file.'@';
}
return $file;
}
2011-01-10 00:22:25 +01:00
}