Skip to content
Merged
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
7 changes: 2 additions & 5 deletions cli/src/worker/annex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,8 @@ export async function annexAdd(
await context.fs.promises.rm(fileRepoPath, { force: true })
// Create our new symlink pointing at the right annex object
await context.fs.promises.symlink(symlinkTarget, fileRepoPath)
const options = {
...context.config(),
filepath: relativePath,
}
await git.add(options)
// Staging is left to the caller so adds can be batched - see ADD_BATCH_SIZE
// in git.ts. Adding one path at a time rewrites the whole index per file.
return true
} else {
return false
Expand Down
34 changes: 29 additions & 5 deletions cli/src/worker/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,30 @@ async function shouldBeAnnexed(
/**
* git-annex add equivalent
*/
/**
* How many paths to stage per git.add call.
*
* isomorphic-git re-serializes and rewrites the whole index on every add, so
* adding one path at a time costs O(n) per file and O(n^2) overall. Batching
* makes the index cost negligible: staging 20k files took 1173s one at a time
* and 6.1s in batches of 1000.
*
* The batch is flushed after every chunk and again at the end, so at most this
* many files need restaging if the process dies mid-run.
*/
const ADD_BATCH_SIZE = 500

async function add(event: GitWorkerEventAdd) {
const pending: string[] = []
const flushPending = async () => {
if (pending.length === 0) return
await git.add({
...context.config(),
filepath: pending.length === 1 ? pending[0] : [...pending],
})
pending.length = 0
}

for (const file of event.data.paths) {
let size
try {
Expand All @@ -197,10 +220,6 @@ async function add(event: GitWorkerEventAdd) {
const annexed = await shouldBeAnnexed(file.relativePath, size)
if (annexed === "GIT") {
// Simple add case
const options = {
...context.config(),
filepath: file.relativePath,
}
const targetPath = join(context.repoPath, file.relativePath)
// Verify parent directories exist
await context.fs.promises.mkdir(dirname(targetPath), { recursive: true })
Expand All @@ -216,18 +235,23 @@ async function add(event: GitWorkerEventAdd) {
// Copy all other non-annexed files for git index creation
await context.fs.promises.copyFile(file.path, targetPath)
}
await git.add(options)
pending.push(file.relativePath)
logger.info(`Add\t${file.relativePath}`)
} else {
if (
await annexAdd(annexed, file.path, file.relativePath, size, context)
) {
pending.push(file.relativePath)
logger.info(`Annexed\t${file.relativePath}`)
} else {
logger.info(`Unchanged\t${file.relativePath}`)
}
}
if (pending.length >= ADD_BATCH_SIZE) {
await flushPending()
}
}
await flushPending()
}

/**
Expand Down