Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 74130e4

Browse files
committed
Fix staging/unstaging lines without symlink changes, and add tests
1 parent 4dd85fe commit 74130e4

File tree

2 files changed

+97
-8
lines changed

2 files changed

+97
-8
lines changed

lib/models/file-patch.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,20 @@ export default class FilePatch {
205205
delta += newRowCount - hunk.getNewRowCount();
206206
}
207207

208-
return this.clone({
209-
newFile: this.getNewPath() ? this.getNewFile() : this.getOldFile(),
210-
patch: this.getPatch().clone({hunks}),
211-
});
208+
if (this.hasTypechange() && this.getStatus() === 'deleted') {
209+
// handle special case when symlink is created where a file was deleted.
210+
// In order to stage the line deletion, we must ensure that the created file patch a modified status
211+
// and that new file is not a symlink
212+
return this.clone({
213+
newFile: this.getNewFile().clone({mode: null, symlink: null}),
214+
patch: this.getPatch().clone({hunks, status: 'modified'}),
215+
});
216+
} else {
217+
return this.clone({
218+
newFile: this.getNewPath() ? this.getNewFile() : this.getOldFile(),
219+
patch: this.getPatch().clone({hunks, status: 'modified'}),
220+
});
221+
}
212222
}
213223

214224
getUnstagePatch() {
@@ -286,10 +296,21 @@ export default class FilePatch {
286296
delta += oldRowCount - hunk.getOldRowCount();
287297
}
288298

289-
return this.clone({
290-
oldFile: this.getOldPath() ? this.getOldFile() : this.getNewFile(),
291-
patch: this.getPatch().clone({hunks}),
292-
}).getUnstagePatch();
299+
if (this.hasTypechange() && this.getStatus() === 'added') {
300+
// handle special case when symlink is created where a file was deleted.
301+
// In order to unstage the line addition, we must ensure that the created file patch a modified status
302+
// and that oldFile is not a symlink
303+
const oldFile = this.getOldPath() ? this.getOldFile() : this.getNewFile();
304+
return this.clone({
305+
oldFile: oldFile.clone({mode: null, symlink: null}),
306+
patch: this.getPatch().clone({hunks, status: 'modified'}),
307+
}).getUnstagePatch();
308+
} else {
309+
return this.clone({
310+
oldFile: this.getOldPath() ? this.getOldFile() : this.getNewFile(),
311+
patch: this.getPatch().clone({hunks, status: 'modified'}),
312+
}).getUnstagePatch();
313+
}
293314
}
294315

295316
toString() {

test/controllers/file-patch-controller.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,74 @@ describe('FilePatchController', function() {
322322
}
323323
}
324324

325+
it('unstages added lines that don\'t require symlink change', async function() {
326+
const workingDirPath = await cloneRepository('symlinks');
327+
const repository = await buildRepository(workingDirPath);
328+
329+
// correctly handle symlinks on Windows
330+
repository.git.exec(['config', 'core.symlinks', 'true']);
331+
332+
const deletedSymlinkAddedFilePath = 'symlink.txt';
333+
fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath));
334+
fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8');
335+
336+
// Stage whole file
337+
await repository.stageFiles([deletedSymlinkAddedFilePath]);
338+
339+
const component = createComponent(repository, deletedSymlinkAddedFilePath);
340+
const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath, initialStagingStatus: 'staged'}));
341+
342+
// index shows symlink deltion and added lines
343+
assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n');
344+
assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644');
345+
346+
// Unstage a couple added lines, but not all
347+
await assert.async.isTrue(wrapper.find('HunkView').exists());
348+
const opPromise0 = switchboard.getFinishStageOperationPromise();
349+
const hunkView0 = wrapper.find('HunkView').at(0);
350+
hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1});
351+
hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {});
352+
window.dispatchEvent(new MouseEvent('mouseup'));
353+
hunkView0.find('button.github-HunkView-stageButton').simulate('click');
354+
await opPromise0;
355+
356+
repository.refresh();
357+
// index shows symlink deletions still staged, only a couple of lines have been unstaged
358+
assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644');
359+
assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nbaz\nzoo\n');
360+
});
361+
362+
it('stages deleted lines that don\'t require symlink change', async function() {
363+
const workingDirPath = await cloneRepository('symlinks');
364+
const repository = await buildRepository(workingDirPath);
365+
366+
const deletedFileAddedSymlinkPath = 'a.txt';
367+
fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath));
368+
fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath));
369+
370+
const component = createComponent(repository, deletedFileAddedSymlinkPath);
371+
const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath, initialStagingStatus: 'unstaged'}));
372+
373+
// index shows file is not a symlink, no deleted lines
374+
assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644');
375+
assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\nbar\nbaz\n\n');
376+
377+
// stage a couple of lines, but not all
378+
await assert.async.isTrue(wrapper.find('HunkView').exists());
379+
const opPromise0 = switchboard.getFinishStageOperationPromise();
380+
const hunkView0 = wrapper.find('HunkView').at(0);
381+
hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1});
382+
hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {});
383+
window.dispatchEvent(new MouseEvent('mouseup'));
384+
hunkView0.find('button.github-HunkView-stageButton').simulate('click');
385+
await opPromise0;
386+
387+
repository.refresh();
388+
// index shows symlink change has not been staged, a couple of lines have been deleted
389+
assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644');
390+
assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\n\n');
391+
});
392+
325393
it('stages symlink change when staging added lines that depend on change', async function() {
326394
const workingDirPath = await cloneRepository('symlinks');
327395
const repository = await buildRepository(workingDirPath);

0 commit comments

Comments
 (0)