Beta sync fixes (#2753)

* Fix trailing whitespace in vcs

* Add logging to sync graphql queries

* Don't remotely create a sync project when opening the share dialog

* Fix pulling workspaces with a missing default branch

* Fix the sync merge modal

* Fix workspaces dissapearing when switching to an empty default branch
This commit is contained in:
David Marby 2020-10-21 12:42:36 +02:00 committed by GitHub
parent 52de6d819d
commit edcfcf5cef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 46 deletions

View File

@ -367,8 +367,8 @@ export default class VCS {
}; };
} }
async getHistoryCount(): Promise<number> { async getHistoryCount(branchName?: string): Promise<number> {
const branch = await this._getCurrentBranch(); const branch = branchName ? await this._getBranch(branchName) : await this._getCurrentBranch();
return branch.snapshots.length; return branch.snapshots.length;
} }
@ -716,6 +716,7 @@ export default class VCS {
const { data, errors } = await fetch.post('/graphql?' + name, { query, variables }, sessionId); const { data, errors } = await fetch.post('/graphql?' + name, { query, variables }, sessionId);
if (errors && errors.length) { if (errors && errors.length) {
console.log(`[sync] Failed to query ${name}`, errors);
throw new Error(`Failed to query ${name}`); throw new Error(`Failed to query ${name}`);
} }
@ -727,7 +728,7 @@ export default class VCS {
` `
query ($projectId: ID!, $ids: [ID!]!) { query ($projectId: ID!, $ids: [ID!]!) {
blobsMissing(project: $projectId, ids: $ids) { blobsMissing(project: $projectId, ids: $ids) {
missing missing
} }
} }
`, `,
@ -766,7 +767,7 @@ export default class VCS {
await this._runGraphQL( await this._runGraphQL(
` `
mutation ($projectId: ID!, $branch: String!) { mutation ($projectId: ID!, $branch: String!) {
branchRemove(project: $projectId, name: $branch) branchRemove(project: $projectId, name: $branch)
}`, }`,
{ {
projectId: this._projectId(), projectId: this._projectId(),
@ -1127,32 +1128,25 @@ export default class VCS {
} }
async _queryProjectTeams(): Promise<Array<Team>> { async _queryProjectTeams(): Promise<Array<Team>> {
const run = async () => { const { project } = await this._runGraphQL(
const { project } = await this._runGraphQL( `
` query ($id: ID!) {
query ($id: ID!) { project(id: $id) {
project(id: $id) { teams {
teams { id
id name
name
}
} }
} }
`, }
{ `,
id: this._projectId(), {
}, id: this._projectId(),
'project.teams', },
); 'project.teams',
return project; );
};
let project = await run(); if (!project) {
throw new Error('Please push the workspace to be able to share it');
// Retry once if project doesn't exist yet
if (project === null) {
await this._getOrCreateRemoteProject();
project = await run();
} }
return project.teams; return project.teams;

View File

@ -19,6 +19,7 @@ import LoginModal from '../modals/login-modal';
import * as session from '../../../account/session'; import * as session from '../../../account/session';
import PromptButton from '../base/prompt-button'; import PromptButton from '../base/prompt-button';
import * as db from '../../../common/database'; import * as db from '../../../common/database';
import * as models from '../../../models';
// Stop refreshing if user hasn't been active in this long // Stop refreshing if user hasn't been active in this long
const REFRESH_USER_ACTIVITY = 1000 * 60 * 10; const REFRESH_USER_ACTIVITY = 1000 * 60 * 10;
@ -26,6 +27,8 @@ const REFRESH_USER_ACTIVITY = 1000 * 60 * 10;
// Refresh dropdown periodically // Refresh dropdown periodically
const REFRESH_PERIOD = 1000 * 60 * 1; const REFRESH_PERIOD = 1000 * 60 * 1;
const DEFAULT_BRANCH_NAME = 'master';
type Props = { type Props = {
workspace: Workspace, workspace: Workspace,
vcs: VCS, vcs: VCS,
@ -256,23 +259,51 @@ class SyncDropdown extends React.PureComponent<Props, State> {
const { vcs } = this.props; const { vcs } = this.props;
this.setState({ loadingProjectPull: true }); this.setState({ loadingProjectPull: true });
await vcs.setProject(p); await vcs.setProject(p);
await vcs.checkout([], 'master');
// Pull changes await vcs.checkout([], DEFAULT_BRANCH_NAME);
await vcs.pull([]); // There won't be any existing docs since it's a new pull
const flushId = await db.bufferChanges(); const remoteBranches = await vcs.getRemoteBranches();
for (const doc of await vcs.allDocuments()) { const defaultBranchMissing = !remoteBranches.includes(DEFAULT_BRANCH_NAME);
await db.upsert(doc);
// The default branch does not exist, so we create it and the workspace locally
if (defaultBranchMissing) {
const workspace: Workspace = await models.initModel(models.workspace.type, {
_id: p.rootDocumentId,
name: p.name,
});
await db.upsert(workspace);
} else {
// Pull changes
await vcs.pull([]); // There won't be any existing docs since it's a new pull
const flushId = await db.bufferChanges();
for (const doc of await vcs.allDocuments()) {
await db.upsert(doc);
}
await db.flushChanges(flushId);
} }
await db.flushChanges(flushId);
await this.refreshMainAttributes({ loadingProjectPull: false }); await this.refreshMainAttributes({ loadingProjectPull: false });
} }
async _handleSwitchBranch(branch: string) { async _handleSwitchBranch(branch: string) {
const { vcs, syncItems } = this.props; const { vcs, syncItems } = this.props;
try { try {
const delta = await vcs.checkout(syncItems, branch); const delta = await vcs.checkout(syncItems, branch);
if (branch === DEFAULT_BRANCH_NAME) {
const { historyCount } = this.state;
const defaultBranchHistoryCount = await vcs.getHistoryCount(DEFAULT_BRANCH_NAME);
// If the default branch has no snapshots, but the current branch does
// It will result in the workspace getting deleted
// So we filter out the workspace from the delta to prevent this
if (!defaultBranchHistoryCount && historyCount) {
delta.remove = delta.remove.filter(e => e.type !== models.workspace.type);
}
}
await db.batchModifyDocs(delta); await db.batchModifyDocs(delta);
} catch (err) { } catch (err) {
showAlert({ showAlert({

View File

@ -152,15 +152,32 @@ class WorkspaceDropdown extends React.PureComponent<Props, State> {
await newVCS.removeProjectsForRoot(project.rootDocumentId); await newVCS.removeProjectsForRoot(project.rootDocumentId);
// Set project, checkout master, and pull // Set project, checkout master, and pull
await newVCS.setProject(project); const defaultBranch = 'master';
await newVCS.checkout([], 'master');
await newVCS.pull([]); // There won't be any existing docs since it's a new pull
const flushId = await db.bufferChanges(); await newVCS.setProject(project);
for (const doc of await newVCS.allDocuments()) { await newVCS.checkout([], defaultBranch);
await db.upsert(doc);
const remoteBranches = await newVCS.getRemoteBranches();
const defaultBranchMissing = !remoteBranches.includes(defaultBranch);
// The default branch does not exist, so we create it and the workspace locally
if (defaultBranchMissing) {
const workspace: Workspace = await models.initModel(models.workspace.type, {
_id: project.rootDocumentId,
name: project.name,
});
await db.upsert(workspace);
} else {
await newVCS.pull([]); // There won't be any existing docs since it's a new pull
const flushId = await db.bufferChanges();
for (const doc of await newVCS.allDocuments()) {
await db.upsert(doc);
}
await db.flushChanges(flushId);
} }
await db.flushChanges(flushId);
await this._refreshRemoteWorkspaces(); await this._refreshRemoteWorkspaces();
} catch (err) { } catch (err) {
this._dropdown && this._dropdown.hide(); this._dropdown && this._dropdown.hide();

View File

@ -25,9 +25,12 @@ class SyncMergeModal extends React.PureComponent<Props, State> {
modal: ?Modal; modal: ?Modal;
_handleDone: (Array<MergeConflict>) => void; _handleDone: (Array<MergeConflict>) => void;
state = { constructor(props: Props) {
conflicts: [], super(props);
}; this.state = {
conflicts: [],
};
}
_setModalRef(n: ?Modal) { _setModalRef(n: ?Modal) {
this.modal = n; this.modal = n;
@ -90,7 +93,6 @@ class SyncMergeModal extends React.PureComponent<Props, State> {
Mine{' '} Mine{' '}
<input <input
type="radio" type="radio"
name="choice"
value={conflict.mineBlob} value={conflict.mineBlob}
checked={conflict.choose === conflict.mineBlob} checked={conflict.choose === conflict.mineBlob}
onChange={e => this._handleToggleSelect(conflict.key, e)} onChange={e => this._handleToggleSelect(conflict.key, e)}
@ -100,7 +102,6 @@ class SyncMergeModal extends React.PureComponent<Props, State> {
Theirs{' '} Theirs{' '}
<input <input
type="radio" type="radio"
name="choice"
value={conflict.theirsBlob} value={conflict.theirsBlob}
checked={conflict.choose === conflict.theirsBlob} checked={conflict.choose === conflict.theirsBlob}
onChange={e => this._handleToggleSelect(conflict.key, e)} onChange={e => this._handleToggleSelect(conflict.key, e)}

View File

@ -72,6 +72,14 @@ class SyncShareModal extends React.PureComponent<Props, State> {
this.setState({ loading: true }); this.setState({ loading: true });
this.modal && this.modal.show(); this.modal && this.modal.show();
if (!vcs.hasProject()) {
this.setState({
error: 'Please set up sync to be able to share the workspace',
loading: false,
});
return;
}
let results; let results;
try { try {
results = await Promise.all([vcs.teams(), vcs.projectTeams()]); results = await Promise.all([vcs.teams(), vcs.projectTeams()]);