gitdone-agent 0.7.1 → 0.7.3

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.
Files changed (2) hide show
  1. package/index.js +52 -7
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -561,6 +561,28 @@ function gitTry(cmd, cwd) {
561
561
  }
562
562
  }
563
563
 
564
+ // Like gitTry(), but hands git an ARGV array instead of a shell command string,
565
+ // so git.exe runs directly with no shell in between. A shell string goes through
566
+ // cmd.exe on Windows, which (a) mangles Cyrillic and (b) treats &, |, %, ^, <, >
567
+ // as operators — so a Bulgarian commit message, or a path containing such a
568
+ // character, silently corrupted the command and the commit failed. The old
569
+ // commit path swallowed that via git() + `|| 'committed'` and reported a fake
570
+ // success while nothing was committed (gd-445). gitDiffUntracked() already uses
571
+ // execFileSync for the same reason. Local calls get 2 min; pass net for 5.
572
+ function gitArgs(args, cwd, { net = false } = {}) {
573
+ try {
574
+ const out = execFileSync('git', args, {
575
+ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
576
+ maxBuffer: GIT_MAX_BUFFER, timeout: net ? GIT_NET_TIMEOUT_MS : GIT_TIMEOUT_MS, env: GIT_ENV,
577
+ }).trim()
578
+ return { ok: true, out }
579
+ } catch (err) {
580
+ const timedOut = err.signal === 'SIGTERM' && err.code == null
581
+ const out = (err.stderr || err.stdout || (timedOut ? `git не отговори и беше прекратен` : err.message) || '').toString().trim()
582
+ return { ok: false, out }
583
+ }
584
+ }
585
+
564
586
  // Never let a token reach the log file or the server's command-result.
565
587
  function redact(text, token) {
566
588
  if (!text || !token) return text
@@ -762,7 +784,18 @@ function getSnapshot(repoPath) {
762
784
  // -uall lists every untracked file individually instead of collapsing a new
763
785
  // directory into a single `dir/` entry — so the change list matches what
764
786
  // GitHub Desktop shows, file-for-file (gd-369).
765
- const statusLines = git('git status --porcelain -uall', repoPath).split('\n').filter(Boolean)
787
+ // Read the status RAW (not via git(), which .trim()s the whole output). The
788
+ // porcelain format is fixed-width — 2 status columns, a space, then the path —
789
+ // and the first line of a worktree-only change starts with a space (e.g.
790
+ // " M .env.example"). git().trim() ate that leading space on the FIRST line
791
+ // only, shifting its columns so the path lost its first character (".env.example"
792
+ // → "env.example"); the mangled path then failed `git add` (or, before gd-445,
793
+ // failed silently). Strip only a trailing \r per line, never leading spaces (gd-446).
794
+ const statusLines = gitRaw('git status --porcelain -uall', repoPath)
795
+ .toString('utf8')
796
+ .split('\n')
797
+ .map((l) => l.replace(/\r$/, ''))
798
+ .filter(Boolean)
766
799
  const modified = []
767
800
  const staged = []
768
801
  const statuses = {}
@@ -1700,20 +1733,32 @@ async function executeCommand(cfg, cmd, repoPath) {
1700
1733
  const auth = cfg.auth?.[repoPath] ?? null
1701
1734
  try {
1702
1735
  if (cmd.type === 'commit') {
1703
- const msg = (cmd.payload?.message ?? 'commit').replace(/"/g, '\\"')
1736
+ // Everything here goes through gitArgs (argv, no shell): the message and the
1737
+ // paths are user/Cyrillic data that cmd.exe would corrupt, which used to make
1738
+ // the commit fail silently while the console showed a fake ✓ (gd-445).
1739
+ const msg = (cmd.payload?.message ?? '').trim() || 'commit'
1704
1740
  // When the UI sends a list of selected files, stage ONLY those so the
1705
1741
  // resulting commit (and the push that follows) contains just the checked
1706
1742
  // files. No list → stage everything (old behaviour / older UIs).
1707
1743
  const files = Array.isArray(cmd.payload?.files)
1708
1744
  ? cmd.payload.files.filter((f) => typeof f === 'string' && f.trim())
1709
1745
  : []
1710
- if (files.length) {
1711
- const quoted = files.map((f) => `"${f.replace(/"/g, '\\"')}"`).join(' ')
1712
- git(`git add -- ${quoted}`, repoPath)
1746
+ const add = files.length
1747
+ ? gitArgs(['add', '--', ...files], repoPath)
1748
+ : gitArgs(['add', '-A'], repoPath)
1749
+ if (!add.ok) throw new Error(`git add се провали: ${add.out}`)
1750
+ const commit = gitArgs(['commit', '-m', msg], repoPath)
1751
+ if (commit.ok) {
1752
+ result = commit.out || 'committed'
1753
+ } else if (/nothing to commit|no changes added|working tree clean/i.test(commit.out)) {
1754
+ // Benign: the checked files carried no staged change (already committed or
1755
+ // whitespace-only). Say so plainly instead of faking success.
1756
+ result = 'няма промени за commit'
1713
1757
  } else {
1714
- git('git add -A', repoPath)
1758
+ // Real failure (hook, identity, lock, corrupt path…) — surface it so the
1759
+ // console shows the reason instead of the old silent fake ✓.
1760
+ throw new Error(commit.out || 'git commit се провали')
1715
1761
  }
1716
- result = git(`git commit -m "${msg}"`, repoPath) || 'committed'
1717
1762
  } else if (cmd.type === 'push' || cmd.type === 'pull') {
1718
1763
  result = pushOrPull(cmd.type, repoPath, auth)
1719
1764
  } else if (cmd.type === 'discard') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdone-agent",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Local git agent for gitdone — watches a local repo and sends snapshots to gitdone.eu",
5
5
  "type": "module",
6
6
  "bin": {