Skip to content

Commit

Permalink
feat: do not use a git stash for better performance
Browse files Browse the repository at this point in the history
  • Loading branch information
iiroj committed Oct 2, 2021
1 parent 29d4356 commit ff0cc0d
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 99 deletions.
90 changes: 24 additions & 66 deletions lib/gitWorkflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const {
GitError,
RestoreOriginalStateError,
ApplyEmptyCommitError,
GetBackupStashError,
HideUnstagedChangesError,
RestoreMergeStatusError,
RestoreUnstagedChangesError,
Expand Down Expand Up @@ -41,8 +40,6 @@ const processRenames = (files, includeRenameFrom = true) =>
return flattened
}, [])

const STASH = 'lint-staged automatic backup'

const PATCH_UNSTAGED = 'lint-staged_unstaged.patch'

const GIT_DIFF_ARGS = [
Expand Down Expand Up @@ -90,19 +87,6 @@ class GitWorkflow {
return path.resolve(this.gitConfigDir, `./${filename}`)
}

/**
* Get name of backup stash
*/
async getBackupStash(ctx) {
const stashes = await this.execGit(['stash', 'list'])
const index = stashes.split('\n').findIndex((line) => line.includes(STASH))
if (index === -1) {
ctx.errors.add(GetBackupStashError)
throw new Error('lint-staged automatic backup is missing!')
}
return `refs/stash@{${index}}`
}

/**
* Get a list of unstaged deleted files
*/
Expand Down Expand Up @@ -184,7 +168,7 @@ class GitWorkflow {
}

/**
* Create a diff of partially staged files and backup stash if enabled.
* Create a diff of partially staged files.
*/
async prepare(ctx) {
try {
Expand All @@ -203,25 +187,6 @@ class GitWorkflow {
ctx.hasPartiallyStagedFiles = false
}

/**
* If backup stash should be skipped, no need to continue
*/
if (!ctx.shouldBackup) return

// When backup is enabled, the revert will clear ongoing merge status.
await this.backupMergeStatus()

// Get a list of unstaged deleted files, because certain bugs might cause them to reappear:
// - in git versions =< 2.13.0 the `git stash --keep-index` option resurrects deleted files
// - git stash can't infer RD or MD states correctly, and will lose the deletion
this.deletedFiles = await this.getDeletedFiles()

// Save stash of all staged files.
// The `stash create` command creates a dangling commit without removing any files,
// and `stash store` saves it as an actual stash.
const hash = await this.execGit(['stash', 'create'])
await this.execGit(['stash', 'store', '--quiet', '--message', STASH, hash])

debug('Done backing up original state!')
} catch (error) {
handleError(error, ctx)
Expand All @@ -245,9 +210,26 @@ class GitWorkflow {
}

/**
* Applies back task modifications, and unstaged changes hidden in the stash.
* In case of a merge-conflict retry with 3-way merge.
* Restore original HEAD state for partially staged files in case of errors
*/
async restorePartiallyStagedFiles(ctx) {
try {
debug('Restoring original state...')

// Flush all unstaged changes. Since they were diffed before
// running tasks, these will include only changes generated by tasks
await this.hideUnstagedChanges(ctx)

const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
await this.execGit(['apply', ...GIT_APPLY_ARGS, unstagedPatch])

debug('Done restoring original state!')
} catch (error) {
handleError(error, ctx, RestoreOriginalStateError)
}
}

/** Add all task modifications to index for files that were staged before running. */
async applyModifications(ctx) {
debug('Adding task modifications to index...')

Expand Down Expand Up @@ -299,37 +281,13 @@ class GitWorkflow {
}

/**
* Restore original HEAD state in case of errors
*/
async restoreOriginalState(ctx) {
try {
debug('Restoring original state...')
await this.execGit(['reset', '--hard', 'HEAD'])
await this.execGit(['stash', 'apply', '--quiet', '--index', await this.getBackupStash(ctx)])

// Restore meta information about ongoing git merge
await this.restoreMergeStatus(ctx)

// If stashing resurrected deleted files, clean them out
await Promise.all(this.deletedFiles.map((file) => unlink(file)))

// Clean out patch
await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))

debug('Done restoring original state!')
} catch (error) {
handleError(error, ctx, RestoreOriginalStateError)
}
}

/**
* Drop the created stashes after everything has run
* Drop the created diff file after everything has run
*/
async cleanup(ctx) {
try {
debug('Dropping backup stash...')
await this.execGit(['stash', 'drop', '--quiet', await this.getBackupStash(ctx)])
debug('Done dropping backup stash!')
debug('Removing temp files...')
await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))
debug('Done removing temp files!')
} catch (error) {
handleError(error, ctx)
}
Expand Down
15 changes: 3 additions & 12 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ const { cosmiconfig } = require('cosmiconfig')
const debugLog = require('debug')('lint-staged')
const stringifyObject = require('stringify-object')

const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./messages')
const { PREVENTED_EMPTY_COMMIT, GIT_ERROR } = require('./messages')
const printTaskOutput = require('./printTaskOutput')
const runAll = require('./runAll')
const {
ApplyEmptyCommitError,
ConfigNotFoundError,
GetBackupStashError,
GitError,
} = require('./symbols')
const { ApplyEmptyCommitError, ConfigNotFoundError, GitError } = require('./symbols')
const validateConfig = require('./validateConfig')
const validateOptions = require('./validateOptions')

Expand Down Expand Up @@ -140,12 +135,8 @@ const lintStaged = async (
const { ctx } = runAllError
if (ctx.errors.has(ApplyEmptyCommitError)) {
logger.warn(PREVENTED_EMPTY_COMMIT)
} else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
} else if (ctx.errors.has(GitError)) {
logger.error(GIT_ERROR)
if (ctx.shouldBackup) {
// No sense to show this if the backup stash itself is missing.
logger.error(RESTORE_STASH_EXAMPLE)
}
}

printTaskOutput(ctx, logger)
Expand Down
8 changes: 0 additions & 8 deletions lib/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,6 @@ const PREVENTED_EMPTY_COMMIT = `
Use the --allow-empty option to continue, or check your task configuration`)}
`

const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored from a git stash:
> git stash list
stash@{0}: automatic lint-staged backup
> git stash apply --index stash@{0}
`

