4
* Interfaces with the VCS in the working copy.
6
* @task status Path Status
8
abstract class ArcanistRepositoryAPI {
10
const FLAG_MODIFIED = 1;
12
const FLAG_DELETED = 4;
13
const FLAG_UNTRACKED = 8;
14
const FLAG_CONFLICT = 16;
15
const FLAG_MISSING = 32;
16
const FLAG_UNSTAGED = 64;
17
const FLAG_UNCOMMITTED = 128;
18
const FLAG_EXTERNALS = 256;
20
// Occurs in SVN when you replace a file with a directory without telling
22
const FLAG_OBSTRUCTED = 512;
24
// Occurs in SVN when an update was interrupted or failed, e.g. you ^C'd it.
25
const FLAG_INCOMPLETE = 1024;
28
protected $diffLinesOfContext = 0x7FFF;
29
private $baseCommitExplanation = '???';
30
private $configurationManager;
31
private $baseCommitArgumentRules;
33
private $uncommittedStatusCache;
34
private $commitRangeStatusCache;
36
private $symbolicBaseCommit;
37
private $resolvedBaseCommit;
39
abstract public function getSourceControlSystemName();
41
public function getDiffLinesOfContext() {
42
return $this->diffLinesOfContext;
45
public function setDiffLinesOfContext($lines) {
46
$this->diffLinesOfContext = $lines;
50
public function getWorkingCopyIdentity() {
51
return $this->configurationManager->getWorkingCopyIdentity();
54
public function getConfigurationManager() {
55
return $this->configurationManager;
58
public static function newAPIFromConfigurationManager(
59
ArcanistConfigurationManager $configuration_manager) {
61
$working_copy = $configuration_manager->getWorkingCopyIdentity();
66
'Trying to create a RepositoryAPI without a working copy!'));
69
$root = $working_copy->getProjectRoot();
70
switch ($working_copy->getVCSType()) {
72
$api = new ArcanistSubversionAPI($root);
75
$api = new ArcanistMercurialAPI($root);
78
$api = new ArcanistGitAPI($root);
83
'The current working directory is not part of a working copy for '.
84
'a supported version control system (Git, Subversion or '.
88
$api->configurationManager = $configuration_manager;
92
public function __construct($path) {
96
public function getPath($to_file = null) {
97
if ($to_file !== null) {
98
return $this->path.DIRECTORY_SEPARATOR.
99
ltrim($to_file, DIRECTORY_SEPARATOR);
101
return $this->path.DIRECTORY_SEPARATOR;
106
/* -( Path Status )-------------------------------------------------------- */
109
abstract protected function buildUncommittedStatus();
110
abstract protected function buildCommitRangeStatus();
114
* Get a list of uncommitted paths in the working copy that have been changed
115
* or are affected by other status effects, like conflicts or untracked
118
* Convenience methods @{method:getUntrackedChanges},
119
* @{method:getUnstagedChanges}, @{method:getUncommittedChanges},
120
* @{method:getMergeConflicts}, and @{method:getIncompleteChanges} allow
121
* simpler selection of paths in a specific state.
123
* This method returns a map of paths to bitmasks with status, using
124
* `FLAG_` constants. For example:
127
* 'some/uncommitted/file.txt' => ArcanistRepositoryAPI::FLAG_UNSTAGED,
130
* A file may be in several states. Not all states are possible with all
131
* version control systems.
133
* @return map<string, bitmask> Map of paths, see above.
136
final public function getUncommittedStatus() {
137
if ($this->uncommittedStatusCache === null) {
138
$status = $this->buildUncommittedStatus();
140
$this->uncommittedStatusCache = $status;
142
return $this->uncommittedStatusCache;
149
final public function getUntrackedChanges() {
150
return $this->getUncommittedPathsWithMask(self::FLAG_UNTRACKED);
157
final public function getUnstagedChanges() {
158
return $this->getUncommittedPathsWithMask(self::FLAG_UNSTAGED);
165
final public function getUncommittedChanges() {
166
return $this->getUncommittedPathsWithMask(self::FLAG_UNCOMMITTED);
173
final public function getMergeConflicts() {
174
return $this->getUncommittedPathsWithMask(self::FLAG_CONFLICT);
181
final public function getIncompleteChanges() {
182
return $this->getUncommittedPathsWithMask(self::FLAG_INCOMPLETE);
189
final public function getMissingChanges() {
190
return $this->getUncommittedPathsWithMask(self::FLAG_MISSING);
197
private function getUncommittedPathsWithMask($mask) {
199
foreach ($this->getUncommittedStatus() as $path => $flags) {
200
if ($flags & $mask) {
209
* Get a list of paths affected by the commits in the current commit range.
211
* See @{method:getUncommittedStatus} for a description of the return value.
213
* @return map<string, bitmask> Map from paths to status.
216
final public function getCommitRangeStatus() {
217
if ($this->commitRangeStatusCache === null) {
218
$status = $this->buildCommitRangeStatus();
220
$this->commitRangeStatusCache = $status;
222
return $this->commitRangeStatusCache;
227
* Get a list of paths affected by commits in the current commit range, or
228
* uncommitted changes in the working copy. See @{method:getUncommittedStatus}
229
* or @{method:getCommitRangeStatus} to retrieve smaller parts of the status.
231
* See @{method:getUncommittedStatus} for a description of the return value.
233
* @return map<string, bitmask> Map from paths to status.
236
final public function getWorkingCopyStatus() {
237
$range_status = $this->getCommitRangeStatus();
238
$uncommitted_status = $this->getUncommittedStatus();
240
$result = new PhutilArrayWithDefaultValue($range_status);
241
foreach ($uncommitted_status as $path => $mask) {
242
$result[$path] |= $mask;
245
$result = $result->toArray();
252
* Drops caches after changes to the working copy. By default, some queries
253
* against the working copy are cached. They
258
final public function reloadWorkingCopy() {
259
$this->uncommittedStatusCache = null;
260
$this->commitRangeStatusCache = null;
262
$this->didReloadWorkingCopy();
263
$this->reloadCommitRange();
270
* Hook for implementations to dirty working copy caches after the working
271
* copy has been updated.
276
protected function didReloadWorkingCopy() {
282
* Fetches the original file data for each path provided.
284
* @return map<string, string> Map from path to file data.
286
public function getBulkOriginalFileData($paths) {
288
foreach ($paths as $path) {
289
$filedata[$path] = $this->getOriginalFileData($path);
296
* Fetches the current file data for each path provided.
298
* @return map<string, string> Map from path to file data.
300
public function getBulkCurrentFileData($paths) {
302
foreach ($paths as $path) {
303
$filedata[$path] = $this->getCurrentFileData($path);
310
* @return Traversable
312
abstract public function getAllFiles();
314
abstract public function getBlame($path);
316
abstract public function getRawDiffText($path);
317
abstract public function getOriginalFileData($path);
318
abstract public function getCurrentFileData($path);
319
abstract public function getLocalCommitInformation();
320
abstract public function getSourceControlBaseRevision();
321
abstract public function getCanonicalRevisionName($string);
322
abstract public function getBranchName();
323
abstract public function getSourceControlPath();
324
abstract public function isHistoryDefaultImmutable();
325
abstract public function supportsAmend();
326
abstract public function getWorkingCopyRevision();
327
abstract public function updateWorkingCopy();
328
abstract public function getMetadataPath();
329
abstract public function loadWorkingCopyDifferentialRevisions(
330
ConduitClient $conduit,
332
abstract public function getRemoteURI();
335
public function getUnderlyingWorkingCopyRevision() {
336
return $this->getWorkingCopyRevision();
339
public function getChangedFiles($since_commit) {
340
throw new ArcanistCapabilityNotSupportedException($this);
343
public function getAuthor() {
344
throw new ArcanistCapabilityNotSupportedException($this);
347
public function addToCommit(array $paths) {
348
throw new ArcanistCapabilityNotSupportedException($this);
351
abstract public function supportsLocalCommits();
353
public function doCommit($message) {
354
throw new ArcanistCapabilityNotSupportedException($this);
357
public function amendCommit($message = null) {
358
throw new ArcanistCapabilityNotSupportedException($this);
361
public function getAllBranches() {
362
// TODO: Implement for Mercurial/SVN and make abstract.
366
public function hasLocalCommit($commit) {
367
throw new ArcanistCapabilityNotSupportedException($this);
370
public function getCommitMessage($commit) {
371
throw new ArcanistCapabilityNotSupportedException($this);
374
public function getCommitSummary($commit) {
375
throw new ArcanistCapabilityNotSupportedException($this);
378
public function getAllLocalChanges() {
379
throw new ArcanistCapabilityNotSupportedException($this);
382
abstract public function supportsLocalBranchMerge();
384
public function performLocalBranchMerge($branch, $message) {
385
throw new ArcanistCapabilityNotSupportedException($this);
388
public function getFinalizedRevisionMessage() {
389
throw new ArcanistCapabilityNotSupportedException($this);
392
public function execxLocal($pattern /* , ... */) {
393
$args = func_get_args();
394
return $this->buildLocalFuture($args)->resolvex();
397
public function execManualLocal($pattern /* , ... */) {
398
$args = func_get_args();
399
return $this->buildLocalFuture($args)->resolve();
402
public function execFutureLocal($pattern /* , ... */) {
403
$args = func_get_args();
404
return $this->buildLocalFuture($args);
407
abstract protected function buildLocalFuture(array $argv);
409
public function canStashChanges() {
413
public function stashChanges() {
414
throw new ArcanistCapabilityNotSupportedException($this);
417
public function unstashChanges() {
418
throw new ArcanistCapabilityNotSupportedException($this);
421
/* -( Scratch Files )------------------------------------------------------ */
425
* Try to read a scratch file, if it exists and is readable.
427
* @param string Scratch file name.
428
* @return mixed String for file contents, or false for failure.
431
public function readScratchFile($path) {
432
$full_path = $this->getScratchFilePath($path);
437
if (!Filesystem::pathExists($full_path)) {
442
$result = Filesystem::readFile($full_path);
443
} catch (FilesystemException $ex) {
452
* Try to write a scratch file, if there's somewhere to put it and we can
455
* @param string Scratch file name to write.
456
* @param string Data to write.
457
* @return bool True on success, false on failure.
460
public function writeScratchFile($path, $data) {
461
$dir = $this->getScratchFilePath('');
466
if (!Filesystem::pathExists($dir)) {
468
Filesystem::createDirectory($dir);
469
} catch (Exception $ex) {
475
Filesystem::writeFile($this->getScratchFilePath($path), $data);
476
} catch (FilesystemException $ex) {
485
* Try to remove a scratch file.
487
* @param string Scratch file name to remove.
488
* @return bool True if the file was removed successfully.
491
public function removeScratchFile($path) {
492
$full_path = $this->getScratchFilePath($path);
498
Filesystem::remove($full_path);
499
} catch (FilesystemException $ex) {
508
* Get a human-readable description of the scratch file location.
510
* @param string Scratch file name.
511
* @return mixed String, or false on failure.
514
public function getReadableScratchFilePath($path) {
515
$full_path = $this->getScratchFilePath($path);
517
return Filesystem::readablePath(
527
* Get the path to a scratch file, if possible.
529
* @param string Scratch file name.
530
* @return mixed File path, or false on failure.
533
public function getScratchFilePath($path) {
534
$new_scratch_path = Filesystem::resolvePath(
536
$this->getMetadataPath());
538
static $checked = false;
541
$old_scratch_path = $this->getPath('.arc');
542
// we only want to do the migration once
543
// unfortunately, people have checked in .arc directories which
544
// means that the old one may get recreated after we delete it
545
if (Filesystem::pathExists($old_scratch_path) &&
546
!Filesystem::pathExists($new_scratch_path)) {
547
Filesystem::createDirectory($new_scratch_path);
548
$existing_files = Filesystem::listDirectory($old_scratch_path, true);
549
foreach ($existing_files as $file) {
550
$new_path = Filesystem::resolvePath($file, $new_scratch_path);
551
$old_path = Filesystem::resolvePath($file, $old_scratch_path);
552
Filesystem::writeFile(
554
Filesystem::readFile($old_path));
556
Filesystem::remove($old_scratch_path);
559
return Filesystem::resolvePath($path, $new_scratch_path);
563
/* -( Base Commits )------------------------------------------------------- */
565
abstract public function supportsCommitRanges();
567
final public function setBaseCommit($symbolic_commit) {
568
if (!$this->supportsCommitRanges()) {
569
throw new ArcanistCapabilityNotSupportedException($this);
572
$this->symbolicBaseCommit = $symbolic_commit;
573
$this->reloadCommitRange();
577
public function setHeadCommit($symbolic_commit) {
578
throw new ArcanistCapabilityNotSupportedException($this);
581
final public function getBaseCommit() {
582
if (!$this->supportsCommitRanges()) {
583
throw new ArcanistCapabilityNotSupportedException($this);
586
if ($this->resolvedBaseCommit === null) {
587
$commit = $this->buildBaseCommit($this->symbolicBaseCommit);
588
$this->resolvedBaseCommit = $commit;
591
return $this->resolvedBaseCommit;
594
public function getHeadCommit() {
595
throw new ArcanistCapabilityNotSupportedException($this);
598
final public function reloadCommitRange() {
599
$this->resolvedBaseCommit = null;
600
$this->baseCommitExplanation = null;
602
$this->didReloadCommitRange();
607
protected function didReloadCommitRange() {
611
protected function buildBaseCommit($symbolic_commit) {
612
throw new ArcanistCapabilityNotSupportedException($this);
615
public function getBaseCommitExplanation() {
616
return $this->baseCommitExplanation;
619
public function setBaseCommitExplanation($explanation) {
620
$this->baseCommitExplanation = $explanation;
624
public function resolveBaseCommitRule($rule, $source) {
628
public function setBaseCommitArgumentRules($base_commit_argument_rules) {
629
$this->baseCommitArgumentRules = $base_commit_argument_rules;
633
public function getBaseCommitArgumentRules() {
634
return $this->baseCommitArgumentRules;
637
public function resolveBaseCommit() {
638
$base_commit_rules = array(
639
'runtime' => $this->getBaseCommitArgumentRules(),
645
$all_sources = $this->configurationManager->getConfigFromAllSources('base');
647
$base_commit_rules = $all_sources + $base_commit_rules;
649
$parser = new ArcanistBaseCommitParser($this);
650
$commit = $parser->resolveBaseCommit($base_commit_rules);
655
public function getRepositoryUUID() {