UNPKG

5.37 kBJavaScriptView Raw
1'use strict'
2
3const del = require('del')
4const debug = require('debug')('lint-staged:git')
5
6const execGit = require('./execGit')
7
8let workingCopyTree = null
9let indexTree = null
10let formattedIndexTree = null
11
12async function writeTree(options) {
13 return execGit(['write-tree'], options)
14}
15
16async function getDiffForTrees(tree1, tree2, options) {
17 debug(`Generating diff between trees ${tree1} and ${tree2}...`)
18 return execGit(
19 [
20 'diff-tree',
21 '--ignore-submodules',
22 '--binary',
23 '--no-color',
24 '--no-ext-diff',
25 '--unified=0',
26 tree1,
27 tree2
28 ],
29 options
30 )
31}
32
33async function hasPartiallyStagedFiles(options) {
34 const stdout = await execGit(['status', '--porcelain'], options)
35 if (!stdout) return false
36
37 const changedFiles = stdout.split('\n')
38 const partiallyStaged = changedFiles.filter(line => {
39 /**
40 * See https://git-scm.com/docs/git-status#_short_format
41 * The first letter of the line represents current index status,
42 * and second the working tree
43 */
44 const [index, workingTree] = line
45 return index !== ' ' && workingTree !== ' ' && index !== '?' && workingTree !== '?'
46 })
47
48 return partiallyStaged.length > 0
49}
50
51// eslint-disable-next-line
52async function gitStashSave(options) {
53 debug('Stashing files...')
54 // Save ref to the current index
55 indexTree = await writeTree(options)
56 // Add working copy changes to index
57 await execGit(['add', '.'], options)
58 // Save ref to the working copy index
59 workingCopyTree = await writeTree(options)
60 // Restore the current index
61 await execGit(['read-tree', indexTree], options)
62 // Remove all modifications
63 await execGit(['checkout-index', '-af'], options)
64 // await execGit(['clean', '-dfx'], options)
65 debug('Done stashing files!')
66 return [workingCopyTree, indexTree]
67}
68
69async function updateStash(options) {
70 formattedIndexTree = await writeTree(options)
71 return formattedIndexTree
72}
73
74async function applyPatchFor(tree1, tree2, options) {
75 const diff = await getDiffForTrees(tree1, tree2, options)
76 /**
77 * This is crucial for patch to work
78 * For some reason, git-apply requires that the patch ends with the newline symbol
79 * See http://git.661346.n2.nabble.com/Bug-in-Git-Gui-Creates-corrupt-patch-td2384251.html
80 * and https://stackoverflow.com/questions/13223868/how-to-stage-line-by-line-in-git-gui-although-no-newline-at-end-of-file-warnin
81 */
82 // TODO: Figure out how to test this. For some reason tests were working but in the real env it was failing
83 const patch = `${diff}\n` // TODO: This should also work on Windows but test would be good
84 if (patch) {
85 try {
86 /**
87 * Apply patch to index. We will apply it with --reject so it it will try apply hunk by hunk
88 * We're not interested in failied hunks since this mean that formatting conflicts with user changes
89 * and we prioritize user changes over formatter's
90 */
91 await execGit(
92 ['apply', '-v', '--whitespace=nowarn', '--reject', '--recount', '--unidiff-zero'],
93 {
94 ...options,
95 input: patch
96 }
97 )
98 } catch (err) {
99 debug('Could not apply patch to the stashed files cleanly')
100 debug(err)
101 debug('Patch content:')
102 debug(patch)
103 throw new Error('Could not apply patch to the stashed files cleanly.', err)
104 }
105 }
106}
107
108async function gitStashPop(options) {
109 if (workingCopyTree === null) {
110 throw new Error('Trying to restore from stash but could not find working copy stash.')
111 }
112
113 debug('Restoring working copy')
114 // Restore the stashed files in the index
115 await execGit(['read-tree', workingCopyTree], options)
116 // and sync it to the working copy (i.e. update files on fs)
117 await execGit(['checkout-index', '-af'], options)
118
119 // Then, restore the index after working copy is restored
120 if (indexTree !== null && formattedIndexTree === null) {
121 // Restore changes that were in index if there are no formatting changes
122 debug('Restoring index')
123 await execGit(['read-tree', indexTree], options)
124 } else {
125 /**
126 * There are formatting changes we want to restore in the index
127 * and in the working copy. So we start by restoring the index
128 * and after that we'll try to carry as many as possible changes
129 * to the working copy by applying the patch with --reject option.
130 */
131 debug('Restoring index with formatting changes')
132 await execGit(['read-tree', formattedIndexTree], options)
133 try {
134 await applyPatchFor(indexTree, formattedIndexTree, options)
135 } catch (err) {
136 debug(
137 'Found conflicts between formatters and local changes. Formatters changes will be ignored for conflicted hunks.'
138 )
139 /**
140 * Clean up working directory from *.rej files that contain conflicted hanks.
141 * These hunks are coming from formatters so we'll just delete them since they are irrelevant.
142 */
143 try {
144 const rejFiles = await del(['*.rej'], options)
145 debug('Deleted files and folders:\n', rejFiles.join('\n'))
146 } catch (delErr) {
147 debug('Error deleting *.rej files', delErr)
148 }
149 }
150 }
151 // Clean up references
152 workingCopyTree = null
153 indexTree = null
154 formattedIndexTree = null
155
156 return null
157}
158
159module.exports = {
160 execGit,
161 gitStashSave,
162 gitStashPop,
163 hasPartiallyStagedFiles,
164 updateStash
165}