const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.'

module.exports = {
Expand All @@ -78,7 +71,6 @@ module.exports = {
NO_TASKS,
NOT_GIT_REPO,
PREVENTED_EMPTY_COMMIT,
RESTORE_STASH_EXAMPLE,
SKIPPED_GIT_ERROR,
skippingBackup,
TASK_ERROR,
Expand Down
12 changes: 6 additions & 6 deletions lib/runAll.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ const runAll = async (
enabled: hasPartiallyStagedFiles,
},
...listrTasks,
{
title: 'Reverting to original state because of errors...',
task: (ctx) => git.restorePartiallyStagedFiles(ctx),
enabled: restoreOriginalStateEnabled,
skip: restoreOriginalStateSkipped,
},
{
title: 'Applying modifications...',
task: (ctx) => git.applyModifications(ctx),
Expand All @@ -230,12 +236,6 @@ const runAll = async (
enabled: hasPartiallyStagedFiles,
skip: restoreUnstagedChangesSkipped,
},
{
title: 'Reverting to original state because of errors...',
task: (ctx) => git.restoreOriginalState(ctx),
enabled: restoreOriginalStateEnabled,
skip: restoreOriginalStateSkipped,
},
{
title: 'Cleaning up...',
task: (ctx) => git.cleanup(ctx),
Expand Down
6 changes: 1 addition & 5 deletions lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ const restoreUnstagedChangesSkipped = (ctx) => {
}
}

const restoreOriginalStateEnabled = (ctx) =>
ctx.shouldBackup &&
(ctx.errors.has(TaskError) ||
ctx.errors.has(ApplyEmptyCommitError) ||
ctx.errors.has(RestoreUnstagedChangesError))
const restoreOriginalStateEnabled = (ctx) => ctx.shouldBackup && ctx.errors.has(TaskError)

const restoreOriginalStateSkipped = (ctx) => {
// Should be skipped in case of unknown git errors
Expand Down
2 changes: 0 additions & 2 deletions lib/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const ApplyEmptyCommitError = Symbol('ApplyEmptyCommitError')
const ConfigNotFoundError = new Error('Config could not be found')
const GetBackupStashError = Symbol('GetBackupStashError')
const GetStagedFilesError = Symbol('GetStagedFilesError')
const GitError = Symbol('GitError')
const GitRepoError = Symbol('GitRepoError')
Expand All @@ -16,7 +15,6 @@ const TaskError = Symbol('TaskError')
module.exports = {
ApplyEmptyCommitError,
ConfigNotFoundError,
GetBackupStashError,
GetStagedFilesError,
GitError,
GitRepoError,
Expand Down

0 comments on commit ff0cc0d

Please sign in to comment.