8000 feat: add "--no-revert" flag to disable reverting to original state i… · lint-staged/lint-staged@e729daa · GitHub
[go: up one dir, main page]

Skip to content

Commit e729daa

Browse files
committed
feat: add "--no-revert" flag to disable reverting to original state in case of errors
1 parent c37dc38 commit e729daa

File tree

9 files changed

+198
-33
lines changed

9 files changed

+198
-33
lines changed

.changeset/clean-spiders-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'lint-staged': minor
3+
---
4+
5+
A new flag `--no-revert` has been introduced for when task modifications should be appl 57AE ied to the index before aborting the commit in case of errors. By default, _lint-staged_ will clear all task modifications and revert to the original state.

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,12 @@ Options:
109109
-c, --config [path] path to configuration file, or - to read from stdin
110110
--cwd [path] run all tasks in specific directory, instead of the current
111111
-d, --debug print additional debug information (default: false)
112-
--diff [string] override the default "--staged" flag of "git diff" to get list of files.
113-
Implies "--no-stash".
114-
--diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of
115-
files
112+
--diff [string] override the default "--staged" flag of "git diff" to get list of files. Implies
113+
"--no-stash".
114+
--diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
116115
--max-arg-length [number] maximum length of the command-line argument string (default: 0)
117-
--no-stash disable the backup stash, and do not revert in case of errors. Implies
118-
"--no-hide-partially-staged".
116+
--no-revert do not revert to original state in case of errors.
117+
--no-stash disable the backup stash. Implies "--no-revert".
119118
--no-hide-partially-staged disable hiding unstaged changes from partially staged files
120119
-q, --quiet disable lint-staged’s own console output (default: false)
121120
-r, --relative pass relative filepaths to tasks (default: false)
@@ -144,10 +143,11 @@ Any lost modifications can be restored from a git stash:
144143
- **`--diff`**: By default tasks are filtered against all files staged in git, generated from `git diff --staged`. This option allows you to override the `--staged` flag with arbitrary revisions. For example to get a list of changed files between two branches, use `--diff="branch1...branch2"`. You can also read more from about [git diff](https://git-scm.com/docs/git-diff) and [gitrevisions](https://git-scm.com/docs/gitrevisions). This option also implies `--no-stash`.
145144
- **`--diff-filter`**: By default only files that are _added_, _copied_, _modified_, or _renamed_ are included. Use this flag to override the default `ACMR` value with something else: _added_ (`A`), _copied_ (`C`), _deleted_ (`D`), _modified_ (`M`), _renamed_ (`R`), _type changed_ (`T`), _unmerged_ (`U`), _unknown_ (`X`), or _pairing broken_ (`B`). See also the `git diff` docs for [--diff-filter](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203).
146145
- **`--max-arg-length`**: long commands (a lot of files) are automatically split into multiple chunks when it detects the current shell cannot handle them. Use this flag to override the maximum length of the generated command string.
147-
- **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. Can be re-enabled with `--stash`. This option also implies `--no-hide-partially-staged`.
146+
- **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. This option also implies `--no-hide-partially-staged`.
148147
- **`--no-hide-partially-staged`**: By default, unstaged changes from partially staged files will be hidden. This option will disable this behavior and include all unstaged changes in partially staged files. Can be re-enabled with `--hide-partially-staged`
149148
- **`--quiet`**: Supress all CLI output, except from tasks.
150149
- **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
150+
- **`--no-revert`**: By default all task modifications will be reverted in case of an error. This option will disable the behavior, and apply task modifications to the index before aborting the commit.
151151
- **`--verbose`**: Show task output even when tasks succeed. By default only failed output is shown.
152152

153153
## Configuration

bin/lint-staged.js

Lines changed: 12 additions & 15 deletions
< F438 th scope="col">Diff line change
Original file line numberDiff line number
@@ -61,30 +61,26 @@ program.option(
6161
program.option('--max-arg-length [number]', 'maximum length of the command-line argument string', 0)
6262

6363
/**
64-
* We don't want to show the `--stash` flag because it's on by default, and only show the
65-
* negatable flag `--no-stash` in stead. There seems to be a bug in Commander.js where
64+
* We don't want to show the `--revert` flag because it's on by default, and only show the
65+
* negatable flag `--no-rever` instead. There seems to be a bug in Commander.js where
6666
* configuring only the latter won't actually set the default value.
6767
*/
6868
program
6969
.addOption(
70-
new Option('--stash', 'enable the backup stash, and revert in case of errors')
71-
.default(true)
72-
.hideHelp()
70+
new Option('--revert', 'revert to original state in case of errors').default(true).hideHelp()
7371
)
7472
.addOption(
75-
new Option(
76-
'--no-stash',
77-
'disable the backup stash, and do not revert in case of errors. Implies "--no-hide-partially-staged".'
78-
)
73+
new Option('--no-revert', 'do not revert to original state in case of errors.').default(false)
74+
)
75+
76+
program
77+
.addOption(new Option('--stash', 'enable the backup stash').default(true).hideHelp())
78+
.addOption(
79+
new Option('--no-stash', 'disable the backup stash. Implies "--no-revert".')
7980
.default(false)
80-
.implies({ hidePartiallyStaged: false })
81+
.implies({ revert: false, hidePartiallyStaged: false })
8182
)
8283

83-
/**
84-
* We don't want to show the `--hide-partially-staged` flag because it's on by default, and only show the
85-
* negatable flag `--no-hide-partially-staged` in stead. There seems to be a bug in Commander.js where
86-
* configuring only the latter won't actually set the default value.
87-
*/
8884
program
8985
.addOption(
9086
new Option('--hide-partially-staged', 'hide unstaged changes from partially staged files')
@@ -127,6 +123,7 @@ const options = {
127123
maxArgLength: cliOptions.maxArgLength || undefined,
128124
quiet: !!cliOptions.quiet,
129125
relative: !!cliOptions.relative,
126+
revert: !!cliOptions.revert, // commander inverts `no-<x>` flags to `!x`
130127
stash: !!cliOptions.stash, // commander inverts `no-<x>` flags to `!x`
131128
hidePartiallyStaged: !!cliOptions.hidePartiallyStaged, // commander inverts `no-<x>` flags to `!x`
132129
verbose: !!cliOptions.verbose,

lib/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ export type Options = {
6666
* @default false
6767
*/
6868
relative?: boolean
69+
/**
70+
* Revert to original state in case of errors
71+
* @default true
72+
*/
73+
revert?: boolean
6974
/**
7075
* Enable the backup stash, and revert in case of errors.
7176
* @warn Disabling this also implies `hidePartiallyStaged: false`.

lib/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const getMaxArgLength = () => {
5757
* @param {number} [options.maxArgLength] - Maximum argument string length
5858
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
5959
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
60+
* @param {boolean} [options.revert] - revert to original state in case of errors
6061
* @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
6162
* @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
6263
* @param {Logger} [logger]
@@ -78,6 +79,8 @@ const lintStaged = async (
7879
relative = false,
7980
// Stashing should be disabled by default when the `diff` option is used
8081
stash = diff === undefined,
82+
// Cannot revert to original state without stash
83+
revert = stash,
8184
hidePartiallyStaged = stash,
8285
verbose = false,
8386
} = {},
@@ -110,6 +113,7 @@ const lintStaged = async (
110113
maxArgLength,
111114
quiet,
112115
relative,
116+
revert,
113117
stash,
114118
hidePartiallyStaged,
115119
verbose,

lib/runAll.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const createError = (ctx) => Object.assign(new Error('lint-staged failed'), { ct
5959
* @param {number} [options.maxArgLength] - Maximum argument string length
6060
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
6161
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
62+
* @param {boolean} [options.revert] - revert to original state in case of errors
6263
* @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
6364
* @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
6465
* @param {Logger} logger
@@ -79,6 +80,8 @@ export const runAll = async (
7980
relative = false,
8081
// Stashing should be disabled by default when the `diff` option is used
8182
stash = diff === undefined,
83+
// Cannot revert to original state without stash
84+
revert = stash,
8285
hidePartiallyStaged = stash,
8386
verbose = false,
8487
},
@@ -91,7 +94,7 @@ export const runAll = async (
9194
cwd = hasExplicitCwd ? path.resolve(cwd) : process.cwd()
9295
debugLog('Using working directory `%s`', cwd)
9396

94-
const ctx = getInitialState({ quiet })
97+
const ctx = getInitialState({ quiet, revert })
9598

9699
const { topLevelDir, gitConfigDir } = await resolveGitRepo(cwd)
97100
if (!topLevelDir) {

lib/state.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
TaskError,
99
} from './symbols.js'
1010

11-
export const getInitialState = ({ quiet = false } = {}) => ({
11+
export const getInitialState = ({ quiet = false, revert = true } = {}) => ({
1212
hasPartiallyStagedFiles: null,
1313
shouldBackup: null,
14+
shouldRevert: revert,
1415
backupHash: null,
1516
shouldHidePartiallyStaged: true,
1617
errors: new Set([]),
@@ -23,8 +24,8 @@ export const shouldHidePartiallyStagedFiles = (ctx) =>
2324
ctx.hasPartiallyStagedFiles && ctx.shouldHidePartiallyStaged
2425

2526
export const applyModificationsSkipped = (ctx) => {
26-
// Always apply back unstaged modifications when skipping backup
27-
if (!ctx.shouldBackup) return false
27+
// Always apply back unstaged modifications when skipping revert or backup
28+
if (!ctx.shouldRevert || !ctx.shouldBackup) return false
2829
// Should be skipped in case of git errors
2930
if (ctx.errors.has(GitError)) {
3031
return GIT_ERROR
@@ -48,7 +49,9 @@ export const restoreUnstagedChangesSkipped = (ctx) => {
4849
}
4950

5051
export const restoreOriginalStateEnabled = (ctx) =>
51-
ctx.shouldBackup && (ctx.errors.has(TaskError) || ctx.errors.has(RestoreUnstagedChangesError))
52+
!!ctx.shouldRevert &&
53+
!!ctx.shouldBackup &&
54+
(ctx.errors.has(TaskError) || ctx.errors.has(RestoreUnstagedChangesError))
5255

5356
export const restoreOriginalStateSkipped = (ctx) => {
5457
// Should be skipped in case of unknown git errors

test/integration/no-revert.test.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { jest } from '@jest/globals'
2+
3+
import { withGitIntegration } from './__utils__/withGitIntegration.js'
4+
5+
jest.setTimeout(20000)
6+
jest.retryTimes(2)
7+
8+
const UGLY_FILE = `console.log('Hello, world!', unreferencedVariable)`
9+
10+
const PRETTY_FILE = `console.log("Hello, world!", unreferencedVariable);
11+
`
12+
13+
describe('lint-staged', () => {
14+
test(
15+
'should revert linter changes by default',
16+
withGitIntegration(async ({ execGit, gitCommit, readFile, writeFile }) => {
17+
await writeFile(
18+
'.lintstagedrc.json',
19+
JSON.stringify({
20+
'*.js': ['prettier --write', 'node'],
21+
})
22+
)
23+
24+
await writeFile('test.js', UGLY_FILE)
25+
26+
await execGit(['add', '.'])
27+
28+
await expect(gitCommit()).rejects.toThrow(
29+
'ReferenceError: unreferencedVariable is not defined'
30+
)
31+
32+
expect(await readFile('test.js')).toEqual(UGLY_FILE)
33+
})
34+
)
35+
36+
test(
37+
'should not revert linter changes when --no-revert is used',
38+
withGitIntegration(async ({ execGit, gitCommit, readFile, writeFile }) => {
39+
await writeFile(
40+
'.lintstagedrc.json',
41+
JSON.stringify({
42+
'*.js': ['prettier --write', 'node'],
43+
})
44+
)
45+
46+
await writeFile('test.js', UGLY_FILE)
47+
48+
await execGit(['add', '.'])
49+
50+
await expect(
51+
gitCommit({
52+
lintStaged: { revert: false },
53+
})
54+
).rejects.toThrow('ReferenceError: unreferencedVariable is not defined')
55+
56+
expect(await readFile('test.js')).toEqual(PRETTY_FILE)
57+
})
58+
)
59+
})

test/unit/state.spec.js

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,129 @@
11
import {
22
applyModificationsSkipped,
33
cleanupSkipped,
4+
getInitialState,
5+
restoreOriginalStateEnabled,
46
restoreOriginalStateSkipped,
57
restoreUnstagedChangesSkipped,
68
} from '../../lib/state.js'
7-
import { GitError, RestoreOriginalStateError } from '../../lib/symbols.js'
9+
import {
10+
GitError,
11+
RestoreOriginalStateError,
12+
RestoreUnstagedChangesError,
13+
TaskError,
14+
} from '../../lib/symbols.js'
815

916
describe('applyModificationsSkipped', () => {
17+
it('should return false when reverting is disabled', () => {
18+
const state = getInitialState()
19+
const result = applyModificationsSkipped({
20+
...state,
21+
shouldRevert: false,
22+
})
23+
24+
expect(result).toEqual(false)
25+
})
26+
1027
it('should return false when backup is disabled', () => {
11-
const result = applyModificationsSkipped({ shouldBackup: false })
28+
const state = getInitialState()
29+
const result = applyModificationsSkipped({
30+
...state,
31+
shouldBackup: false,
32+
})
33+
1234
expect(result).toEqual(false)
1335
})
1436

1537
it('should return error message when there is an unkown git error', () => {
16-
const result = applyModificationsSkipped({ shouldBackup: true, errors: new Set([GitError]) })
38+
const state = getInitialState()
39+
const result = applyModificationsSkipped({
40+
...state,
41+
shouldBackup: true,
42+
errors: new Set([GitError]),
43+
})
44+
1745
expect(typeof result === 'string').toEqual(true)
1846
})
1947
})
2048

2149
describe('restoreUnstagedChangesSkipped', () => {
2250
it('should return error message when there is an unkown git error', () => {
23-
const result = restoreUnstagedChangesSkipped({ errors: new Set([GitError]) })
51+
const state = getInitialState()
52+
const result = restoreUnstagedChangesSkipped({
53+
...state,
54+
errors: new Set([GitError]),
55+
})
56+
2457
expect(typeof result === 'string').toEqual(true)
2558
})
2659
})
2760

61+
describe('restoreOriginalStateEnabled', () => {
62+
it('should return false by default', () => {
63+
const state = getInitialState()
64+
const result = restoreOriginalStateEnabled({
65+
...state,
66+
})
67+
68+
expect(result).toEqual(false)
69+
})
70+
71+
it('should return true when backup enabled and there are task errors', () => {
72+
const state = getInitialState()
73+
const result = restoreOriginalStateEnabled({
74+
...state,
75+
shouldBackup: true,
76+
errors: new Set([TaskError]),
77+
})
78+
79+
expect(result).toEqual(true)
80+
})
81+
82+
it('should return true when backup enabled and unstaged changes failed to restore', () => {
83+
const state = getInitialState()
84+
const result = restoreOriginalStateEnabled({
85+
...state,
86+
shouldBackup: true,
87+
shouldRevert: true,
88+
errors: new Set([RestoreUnstagedChangesError]),
89+
})
90+
91+
expect(result).toEqual(true)
92+
})
93+
94+
it('should return false when reverting is disabled', () => {
95+
const state = getInitialState()
96+
const result = restoreOriginalStateEnabled({
97+
...state,
98+
shouldBackup: true,
99+
shouldRevert: false,
100+
errors: new Set([TaskError]),
101+
})
102+
103+
expect(result).toEqual(false)
104+
})
105+
})
106+
28107
describe('restoreOriginalStateSkipped', () => {
29108
it('should return error message when there is an unkown git error', () => {
30-
const result = restoreOriginalStateSkipped({ errors: new Set([GitError]) })
109+
const state = getInitialState()
110+
const result = restoreOriginalStateSkipped({
111+
...state,
112+
errors: new Set([ 995D GitError]),
113+
})
114+
31115
expect(typeof result === 'string').toEqual(true)
32116
})
33117
})
34118

35119
describe('shouldSkipCleanup', () => {
36120
it('should return error message when reverting to original state fails', () => {
37-
const result = cleanupSkipped({ errors: new Set([RestoreOriginalStateError]) })
121+
const state = getInitialState()
122+
const result = cleanupSkipped({
123+
...state,
124+
errors: new Set([RestoreOriginalStateError]),
125+
})
126+
38127
expect(typeof result === 'string').toEqual(true)
39128
})
40129
})

0 commit comments

Comments
 (0)
0