Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Git workflow #75

Merged
merged 139 commits into from Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from 109 commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
1cbd6dd
Use `git stash --keep-index` to enable linting and committing only st…
okonet Oct 7, 2016
fe2a5d2
Added gitWorkflow and basic tests
okonet Oct 10, 2016
c801b04
More work on tests + some refactoring
okonet Oct 10, 2016
23f300b
Added more tests + fixed some bugs
okonet Oct 10, 2016
763bc9a
Work with absolute and relative paths. Added tests.
okonet Oct 10, 2016
4bb557b
Switched to gitWorkflow
okonet Oct 10, 2016
479e5d1
Added test user name and email to try to fix Travis
okonet Oct 10, 2016
72596b3
Bumped version to 4.0.0-beta-1
okonet Oct 10, 2016
fba5ccd
Bumped version
okonet Oct 10, 2016
eff0daa
Extracted expect extension to a separate file. Added a failing test f…
okonet Oct 11, 2016
9fbdbc8
Switched to async/await for tests
okonet Oct 11, 2016
ce16b5d
Removed no-console eslint error
okonet Oct 20, 2016
b38618e
Some progress on git workflow.
okonet Oct 20, 2016
8b15f70
Merge branch 'master' into git-worflow
okonet Mar 14, 2017
d4bde33
tests: Migrate git workflow tests to Jest
okonet Mar 14, 2017
6bf42cd
chore: Switch to husky and fix dependencies
okonet Mar 14, 2017
bb6d983
chore: Add *.json to .lintstagedrc config
okonet Mar 14, 2017
91375fa
tests: Remove dependency to expect
okonet Mar 14, 2017
f6cd4d6
tests: Update tests
okonet Mar 14, 2017
27362ec
Use gitRestore in post command
okonet Mar 14, 2017
7d99e40
tests: Update tests and add gitRestore
okonet Mar 14, 2017
de605bc
test: Skip failing test for now
okonet Mar 15, 2017
1de7c6c
Trying a proposed approach and restoring from stash before the commit
okonet Mar 15, 2017
76a0932
tests: Removed unnecessary code, test, and snapshot
okonet Mar 15, 2017
5442438
Merge branch 'master' into git-worflow
Nov 28, 2017
646cdd4
refactor: Fix ESLint errors, run prettier, remove yarn.lock
Nov 28, 2017
e7307c2
refactor: Use functions and normal modules.export instead of class
Nov 28, 2017
b9f031d
feat: Add git stash and restore workflow
Nov 28, 2017
dd1228c
Move git workflow to runAll and use Listr for running it
Nov 28, 2017
31ded6e
Only stash/unstash files if there are unstaged files found
Nov 28, 2017
9b10e1c
Use base options for root listr tasks
Nov 29, 2017
383bed5
Better tests for git workflow
Nov 29, 2017
b9b693a
WIP on better partially staged patch application and conflict resolution
Nov 30, 2017
1b58716
Reduce boilerplate in test
Nov 30, 2017
fc9a2ae
Add a failing test for partially staged files
Nov 30, 2017
6611bd3
refactor: Remove unused jest-stdout-serializer
Jan 24, 2018
de57b1a
Create more git utilities for staged / unsteaged files
Jan 24, 2018
b8d54f7
tests: Add a failing test for deletion
Jan 24, 2018
bfe9c65
tests: More tests for git workflow
Jan 24, 2018
a2c4d72
Fix execution order problem
Jan 24, 2018
07b5fe8
Run in verbose mode
Jan 24, 2018
5c9a505
tests: Update test case and comment the conent check for now
Jan 24, 2018
d0777c1
Generate path if there are unstaged files
Jan 25, 2018
778180c
tests: Skip the failing test
Jan 25, 2018
ad48268
tests: Update snapshots
Jan 25, 2018
fbb27c7
Merge branch 'master' into git-worflow
Jan 25, 2018
627b241
chore: Use debug instead of console.log
Jan 25, 2018
f76f011
Run lint-staged in debug mode
Jan 25, 2018
3f7b7b0
Remove unused code that fails tests on Node 4
Jan 25, 2018
2e3765c
fixup! chore: Use debug instead of console.log
Jan 25, 2018
b9697f3
Remove unused post.js and pre.js files
Jan 25, 2018
490d6ff
Add stashUnstaged flag to the config
Jan 25, 2018
b163fb9
Fix tests
Jan 28, 2018
987724e
Update test title
Jan 28, 2018
cb5e547
Check if there are staged files before stashing
Mar 2, 2018
b90cda1
Add logging for git commands
Mar 2, 2018
7a84caf
chore: Use Node >=8 to supoort async / await
Mar 31, 2018
27de35c
chore: Disable prettier eslint plugin in .eslintrc
Mar 31, 2018
0ca3bfb
chore: Use prettier for *.js, *.json and *.md files
Mar 31, 2018
c1bda2b
test: Better test for partially-staged files
Mar 31, 2018
effd855
Use task context and set hasErrors if linters fail
Mar 31, 2018
69101d6
Cleaner test name
Mar 31, 2018
1bfb8e1
Add update index step to runAll which should only be executed if all …
Mar 31, 2018
3ef13f3
chore: Update yarn.lock
Mar 31, 2018
a0b4d8c
WIP on better gitWorkflow
Mar 31, 2018
5eb6fe8
Use async/await for patch strategy
Apr 1, 2018
844e57a
Better test names
Apr 1, 2018
ede22f7
fix test
Apr 5, 2018
6b8c4ab
Upgrade Jest
Apr 5, 2018
9948b6e
Use async/await for git workflow. Return stdout from execGit
Apr 5, 2018
bcbe2cc
Remove console.log
Apr 5, 2018
c0e4803
fixup! Use async/await for git workflow. Return stdout from execGit
Apr 5, 2018
c4f8246
tests: Fix tests for gitWorkflow.
Sep 4, 2018
6c069c7
Implement the thing!
Sep 4, 2018
3932dc2
Remove patch file after applying it. Add debug message
Sep 4, 2018
1b96d41
When formatted changes could not be applied, we still want to format …
Sep 4, 2018
d920f44
fixup! tests: Fix tests for gitWorkflow.
Sep 5, 2018
4870675
fix
Sep 5, 2018
ac92f94
fix(git): Fix hasUnstagedFiles check + add test
Sep 5, 2018
f4010cb
Make the partially staged test more realistic
Sep 5, 2018
414856c
Fix the partially staged case by adding a EOF to patch file
Sep 5, 2018
21b629c
Merge branch 'master' into git-worflow
Sep 6, 2018
bdc6926
v8.0.0-beta.0
Sep 6, 2018
4c1a8d9
fix: only run stash/unstash sequence when partially staged files dete…
Sep 7, 2018
30fa156
fix: Add cleanup function and use named variables for refs
Sep 7, 2018
44ccac1
refactor: Remove unneded tree check
Sep 7, 2018
1e25925
fix: Restore working copy when terminated
Sep 7, 2018
02eb5d1
refactor: remove unused `stashUnstaged` option
Sep 7, 2018
c836cf3
fix: check if `git status` returns a string
Sep 7, 2018
4832b4c
v8.0.0-beta.1
Sep 8, 2018
1d20445
test: mock gitWorkflow module in runAll tests
Sep 9, 2018
32f155a
fix: Do not display skip message for stashing step if stashed
Sep 11, 2018
99379a7
tests: Add more tests for stashing and restoring
Sep 11, 2018
36cd1f5
tests: Mock gitWorkflow in index.spec. Test non-zero exitCode
Sep 11, 2018
7be76dd
feat: Add whitespace between errors and make error messages stand out
Sep 13, 2018
1562d46
feat: Better task name and message for update stash step
Sep 13, 2018
9a034db
fix: use the forked version of listr-update-renderer until the fix is…
Sep 13, 2018
79eda6b
fix: upgrade execa to v1.0.0
Sep 14, 2018
52382c6
refactor: Cleanup gitWorkflow module
Sep 14, 2018
9b17afe
feat: detect SIGINT termination for linter tasks and exit appropriately
Sep 14, 2018
15266ca
fix: do not exit main Listr process on SIGINT
Sep 14, 2018
f65ac02
refactor: add a return from the main process
Sep 14, 2018
55c6a6c
v8.0.0-beta.2
Sep 24, 2018
6c490e8
tests: Move mock file to /test
Oct 11, 2018
9b4b4ea
fixup! chore: Use Node >=8 to supoort async / await
Oct 19, 2018
60da8bb
tests: Remove redundant test
Oct 19, 2018
4c0d6bb
refactor(gitWorkflow): Use g-status and remove custom partially stage…
Oct 19, 2018
e9fe671
refactor(gitWorkflow): Remove redundant gitDir option
Oct 19, 2018
fa492ab
fix(gitWorflow): Make the copy for partially staged case more clear
Oct 19, 2018
63d464f
tests(windows): Use mock of log-symbols to match snapshots on Mac/Win…
Oct 21, 2018
60b9895
Merge branch 'snapshots-win' into git-worflow
Oct 21, 2018
032c49c
tests: Update snapshots
Oct 21, 2018
adcd368
tests: Replace CLRF with LF in tests to normalize on Windows
Oct 21, 2018
7f5bd99
fix: Fix fail message when termination signal is detected
Oct 21, 2018
a2cb6b9
fix: Fix the case where failure message could reach impossible state
Oct 21, 2018
d21cd62
fixup! chore: Use Node >=8 to supoort async / await
Oct 21, 2018
8506945
refactor: Fix typo
Oct 21, 2018
e89ea5d
refactor: Use `skip` instead of `enabled` for stashing task
Oct 21, 2018
c7bd02d
chore: Remove fs-promise in favor of pify and native fs
Oct 21, 2018
3bc1526
chore: Do not use debug flag for lint-staged
Oct 21, 2018
a8cc52d
tests: Restore execa mock and remove redundant code
Oct 21, 2018
160987f
tests: Remove unnecessary stash checks since we don't rely on git stash
Oct 21, 2018
7192141
Merge remote-tracking branch 'origin/master' into git-worflow
Oct 21, 2018
815a2ab
tests: Switch some tests to inline snapshots
Oct 21, 2018
da2adf9
tests: Add more tests for renamed and untracked files
Oct 21, 2018
e78c034
v8.0.0-beta.3
Oct 22, 2018
083a201
refactor: Use stdin for patch instead of writing a the file
Oct 25, 2018
95dd571
fixup! chore: Use Node >=8 to supoort async / await
Oct 25, 2018
fb4e7ba
chore: Remove unused dependency
Oct 25, 2018
53fac77
fix: Delete *.rej files
Oct 25, 2018
499d7d7
fix(git): Always add a newline to the end of the patch.
Oct 25, 2018
3a74c22
v8.0.0-beta.4
Oct 25, 2018
6da31f0
fix(git): Fix hasPartiallyStagedFiles return true for untracked files
Oct 27, 2018
454b978
chore: Add a TODO
Oct 28, 2018
e95cdd2
refactor: Use Object spread
Oct 28, 2018
2bd723a
test: Add jest-snapshot-serializer-ansi
Oct 28, 2018
ed94335
refactor: Remove impossible case handling from makeErr
Oct 28, 2018
e3af19c
refactor: Rename function to match exports
Oct 29, 2018
66a3e9f
chore: Revert version in package.json
Oct 29, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Expand Up @@ -7,7 +7,6 @@ node_js:
- '10'
- '9'
- '8'
- '6'

