Skip to content

Commit f5f784f

Browse files
author
epriestley
committedMay 30, 2016
Version clustered, observed repositories in a reasonable way (by largest discovered HEAD)
Summary: Ref T4292. For hosted, clustered repositories we have a good way to increment the internal version of the repository: every time a user pushes something, we increment the version by 1. We don't have a great way to do this for observed/remote repositories because when we `git fetch` we might get nothing, or we might get some changes, and we can't easily tell //what// changes we got. For example, if we see that another node is at "version 97", and we do a fetch and see some changes, we don't know if we're in sync with them (i.e., also at "version 97") or ahead of them (at "version 98"). This implements a simple way to version an observed repository: - Take the head of every branch/tag. - Look them up. - Pick the biggest internal ID number. This will work //except// when branches are deleted, which could cause the version to go backward if the "biggest commit" is the one that was deleted. This should be OK, since it's rare and the effects are minor and the repository will "self-heal" on the next actual push. Test Plan: - Created an observed repository. - Ran `bin/repository update` and observed a sensible version number appear in the version table. - Pushed to the remote, did another update, saw a sensible update. - Did an update with no push, saw no effect on version number. - Toggled repository to hosted, saw the version reset. - Simulated read traffic to out-of-sync node, saw it do a remote fetch. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4292 Differential Revision: https://secure.phabricator.com/D15986
1 parent e81637a commit f5f784f

10 files changed

+316
-70
lines changed
 

‎src/applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ protected function getGitResult(ConduitAPIRequest $request) {
5656
} else {
5757
$refs = id(new DiffusionLowLevelGitRefQuery())
5858
->setRepository($repository)
59-
->withIsOriginBranch(true)
59+
->withRefTypes(
60+
array(
61+
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
62+
))
6063
->execute();
6164
}
6265

