Page MenuHomeSealhub

No OneTemporary

diff --git a/src/workflow/land/ArcanistLandWorkflow.php b/src/workflow/land/ArcanistLandWorkflow.php
index 76f10da6..4dbfabb7 100644
--- a/src/workflow/land/ArcanistLandWorkflow.php
+++ b/src/workflow/land/ArcanistLandWorkflow.php
@@ -1,310 +1,317 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Lands a branch by rebasing, merging and amending it.
*
* @group workflow
*/
final class ArcanistLandWorkflow extends ArcanistBaseWorkflow {
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
- **land** __branch__ [--onto __master__]
+ **land** [__options__] __branch__ [--onto __master__]
Supports: git
Land an accepted change (currently sitting in local feature branch
__branch__) onto __master__ and push it to the remote. Then, delete
the feature branch.
In mutable repositories, this will perform a --squash merge (the
entire branch will be represented by one commit on __master__). In
- immutable repositories, it will perform a --no-ff merge (the branch
- will always be merged into __master__ with a merge commit).
+ immutable repositories (or when --merge is provided), it will perform
+ a --no-ff merge (the branch will always be merged into __master__ with
+ a merge commit).
EOTEXT
);
}
public function requiresWorkingCopy() {
return true;
}
public function requiresConduit() {
return true;
}
public function requiresAuthentication() {
return true;
}
public function requiresRepositoryAPI() {
return true;
}
public function getArguments() {
return array(
'onto' => array(
'param' => 'master',
'help' => "Land feature branch onto a branch other than ".
"'master' (default).",
),
'hold' => array(
'help' => "Prepare the change to be pushed, but do not actually ".
"push it.",
),
'keep-branch' => array(
'help' => "Keep the feature branch after pushing changes to the ".
"remote (by default, it is deleted).",
),
'remote' => array(
'param' => 'origin',
'help' => "Push to a remote other than 'origin' (default).",
),
+ 'merge' => array(
+ 'help' => 'Perform a --no-ff merge, not a --squash merge. If the '.
+ 'project is marked as having an immutable history, this is '.
+ 'the default behavior.',
+ ),
'*' => 'branch',
);
}
public function run() {
$branch = $this->getArgument('branch');
if (count($branch) !== 1) {
throw new ArcanistUsageException(
"Specify exactly one branch to land changes from.");
}
$branch = head($branch);
$remote = $this->getArgument('remote', 'origin');
$onto = $this->getArgument('onto', 'master');
- $is_immutable = $this->isHistoryImmutable();
+ $is_immutable = $this->isHistoryImmutable() ||
+ $this->getArgument('merge');
$repository_api = $this->getRepositoryAPI();
if (!($repository_api instanceof ArcanistGitAPI)) {
throw new ArcanistUsageException("'arc land' only supports git.");
}
list($err) = exec_manual(
'(cd %s && git rev-parse --verify %s)',
$repository_api->getPath(),
$branch);
if ($err) {
throw new ArcanistUsageException("Branch '{$branch}' does not exist.");
}
$this->requireCleanWorkingCopy();
$repository_api->parseRelativeLocalCommit(array($remote.'/'.$onto));
$old_branch = $repository_api->getBranchName();
execx(
'(cd %s && git checkout %s)',
$repository_api->getPath(),
$onto);
echo phutil_console_format(
"Switched to branch **%s**. Updating branch...\n",
$onto);
execx(
'(cd %s && git pull --ff-only)',
$repository_api->getPath());
list($out) = execx(
'(cd %s && git log %s/%s..%s)',
$repository_api->getPath(),
$remote,
$onto,
$onto);
if (strlen(trim($out))) {
throw new ArcanistUsageException(
"Local branch '{$onto}' is ahead of '{$remote}/{$onto}', so landing ".
"a feature branch would push additional changes. Push or reset the ".
"changes in '{$onto}' before running 'arc land'.");
}
execx(
'(cd %s && git checkout %s)',
$repository_api->getPath(),
$branch);
echo phutil_console_format(
"Switched to branch **%s**. Identifying and merging...\n",
$branch);
if (!$is_immutable) {
$err = phutil_passthru(
'(cd %s && git rebase %s)',
$repository_api->getPath(),
$onto);
if ($err) {
throw new ArcanistUsageException(
"'git rebase {$onto}' failed. You can abort with 'git rebase ".
"--abort', or resolve conflicts and use 'git rebase --continue' to ".
"continue forward. After resolving the rebase, run 'arc land' ".
"again.");
}
// Now that we've rebased, the merge-base of origin/master and HEAD may
// be different. Reparse the relative commit.
$repository_api->parseRelativeLocalCommit(array($remote.'/'.$onto));
}
$revisions = $repository_api->loadWorkingCopyDifferentialRevisions(
$this->getConduit(),
array(
'authors' => array($this->getUserPHID()),
));
if (!count($revisions)) {
throw new ArcanistUsageException(
"arc can not identify which revision exists on branch '{$branch}'. ".
"Update the revision with recent changes to synchronize the branch ".
"name and hashes, or use 'arc amend' to amend the commit message at ".
"HEAD.");
} else if (count($revisions) > 1) {
$message =
"There are multiple revisions on feature branch '{$branch}' which are ".
"not present on '{$onto}':\n\n".
$this->renderRevisionList($revisions)."\n".
"Separate these revisions onto different branches, or manually land ".
"them in '{$onto}'.";
throw new ArcanistUsageException($message);
}
$revision = head($revisions);
$rev_id = $revision['id'];
$rev_title = $revision['title'];
if ($revision['status'] != ArcanistDifferentialRevisionStatus::ACCEPTED) {
$ok = phutil_console_confirm(
- "Revision 'D{$id}: {$rev_title}' has not been accepted. Continue ".
+ "Revision 'D{$rev_id}: {$rev_title}' has not been accepted. Continue ".
"anyway?");
if (!$ok) {
throw new ArcanistUserAbortException();
}
}
echo "Landing revision 'D{$rev_id}: {$rev_title}'...\n";
$message = $this->getConduit()->callMethodSynchronous(
'differential.getcommitmessage',
array(
'revision_id' => $revision['id'],
));
execx(
'(cd %s && git checkout %s)',
$repository_api->getPath(),
$onto);
if ($is_immutable) {
// In immutable histories, do a --no-ff merge to force a merge commit with
// the right message.
$err = phutil_passthru(
'(cd %s && git merge --no-ff -m %s %s)',
$repository_api->getPath(),
$message,
$branch);
if ($err) {
throw new ArcanistUsageException(
"'git merge' failed. Your working copy has been left in a partially ".
"merged state. You can: abort with 'git merge --abort'; or follow ".
- "the instructions to complete the merge, and then push.");
+ "the instructions to complete the merge.");
}
} else {
// In mutable histories, do a --squash merge.
execx(
'(cd %s && git merge --squash --ff-only %s)',
$repository_api->getPath(),
$branch);
execx(
'(cd %s && git commit -m %s)',
$repository_api->getPath(),
$message);
}
if ($this->getArgument('hold')) {
echo phutil_console_format(
"Holding change in **%s**: it has NOT been pushed yet.\n",
$onto);
} else {
echo "Pushing change...\n\n";
$err = phutil_passthru(
'(cd %s && git push %s %s)',
$repository_api->getPath(),
$remote,
$onto);
if ($err) {
throw new ArcanistUsageException("'git push' failed.");
}
$mark_workflow = $this->buildChildWorkflow(
'mark-committed',
array(
'--finalize',
'--quiet',
$revision['id'],
));
$mark_workflow->run();
echo "\n";
}
if (!$this->getArgument('keep-branch')) {
list($ref) = execx(
'(cd %s && git rev-parse --verify %s)',
$repository_api->getPath(),
$branch);
$ref = trim($ref);
$recovery_command = csprintf(
'git checkout -b %s %s',
$branch,
$ref);
echo "Cleaning up feature branch...\n";
echo "(Use `{$recovery_command}` if you want it back.)\n";
execx(
'(cd %s && git branch -D %s)',
$repository_api->getPath(),
$branch);
}
// If we were on some branch A and the user ran "arc land B", switch back
// to A.
if (($old_branch != $branch) && ($old_branch != $onto)) {
execx(
'(cd %s && git checkout %s)',
$repository_api->getPath(),
$old_branch);
echo phutil_console_format(
"Switched back to branch **%s**.\n",
$old_branch);
}
echo "Done.\n";
return 0;
}
protected function getSupportedRevisionControlSystems() {
return array('git');
}
}
diff --git a/src/workflow/merge/ArcanistMergeWorkflow.php b/src/workflow/merge/ArcanistMergeWorkflow.php
index 0271c127..67ba55e6 100644
--- a/src/workflow/merge/ArcanistMergeWorkflow.php
+++ b/src/workflow/merge/ArcanistMergeWorkflow.php
@@ -1,163 +1,167 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Merges a branch using "git merge" or "hg merge", using a template commit
* message from Differential.
*
* @group workflow
*/
final class ArcanistMergeWorkflow extends ArcanistBaseWorkflow {
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
**merge** [__branch__] [--revision __revision_id__] [--show]
- Supports: git, hg
- Execute a "git merge <branch>" or "hg merge --rev <branch>" of a
- reviewed branch, but give the merge commit a useful commit message
- with information from Differential.
+ Supports: hg
+ Execute a "hg merge --rev <branch>" of a reviewed branch, but give the
+ merge commit a useful commit message with information from
+ Differential.
- In Git, this operates like "git merge <branch>" and should be executed
- from the branch you want to merge __into__, just like "git merge".
- Branch is required.
-
- In Mercurial, this operates like "hg merge" (default) or
- "hg merge --rev <branch>" and should be executed from the branch you
- want to merge __from__, just like "hg merge". It will also effect an
- "hg commit" with a rich commit message.
+ Tthis operates like "hg merge" (default) or "hg merge --rev <branch>"
+ and should be executed from the branch you want to merge __from__,
+ just like "hg merge". It will also effect an "hg commit" with a rich
+ commit message if possible.
EOTEXT
);
}
public function requiresWorkingCopy() {
return true;
}
public function requiresConduit() {
return true;
}
public function requiresAuthentication() {
return true;
}
public function requiresRepositoryAPI() {
return true;
}
public function getArguments() {
return array(
'show' => array(
'help' =>
"Don't merge, just show the commit message."
),
'revision' => array(
'param' => 'revision',
'help' =>
"Use the message for a specific revision. If 'arc' can't figure ".
"out which revision you want, you can tell it explicitly.",
),
'*' => 'branch',
);
}
public function run() {
+ $repository_api = $this->getRepositoryAPI();
+
+ if ($repository_api instanceof ArcanistGitAPI) {
+ throw new ArcanistUsageException(
+ "'arc merge' no longer supports git. Use ".
+ "'arc land --keep-branch --hold --merge <feature_branch>' instead.");
+ }
+
$this->writeStatusMessage(
phutil_console_format(
"**WARNING:** 'arc merge' is new and experimental.\n"));
- $repository_api = $this->getRepositoryAPI();
+
if (!$repository_api->supportsLocalBranchMerge()) {
$name = $repository_api->getSourceControlSystemName();
throw new ArcanistUsageException(
"This source control system ('{$name}') does not support 'arc merge'.");
}
if ($repository_api->getUncommittedChanges()) {
throw new ArcanistUsageException(
"You have uncommitted changes in this working copy. Commit ".
"(or revert) them before proceeding.");
}
$branch = $this->getArgument('branch');
if (count($branch) > 1) {
throw new ArcanistUsageException("Specify only one branch to merge.");
} else {
$branch = head($branch);
}
$conduit = $this->getConduit();
$revisions = $conduit->callMethodSynchronous(
'differential.find',
array(
'guids' => array($this->getUserPHID()),
'query' => 'committable',
));
// TODO: Make an effort to guess which revision the user means here. Branch
// name is a very strong heuristic but Conduit doesn't make it easy to get
// right now. We now also have "commits:local" after D857. Between these
// we should be able to get this right automatically in essentially every
// reasonable case.
try {
$revision = $this->chooseRevision(
$revisions,
$this->getArgument('revision'),
'Which revision do you want to merge?');
$revision_id = $revision->getID();
} catch (ArcanistChooseInvalidRevisionException $ex) {
throw new ArcanistUsageException(
"You can only merge Differential revisions which have been accepted.");
} catch (ArcanistChooseNoRevisionsException $ex) {
throw new ArcanistUsageException(
"You have no accepted Differential revisions.");
}
$message = $conduit->callMethodSynchronous(
'differential.getcommitmessage',
array(
'revision_id' => $revision_id,
'edit' => false,
));
if ($this->getArgument('show')) {
echo $message."\n";
} else {
$repository_api->performLocalBranchMerge($branch, $message);
echo "Merged '{$branch}'.\n";
$mark_workflow = $this->buildChildWorkflow(
'mark-committed',
array(
'--finalize',
$revision_id,
));
$mark_workflow->run();
}
return 0;
}
protected function getSupportedRevisionControlSystems() {
- return array('git', 'hg');
+ return array('hg');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 13:57 (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556934
Default Alt Text
(16 KB)

Event Timeline