gitdone-agent 0.6.5 → 0.6.6

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 +42 -5
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -27,7 +27,7 @@ import { randomUUID } from 'node:crypto'
27
27
  // Reported to the server on every sync so the web UI can flag outdated agents.
28
28
  // Keep in lockstep with packages/agent/package.json "version" AND
29
29
  // src/lib/agentVersion.ts LATEST_AGENT_VERSION.
30
- const AGENT_VERSION = '0.6.5'
30
+ const AGENT_VERSION = '0.6.6'
31
31
 
32
32
  const AGENT_DIR = join(homedir(), '.gitdone-agent')
33
33
  const CONFIG_PATH = join(AGENT_DIR, 'config.json')
@@ -403,6 +403,42 @@ function decodeDiffText(buf) {
403
403
  }
404
404
  }
405
405
 
406
+ // Git C-quotes paths with spaces / special chars / non-ASCII bytes, wrapping them
407
+ // in double quotes with backslash escapes (`"export coca_cola/x.cs"`, octal `\NNN`
408
+ // for raw bytes). Both `git status --porcelain` and the `diff --git` header do it.
409
+ // Left unhandled, files with spaces (or Cyrillic) got mis-keyed and their diff was
410
+ // dropped (gd-321). This reverses it back to a plain path so the diff key matches
411
+ // the file name shown in the list.
412
+ function unquoteGitPath(s) {
413
+ if (typeof s !== 'string' || s.length < 2 || s[0] !== '"' || s[s.length - 1] !== '"') return s
414
+ const body = s.slice(1, -1)
415
+ const bytes = []
416
+ for (let i = 0; i < body.length; i++) {
417
+ if (body[i] === '\\' && i + 1 < body.length) {
418
+ const n = body[i + 1]
419
+ if (n === 'n') { bytes.push(10); i++ }
420
+ else if (n === 't') { bytes.push(9); i++ }
421
+ else if (n === 'r') { bytes.push(13); i++ }
422
+ else if (n === '"') { bytes.push(34); i++ }
423
+ else if (n === '\\') { bytes.push(92); i++ }
424
+ else if (n >= '0' && n <= '7') { bytes.push(parseInt(body.substr(i + 1, 3), 8) & 0xff); i += 3 }
425
+ else { bytes.push(body.charCodeAt(i)) }
426
+ } else {
427
+ bytes.push(body.charCodeAt(i) & 0xff)
428
+ }
429
+ }
430
+ return Buffer.from(bytes).toString('utf8')
431
+ }
432
+
433
+ // Read the b-side path from a `diff --git` header line, handling git's quoting
434
+ // of paths with spaces / special chars (gd-321).
435
+ function diffHeaderPath(hdr) {
436
+ const q = hdr.match(/ ("b\/.*")$/) // quoted: "a/x" "b/x"
437
+ if (q) return unquoteGitPath(q[1]).replace(/^b\//, '')
438
+ const u = hdr.match(/ b\/(.*)$/) // plain: a/x b/x
439
+ return u ? u[1] : null
440
+ }
441
+
406
442
  function parseDiffByFile(diffBuf) {
407
443
  const files = {}
408
444
  if (!diffBuf || diffBuf.length === 0) return files
@@ -413,10 +449,11 @@ function parseDiffByFile(diffBuf) {
413
449
  for (const section of sections) {
414
450
  if (!section.trim()) continue
415
451
  // Decode THIS file's bytes on their own (UTF-8 or CP1251), then read the
416
- // filename from the decoded header.
452
+ // filename from the decoded header (quoted or not).
417
453
  const text = decodeDiffText(Buffer.from(section, 'latin1'))
418
- const match = text.match(/^diff --git a\/.+ b\/(.+)$/m)
419
- if (match) files[match[1]] = text
454
+ const hdr = text.match(/^diff --git (.+)$/m)
455
+ const name = hdr ? diffHeaderPath(hdr[1]) : null
456
+ if (name) files[name] = text
420
457
  }
421
458
  return files
422
459
  }
@@ -430,7 +467,7 @@ function getSnapshot(repoPath) {
430
467
  const statuses = {}
431
468
  for (const line of statusLines) {
432
469
  const xy = line.slice(0, 2)
433
- const file = line.slice(3)
470
+ const file = unquoteGitPath(line.slice(3))
434
471
  statuses[file] = xy
435
472
  if (xy[0] !== ' ' && xy[0] !== '?') staged.push(file)
436
473
  if (xy[1] !== ' ') modified.push(file)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdone-agent",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
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": {