Skip to content

Commit 35fcce9

Browse files
committed
feat: add --diff option for overriding list of (staged) files
The `--diff` option can be used to, for example, run _lint-staged_ against the list of files changed between two branches: `--diff branch1...branch2`.
1 parent 383a96e commit 35fcce9

File tree

7 files changed

+110
-10
lines changed

7 files changed

+110
-10
lines changed

‎README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ Options:
9292
-c, --config [path] path to configuration file, or - to read from stdin
9393
--cwd [path] run all tasks in specific directory, instead of the current
9494
-d, --debug print additional debug information (default: false)
95+
--diff [string] override the default "--staged" flag of "git diff" to get list of files
96+
--max-arg-length [number] maximum length of the command-line argument string (default: 0)
9597
--no-stash disable the backup stash, and do not revert in case of errors
9698
-q, --quiet disable lint-staged’s own console output (default: false)
9799
-r, --relative pass relative filepaths to tasks (default: false)
@@ -111,6 +113,8 @@ Options:
111113
- **`--debug`**: Run in debug mode. When set, it does the following:
112114
- uses [debug](https://github.com/visionmedia/debug) internally to log additional information about staged files, commands being executed, location of binaries, etc. Debug logs, which are automatically enabled by passing the flag, can also be enabled by setting the environment variable `$DEBUG` to `lint-staged*`.
113115
- uses [`verbose` renderer](https://github.com/SamVerschueren/listr-verbose-renderer) for `listr`; this causes serial, uncoloured output to the terminal, instead of the default (beautified, dynamic) output.
116+
- **`--diff`**: By default linters 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).
117+
- **`--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.
114118
- **`--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.
115119
- **`--quiet`**: Supress all CLI output, except from tasks.
116120
- **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
@@ -596,7 +600,7 @@ const success = await lintStaged({
596600
relative: false,
597601
shell: false,
598602
stash: true,
599-
verbose: false
603+
verbose: false,
600604
})
601605
```
602606

@@ -713,6 +717,30 @@ Example repo: [sudo-suhas/lint-staged-django-react-demo](https://github.com/sudo
713717

714718
</details>
715719

720+
### Can I run `lint-staged` in CI, or when there are no staged files?
721+
722+
<details>
723+
<summary>Click to expand</summary>
724+
725+
Lint-staged will by default run against files staged in git, and should be run during the git pre-commit hook, for example. It's also possible to override this default behaviour and run against files in a specific diff, for example
726+
all changed files between two different branches. If you want to run _lint-staged_ in the CI, maybe you can set it up to compare the branch in a _Pull Request_/_Merge Request_ to the target branch.
727+
728+
Try out the `git diff` command until you are satisfied with the result, for example:
729+
730+
```
731+
git diff --diff-filter=ACMR --name-only master...my-branch
732+
```
733+
734+
This will print a list of _added_, _changed_, _modified_, and _renamed_ files between `master` and `my-branch`.
735+
736+
You can then run lint-staged against the same files with:
737+
738+
```
739+
npx lint-staged --diff="master...my-branch"
740+
```
741+
742+
</details>
743+
716744
### Can I use `lint-staged` with `ng lint`
717745

718746
<details>

‎bin/lint-staged.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ cli.option('--cwd [path]', 'run all tasks in specific directory, instead of the
4242

4343
cli.option('-d, --debug', 'print additional debug information', false)
4444

45+
cli.option(
46+
'--diff [string]',
47+
'override the default "--staged" flag of "git diff" to get list of files'
48+
)
49+
4550
cli.option('--max-arg-length [number]', 'maximum length of the command-line argument string', 0)
4651

4752
/**
@@ -86,6 +91,7 @@ const options = {
8691
configPath: cliOptions.config,
8792
cwd: cliOptions.cwd,
8893
debug: !!cliOptions.debug,
94+
diff: cliOptions.diff,
8995
maxArgLength: cliOptions.maxArgLength || undefined,
9096
quiet: !!cliOptions.quiet,
9197
relative: !!cliOptions.relative,

‎lib/getStagedFiles.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ import normalize from 'normalize-path'
55
import { execGit } from './execGit.js'
66
import { parseGitZOutput } from './parseGitZOutput.js'
77

8-
export const getStagedFiles = async ({ cwd = process.cwd() } = {}) => {
9-
try {
10-
// Docs for --diff-filter option: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
11-
// Docs for -z option: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--z
12-
const lines = await execGit(['diff', '--staged', '--diff-filter=ACMR', '--name-only', '-z'], {
13-
cwd,
14-
})
8+
/**
9+
* Docs for --diff-filter option: @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
10+
* Docs for -z option: @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--z
11+
*/
12+
const ARGS = ['diff', '--diff-filter=ACMR', '--name-only', '-z']
1513

14+
export const getStagedFiles = async ({ cwd = process.cwd(), diff } = {}) => {
15+
try {
16+
/** Use `--diff branch1...branch2` or `--diff="branch1 branch2", or fall back to default staged files */
17+
const diffArgs = diff !== undefined ? diff.trim().split(' ') : ['--staged']
18+
const lines = await execGit([...ARGS, ...diffArgs], { cwd })
1619
if (!lines) return []
17-
1820
return parseGitZOutput(lines).map((file) => normalize(path.resolve(cwd, file)))
1921
} catch {
2022
return null

‎lib/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const getMaxArgLength = () => {
4949
* @param {string} [options.configPath] - Path to configuration file
5050
* @param {Object} [options.cwd] - Current working directory
5151
* @param {boolean} [options.debug] - Enable debug mode
52+
* @param {string} [options.diff] - Override the default "--staged" flag of "git diff" to get list of files
5253
* @param {number} [options.maxArgLength] - Maximum argument string length
5354
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
5455
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
@@ -67,6 +68,7 @@ const lintStaged = async (
6768
configPath,
6869
cwd,
6970
debug = false,
71+
diff,
7072
maxArgLength = getMaxArgLength() / 2,
7173
quiet = false,
7274
relative = false,
@@ -91,6 +93,7 @@ const lintStaged = async (
9193
configPath,
9294
cwd,
9395
debug,
96+
diff,
9497
maxArgLength,
9598
quiet,
9699
relative,

‎lib/runAll.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const createError = (ctx) => Object.assign(new Error('lint-staged failed'), { ct
5252
* @param {string} [options.configPath] - Explicit path to a config file
5353
* @param {string} [options.cwd] - Current working directory
5454
* @param {boolean} [options.debug] - Enable debug mode
55+
* @param {string} [options.diff] - Override the default "--staged" flag of "git diff" to get list of files
5556
* @param {number} [options.maxArgLength] - Maximum argument string length
5657
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
5758
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
@@ -69,6 +70,7 @@ export const runAll = async (
6970
configPath,
7071
cwd,
7172
debug = false,
73+
diff,
7274
maxArgLength,
7375
quiet = false,
7476
relative = false,
@@ -106,7 +108,7 @@ export const runAll = async (
106108
logger.warn(skippingBackup(hasInitialCommit))
107109
}
108110

109-
const files = await getStagedFiles({ cwd: gitDir })
111+
const files = await getStagedFiles({ cwd: gitDir, diff })
110112
if (!files) {
111113
if (!quiet) ctx.output.push(FAILED_GET_STAGED_FILES)
112114
ctx.errors.add(GetStagedFilesError)

‎test/getStagedFiles.spec.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@ jest.mock('../lib/execGit')
1111
const normalizePath = (input) => normalize(path.resolve('/', input))
1212

1313
describe('getStagedFiles', () => {
14+
afterEach(() => {
15+
jest.clearAllMocks()
16+
})
17+
1418
it('should return array of file names', async () => {
1519
execGit.mockImplementationOnce(async () => 'foo.js\u0000bar.js\u0000')
1620
const staged = await getStagedFiles({ cwd: '/' })
1721
// Windows filepaths
1822
expect(staged).toEqual([normalizePath('/foo.js'), normalizePath('/bar.js')])
23+
24+
expect(execGit).toHaveBeenLastCalledWith(
25+
['diff', '--diff-filter=ACMR', '--name-only', '-z', '--staged'],
26+
{ cwd: '/' }
27+
)
1928
})
2029

2130
it('should return empty array when no staged files', async () => {
@@ -31,4 +40,28 @@ describe('getStagedFiles', () => {
3140
const staged = await getStagedFiles({})
3241
expect(staged).toEqual(null)
3342
})
43+
44+
it('should support overriding diff trees with ...', async () => {
45+
execGit.mockImplementationOnce(async () => 'foo.js\u0000bar.js\u0000')
46+
const staged = await getStagedFiles({ cwd: '/', diff: 'master...my-branch' })
47+
// Windows filepaths
48+
expect(staged).toEqual([normalizePath('/foo.js'), normalizePath('/bar.js')])
49+
50+
expect(execGit).toHaveBeenLastCalledWith(
51+
['diff', '--diff-filter=ACMR', '--name-only', '-z', 'master...my-branch'],
52+
{ cwd: '/' }
53+
)
54+
})
55+
56+
it('should support overriding diff trees with multiple args', async () => {
57+
execGit.mockImplementationOnce(async () => 'foo.js\u0000bar.js\u0000')
58+
const staged = await getStagedFiles({ cwd: '/', diff: 'master my-branch' })
59+
// Windows filepaths
60+
expect(staged).toEqual([normalizePath('/foo.js'), normalizePath('/bar.js')])
61+
62+
expect(execGit).toHaveBeenLastCalledWith(
63+
['diff', '--diff-filter=ACMR', '--name-only', '-z', 'master', 'my-branch'],
64+
{ cwd: '/' }
65+
)
66+
})
3467
})

‎test/integration.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,32 @@ describe('lint-staged', () => {
13091309
// File outside deeper/ was not fixed
13101310
expect(await readFile('file.js')).toEqual(testJsFileUgly)
13111311
})
1312+
1313+
it('should support overriding file list using --diff', async () => {
1314+
// Commit ugly file
1315+
await appendFile('test.js', testJsFileUgly)
1316+
await execGit(['add', 'test.js'])
1317+
await execGit(['commit', '-m', 'ugly'], { cwd })
1318+
1319+
const hashes = (await execGit(['log', '--format=format:%H'])).trim().split('\n')
1320+
expect(hashes).toHaveLength(2)
1321+
1322+
// Run lint-staged with `--diff` between the two commits.
1323+
// Nothing is staged at this point, so don't rung `gitCommit`
1324+
const passed = await lintStaged({
1325+
config: { '*.js': 'prettier --list-different' },
1326+
cwd,
1327+
debug: true,
1328+
diff: `${hashes[1]}...${hashes[0]}`,
1329+
stash: false,
1330+
})
1331+
1332+
// Lint-staged failed because commit diff contains ugly file
1333+
expect(passed).toEqual(false)
1334+
1335+
expect(console.printHistory()).toMatch('prettier --list-different:')
1336+
expect(console.printHistory()).toMatch('test.js')
1337+
})
13121338
})
13131339

13141340
describe('lintStaged', () => {

0 commit comments

Comments
 (0)