Skip to content

Commit

Permalink
refactor: GitWorkflow is a class
Browse files Browse the repository at this point in the history
  • Loading branch information
iiroj committed Nov 14, 2019
1 parent 9b101cd commit 128631d
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 109 deletions.
181 changes: 90 additions & 91 deletions src/gitWorkflow.js
Expand Up @@ -6,104 +6,103 @@ const execGit = require('./execGit')

const STASH = 'lint-staged automatic backup'

let unstagedDiff = null

/**
* Get name of backup stash
*
* @param {Object} [options]
* @returns {Promise<Object>}
*/
async function getBackupStash(options) {
const stashes = await execGit(['stash', 'list'], options)
const index = stashes.split('\n').findIndex(line => line.includes(STASH))
return `stash@{${index}}`
}

/**
* Create backup stashes, one of everything and one of only staged changes
* Leves stages files in index for running tasks
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async function stashBackup(options) {
debug('Backing up original state...')

// Get stash of entire original state, including unstaged changes
// Keep index so that tasks only work on those files
await execGit(['stash', 'save', '--quiet', '--include-untracked', '--keep-index', STASH], options)

// Since only staged files are now present, get a diff of unstaged changes
// by comparing current index against original stash, but in reverse
const original = await getBackupStash(options)
unstagedDiff = await execGit(
['diff', '--unified=0', '--no-color', '--no-ext-diff', '-p', original, '-R'],
options
)
const gitApplyArgs = ['apply', '-v', '--whitespace=nowarn', '--recount', '--unidiff-zero']

debug('Done backing up original state!')
}
class GitWorkflow {
constructor(cwd) {
this.execGit = (args, options = {}) => execGit(args, { ...options, cwd })
this.unstagedDiff = null
}

const gitApplyArgs = ['apply', '-v', '--whitespace=nowarn', '--recount', '--unidiff-zero']
/**
* Get name of backup stash
*
* @param {Object} [options]
* @returns {Promise<Object>}
*/
async getBackupStash() {
const stashes = await this.execGit(['stash', 'list'])
const index = stashes.split('\n').findIndex(line => line.includes(STASH))
return `stash@{${index}}`
}

/**
* Resets everything and applies back unstaged and staged changes,
* possibly with modifications by tasks
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async function restoreUnstagedChanges(options) {
debug('Restoring unstaged changes...')
/**
* Create backup stashes, one of everything and one of only staged changes
* Leves stages files in index for running tasks
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async stashBackup() {
debug('Backing up original state...')
// Get stash of entire original state, including unstaged changes
// Keep index so that tasks only work on those files
await this.execGit(['stash', 'save', '--quiet', '--include-untracked', '--keep-index', STASH])
// Since only staged files are now present, get a diff of unstaged changes
// by comparing current index against original stash, but in reverse
const original = await this.getBackupStash()
this.unstagedDiff = await this.execGit([
'diff',
'--unified=0',
'--no-color',
'--no-ext-diff',
'-p',
original,
'-R'
])
debug('Done backing up original state!')
}

if (unstagedDiff) {
try {
await execGit(gitApplyArgs, { ...options, input: `${unstagedDiff}\n` })
} catch (error) {
debug('Error when restoring changes:')
debug(error)
debug('Retrying with 3-way merge')
// Retry with `--3way` if normal apply fails
await execGit([...gitApplyArgs, '--3way'], { ...options, input: `${unstagedDiff}\n` })
/**
* Resets everything and applies back unstaged and staged changes,
* possibly with modifications by tasks
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async restoreUnstagedChanges() {
debug('Restoring unstaged changes...')
if (this.unstagedDiff) {
try {
await this.execGit(gitApplyArgs, { input: `${this.unstagedDiff}\n` })
} catch (error) {
debug('Error when restoring changes:')
debug(error)
debug('Retrying with 3-way merge')
// Retry with `--3way` if normal apply fails
await this.execGit([...gitApplyArgs, '--3way'], { input: `${this.unstagedDiff}\n` })
}
}
debug('Done restoring unstaged changes!')
}

debug('Done restoring unstaged changes!')
}

/**
* Restore original HEAD state in case of errors
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async function restoreOriginalState(options) {
debug('Restoring original state...')
const original = await getBackupStash(options)
await execGit(['reset', '--hard', 'HEAD'], options)
await execGit(['stash', 'apply', '--quiet', '--index', original], options)
debug('Done restoring original state!')
}
/**
* Restore original HEAD state in case of errors
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async restoreOriginalState() {
debug('Restoring original state...')
const original = await this.getBackupStash()
await this.execGit(['reset', '--hard', 'HEAD'])
await this.execGit(['stash', 'apply', '--quiet', '--index', original])
debug('Done restoring original state!')
}

/**
* Drop the created stashes after everything has run
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async function dropBackup(options) {
debug('Dropping backup stash...')
const original = await getBackupStash(options)
await execGit(['stash', 'drop', '--quiet', original], options)
unstagedDiff = null
debug('Done dropping backup stash!')
/**
* Drop the created stashes after everything has run
*
* @param {Object} [options]
* @returns {Promise<void>}
*/
async dropBackup() {
debug('Dropping backup stash...')
const original = await this.getBackupStash()
await this.execGit(['stash', 'drop', '--quiet', original])
this.unstagedDiff = null
debug('Done dropping backup stash!')
}
}

