difit 4.0.0 → 4.0.1

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 (82) hide show
  1. package/README.ja.md +15 -14
  2. package/README.ko.md +15 -14
  3. package/README.md +15 -14
  4. package/README.zh.md +15 -14
  5. package/dist/cli/index.js +16 -0
  6. package/dist/cli/index.test.js +125 -1
  7. package/dist/client/assets/{_basePickBy-B9N-f0iT.js → _basePickBy-ChXFkTMC.js} +1 -1
  8. package/dist/client/assets/{_baseUniq-tbL7nVvN.js → _baseUniq-Mj_sFFQW.js} +1 -1
  9. package/dist/client/assets/{arc-BOY-7mep.js → arc-BMA6S9F1.js} +1 -1
  10. package/dist/client/assets/{architectureDiagram-2XIMDMQ5-59AvHaSB.js → architectureDiagram-2XIMDMQ5-0uiM_v5K.js} +1 -1
  11. package/dist/client/assets/{blockDiagram-WCTKOSBZ-DXIlumQk.js → blockDiagram-WCTKOSBZ-CM7ZLL6F.js} +1 -1
  12. package/dist/client/assets/{c4Diagram-IC4MRINW-BbfZ0uRn.js → c4Diagram-IC4MRINW-DKtCnVwn.js} +1 -1
  13. package/dist/client/assets/channel-D057yzDp.js +1 -0
  14. package/dist/client/assets/{chunk-4BX2VUAB-l7rcB2IW.js → chunk-4BX2VUAB-Wsl8DxEB.js} +1 -1
  15. package/dist/client/assets/{chunk-55IACEB6-CrZL3qv9.js → chunk-55IACEB6-CHm9X5i7.js} +1 -1
  16. package/dist/client/assets/{chunk-FMBD7UC4-CrKv7ndg.js → chunk-FMBD7UC4-BSa8SHgd.js} +1 -1
  17. package/dist/client/assets/{chunk-JSJVCQXG-DyBDhAEM.js → chunk-JSJVCQXG-Cpk76oJ3.js} +1 -1
  18. package/dist/client/assets/{chunk-KX2RTZJC-By5mkZmU.js → chunk-KX2RTZJC-D8YvfZVu.js} +1 -1
  19. package/dist/client/assets/{chunk-NQ4KR5QH-C30p9xRx.js → chunk-NQ4KR5QH-BogviJOv.js} +1 -1
  20. package/dist/client/assets/{chunk-QZHKN3VN-DVlhR2wU.js → chunk-QZHKN3VN-DwLJYu26.js} +1 -1
  21. package/dist/client/assets/{chunk-WL4C6EOR-Cn7a6CO3.js → chunk-WL4C6EOR-BFDpGxW2.js} +1 -1
  22. package/dist/client/assets/classDiagram-VBA2DB6C---D4iOts.js +1 -0
  23. package/dist/client/assets/classDiagram-v2-RAHNMMFH---D4iOts.js +1 -0
  24. package/dist/client/assets/clone-xSR3otEf.js +1 -0
  25. package/dist/client/assets/{cose-bilkent-S5V4N54A-LyauIk_9.js → cose-bilkent-S5V4N54A-oEosZ_5y.js} +1 -1
  26. package/dist/client/assets/{dagre-KLK3FWXG-DRWb2KE3.js → dagre-KLK3FWXG-gFld4u1H.js} +1 -1
  27. package/dist/client/assets/{diagram-E7M64L7V-ChT6mNWK.js → diagram-E7M64L7V-gJq3kSrf.js} +1 -1
  28. package/dist/client/assets/{diagram-IFDJBPK2-CqbTduoP.js → diagram-IFDJBPK2-BsUm_q22.js} +1 -1
  29. package/dist/client/assets/{diagram-P4PSJMXO-Bzv5Z3ri.js → diagram-P4PSJMXO-juB-sfcR.js} +1 -1
  30. package/dist/client/assets/{erDiagram-INFDFZHY-CvXfUZ4L.js → erDiagram-INFDFZHY-Dn77qXAt.js} +1 -1
  31. package/dist/client/assets/{flowDiagram-PKNHOUZH-CxmpNUKq.js → flowDiagram-PKNHOUZH-DtmvDYdN.js} +1 -1
  32. package/dist/client/assets/{ganttDiagram-A5KZAMGK-9LpZCsg6.js → ganttDiagram-A5KZAMGK-BlDaKLbQ.js} +1 -1
  33. package/dist/client/assets/{gitGraphDiagram-K3NZZRJ6-C6yZOrQJ.js → gitGraphDiagram-K3NZZRJ6-DeAAeuMS.js} +1 -1
  34. package/dist/client/assets/{graph-bUZ7uHLW.js → graph-NX9gBP47.js} +1 -1
  35. package/dist/client/assets/{index-BLNN1bfE.js → index-kJdw4DY-.js} +15 -15
  36. package/dist/client/assets/{infoDiagram-LFFYTUFH-Djdy3W21.js → infoDiagram-LFFYTUFH-CAaX023c.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-PHBUUO56-oOdwCpeS.js → ishikawaDiagram-PHBUUO56-CmiTQStv.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-4ABVD52K-DTb_nGAw.js → journeyDiagram-4ABVD52K-B0SHC7mz.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-K7BYSVSG-CMtP7pHA.js → kanban-definition-K7BYSVSG-IfRdhzz7.js} +1 -1
  40. package/dist/client/assets/{layout-CXr5MatK.js → layout-l3OdNQhJ.js} +1 -1
  41. package/dist/client/assets/{linear-pOMS9pjV.js → linear-CQ0hx5Qs.js} +1 -1
  42. package/dist/client/assets/{mermaid.core-DV5JJ1Ie.js → mermaid.core-DqlPTabt.js} +4 -4
  43. package/dist/client/assets/{mindmap-definition-YRQLILUH-DN-sbonc.js → mindmap-definition-YRQLILUH-DIgSmG_f.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-SKSYHLDU-tAHCkgh1.js → pieDiagram-SKSYHLDU-FzM5qoIB.js} +1 -1
  45. package/dist/client/assets/{prism-csharp-5CQ0RcEE.js → prism-csharp-DCfUUOUs.js} +1 -1
  46. package/dist/client/assets/{prism-elixir-BSOTyVg2.js → prism-elixir-riuOL1mm.js} +1 -1
  47. package/dist/client/assets/{prism-hcl-BYvi1mtM.js → prism-hcl-CizuX1s4.js} +1 -1
  48. package/dist/client/assets/{prism-java-DMU2FM4X.js → prism-java-DYCKrDUh.js} +1 -1
  49. package/dist/client/assets/{prism-perl-CpfvaEQk.js → prism-perl-BJwBYR3Y.js} +1 -1
  50. package/dist/client/assets/{prism-php-SC920LoD.js → prism-php-BMhFuA7y.js} +1 -1
  51. package/dist/client/assets/{prism-ruby-DZph-YiO.js → prism-ruby-Bcu0cDEh.js} +1 -1
  52. package/dist/client/assets/{prism-solidity-qTLbmiAT.js → prism-solidity-DDDs3w-w.js} +1 -1
  53. package/dist/client/assets/{quadrantDiagram-337W2JSQ-B0wODmgR.js → quadrantDiagram-337W2JSQ-BBrApyD7.js} +1 -1
  54. package/dist/client/assets/{requirementDiagram-Z7DCOOCP-A3aeHC06.js → requirementDiagram-Z7DCOOCP-CLXiwUaA.js} +1 -1
  55. package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-BWa6kZhG.js → sankeyDiagram-WA2Y5GQK-9Y3Ly5qe.js} +1 -1
  56. package/dist/client/assets/{sequenceDiagram-2WXFIKYE-Cx_COX9G.js → sequenceDiagram-2WXFIKYE-DEpX1BA5.js} +1 -1
  57. package/dist/client/assets/{stateDiagram-RAJIS63D-BXGnN6rZ.js → stateDiagram-RAJIS63D-Ck3ullwA.js} +1 -1
  58. package/dist/client/assets/stateDiagram-v2-FVOUBMTO-X6UiDsar.js +1 -0
  59. package/dist/client/assets/{timeline-definition-YZTLITO2-DbqaUm9k.js → timeline-definition-YZTLITO2-CMezf3XV.js} +1 -1
  60. package/dist/client/assets/{treemap-KZPCXAKY-CfEujPCR.js → treemap-KZPCXAKY-DqrcV0gQ.js} +1 -1
  61. package/dist/client/assets/{vennDiagram-LZ73GAT5-CqJE8CAD.js → vennDiagram-LZ73GAT5-eQg945Fz.js} +1 -1
  62. package/dist/client/assets/{xychartDiagram-JWTSCODW-CfdDvzHC.js → xychartDiagram-JWTSCODW-_hqdXeX1.js} +1 -1
  63. package/dist/client/index.html +1 -1
  64. package/dist/server/git-diff-tui.d.ts +1 -1
  65. package/dist/server/git-diff-tui.js +7 -5
  66. package/dist/server/git-diff-tui.test.d.ts +1 -0
  67. package/dist/server/git-diff-tui.test.js +60 -0
  68. package/dist/server/git-diff.d.ts +3 -1
  69. package/dist/server/git-diff.js +40 -3
  70. package/dist/server/git-diff.test.js +20 -0
  71. package/dist/server/server.d.ts +1 -0
  72. package/dist/server/server.js +4 -3
  73. package/dist/server/server.test.js +32 -0
  74. package/dist/tui/App.d.ts +1 -0
  75. package/dist/tui/App.js +2 -2
  76. package/dist/types/diff.d.ts +1 -0
  77. package/package.json +1 -1
  78. package/dist/client/assets/channel-cZXsTJxA.js +0 -1
  79. package/dist/client/assets/classDiagram-VBA2DB6C-B_coIPEy.js +0 -1
  80. package/dist/client/assets/classDiagram-v2-RAHNMMFH-B_coIPEy.js +0 -1
  81. package/dist/client/assets/clone-BjaT2HOk.js +0 -1
  82. package/dist/client/assets/stateDiagram-v2-FVOUBMTO-CMw3xNha.js +0 -1
