@itcase/config 1.7.24 → 1.7.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/smart-commit/cli.js +121 -11
package/package.json
CHANGED
package/smart-commit/cli.js
CHANGED
|
@@ -221,10 +221,17 @@ async function main(argv) {
|
|
|
221
221
|
|
|
222
222
|
// Drop leftover index entries so only this plan's files are committed.
|
|
223
223
|
git(cwd, ['reset', '-q', 'HEAD'])
|
|
224
|
-
stageFiles(cwd, filesToStage)
|
|
224
|
+
const skippedOnStage = stageFiles(cwd, filesToStage)
|
|
225
225
|
const staged = lines(cwd, ['diff', '--cached', '--name-only'])
|
|
226
226
|
const stagedForPlan = filesToStage.filter((file) => staged.includes(file))
|
|
227
227
|
if (!stagedForPlan.length) {
|
|
228
|
+
const skipped = new Set(skippedOnStage)
|
|
229
|
+
// Ignored paths were not staged on purpose. Stop this commit and keep
|
|
230
|
+
// going — one ignored directory must not drop the rest of the run.
|
|
231
|
+
if (filesToStage.every((file) => skipped.has(file))) {
|
|
232
|
+
process.stdout.write(`${dim(` skip ignored: ${plan.message}`)}\n`)
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
228
235
|
throw new Error(
|
|
229
236
|
`Nothing staged for commit: ${plan.message}\n` +
|
|
230
237
|
`Files: ${filesToStage.join(', ')}`,
|
|
@@ -1548,13 +1555,28 @@ function isOnDisk(cwd, file) {
|
|
|
1548
1555
|
function gitIgnoredFiles(cwd, files) {
|
|
1549
1556
|
if (!files.length) return []
|
|
1550
1557
|
|
|
1551
|
-
|
|
1558
|
+
// Without --no-index, check-ignore hides tracked files that match
|
|
1559
|
+
// gitignore. Those still stage, unless a parent directory is ignored:
|
|
1560
|
+
// `git add` then exits 1 and names the directory, not the file.
|
|
1561
|
+
const direct = pathsIgnoredByGit(cwd, files)
|
|
1562
|
+
const underIgnoredDirectory = filesUnderIgnoredDirectory(cwd, files)
|
|
1563
|
+
return [...new Set([...direct, ...underIgnoredDirectory])]
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function pathsIgnoredByGit(cwd, paths, option = {}) {
|
|
1567
|
+
if (!paths.length) return []
|
|
1568
|
+
|
|
1569
|
+
const args = ['check-ignore', '-z', '--stdin']
|
|
1570
|
+
if (option.noIndex) args.push('--no-index')
|
|
1571
|
+
const result = spawnSync('git', args, {
|
|
1572
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
1552
1573
|
cwd: cwd,
|
|
1553
1574
|
encoding: 'utf8',
|
|
1554
|
-
input: `${
|
|
1575
|
+
input: `${paths.join('\0')}\0`,
|
|
1555
1576
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1556
1577
|
})
|
|
1557
|
-
// 0 — at least one path is ignored, 1 — none.
|
|
1578
|
+
// 0 — at least one path is ignored, 1 — none. Any other status is a
|
|
1579
|
+
// fatal; treat it as "nothing ignored" and let git add surface it.
|
|
1558
1580
|
if (result.status !== 0) return []
|
|
1559
1581
|
|
|
1560
1582
|
const ignored = new Set(
|
|
@@ -1562,7 +1584,67 @@ function gitIgnoredFiles(cwd, files) {
|
|
|
1562
1584
|
.split('\0')
|
|
1563
1585
|
.filter(Boolean),
|
|
1564
1586
|
)
|
|
1565
|
-
return
|
|
1587
|
+
return paths.filter((file) => ignored.has(file))
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
function ancestorDirectories(file) {
|
|
1591
|
+
const ancestors = []
|
|
1592
|
+
let directory = path.posix.dirname(file)
|
|
1593
|
+
while (directory && directory !== '.') {
|
|
1594
|
+
ancestors.push(directory)
|
|
1595
|
+
directory = path.posix.dirname(directory)
|
|
1596
|
+
}
|
|
1597
|
+
return ancestors
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
function filesUnderIgnoredDirectory(cwd, files) {
|
|
1601
|
+
const ancestorsByFile = new Map()
|
|
1602
|
+
const uniqueAncestors = []
|
|
1603
|
+
const seen = new Set()
|
|
1604
|
+
for (const file of files) {
|
|
1605
|
+
const ancestors = ancestorDirectories(file)
|
|
1606
|
+
ancestorsByFile.set(file, ancestors)
|
|
1607
|
+
for (const ancestor of ancestors) {
|
|
1608
|
+
if (seen.has(ancestor)) continue
|
|
1609
|
+
seen.add(ancestor)
|
|
1610
|
+
uniqueAncestors.push(ancestor)
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const ignoredAncestors = new Set(
|
|
1615
|
+
pathsIgnoredByGit(cwd, uniqueAncestors, { noIndex: true }),
|
|
1616
|
+
)
|
|
1617
|
+
if (!ignoredAncestors.size) return []
|
|
1618
|
+
|
|
1619
|
+
return files.filter((file) =>
|
|
1620
|
+
ancestorsByFile
|
|
1621
|
+
.get(file)
|
|
1622
|
+
.some((ancestor) => ignoredAncestors.has(ancestor)),
|
|
1623
|
+
)
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function pathMatchesIgnored(file, ignoredPaths) {
|
|
1627
|
+
return ignoredPaths.some(
|
|
1628
|
+
(ignored) => file === ignored || file.startsWith(`${ignored}/`),
|
|
1629
|
+
)
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function ignoredPathsFromGitAdd(stderr) {
|
|
1633
|
+
const header =
|
|
1634
|
+
'The following paths are ignored by one of your .gitignore files:'
|
|
1635
|
+
const start = String(stderr || '').indexOf(header)
|
|
1636
|
+
if (start < 0) return []
|
|
1637
|
+
|
|
1638
|
+
const paths = []
|
|
1639
|
+
for (const line of String(stderr)
|
|
1640
|
+
.slice(start + header.length)
|
|
1641
|
+
.split('\n')) {
|
|
1642
|
+
const trimmed = line.trim().replace(/\/+$/, '')
|
|
1643
|
+
if (!trimmed) continue
|
|
1644
|
+
if (trimmed.startsWith('hint:')) break
|
|
1645
|
+
paths.push(trimmed)
|
|
1646
|
+
}
|
|
1647
|
+
return paths
|
|
1566
1648
|
}
|
|
1567
1649
|
|
|
1568
1650
|
function isTracked(cwd, file) {
|
|
@@ -1631,14 +1713,17 @@ function includeHookLockfiles(cwd, planFiles, dirtyBefore) {
|
|
|
1631
1713
|
}
|
|
1632
1714
|
|
|
1633
1715
|
function stageFiles(cwd, files) {
|
|
1634
|
-
if (!files.length) return
|
|
1716
|
+
if (!files.length) return []
|
|
1635
1717
|
|
|
1636
|
-
// `git add -- ignored
|
|
1637
|
-
//
|
|
1638
|
-
//
|
|
1718
|
+
// `git add -- path/inside/ignored-dir` exits 1 and names the directory,
|
|
1719
|
+
// after it has already updated the index. Drop those paths first. If git
|
|
1720
|
+
// add still reports an ignored directory, take those paths back out and
|
|
1721
|
+
// keep the rest of the batch. Tracked files that match gitignore but do
|
|
1722
|
+
// not sit inside an ignored directory still stage.
|
|
1639
1723
|
const ignored = new Set(gitIgnoredFiles(cwd, files))
|
|
1640
1724
|
const stageableFiles = files.filter((file) => !ignored.has(file))
|
|
1641
|
-
|
|
1725
|
+
const skipped = [...ignored]
|
|
1726
|
+
if (!stageableFiles.length) return skipped
|
|
1642
1727
|
|
|
1643
1728
|
const existing = []
|
|
1644
1729
|
const removed = []
|
|
@@ -1650,11 +1735,33 @@ function stageFiles(cwd, files) {
|
|
|
1650
1735
|
// `git add -A -- deleted/path` warns or misses the deletion when the
|
|
1651
1736
|
// parent directory is already gone. `git rm` stages the index removal.
|
|
1652
1737
|
forEachBatch(existing, (batch) => {
|
|
1653
|
-
git(cwd, ['add', '-A', '--', ...batch]
|
|
1738
|
+
const result = git(cwd, ['add', '-A', '--', ...batch], {
|
|
1739
|
+
allowIgnored: true,
|
|
1740
|
+
})
|
|
1741
|
+
if (!result.status) return
|
|
1742
|
+
|
|
1743
|
+
const ignoredPaths = ignoredPathsFromGitAdd(result.stderr || '')
|
|
1744
|
+
const blocked = batch.filter((file) =>
|
|
1745
|
+
pathMatchesIgnored(file, ignoredPaths),
|
|
1746
|
+
)
|
|
1747
|
+
if (!blocked.length) {
|
|
1748
|
+
throw new Error(
|
|
1749
|
+
(result.stderr || '').trim() || `git add -A -- ${batch.join(' ')}`,
|
|
1750
|
+
)
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// git add writes the index before returning 1, including tracked files
|
|
1754
|
+
// under the ignored directory. Take those back out of this commit.
|
|
1755
|
+
git(cwd, ['reset', '-q', 'HEAD', '--', ...blocked])
|
|
1756
|
+
skipped.push(...blocked)
|
|
1757
|
+
process.stdout.write(
|
|
1758
|
+
`${dim(` skipped (gitignore): ${blocked.join(', ')}`)}\n`,
|
|
1759
|
+
)
|
|
1654
1760
|
})
|
|
1655
1761
|
forEachBatch(removed, (batch) => {
|
|
1656
1762
|
git(cwd, ['rm', '--ignore-unmatch', '-q', '--', ...batch])
|
|
1657
1763
|
})
|
|
1764
|
+
return skipped
|
|
1658
1765
|
}
|
|
1659
1766
|
|
|
1660
1767
|
async function confirm(count) {
|
|
@@ -1899,6 +2006,9 @@ function git(cwd, args, option = {}) {
|
|
|
1899
2006
|
|
|
1900
2007
|
const stderr = result.stderr || ''
|
|
1901
2008
|
if (!retryIndexLock(cwd, attempt, stderr)) {
|
|
2009
|
+
if (option.allowIgnored && ignoredPathsFromGitAdd(stderr).length) {
|
|
2010
|
+
return result
|
|
2011
|
+
}
|
|
1902
2012
|
if (option.inherit && stderr) process.stderr.write(stderr)
|
|
1903
2013
|
throw new Error(stderr.trim() || `git ${args.join(' ')}`)
|
|
1904
2014
|
}
|