diff --git a/components/commit/commit.js b/components/commit/commit.js index 028ce110d..d5c8b0d08 100644 --- a/components/commit/commit.js +++ b/components/commit/commit.js @@ -55,8 +55,8 @@ class CommitViewModel { this.authorDateFromNow(this.authorDate().fromNow()); this.authorName(args.authorName); this.authorEmail(args.authorEmail); - this.numberOfAddedLines(args.fileLineDiffs.length > 0 ? args.fileLineDiffs[0][0] : 0); - this.numberOfRemovedLines(args.fileLineDiffs.length > 0 ? args.fileLineDiffs[0][1] : 0); + this.numberOfAddedLines(args.total.additions); + this.numberOfRemovedLines(args.total.deletions); this.fileLineDiffs(args.fileLineDiffs); this.isInited = true; this.commitDiff = ko.observable(components.create('commitDiff', { diff --git a/components/commitdiff/commitdiff.html b/components/commitdiff/commitdiff.html index 1da08fd46..8cde60b26 100644 --- a/components/commitdiff/commitdiff.html +++ b/components/commitdiff/commitdiff.html @@ -19,7 +19,7 @@
- + ( diff --git a/components/commitdiff/commitdiff.js b/components/commitdiff/commitdiff.js index 84ff2c0f9..eabd7bdb2 100644 --- a/components/commitdiff/commitdiff.js +++ b/components/commitdiff/commitdiff.js @@ -14,7 +14,6 @@ class CommitDiff { this.wordWrap = args.wordWrap = args.wordWrap || components.create('textdiff.wordwrap'); this.whiteSpace = args.whiteSpace = args.whiteSpace || components.create('textdiff.whitespace'); - args.fileLineDiffs.shift(); // remove first line that has "total" this.loadFileLineDiffs(args); } diff --git a/components/commitdiff/commitlinediff.js b/components/commitdiff/commitlinediff.js index eae9d9226..4a3ff374b 100644 --- a/components/commitdiff/commitlinediff.js +++ b/components/commitdiff/commitlinediff.js @@ -4,10 +4,12 @@ const programEvents = require('ungit-program-events'); class CommitLineDiff { constructor(args, fileLineDiff) { - this.added = ko.observable(fileLineDiff[0]); - this.removed = ko.observable(fileLineDiff[1]); - this.fileName = ko.observable(fileLineDiff[2]); - this.fileType = fileLineDiff[3]; + this.added = ko.observable(fileLineDiff.additions); + this.removed = ko.observable(fileLineDiff.deletions); + this.fileName = ko.observable(fileLineDiff.fileName); + this.oldFileName = ko.observable(fileLineDiff.oldFileName); + this.displayName = ko.observable(fileLineDiff.displayName); + this.fileType = fileLineDiff.type; this.isShowingDiffs = ko.observable(false); this.repoPath = args.repoPath; this.server = args.server; @@ -21,6 +23,7 @@ class CommitLineDiff { getSpecificDiff() { return components.create(`${this.fileType}diff`, { filename: this.fileName(), + oldFilename: this.oldFileName(), repoPath: this.repoPath, server: this.server, sha1: this.sha1, diff --git a/components/imagediff/imagediff.js b/components/imagediff/imagediff.js index ec13f9e64..7841c8e6e 100644 --- a/components/imagediff/imagediff.js +++ b/components/imagediff/imagediff.js @@ -7,6 +7,7 @@ components.register('imagediff', args => new ImageDiffViewModel(args)); class ImageDiffViewModel { constructor(args) { this.filename = args.filename; + this.oldFilename = args.oldFilename; this.repoPath = args.repoPath; this.isNew = ko.observable(false); this.isRemoved = ko.observable(false); @@ -16,9 +17,9 @@ class ImageDiffViewModel { if (this.isRemoved()) return 'removed'; return 'changed'; }); - const gitDiffURL = `${ungit.config.rootPath}/api/diff/image?path=${encodeURIComponent(this.repoPath())}&filename=${this.filename}&version=`; - this.oldImageSrc = gitDiffURL + (this.sha1 ? this.sha1 + '^' : 'HEAD'); - this.newImageSrc = gitDiffURL + (this.sha1 ? this.sha1 : 'current'); + const gitDiffURL = `${ungit.config.rootPath}/api/diff/image?path=${encodeURIComponent(this.repoPath())}`; + this.oldImageSrc = gitDiffURL + `&filename=${this.oldFilename}&version=${(this.sha1 ? this.sha1 + '^' : 'HEAD')}`; + this.newImageSrc = gitDiffURL + `&filename=${this.filename}&version=${(this.sha1 ? this.sha1 : 'current')}`; this.isShowingDiffs = args.isShowingDiffs; this.rightArrowIcon = octicons['arrow-right'].toSVG({ 'height': 100 }); this.downArrowIcon = octicons['arrow-down'].toSVG({ 'height': 100 }); diff --git a/components/staging/staging.js b/components/staging/staging.js index ffb55e582..1d83fd5eb 100644 --- a/components/staging/staging.js +++ b/components/staging/staging.js @@ -175,16 +175,16 @@ class StagingViewModel { setFiles(files) { const newFiles = []; - for(const file in files) { - let fileViewModel = this.filesByPath[file]; + for(let fileStatus of Object.values(files)) { + let fileViewModel = this.filesByPath[fileStatus.fileName]; if (!fileViewModel) { - this.filesByPath[file] = fileViewModel = new FileViewModel(this, file); + this.filesByPath[fileStatus.fileName] = fileViewModel = new FileViewModel(this, fileStatus.fileName, fileStatus.oldFileName, fileStatus.displayName); } else { // this is mainly for patching and it may not fire due to the fact that // '/commit' triggers working-tree-changed which triggers throttled refresh fileViewModel.diff().invalidateDiff(); } - fileViewModel.setState(files[file]); + fileViewModel.setState(fileStatus); newFiles.push(fileViewModel); } this.files(newFiles); @@ -324,12 +324,13 @@ class StagingViewModel { } class FileViewModel { - constructor(staging, name) { + constructor(staging, name, oldName, displayName) { this.staging = staging; this.server = staging.server; this.editState = ko.observable('staged'); // staged, patched and none this.name = ko.observable(name); - this.displayName = ko.observable(name); + this.oldName = ko.observable(oldName); + this.displayName = ko.observable(displayName); this.isNew = ko.observable(false); this.removed = ko.observable(false); this.conflict = ko.observable(false); @@ -341,7 +342,7 @@ class FileViewModel { // only show modfied whe not removed, not conflicted, not new, not renamed // and length of additions and deletions is 0. return !this.removed() && !this.conflict() && !this.isNew() && - !this.renamed() && this.additions().length === 0 && this.deletions().length === 0; + this.additions().length === 0 && this.deletions().length === 0; }); this.fileType = ko.observable('text'); this.patchLineList = ko.observableArray(); @@ -366,6 +367,8 @@ class FileViewModel { getSpecificDiff() { return components.create(!this.name() || `${this.fileType()}diff`, { filename: this.name(), + oldFilename: this.oldName(), + displayFilename: this.displayName(), repoPath: this.staging.repoPath, server: this.server, textDiffType: this.staging.textDiffType, @@ -444,7 +447,6 @@ class FileViewModel { } toggleDiffs() { - if (this.renamed()) return; // do not show diffs for renames this.isShowingDiffs(!this.isShowingDiffs()); } diff --git a/components/textdiff/textdiff.js b/components/textdiff/textdiff.js index 0b6a684f9..980836ae8 100644 --- a/components/textdiff/textdiff.js +++ b/components/textdiff/textdiff.js @@ -62,6 +62,7 @@ class WhiteSpace { class TextDiffViewModel { constructor(args) { this.filename = args.filename; + this.oldFilename = args.oldFilename this.repoPath = args.repoPath; this.server = args.server; this.sha1 = args.sha1; @@ -99,6 +100,7 @@ class TextDiffViewModel { getDiffArguments() { return { file: this.filename, + oldFile: this.oldFilename, path: this.repoPath(), sha1: this.sha1 ? this.sha1 : '', whiteSpace: this.whiteSpace.value() diff --git a/source/git-api.js b/source/git-api.js index 0b6c978e1..5b90761ab 100644 --- a/source/git-api.js +++ b/source/git-api.js @@ -260,7 +260,7 @@ exports.registerApi = (env) => { app.get(`${exports.pathPrefix}/diff`, ensureAuthenticated, ensurePathExists, (req, res) => { const isIgnoreWhiteSpace = req.query.whiteSpace === "true" ? true : false; - jsonResultOrFailProm(res, gitPromise.diffFile(req.query.path, req.query.file, req.query.sha1, isIgnoreWhiteSpace)); + jsonResultOrFailProm(res, gitPromise.diffFile(req.query.path, req.query.file, req.query.oldFile, req.query.sha1, isIgnoreWhiteSpace)); }); app.get(`${exports.pathPrefix}/diff/image`, ensureAuthenticated, ensurePathExists, (req, res) => { @@ -327,11 +327,11 @@ exports.registerApi = (env) => { }); app.get(`${exports.pathPrefix}/show`, ensureAuthenticated, (req, res) => { - jsonResultOrFailProm(res, gitPromise(['show', '--numstat', req.query.sha1], req.query.path).then(gitParser.parseGitLog)); + jsonResultOrFailProm(res, gitPromise(['show', '--numstat', '-z', req.query.sha1], req.query.path).then(gitParser.parseGitLog)); }); app.get(`${exports.pathPrefix}/head`, ensureAuthenticated, ensurePathExists, (req, res) => { - const task = gitPromise(['log', '--decorate=full', '--pretty=fuller', '--parents', '--max-count=1'], req.query.path) + const task = gitPromise(['log', '--decorate=full', '--pretty=fuller', '-z', '--parents', '--max-count=1'], req.query.path) .then(gitParser.parseGitLog) .catch((err) => { if (err.stderr.indexOf('fatal: bad default revision \'HEAD\'') == 0) @@ -614,7 +614,7 @@ exports.registerApi = (env) => { }); app.get(`${exports.pathPrefix}/stashes`, ensureAuthenticated, ensurePathExists, (req, res) => { - const task = gitPromise(['stash', 'list', '--decorate=full', '--pretty=fuller', '--parents', '--numstat'], req.query.path) + const task = gitPromise(['stash', 'list', '--decorate=full', '--pretty=fuller', '-z', '--parents', '--numstat'], req.query.path) .then(gitParser.parseGitLog); jsonResultOrFailProm(res, task); }); diff --git a/source/git-parser.js b/source/git-parser.js index 627eb16f4..5d1896290 100644 --- a/source/git-parser.js +++ b/source/git-parser.js @@ -3,24 +3,36 @@ const fileType = require('./utils/file-type.js'); const _ = require('lodash'); exports.parseGitStatus = (text, args) => { - const lines = text.split('\n'); + const lines = text.split('\x00'); const files = {}; // skipping first line... - lines.slice(1).forEach((line) => { - if (line == '') return; + let lineIterator = lines.slice(1).values(); + + for(let line of lineIterator){ + if (line == '') continue; const status = line.slice(0, 2); - const filename = line.slice(3).trim().replace(/^"(.*)"$/, '$1'); // may contain old and renamed file name. - const finalFilename = status[0] == 'R' ? filename.slice(filename.indexOf('>') + 2) : filename; - files[finalFilename] = { - displayName: filename, + const newFileName = line.slice(3).trim(); + let oldFileName; + let displayName; + if (status[0] == 'R') { + oldFileName = lineIterator.next().value + displayName = `${oldFileName} → ${newFileName}`; + } else { + oldFileName = newFileName; + displayName = newFileName; + } + files[newFileName] = { + fileName: newFileName, + oldFileName: oldFileName, + displayName: displayName, staged: status[0] == 'A' || status[0] == 'M', removed: status[0] == 'D' || status[1] == 'D', isNew: (status[0] == '?' || status[0] == 'A') && !(status[0] == 'D' || status[1] == 'D'), conflict: (status[0] == 'A' && status[1] == 'A') || status[0] == 'U' || status[1] == 'U', renamed: status[0] == 'R', - type: fileType(finalFilename) + type: fileType(newFileName) }; - }); + } return { isMoreToLoad: false, @@ -30,16 +42,19 @@ exports.parseGitStatus = (text, args) => { }; }; +const fileChangeRegex = /(?[\d-]+)\t(?[\d-]+)\t((?[^\x00]+?)\x00|\x00(?[^\x00]+?)\x00(?[^\x00]+?)\x00)/g; + exports.parseGitStatusNumstat = (text) => { const result = {}; - text.split('\n').forEach((line) => { - if (line == '') return; - const parts = line.split('\t'); - result[parts[2]] = { - additions: parts[0], - deletions: parts[1] + fileChangeRegex.lastIndex = 0; + let match = fileChangeRegex.exec(text); + while (match !== null) { + result[match.groups.fileName || match.groups.newFileName] = { + additions: match.groups.additions, + deletions: match.groups.deletions }; - }); + match = fileChangeRegex.exec(text); + } return result; }; @@ -141,24 +156,50 @@ exports.parseGitLog = (data) => { currentCommmit.message += row.trim(); } const parseFileChanges = (row, index) => { - if (rows.length === index + 1 || rows[index + 1] && rows[index + 1].indexOf('commit ') === 0) { - const total = [0, 0, 'Total']; - for (let n = 0; n < currentCommmit.fileLineDiffs.length; n++) { - const fileLineDiff = currentCommmit.fileLineDiffs[n]; - if (!isNaN(parseInt(fileLineDiff[0], 10))) { - total[0] += fileLineDiff[0] = parseInt(fileLineDiff[0], 10); - } - if (!isNaN(parseInt(fileLineDiff[1], 10))) { - total[1] += fileLineDiff[1] = parseInt(fileLineDiff[1], 10); - } + // git log is using -z so all the file changes are on one line + // merge commits start the file changes with a null + if (row[0] === '\x00'){ + row = row.slice(1); + } + fileChangeRegex.lastIndex = 0; + while (row[fileChangeRegex.lastIndex] && row[fileChangeRegex.lastIndex] !== '\x00') { + let match = fileChangeRegex.exec(row); + let fileName = match.groups.fileName || match.groups.newFileName; + let oldFileName = match.groups.oldFileName || match.groups.fileName; + let displayName; + if(match.groups.oldFileName) { + displayName = `${match.groups.oldFileName} → ${match.groups.newFileName}`; + } else { + displayName = fileName; } - currentCommmit.fileLineDiffs.splice(0, 0, total); - parser = parseCommitLine; - return; + currentCommmit.fileLineDiffs.push({ + additions: match.groups.additions, + deletions: match.groups.deletions, + fileName: fileName, + oldFileName: oldFileName, + displayName: displayName, + type: fileType(fileName) + }); + } + const nextRow = row.slice(fileChangeRegex.lastIndex + 1); + const total = { + additions: 0, + deletions: 0 + }; + for (let fileLineDiff of currentCommmit.fileLineDiffs) { + if (!isNaN(parseInt(fileLineDiff.additions, 10))) { + total.additions += fileLineDiff.additions = parseInt(fileLineDiff.additions, 10); + } + if (!isNaN(parseInt(fileLineDiff.deletions, 10))) { + total.deletions += fileLineDiff.deletions = parseInt(fileLineDiff.deletions, 10); + } + } + currentCommmit.total = total; + parser = parseCommitLine; + if (nextRow) { + parser(nextRow, index); } - const splitted = row.split('\t'); - splitted.push(fileType(splitted[2])); - currentCommmit.fileLineDiffs.push(splitted); + return; } let parser = parseCommitLine; const rows = data.split('\n'); diff --git a/source/git-promise.js b/source/git-promise.js index 1a49fe803..96cf49499 100644 --- a/source/git-promise.js +++ b/source/git-promise.js @@ -177,17 +177,17 @@ const getGitError = (args, stderr, stdout) => { git.status = (repoPath, file) => { return Bluebird.props({ - numStatsStaged: git([gitOptionalLocks, 'diff', '--no-renames', '--numstat', '--cached', '--', (file || '')], repoPath) + numStatsStaged: git([gitOptionalLocks, 'diff', '--numstat', '--cached', '-z', '--', (file || '')], repoPath) .then(gitParser.parseGitStatusNumstat), numStatsUnstaged: Bluebird.resolve().then(() => { if (config.isEnableNumStat) { - return git([gitOptionalLocks, 'diff', '--no-renames', '--numstat', '--', (file || '')], repoPath) + return git([gitOptionalLocks, 'diff', '--numstat', '-z', '--', (file || '')], repoPath) .then(gitParser.parseGitStatusNumstat); } else { return {} } }), - status: git([gitOptionalLocks, 'status', '-s', '-b', '-u', (file || '')], repoPath) + status: git([gitOptionalLocks, 'status', '-s', '-b', '-u', '-z', (file || '')], repoPath) .then(gitParser.parseGitStatus) .then((status) => { return Bluebird.props({ @@ -282,11 +282,15 @@ git.binaryFileContent = (repoPath, filename, version, outPipe) => { return git(['show', `${version}:${filename}`], repoPath, null, outPipe); } -git.diffFile = (repoPath, filename, sha1, ignoreWhiteSpace) => { +git.diffFile = (repoPath, filename, oldFilename, sha1, ignoreWhiteSpace) => { if (sha1) { return git(['rev-list', '--max-parents=0', sha1], repoPath).then((initialCommitSha1) => { let prevSha1 = sha1 == initialCommitSha1.trim() ? gitEmptyReproSha1 : `${sha1}^`; - return git(['diff', ignoreWhiteSpace ? '-w' : '', prevSha1, sha1, "--", filename.trim()], repoPath); + if (oldFilename && oldFilename !== filename) { + return git(['diff', ignoreWhiteSpace ? '-w' : '', `${prevSha1}:${oldFilename.trim()}`, `${sha1}:${filename.trim()}`], repoPath); + } else { + return git(['diff', ignoreWhiteSpace ? '-w' : '', prevSha1, sha1, "--", filename.trim()], repoPath); + } }); } @@ -304,6 +308,8 @@ git.diffFile = (repoPath, filename, sha1, ignoreWhiteSpace) => { } else { if (file && file.isNew) { return git(['diff', '--no-index', isWindows ? 'NUL' : '/dev/null', filename.trim()], repoPath, true); + } else if (file && file.renamed) { + return git(['diff', ignoreWhiteSpace ? '-w' : '', `HEAD:${oldFilename}`, filename.trim()], repoPath); } else { return git(['diff', ignoreWhiteSpace ? '-w' : '', 'HEAD', '--', filename.trim()], repoPath); } @@ -437,7 +443,7 @@ git.revParse = (repoPath) => { } git.log = (path, limit, skip, maxActiveBranchSearchIteration) => { - return git(['log', '--cc', '--decorate=full', '--show-signature', '--date=default', '--pretty=fuller', '--branches', '--tags', '--remotes', '--parents', '--no-notes', '--numstat', '--date-order', `--max-count=${limit}`, `--skip=${skip}`], path) + return git(['log', '--cc', '--decorate=full', '--show-signature', '--date=default', '--pretty=fuller', '-z', '--branches', '--tags', '--remotes', '--parents', '--no-notes', '--numstat', '--date-order', `--max-count=${limit}`, `--skip=${skip}`], path) .then(gitParser.parseGitLog) .then((log) => { log = log ? log : []; diff --git a/test/spec.git-api.branching.js b/test/spec.git-api.branching.js index 5e497184f..ef6ba6108 100644 --- a/test/spec.git-api.branching.js +++ b/test/spec.git-api.branching.js @@ -134,9 +134,12 @@ describe('git-api branching', function () { it('status should list the changed file', () => { return common.get(req, '/status', { path: testDir }).then(res => { + expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile1]).to.eql({ displayName: testFile1, + fileName: testFile1, + oldFileName: testFile1, isNew: false, staged: false, removed: false, diff --git a/test/spec.git-api.conflict.js b/test/spec.git-api.conflict.js index 8215612fc..38fac3e35 100644 --- a/test/spec.git-api.conflict.js +++ b/test/spec.git-api.conflict.js @@ -55,6 +55,8 @@ describe('git-api conflict rebase', function () { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile1]).to.eql({ displayName: testFile1, + fileName: testFile1, + oldFileName: testFile1, isNew: false, staged: false, removed: false, @@ -121,6 +123,8 @@ describe('git-api conflict checkout', function() { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile1]).to.eql({ displayName: testFile1, + fileName: testFile1, + oldFileName: testFile1, isNew: false, staged: false, removed: false, @@ -176,6 +180,8 @@ describe('git-api conflict merge', function () { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile1]).to.eql({ displayName: testFile1, + fileName: testFile1, + oldFileName: testFile1, isNew: false, staged: false, removed: false, @@ -204,9 +210,16 @@ describe('git-api conflict merge', function () { return common.get(req, '/gitlog', { path: testDir }).then((res) => { expect(res.nodes).to.be.a('array'); expect(res.nodes.length).to.be(4); - expect(res.nodes[0].fileLineDiffs.length).to.be(2); - expect(res.nodes[0].fileLineDiffs[0]).to.eql([1, 1, 'Total']); - expect(res.nodes[0].fileLineDiffs[1]).to.eql([1, 1, testFile1, 'text']); + expect(res.nodes[0].total).to.eql({additions: 1, deletions: 1}); + expect(res.nodes[0].fileLineDiffs.length).to.be(1); + expect(res.nodes[0].fileLineDiffs[0]).to.eql({ + additions: 1, + deletions: 1, + fileName: testFile1, + oldFileName: testFile1, + displayName: testFile1, + type: 'text' + }); }); }); @@ -255,6 +268,8 @@ describe('git-api conflict solve by deleting', function () { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile1]).to.eql({ displayName: testFile1, + fileName: testFile1, + oldFileName: testFile1, isNew: false, staged: false, removed: false, diff --git a/test/spec.git-api.diff.js b/test/spec.git-api.diff.js index d737a4637..82ae75cf0 100644 --- a/test/spec.git-api.diff.js +++ b/test/spec.git-api.diff.js @@ -24,6 +24,7 @@ describe('git-api diff', () => { after(() => common.post(req, '/testing/cleanup', undefined)); const testFile = 'afile.txt'; + const testFile2 = 'anotherfile.txt'; const testImage = 'icon.png' it('diff on non existing file should fail', () => { @@ -56,10 +57,10 @@ describe('git-api diff', () => { }); it('should be possible to commit a file', () => { - return common.post(req, '/commit', { path: testDir, message: "Init", files: [{ name: testFile }] }); + return common.post(req, '/commit', { path: testDir, message: "Init File", files: [{ name: testFile }] }); }); it('should be possible to commit an image file', () => { - return common.post(req, '/commit', { path: testDir, message: "Init", files: [{ name: testImage }] }); + return common.post(req, '/commit', { path: testDir, message: "Init Image", files: [{ name: testImage }] }); }); it('diff on first commit should work', () => { @@ -123,12 +124,35 @@ describe('git-api diff', () => { .then((res) => expect(res.toString()).to.be('png')); }); - it('should be possible to commit a file', () => { - return common.post(req, '/commit', { path: testDir, message: "Init", files: [{ name: testFile }] }); + it('should be possible to rename a modified file', () => { + return common.post(req, '/testing/git', { repo: testDir, command: ['mv', testFile, testFile2] }); + }); + + it('diff on renamed and modified file should work', () => { + return common.get(req, '/diff', { path: testDir, file: testFile2, oldFile: testFile}).then((res) => { + expect(res.indexOf('diff --git a/afile.txt b/anotherfile.txt')).to.be.above(-1); + expect(res.indexOf('+more')).to.be.above(-1); + }); + }); + + it('should be possible to commit the renamed and modified file', () => { + return common.post(req, '/commit', { path: testDir, message: "Move and Change", files: [{ name: testFile2 }] }); + }); + + it('diff on commit with renamed and modified file should work', () => { + return common.get(req, '/gitlog', { path: testDir }) + .then((res) => { + expect(res.nodes.length).to.be(3); + return common.get(req, '/diff', { path: testDir, file: testFile2, oldFile:testFile, sha1: res.nodes[0].sha1 }); + }).then((res) => { + for (let i = 0; i < content.length; i++) { + expect(res.indexOf(content[i])).to.be.above(-1); + } + }); }); it('removing a test file should work', () => { - return common.post(req, '/testing/removefile', { file: path.join(testDir, testFile) }); + return common.post(req, '/testing/removefile', { file: path.join(testDir, testFile2) }); }); it('should be possible to commit an image file', () => { @@ -139,7 +163,7 @@ describe('git-api diff', () => { }); it('diff on removed file should work', () => { - return common.get(req, '/diff', { path: testDir, file: testFile }) + return common.get(req, '/diff', { path: testDir, file: testFile2 }) .then((res) => { expect(res.indexOf('deleted file')).to.be.above(-1); expect(res.indexOf("@@ -1,6 +0,0 @@")).to.be.above(-1); @@ -158,7 +182,7 @@ describe('git-api diff', () => { .then(() => common.get(req, '/gitlog', { path: testDir })) .then((res) => { // find a commit which contains the testFile - const commit = res.nodes.filter((commit) => commit.fileLineDiffs.some((lineDiff) => lineDiff[2] == testFile))[0]; + const commit = res.nodes.filter((commit) => commit.fileLineDiffs.some((lineDiff) => lineDiff.fileName == testFile))[0]; return common.get(req, '/diff', { path: testDir, sha1: commit.sha1, file: testFile }) }); }); diff --git a/test/spec.git-api.js b/test/spec.git-api.js index c801f6296..94cfd96b6 100644 --- a/test/spec.git-api.js +++ b/test/spec.git-api.js @@ -125,6 +125,8 @@ describe('git-api', () => { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile]).to.eql({ displayName: testFile, + fileName: testFile, + oldFileName: testFile, isNew: true, staged: false, removed: false, @@ -184,6 +186,8 @@ describe('git-api', () => { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile]).to.eql({ displayName: testFile, + fileName: testFile, + oldFileName: testFile, isNew: false, staged: false, removed: false, @@ -224,6 +228,8 @@ describe('git-api', () => { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile2]).to.eql({ displayName: testFile2, + fileName: testFile2, + oldFileName: testFile2, isNew: true, staged: false, removed: false, @@ -267,6 +273,8 @@ describe('git-api', () => { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile3]).to.eql({ displayName: testFile3, + fileName: testFile3, + oldFileName: testFile3, isNew: true, staged: false, removed: false, @@ -314,6 +322,8 @@ describe('git-api', () => { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile]).to.eql({ displayName: testFile, + fileName: testFile, + oldFileName: testFile, isNew: false, staged: false, removed: true, @@ -348,14 +358,16 @@ describe('git-api', () => { return common.get(req, '/status', { path: testDir }).then((res) => { expect(Object.keys(res.files).length).to.be(1); expect(res.files[testFile4]).to.eql({ - displayName: `${testFile3} -> ${testFile4}`, + displayName: `${testFile3} → ${testFile4}`, + fileName: testFile4, + oldFileName: testFile3, isNew: false, staged: false, removed: false, conflict: false, renamed: true, type: 'text', - additions: '2', + additions: '0', deletions: '0' }); }); diff --git a/test/spec.git-api.submodule.js b/test/spec.git-api.submodule.js index 9c6d1caf8..507844970 100644 --- a/test/spec.git-api.submodule.js +++ b/test/spec.git-api.submodule.js @@ -83,6 +83,8 @@ describe('git-api submodule', function () { expect(Object.keys(res.files).length).to.be(1); expect(res.files[submodulePath]).to.eql({ displayName: submodulePath, + fileName: submodulePath, + oldFileName: submodulePath, isNew: false, staged: false, removed: false, diff --git a/test/spec.git-parser.js b/test/spec.git-parser.js index 4741c7e2c..aea63424f 100644 --- a/test/spec.git-parser.js +++ b/test/spec.git-parser.js @@ -184,7 +184,7 @@ describe('git-parser parseGitLog', () => { }) }); it('parses multiple commits in a row', () => { - const gitLog = dedent` + const gitLog = dedent(` commit 5867e2766b0a0f81ad59ce9e9895d9b1a3523aa4 37d1154434b70854ed243967e0d7e37aa3564551 (HEAD -> refs/heads/git-parser-specs) Author: Test ungit AuthorDate: Fri Jan 4 14:54:06 2019 +0100 @@ -193,10 +193,7 @@ describe('git-parser parseGitLog', () => { parseGitLog + gix reflox parsing - 1 1 source/git-parser.js - 175 0 test/spec.git-parser.js - - commit 37d1154434b70854ed243967e0d7e37aa3564551 d58c8e117fc257520d90b099fd2c6acd7c1e8861 + 1 1 source/git-parser.js\x00175 0 test/spec.git-parser.js\x00\x00commit 37d1154434b70854ed243967e0d7e37aa3564551 d58c8e117fc257520d90b099fd2c6acd7c1e8861 Author: Test ungit AuthorDate: Fri Jan 4 14:03:56 2019 +0100 Commit: Test ungit @@ -204,8 +201,7 @@ describe('git-parser parseGitLog', () => { submodules parser - 32 0 test/spec.git-parser.js\n - ` + 32 0 test/spec.git-parser.js\x00\x00`) const res = gitParser.parseGitLog(gitLog) expect(res[0]).to.eql({ @@ -215,10 +211,27 @@ describe('git-parser parseGitLog', () => { commitDate: "Fri Jan 4 14:54:06 2019 +0100", committerEmail: "test@example.com", committerName: "Test ungit", + total: { + "additions": 176, + "deletions": 1 + }, fileLineDiffs: [ - [176, 1, "Total"], - [1, 1, "source/git-parser.js", "text"], - [175, 0, "test/spec.git-parser.js", "text"], + { + "additions": 1, + "deletions": 1, + "displayName": "source/git-parser.js", + "fileName": "source/git-parser.js", + "oldFileName": "source/git-parser.js", + "type": "text" + }, + { + "additions": 175, + "deletions": 0, + "displayName": "test/spec.git-parser.js", + "fileName": "test/spec.git-parser.js", + "oldFileName": "test/spec.git-parser.js", + "type": "text" + } ], isHead: true, message: "parseGitLog + gix reflox parsing", @@ -238,9 +251,19 @@ describe('git-parser parseGitLog', () => { commitDate: "Fri Jan 4 14:03:56 2019 +0100", committerEmail: "test@example.com", committerName: "Test ungit", + total: { + "additions": 32, + "deletions": 0 + }, fileLineDiffs: [ - [32, 0, "Total"], - [32, 0, "test/spec.git-parser.js", "text"], + { + "additions": 32, + "deletions": 0, + "displayName": "test/spec.git-parser.js", + "fileName": "test/spec.git-parser.js", + "oldFileName": "test/spec.git-parser.js", + "type": "text" + } ], isHead: false, message: "submodules parser", @@ -252,7 +275,7 @@ describe('git-parser parseGitLog', () => { }) }); it('parses reflog commits without email', () => { - const gitLog = dedent` + const gitLog = dedent(` commit 37d11544 d58c8e11 (HEAD -> refs/heads/git-parser-specs) Reflog: git-parser-specs@{Fri Jan 4 14:03:56 2019 +0100} (Test ungit) Reflog message: commit: submodules parser @@ -263,8 +286,7 @@ describe('git-parser parseGitLog', () => { submodules parser - 32 0 test/spec.git-parser.js\n - ` + 32 0 test/spec.git-parser.js\x00\x00`) expect(gitParser.parseGitLog(gitLog)[0]).to.eql({ authorDate: "Fri Jan 4 14:03:56 2019 +0100", @@ -273,9 +295,19 @@ describe('git-parser parseGitLog', () => { commitDate: "Fri Jan 4 14:03:56 2019 +0100", committerEmail: "test@example.com", committerName: "Test ungit", + total: { + "additions": 32, + "deletions": 0 + }, fileLineDiffs: [ - [32, 0, "Total"], - [32, 0, "test/spec.git-parser.js", "text"] + { + "additions": 32, + "deletions": 0, + "displayName": "test/spec.git-parser.js", + "fileName": "test/spec.git-parser.js", + "oldFileName": "test/spec.git-parser.js", + "type": "text" + } ], isHead: true, message: "submodules parser", @@ -293,7 +325,7 @@ describe('git-parser parseGitLog', () => { }) }); it('parses reflog commits', () => { - const gitLog = dedent` + const gitLog = dedent(` commit 37d11544 d58c8e11 (HEAD -> refs/heads/git-parser-specs) Reflog: git-parser-specs@{Fri Jan 4 14:03:56 2019 +0100} (Test ungit ) Reflog message: commit: submodules parser @@ -304,8 +336,7 @@ describe('git-parser parseGitLog', () => { submodules parser - 32 0 test/spec.git-parser.js\n - ` + 32 0 test/spec.git-parser.js\x00\x00`) expect(gitParser.parseGitLog(gitLog)[0]).to.eql({ authorDate: "Fri Jan 4 14:03:56 2019 +0100", @@ -314,9 +345,19 @@ describe('git-parser parseGitLog', () => { commitDate: "Fri Jan 4 14:03:56 2019 +0100", committerEmail: "test@example.com", committerName: "Test ungit", + total: { + "additions": 32, + "deletions": 0 + }, fileLineDiffs: [ - [32, 0, "Total"], - [32, 0, "test/spec.git-parser.js", "text"] + { + "additions": 32, + "deletions": 0, + "displayName": "test/spec.git-parser.js", + "fileName": "test/spec.git-parser.js", + "oldFileName": "test/spec.git-parser.js", + "type": "text" + } ], isHead: true, message: "submodules parser", @@ -389,7 +430,7 @@ describe('git-parser parseGitLog', () => { }); }); it('parses the git log', () => { - const gitLog = dedent` + const gitLog = dedent(` commit 37d1154434b70854ed243967e0d7e37aa3564551 d58c8e117fc257520d90b099fd2c6acd7c1e8861 (HEAD -> refs/heads/git-parser-specs) Author: Test ungit AuthorDate: Fri Jan 4 14:03:56 2019 +0100 @@ -398,15 +439,24 @@ describe('git-parser parseGitLog', () => { submodules parser - 32 0 test/spec.git-parser.js\n - ` + 32 0 test/spec.git-parser.js\x00\x00`) expect(gitParser.parseGitLog(gitLog)[0]).to.eql( { refs: [ 'HEAD', 'refs/heads/git-parser-specs' ], + total: { + "additions": 32, + "deletions": 0 + }, fileLineDiffs: [ - [32, 0, "Total"], - [32, 0, "test/spec.git-parser.js", "text"] + { + "additions": 32, + "deletions": 0, + "displayName": "test/spec.git-parser.js", + "fileName": "test/spec.git-parser.js", + "oldFileName": "test/spec.git-parser.js", + "type": "text" + } ], sha1: '37d1154434b70854ed243967e0d7e37aa3564551', parents: [ 'd58c8e117fc257520d90b099fd2c6acd7c1e8861' ], @@ -628,11 +678,7 @@ describe('parseGitLsRemote', () => { describe('parseGitStatusNumstat', () => { it('parses the git status numstat', () => { - const gitStatusNumstat = dedent` - 1459 202 package-lock.json - 2 1 package.json - 13 0 test/spec.git-parser.js - ` + const gitStatusNumstat = `1459 202 package-lock.json\x002 1 package.json\x0013 0 test/spec.git-parser.js\x00` expect(gitParser.parseGitStatusNumstat(gitStatusNumstat)).to.eql({ "package-lock.json": { additions: "1459", deletions: "202" }, @@ -641,13 +687,12 @@ describe('parseGitStatusNumstat', () => { }); }) it('skips empty lines', () => { - const gitStatusNumstat = dedent` - 1459 202 package-lock.json + const gitStatusNumstat = dedent(` + 1459 202 package-lock.json\x00 - 2 1 package.json - 13 0 test/spec.git-parser.js - ` + 2 1 package.json\x0013 0 test/spec.git-parser.js\x00 + `) expect(gitParser.parseGitStatusNumstat(gitStatusNumstat)).to.eql({ "package-lock.json": { additions: "1459", deletions: "202" }, @@ -659,69 +704,66 @@ describe('parseGitStatusNumstat', () => { describe('parseGitStatus', () => { it('parses git status', () => { - const gitStatus = dedent` - ## git-parser-specs - A file1.js - M file2.js - D file3.js - D file4.js - U file5.js - U file6.js - AA file7.js - ? file8.js - A file9.js - ?D file10.js - AD file11.js - M file12.js - ?? file13.js - - R ../source/sysinfo.js -> ../source/sys.js - ` + const gitStatus = `## git-parser-specs\x00` + + `A file1.js\x00` + + `M file2.js\x00` + + `D file3.js\x00` + + ` D file4.js\x00` + + ` U file5.js\x00` + + `U file6.js\x00` + + `AA file7.js\x00` + + `? file8.js\x00` + + `A file9.js\x00` + + `?D file10.js\x00` + + `AD file11.js\x00` + + ` M file12.js\x00` + + `?? file13.js\x00` + + `R ../source/sys.js\x00../source/sysinfo.js\x00` expect(gitParser.parseGitStatus(gitStatus)).to.eql({ branch: "git-parser-specs", files: { "../source/sys.js": { - conflict: false, displayName: "../source/sysinfo.js -> ../source/sys.js", isNew: false, removed: false, renamed: true, staged: false, type: "text" + conflict: false, displayName: "../source/sysinfo.js → ../source/sys.js", fileName: "../source/sys.js", oldFileName: "../source/sysinfo.js", isNew: false, removed: false, renamed: true, staged: false, type: "text" }, "file1.js": { - conflict: false, displayName: "file1.js", isNew: true, removed: false, renamed: false, staged: true, type: "text" + conflict: false, displayName: "file1.js", fileName: "file1.js", oldFileName: "file1.js", isNew: true, removed: false, renamed: false, staged: true, type: "text" }, "file2.js": { - conflict: false, displayName: "file2.js", isNew: false, removed: false, renamed: false, staged: true, type: "text" + conflict: false, displayName: "file2.js", fileName: "file2.js", oldFileName: "file2.js", isNew: false, removed: false, renamed: false, staged: true, type: "text" }, "file3.js": { - conflict: false, displayName: "file3.js", isNew: false, removed: true, renamed: false, staged: false, type: "text" + conflict: false, displayName: "file3.js", fileName: "file3.js", oldFileName: "file3.js", isNew: false, removed: true, renamed: false, staged: false, type: "text" }, "file4.js": { - conflict: false, displayName: "file4.js", isNew: false, removed: true, renamed: false, staged: false, type: "text" + conflict: false, displayName: "file4.js", fileName: "file4.js", oldFileName: "file4.js", isNew: false, removed: true, renamed: false, staged: false, type: "text" }, "file5.js": { - conflict: true, displayName: "file5.js", isNew: false, removed: false, renamed: false, staged: false, type: "text" + conflict: true, displayName: "file5.js", fileName: "file5.js", oldFileName: "file5.js", isNew: false, removed: false, renamed: false, staged: false, type: "text" }, "file6.js": { - conflict: true, displayName: "file6.js", isNew: false, removed: false, renamed: false, staged: false, type: "text" + conflict: true, displayName: "file6.js", fileName: "file6.js", oldFileName: "file6.js", isNew: false, removed: false, renamed: false, staged: false, type: "text" }, "file7.js": { - conflict: true, displayName: "file7.js", isNew: true, removed: false, renamed: false, staged: true, type: "text" + conflict: true, displayName: "file7.js", fileName: "file7.js", oldFileName: "file7.js", isNew: true, removed: false, renamed: false, staged: true, type: "text" }, "file8.js": { - conflict: false, displayName: "file8.js", isNew: true, removed: false, renamed: false, staged: false, type: "text" + conflict: false, displayName: "file8.js", fileName: "file8.js", oldFileName: "file8.js", isNew: true, removed: false, renamed: false, staged: false, type: "text" }, "file9.js": { - conflict: false, displayName: "file9.js", isNew: true, removed: false, renamed: false, staged: true, type: "text" + conflict: false, displayName: "file9.js", fileName: "file9.js", oldFileName: "file9.js", isNew: true, removed: false, renamed: false, staged: true, type: "text" }, "file10.js": { - conflict: false, displayName: "file10.js", isNew: false, removed: true, renamed: false, staged: false, type: "text" + conflict: false, displayName: "file10.js", fileName: "file10.js", oldFileName: "file10.js", isNew: false, removed: true, renamed: false, staged: false, type: "text" }, "file11.js": { - conflict: false, displayName: "file11.js", isNew: false, removed: true, renamed: false, staged: true, type: "text" + conflict: false, displayName: "file11.js", fileName: "file11.js", oldFileName: "file11.js", isNew: false, removed: true, renamed: false, staged: true, type: "text" }, "file12.js": { - conflict: false, displayName: "file12.js", isNew: false, removed: false, renamed: false, staged: false, type: "text" + conflict: false, displayName: "file12.js", fileName: "file12.js", oldFileName: "file12.js", isNew: false, removed: false, renamed: false, staged: false, type: "text" }, "file13.js": { - conflict: false, displayName: "file13.js", isNew: true, removed: false, renamed: false, staged: false, type: "text" + conflict: false, displayName: "file13.js", fileName: "file13.js", oldFileName: "file13.js", isNew: true, removed: false, renamed: false, staged: false, type: "text" } }, inited: true,