1<?php 2 3/** 4 * Lands a branch by rebasing, merging and amending it. 5 */ 6final class ArcanistLandWorkflow 7 extends ArcanistArcWorkflow { 8 9 public function getWorkflowName() { 10 return 'land'; 11 } 12 13 public function getWorkflowInformation() { 14 $help = pht(<<<EOTEXT 15Supports: git, git/p4, git/svn, hg 16 17Publish accepted revisions after review. This command is the last step in the 18standard Differential code review workflow. 19 20To publish changes in local branch or bookmark "feature1", you will usually 21run this command: 22 23 **$ arc land feature1** 24 25This workflow merges and pushes changes associated with revisions that are 26ancestors of __ref__. Without __ref__, the current working copy state will be 27used. You can specify multiple __ref__ arguments to publish multiple changes at 28once. 29 30A __ref__ can be any symbol which identifies a commit: a branch name, a tag 31name, a bookmark name, a topic name, a raw commit hash, a symbolic reference, 32etc. 33 34When you provide a __ref__, all unpublished changes which are present in 35ancestors of that __ref__ will be selected for publishing. (With the 36**--pick** flag, only the unpublished changes you directly reference will be 37selected.) 38 39For example, if you provide local branch "feature3" as a __ref__ argument, that 40may also select the changes in "feature1" and "feature2" (if they are ancestors 41of "feature3"). If you stack changes in a single local branch, all commits in 42the stack may be selected. 43 44The workflow merges unpublished changes reachable from __ref__ "into" some 45intermediate branch, then pushes the combined state "onto" some destination 46branch (or list of branches). 47 48(In Mercurial, the "into" and "onto" branches may be bookmarks instead.) 49 50In the most common case, there is only one "onto" branch (often "master" or 51"default" or some similar branch) and the "into" branch is the same branch. For 52example, it is common to merge local feature branch "feature1" into 53"origin/master", then push it onto "origin/master". 54 55The list of "onto" branches is selected by examining these sources in order: 56 57 - the **--onto** flags; 58 - the __arc.land.onto__ configuration setting; 59 - (in Git) the upstream of the branch targeted by the land operation, 60 recursively; 61 - or by falling back to a standard default: 62 - (in Git) "master"; 63 - (in Mercurial) "default". 64 65The remote to push "onto" is selected by examining these sources in order: 66 67 - the **--onto-remote** flag; 68 - the __arc.land.onto-remote__ configuration setting; 69 - (in Git) the upstream of the current branch, recursively; 70 - (in Git) the special "p4" remote which indicates a repository has 71 been synchronized with Perforce; 72 - or by falling back to a standard default: 73 - (in Git) "origin"; 74 - (in Mercurial) "default". 75 76The branch to merge "into" is selected by examining these sources in order: 77 78 - the **--into** flag; 79 - the **--into-empty** flag; 80 - or by falling back to the first "onto" branch. 81 82The remote to merge "into" is selected by examining these sources in order: 83 84 - the **--into-remote** flag; 85 - the **--into-local** flag (which disables fetching before merging); 86 - or by falling back to the "onto" remote. 87 88After selecting remotes and branches, the commits which will land are printed. 89 90With **--preview**, execution stops here, before the change is merged. 91 92The "into" branch is fetched from the "into" remote (unless **--into-local** or 93**--into-empty** are specified) and the changes are merged into the state in 94the "into" branch according to the selected merge strategy. 95 96The default merge strategy is "squash", which produces a single commit from 97all local commits for each change. A different strategy can be selected with 98the **--strategy** flag. 99 100The resulting merged change will be given an up-to-date commit message 101describing the final state of the revision in Differential. 102 103With **--hold**, execution stops here, before the change is pushed. 104 105The change is pushed onto all of the "onto" branches in the "onto" remote. 106 107If you are landing multiple changes, they are normally all merged locally and 108then all pushed in a single operation. Instead, you can merge and push them one 109at a time with **--incremental**. 110 111Under merge strategies which mutate history (including the default "squash" 112strategy), local refs which descend from commits that were published are 113now updated. For example, if you land "feature4", local branches "feature5" and 114"feature6" may now be rebased on the published version of the change. 115 116Once everything has been pushed, cleanup occurs. Consulting mystical sources of 117power, the workflow makes a guess about what state you wanted to end up in 118after the process finishes. The working copy is put into that state. 119 120Any obsolete refs that point at commits which were published are deleted, 121unless the **--keep-branches** flag is passed. 122EOTEXT 123 ); 124 125 return $this->newWorkflowInformation() 126 ->setSynopsis(pht('Publish reviewed changes.')) 127 ->addExample(pht('**land** [__options__] -- [__ref__ ...]')) 128 ->setHelp($help); 129 } 130 131 public function getWorkflowArguments() { 132 return array( 133 $this->newWorkflowArgument('hold') 134 ->setHelp( 135 pht( 136 'Prepare the changes to be pushed, but do not actually push '. 137 'them.')), 138 $this->newWorkflowArgument('keep-branches') 139 ->setHelp( 140 pht( 141 'Keep local branches around after changes are pushed. By '. 142 'default, local branches are deleted after the changes they '. 143 'contain are published.')), 144 $this->newWorkflowArgument('onto-remote') 145 ->setParameter('remote-name') 146 ->setHelp(pht('Push to a remote other than the default.')) 147 ->addRelatedConfig('arc.land.onto-remote'), 148 $this->newWorkflowArgument('onto') 149 ->setParameter('branch-name') 150 ->setRepeatable(true) 151 ->addRelatedConfig('arc.land.onto') 152 ->setHelp( 153 array( 154 pht( 155 'After merging, push changes onto a specified branch.'), 156 pht( 157 'Specifying this flag multiple times will push to multiple '. 158 'branches.'), 159 )), 160 $this->newWorkflowArgument('strategy') 161 ->setParameter('strategy-name') 162 ->addRelatedConfig('arc.land.strategy') 163 ->setHelp( 164 array( 165 pht( 166 'Merge using a particular strategy. Supported strategies are '. 167 '"squash" and "merge".'), 168 pht( 169 'The "squash" strategy collapses multiple local commits into '. 170 'a single commit when publishing. It produces a linear '. 171 'published history (but discards local checkpoint commits). '. 172 'This is the default strategy.'), 173 pht( 174 'The "merge" strategy generates a merge commit when publishing '. 175 'that retains local checkpoint commits (but produces a '. 176 'nonlinear published history). Select this strategy if you do '. 177 'not want "arc land" to discard checkpoint commits.'), 178 )), 179 $this->newWorkflowArgument('revision') 180 ->setParameter('revision-identifier') 181 ->setHelp( 182 pht( 183 'Land a specific revision, rather than determining revisions '. 184 'automatically from the commits that are landing.')), 185 $this->newWorkflowArgument('preview') 186 ->setHelp( 187 pht( 188 'Show the changes that will land. Does not modify the working '. 189 'copy or the remote.')), 190 $this->newWorkflowArgument('into') 191 ->setParameter('commit-ref') 192 ->setHelp( 193 pht( 194 'Specify the state to merge into. By default, this is the same '. 195 'as the "onto" ref.')), 196 $this->newWorkflowArgument('into-remote') 197 ->setParameter('remote-name') 198 ->setHelp( 199 pht( 200 'Specifies the remote to fetch the "into" ref from. By '. 201 'default, this is the same as the "onto" remote.')), 202 $this->newWorkflowArgument('into-local') 203 ->setHelp( 204 pht( 205 'Use the local "into" ref state instead of fetching it from '. 206 'a remote.')), 207 $this->newWorkflowArgument('into-empty') 208 ->setHelp( 209 pht( 210 'Merge into the empty state instead of an existing state. This '. 211 'mode is primarily useful when creating a new repository, and '. 212 'selected automatically if the "onto" ref does not exist and the '. 213 '"into" state is not specified.')), 214 $this->newWorkflowArgument('incremental') 215 ->setHelp( 216 array( 217 pht( 218 'When landing multiple revisions at once, push and rebase '. 219 'after each merge completes instead of waiting until all '. 220 'merges are completed to push.'), 221 pht( 222 'This is slower than the default behavior and not atomic, '. 223 'but may make it easier to resolve conflicts and land '. 224 'complicated changes by allowing you to make progress one '. 225 'step at a time.'), 226 )), 227 $this->newWorkflowArgument('pick') 228 ->setHelp( 229 pht( 230 'Land only the changes directly named by arguments, instead '. 231 'of all reachable ancestors.')), 232 $this->newWorkflowArgument('ref') 233 ->setWildcard(true), 234 ); 235 } 236 237 protected function newPrompts() { 238 return array( 239 $this->newPrompt('arc.land.large-working-set') 240 ->setDescription( 241 pht( 242 'Confirms landing more than %s commit(s) in a single operation.', 243 new PhutilNumber($this->getLargeWorkingSetLimit()))), 244 $this->newPrompt('arc.land.confirm') 245 ->setDescription( 246 pht( 247 'Confirms that the correct changes have been selected to '. 248 'land.')), 249 $this->newPrompt('arc.land.implicit') 250 ->setDescription( 251 pht( 252 'Confirms that local commits which are not associated with '. 253 'a revision have been associated correctly and should land.')), 254 $this->newPrompt('arc.land.unauthored') 255 ->setDescription( 256 pht( 257 'Confirms that revisions you did not author should land.')), 258 $this->newPrompt('arc.land.changes-planned') 259 ->setDescription( 260 pht( 261 'Confirms that revisions with changes planned should land.')), 262 $this->newPrompt('arc.land.published') 263 ->setDescription( 264 pht( 265 'Confirms that revisions that are already published should land.')), 266 $this->newPrompt('arc.land.not-accepted') 267 ->setDescription( 268 pht( 269 'Confirms that revisions that are not accepted should land.')), 270 $this->newPrompt('arc.land.open-parents') 271 ->setDescription( 272 pht( 273 'Confirms that revisions with open parent revisions should '. 274 'land.')), 275 $this->newPrompt('arc.land.failed-builds') 276 ->setDescription( 277 pht( 278 'Confirms that revisions with failed builds should land.')), 279 $this->newPrompt('arc.land.ongoing-builds') 280 ->setDescription( 281 pht( 282 'Confirms that revisions with ongoing builds should land.')), 283 $this->newPrompt('arc.land.create') 284 ->setDescription( 285 pht( 286 'Confirms that new branches or bookmarks should be created '. 287 'in the remote.')), 288 ); 289 } 290 291 public function getLargeWorkingSetLimit() { 292 return 50; 293 } 294 295 public function runWorkflow() { 296 $working_copy = $this->getWorkingCopy(); 297 $repository_api = $working_copy->getRepositoryAPI(); 298 299 $land_engine = $repository_api->getLandEngine(); 300 if (!$land_engine) { 301 throw new PhutilArgumentUsageException( 302 pht( 303 '"arc land" must be run in a Git or Mercurial working copy.')); 304 } 305 306 $is_incremental = $this->getArgument('incremental'); 307 $source_refs = $this->getArgument('ref'); 308 309 $onto_remote_arg = $this->getArgument('onto-remote'); 310 $onto_args = $this->getArgument('onto'); 311 312 $into_remote = $this->getArgument('into-remote'); 313 $into_empty = $this->getArgument('into-empty'); 314 $into_local = $this->getArgument('into-local'); 315 $into = $this->getArgument('into'); 316 317 $is_preview = $this->getArgument('preview'); 318 $should_hold = $this->getArgument('hold'); 319 $should_keep = $this->getArgument('keep-branches'); 320 321 $revision = $this->getArgument('revision'); 322 $strategy = $this->getArgument('strategy'); 323 $pick = $this->getArgument('pick'); 324 325 $land_engine 326 ->setViewer($this->getViewer()) 327 ->setWorkflow($this) 328 ->setLogEngine($this->getLogEngine()) 329 ->setSourceRefs($source_refs) 330 ->setShouldHold($should_hold) 331 ->setShouldKeep($should_keep) 332 ->setStrategyArgument($strategy) 333 ->setShouldPreview($is_preview) 334 ->setOntoRemoteArgument($onto_remote_arg) 335 ->setOntoArguments($onto_args) 336 ->setIntoRemoteArgument($into_remote) 337 ->setIntoEmptyArgument($into_empty) 338 ->setIntoLocalArgument($into_local) 339 ->setIntoArgument($into) 340 ->setPickArgument($pick) 341 ->setIsIncremental($is_incremental) 342 ->setRevisionSymbol($revision); 343 344 $land_engine->execute(); 345 } 346 347} 348