From 2f1e886cba422844b0496a96696dae5296835862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iiro=20J=C3=A4ppinen?= Date: Wed, 11 Sep 2019 18:37:33 +0300 Subject: [PATCH] fix: gitWorkflow handles active merge mode --- src/gitWorkflow.js | 59 +++++++++++++++++++++++++++++++++++- test/runAll.unmocked.spec.js | 53 +++++++++++++++++--------------- 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/gitWorkflow.js b/src/gitWorkflow.js index d7a8639a0..0840daf19 100644 --- a/src/gitWorkflow.js +++ b/src/gitWorkflow.js @@ -1,9 +1,15 @@ 'use strict' const debug = require('debug')('lint-staged:git') +const fs = require('fs') +const path = require('path') const execGit = require('./execGit') +const MERGE_HEAD = 'MERGE_HEAD' +const MERGE_MODE = 'MERGE_MODE' +const MERGE_MSG = 'MERGE_MSG' + const STASH = 'lint-staged automatic backup' const gitApplyArgs = ['apply', '-v', '--whitespace=nowarn', '--recount', '--unidiff-zero'] @@ -12,6 +18,33 @@ class GitWorkflow { constructor(cwd) { this.execGit = (args, options = {}) => execGit(args, { ...options, cwd }) this.unstagedDiff = null + this.cwd = cwd + } + + /** + * Read file from .git directory, returning a buffer or null + * @param {String} filename Relative path to file + * @returns {Promise} + */ + readGitConfigFile(filename) { + const resolvedPath = path.resolve(this.cwd, '.git', filename) + return new Promise(resolve => { + fs.readFile(resolvedPath, (error, file) => { + resolve(error && error.code === 'ENOENT' ? null : file) + }) + }) + } + + /** + * Write buffer to relative .git directory + * @param {String} filename Relative path to file + * @param {Buffer} buffer + */ + writeGitConfigFile(filename, buffer) { + const resolvedPath = path.resolve(this.cwd, '.git', filename) + return new Promise(resolve => { + fs.writeFile(resolvedPath, buffer, resolve) + }) } /** @@ -35,6 +68,21 @@ class GitWorkflow { */ async stashBackup() { debug('Backing up original state...') + + // Git stash loses metadata about a possible merge mode + // Manually check and backup if necessary + const mergeHead = await this.readGitConfigFile(MERGE_HEAD) + if (mergeHead) { + debug('Detected current merge mode!') + debug('Backing up merge state...') + this.mergeHead = mergeHead + await Promise.all([ + this.readGitConfigFile(MERGE_MODE).then(mergeMode => (this.mergeMode = mergeMode)), + this.readGitConfigFile(MERGE_MSG).then(mergeMsg => (this.mergeMsg = mergeMsg)) + ]) + debug('Done backing up merge state!') + } + // Get stash of entire original state, including unstaged changes // Keep index so that tasks only work on those files await this.execGit(['stash', 'save', '--quiet', '--include-untracked', '--keep-index', STASH]) @@ -100,8 +148,17 @@ class GitWorkflow { debug('Dropping backup stash...') const original = await this.getBackupStash() await this.execGit(['stash', 'drop', '--quiet', original]) - this.unstagedDiff = null debug('Done dropping backup stash!') + + if (this.mergeHead) { + debug('Detected backup merge state!') + debug('Restoring merge state...') + const writePromises = [this.writeGitConfigFile(MERGE_HEAD, this.mergeHead)] + if (this.mergeMode) writePromises.push(this.writeGitConfigFile(MERGE_MODE, this.mergeMode)) + if (this.mergeMsg) writePromises.push(this.writeGitConfigFile(MERGE_MSG, this.mergeMsg)) + await Promise.all(writePromises) + debug('Done restoring merge state!') + } } } diff --git a/test/runAll.unmocked.spec.js b/test/runAll.unmocked.spec.js index 800e0b057..323a77245 100644 --- a/test/runAll.unmocked.spec.js +++ b/test/runAll.unmocked.spec.js @@ -300,17 +300,17 @@ describe('runAll', () => { // But local modifications are gone expect(await execGit(['diff'])).not.toEqual(diff) expect(await execGit(['diff'])).toMatchInlineSnapshot(` - "diff --git a/test.js b/test.js - index f80f875..1c5643c 100644 - --- a/test.js - +++ b/test.js - @@ -1,3 +1,3 @@ - module.exports = { - - 'foo': 'bar', - -} - + foo: \\"bar\\" - +};" - `) + "diff --git a/test.js b/test.js + index f80f875..1c5643c 100644 + --- a/test.js + +++ b/test.js + @@ -1,3 +1,3 @@ + module.exports = { + - 'foo': 'bar', + -} + + foo: \\"bar\\" + +};" + `) expect(await readFile('test.js')).not.toEqual(testJsFileUgly + appended) expect(await readFile('test.js')).toEqual(testJsFilePretty) @@ -366,13 +366,13 @@ describe('runAll', () => { } expect(await readFile('test.js')).toMatchInlineSnapshot(` - "<<<<<<< HEAD - module.exports = \\"foo\\"; - ======= - module.exports = \\"bar\\"; - >>>>>>> branch-b - " - `) + "<<<<<<< HEAD + module.exports = \\"foo\\"; + ======= + module.exports = \\"bar\\"; + >>>>>>> branch-b + " + `) // Fix conflict and commit using lint-staged await writeFile('test.js', fileInBranchB) @@ -381,15 +381,18 @@ describe('runAll', () => { // Do not use `gitCommit` wrapper here await runAll({ ...fixJsConfig, cwd, quiet: true }) + await execGit(['commit', '--no-edit']) - // Lint-staged lost MERGE_HEAD - try { - await execGit(['merge', '--continue']) - } catch ({ stderr }) { - expect(stderr).toMatch('There is no merge in progress (MERGE_HEAD missing)') - } + // Nothing is wrong, so a new commit is created and file is pretty + expect(await execGit(['rev-list', '--count', 'HEAD'])).toEqual('4') + expect(await execGit(['log', '-1', '--pretty=%B'])).toMatchInlineSnapshot(` + "Merge branch 'branch-b' - // TODO: Fix behaviour by saving/restoring MERGE_HEAD, and then complete test + # Conflicts: + # test.js + " + `) + expect(await readFile('test.js')).toEqual(fileInBranchBFixed) }) afterEach(async () => {