@shivanshshrivas/gwit 0.2.1 → 0.2.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 (3) hide show
  1. package/README.md +12 -9
  2. package/dist/index.cjs +146 -53
  3. package/package.json +5 -4
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
- # gwit
2
-
3
- [![npm version](https://img.shields.io/npm/v/%40shivanshshrivas%2Fgwit)](https://www.npmjs.com/package/@shivanshshrivas/gwit)
4
- [![npm downloads](https://img.shields.io/npm/dm/%40shivanshshrivas%2Fgwit)](https://www.npmjs.com/package/@shivanshshrivas/gwit)
5
- [![CI](https://github.com/shivanshshrivas/gwit/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/shivanshshrivas/gwit/actions/workflows/ci.yml)
6
- [![Node >=20](https://img.shields.io/badge/node-%3E%3D20-339933)](https://nodejs.org/)
7
- [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](./LICENSE)
8
-
9
- Fully isolated git worktrees, in one command.
1
+ # gwit
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40shivanshshrivas%2Fgwit)](https://www.npmjs.com/package/@shivanshshrivas/gwit)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40shivanshshrivas%2Fgwit)](https://www.npmjs.com/package/@shivanshshrivas/gwit)
5
+ [![CI](https://github.com/shivanshshrivas/gwit/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/shivanshshrivas/gwit/actions/workflows/ci.yml)
6
+ [![Node >=20](https://img.shields.io/badge/node-%3E%3D20-339933)](https://nodejs.org/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](./LICENSE)
8
+
9
+ Fully isolated git worktrees, in one command.
10
10
 
11
11
  gwit wraps `git worktree` to turn a fresh checkout into a fully working environment - gitignored files copied, unique port assigned, per-worktree env vars injected, and setup scripts run automatically.
12
12
 
@@ -134,6 +134,9 @@ gwit sync --back # auto-detect branch, then merge back
134
134
  If both sides changed different text regions, gwit applies a clean merge.
135
135
  If both sides changed the same region, gwit writes git-style conflict markers
136
136
  (`<<<<<<<`, `=======`, `>>>>>>>`). Binary conflicts are skipped with a warning.
137
+ Files currently matched by `.gwitinclude` but not present in the snapshot
138
+ (for example, new files created later inside an included ignored directory)
139
+ are reverse-copied directly from worktree to main.
137
140
 
138
141
  ### `gwit open <branch>`
139
142
 
package/dist/index.cjs CHANGED
@@ -437,11 +437,67 @@ function expandGlob(pattern, cwd) {
437
437
  const files = result.stdout.split("\n").filter((f) => f.length > 0);
438
438
  return files.filter((f) => (0, import_minimatch.minimatch)(f, pattern, { dot: true }));
439
439
  }
440
+ function normalizeRelPath(relPath) {
441
+ return relPath.replace(/\\/g, "/");
442
+ }
443
+ function resolveLiteralEntryFiles(entry, sourcePath) {
444
+ const entryPath = entry.replace(/\/$/, "");
445
+ if (entryPath.length === 0) return [];
446
+ if (path2.isAbsolute(entryPath)) return [];
447
+ const sourceResolved = path2.resolve(sourcePath);
448
+ const resolvedEntry = path2.resolve(sourcePath, entryPath);
449
+ const relToSource = path2.relative(sourceResolved, resolvedEntry);
450
+ if (relToSource.startsWith("..") || path2.isAbsolute(relToSource)) return [];
451
+ if (!fs2.existsSync(resolvedEntry)) return [];
452
+ const stat = fs2.statSync(resolvedEntry);
453
+ if (stat.isFile()) {
454
+ return [normalizeRelPath(relToSource)];
455
+ }
456
+ if (!stat.isDirectory()) return [];
457
+ const files = [];
458
+ const stack = [resolvedEntry];
459
+ while (stack.length > 0) {
460
+ const current = stack.pop();
461
+ if (!current) continue;
462
+ const entries = fs2.readdirSync(current, { withFileTypes: true });
463
+ for (const dirent of entries) {
464
+ const childPath = path2.join(current, dirent.name);
465
+ if (dirent.isDirectory()) {
466
+ stack.push(childPath);
467
+ continue;
468
+ }
469
+ if (dirent.isFile()) {
470
+ const rel = path2.relative(sourceResolved, childPath);
471
+ if (!rel.startsWith("..") && !path2.isAbsolute(rel)) {
472
+ files.push(normalizeRelPath(rel));
473
+ }
474
+ }
475
+ }
476
+ }
477
+ return files;
478
+ }
440
479
  function parseGwitInclude(mainPath) {
441
480
  const includePath = path2.join(mainPath, GWITINCLUDE_FILE);
442
481
  if (!fs2.existsSync(includePath)) return [];
443
482
  return _parseLines(fs2.readFileSync(includePath, "utf-8"));
444
483
  }
484
+ function resolveIncludedFilePaths(mainPath, sourcePath) {
485
+ const rawEntries = parseGwitInclude(mainPath);
486
+ if (rawEntries.length === 0) return [];
487
+ const files = /* @__PURE__ */ new Set();
488
+ for (const entry of rawEntries) {
489
+ if (isGlobPattern(entry)) {
490
+ for (const match of expandGlob(entry, sourcePath)) {
491
+ files.add(normalizeRelPath(match));
492
+ }
493
+ continue;
494
+ }
495
+ for (const relPath of resolveLiteralEntryFiles(entry, sourcePath)) {
496
+ files.add(relPath);
497
+ }
498
+ }
499
+ return [...files].sort();
500
+ }
445
501
  function copyIncludedFiles(mainPath, worktreePath) {
446
502
  const rawEntries = parseGwitInclude(mainPath);
447
503
  if (rawEntries.length === 0) return [];
@@ -537,7 +593,7 @@ var path3 = __toESM(require("path"));
537
593
  var import_crypto = require("crypto");
538
594
  var FILES_DIR_NAME = "files";
539
595
  var MANIFEST_FILE_NAME = "manifest.json";
540
- function normalizeRelPath(relPath) {
596
+ function normalizeRelPath2(relPath) {
541
597
  return relPath.replace(/\\/g, "/");
542
598
  }
543
599
  function tryChmod(filePath, mode) {
@@ -565,7 +621,7 @@ function collectEntryFiles(entryPath, mainPath) {
565
621
  }
566
622
  const stat = fs3.statSync(resolvedEntry);
567
623
  if (stat.isFile()) {
568
- return [normalizeRelPath(relToMain)];
624
+ return [normalizeRelPath2(relToMain)];
569
625
  }
570
626
  const files = [];
571
627
  const stack = [resolvedEntry];
@@ -582,7 +638,7 @@ function collectEntryFiles(entryPath, mainPath) {
582
638
  if (entry.isFile()) {
583
639
  const rel = path3.relative(resolvedMain, childPath);
584
640
  if (!rel.startsWith("..") && !path3.isAbsolute(rel)) {
585
- files.push(normalizeRelPath(rel));
641
+ files.push(normalizeRelPath2(rel));
586
642
  }
587
643
  }
588
644
  }
@@ -1421,7 +1477,7 @@ var fs9 = __toESM(require("fs"));
1421
1477
  // src/core/merge.ts
1422
1478
  var fs8 = __toESM(require("fs"));
1423
1479
  var path7 = __toESM(require("path"));
1424
- function normalizeRelPath2(relPath) {
1480
+ function normalizeRelPath3(relPath) {
1425
1481
  return relPath.replace(/\\/g, "/");
1426
1482
  }
1427
1483
  function copyFileToMain(worktreeFile, mainFile) {
@@ -1459,6 +1515,73 @@ function runGitMergeFile(mainFile, snapshotFile, worktreeFile, branch) {
1459
1515
  ]);
1460
1516
  return result.exitCode > 0;
1461
1517
  }
1518
+ function isInMergeScope(relPath, mainPath) {
1519
+ return isGitIgnored(relPath, mainPath) && !isGitTracked(relPath, mainPath);
1520
+ }
1521
+ function processSnapshotTrackedFile(result, relPath, worktreePath, mainPath, slug, branch, baseHash) {
1522
+ if (!isInMergeScope(relPath, mainPath)) {
1523
+ result.skipped.push(relPath);
1524
+ return;
1525
+ }
1526
+ const mainFile = path7.resolve(mainPath, relPath);
1527
+ const worktreeFile = path7.resolve(worktreePath, relPath);
1528
+ const snapshotFile = getSnapshotFilePath(slug, relPath);
1529
+ if (!isExistingFile(mainFile)) {
1530
+ result.skipped.push(relPath);
1531
+ return;
1532
+ }
1533
+ if (!isExistingFile(worktreeFile)) {
1534
+ result.skipped.push(relPath);
1535
+ return;
1536
+ }
1537
+ if (!snapshotFile || !isExistingFile(snapshotFile)) {
1538
+ result.skipped.push(relPath);
1539
+ return;
1540
+ }
1541
+ const mainHash = hashFile(mainFile);
1542
+ const worktreeHash = hashFile(worktreeFile);
1543
+ if (baseHash === mainHash && baseHash === worktreeHash) {
1544
+ result.skipped.push(relPath);
1545
+ return;
1546
+ }
1547
+ if (baseHash === mainHash) {
1548
+ copyFileToMain(worktreeFile, mainFile);
1549
+ result.copied.push(relPath);
1550
+ return;
1551
+ }
1552
+ if (baseHash === worktreeHash || mainHash === worktreeHash) {
1553
+ result.skipped.push(relPath);
1554
+ return;
1555
+ }
1556
+ if (isBinaryFile(mainFile) || isBinaryFile(worktreeFile) || isBinaryFile(snapshotFile)) {
1557
+ result.binarySkipped.push(relPath);
1558
+ return;
1559
+ }
1560
+ const hasConflicts = runGitMergeFile(mainFile, snapshotFile, worktreeFile, branch);
1561
+ if (hasConflicts) {
1562
+ result.conflicts.push(relPath);
1563
+ return;
1564
+ }
1565
+ result.merged.push(relPath);
1566
+ }
1567
+ function processNonSnapshotIncludeFile(result, relPath, worktreePath, mainPath) {
1568
+ if (!isInMergeScope(relPath, mainPath)) {
1569
+ result.skipped.push(relPath);
1570
+ return;
1571
+ }
1572
+ const worktreeFile = path7.resolve(worktreePath, relPath);
1573
+ if (!isExistingFile(worktreeFile)) {
1574
+ result.skipped.push(relPath);
1575
+ return;
1576
+ }
1577
+ const mainFile = path7.resolve(mainPath, relPath);
1578
+ if (isExistingFile(mainFile) && hashFile(mainFile) === hashFile(worktreeFile)) {
1579
+ result.skipped.push(relPath);
1580
+ return;
1581
+ }
1582
+ copyFileToMain(worktreeFile, mainFile);
1583
+ result.copied.push(relPath);
1584
+ }
1462
1585
  function mergeBackIncludedFiles(worktreePath, mainPath, slug) {
1463
1586
  const result = {
1464
1587
  copied: [],
@@ -1469,59 +1592,29 @@ function mergeBackIncludedFiles(worktreePath, mainPath, slug) {
1469
1592
  };
1470
1593
  const manifest = readSnapshot(slug);
1471
1594
  if (!manifest) return result;
1472
- const relPaths = Object.keys(manifest.files).sort();
1473
- for (const rawRelPath of relPaths) {
1474
- const relPath = normalizeRelPath2(rawRelPath);
1475
- const base = manifest.files[relPath];
1595
+ const snapshotEntries = Object.keys(manifest.files).sort();
1596
+ const snapshotRelPathSet = new Set(snapshotEntries.map((relPath) => normalizeRelPath3(relPath)));
1597
+ for (const rawRelPath of snapshotEntries) {
1598
+ const relPath = normalizeRelPath3(rawRelPath);
1599
+ const base = manifest.files[rawRelPath];
1476
1600
  if (!base) {
1477
1601
  result.skipped.push(relPath);
1478
1602
  continue;
1479
1603
  }
1480
- if (!isGitIgnored(relPath, mainPath) || isGitTracked(relPath, mainPath)) {
1481
- result.skipped.push(relPath);
1482
- continue;
1483
- }
1484
- const mainFile = path7.resolve(mainPath, relPath);
1485
- const worktreeFile = path7.resolve(worktreePath, relPath);
1486
- const snapshotFile = getSnapshotFilePath(slug, relPath);
1487
- if (!isExistingFile(mainFile)) {
1488
- result.skipped.push(relPath);
1489
- continue;
1490
- }
1491
- if (!isExistingFile(worktreeFile)) {
1492
- result.skipped.push(relPath);
1493
- continue;
1494
- }
1495
- if (!snapshotFile || !isExistingFile(snapshotFile)) {
1496
- result.skipped.push(relPath);
1497
- continue;
1498
- }
1499
- const baseHash = base.hash;
1500
- const mainHash = hashFile(mainFile);
1501
- const worktreeHash = hashFile(worktreeFile);
1502
- if (baseHash === mainHash && baseHash === worktreeHash) {
1503
- result.skipped.push(relPath);
1504
- continue;
1505
- }
1506
- if (baseHash === mainHash) {
1507
- copyFileToMain(worktreeFile, mainFile);
1508
- result.copied.push(relPath);
1509
- continue;
1510
- }
1511
- if (baseHash === worktreeHash || mainHash === worktreeHash) {
1512
- result.skipped.push(relPath);
1513
- continue;
1514
- }
1515
- if (isBinaryFile(mainFile) || isBinaryFile(worktreeFile) || isBinaryFile(snapshotFile)) {
1516
- result.binarySkipped.push(relPath);
1517
- continue;
1518
- }
1519
- const hasConflicts = runGitMergeFile(mainFile, snapshotFile, worktreeFile, manifest.branch);
1520
- if (hasConflicts) {
1521
- result.conflicts.push(relPath);
1522
- } else {
1523
- result.merged.push(relPath);
1524
- }
1604
+ processSnapshotTrackedFile(
1605
+ result,
1606
+ relPath,
1607
+ worktreePath,
1608
+ mainPath,
1609
+ slug,
1610
+ manifest.branch,
1611
+ base.hash
1612
+ );
1613
+ }
1614
+ const includeFiles = resolveIncludedFilePaths(mainPath, worktreePath);
1615
+ for (const relPath of includeFiles) {
1616
+ if (snapshotRelPathSet.has(relPath)) continue;
1617
+ processNonSnapshotIncludeFile(result, relPath, worktreePath, mainPath);
1525
1618
  }
1526
1619
  return result;
1527
1620
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivanshshrivas/gwit",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Fully isolated git worktrees for parallel development",
5
5
  "keywords": [
6
6
  "git",
@@ -24,9 +24,10 @@
24
24
  "files": [
25
25
  "dist"
26
26
  ],
27
- "scripts": {
28
- "build": "tsup",
29
- "dev": "tsx src/index.ts",
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "prepack": "npm run build",
30
+ "dev": "tsx src/index.ts",
30
31
  "typecheck": "tsc --noEmit",
31
32
  "test": "vitest run",
32
33
  "test:watch": "vitest",