Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "[eas-cli] Use Git to pack Git projects" #2870

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/eas-cli/src/build/__tests__/configure-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe(ensureProjectConfiguredAsync, () => {
}),
});
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeTruthy();
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
const vcsClientMock = jest.mocked(new GitClient());
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
Expand All @@ -60,7 +60,7 @@ describe(ensureProjectConfiguredAsync, () => {
});
});
await expect(fs.pathExists(EasJsonAccessor.formatEasJsonPath('.'))).resolves.toBeFalsy();
const vcsClientMock = jest.mocked(new GitClient({ requireCommit: false }));
const vcsClientMock = jest.mocked(new GitClient());
vcsClientMock.showChangedFilesAsync.mockImplementation(async () => {});
vcsClientMock.isCommitRequiredAsync.mockImplementation(async () => false);
vcsClientMock.trackFileAsync.mockImplementation(async () => {});
Expand Down
11 changes: 3 additions & 8 deletions packages/eas-cli/src/commands/build/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
import EasCommand from '../../commandUtils/EasCommand';
import { RequestedPlatform } from '../../platform';
import { enableJsonOutput } from '../../utils/json';
import GitClient from '../../vcs/clients/git';
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
import NoVcsClient from '../../vcs/clients/noVcs';

/**
* This command will be run on the EAS Build workers, when building
Expand Down Expand Up @@ -63,16 +64,10 @@ export default class BuildInternal extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildInternal, {
nonInteractive: true,
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
withServerSideEnvironment: null,
});

if (vcsClient instanceof GitClient) {
// `build:internal` is run on EAS workers and the repo may have been changed
// by pre-install hooks or other scripts. We don't want to require committing changes
// to continue the build.
vcsClient.requireCommit = false;
}

await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);

await runBuildAndSubmitAsync({
Expand Down
5 changes: 1 addition & 4 deletions packages/eas-cli/src/commands/project/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,7 @@ export default class Onboarding extends EasCommand {
cloneMethod,
});

const vcsClient = new GitClient({
maybeCwdOverride: finalTargetProjectDirectory,
requireCommit: false,
});
const vcsClient = new GitClient(finalTargetProjectDirectory);
if (!app.githubRepository) {
await fs.remove(path.join(finalTargetProjectDirectory, '.git'));
await runCommandAsync({
Expand Down
11 changes: 3 additions & 8 deletions packages/eas-cli/src/commands/submit/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import AndroidSubmitCommand from '../../submit/android/AndroidSubmitCommand';
import { SubmissionContext, createSubmissionContextAsync } from '../../submit/context';
import IosSubmitCommand from '../../submit/ios/IosSubmitCommand';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import GitClient from '../../vcs/clients/git';
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
import NoVcsClient from '../../vcs/clients/noVcs';

/**
* This command will be run on the EAS workers.
Expand Down Expand Up @@ -64,16 +65,10 @@ export default class SubmitInternal extends EasCommand {
vcsClient,
} = await this.getContextAsync(SubmitInternal, {
nonInteractive: true,
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
withServerSideEnvironment: null,
});

if (vcsClient instanceof GitClient) {
// `build:internal` is run on EAS workers and the repo may have been changed
// by pre-install hooks or other scripts. We don't want to require committing changes
// to continue the build.
vcsClient.requireCommit = false;
}

const submissionProfile = await EasJsonUtils.getSubmitProfileAsync(
EasJsonAccessor.fromProjectPath(projectDir),
flags.platform,
Expand Down
159 changes: 32 additions & 127 deletions packages/eas-cli/src/vcs/clients/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as PackageManagerUtils from '@expo/package-manager';
import spawnAsync from '@expo/spawn-async';
import { Errors } from '@oclif/core';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';

import Log, { learnMore } from '../../log';
Expand All @@ -15,17 +14,11 @@ import {
gitStatusAsync,
isGitInstalledAsync,
} from '../git';
import { EASIGNORE_FILENAME, Ignore, makeShallowCopyAsync } from '../local';
import { Client } from '../vcs';

export default class GitClient extends Client {
private readonly maybeCwdOverride?: string;
public requireCommit: boolean;

constructor(options: { maybeCwdOverride?: string; requireCommit: boolean }) {
constructor(private readonly maybeCwdOverride?: string) {
super();
this.maybeCwdOverride = options.maybeCwdOverride;
this.requireCommit = options.requireCommit;
}

public override async ensureRepoExistsAsync(): Promise<void> {
Expand Down Expand Up @@ -126,6 +119,10 @@ export default class GitClient extends Client {
}
}

public override async isCommitRequiredAsync(): Promise<boolean> {
return await this.hasUncommittedChangesAsync();
}

public override async showChangedFilesAsync(): Promise<void> {
const gitStatusOutput = await gitStatusAsync({
showUntracked: true,
Expand All @@ -147,80 +144,50 @@ export default class GitClient extends Client {
).stdout.trim();
}

public override async isCommitRequiredAsync(): Promise<boolean> {
if (!this.requireCommit) {
return false;
}

return await this.hasUncommittedChangesAsync();
}

public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
if (await this.isCommitRequiredAsync()) {
if (await this.hasUncommittedChangesAsync()) {
// it should already be checked before this function is called, but in case it wasn't
// we want to ensure that any changes were introduced by call to `setGitCaseSensitivityAsync`
throw new Error('You have some uncommitted changes in your repository.');
}

const rootPath = await this.getRootPathAsync();

let gitRepoUri;
if (process.platform === 'win32') {
// getRootDirectoryAsync() will return C:/path/to/repo on Windows and path
// prefix should be file:///
gitRepoUri = `file:///${rootPath}`;
gitRepoUri = `file:///${await this.getRootPathAsync()}`;
} else {
// getRootDirectoryAsync() will /path/to/repo, and path prefix should be
// file:/// so only file:// needs to be prepended
gitRepoUri = `file://${rootPath}`;
gitRepoUri = `file://${await this.getRootPathAsync()}`;
}

await assertEnablingGitCaseSensitivityDoesNotCauseNewUncommittedChangesAsync(rootPath);

const isCaseSensitive = await isGitCaseSensitiveAsync(rootPath);
const isCaseSensitive = await isGitCaseSensitiveAsync(this.maybeCwdOverride);
await setGitCaseSensitivityAsync(true, this.maybeCwdOverride);
try {
await setGitCaseSensitivityAsync(true, rootPath);
if (await this.hasUncommittedChangesAsync()) {
Log.error('Detected inconsistent filename casing between your local filesystem and git.');
Log.error('This will likely cause your build to fail. Impacted files:');
await spawnAsync('git', ['status', '--short'], {
stdio: 'inherit',
cwd: this.maybeCwdOverride,
});
Log.newLine();
Log.error(
`Error: Resolve filename casing inconsistencies before proceeding. ${learnMore(
'https://expo.fyi/macos-ignorecase'
)}`
);
throw new Error('You have some uncommitted changes in your repository.');
}
await spawnAsync(
'git',
['clone', '--no-hardlinks', '--depth', '1', gitRepoUri, destinationPath],
{ cwd: rootPath }
{
cwd: this.maybeCwdOverride,
}
);

const sourceEasignorePath = path.join(rootPath, EASIGNORE_FILENAME);
if (await fs.exists(sourceEasignorePath)) {
const cachedFilesWeShouldHaveIgnored = (
await spawnAsync(
'git',
[
'ls-files',
'--exclude-from',
sourceEasignorePath,
// `--ignored --cached` makes git print files that should be
// ignored by rules from `--exclude-from`, but instead are currently cached.
'--ignored',
'--cached',
// separates file names with null characters
'-z',
],
{ cwd: destinationPath }
)
).stdout
.split('\0')
// ls-files' output is terminated by a null character
.filter(file => file !== '');

await Promise.all(
cachedFilesWeShouldHaveIgnored.map(file => fs.rm(path.join(destinationPath, file)))
);
}
} finally {
await setGitCaseSensitivityAsync(isCaseSensitive, rootPath);
await setGitCaseSensitivityAsync(isCaseSensitive, this.maybeCwdOverride);
}

// After we create the shallow Git copy, we copy the files
// again. This way we include the changed and untracked files
// (`git clone` only copies the committed changes).
await makeShallowCopyAsync(rootPath, destinationPath);
}

public override async getCommitHashAsync(): Promise<string | undefined> {
Expand Down Expand Up @@ -285,24 +252,10 @@ export default class GitClient extends Client {
}

public override async isFileIgnoredAsync(filePath: string): Promise<boolean> {
const rootPath = await this.getRootPathAsync();
const easIgnorePath = path.join(rootPath, EASIGNORE_FILENAME);
if (await fs.exists(easIgnorePath)) {
const ignore = new Ignore(rootPath);
const wouldNotBeCopiedToClone = ignore.ignores(filePath);
const wouldBeDeletedFromClone =
(
await spawnAsync(
'git',
['ls-files', '--exclude-from', easIgnorePath, '--ignored', '--cached', filePath],
{ cwd: rootPath }
)
).stdout.trim() !== '';
return wouldNotBeCopiedToClone && wouldBeDeletedFromClone;
}

try {
await spawnAsync('git', ['check-ignore', '-q', filePath], { cwd: rootPath });
await spawnAsync('git', ['check-ignore', '-q', filePath], {
cwd: this.maybeCwdOverride ?? path.normalize(await this.getRootPathAsync()),
});
return true;
} catch {
return false;
Expand Down Expand Up @@ -433,51 +386,3 @@ async function setGitCaseSensitivityAsync(
});
}
}

async function assertEnablingGitCaseSensitivityDoesNotCauseNewUncommittedChangesAsync(
cwd: string
): Promise<void> {
// Remember uncommited changes before case sensitivity change
// for later comparison so we log to the user only the files
// that were marked as changed after the case sensitivity change.
const uncommittedChangesBeforeCaseSensitivityChange = await gitStatusAsync({
showUntracked: true,
cwd,
});

const isCaseSensitive = await isGitCaseSensitiveAsync(cwd);
await setGitCaseSensitivityAsync(true, cwd);
try {
const uncommitedChangesAfterCaseSensitivityChange = await gitStatusAsync({
showUntracked: true,
cwd,
});

if (
uncommitedChangesAfterCaseSensitivityChange !== uncommittedChangesBeforeCaseSensitivityChange
) {
const baseUncommitedChangesSet = new Set(
uncommittedChangesBeforeCaseSensitivityChange.split('\n')
);

const errorMessage = [
'Detected inconsistent filename casing between your local filesystem and git.',
'This will likely cause your job to fail. Impacted files:',
...uncommitedChangesAfterCaseSensitivityChange.split('\n').flatMap(changedFile => {
// This file was changed before the case sensitivity change too.
if (baseUncommitedChangesSet.has(changedFile)) {
return [];
}
return [changedFile];
}),
`Resolve filename casing inconsistencies before proceeding. ${learnMore(
'https://expo.fyi/macos-ignorecase'
)}`,
];

throw new Error(errorMessage.join('\n'));
}
} finally {
await setGitCaseSensitivityAsync(isCaseSensitive, cwd);
}
}
45 changes: 45 additions & 0 deletions packages/eas-cli/src/vcs/clients/gitNoCommit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import spawnAsync from '@expo/spawn-async';
import chalk from 'chalk';
import path from 'path';

import GitClient from './git';
import Log from '../../log';
import { Ignore, makeShallowCopyAsync } from '../local';

export default class GitNoCommitClient extends GitClient {
public override async isCommitRequiredAsync(): Promise<boolean> {
return false;
}

public override async getRootPathAsync(): Promise<string> {
return (await spawnAsync('git', ['rev-parse', '--show-toplevel'])).stdout.trim();
}

public override async makeShallowCopyAsync(destinationPath: string): Promise<void> {
// normalize converts C:/some/path to C:\some\path on windows
const srcPath = path.normalize(await this.getRootPathAsync());
await makeShallowCopyAsync(srcPath, destinationPath);
}

public override async isFileIgnoredAsync(filePath: string): Promise<boolean> {
// normalize converts C:/some/path to C:\some\path on windows
const rootPath = path.normalize(await this.getRootPathAsync());
const ignore = new Ignore(rootPath);
await ignore.initIgnoreAsync();
return ignore.ignores(filePath);
}

public override async trackFileAsync(file: string): Promise<void> {
try {
await super.trackFileAsync(file);
} catch {
// In the no commit workflow it doesn't matter if we fail to track changes,
// so we can ignore if this throws an exception
Log.warn(
`Unable to track ${chalk.bold(path.basename(file))} in Git. Proceeding without tracking.`
);
Log.warn(` Reason: the command ${chalk.bold(`"git add ${file}"`)} exited with an error.`);
Log.newLine();
}
}
}
6 changes: 5 additions & 1 deletion packages/eas-cli/src/vcs/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import chalk from 'chalk';

import GitClient from './clients/git';
import GitNoCommitClient from './clients/gitNoCommit';
import NoVcsClient from './clients/noVcs';
import { Client } from './vcs';

Expand All @@ -22,5 +23,8 @@ export function resolveVcsClient(requireCommit: boolean = false): Client {
}
return new NoVcsClient();
}
return new GitClient({ requireCommit });
if (requireCommit) {
return new GitClient();
}
return new GitNoCommitClient();
}
2 changes: 1 addition & 1 deletion packages/eas-cli/src/vcs/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs-extra';
import createIgnore, { Ignore as SingleFileIgnore } from 'ignore';
import path from 'path';

export const EASIGNORE_FILENAME = '.easignore';
const EASIGNORE_FILENAME = '.easignore';
const GITIGNORE_FILENAME = '.gitignore';

const DEFAULT_IGNORE = `
Expand Down
Loading