Hello community, here is the log from the commit of package arcanist for openSUSE:Factory checked in at 2020-01-13 22:22:35 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/arcanist (Old) and /work/SRC/openSUSE:Factory/.arcanist.new.6675 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "arcanist" Mon Jan 13 22:22:35 2020 rev:3 rq:764032 version:0.0~git.20191118T203151~cc850163 Changes: -------- --- /work/SRC/openSUSE:Factory/arcanist/arcanist.changes 2019-11-06 13:52:47.343940690 +0100 +++ /work/SRC/openSUSE:Factory/.arcanist.new.6675/arcanist.changes 2020-01-13 22:22:38.526549336 +0100 @@ -1,0 +2,20 @@ +Mon Jan 13 12:56:00 UTC 2020 - i...@guoyunhe.me + +- Update to version 0.0~git.20191118T203151~cc850163: + * When "arc close-revision --finalize ..." skips closing a revision, print a message + * When generating diffs in "arc diff", disable Git config option "diff.suppressBlankEmpty" + * Make "arc land --merge" an explicit error when targeting a Perforce remote + * In "arc land", when "remote/onto" does not exist locally, try to fetch it before giving up + * Update "arc help land" to reference Perforce support + * Support Perforce/Git repositories in "arc land" + * Move Git-specific "arc land" parsing of "--onto" and "--remote" into GitLandEngine + * Add a lint check for deprecated argument order to "implode()" + * When running "arc land" from a detached HEAD, don't try to delete the source ref + * Fix two "msort()" vs "msortv()" issues in "arc land" + +------------------------------------------------------------------- +Mon Jan 13 12:52:01 UTC 2020 - Yunhe Guo <i...@guoyunhe.me> + +- Change LICENSE to %license section and update template + +------------------------------------------------------------------- Old: ---- arcanist-0.0~git.20190905T053100~3cdfe1ff.obscpio arcanist-0.0~git.20190905T053100~3cdfe1ff.tar.xz arcanist.obsinfo New: ---- _servicedata arcanist-0.0~git.20191118T203151~cc850163.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ arcanist.spec ++++++ --- /var/tmp/diff_new_pack.2Q5Qbv/_old 2020-01-13 22:22:39.670549867 +0100 +++ /var/tmp/diff_new_pack.2Q5Qbv/_new 2020-01-13 22:22:39.674549869 +0100 @@ -1,7 +1,7 @@ # # spec file for package arcanist # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,12 +17,12 @@ Name: arcanist -Version: 0.0~git.20190905T053100~3cdfe1ff +Version: 0.0~git.20191118T203151~cc850163 Release: 0 Summary: Command-line interface to Phabricator License: Apache-2.0 -Url: https://secure.phabricator.com/diffusion/ARC/ +URL: https://secure.phabricator.com/diffusion/ARC/ Source0: %{name}-%{version}.tar.xz Patch0: remove-arc-upgrade.patch @@ -84,7 +84,8 @@ %files %defattr(-,root,root,-) -%doc LICENSE NOTICE README.md +%doc NOTICE README.md +%license LICENSE %{_bindir}/arc %dir %{_datadir}/bash-completion %dir %{_datadir}/bash-completion/completions ++++++ _service ++++++ --- /var/tmp/diff_new_pack.2Q5Qbv/_old 2020-01-13 22:22:39.694549878 +0100 +++ /var/tmp/diff_new_pack.2Q5Qbv/_new 2020-01-13 22:22:39.694549878 +0100 @@ -1,15 +1,16 @@ <services> - <service mode="disabled" name="tar_scm"> + <service mode="localonly" name="tar_scm"> <param name="scm">git</param> <param name="url">https://github.com/phacility/arcanist</param> <param name="revision">master</param> <param name="versionformat">0~git.%ci~%h</param> <param name="versionprefix">0</param> <param name="filename">arcanist</param> + <param name="changesgenerate">enable</param> </service> - <service mode="disabled" name="recompress"> + <service mode="localonly" name="recompress"> <param name="file">*.tar</param> <param name="compression">xz</param> </service> - <service mode="disabled" name="set_version" /> + <service mode="localonly" name="set_version" /> </services> ++++++ _servicedata ++++++ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/phacility/arcanist</param> <param name="changesrevision">cc850163f30c4697e925df0d6212469679600a2c</param></service></servicedata>++++++ arcanist-0.0~git.20190905T053100~3cdfe1ff.tar.xz -> arcanist-0.0~git.20191118T203151~cc850163.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/__phutil_library_map__.php new/arcanist-0.0~git.20191118T203151~cc850163/src/__phutil_library_map__.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/__phutil_library_map__.php 2019-09-05 14:31:00.000000000 +0200 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/__phutil_library_map__.php 2019-11-19 05:31:51.000000000 +0100 @@ -185,6 +185,8 @@ 'ArcanistImplicitFallthroughXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitFallthroughXHPASTLinterRuleTestCase.php', 'ArcanistImplicitVisibilityXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitVisibilityXHPASTLinterRule.php', 'ArcanistImplicitVisibilityXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitVisibilityXHPASTLinterRuleTestCase.php', + 'ArcanistImplodeArgumentOrderXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplodeArgumentOrderXHPASTLinterRule.php', + 'ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase.php', 'ArcanistInlineHTMLXHPASTLinterRule' => 'lint/linter/ArcanistInlineHTMLXHPASTLinterRule.php', 'ArcanistInlineHTMLXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistInlineHTMLXHPASTLinterRuleTestCase.php', 'ArcanistInnerFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInnerFunctionXHPASTLinterRule.php', @@ -608,6 +610,8 @@ 'ArcanistImplicitFallthroughXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistImplicitVisibilityXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistImplicitVisibilityXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistImplodeArgumentOrderXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistInlineHTMLXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistInlineHTMLXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistInnerFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/land/ArcanistGitLandEngine.php new/arcanist-0.0~git.20191118T203151~cc850163/src/land/ArcanistGitLandEngine.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/land/ArcanistGitLandEngine.php 2019-09-05 14:31:00.000000000 +0200 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/land/ArcanistGitLandEngine.php 2019-11-19 05:31:51.000000000 +0100 @@ -8,6 +8,55 @@ private $sourceCommit; private $mergedRef; private $restoreWhenDestroyed; + private $isGitPerforce; + + private function setIsGitPerforce($is_git_perforce) { + $this->isGitPerforce = $is_git_perforce; + return $this; + } + + private function getIsGitPerforce() { + return $this->isGitPerforce; + } + + public function parseArguments() { + $api = $this->getRepositoryAPI(); + + $onto = $this->getEngineOnto(); + $this->setTargetOnto($onto); + + $remote = $this->getEngineRemote(); + + $is_pushable = $api->isPushableRemote($remote); + $is_perforce = $api->isPerforceRemote($remote); + + if (!$is_pushable && !$is_perforce) { + throw new PhutilArgumentUsageException( + pht( + 'No pushable remote "%s" exists. Use the "--remote" flag to choose '. + 'a valid, pushable remote to land changes onto.', + $remote)); + } + + if ($is_perforce) { + $this->setIsGitPerforce(true); + $this->writeWarn( + pht('P4 MODE'), + pht( + 'Operating in Git/Perforce mode after selecting a Perforce '. + 'remote.')); + + if (!$this->getShouldSquash()) { + throw new PhutilArgumentUsageException( + pht( + 'Perforce mode does not support the "merge" land strategy. '. + 'Use the "squash" land strategy when landing to a Perforce '. + 'remote (you can use "--squash" to select this strategy).')); + } + } + + $this->setTargetRemote($remote); + } public function execute() { $this->verifySourceAndTargetExist(); @@ -98,9 +147,34 @@ $this->getTargetFullRef()); if ($err) { - throw new Exception( + $this->writeWarn( + pht('TARGET'), pht( - 'Branch "%s" does not exist in remote "%s".', + 'No local ref exists for branch "%s" in remote "%s", attempting '. + 'fetch...', + $this->getTargetOnto(), + $this->getTargetRemote())); + + $api->execManualLocal( + 'fetch %s %s --', + $this->getTargetRemote(), + $this->getTargetOnto()); + + list($err) = $api->execManualLocal( + 'rev-parse --verify %s', + $this->getTargetFullRef()); + if ($err) { + throw new Exception( + pht( + 'Branch "%s" does not exist in remote "%s".', + $this->getTargetOnto(), + $this->getTargetRemote())); + } + + $this->writeInfo( + pht('FETCHED'), + pht( + 'Fetched branch "%s" from remote "%s".', $this->getTargetOnto(), $this->getTargetRemote())); } @@ -124,25 +198,45 @@ $ref = $this->getTargetFullRef(); - $this->writeInfo( - pht('FETCH'), - pht('Fetching %s...', $ref)); - // NOTE: Although this output isn't hugely useful, we need to passthru // instead of using a subprocess here because `git fetch` may prompt the // user to enter a password if they're fetching over HTTP with basic // authentication. See T10314. - $err = $api->execPassthru( - 'fetch --quiet -- %s %s', - $this->getTargetRemote(), - $this->getTargetOnto()); + if ($this->getIsGitPerforce()) { + $this->writeInfo( + pht('P4 SYNC'), + pht('Synchronizing "%s" from Perforce...', $ref)); - if ($err) { - throw new ArcanistUsageException( - pht( - 'Fetch failed! Fix the error and run "%s" again.', - 'arc land')); + $sync_ref = sprintf( + 'refs/remotes/%s/%s', + $this->getTargetRemote(), + $this->getTargetOnto()); + + $err = $api->execPassthru( + 'p4 sync --silent --branch %R --', + $sync_ref); + + if ($err) { + throw new ArcanistUsageException( + pht( + 'Perforce sync failed! Fix the error and run "arc land" again.')); + } + } else { + $this->writeInfo( + pht('FETCH'), + pht('Fetching "%s"...', $ref)); + + $err = $api->execPassthru( + 'fetch --quiet -- %s %s', + $this->getTargetRemote(), + $this->getTargetOnto()); + + if ($err) { + throw new ArcanistUsageException( + pht( + 'Fetch failed! Fix the error and run "arc land" again.')); + } } } @@ -212,21 +306,68 @@ private function pushChange() { $api = $this->getRepositoryAPI(); - $this->writeInfo( - pht('PUSHING'), - pht('Pushing changes to "%s".', $this->getTargetFullRef())); + if ($this->getIsGitPerforce()) { + $this->writeInfo( + pht('SUBMITTING'), + pht('Submitting changes to "%s".', $this->getTargetFullRef())); - $err = $api->execPassthru( - 'push -- %s %s:%s', - $this->getTargetRemote(), - $this->mergedRef, - $this->getTargetOnto()); + $config_argv = array(); - if ($err) { - throw new ArcanistUsageException( - pht( - 'Push failed! Fix the error and run "%s" again.', - 'arc land')); + // Skip the "git p4 submit" interactive editor workflow. We expect + // the commit message that "arc land" has built to be satisfactory. + $config_argv[] = '-c'; + $config_argv[] = 'git-p4.skipSubmitEdit=true'; + + // Skip the "git p4 submit" confirmation prompt if the user does not edit + // the submit message. + $config_argv[] = '-c'; + $config_argv[] = 'git-p4.skipSubmitEditCheck=true'; + + $flags_argv = array(); + + // Disable implicit "git p4 rebase" as part of submit. We're allowing + // the implicit "git p4 sync" to go through since this puts us in a + // state which is generally similar to the state after "git push", with + // updated remotes. + + // We could do a manual "git p4 sync" with a more narrow "--branch" + // instead, but it's not clear that this is beneficial. + $flags_argv[] = '--disable-rebase'; + + // Detect moves and submit them to Perforce as move operations. + $flags_argv[] = '-M'; + + // If we run into a conflict, abort the operation. We expect users to + // fix conflicts and run "arc land" again. + $flags_argv[] = '--conflict=quit'; + + $err = $api->execPassthru( + '%LR p4 submit %LR --commit %R --', + $config_argv, + $flags_argv, + $this->mergedRef); + + if ($err) { + throw new ArcanistUsageException( + pht( + 'Submit failed! Fix the error and run "arc land" again.')); + } + } else { + $this->writeInfo( + pht('PUSHING'), + pht('Pushing changes to "%s".', $this->getTargetFullRef())); + + $err = $api->execPassthru( + 'push -- %s %s:%s', + $this->getTargetRemote(), + $this->mergedRef, + $this->getTargetOnto()); + + if ($err) { + throw new ArcanistUsageException( + pht( + 'Push failed! Fix the error and run "arc land" again.')); + } } } @@ -332,39 +473,53 @@ return; } - if (!$path->isConnectedToRemote()) { - $this->writeInfo( - pht('UPDATE'), - pht( - 'Local branch "%s" is not connected to a remote, staying on '. - 'detached HEAD.', - $local_branch)); - return; - } + $is_perforce = $this->getIsGitPerforce(); - $remote_remote = $path->getRemoteRemoteName(); - $remote_branch = $path->getRemoteBranchName(); + if ($is_perforce) { + // If we're in Perforce mode, we don't expect to have a meaningful + // path to the remote: the "p4" remote is not a real remote, and + // "git p4" commands do not configure branch upstreams to provide + // a path. + + // Just pretend the target branch is connected directly to the remote, + // since this is effectively the behavior of Perforce and appears to + // do the right thing. + $cascade_branches = array($local_branch); + } else { + if (!$path->isConnectedToRemote()) { + $this->writeInfo( + pht('UPDATE'), + pht( + 'Local branch "%s" is not connected to a remote, staying on '. + 'detached HEAD.', + $local_branch)); + return; + } - $remote_actual = $remote_remote.'/'.$remote_branch; - $remote_expect = $this->getTargetFullRef(); - if ($remote_actual != $remote_expect) { - $this->writeInfo( - pht('UPDATE'), - pht( - 'Local branch "%s" is connected to a remote ("%s") other than '. - 'the target remote ("%s"), staying on detached HEAD.', - $local_branch, - $remote_actual, - $remote_expect)); - return; - } + $remote_remote = $path->getRemoteRemoteName(); + $remote_branch = $path->getRemoteBranchName(); - // If we get this far, we have a sequence of branches which ultimately - // connect to the remote. We're going to try to update them all in reverse - // order, from most-upstream to most-local. + $remote_actual = $remote_remote.'/'.$remote_branch; + $remote_expect = $this->getTargetFullRef(); + if ($remote_actual != $remote_expect) { + $this->writeInfo( + pht('UPDATE'), + pht( + 'Local branch "%s" is connected to a remote ("%s") other than '. + 'the target remote ("%s"), staying on detached HEAD.', + $local_branch, + $remote_actual, + $remote_expect)); + return; + } - $cascade_branches = $path->getLocalBranches(); - $cascade_branches = array_reverse($cascade_branches); + // If we get this far, we have a sequence of branches which ultimately + // connect to the remote. We're going to try to update them all in reverse + // order, from most-upstream to most-local. + + $cascade_branches = $path->getLocalBranches(); + $cascade_branches = array_reverse($cascade_branches); + } // First, check if any of them are ahead of the remote. @@ -428,7 +583,12 @@ } } - if ($cascade_targets) { + if ($is_perforce) { + // In Perforce, we've already set the remote to the right state with an + // implicit "git p4 sync" during "git p4 submit", and "git pull" isn't a + // meaningful operation. We're going to skip this step and jump down to + // the "git reset --hard" below to get everything into the right state. + } else if ($cascade_targets) { $this->writeInfo( pht('UPDATE'), pht( @@ -445,7 +605,10 @@ $cascade_branch)); $api->execxLocal('checkout %s --', $cascade_branch); - $api->execxLocal('pull --'); + $api->execxLocal( + 'pull %s %s --', + $this->getTargetRemote(), + $cascade_branch); } if (!$local_ahead) { @@ -569,16 +732,27 @@ } private function didHoldChanges() { - $this->writeInfo( - pht('HOLD'), - pht( - 'Holding change locally, it has not been pushed.')); + if ($this->getIsGitPerforce()) { + $this->writeInfo( + pht('HOLD'), + pht( + 'Holding change locally, it has not been submitted.')); + + $push_command = csprintf( + '$ git p4 submit -M --commit %R --', + $this->mergedRef); + } else { + $this->writeInfo( + pht('HOLD'), + pht( + 'Holding change locally, it has not been pushed.')); - $push_command = csprintf( - '$ git push -- %R %R:%R', - $this->getTargetRemote(), - $this->mergedRef, - $this->getTargetOnto()); + $push_command = csprintf( + '$ git push -- %R %R:%R', + $this->getTargetRemote(), + $this->mergedRef, + $this->getTargetOnto()); + } $restore_command = csprintf( '$ git checkout %R --', @@ -587,9 +761,9 @@ echo tsprintf( "\n%s\n\n". "%s\n\n". - " %s\n\n". + " **%s**\n\n". "%s\n\n". - " %s\n\n". + " **%s**\n\n". "%s\n", pht( 'This local working copy now contains the merged changes in a '. @@ -597,7 +771,7 @@ pht('You can push the changes manually with this command:'), $push_command, pht( - 'You can go back to how things were before you ran `arc land` with '. + 'You can go back to how things were before you ran "arc land" with '. 'this command:'), $restore_command, pht( @@ -615,4 +789,125 @@ return !$err; } + private function getEngineOnto() { + $source_ref = $this->getSourceRef(); + + $onto = $this->getOntoArgument(); + if ($onto !== null) { + $this->writeInfo( + pht('TARGET'), + pht( + 'Landing onto "%s", selected with the "--onto" flag.', + $onto)); + return $onto; + } + + $api = $this->getRepositoryAPI(); + $path = $api->getPathToUpstream($source_ref); + + if ($path->getLength()) { + $cycle = $path->getCycle(); + if ($cycle) { + $this->writeWarn( + pht('LOCAL CYCLE'), + pht( + 'Local branch tracks an upstream, but following it leads to a '. + 'local cycle; ignoring branch upstream.')); + + echo tsprintf( + "\n %s\n\n", + implode(' -> ', $cycle)); + + } else { + if ($path->isConnectedToRemote()) { + $onto = $path->getRemoteBranchName(); + $this->writeInfo( + pht('TARGET'), + pht( + 'Landing onto "%s", selected by following tracking branches '. + 'upstream to the closest remote.', + $onto)); + return $onto; + } else { + $this->writeInfo( + pht('NO PATH TO UPSTREAM'), + pht( + 'Local branch tracks an upstream, but there is no path '. + 'to a remote; ignoring branch upstream.')); + } + } + } + + $workflow = $this->getWorkflow(); + + $config_key = 'arc.land.onto.default'; + $onto = $workflow->getConfigFromAnySource($config_key); + if ($onto !== null) { + $this->writeInfo( + pht('TARGET'), + pht( + 'Landing onto "%s", selected by "%s" configuration.', + $onto, + $config_key)); + return $onto; + } + + $onto = 'master'; + $this->writeInfo( + pht('TARGET'), + pht( + 'Landing onto "%s", the default target under git.', + $onto)); + + return $onto; + } + + private function getEngineRemote() { + $source_ref = $this->getSourceRef(); + + $remote = $this->getRemoteArgument(); + if ($remote !== null) { + $this->writeInfo( + pht('REMOTE'), + pht( + 'Using remote "%s", selected with the "--remote" flag.', + $remote)); + return $remote; + } + + $api = $this->getRepositoryAPI(); + $path = $api->getPathToUpstream($source_ref); + + $remote = $path->getRemoteRemoteName(); + if ($remote !== null) { + $this->writeInfo( + pht('REMOTE'), + pht( + 'Using remote "%s", selected by following tracking branches '. + 'upstream to the closest remote.', + $remote)); + return $remote; + } + + $remote = 'p4'; + if ($api->isPerforceRemote($remote)) { + $this->writeInfo( + pht('REMOTE'), + pht( + 'Using Perforce remote "%s". The existence of this remote implies '. + 'this working copy was synchronized from a Perforce repository.', + $remote)); + return $remote; + } + + $remote = 'origin'; + $this->writeInfo( + pht('REMOTE'), + pht( + 'Using remote "%s", the default remote under Git.', + $remote)); + + return $remote; + } + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/land/ArcanistLandEngine.php new/arcanist-0.0~git.20191118T203151~cc850163/src/land/ArcanistLandEngine.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/land/ArcanistLandEngine.php 2019-09-05 14:31:00.000000000 +0200 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/land/ArcanistLandEngine.php 2019-11-19 05:31:51.000000000 +0100 @@ -13,6 +13,8 @@ private $shouldSquash; private $shouldDeleteRemote; private $shouldPreview; + private $remoteArgument; + private $ontoArgument; // TODO: This is really grotesque. private $buildMessageCallback; @@ -117,6 +119,25 @@ return $this->commitMessageFile; } + final public function setRemoteArgument($remote_argument) { + $this->remoteArgument = $remote_argument; + return $this; + } + + final public function getRemoteArgument() { + return $this->remoteArgument; + } + + final public function setOntoArgument($onto_argument) { + $this->ontoArgument = $onto_argument; + return $this; + } + + final public function getOntoArgument() { + return $this->ontoArgument; + } + + abstract public function parseArguments(); abstract public function execute(); abstract protected function getLandingCommits(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/lint/linter/xhpast/rules/ArcanistImplodeArgumentOrderXHPASTLinterRule.php new/arcanist-0.0~git.20191118T203151~cc850163/src/lint/linter/xhpast/rules/ArcanistImplodeArgumentOrderXHPASTLinterRule.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/lint/linter/xhpast/rules/ArcanistImplodeArgumentOrderXHPASTLinterRule.php 1970-01-01 01:00:00.000000000 +0100 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/lint/linter/xhpast/rules/ArcanistImplodeArgumentOrderXHPASTLinterRule.php 2019-11-19 05:31:51.000000000 +0100 @@ -0,0 +1,39 @@ +<?php + +final class ArcanistImplodeArgumentOrderXHPASTLinterRule + extends ArcanistXHPASTLinterRule { + + const ID = 129; + + public function getLintName() { + return pht('Implode With Glue First'); + } + + public function getLintSeverity() { + return ArcanistLintSeverity::SEVERITY_ERROR; + } + + public function process(XHPASTNode $root) { + $implosions = $this->getFunctionCalls($root, array('implode')); + foreach ($implosions as $implosion) { + $parameters = $implosion->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); + + if (count($parameters->getChildren()) != 2) { + continue; + } + + $parameter = $parameters->getChildByIndex(1); + if (!$parameter->isStaticScalar()) { + continue; + } + + $this->raiseLintAtNode( + $implosion, + pht( + 'When calling "implode()", pass the "glue" argument first. (The '. + 'other parameter order is deprecated in PHP 7.4 and raises a '. + 'warning.)')); + } + } + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/lint/linter/xhpast/rules/__tests__/ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase.php new/arcanist-0.0~git.20191118T203151~cc850163/src/lint/linter/xhpast/rules/__tests__/ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/lint/linter/xhpast/rules/__tests__/ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase.php 1970-01-01 01:00:00.000000000 +0100 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/lint/linter/xhpast/rules/__tests__/ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase.php 2019-11-19 05:31:51.000000000 +0100 @@ -0,0 +1,11 @@ +<?php + +final class ArcanistImplodeArgumentOrderXHPASTLinterRuleTestCase + extends ArcanistXHPASTLinterRuleTestCase { + + public function testLinter() { + $this->executeTestsInDirectory( + dirname(__FILE__).'/implode-argument-order/'); + } + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/lint/linter/xhpast/rules/__tests__/implode-argument-order/implode.lint-test new/arcanist-0.0~git.20191118T203151~cc850163/src/lint/linter/xhpast/rules/__tests__/implode-argument-order/implode.lint-test --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/lint/linter/xhpast/rules/__tests__/implode-argument-order/implode.lint-test 1970-01-01 01:00:00.000000000 +0100 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/lint/linter/xhpast/rules/__tests__/implode-argument-order/implode.lint-test 2019-11-19 05:31:51.000000000 +0100 @@ -0,0 +1,18 @@ +<?php + +// This is the correct argument order. +implode(' ', $x); + +// This is the legacy argument order which warns in PHP 7.4+. +implode($x, ' '); + +// No warning: we can't statically tell which one is the glue. +implode($x, $y); + +// No warning: these are likely wrong, but not a glue order problem. +implode(); +implode($x); +implode($x, $y, $z); + +~~~~~~~~~~ +error:7:1:XHP129:Implode With Glue First diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/repository/api/ArcanistGitAPI.php new/arcanist-0.0~git.20191118T203151~cc850163/src/repository/api/ArcanistGitAPI.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/repository/api/ArcanistGitAPI.php 2019-09-05 14:31:00.000000000 +0200 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/repository/api/ArcanistGitAPI.php 2019-11-19 05:31:51.000000000 +0100 @@ -464,15 +464,27 @@ */ public function getFullGitDiff($base, $head = null) { $options = $this->getDiffFullOptions(); + $config_options = array(); + + // See T13432. Disable the rare "diff.suppressBlankEmpty" configuration + // option, which discards the " " (space) change type prefix on unchanged + // blank lines. At time of writing the parser does not handle these + // properly, but generating a more-standard diff is generally desirable + // even if a future parser handles this case more gracefully. + + $config_options[] = '-c'; + $config_options[] = 'diff.suppressBlankEmpty=false'; if ($head !== null) { list($stdout) = $this->execxLocal( - "diff {$options} %s %s --", + "%LR diff {$options} %s %s --", + $config_options, $base, $head); } else { list($stdout) = $this->execxLocal( - "diff {$options} %s --", + "%LR diff {$options} %s --", + $config_options, $base); } @@ -1550,4 +1562,31 @@ return $path; } + public function isPerforceRemote($remote_name) { + // See T13434. In Perforce workflows, "git p4 clone" creates "p4" refs + // under "refs/remotes/", but does not define a real remote named "p4". + + // We treat this remote as though it were a real remote during "arc land", + // but it does not respond to commands like "git remote show p4", so we + // need to handle it specially. + + if ($remote_name !== 'p4') { + return false; + } + + $remote_dir = $this->getMetadataPath().'/refs/remotes/p4'; + if (!Filesystem::pathExists($remote_dir)) { + return false; + } + + return true; + } + + public function isPushableRemote($remote_name) { + list($err, $stdout) = $this->execManualLocal( + 'remote get-url --push -- %s', + $remote_name); + return !$err; + } + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/workflow/ArcanistCloseRevisionWorkflow.php new/arcanist-0.0~git.20191118T203151~cc850163/src/workflow/ArcanistCloseRevisionWorkflow.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/workflow/ArcanistCloseRevisionWorkflow.php 2019-09-05 14:31:00.000000000 +0200 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/workflow/ArcanistCloseRevisionWorkflow.php 2019-11-19 05:31:51.000000000 +0100 @@ -89,11 +89,13 @@ )); $revision = head($revisions); + $object_name = "D{$revision_id}"; + if (!$revision && !$is_finalize) { throw new ArcanistUsageException( pht( 'Revision %s does not exist.', - "D{$revision_id}")); + $object_name)); } $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; @@ -104,15 +106,20 @@ pht( "Revision %s can not be closed. You can only close ". "revisions which have been 'accepted'.", - "D{$revision_id}")); + $object_name)); } if ($revision) { + $revision_display = sprintf( + '%s %s', + $object_name, + $revision['title']); + if (!$is_finalize && $revision['authorPHID'] != $this->getUserPHID()) { $prompt = pht( - 'You are not the author of revision %s, '. + 'You are not the author of revision "%s", '. 'are you sure you want to close it?', - "D{$revision_id}"); + $object_name); if (!phutil_console_confirm($prompt)) { throw new ArcanistUserAbortException(); } @@ -120,24 +127,42 @@ $actually_close = true; if ($is_finalize) { - if ($this->getRepositoryPHID() || - $revision['status'] != $status_accepted) { + if ($this->getRepositoryPHID()) { + $actually_close = false; + } else if ($revision['status'] != $status_accepted) { + // See T13458. The server doesn't permit a transition to "Closed" + // over the API if the revision is not "Accepted". If we won't be + // able to close the revision, skip the attempt and print a + // message. + + $this->writeWarn( + pht('OPEN REVISION'), + pht( + 'Revision "%s" is not in state "Accepted", so it will '. + 'be left open.', + $object_name)); + $actually_close = false; } } - if ($actually_close) { - $revision_name = $revision['title']; - echo pht( - "Closing revision %s '%s'...\n", - "D{$revision_id}", - $revision_name); + if ($actually_close) { + $this->writeInfo( + pht('CLOSE'), + pht( + 'Closing revision "%s"...', + $revision_display)); $conduit->callMethodSynchronous( 'differential.close', array( 'revisionID' => $revision_id, )); + + $this->writeOkay( + pht('CLOSE'), + pht( + 'Done, closed revision.')); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/workflow/ArcanistLandWorkflow.php new/arcanist-0.0~git.20191118T203151~cc850163/src/workflow/ArcanistLandWorkflow.php --- old/arcanist-0.0~git.20190905T053100~3cdfe1ff/src/workflow/ArcanistLandWorkflow.php 2019-09-05 14:31:00.000000000 +0200 +++ new/arcanist-0.0~git.20191118T203151~cc850163/src/workflow/ArcanistLandWorkflow.php 2019-11-19 05:31:51.000000000 +0100 @@ -44,10 +44,10 @@ public function getCommandHelp() { return phutil_console_format(<<<EOTEXT - Supports: git, hg + Supports: git, git/p4, hg Publish an accepted revision after review. This command is the last - step in the standard Differential pre-publish code review workflow. + step in the standard Differential code review workflow. This command merges and pushes changes associated with an accepted revision that are currently sitting in __ref__, which is usually the @@ -57,6 +57,9 @@ Under Git: branches, tags, and arbitrary commits (detached HEADs) may be landed. + Under Git/Perforce: branches, tags, and arbitrary commits may + be submitted. + Under Mercurial: branches and bookmarks may be landed, but only onto a target of the same type. See T3855. @@ -66,7 +69,8 @@ A target branch is selected by examining these sources in order: - the **--onto** flag; - - the upstream of the current branch, recursively (Git only); + - the upstream of the branch targeted by the land operation, + recursively (Git only); - the __arc.land.onto.default__ configuration setting; - or by falling back to a standard default: - "master" in Git; @@ -76,6 +80,8 @@ - the **--remote** flag; - the upstream of the current branch, recursively (Git only); + - the special "p4" remote which indicates a repository has + been synchronized with Perforce (Git only); - or by falling back to a standard default: - "origin" in Git; - the default remote in Mercurial. @@ -159,7 +165,7 @@ 'remote' => array( 'param' => 'origin', 'help' => pht( - "Push to a remote other than the default ('origin' in git)."), + 'Push to a remote other than the default.'), ), 'merge' => array( 'help' => pht( @@ -224,23 +230,36 @@ } if ($engine) { - $this->readEngineArguments(); - $this->requireCleanWorkingCopy(); - $should_hold = $this->getArgument('hold'); + $remote_arg = $this->getArgument('remote'); + $onto_arg = $this->getArgument('onto'); $engine ->setWorkflow($this) ->setRepositoryAPI($this->getRepositoryAPI()) ->setSourceRef($this->branch) - ->setTargetRemote($this->remote) - ->setTargetOnto($this->onto) ->setShouldHold($should_hold) ->setShouldKeep($this->keepBranch) ->setShouldSquash($this->useSquash) ->setShouldPreview($this->preview) + ->setRemoteArgument($remote_arg) + ->setOntoArgument($onto_arg) ->setBuildMessageCallback(array($this, 'buildEngineMessage')); + // The goal here is to raise errors with flags early (which is cheap), + // before we test if the working copy is clean (which can be slow). This + // could probably be structured more cleanly. + + $engine->parseArguments(); + + // This must be configured or we fail later inside "buildEngineMessage()". + // This is less than ideal. + $this->ontoRemoteBranch = sprintf( + '%s/%s', + $engine->getTargetRemote(), + $engine->getTargetOnto()); + + $this->requireCleanWorkingCopy(); $engine->execute(); if (!$should_hold && !$this->preview) { @@ -337,123 +356,6 @@ return $refspec; } - private function readEngineArguments() { - // NOTE: This is hard-coded for Git right now. - // TODO: Clean this up and move it into LandEngines. - - $onto = $this->getEngineOnto(); - $remote = $this->getEngineRemote(); - - // This just overwrites work we did earlier, but it has to be up in this - // class for now because other parts of the workflow still depend on it. - $this->onto = $onto; - $this->remote = $remote; - $this->ontoRemoteBranch = $this->remote.'/'.$onto; - } - - private function getEngineOnto() { - $onto = $this->getArgument('onto'); - if ($onto !== null) { - $this->writeInfo( - pht('TARGET'), - pht( - 'Landing onto "%s", selected by the --onto flag.', - $onto)); - return $onto; - } - - $api = $this->getRepositoryAPI(); - $path = $api->getPathToUpstream($this->branch); - - if ($path->getLength()) { - $cycle = $path->getCycle(); - if ($cycle) { - $this->writeWarn( - pht('LOCAL CYCLE'), - pht( - 'Local branch tracks an upstream, but following it leads to a '. - 'local cycle; ignoring branch upstream.')); - - echo tsprintf( - "\n %s\n\n", - implode(' -> ', $cycle)); - - } else { - if ($path->isConnectedToRemote()) { - $onto = $path->getRemoteBranchName(); - $this->writeInfo( - pht('TARGET'), - pht( - 'Landing onto "%s", selected by following tracking branches '. - 'upstream to the closest remote.', - $onto)); - return $onto; - } else { - $this->writeInfo( - pht('NO PATH TO UPSTREAM'), - pht( - 'Local branch tracks an upstream, but there is no path '. - 'to a remote; ignoring branch upstream.')); - } - } - } - - $config_key = 'arc.land.onto.default'; - $onto = $this->getConfigFromAnySource($config_key); - if ($onto !== null) { - $this->writeInfo( - pht('TARGET'), - pht( - 'Landing onto "%s", selected by "%s" configuration.', - $onto, - $config_key)); - return $onto; - } - - $onto = 'master'; - $this->writeInfo( - pht('TARGET'), - pht( - 'Landing onto "%s", the default target under git.', - $onto)); - return $onto; - } - - private function getEngineRemote() { - $remote = $this->getArgument('remote'); - if ($remote !== null) { - $this->writeInfo( - pht('REMOTE'), - pht( - 'Using remote "%s", selected by the --remote flag.', - $remote)); - return $remote; - } - - $api = $this->getRepositoryAPI(); - $path = $api->getPathToUpstream($this->branch); - - $remote = $path->getRemoteRemoteName(); - if ($remote !== null) { - $this->writeInfo( - pht('REMOTE'), - pht( - 'Using remote "%s", selected by following tracking branches '. - 'upstream to the closest remote.', - $remote)); - return $remote; - } - - $remote = 'origin'; - $this->writeInfo( - pht('REMOTE'), - pht( - 'Using remote "%s", the default remote under git.', - $remote)); - return $remote; - } - - private function readArguments() { $repository_api = $this->getRepositoryAPI(); $this->isGit = $repository_api instanceof ArcanistGitAPI;