1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-12-23 22:10:54 +01: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;
}
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) {
$tmp_file = new TempFile();
Filesystem::writeFile($tmp_file, $message);

View file

@ -594,6 +594,20 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
$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) {
$tmp_file = new TempFile();
Filesystem::writeFile($tmp_file, $message);

View file

@ -192,6 +192,14 @@ abstract class ArcanistRepositoryAPI {
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) {
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) {
list($stdout) = execx(
'svn propget %s %s@',

View file

@ -36,6 +36,13 @@
*/
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 $conduitURI;
private $conduitCredentials;
@ -51,7 +58,7 @@ abstract class ArcanistBaseWorkflow {
private $passedArguments;
private $command;
private $repositoryEncoding;
private $projectInfo;
private $arcanistConfiguration;
private $parentWorkflow;
@ -709,9 +716,16 @@ abstract class ArcanistBaseWorkflow {
return empty($this->arguments['allow-untracked']);
}
public function setCommitMode($mode) {
$this->commitMode = $mode;
return $this;
}
public function requireCleanWorkingCopy() {
$api = $this->getRepositoryAPI();
$must_commit = array();
$working_copy_desc = phutil_console_format(
" Working copy: __%s__\n\n",
$api->getPath());
@ -740,10 +754,16 @@ abstract class ArcanistBaseWorkflow {
"may have forgotten to 'hg add' them to your commit.");
}
$prompt = "Do you want to continue without adding these files?";
if (!phutil_console_confirm($prompt, $default_no = false)) {
throw new ArcanistUserAbortException();
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?";
if (!phutil_console_confirm($prompt, $default_no = false)) {
throw new ArcanistUserAbortException();
}
}
}
}
@ -770,25 +790,120 @@ abstract class ArcanistBaseWorkflow {
$unstaged = $api->getUnstagedChanges();
if ($unstaged) {
throw new ArcanistUsageException(
"You have unstaged changes in this working copy. Stage and commit (or ".
"revert) them before proceeding.\n\n".
echo "You have unstaged changes in this working copy.\n\n".
$working_copy_desc.
" 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();
foreach ($uncommitted as $key => $path) {
if (array_key_exists($path, $must_commit)) {
unset($uncommitted[$key]);
}
}
if ($uncommitted) {
throw new ArcanistUncommittedChangesException(
"You have uncommitted changes in this working copy. Commit (or ".
"revert) them before proceeding.\n\n".
echo "You have uncommitted changes in this working copy.\n\n".
$working_copy_desc.
" Uncommitted changes in working copy\n".
" ".implode("\n ", $uncommitted)."\n");
" Uncommitted changes in working copy:\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(
ConduitClient $conduit,
@ -1276,26 +1391,25 @@ abstract class ArcanistBaseWorkflow {
}
protected function getRepositoryEncoding() {
if ($this->repositoryEncoding) {
return $this->repositoryEncoding;
}
$default = 'UTF-8';
return nonempty(idx($this->getProjectInfo(), 'encoding'), $default);
}
$project_id = $this->getWorkingCopy()->getProjectID();
if (!$project_id) {
return $default;
protected function getProjectInfo() {
if ($this->projectInfo === null) {
$project_id = $this->getWorkingCopy()->getProjectID();
if (!$project_id) {
$this->projectInfo = array();
} else {
$this->projectInfo = $this->getConduit()->callMethodSynchronous(
'arcanist.projectinfo',
array(
'name' => $project_id,
));
}
}
$project_info = $this->getConduit()->callMethodSynchronous(
'arcanist.projectinfo',
array(
'name' => $project_id,
));
$this->repositoryEncoding = nonempty($project_info['encoding'], $default);
return $this->repositoryEncoding;
return $this->projectInfo;
}
protected function newInteractiveEditor($text) {

View file

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