Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Use null objects for Repositories #586

Closed
wants to merge 1 commit into from
Closed
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
141 changes: 141 additions & 0 deletions lib/models/repository.js
Original file line number Diff line number Diff line change
@@ -31,6 +31,146 @@ export class CommitError extends Error {
}
}

class BaseRepository {
static from(repository) {
return new this(
repository.workingDirectoryPath,
repository.git,
repository.emitter,
repository.destroyed,
);
}

constructor(workingDirectoryPath, gitStrategy, emitter, destroyed) {
this.workingDirectoryPath = workingDirectoryPath;
this.git = gitStrategy;
this.emitter = emitter || new Emitter();
this.destroyed = destroyed;
}

isLoading() {
return false;
}

isOpen() {
return false;
}

isAbsent() {
return false;
}

isDestroyed() {
return this.destroyed;
}

destroy() {
if (this.isDestroyed()) {
return;
}

this.destroyed = true;
this.emitter.emit('did-destroy');
this.emitter.dispose();
this.windowFocusDisposable.dispose();
}

onDidDestroy(callback) {
return this.emitter.on('did-destroy', callback);
}

onDidUpdate(callback) {
return this.emitter.on('did-update', callback);
}

didUpdate() {
this.emitter.emit('did-update');
}

getWorkingDirectoryPath() {
return this.workingDirectoryPath;
}

getGitDirectoryPath() {
return path.join(this.getWorkingDirectoryPath(), '.git');
}

refresh() {
// Do nothing.
}
}

class LoadingRepository extends BaseRepository {
isLoading() {
return true;
}

async open() {
if (await this.git.isGitRepository()) {
const openRepo = OpenRepository.from(this);
await openRepo.updateDiscardHistory();
return openRepo;
} else {
return AbsentRepository.from(this);
}
}
}

class OpenRepository extends BaseRepository {
constructor(workingDirectoryPath, gitStrategy, emitter, destroyed) {
super(workingDirectoryPath, gitStrategy, emitter, destroyed);

this.stagedFilePatchesByPath = new Map();
this.stagedFilePatchesSinceParentCommitByPath = new Map();
this.unstagedFilePatchesByPath = new Map();

this.discardHistory = new DiscardHistory(
this.createBlob,
this.expandBlobToFile,
this.mergeFile,
{maxHistoryLength: 60},
);

const onWindowFocus = () => this.updateDiscardHistory();
window.addEventListener('focus', onWindowFocus);
this.windowFocusDisposable = new Disposable(() => window.removeEventListener('focus', onWindowFocus));
}

isOpen() {
return true;
}

destroy() {
super();

this.windowFocusDisposable.dispose();
}

refresh() {
this.clearCachedFilePatches();
this.fileStatusesPromise = null;
this.stagedChangesSinceParentCommitPromise = null;
this.didUpdate();
}

fetchStatusesForChangedFiles() {
return this.git.getStatusesForChangedFiles();
}

getStatusesForChangedFiles() {
if (!this.fileStatusesPromise) {
this.fileStatusesPromise = this.fetchStatusesForChangedFiles();
}
return this.fileStatusesPromise;
}
}

class AbsentRepository extends BaseRepository {
isAbsent() {
return true;
}
}

export default class Repository {
static async open(workingDirectoryPath, gitStrategy) {
const strategy = gitStrategy || CompositeGitStrategy.create(workingDirectoryPath);
@@ -451,6 +591,7 @@ export default class Repository {
await this.setConfig('atomGithub.historySha', historySha);
}

@autobind
async updateDiscardHistory() {
const historySha = await this.git.getConfig('atomGithub.historySha');
const history = historySha ? JSON.parse(await this.git.getBlobContents(historySha)) : {};
34 changes: 33 additions & 1 deletion test/models/repository.test.js
Original file line number Diff line number Diff line change
@@ -40,10 +40,42 @@ describe('Repository', function() {
});
});

describe.only('repository state', function() {
it('transitions from loading to open on a workdir with a git repository', async function() {
const workdirPath = await cloneRepository('three-files');

const loading = Repository.forPath(workdirPath);
assert.isTrue(loading.isLoading());
assert.isFalse(loading.isOpen());
assert.isFalse(loading.isAbsent());

const loaded = await loading.open();
assert.isFalse(loaded.isLoading());
assert.isTrue(loaded.isOpen());
assert.isFalse(loaded.isAbsent());
});

it('transitions from loading to absent on a workdir without a git repository', async function() {
const repositorylessPath = fs.realpathSync(temp.mkdirSync());

const loading = Repository.forPath(repositorylessPath);
assert.isTrue(loading.isLoading());
assert.isFalse(loading.isOpen());
assert.isFalse(loading.isAbsent());

const absent = await loading.open();
assert.isFalse(absent.isLoading());
assert.isFalse(absent.isOpen());
assert.isTrue(absent.isAbsent());
});
});

describe('init', function() {
it('creates a repository in the given dir and returns the repository', async function() {
const soonToBeRepositoryPath = fs.realpathSync(temp.mkdirSync());
const repo = await Repository.init(soonToBeRepositoryPath);
const nullRepo = await Repository.open(soonToBeRepositoryPath);

const repo = nullRepo.init(soonToBeRepositoryPath);
assert.isTrue(await repo.isGitRepository());
assert.equal(repo.getWorkingDirectoryPath(), soonToBeRepositoryPath);
});