@@ -1,6 +1,6 @@
1
1
  import simpleGit from 'simple-git';
2
2
  import { validateDiffArguments, createCommitRangeString } from '../cli/utils.js';
3
- export async function loadGitDiff(targetCommitish, baseCommitish, repoPath) {
3
+ export async function loadGitDiff(targetCommitish, baseCommitish, repoPath, contextLines) {
4
4
  // Validate arguments
5
5
  const validation = validateDiffArguments(targetCommitish, baseCommitish);
6
6
  if (!validation.valid) {
@@ -44,34 +44,36 @@ export async function loadGitDiff(targetCommitish, baseCommitish, repoPath) {
44
44
  const path = pathParts.join('\t');
45
45
  return { status, path };
46
46
  });
47
+ const contextArgs = contextLines !== undefined ? [`-U${contextLines}`] : [];
47
48
  // Get diff for each file individually
48
49
  const fileDiffs = await Promise.all(fileChanges.map(async ({ status, path }) => {
49
50
  let fileDiff = '';
50
51
  // Handle individual file diffs (base is always a regular commit)
51
52
  if (targetCommitish === 'working') {
52
53
  // Show unstaged changes (working vs staged)
53
- fileDiff = await git.diff(['--', path]);
54
+ fileDiff = await git.diff([...contextArgs, '--', path]);
54
55
  }
55
56
  else if (targetCommitish === 'staged') {
56
57
  // Show staged changes against base commit
57
- fileDiff = await git.diff(['--cached', baseCommitish, '--', path]);
58
+ fileDiff = await git.diff(['--cached', baseCommitish, ...contextArgs, '--', path]);
58
59
  }
59
60
  else if (targetCommitish === '.') {
60
61
  // Show all uncommitted changes against base commit
61
- fileDiff = await git.diff([baseCommitish, '--', path]);
62
+ fileDiff = await git.diff([baseCommitish, ...contextArgs, '--', path]);
62
63
  }
63
64
  else {
64
65
  try {
65
66
  // Both are regular commits: standard commit-to-commit comparison
66
67
  fileDiff = await git.diff([
67
68
  createCommitRangeString(baseCommitish, targetCommitish),
69
+ ...contextArgs,
68
70
  '--',
69
71
  path,
70
72
  ]);
71
73
  }
72
74
  catch {
73
75
  // For new files or if parent doesn't exist
74
- fileDiff = await git.diff([targetCommitish, '--', path]);
76
+ fileDiff = await git.diff([targetCommitish, ...contextArgs, '--', path]);
75
77
  }
76
78
  }
77
79
  const lines = fileDiff.split('\n');
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { loadGitDiff } from './git-diff-tui.js';
3
+ const mockDiff = vi.hoisted(() => vi.fn());
4
+ const mockSimpleGit = vi.hoisted(() => vi.fn(() => ({ diff: mockDiff })));
5
+ vi.mock('simple-git', () => ({
6
+ default: mockSimpleGit,
7
+ }));
8
+ describe('loadGitDiff', () => {
9
+ beforeEach(() => {
10
+ mockDiff.mockReset();
11
+ mockSimpleGit.mockClear();
12
+ });
13
+ it.each([
14
+ {
15
+ name: 'working tree diffs',
16
+ targetCommitish: 'working',
17
+ baseCommitish: 'staged',
18
+ expectedListArgs: ['--name-status'],
19
+ expectedFileArgs: ['-U5', '--', 'src/file.ts'],
20
+ },
21
+ {
22
+ name: 'staged diffs',
23
+ targetCommitish: 'staged',
24
+ baseCommitish: 'HEAD',
25
+ expectedListArgs: ['--cached', 'HEAD', '--name-status'],
26
+ expectedFileArgs: ['--cached', 'HEAD', '-U5', '--', 'src/file.ts'],
27
+ },
28
+ {
29
+ name: 'working tree against a base commit',
30
+ targetCommitish: '.',
31
+ baseCommitish: 'HEAD',
32
+ expectedListArgs: ['HEAD', '--name-status'],
33
+ expectedFileArgs: ['HEAD', '-U5', '--', 'src/file.ts'],
34
+ },
35
+ {
36
+ name: 'commit comparisons',
37
+ targetCommitish: 'HEAD',
38
+ baseCommitish: 'HEAD^',
39
+ expectedListArgs: ['HEAD^...HEAD', '--name-status'],
40
+ expectedFileArgs: ['HEAD^...HEAD', '-U5', '--', 'src/file.ts'],
41
+ },
42
+ ])('passes context lines for $name', async ({ targetCommitish, baseCommitish, expectedListArgs, expectedFileArgs }) => {
43
+ mockDiff
44
+ .mockResolvedValueOnce('M\tsrc/file.ts')
45
+ .mockResolvedValueOnce('@@ -1 +1 @@\n-old line\n+new line\n');
46
+ const result = await loadGitDiff(targetCommitish, baseCommitish, '/repo', 5);
47
+ expect(mockSimpleGit).toHaveBeenCalledWith('/repo');
48
+ expect(mockDiff).toHaveBeenNthCalledWith(1, expectedListArgs);
49
+ expect(mockDiff).toHaveBeenNthCalledWith(2, expectedFileArgs);
50
+ expect(result).toEqual([
51
+ {
52
+ path: 'src/file.ts',
53
+ status: 'M',
54
+ diff: '@@ -1 +1 @@\n-old line\n+new line\n',
55
+ additions: 1,
56
+ deletions: 1,
57
+ },
58
+ ]);
59
+ });
60
+ });
@@ -7,7 +7,7 @@ export declare class GitDiffParser {
7
7
  private static readonly GENERATED_HEADER_SCAN_BYTES;
8
8
  constructor(repoPath?: string);
9
9
  private normalizeRepositoryRelativePath;
10
- parseDiff(targetCommitish: string, baseCommitish: string, ignoreWhitespace?: boolean): Promise<DiffResponse>;
10
+ parseDiff(targetCommitish: string, baseCommitish: string, ignoreWhitespace?: boolean, contextLines?: number): Promise<DiffResponse>;
11
11
  private parseUnifiedDiff;
12
12
  private decodeGitPath;
13
13
  private extractPathFromLine;
@@ -27,6 +27,7 @@ export declare class GitDiffParser {
27
27
  resolveCommitish(commitish: string): Promise<string>;
28
28
  clearResolvedCommitCache(): void;
29
29
  getDefaultBranch(): Promise<string | null>;
30
+ getOriginDefaultBranch(): Promise<string | null>;
30
31
  getRevisionOptions(currentBase?: string, currentTarget?: string): Promise<{
31
32
  branches: Array<{
32
33
  name: string;
@@ -37,6 +38,7 @@ export declare class GitDiffParser {
37
38
  shortHash: string;
38
39
  message: string;
39
40
  }>;
41
+ originDefaultBranch?: string;
40
42
  resolvedBase?: string;
41
43
  resolvedTarget?: string;
42
44
  }>;
@@ -28,7 +28,7 @@ export class GitDiffParser {
28
28
  }
29
29
  return normalizedFilepath;
30
30
  }
31
- async parseDiff(targetCommitish, baseCommitish, ignoreWhitespace = false) {
31
+ async parseDiff(targetCommitish, baseCommitish, ignoreWhitespace = false, contextLines) {
32
32
  try {
33
33
  // Validate arguments
34
34
  const validation = validateDiffArguments(targetCommitish, baseCommitish);
@@ -65,6 +65,9 @@ export class GitDiffParser {
65
65
  if (ignoreWhitespace) {
66
66
  diffArgs.push('-w');
67
67
  }
68
+ if (contextLines !== undefined) {
69
+ diffArgs.push(`-U${contextLines}`);
70
+ }
68
71
  // Ignore external diff-tools to unify output.
69
72
  // https://github.com/yoshiko-pg/difit/issues/19
70
73
  diffArgs.push('--no-ext-diff', '--color=never');
@@ -478,11 +481,39 @@ export class GitDiffParser {
478
481
  }
479
482
  return null;
480
483
  }
484
+ async getOriginDefaultBranch() {
485
+ try {
486
+ const result = await this.git.raw(['symbolic-ref', 'refs/remotes/origin/HEAD']);
487
+ const match = result.trim().match(/refs\/remotes\/origin\/(.+)/);
488
+ if (match) {
489
+ return `origin/${match[1]}`;
490
+ }
491
+ }
492
+ catch {
493
+ const commonDefaults = ['main', 'master'];
494
+ for (const defaultName of commonDefaults) {
495
+ try {
496
+ await this.git.raw([
497
+ 'show-ref',
498
+ '--verify',
499
+ '--quiet',
500
+ `refs/remotes/origin/${defaultName}`,
501
+ ]);
502
+ return `origin/${defaultName}`;
503
+ }
504
+ catch {
505
+ // Ignore missing refs and continue checking common defaults.
506
+ }
507
+ }
508
+ }
509
+ return null;
510
+ }
481
511
  async getRevisionOptions(currentBase, currentTarget) {
482
- const [branchResult, logResult, defaultBranch] = await Promise.all([
512
+ const [branchResult, logResult, defaultBranch, originDefaultBranch] = await Promise.all([
483
513
  this.git.branchLocal(),
484
514
  this.git.log({ maxCount: 20 }),
485
515
  this.getDefaultBranch(),
516
+ this.getOriginDefaultBranch(),
486
517
  ]);
487
518
  const branches = Object.entries(branchResult.branches).map(([name, data]) => ({
488
519
  name,
@@ -526,6 +557,12 @@ export class GitDiffParser {
526
557
  // If resolution fails, leave undefined
527
558
  }
528
559
  }
529
- return { branches, commits, resolvedBase, resolvedTarget };
560
+ return {
561
+ branches,
562
+ commits,
563
+ originDefaultBranch: originDefaultBranch ?? undefined,
564
+ resolvedBase,
565
+ resolvedTarget,
566
+ };
530
567
  }
531
568
  }
@@ -1071,6 +1071,26 @@ index abc123..def456 100644
1071
1071
  });
1072
1072
  });
1073
1073
  describe('parseDiff', () => {
1074
+ it('passes context lines through to git diff', async () => {
1075
+ const gitDiff = parser.git.diff;
1076
+ const gitRevparse = parser.git.revparse;
1077
+ gitRevparse
1078
+ .mockResolvedValueOnce('1234567890abcdef1234567890abcdef12345678')
1079
+ .mockResolvedValueOnce('abcdef1234567890abcdef1234567890abcdef12');
1080
+ gitDiff.mockResolvedValue('');
1081
+ const response = await parser.parseDiff('HEAD', 'HEAD~1', false, 5);
1082
+ expect(gitDiff).toHaveBeenCalledWith([
1083
+ 'abcdef1...1234567',
1084
+ '-U5',
1085
+ '--no-ext-diff',
1086
+ '--color=never',
1087
+ ]);
1088
+ expect(response).toEqual({
1089
+ commit: 'abcdef1...1234567',
1090
+ files: [],
1091
+ isEmpty: true,
1092
+ });
1093
+ });
1074
1094
  it('accepts branch refs with revision suffixes', async () => {
1075
1095
  const gitDiff = parser.git.diff;
1076
1096
  const gitRevparse = parser.git.revparse;
@@ -15,6 +15,7 @@ interface ServerOptions {
15
15
  keepAlive?: boolean;
16
16
  diffMode?: DiffMode;
17
17
  repoPath?: string;
18
+ contextLines?: number;
18
19
  }
19
20
  export declare function startServer(options: ServerOptions): Promise<{
20
21
  port: number;
@@ -51,7 +51,7 @@ export async function startServer(options) {
51
51
  diffDataCache = parser.parseStdinDiff(options.stdinDiff);
52
52
  }
53
53
  else {
54
- diffDataCache = await parser.parseDiff(options.targetCommitish ?? '', options.baseCommitish ?? '', currentIgnoreWhitespace);
54
+ diffDataCache = await parser.parseDiff(options.targetCommitish ?? '', options.baseCommitish ?? '', currentIgnoreWhitespace, options.contextLines);
55
55
  }
56
56
  // Function to invalidate cache when file changes are detected
57
57
  const invalidateCache = () => {
@@ -92,7 +92,7 @@ export async function startServer(options) {
92
92
  currentIgnoreWhitespace = ignoreWhitespace;
93
93
  currentBaseCommitish = requestedBase;
94
94
  currentTargetCommitish = requestedTarget;
95
- diffDataCache = await parser.parseDiff(requestedTarget, requestedBase, ignoreWhitespace);
95
+ diffDataCache = await parser.parseDiff(requestedTarget, requestedBase, ignoreWhitespace, options.contextLines);
96
96
  generatedStatusCache.clear();
97
97
  }
98
98
  // Resolve symbolic refs like HEAD/HEAD^ to actual hashes for the UI
@@ -179,7 +179,7 @@ export async function startServer(options) {
179
179
  return;
180
180
  }
181
181
  try {
182
- const { branches, commits, resolvedBase, resolvedTarget } = await parser.getRevisionOptions(currentBaseCommitish, currentTargetCommitish);
182
+ const { branches, commits, originDefaultBranch, resolvedBase, resolvedTarget } = await parser.getRevisionOptions(currentBaseCommitish, currentTargetCommitish);
183
183
  const response = {
184
184
  specialOptions: [
185
185
  { value: '.', label: 'All Uncommitted Changes' },
@@ -188,6 +188,7 @@ export async function startServer(options) {
188
188
  ],
189
189
  branches,
190
190
  commits,
191
+ originDefaultBranch,
191
192
  resolvedBase,
192
193
  resolvedTarget,
193
194
  };
@@ -5,6 +5,7 @@ import { startServer } from './server.js';
5
5
  // Add fetch polyfill for Node.js test environment
6
6
  const { fetch } = await import('undici');
7
7
  globalThis.fetch = fetch;
8
+ const parserInstances = vi.hoisted(() => []);
8
9
  // Helper function to get available port
9
10
  async function getAvailablePort(preferredPort) {
10
11
  let port = preferredPort;
@@ -25,6 +26,9 @@ async function getAvailablePort(preferredPort) {
25
26
  // Mock GitDiffParser
26
27
  vi.mock('./git-diff.js', () => {
27
28
  class GitDiffParserMock {
29
+ constructor() {
30
+ parserInstances.push(this);
31
+ }
28
32
  validateCommit = vi.fn().mockResolvedValue(true);
29
33
  parseDiff = vi.fn().mockResolvedValue({
30
34
  targetCommit: 'abc123',
@@ -68,6 +72,7 @@ vi.mock('./git-diff.js', () => {
68
72
  getRevisionOptions = vi.fn().mockResolvedValue({
69
73
  branches: [{ name: 'main', current: true }],
70
74
  commits: [{ hash: 'abc1234', shortHash: 'abc1234', message: 'Test commit' }],
75
+ originDefaultBranch: 'origin/main',
71
76
  resolvedBase: 'abc1234',
72
77
  resolvedTarget: 'def5678',
73
78
  });
@@ -167,6 +172,7 @@ describe('Server Integration Tests', () => {
167
172
  // Mock process.exit to prevent tests from actually exiting
168
173
  originalProcessExit = process.exit;
169
174
  process.exit = vi.fn();
175
+ parserInstances.length = 0;
170
176
  });
171
177
  afterEach(async () => {
172
178
  // Restore process.exit
@@ -226,6 +232,17 @@ describe('Server Integration Tests', () => {
226
232
  servers.push(result.server);
227
233
  expect(result.url).toContain('http://localhost:'); // Display host conversion
228
234
  });
235
+ it('passes context lines to the initial diff load', async () => {
236
+ const result = await startServer({
237
+ targetCommitish: 'HEAD',
238
+ baseCommitish: 'HEAD^',
239
+ preferredPort: 9025,
240
+ contextLines: 4,
241
+ });
242
+ servers.push(result.server);
243
+ const parser = parserInstances.at(-1);
244
+ expect(parser?.parseDiff).toHaveBeenCalledWith('HEAD', 'HEAD^', false, 4);
245
+ });
229
246
  });
230
247
  describe('API endpoints', () => {
231
248
  let port;
@@ -256,6 +273,20 @@ describe('Server Integration Tests', () => {
256
273
  expect(response.ok).toBe(true);
257
274
  expect(data).toHaveProperty('ignoreWhitespace', true);
258
275
  });
276
+ it('GET /api/diff preserves context lines when recalculating revisions', async () => {
277
+ const result = await startServer({
278
+ targetCommitish: 'HEAD',
279
+ baseCommitish: 'HEAD^',
280
+ preferredPort: 9031,
281
+ contextLines: 2,
282
+ });
283
+ servers.push(result.server);
284
+ const parser = parserInstances.at(-1);
285
+ parser?.parseDiff.mockClear();
286
+ const response = await fetch(`http://localhost:${result.port}/api/diff?base=main&target=feature&ignoreWhitespace=true`);
287
+ expect(response.ok).toBe(true);
288
+ expect(parser?.parseDiff).toHaveBeenCalledWith('feature', 'main', true, 2);
289
+ });
259
290
  it('GET /api/diff returns comment import payload when configured', async () => {
260
291
  const importedComments = [
261
292
  {
@@ -565,6 +596,7 @@ describe('Server Integration Tests', () => {
565
596
  expect(data.commits).toEqual([
566
597
  { hash: 'abc1234', shortHash: 'abc1234', message: 'Test commit' },
567
598
  ]);
599
+ expect(data.originDefaultBranch).toBe('origin/main');
568
600
  expect(data.resolvedBase).toBe('abc1234');
569
601
  expect(data.resolvedTarget).toBe('def5678');
570
602
  });
package/dist/tui/App.d.ts CHANGED
@@ -4,6 +4,7 @@ interface AppProps {
4
4
  baseCommitish: string;
5
5
  mode?: string;
6
6
  repoPath?: string;
7
+ contextLines?: number;
7
8
  }
8
9
  declare const App: React.FC<AppProps>;
9
10
  export default App;
package/dist/tui/App.js CHANGED
@@ -6,7 +6,7 @@ import DiffViewer from './components/DiffViewer.js';
6
6
  import FileList from './components/FileList.js';
7
7
  import SideBySideDiffViewer from './components/SideBySideDiffViewer.js';
8
8
  import StatusBar from './components/StatusBar.js';
9
- const App = ({ targetCommitish, baseCommitish, mode, repoPath }) => {
9
+ const App = ({ targetCommitish, baseCommitish, mode, repoPath, contextLines, }) => {
10
10
  const [files, setFiles] = useState([]);
11
11
  const [selectedFileIndex, setSelectedFileIndex] = useState(0);
12
12
  const [loading, setLoading] = useState(true);
@@ -17,7 +17,7 @@ const App = ({ targetCommitish, baseCommitish, mode, repoPath }) => {
17
17
  setLoading(true);
18
18
  setError(null);
19
19
  try {
20
- const fileDiffs = await loadGitDiff(targetCommitish, baseCommitish, repoPath);
20
+ const fileDiffs = await loadGitDiff(targetCommitish, baseCommitish, repoPath, contextLines);
21
21
  setFiles(fileDiffs);
22
22
  setLoading(false);
23
23
  }
@@ -177,6 +177,7 @@ export interface RevisionsResponse {
177
177
  specialOptions: RevisionOption[];
178
178
  branches: BranchInfo[];
179
179
  commits: CommitInfo[];
180
+ originDefaultBranch?: string;
180
181
  resolvedBase?: string;
181
182
  resolvedTarget?: string;
182
183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "difit",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "A lightweight command-line tool that spins up a local web server to display Git commit diffs in a GitHub-like Files changed view",
5
5
  "keywords": [
6
6
  "cli",
@@ -1 +0,0 @@
1
- import{V as a,W as n}from"./mermaid.core-DV5JJ1Ie.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-Cn7a6CO3.js";import{_ as i}from"./mermaid.core-DV5JJ1Ie.js";import"./chunk-FMBD7UC4-CrKv7ndg.js";import"./chunk-JSJVCQXG-DyBDhAEM.js";import"./chunk-55IACEB6-CrZL3qv9.js";import"./chunk-KX2RTZJC-By5mkZmU.js";import"./index-BLNN1bfE.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-WL4C6EOR-Cn7a6CO3.js";import{_ as i}from"./mermaid.core-DV5JJ1Ie.js";import"./chunk-FMBD7UC4-CrKv7ndg.js";import"./chunk-JSJVCQXG-DyBDhAEM.js";import"./chunk-55IACEB6-CrZL3qv9.js";import"./chunk-KX2RTZJC-By5mkZmU.js";import"./index-BLNN1bfE.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./_baseUniq-tbL7nVvN.js";var e=4;function a(o){return r(o,e)}export{a as c};
@@ -1 +0,0 @@
1
- import{s as e,b as r,a,S as s}from"./chunk-NQ4KR5QH-C30p9xRx.js";import{_ as i}from"./mermaid.core-DV5JJ1Ie.js";import"./chunk-55IACEB6-CrZL3qv9.js";import"./chunk-KX2RTZJC-By5mkZmU.js";import"./index-BLNN1bfE.js";var p={parser:a,get db(){return new s(2)},renderer:r,styles:e,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{p as diagram};