module.exports = {
execGit,
stashBackup,
restoreUnstagedChanges,
restoreOriginalState,
dropBackup
}
module.exports = GitWorkflow
12 changes: 7 additions & 5 deletions src/runAll.js
Expand Up @@ -9,7 +9,7 @@ const symbols = require('log-symbols')

const generateTasks = require('./generateTasks')
const getStagedFiles = require('./getStagedFiles')
const git = require('./gitWorkflow')
const GitWorkflow = require('./gitWorkflow')
const makeCmdTasks = require('./makeCmdTasks')
const resolveGitDir = require('./resolveGitDir')

Expand Down Expand Up @@ -102,11 +102,13 @@ https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-com
return 'No tasks to run.'
}

const git = new GitWorkflow(gitDir)

return new Listr(
[
{
title: 'Preparing...',
task: () => git.stashBackup({ cwd: gitDir })
task: () => git.stashBackup()
},
{
title: 'Running tasks...',
Expand All @@ -115,13 +117,13 @@ https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-com
{
title: 'Restoring original state due to errors...',
enabled: ctx => ctx.hasErrors,
task: () => git.restoreOriginalState({ cwd: gitDir })
task: () => git.restoreOriginalState()
},
{
title: 'Cleaning up...',
task: async ctx => {
if (!ctx.hasErrors) await git.restoreUnstagedChanges({ cwd: gitDir })
await git.dropBackup({ cwd: gitDir })
if (!ctx.hasErrors) await git.restoreUnstagedChanges()
await git.dropBackup()
}
}
],
Expand Down
17 changes: 7 additions & 10 deletions test/__mocks__/gitWorkflow.js
@@ -1,11 +1,8 @@
const stashBackup = jest.fn().mockImplementation(() => Promise.resolve(null))
const restoreUnstagedChanges = jest.fn().mockImplementation(() => Promise.resolve(null))
const restoreOriginalState = jest.fn().mockImplementation(() => Promise.resolve(null))
const dropBackup = jest.fn().mockImplementation(() => Promise.resolve(null))

module.exports = {
stashBackup,
restoreUnstagedChanges,
restoreOriginalState,
dropBackup
const stub = {
stashBackup: jest.fn().mockImplementation(() => Promise.resolve()),
restoreUnstagedChanges: jest.fn().mockImplementation(() => Promise.resolve()),
restoreOriginalState: jest.fn().mockImplementation(() => Promise.resolve()),
dropBackup: jest.fn().mockImplementation(() => Promise.resolve())
}

module.exports = () => stub
4 changes: 1 addition & 3 deletions test/runAll.spec.js
Expand Up @@ -36,14 +36,11 @@ describe('runAll', () => {
})

it('should resolve the promise with no tasks', async () => {
expect.assertions(1)
const res = await runAll({ config: {} })

expect(res).toEqual('No tasks to run.')
})

it('should resolve the promise with no files', async () => {
expect.assertions(1)
await runAll({ config: { '*.js': ['echo "sample"'] } })
expect(console.printHistory()).toMatchSnapshot()
})
Expand All @@ -56,6 +53,7 @@ describe('runAll', () => {
} catch (err) {
console.log(err)
}

expect(console.printHistory()).toMatchSnapshot()
})

Expand Down

0 comments on commit 128631d

Please sign in to comment.