‎src/applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ private function loadGitTagList() {
7272

7373
$refs = id(new DiffusionLowLevelGitRefQuery())
7474
->setRepository($repository)
75-
->withIsTag(true)
75+
->withRefTypes(
76+
array(
77+
PhabricatorRepositoryRefCursor::TYPE_TAG,
78+
))
7679
->execute();
7780

7881
$tags = array();

‎src/applications/diffusion/editor/DiffusionURIEditor.php

+13
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,8 @@ protected function applyFinalEffects(
463463
break;
464464
}
465465

466+
$was_hosted = $repository->isHosted();
467+
466468
if ($observe_uri) {
467469
$repository
468470
->setHosted(false)
@@ -477,6 +479,17 @@ protected function applyFinalEffects(
477479

478480
$repository->save();
479481

482+
$is_hosted = $repository->isHosted();
483+
484+
// If we've swapped the repository from hosted to observed or vice versa,
485+
// reset all the cluster version clocks.
486+
if ($was_hosted != $is_hosted) {
487+
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
488+
->setViewer($this->getActor())
489+
->setRepository($repository)
490+
->synchronizeWorkingCopyAfterHostingChange();
491+
}
492+
480493
return $xactions;
481494
}
482495

‎src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php

+22-6
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,28 @@ private function buildClusterStatusPanel() {
137137
$version = idx($versions, $device->getPHID());
138138
if ($version) {
139139
$version_number = $version->getRepositoryVersion();
140-
$version_number = phutil_tag(
141-
'a',
142-
array(
143-
'href' => "/diffusion/pushlog/view/{$version_number}/",
144-
),
145-
$version_number);
140+
141+
$href = null;
142+
if ($repository->isHosted()) {
143+
$href = "/diffusion/pushlog/view/{$version_number}/";
144+
} else {
145+
$commit = id(new DiffusionCommitQuery())
146+
->setViewer($viewer)
147+
->withIDs(array($version_number))
148+
->executeOne();
149+
if ($commit) {
150+
$href = $commit->getURI();
151+
}
152+
}
153+
154+
if ($href) {
155+
$version_number = phutil_tag(
156+
'a',
157+
array(
158+
'href' => $href,
159+
),
160+
$version_number);
161+
}
146162
} else {
147163
$version_number = '-';
148164
}

‎src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php

+163-24
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,53 @@ public function synchronizeWorkingCopyAfterCreation() {
8282
}
8383

8484

85+
/**
86+
* @task sync
87+
*/
88+
public function synchronizeWorkingCopyAfterHostingChange() {
89+
if (!$this->shouldEnableSynchronization()) {
90+
return;
91+
}
92+
93+
$repository = $this->getRepository();
94+
$repository_phid = $repository->getPHID();
95+
96+
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
97+
$repository_phid);
98+
$versions = mpull($versions, null, 'getDevicePHID');
99+
100+
// After converting a hosted repository to observed, or vice versa, we
101+
// need to reset version numbers because the clocks for observed and hosted
102+
// repositories run on different units.
103+
104+
// We identify all the cluster leaders and reset their version to 0.
105+
// We identify all the cluster followers and demote them.
106+
107+
// This allows the cluter to start over again at version 0 but keep the
108+
// same leaders.
109+
110+
if ($versions) {
111+
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
112+
foreach ($versions as $version) {
113+
$device_phid = $version->getDevicePHID();
114+
115+
if ($version->getRepositoryVersion() == $max_version) {
116+
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
117+
$repository_phid,
118+
$device_phid,
119+
0);
120+
} else {
121+
PhabricatorRepositoryWorkingCopyVersion::demoteDevice(
122+
$repository_phid,
123+
$device_phid);
124+
}
125+
}
126+
}
127+
128+
return $this;
129+
}
130+
131+
85132
/**
86133
* @task sync
87134
*/
@@ -149,14 +196,18 @@ public function synchronizeWorkingCopyBeforeRead() {
149196

150197
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
151198
if ($max_version > $this_version) {
152-
$fetchable = array();
153-
foreach ($versions as $version) {
154-
if ($version->getRepositoryVersion() == $max_version) {
155-
$fetchable[] = $version->getDevicePHID();
199+
if ($repository->isHosted()) {
200+
$fetchable = array();
201+
foreach ($versions as $version) {
202+
if ($version->getRepositoryVersion() == $max_version) {
203+
$fetchable[] = $version->getDevicePHID();
204+
}
156205
}
157-
}
158206

159-
$this->synchronizeWorkingCopyFromDevices($fetchable);
207+
$this->synchronizeWorkingCopyFromDevices($fetchable);
208+
} else {
209+
$this->synchornizeWorkingCopyFromRemote();
210+
}
160211

161212
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
162213
$repository_phid,
@@ -329,6 +380,47 @@ public function synchronizeWorkingCopyBeforeWrite() {
329380
}
330381

331382

383+
public function synchronizeWorkingCopyAfterDiscovery($new_version) {
384+
if (!$this->shouldEnableSynchronization()) {
385+
return;
386+
}
387+
388+
$repository = $this->getRepository();
389+
$repository_phid = $repository->getPHID();
390+
if ($repository->isHosted()) {
391+
return;
392+
}
393+
394+
$viewer = $this->getViewer();
395+
396+
$device = AlmanacKeys::getLiveDevice();
397+
$device_phid = $device->getPHID();
398+
399+
// NOTE: We are not holding a lock here because this method is only called
400+
// from PhabricatorRepositoryDiscoveryEngine, which already holds a device
401+
// lock. Even if we do race here and record an older version, the
402+
// consequences are mild: we only do extra work to correct it later.
403+
404+
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
405+
$repository_phid);
406+
$versions = mpull($versions, null, 'getDevicePHID');
407+
408+
$this_version = idx($versions, $device_phid);
409+
if ($this_version) {
410+
$this_version = (int)$this_version->getRepositoryVersion();
411+
} else {
412+
$this_version = -1;
413+
}
414+
415+
if ($new_version > $this_version) {
416+
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
417+
$repository_phid,
418+
$device_phid,
419+
$new_version);
420+
}
421+
}
422+
423+
332424
/**
333425
* @task sync
334426
*/
@@ -471,13 +563,6 @@ private function shouldEnableSynchronization() {
471563
return false;
472564
}
473565

474-
// TODO: It may eventually make sense to try to version and synchronize
475-
// observed repositories (so that daemons don't do reads against out-of
476-
// date hosts), but don't bother for now.
477-
if (!$repository->isHosted()) {
478-
return false;
479-
}
480-
481566
$device = AlmanacKeys::getLiveDevice();
482567
if (!$device) {
483568
return false;
@@ -487,6 +572,50 @@ private function shouldEnableSynchronization() {
487572
}
488573

489574

575+
/**
576+
* @task internal
577+
*/
578+
private function synchornizeWorkingCopyFromRemote() {
579+
$repository = $this->getRepository();
580+
$device = AlmanacKeys::getLiveDevice();
581+
582+
$local_path = $repository->getLocalPath();
583+
$fetch_uri = $repository->getRemoteURIEnvelope();
584+
585+
if ($repository->isGit()) {
586+
$this->requireWorkingCopy();
587+
588+
$argv = array(
589+
'fetch --prune -- %P %s',
590+
$fetch_uri,
591+
'+refs/*:refs/*',
592+
);
593+
} else {
594+
throw new Exception(pht('Remote sync only supported for git!'));
595+
}
596+
597+
$future = DiffusionCommandEngine::newCommandEngine($repository)
598+
->setArgv($argv)
599+
->setSudoAsDaemon(true)
600+
->setCredentialPHID($repository->getCredentialPHID())
601+
->setProtocol($repository->getRemoteProtocol())
602+
->newFuture();
603+
604+
$future->setCWD($local_path);
605+
606+
try {
607+
$future->resolvex();
608+
} catch (Exception $ex) {
609+
$this->logLine(
610+
pht(
611+
'Synchronization of "%s" from remote failed: %s',
612+
$device->getName(),
613+
$ex->getMessage()));
614+
throw $ex;
615+
}
616+
}
617+
618+
490619
/**
491620
* @task internal
492621
*/
@@ -560,17 +689,7 @@ private function synchronizeWorkingCopyFromBinding($binding) {
560689
$local_path = $repository->getLocalPath();
561690

562691
if ($repository->isGit()) {
563-
if (!Filesystem::pathExists($local_path)) {
564-
throw new Exception(
565-
pht(
566-
'Repository "%s" does not have a working copy on this device '.
567-
'yet, so it can not be synchronized. Wait for the daemons to '.
568-
'construct one or run `bin/repository update %s` on this host '.
569-
'("%s") to build it explicitly.',
570-
$repository->getDisplayName(),
571-
$repository->getMonogram(),
572-
$device->getName()));
573-
}
692+
$this->requireWorkingCopy();
574693

575694
$argv = array(
576695
'fetch --prune -- %s %s',
@@ -622,4 +741,24 @@ private function logText($message) {
622741
}
623742
return $this;
624743
}
744+
745+
private function requireWorkingCopy() {
746+
$repository = $this->getRepository();
747+
$local_path = $repository->getLocalPath();
748+
749+
if (!Filesystem::pathExists($local_path)) {
750+
$device = AlmanacKeys::getLiveDevice();
751+
752+
throw new Exception(
753+
pht(
754+
'Repository "%s" does not have a working copy on this device '.
755+
'yet, so it can not be synchronized. Wait for the daemons to '.
756+
'construct one or run `bin/repository update %s` on this host '.
757+
'("%s") to build it explicitly.',
758+
$repository->getDisplayName(),
759+
$repository->getMonogram(),
760+
$device->getName()));
761+
}
762+
}
763+
625764
}

‎src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php

+19-16
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,33 @@
66
*/
77
final class DiffusionLowLevelGitRefQuery extends DiffusionLowLevelQuery {
88

9-
private $isTag;
10-
private $isOriginBranch;
9+
private $refTypes;
1110

12-
public function withIsTag($is_tag) {
13-
$this->isTag = $is_tag;
14-
return $this;
15-
}
16-
17-
public function withIsOriginBranch($is_origin_branch) {
18-
$this->isOriginBranch = $is_origin_branch;
11+
public function withRefTypes(array $ref_types) {
12+
$this->refTypes = $ref_types;
1913
return $this;
2014
}
2115

2216
protected function executeQuery() {
17+
$ref_types = $this->refTypes;
18+
if ($ref_types) {
19+
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
20+
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
21+
22+
$ref_types = array_fuse($ref_types);
23+
24+
$with_branches = isset($ref_types[$type_branch]);
25+
$with_tags = isset($ref_types[$type_tag]);
26+
} else {
27+
$with_branches = true;
28+
$with_tags = true;
29+
}
30+
2331
$repository = $this->getRepository();
2432

2533
$prefixes = array();
2634

27-
$any = ($this->isTag || $this->isOriginBranch);
28-
if (!$any) {
29-
throw new Exception(pht('Specify types of refs to query.'));
30-
}
31-
32-
if ($this->isOriginBranch) {
35+
if ($with_branches) {
3336
if ($repository->isWorkingCopyBare()) {
3437
$prefix = 'refs/heads/';
3538
} else {
@@ -39,7 +42,7 @@ protected function executeQuery() {
3942
$prefixes[] = $prefix;
4043
}
4144

42-
if ($this->isTag) {
45+
if ($with_tags) {
4346
$prefixes[] = 'refs/tags/';
4447
}
4548

‎src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ private function resolveGitRefs() {
6666
// First, resolve branches and tags.
6767
$ref_map = id(new DiffusionLowLevelGitRefQuery())
6868
->setRepository($repository)
69-
->withIsTag(true)
70-
->withIsOriginBranch(true)
69+
->withRefTypes(
70+
array(
71+
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
72+
PhabricatorRepositoryRefCursor::TYPE_TAG,
73+
))
7174
->execute();
7275
$ref_map = mgroup($ref_map, 'getShortName');
7376

0 commit comments

Comments
 (0)
Failed to load comments.