@itcase/config 1.7.24 → 1.7.26
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 +16 -16
- package/smart-commit/cli.js +248 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itcase/config",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.26",
|
|
4
4
|
"author": "ITCase",
|
|
5
5
|
"description": "ITCase Config",
|
|
6
6
|
"engines": {
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"@commitlint/config-conventional": "^21.2.3",
|
|
44
44
|
"@commitlint/cz-commitlint": "^21.2.3",
|
|
45
45
|
"@lehoczky/postcss-fluid": "^1.0.3",
|
|
46
|
-
"@lerna-lite/cli": "^5.6.
|
|
47
|
-
"@lerna-lite/exec": "^5.6.
|
|
48
|
-
"@lerna-lite/list": "^5.6.
|
|
49
|
-
"@lerna-lite/run": "^5.6.
|
|
50
|
-
"@lerna-lite/watch": "^5.6.
|
|
46
|
+
"@lerna-lite/cli": "^5.6.3",
|
|
47
|
+
"@lerna-lite/exec": "^5.6.3",
|
|
48
|
+
"@lerna-lite/list": "^5.6.3",
|
|
49
|
+
"@lerna-lite/run": "^5.6.3",
|
|
50
|
+
"@lerna-lite/watch": "^5.6.3",
|
|
51
51
|
"@rollup/plugin-alias": "^6.0.0",
|
|
52
52
|
"@rollup/plugin-babel": "^7.1.0",
|
|
53
53
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
@@ -68,11 +68,11 @@
|
|
|
68
68
|
"commitizen": "^4.3.2",
|
|
69
69
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
70
70
|
"conventional-changelog-writer": "9.3.0",
|
|
71
|
-
"cssnano": "^9.
|
|
72
|
-
"cssnano-preset-default": "^9.
|
|
71
|
+
"cssnano": "^9.8.0",
|
|
72
|
+
"cssnano-preset-default": "^9.8.0",
|
|
73
73
|
"inquirer": "^14.2.3",
|
|
74
74
|
"log-update": "^8.0.0",
|
|
75
|
-
"postcss": "^8.5.
|
|
75
|
+
"postcss": "^8.5.30",
|
|
76
76
|
"postcss-aspect-ratio-polyfill": "^2.0.0",
|
|
77
77
|
"postcss-clamp": "^4.1.0",
|
|
78
78
|
"postcss-cli": "^12.0.0",
|
|
@@ -80,19 +80,19 @@
|
|
|
80
80
|
"postcss-combine-duplicated-selectors": "^11.0.0",
|
|
81
81
|
"postcss-convert-unit": "^0.1.3",
|
|
82
82
|
"postcss-dark-theme-class": "^2.0.0",
|
|
83
|
-
"postcss-discard-duplicates": "^9.0.
|
|
83
|
+
"postcss-discard-duplicates": "^9.0.7",
|
|
84
84
|
"postcss-each": "^1.1.0",
|
|
85
85
|
"postcss-easings": "^4.0.0",
|
|
86
86
|
"postcss-extend-rule": "^4.0.0",
|
|
87
87
|
"postcss-flexbugs-fixes": "^5.0.2",
|
|
88
88
|
"postcss-hexrgba": "^2.1.0",
|
|
89
|
-
"postcss-import": "^17.0.
|
|
89
|
+
"postcss-import": "^17.0.1",
|
|
90
90
|
"postcss-import-ext-glob": "^2.1.1",
|
|
91
91
|
"postcss-loader": "^8.2.1",
|
|
92
92
|
"postcss-merge-at-rules": "^2.0.1",
|
|
93
93
|
"postcss-mixins": "^12.1.2",
|
|
94
94
|
"postcss-mq-extract": "^1.0.0",
|
|
95
|
-
"postcss-nested": "^
|
|
95
|
+
"postcss-nested": "^9.0.0",
|
|
96
96
|
"postcss-nested-ancestors": "^3.0.0",
|
|
97
97
|
"postcss-normalize": "^13.0.1",
|
|
98
98
|
"postcss-prepend-imports": "^1.0.1",
|
|
@@ -110,17 +110,17 @@
|
|
|
110
110
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
111
111
|
"rollup-preserve-directives": "^1.1.3",
|
|
112
112
|
"semantic-release": "^25.0.9",
|
|
113
|
-
"semantic-release-lerna": "^3.6.
|
|
113
|
+
"semantic-release-lerna": "^3.6.7"
|
|
114
114
|
},
|
|
115
115
|
"devDependencies": {
|
|
116
116
|
"@itcase/agent-skills": "^1.0.13",
|
|
117
|
-
"@itcase/lint": "^1.1.
|
|
117
|
+
"@itcase/lint": "^1.1.130",
|
|
118
118
|
"eslint": "9.39.2",
|
|
119
119
|
"husky": "^9.1.7",
|
|
120
120
|
"inquirer": "^14.2.3",
|
|
121
121
|
"lint-staged": "^17.6.0",
|
|
122
|
-
"prettier": "^3.9.
|
|
123
|
-
"storybook": "^10.6.
|
|
122
|
+
"prettier": "^3.9.10",
|
|
123
|
+
"storybook": "^10.6.2",
|
|
124
124
|
"stylelint": "^17.16.0",
|
|
125
125
|
"typescript": "^6.0.3"
|
|
126
126
|
}
|
package/smart-commit/cli.js
CHANGED
|
@@ -24,10 +24,18 @@ const CLI_RELATIVE = 'smart-commit/cli.js'
|
|
|
24
24
|
// Cheap Cursor Models pool — commit messages do not need Auto / frontier models.
|
|
25
25
|
const DEFAULT_MODEL = 'composer-2.5'
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
// A small change set still sends the patch. A large tree used to be many
|
|
28
|
+
// agent calls with up to 80k chars of diff each. Compact numstat keeps that
|
|
29
|
+
// to one call.
|
|
30
|
+
const MAX_DIFF = 20_000
|
|
31
|
+
const COMPACT_SUMMARY_MAX = 12_000
|
|
32
|
+
const SMALL_PLAN_FILES = 12
|
|
33
|
+
const PER_FILE_EXCERPT = 500
|
|
34
|
+
const EXCERPT_SKIP_LINES = 400
|
|
35
|
+
const EXCERPT_MAX_BYTES = 256 * 1024
|
|
28
36
|
const GIT_MAX_BUFFER = 32 * 1024 * 1024
|
|
29
37
|
const GIT_PATH_BATCH = 40
|
|
30
|
-
const PLAN_FILE_BATCH =
|
|
38
|
+
const PLAN_FILE_BATCH = 250
|
|
31
39
|
const ARG_SAFE = 100_000
|
|
32
40
|
const LOCKFILE_NAMES = new Set([
|
|
33
41
|
'package-lock.json',
|
|
@@ -221,10 +229,17 @@ async function main(argv) {
|
|
|
221
229
|
|
|
222
230
|
// Drop leftover index entries so only this plan's files are committed.
|
|
223
231
|
git(cwd, ['reset', '-q', 'HEAD'])
|
|
224
|
-
stageFiles(cwd, filesToStage)
|
|
232
|
+
const skippedOnStage = stageFiles(cwd, filesToStage)
|
|
225
233
|
const staged = lines(cwd, ['diff', '--cached', '--name-only'])
|
|
226
234
|
const stagedForPlan = filesToStage.filter((file) => staged.includes(file))
|
|
227
235
|
if (!stagedForPlan.length) {
|
|
236
|
+
const skipped = new Set(skippedOnStage)
|
|
237
|
+
// Ignored paths were not staged on purpose. Stop this commit and keep
|
|
238
|
+
// going — one ignored directory must not drop the rest of the run.
|
|
239
|
+
if (filesToStage.every((file) => skipped.has(file))) {
|
|
240
|
+
process.stdout.write(`${dim(` skip ignored: ${plan.message}`)}\n`)
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
228
243
|
throw new Error(
|
|
229
244
|
`Nothing staged for commit: ${plan.message}\n` +
|
|
230
245
|
`Files: ${filesToStage.join(', ')}`,
|
|
@@ -601,8 +616,10 @@ function buildPrompt(scopes, types, files, cwd, type) {
|
|
|
601
616
|
|
|
602
617
|
const sourceFiles = files.filter((file) => !isLockfile(file))
|
|
603
618
|
const lockFiles = files.filter((file) => isLockfile(file))
|
|
619
|
+
const diffLimit =
|
|
620
|
+
sourceFiles.length > SMALL_PLAN_FILES ? COMPACT_SUMMARY_MAX : MAX_DIFF
|
|
604
621
|
let diff = buildSourceDiff(cwd, sourceFiles)
|
|
605
|
-
if (diff.length >
|
|
622
|
+
if (diff.length > diffLimit) diff = `${diff.slice(0, diffLimit)}\n…truncated`
|
|
606
623
|
|
|
607
624
|
return `Plan git commits. Do not edit files or run git commit.
|
|
608
625
|
Return ONLY JSON: {"commits":[{"message":"type(scope): subject","files":["a.ts"]}]}
|
|
@@ -615,6 +632,7 @@ Prefer one commit when changes belong to the same feature.
|
|
|
615
632
|
Split by scope. Each listed file in exactly one commit — including deleted files (D) and new/untracked files (??).
|
|
616
633
|
Put each lockfile in the same commit as its sibling package.json. Do not make a lockfile-only commit.
|
|
617
634
|
Do not add Co-authored-by or Made-with trailers.
|
|
635
|
+
Do not use tools or read files. The file list and diff below are the only source.
|
|
618
636
|
${type ? `Prefer type "${type}".\n` : ''}Files:
|
|
619
637
|
${files
|
|
620
638
|
.map((file) => {
|
|
@@ -637,6 +655,11 @@ ${diff || '(none)'}
|
|
|
637
655
|
}
|
|
638
656
|
|
|
639
657
|
function buildSourceDiff(cwd, files) {
|
|
658
|
+
if (files.length > SMALL_PLAN_FILES) return buildCompactSourceDiff(cwd, files)
|
|
659
|
+
return buildFullSourceDiff(cwd, files, MAX_DIFF)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function buildFullSourceDiff(cwd, files, limit) {
|
|
640
663
|
const tracked = []
|
|
641
664
|
const untracked = []
|
|
642
665
|
for (const file of files) {
|
|
@@ -647,13 +670,13 @@ function buildSourceDiff(cwd, files) {
|
|
|
647
670
|
const parts = []
|
|
648
671
|
let used = 0
|
|
649
672
|
const pushLimited = (text) => {
|
|
650
|
-
if (used >=
|
|
673
|
+
if (used >= limit) return false
|
|
651
674
|
// Empty batch (no changes / ENOBUFS skip) — keep scanning remaining files.
|
|
652
675
|
if (!text) return true
|
|
653
|
-
const room =
|
|
676
|
+
const room = limit - used
|
|
654
677
|
if (text.length > room) {
|
|
655
678
|
parts.push(`${text.slice(0, room)}\n…truncated`)
|
|
656
|
-
used =
|
|
679
|
+
used = limit
|
|
657
680
|
return false
|
|
658
681
|
}
|
|
659
682
|
parts.push(text)
|
|
@@ -662,17 +685,120 @@ function buildSourceDiff(cwd, files) {
|
|
|
662
685
|
}
|
|
663
686
|
|
|
664
687
|
forEachBatch(tracked, (batch) => {
|
|
665
|
-
if (used >=
|
|
688
|
+
if (used >= limit) return false
|
|
666
689
|
return pushLimited(tryOut(cwd, ['diff', 'HEAD', '--', ...batch]))
|
|
667
690
|
})
|
|
668
691
|
for (const file of untracked) {
|
|
669
|
-
if (used >=
|
|
692
|
+
if (used >= limit) break
|
|
670
693
|
const piece = buildUntrackedDiff(cwd, file)
|
|
671
694
|
if (!pushLimited(piece)) break
|
|
672
695
|
}
|
|
673
696
|
return parts.join('\n\n')
|
|
674
697
|
}
|
|
675
698
|
|
|
699
|
+
function buildCompactSourceDiff(cwd, files) {
|
|
700
|
+
const tracked = []
|
|
701
|
+
const untracked = new Set()
|
|
702
|
+
for (const file of files) {
|
|
703
|
+
if (isUntracked(cwd, file)) untracked.add(file)
|
|
704
|
+
else tracked.push(file)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const statLines = []
|
|
708
|
+
forEachBatch(tracked, (batch) => {
|
|
709
|
+
const text = tryOut(cwd, ['diff', '--numstat', 'HEAD', '--', ...batch])
|
|
710
|
+
if (text) statLines.push(...text.split('\n').filter(Boolean))
|
|
711
|
+
})
|
|
712
|
+
for (const file of untracked) statLines.push(untrackedNumstatLine(cwd, file))
|
|
713
|
+
|
|
714
|
+
const weightByFile = new Map()
|
|
715
|
+
for (const line of statLines) {
|
|
716
|
+
const file = line.split('\t').slice(2).join('\t')
|
|
717
|
+
weightByFile.set(file, parseNumstatWeight(line))
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const header = `numstat (added, removed, path):\n${statLines.join('\n')}`
|
|
721
|
+
if (header.length >= COMPACT_SUMMARY_MAX) {
|
|
722
|
+
return `${header.slice(0, COMPACT_SUMMARY_MAX)}\n…truncated`
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const ranked = [...files].sort((left, right) => {
|
|
726
|
+
const leftWeight = weightByFile.get(left)
|
|
727
|
+
const rightWeight = weightByFile.get(right)
|
|
728
|
+
return (rightWeight ?? -1) - (leftWeight ?? -1)
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
const excerptHeader = '\n\nchanged lines:\n'
|
|
732
|
+
let room = COMPACT_SUMMARY_MAX - header.length - excerptHeader.length
|
|
733
|
+
const excerpts = []
|
|
734
|
+
for (const file of ranked) {
|
|
735
|
+
if (room < 40) break
|
|
736
|
+
const weight = weightByFile.get(file)
|
|
737
|
+
if (weight == null || weight > EXCERPT_SKIP_LINES) continue
|
|
738
|
+
if (weight === 0 && !untracked.has(file)) continue
|
|
739
|
+
const excerpt = changedLineExcerpt(
|
|
740
|
+
cwd,
|
|
741
|
+
file,
|
|
742
|
+
untracked.has(file),
|
|
743
|
+
Math.min(PER_FILE_EXCERPT, room),
|
|
744
|
+
)
|
|
745
|
+
if (!excerpt) continue
|
|
746
|
+
excerpts.push(excerpt)
|
|
747
|
+
room -= excerpt.length + 1
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (!excerpts.length) return header
|
|
751
|
+
return `${header}${excerptHeader}${excerpts.join('\n')}`
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function parseNumstatWeight(line) {
|
|
755
|
+
const [added, removed] = line.split('\t')
|
|
756
|
+
if (added === '-' || removed === '-') return null
|
|
757
|
+
return (Number(added) || 0) + (Number(removed) || 0)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function untrackedNumstatLine(cwd, file) {
|
|
761
|
+
const text = readTextFileHead(cwd, file)
|
|
762
|
+
if (text == null) return `-\t-\t${file}`
|
|
763
|
+
const lines = text ? text.split('\n').length : 0
|
|
764
|
+
return `${lines}\t0\t${file}`
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function readTextFileHead(cwd, file) {
|
|
768
|
+
const filePath = path.join(cwd, file)
|
|
769
|
+
let stat
|
|
770
|
+
try {
|
|
771
|
+
stat = fs.statSync(filePath)
|
|
772
|
+
} catch {
|
|
773
|
+
return null
|
|
774
|
+
}
|
|
775
|
+
if (!stat.isFile() || stat.size > EXCERPT_MAX_BYTES) return null
|
|
776
|
+
const buffer = fs.readFileSync(filePath)
|
|
777
|
+
if (buffer.includes(0)) return null
|
|
778
|
+
return buffer.toString('utf8')
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function changedLineExcerpt(cwd, file, untracked, cap) {
|
|
782
|
+
if (untracked) {
|
|
783
|
+
const text = readTextFileHead(cwd, file)
|
|
784
|
+
if (!text) return ''
|
|
785
|
+
const body = text.length > cap ? `${text.slice(0, cap)}…` : text
|
|
786
|
+
return `${file}:\n${body}`
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const raw = tryOut(cwd, ['diff', '-U0', '--no-color', 'HEAD', '--', file])
|
|
790
|
+
if (!raw) return ''
|
|
791
|
+
const lines = []
|
|
792
|
+
for (const line of raw.split('\n')) {
|
|
793
|
+
if (line.startsWith('+++') || line.startsWith('---')) continue
|
|
794
|
+
if (line.startsWith('+') || line.startsWith('-')) lines.push(line)
|
|
795
|
+
}
|
|
796
|
+
if (!lines.length) return ''
|
|
797
|
+
let body = lines.join('\n')
|
|
798
|
+
if (body.length > cap) body = `${body.slice(0, cap)}…`
|
|
799
|
+
return `${file}:\n${body}`
|
|
800
|
+
}
|
|
801
|
+
|
|
676
802
|
function buildUntrackedDiff(cwd, file) {
|
|
677
803
|
const filePath = path.join(cwd, file)
|
|
678
804
|
if (!fs.existsSync(filePath)) return ''
|
|
@@ -1548,13 +1674,28 @@ function isOnDisk(cwd, file) {
|
|
|
1548
1674
|
function gitIgnoredFiles(cwd, files) {
|
|
1549
1675
|
if (!files.length) return []
|
|
1550
1676
|
|
|
1551
|
-
|
|
1677
|
+
// Without --no-index, check-ignore hides tracked files that match
|
|
1678
|
+
// gitignore. Those still stage, unless a parent directory is ignored:
|
|
1679
|
+
// `git add` then exits 1 and names the directory, not the file.
|
|
1680
|
+
const direct = pathsIgnoredByGit(cwd, files)
|
|
1681
|
+
const underIgnoredDirectory = filesUnderIgnoredDirectory(cwd, files)
|
|
1682
|
+
return [...new Set([...direct, ...underIgnoredDirectory])]
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
function pathsIgnoredByGit(cwd, paths, option = {}) {
|
|
1686
|
+
if (!paths.length) return []
|
|
1687
|
+
|
|
1688
|
+
const args = ['check-ignore', '-z', '--stdin']
|
|
1689
|
+
if (option.noIndex) args.push('--no-index')
|
|
1690
|
+
const result = spawnSync('git', args, {
|
|
1691
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
1552
1692
|
cwd: cwd,
|
|
1553
1693
|
encoding: 'utf8',
|
|
1554
|
-
input: `${
|
|
1694
|
+
input: `${paths.join('\0')}\0`,
|
|
1555
1695
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1556
1696
|
})
|
|
1557
|
-
// 0 — at least one path is ignored, 1 — none.
|
|
1697
|
+
// 0 — at least one path is ignored, 1 — none. Any other status is a
|
|
1698
|
+
// fatal; treat it as "nothing ignored" and let git add surface it.
|
|
1558
1699
|
if (result.status !== 0) return []
|
|
1559
1700
|
|
|
1560
1701
|
const ignored = new Set(
|
|
@@ -1562,7 +1703,67 @@ function gitIgnoredFiles(cwd, files) {
|
|
|
1562
1703
|
.split('\0')
|
|
1563
1704
|
.filter(Boolean),
|
|
1564
1705
|
)
|
|
1565
|
-
return
|
|
1706
|
+
return paths.filter((file) => ignored.has(file))
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function ancestorDirectories(file) {
|
|
1710
|
+
const ancestors = []
|
|
1711
|
+
let directory = path.posix.dirname(file)
|
|
1712
|
+
while (directory && directory !== '.') {
|
|
1713
|
+
ancestors.push(directory)
|
|
1714
|
+
directory = path.posix.dirname(directory)
|
|
1715
|
+
}
|
|
1716
|
+
return ancestors
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function filesUnderIgnoredDirectory(cwd, files) {
|
|
1720
|
+
const ancestorsByFile = new Map()
|
|
1721
|
+
const uniqueAncestors = []
|
|
1722
|
+
const seen = new Set()
|
|
1723
|
+
for (const file of files) {
|
|
1724
|
+
const ancestors = ancestorDirectories(file)
|
|
1725
|
+
ancestorsByFile.set(file, ancestors)
|
|
1726
|
+
for (const ancestor of ancestors) {
|
|
1727
|
+
if (seen.has(ancestor)) continue
|
|
1728
|
+
seen.add(ancestor)
|
|
1729
|
+
uniqueAncestors.push(ancestor)
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
const ignoredAncestors = new Set(
|
|
1734
|
+
pathsIgnoredByGit(cwd, uniqueAncestors, { noIndex: true }),
|
|
1735
|
+
)
|
|
1736
|
+
if (!ignoredAncestors.size) return []
|
|
1737
|
+
|
|
1738
|
+
return files.filter((file) =>
|
|
1739
|
+
ancestorsByFile
|
|
1740
|
+
.get(file)
|
|
1741
|
+
.some((ancestor) => ignoredAncestors.has(ancestor)),
|
|
1742
|
+
)
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function pathMatchesIgnored(file, ignoredPaths) {
|
|
1746
|
+
return ignoredPaths.some(
|
|
1747
|
+
(ignored) => file === ignored || file.startsWith(`${ignored}/`),
|
|
1748
|
+
)
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function ignoredPathsFromGitAdd(stderr) {
|
|
1752
|
+
const header =
|
|
1753
|
+
'The following paths are ignored by one of your .gitignore files:'
|
|
1754
|
+
const start = String(stderr || '').indexOf(header)
|
|
1755
|
+
if (start < 0) return []
|
|
1756
|
+
|
|
1757
|
+
const paths = []
|
|
1758
|
+
for (const line of String(stderr)
|
|
1759
|
+
.slice(start + header.length)
|
|
1760
|
+
.split('\n')) {
|
|
1761
|
+
const trimmed = line.trim().replace(/\/+$/, '')
|
|
1762
|
+
if (!trimmed) continue
|
|
1763
|
+
if (trimmed.startsWith('hint:')) break
|
|
1764
|
+
paths.push(trimmed)
|
|
1765
|
+
}
|
|
1766
|
+
return paths
|
|
1566
1767
|
}
|
|
1567
1768
|
|
|
1568
1769
|
function isTracked(cwd, file) {
|
|
@@ -1631,14 +1832,17 @@ function includeHookLockfiles(cwd, planFiles, dirtyBefore) {
|
|
|
1631
1832
|
}
|
|
1632
1833
|
|
|
1633
1834
|
function stageFiles(cwd, files) {
|
|
1634
|
-
if (!files.length) return
|
|
1835
|
+
if (!files.length) return []
|
|
1635
1836
|
|
|
1636
|
-
// `git add -- ignored
|
|
1637
|
-
//
|
|
1638
|
-
//
|
|
1837
|
+
// `git add -- path/inside/ignored-dir` exits 1 and names the directory,
|
|
1838
|
+
// after it has already updated the index. Drop those paths first. If git
|
|
1839
|
+
// add still reports an ignored directory, take those paths back out and
|
|
1840
|
+
// keep the rest of the batch. Tracked files that match gitignore but do
|
|
1841
|
+
// not sit inside an ignored directory still stage.
|
|
1639
1842
|
const ignored = new Set(gitIgnoredFiles(cwd, files))
|
|
1640
1843
|
const stageableFiles = files.filter((file) => !ignored.has(file))
|
|
1641
|
-
|
|
1844
|
+
const skipped = [...ignored]
|
|
1845
|
+
if (!stageableFiles.length) return skipped
|
|
1642
1846
|
|
|
1643
1847
|
const existing = []
|
|
1644
1848
|
const removed = []
|
|
@@ -1650,11 +1854,33 @@ function stageFiles(cwd, files) {
|
|
|
1650
1854
|
// `git add -A -- deleted/path` warns or misses the deletion when the
|
|
1651
1855
|
// parent directory is already gone. `git rm` stages the index removal.
|
|
1652
1856
|
forEachBatch(existing, (batch) => {
|
|
1653
|
-
git(cwd, ['add', '-A', '--', ...batch]
|
|
1857
|
+
const result = git(cwd, ['add', '-A', '--', ...batch], {
|
|
1858
|
+
allowIgnored: true,
|
|
1859
|
+
})
|
|
1860
|
+
if (!result.status) return
|
|
1861
|
+
|
|
1862
|
+
const ignoredPaths = ignoredPathsFromGitAdd(result.stderr || '')
|
|
1863
|
+
const blocked = batch.filter((file) =>
|
|
1864
|
+
pathMatchesIgnored(file, ignoredPaths),
|
|
1865
|
+
)
|
|
1866
|
+
if (!blocked.length) {
|
|
1867
|
+
throw new Error(
|
|
1868
|
+
(result.stderr || '').trim() || `git add -A -- ${batch.join(' ')}`,
|
|
1869
|
+
)
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// git add writes the index before returning 1, including tracked files
|
|
1873
|
+
// under the ignored directory. Take those back out of this commit.
|
|
1874
|
+
git(cwd, ['reset', '-q', 'HEAD', '--', ...blocked])
|
|
1875
|
+
skipped.push(...blocked)
|
|
1876
|
+
process.stdout.write(
|
|
1877
|
+
`${dim(` skipped (gitignore): ${blocked.join(', ')}`)}\n`,
|
|
1878
|
+
)
|
|
1654
1879
|
})
|
|
1655
1880
|
forEachBatch(removed, (batch) => {
|
|
1656
1881
|
git(cwd, ['rm', '--ignore-unmatch', '-q', '--', ...batch])
|
|
1657
1882
|
})
|
|
1883
|
+
return skipped
|
|
1658
1884
|
}
|
|
1659
1885
|
|
|
1660
1886
|
async function confirm(count) {
|
|
@@ -1899,6 +2125,9 @@ function git(cwd, args, option = {}) {
|
|
|
1899
2125
|
|
|
1900
2126
|
const stderr = result.stderr || ''
|
|
1901
2127
|
if (!retryIndexLock(cwd, attempt, stderr)) {
|
|
2128
|
+
if (option.allowIgnored && ignoredPathsFromGitAdd(stderr).length) {
|
|
2129
|
+
return result
|
|
2130
|
+
}
|
|
1902
2131
|
if (option.inherit && stderr) process.stderr.write(stderr)
|
|
1903
2132
|
throw new Error(stderr.trim() || `git ${args.join(' ')}`)
|
|
1904
2133
|
}
|