1<?php 2 3abstract class ArcanistRepositoryLocalState 4 extends Phobject { 5 6 private $repositoryAPI; 7 private $shouldRestore; 8 private $stashRef; 9 private $workflow; 10 11 final public function setWorkflow(ArcanistWorkflow $workflow) { 12 $this->workflow = $workflow; 13 return $this; 14 } 15 16 final public function getWorkflow() { 17 return $this->workflow; 18 } 19 20 final public function setRepositoryAPI(ArcanistRepositoryAPI $api) { 21 $this->repositoryAPI = $api; 22 return $this; 23 } 24 25 final public function getRepositoryAPI() { 26 return $this->repositoryAPI; 27 } 28 29 final public function saveLocalState() { 30 $api = $this->getRepositoryAPI(); 31 32 $working_copy_display = tsprintf( 33 " %s: %s\n", 34 pht('Working Copy'), 35 $api->getPath()); 36 37 $conflicts = $api->getMergeConflicts(); 38 if ($conflicts) { 39 echo tsprintf( 40 "\n%!\n%W\n\n%s\n", 41 pht('MERGE CONFLICTS'), 42 pht('You have merge conflicts in this working copy.'), 43 $working_copy_display); 44 45 $lists = array(); 46 47 $lists[] = $this->newDisplayFileList( 48 pht('Merge conflicts in working copy:'), 49 $conflicts); 50 51 $this->printFileLists($lists); 52 53 throw new PhutilArgumentUsageException( 54 pht( 55 'Resolve merge conflicts before proceeding.')); 56 } 57 58 $externals = $api->getDirtyExternalChanges(); 59 if ($externals) { 60 $message = pht( 61 '%s submodule(s) have uncommitted or untracked changes:', 62 new PhutilNumber(count($externals))); 63 64 $prompt = pht( 65 'Ignore the changes to these %s submodule(s) and continue?', 66 new PhutilNumber(count($externals))); 67 68 $list = id(new PhutilConsoleList()) 69 ->setWrap(false) 70 ->addItems($externals); 71 72 id(new PhutilConsoleBlock()) 73 ->addParagraph($message) 74 ->addList($list) 75 ->draw(); 76 77 $ok = phutil_console_confirm($prompt, $default_no = false); 78 if (!$ok) { 79 throw new ArcanistUserAbortException(); 80 } 81 } 82 83 $uncommitted = $api->getUncommittedChanges(); 84 $unstaged = $api->getUnstagedChanges(); 85 $untracked = $api->getUntrackedChanges(); 86 87 // We already dealt with externals. 88 $unstaged = array_diff($unstaged, $externals); 89 90 // We only want files which are purely uncommitted. 91 $uncommitted = array_diff($uncommitted, $unstaged); 92 $uncommitted = array_diff($uncommitted, $externals); 93 94 if ($untracked || $unstaged || $uncommitted) { 95 echo tsprintf( 96 "\n%!\n%W\n\n%s\n", 97 pht('UNCOMMITTED CHANGES'), 98 pht('You have uncommitted changes in this working copy.'), 99 $working_copy_display); 100 101 $lists = array(); 102 103 $lists[] = $this->newDisplayFileList( 104 pht('Untracked changes in working copy:'), 105 $untracked); 106 107 $lists[] = $this->newDisplayFileList( 108 pht('Unstaged changes in working copy:'), 109 $unstaged); 110 111 $lists[] = $this->newDisplayFileList( 112 pht('Uncommitted changes in working copy:'), 113 $uncommitted); 114 115 $this->printFileLists($lists); 116 117 if ($untracked) { 118 $hints = $this->getIgnoreHints(); 119 foreach ($hints as $hint) { 120 echo tsprintf("%?\n", $hint); 121 } 122 } 123 124 if ($this->canStashChanges()) { 125 126 $query = pht('Stash these changes and continue?'); 127 128 $this->getWorkflow() 129 ->getPrompt('arc.state.stash') 130 ->setQuery($query) 131 ->execute(); 132 133 $stash_ref = $this->saveStash(); 134 135 if ($stash_ref === null) { 136 throw new Exception( 137 pht( 138 'Expected a non-null return from call to "%s->saveStash()".', 139 get_class($this))); 140 } 141 142 $this->stashRef = $stash_ref; 143 } else { 144 throw new PhutilArgumentUsageException( 145 pht( 146 'You can not continue with uncommitted changes. Commit or '. 147 'discard them before proceeding.')); 148 } 149 } 150 151 $this->executeSaveLocalState(); 152 $this->shouldRestore = true; 153 154 // TODO: Detect when we're in the middle of a rebase. 155 // TODO: Detect when we're in the middle of a cherry-pick. 156 157 return $this; 158 } 159 160 final public function restoreLocalState() { 161 $this->shouldRestore = false; 162 163 $this->executeRestoreLocalState(); 164 $this->applyStash(); 165 $this->executeDiscardLocalState(); 166 167 return $this; 168 } 169 170 final public function discardLocalState() { 171 $this->shouldRestore = false; 172 173 $this->applyStash(); 174 $this->executeDiscardLocalState(); 175 176 return $this; 177 } 178 179 final public function __destruct() { 180 if ($this->shouldRestore) { 181 $this->restoreLocalState(); 182 } else { 183 $this->discardLocalState(); 184 } 185 } 186 187 final public function getRestoreCommandsForDisplay() { 188 return $this->newRestoreCommandsForDisplay(); 189 } 190 191 protected function canStashChanges() { 192 return false; 193 } 194 195 protected function saveStash() { 196 throw new PhutilMethodNotImplementedException(); 197 } 198 199 protected function restoreStash($ref) { 200 throw new PhutilMethodNotImplementedException(); 201 } 202 203 protected function discardStash($ref) { 204 throw new PhutilMethodNotImplementedException(); 205 } 206 207 private function applyStash() { 208 if ($this->stashRef === null) { 209 return; 210 } 211 $stash_ref = $this->stashRef; 212 $this->stashRef = null; 213 214 $this->restoreStash($stash_ref); 215 $this->discardStash($stash_ref); 216 } 217 218 abstract protected function executeSaveLocalState(); 219 abstract protected function executeRestoreLocalState(); 220 abstract protected function executeDiscardLocalState(); 221 abstract protected function newRestoreCommandsForDisplay(); 222 223 protected function getIgnoreHints() { 224 return array(); 225 } 226 227 final protected function newDisplayFileList($title, array $files) { 228 if (!$files) { 229 return null; 230 } 231 232 $items = array(); 233 $items[] = tsprintf("%s\n\n", $title); 234 foreach ($files as $file) { 235 $items[] = tsprintf( 236 " %s\n", 237 $file); 238 } 239 240 return $items; 241 } 242 243 final protected function printFileLists(array $lists) { 244 $lists = array_filter($lists); 245 246 $last_key = last_key($lists); 247 foreach ($lists as $key => $list) { 248 foreach ($list as $item) { 249 echo tsprintf('%B', $item); 250 } 251 if ($key !== $last_key) { 252 echo tsprintf("\n\n"); 253 } 254 } 255 256 echo tsprintf("\n"); 257 } 258 259} 260