Skip to content

Commit cc69fb3

Browse files
Mario Parejaazz
authored andcommitted
feat: safer handling of partially staged files (#29)
+ Partially staged files are not re-staged + Non-zero exit code upon reformatting partially staged file + Update README
1 parent de790a7 commit cc69fb3

File tree

6 files changed

+62
-7
lines changed

6 files changed

+62
-7
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ In `package.json`'s `"scripts"` section, add:
7474

7575
Pre-commit mode. Under this flag only staged files will be formatted, and they will be re-staged after formatting.
7676

77+
Partially staged files will not be re-staged after formatting and pretty-quick will exit with a non-zero exit code. The intent is to abort the git commit and allow the user to amend their selective staging to include formatting fixes.
78+
7779
### `--branch`
7880

7981
When not in `staged` pre-commit mode, use this flag to compare changes with the specified branch. Defaults to `master` (git) / `default` (hg) branch.

bin/pretty-quick.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const prettyQuick = require('..').default;
99

1010
const args = mri(process.argv.slice(2));
1111

12+
let success = true;
1213
prettyQuick(
1314
process.cwd(),
1415
Object.assign({}, args, {
@@ -28,10 +29,23 @@ prettyQuick(
2829
);
2930
},
3031

32+
onPartiallyStagedFile: file => {
33+
console.log(`✗ Found ${chalk.bold('partially')} staged file ${file}.`);
34+
success = false;
35+
},
36+
3137
onWriteFile: file => {
3238
console.log(`✍️ Fixing up ${chalk.bold(file)}.`);
3339
},
3440
})
3541
);
3642

37-
console.log('✅ Everything is awesome!');
43+
if (success) {
44+
console.log('✅ Everything is awesome!');
45+
} else {
46+
console.log(
47+
'✗ Partially staged files were fixed up.' +
48+
` ${chalk.bold('Please update stage before committing')}.`
49+
);
50+
process.exit(1); // ensure git hooks abort
51+
}

src/__tests__/scm-git.test.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ afterEach(() => {
1111
jest.clearAllMocks();
1212
});
1313

14-
const mockGitFs = () => {
14+
const mockGitFs = (additionalUnstaged = '') => {
1515
mock({
1616
'/.git': {},
17+
'/raz.js': 'raz()',
1718
'/foo.js': 'foo()',
1819
'/bar.md': '# foo',
1920
});
@@ -26,8 +27,8 @@ const mockGitFs = () => {
2627
return { stdout: '' };
2728
case 'diff':
2829
return args[2] === '--cached'
29-
? { stdout: './foo.js\n' }
30-
: { stdout: './foo.js\n' + './bar.md\n' };
30+
? { stdout: './raz.js\n' }
31+
: { stdout: './foo.js\n' + './bar.md\n' + additionalUnstaged };
3132
case 'add':
3233
return { stdout: '' };
3334
default:
@@ -151,19 +152,33 @@ describe('with git', () => {
151152
expect(fs.readFileSync('/bar.md', 'utf8')).toEqual('formatted:# foo');
152153
});
153154

154-
test('with --staged stages staged files', () => {
155+
test('with --staged stages fully-staged files', () => {
155156
mockGitFs();
156157

157158
prettyQuick('root', { since: 'banana', staged: true });
158159

159-
expect(execa.sync).toHaveBeenCalledWith('git', ['add', './foo.js'], {
160+
expect(execa.sync).toHaveBeenCalledWith('git', ['add', './raz.js'], {
161+
cwd: '/',
162+
});
163+
expect(execa.sync).not.toHaveBeenCalledWith('git', ['add', './foo.md'], {
160164
cwd: '/',
161165
});
162166
expect(execa.sync).not.toHaveBeenCalledWith('git', ['add', './bar.md'], {
163167
cwd: '/',
164168
});
165169
});
166170

171+
test('with --staged does not stage previously partially staged files AND aborts commit', () => {
172+
const additionalUnstaged = './raz.js\n'; // raz.js is partly staged and partly not staged
173+
mockGitFs(additionalUnstaged);
174+
175+
prettyQuick('root', { since: 'banana', staged: true });
176+
177+
expect(execa.sync).not.toHaveBeenCalledWith('git', ['add', './raz.js'], {
178+
cwd: '/',
179+
});
180+
});
181+
167182
test('without --staged does NOT stage changed files', () => {
168183
mockGitFs();
169184

src/index.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default (
1212
branch,
1313
onFoundSinceRevision,
1414
onFoundChangedFiles,
15+
onPartiallyStagedFile,
1516
onWriteFile,
1617
} = {}
1718
) => {
@@ -30,13 +31,28 @@ export default (
3031
.filter(isSupportedExtension)
3132
.filter(createIgnorer(directory));
3233

34+
const unstagedFiles = staged
35+
? scm
36+
.getUnstagedChangedFiles(directory, revision)
37+
.filter(isSupportedExtension)
38+
.filter(createIgnorer(directory))
39+
: [];
40+
41+
const wasFullyStaged = f => unstagedFiles.indexOf(f) < 0;
42+
3343
onFoundChangedFiles && onFoundChangedFiles(changedFiles);
3444

3545
formatFiles(directory, changedFiles, {
3646
config,
3747
onWriteFile: file => {
3848
onWriteFile && onWriteFile(file);
39-
staged && scm.stageFile(directory, file);
49+
if (staged) {
50+
if (wasFullyStaged(file)) {
51+
scm.stageFile(directory, file);
52+
} else {
53+
onPartiallyStagedFile && onPartiallyStagedFile(file);
54+
}
55+
}
4056
},
4157
});
4258
};

src/scms/git.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export const getChangedFiles = (directory, revision, staged) => {
6161
].filter(Boolean);
6262
};
6363

64+
export const getUnstagedChangedFiles = (directory, revision) => {
65+
return getChangedFiles(directory, revision, false);
66+
};
67+
6468
export const stageFile = (directory, file) => {
6569
runGit(directory, ['add', file]);
6670
};

src/scms/hg.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ export const getChangedFiles = (directory, revision) => {
3535
].filter(Boolean);
3636
};
3737

38+
export const getUnstagedChangedFiles = () => {
39+
return [];
40+
};
41+
3842
export const stageFile = (directory, file) => {
3943
runHg(directory, ['add', file]);
4044
};

0 commit comments

Comments
 (0)