before_install: yarn global add greenkeeper-lockfile@1
install: yarn install
Expand Down
11 changes: 3 additions & 8 deletions README.md
Expand Up @@ -133,15 +133,10 @@ To extend and customise lint-staged, advanced options are available. To use thes
{
"lint-staged": {
"linters": {
"*.{js,scss}": [
"some command",
"git add"
]
"*.{js,scss}": ["some command", "git add"]
},
"ignore": [
"**/dist/*.min.js"
]
},
"ignore": ["**/dist/*.min.js"]
}
}
```

Expand Down
20 changes: 13 additions & 7 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "lint-staged",
"version": "0.0.0-development",
"version": "8.0.0-beta.2",
"description": "Lint files staged by git",
"license": "MIT",
"repository": "https://github.com/okonet/lint-staged",
Expand All @@ -10,12 +10,12 @@
"Suhas Karanth <sudo.suhas@gmail.com>"
],
"engines": {
"node": ">=6"
"node": ">=8.0.0"
},
"bin": "index.js",
"files": ["index.js", "src"],
"scripts": {
"precommit": "node index.js",
"precommit": "node index.js --debug",
"cz": "git-cz",
"lint:base": "eslint --rule \"prettier/prettier: 2\"",
"lint": "npm run lint:base -- .",
Expand All @@ -25,17 +25,22 @@
"test:watch": "jest --watch"
},
"dependencies": {
"async-exit-hook": "^2.0.1",
"chalk": "^2.3.1",
"commander": "^2.14.1",
"cosmiconfig": "^5.0.2",
"debug": "^3.1.0",
"dedent": "^0.7.0",
"execa": "^0.9.0",
"execa": "^1.0.0",
"find-parent-dir": "^0.3.0",
"fs-promise": "^2.0.3",
okonet marked this conversation as resolved.
Show resolved Hide resolved
okonet marked this conversation as resolved.
Show resolved Hide resolved
"g-status": "^2.0.2",
"is-glob": "^4.0.0",
"is-windows": "^1.0.2",
"jest-validate": "^23.5.0",
"listr": "^0.14.1",
"listr": "^0.14.2",
"listr-update-renderer":
"https://github.com/okonet/listr-update-renderer/tarball/upgrade-log-update",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, would it be possible to get this released as a version on npm instead of depending on it through a loose git branch?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having issues to install anything that depends on lint-staged now. We are behind a corporate proxy here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a npm published version!

"lodash": "^4.17.5",
"log-symbols": "^2.2.0",
"micromatch": "^3.1.8",
Expand All @@ -58,9 +63,10 @@
"eslint-config-okonet": "^5.0.1",
"eslint-plugin-node": "^6.0.0",
"husky": "^0.14.3",
"jest": "^22.4.0",
"jest": "^22.4.3",
"jsonlint": "^1.6.2",
"prettier": "1.11.1"
"prettier": "1.11.1",
"tmp": "0.0.33"
},
"config": {
"commitizen": {
Expand Down
174 changes: 174 additions & 0 deletions src/gitWorkflow.js
@@ -0,0 +1,174 @@
'use strict'

const path = require('path')
const execa = require('execa')
const gStatus = require('g-status')
const fsp = require('fs-promise')
const debug = require('debug')('lint-staged:git')

let workingCopyTree = null
okonet marked this conversation as resolved.
Show resolved Hide resolved
let indexTree = null
let formattedIndexTree = null

function getAbsolutePath(dir) {
return path.isAbsolute(dir) ? dir : path.resolve(dir)
}

async function execGit(cmd, options) {
okonet marked this conversation as resolved.
Show resolved Hide resolved
const cwd = options && options.cwd ? options.cwd : process.cwd()
debug('Running git command', cmd)
try {
const { stdout } = await execa('git', [].concat(cmd), {
cwd: getAbsolutePath(cwd)
})
return stdout
} catch (err) {
throw new Error(err)
okonet marked this conversation as resolved.
Show resolved Hide resolved
}
}

async function writeTree(options) {
return execGit(['write-tree'], options)
}

async function getDiffForTrees(tree1, tree2, options) {
return execGit(
[
'diff-tree',
'--ignore-submodules',
'--binary',
'--no-color',
'--no-ext-diff',
'--unified=0',
tree1,
tree2
],
options
)
}

async function hasPartiallyStagedFiles(options) {
const files = await gStatus(options)
const partiallyStaged = files.filter(file => file.index !== ' ' && file.workingTree !== ' ')
return partiallyStaged.length > 0
}

async function generatePatchForTrees(tree1, tree2, options) {
// TODO: Use stdin instead of the file for patches
sudo-suhas marked this conversation as resolved.
Show resolved Hide resolved
const cwd = options && options.cwd ? options.cwd : process.cwd()
const patch = await getDiffForTrees(tree1, tree2, options)
if (patch.length) {
const filePath = path.join(cwd, '.lint-staged.patch')
debug(`Stashing unstaged files to ${filePath}...`)
await fsp.writeFile(filePath, `${patch}\n`) // The new line is somehow required for patch to not be corrupted
return filePath // Resolve with filePath
}
debug('Nothing to do...')
return null
}

// eslint-disable-next-line
okonet marked this conversation as resolved.
Show resolved Hide resolved
async function gitStash(options) {
debug('Stashing files...')
// Save ref to the current index
indexTree = await writeTree(options)
// Add working copy changes to index
await execGit(['add', '.'], options)
okonet marked this conversation as resolved.
Show resolved Hide resolved
// Save ref to the working copy index
workingCopyTree = await writeTree(options)
// Restore the current index
await execGit(['read-tree', indexTree], options)
// Remove all modifications
await execGit(['checkout-index', '-af'], options)
// await execGit(['clean', '-dfx'], options)
debug('Done stashing files!')
return [workingCopyTree, indexTree]
}

async function updateStash(options) {
formattedIndexTree = await writeTree(options)
return formattedIndexTree
}

async function applyPathFor(tree1, tree2, options) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function name typo {applyPathFor => applyPatchFor}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

const patchPath = await generatePatchForTrees(tree1, tree2, options)
if (patchPath) {
try {
/**
* Apply patch to index. We will apply it with --reject so it it will try apply hunk by hunk
* We're not interested in failied hunks since this mean that formatting conflicts with user changes
* and we prioritize user changes over formatter's
*/
await execGit(
[
'apply',
'-v',
'--whitespace=nowarn',
'--reject',
'--recount',
'--unidiff-zero',
patchPath
],
options
)
} catch (err) {
debug('Could not apply patch to the stashed files cleanly')
debug(err)
debug('Patch content:')
debug(await fsp.readFile(patchPath, { encoding: 'utf-8' }))
throw new Error('Could not apply patch to the stashed files cleanly.', err)
} finally {
// Delete patch file
await fsp.unlink(patchPath)
}
}
}

async function gitPop(options) {
if (workingCopyTree === null) {
throw new Error('Trying to restore from stash but could not find working copy stash.')
}

debug('Restoring working copy')
// Restore the stashed files in the index
await execGit(['read-tree', workingCopyTree], options)
// and sync it to the working copy (i.e. update files on fs)
await execGit(['checkout-index', '-af'], options)

// Then, restore the index after working copy is restored
if (indexTree !== null && formattedIndexTree === null) {
// Restore changes that were in index if there are no formatting changes
debug('Restoring index')
await execGit(['read-tree', indexTree], options)
} else {
/**
* There are formatting changes we want to restore in the index
* and in the working copy. So we start by restoring the index
* and after that we'll try to carry as many as possible changes
* to the working copy by applying the patch with --reject option.
*/
debug('Restoring index with formatting changes')
await execGit(['read-tree', formattedIndexTree], options)
try {
await applyPathFor(indexTree, formattedIndexTree, options)
} catch (err) {
debug(
'Found conflicts between formatters and local changes. Formatters changes will be ignored for conflicted hunks.'
)
}
}
// Clean up references
workingCopyTree = null
indexTree = null
formattedIndexTree = null

return null
}

module.exports = {
execGit,
gitStashSave: gitStash,
gitStashPop: gitPop,
hasPartiallyStagedFiles,
updateStash
}
2 changes: 1 addition & 1 deletion src/index.js
Expand Up @@ -66,7 +66,7 @@ module.exports = function lintStaged(logger = console, configPath, debugMode) {
debug('Normalized config:\n%O', config)
}

runAll(config)
return runAll(config)
.then(() => {
debug('linters were executed successfully!')
// No errors, exiting with 0
Expand Down
67 changes: 49 additions & 18 deletions src/resolveTaskFn.js
Expand Up @@ -4,6 +4,7 @@ const chunk = require('lodash/chunk')
const dedent = require('dedent')
const isWindows = require('is-windows')
const execa = require('execa')
const chalk = require('chalk')
const symbols = require('log-symbols')
const pMap = require('p-map')
const calcChunkSize = require('./calcChunkSize')
Expand All @@ -19,7 +20,7 @@ const debug = require('debug')('lint-staged:task')
* @param {Array<string>} args
* @param {Object} execaOptions
* @param {Array<string>} pathsToLint
* @return {Promise}
* @return {Promise} child_process
*/
function execLinter(bin, args, execaOptions, pathsToLint) {
const binArgs = args.concat(pathsToLint)
Expand All @@ -40,16 +41,33 @@ const successMsg = linter => `${symbols.success} ${linter} passed!`
* log only once.
*
* @param {string} linter
* @param {string} errStdout
* @param {string} errStderr
* @param {Object} result
* @param {string} result.stdout
* @param {string} result.stderr
* @param {boolean} result.failed
* @param {boolean} result.killed
* @param {string} result.signal
* @param {Object} context (see https://github.com/SamVerschueren/listr#context)
* @returns {Error}
*/
function makeErr(linter, errStdout, errStderr) {
function makeErr(linter, result, context = {}) {
// Indicate that some linter will fail so we don't update the index with formatting changes
context.hasErrors = true // eslint-disable-line no-param-reassign
const { stdout, stderr, failed, killed, signal } = result
let message = ''
if (failed) {
message = `${symbols.error} ${chalk.redBright(
`${linter} found some errors. Please fix them and try committing again.`
)}`
}
if (killed || signal != null) {
message = `${symbols.warning} ${chalk.yellow(`${linter} was terminated with ${signal}`)}`
}
const err = new Error()
err.privateMsg = dedent`
${symbols.error} "${linter}" found some errors. Please fix them and try committing again.
${errStdout}
${errStderr}
\n\n\n${message}
${stdout}
${stderr}
`
return err
}
Expand Down Expand Up @@ -79,11 +97,13 @@ module.exports = function resolveTaskFn(options) {

if (!isWindows()) {
debug('%s OS: %s; File path chunking unnecessary', symbols.success, process.platform)
return () =>
return ctx =>
execLinter(bin, args, execaOptions, pathsToLint).then(result => {
if (!result.failed) return successMsg(linter)
if (result.failed || result.killed || result.signal != null) {
throw makeErr(linter, result, ctx)
}

throw makeErr(linter, result.stdout, result.stderr)
return successMsg(linter)
})
}

Expand All @@ -97,7 +117,7 @@ module.exports = function resolveTaskFn(options) {
process.platform,
filePathChunks.length
)
return () =>
return ctx =>
pMap(filePathChunks, mapper, { concurrency })
.catch(err => {
/* This will probably never be called. But just in case.. */
Expand All @@ -107,12 +127,23 @@ module.exports = function resolveTaskFn(options) {
`)
})
.then(results => {
const errors = results.filter(res => res.failed)
if (errors.length === 0) return successMsg(linter)

const errStdout = errors.map(err => err.stdout).join('')
const errStderr = errors.map(err => err.stderr).join('')

throw makeErr(linter, errStdout, errStderr)
const errors = results.filter(res => res.failed || res.killed)
const failed = results.some(res => res.failed)
const killed = results.some(res => res.killed)
const signals = results.map(res => res.signal).filter(Boolean)

if (failed || killed || signals.length > 0) {
const finalResult = {
stdout: errors.map(err => err.stdout).join(''),
stderr: errors.map(err => err.stderr).join(''),
failed,
killed,
signal: signals.join(', ')
}

throw makeErr(linter, finalResult, ctx)
}

return successMsg(linter)
})
}