1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-04-09 02:48:37 +02:00

Allow running arc diff without git commit

Summary:
There's quite some logic in here:

- It automatically decides whether to create a new commit or amend.
- It partially respects 'default-relative-commit'.
- However if it points to a closed revision then it creates a new commit.

Resolves T2025.

Test Plan:
`arc diff` on:

- Clean committed repository.
- Dirty repository without commit since 'default-relative-commit'.
- Dirty repository with non-revision commit since 'default-relative-commit'.
- Dirty repository with revision commit since 'default-relative-commit'.
- `arc diff HEAD^` on dirty repository on top of closed revision.

Reviewers: epriestley, btrahan

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T2025

Differential Revision: https://secure.phabricator.com/D3967
This commit is contained in:
vrana 2012-11-15 12:33:36 -08:00
parent f4222b3c8b
commit 22d8e7467b
6 changed files with 198 additions and 30 deletions

View file

@ -472,6 +472,20 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
return $this->status; return $this->status;
} }
public function addToCommit(array $paths) {
$this->execxLocal(
'add -- %Ls',
$paths);
}
public function doCommit($message) {
$tmp_file = new TempFile();
Filesystem::writeFile($tmp_file, $message);
$this->execxLocal(
'commit --allow-empty-message -F %s',
$tmp_file);
}
public function amendCommit($message) { public function amendCommit($message) {
$tmp_file = new TempFile(); $tmp_file = new TempFile();
Filesystem::writeFile($tmp_file, $message); Filesystem::writeFile($tmp_file, $message);

View file

@ -594,6 +594,20 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
$this->execxLocal('up'); $this->execxLocal('up');
} }
public function addToCommit(array $paths) {
$this->execxLocal(
'add -- %Ls',
$paths);
}
public function doCommit($message) {
$tmp_file = new TempFile();
Filesystem::writeFile($tmp_file, $message);
$this->execxLocal(
'commit -l %s',
$tmp_file);
}
public function amendCommit($message) { public function amendCommit($message) {
$tmp_file = new TempFile(); $tmp_file = new TempFile();
Filesystem::writeFile($tmp_file, $message); Filesystem::writeFile($tmp_file, $message);

View file

@ -192,6 +192,14 @@ abstract class ArcanistRepositoryAPI {
throw new ArcanistCapabilityNotSupportedException($this); throw new ArcanistCapabilityNotSupportedException($this);
} }
public function addToCommit(array $paths) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function doCommit($message) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function amendCommit($message) { public function amendCommit($message) {
throw new ArcanistCapabilityNotSupportedException($this); throw new ArcanistCapabilityNotSupportedException($this);
} }

View file

@ -171,6 +171,12 @@ final class ArcanistSubversionAPI extends ArcanistRepositoryAPI {
} }
} }
public function addToCommit(array $paths) {
$this->execxLocal(
'add -- %Ls',
$paths);
}
public function getSVNProperty($path, $property) { public function getSVNProperty($path, $property) {
list($stdout) = execx( list($stdout) = execx(
'svn propget %s %s@', 'svn propget %s %s@',

View file

@ -36,6 +36,13 @@
*/ */
abstract class ArcanistBaseWorkflow { abstract class ArcanistBaseWorkflow {
const COMMIT_DISABLE = 0;
const COMMIT_ALLOW = 1;
const COMMIT_ENABLE = 2;
private $commitMode = self::COMMIT_DISABLE;
private $shouldAmend;
private $conduit; private $conduit;
private $conduitURI; private $conduitURI;
private $conduitCredentials; private $conduitCredentials;
@ -51,7 +58,7 @@ abstract class ArcanistBaseWorkflow {
private $passedArguments; private $passedArguments;
private $command; private $command;
private $repositoryEncoding; private $projectInfo;
private $arcanistConfiguration; private $arcanistConfiguration;
private $parentWorkflow; private $parentWorkflow;
@ -709,9 +716,16 @@ abstract class ArcanistBaseWorkflow {
return empty($this->arguments['allow-untracked']); return empty($this->arguments['allow-untracked']);
} }
public function setCommitMode($mode) {
$this->commitMode = $mode;
return $this;
}
public function requireCleanWorkingCopy() { public function requireCleanWorkingCopy() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$must_commit = array();
$working_copy_desc = phutil_console_format( $working_copy_desc = phutil_console_format(
" Working copy: __%s__\n\n", " Working copy: __%s__\n\n",
$api->getPath()); $api->getPath());
@ -740,11 +754,17 @@ abstract class ArcanistBaseWorkflow {
"may have forgotten to 'hg add' them to your commit."); "may have forgotten to 'hg add' them to your commit.");
} }
if ($this->askForAdd()) {
$api->addToCommit($untracked);
$must_commit += array_flip($untracked);
} else if ($this->commitMode == self::COMMIT_DISABLE) {
$prompt = "Do you want to continue without adding these files?"; $prompt = "Do you want to continue without adding these files?";
if (!phutil_console_confirm($prompt, $default_no = false)) { if (!phutil_console_confirm($prompt, $default_no = false)) {
throw new ArcanistUserAbortException(); throw new ArcanistUserAbortException();
} }
} }
}
} }
$incomplete = $api->getIncompleteChanges(); $incomplete = $api->getIncompleteChanges();
@ -770,25 +790,120 @@ abstract class ArcanistBaseWorkflow {
$unstaged = $api->getUnstagedChanges(); $unstaged = $api->getUnstagedChanges();
if ($unstaged) { if ($unstaged) {
throw new ArcanistUsageException( echo "You have unstaged changes in this working copy.\n\n".
"You have unstaged changes in this working copy. Stage and commit (or ".
"revert) them before proceeding.\n\n".
$working_copy_desc. $working_copy_desc.
" Unstaged changes in working copy:\n". " Unstaged changes in working copy:\n".
" ".implode("\n ", $unstaged)."\n"); " ".implode("\n ", $unstaged)."\n\n";
if ($this->askForAdd()) {
$api->addToCommit($unstaged);
$must_commit += array_flip($unstaged);
} else {
throw new ArcanistUsageException(
"Stage and commit (or revert) them before proceeding.");
}
} }
$uncommitted = $api->getUncommittedChanges(); $uncommitted = $api->getUncommittedChanges();
foreach ($uncommitted as $key => $path) {
if (array_key_exists($path, $must_commit)) {
unset($uncommitted[$key]);
}
}
if ($uncommitted) { if ($uncommitted) {
throw new ArcanistUncommittedChangesException( echo "You have uncommitted changes in this working copy.\n\n".
"You have uncommitted changes in this working copy. Commit (or ".
"revert) them before proceeding.\n\n".
$working_copy_desc. $working_copy_desc.
" Uncommitted changes in working copy\n". " Uncommitted changes in working copy:\n".
" ".implode("\n ", $uncommitted)."\n"); " ".implode("\n ", $uncommitted)."\n\n";
if ($this->askForAdd()) {
$must_commit += array_flip($uncommitted);
} else {
throw new ArcanistUncommittedChangesException(
"Commit (or revert) them before proceeding.");
} }
} }
if ($must_commit) {
if ($this->shouldAmend) {
$commit = head($api->getLocalCommitInformation());
$api->amendCommit($commit['message']);
} else if ($api->supportsRelativeLocalCommits()) {
$api->doCommit('');
}
}
}
private function shouldAmend() {
$api = $this->getRepositoryAPI();
if ($this->isHistoryImmutable() || !$api->supportsAmend()) {
return false;
}
$commits = $api->getLocalCommitInformation();
if (!$commits) {
return false;
}
$commit = reset($commits);
$message = ArcanistDifferentialCommitMessage::newFromRawCorpus(
$commit['message']);
if ($message->getGitSVNBaseRevision()) {
return false;
}
// TODO: Check last commit's author. If not me then return false.
// TODO: Check commits since tracking branch. If empty then return false.
$repository_phid = idx($this->getProjectInfo(), 'repositoryPHID');
if ($repository_phid) {
$repositories = $this->getConduit()->callMethodSynchronous(
'repository.query',
array());
$callsigns = ipull($repositories, 'callsign', 'phid');
$callsign = idx($callsigns, $repository_phid);
if ($callsign) {
$known_commits = $this->getConduit()->callMethodSynchronous(
'diffusion.getcommits',
array('commits' => array('r'.$callsign.$commit['commit'])));
if ($known_commits) {
return false;
}
}
}
if (!$message->getRevisionID()) {
return true;
}
$in_working_copy = $api->loadWorkingCopyDifferentialRevisions(
$this->getConduit(),
array(
'authors' => array($this->getUserPHID()),
'status' => 'status-open',
));
if ($in_working_copy) {
return true;
}
return false;
}
private function askForAdd() {
if ($this->commitMode == self::COMMIT_DISABLE) {
return false;
} else if ($this->commitMode == self::COMMIT_ENABLE) {
return true;
}
if ($this->shouldAmend === null) {
$this->shouldAmend = $this->shouldAmend();
}
if ($this->shouldAmend) {
$prompt = "Do you want to amend these files to the commit?";
} else {
$prompt = "Do you want to add these files to the commit?";
}
return phutil_console_confirm($prompt);
}
protected function loadDiffBundleFromConduit( protected function loadDiffBundleFromConduit(
ConduitClient $conduit, ConduitClient $conduit,
@ -1276,26 +1391,25 @@ abstract class ArcanistBaseWorkflow {
} }
protected function getRepositoryEncoding() { protected function getRepositoryEncoding() {
if ($this->repositoryEncoding) { $default = 'UTF-8';
return $this->repositoryEncoding; return nonempty(idx($this->getProjectInfo(), 'encoding'), $default);
} }
$default = 'UTF-8'; protected function getProjectInfo() {
if ($this->projectInfo === null) {
$project_id = $this->getWorkingCopy()->getProjectID(); $project_id = $this->getWorkingCopy()->getProjectID();
if (!$project_id) { if (!$project_id) {
return $default; $this->projectInfo = array();
} } else {
$this->projectInfo = $this->getConduit()->callMethodSynchronous(
$project_info = $this->getConduit()->callMethodSynchronous(
'arcanist.projectinfo', 'arcanist.projectinfo',
array( array(
'name' => $project_id, 'name' => $project_id,
)); ));
}
}
$this->repositoryEncoding = nonempty($project_info['encoding'], $default); return $this->projectInfo;
return $this->repositoryEncoding;
} }
protected function newInteractiveEditor($text) { protected function newInteractiveEditor($text) {

View file

@ -267,12 +267,17 @@ EOTEXT
'lint' => true, 'lint' => true,
), ),
), ),
'add-all' => array(
'help' =>
'Automatically add all untracked, unstaged and uncommitted files to '.
'the commit.',
),
'json' => array( 'json' => array(
'help' => 'help' =>
'Emit machine-readable JSON. EXPERIMENTAL! Probably does not work!', 'Emit machine-readable JSON. EXPERIMENTAL! Probably does not work!',
), ),
'no-amend' => array( 'no-amend' => array(
'help' => 'Never amend commits in the working copy.', 'help' => 'Never amend commits in the working copy with lint patches.',
), ),
'uncommitted' => array( 'uncommitted' => array(
'help' => 'Suppress warning about uncommitted changes.', 'help' => 'Suppress warning about uncommitted changes.',
@ -582,6 +587,13 @@ EOTEXT
if ($this->requiresWorkingCopy()) { if ($this->requiresWorkingCopy()) {
try { try {
if ($this->getArgument('add-all')) {
$this->setCommitMode(self::COMMIT_ENABLE);
} else if ($this->getArgument('uncommitted')) {
$this->setCommitMode(self::COMMIT_DISABLE);
} else {
$this->setCommitMode(self::COMMIT_ALLOW);
}
$this->requireCleanWorkingCopy(); $this->requireCleanWorkingCopy();
} catch (ArcanistUncommittedChangesException $ex) { } catch (ArcanistUncommittedChangesException $ex) {
$repository_api = $this->getRepositoryAPI(); $repository_api = $this->getRepositoryAPI();