Skip to content

Commit

Permalink
WIP - #335 added global garbage collection for git objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
tegefaulkes committed Mar 8, 2022
1 parent dc39fd2 commit 39439a5
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 5 deletions.
68 changes: 67 additions & 1 deletion src/vaults/VaultInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ class VaultInternal {
// This ensures that any uncommitted state is dropped
await this.cleanWorkingDirectory();
// Do global GC operation
// FIXME
await this.garbageCollectGitObjects();

// Setting dirty back to false
await this.db.put(
Expand Down Expand Up @@ -983,6 +983,72 @@ class VaultInternal {
force: true,
});
}

/**
* Deletes any git objects that can't be reached from the canonicalBranch.
*/
protected async garbageCollectGitObjects() {
// To garbage collect the git objects,
// we need to walk all objects connected to the master branch
// and delete the object files that are not touched by this walk
const touchedOids = {};
const masterRef = await git.resolveRef({
fs: this.efs,
dir: this.vaultDataDir,
gitdir: this.vaultGitDir,
ref: vaultsUtils.canonicalBranch,
});
const queuedOids: string[] = [masterRef];
while (queuedOids.length > 0) {
const currentOid = queuedOids.shift()!;
if (touchedOids[currentOid] === null) continue;
const result = await git.readObject({
fs: this.efs,
dir: this.vaultDataDir,
gitdir: this.vaultGitDir,
oid: currentOid,
});
touchedOids[result.oid] = result.type;
if (result.format !== 'parsed') continue;
switch (result.type) {
case 'commit':
{
const object = result.object;
queuedOids.push(...object.parent);
queuedOids.push(object.tree);
}
break;
case 'tree':
{
const object = result.object;
for (const item of object) {
touchedOids[item.oid] = item.type;
}
}
break;
default: {
never();
}
}
}
// Walking all objects
const objectPath = path.join(this.vaultGitDir, 'objects');
const buckets = (await this.efs.readdir(objectPath)).filter((item) => {
return item !== 'info' && item !== 'pack';
});
for (const bucket of buckets) {
const bucketPath = path.join(objectPath, bucket.toString());
const oids = await this.efs.readdir(bucketPath);
for (const shortOid of oids) {
const oidPath = path.join(bucketPath, shortOid.toString());
const oid = bucket.toString() + shortOid.toString();
if (touchedOids[oid] === undefined) {
// Removing unused objects
await this.efs.unlink(oidPath);
}
}
}
}
}

export default VaultInternal;
59 changes: 55 additions & 4 deletions tests/vaults/VaultInternal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('VaultInternal', () => {
} as KeyManager;
const secret1 = { name: 'secret-1', content: 'secret-content-1' };
const secret2 = { name: 'secret-2', content: 'secret-content-2' };
const secret3 = { name: 'secret-3', content: 'secret-content-3' };

// Const shortLog = async (vault: VaultInternal, ...args) => {
// const log = await vault.log(...args)
Expand Down Expand Up @@ -626,10 +627,7 @@ describe('VaultInternal', () => {
// Checking if errant commits were cleaned up
await expect(vault.version(newRef1)).rejects.toThrow();
await expect(vault.version(newRef2)).rejects.toThrow();

throw Error();
});
test.todo('test writeG committing');
test('commit added if mutation in writeG', async () => {
const commit = (await vault.log())[0].commitId;
const gen = vault.writeG(async function* (efs): AsyncGenerator {
Expand Down Expand Up @@ -684,7 +682,60 @@ describe('VaultInternal', () => {
expect(log).toHaveLength(2);
expect(log[0].commitId).toStrictEqual(commit);
});

test('garbage collection', async () => {
await vault.writeF(async (efs) => {
await efs.writeFile(secret1.name, secret1.content);
});
await vault.writeF(async (efs) => {
await efs.writeFile(secret2.name, secret2.content);
});
await vault.writeF(async (efs) => {
await efs.writeFile(secret3.name, secret3.content);
});
// @ts-ignore: kidnap efs
const vaultEfs = vault.efs;
// @ts-ignore: kidnap efs
const vaultEfsData = vault.efsVault;
const quickCommit = async (ref: string, secret: string) => {
await vaultEfsData.writeFile(secret, secret);
await git.add({
fs: vaultEfs,
dir: vault.vaultDataDir,
gitdir: vault.vaultGitDir,
filepath: secret,
});
return await git.commit({
fs: vaultEfs,
dir: vault.vaultDataDir,
gitdir: vault.vaultGitDir,
author: {
name: 'test',
email: 'test',
},
message: 'test',
ref: ref,
});
};
const log = await vault.log();
let num = 5;
const refs: string[] = [];
for (const logElement of log) {
refs.push(await quickCommit(logElement.commitId, `secret-${num++}`));
}
// @ts-ignore
await vault.garbageCollectGitObjects();

for (const ref of refs) {
await expect(
git.checkout({
fs: vaultEfs,
dir: vault.vaultDataDir,
gitdir: vault.vaultGitDir,
ref,
}),
).rejects.toThrow(git.Errors.CommitNotFetchedError);
}
});
// Locking tests
const waitDelay = 200;
test('writeF respects read and write locking', async () => {
Expand Down

0 comments on commit 39439a5

Please sign in to comment.