File size: 4,587 Bytes
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61b1da1
a5d1c1b
 
61b1da1
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import pc from 'picocolors';
import { createTwoFilesPatch } from 'diff';
import { projectConfigManager } from '../config/project-config.js';
import { cacheService, normalizePosixPath } from '../services/cache-service.js';
import { CliApiClient } from '../client/api-client.js';
import { parseRepoUrl } from '../services/sync-service.js';
import { outputResult, outputError, OutputOptions } from '../utils/output.js';

export interface DiffEntry {
  file: string;
  type: 'modified' | 'added' | 'deleted';
  patch: string;
}

export async function handleDiff(
  targetPath?: string,
  options: OutputOptions = {}
): Promise<void> {
  const workspaceRoot = projectConfigManager.findEvbRoot();
  if (!workspaceRoot) {
    return outputError(
      'Not inside an Ever-Brain project. Run `evb init <repo>` first.',
      'NOT_IN_WORKSPACE',
      options
    );
  }

  try {
    const config = projectConfigManager.readConfig(workspaceRoot);
    const state = projectConfigManager.readState(workspaceRoot);
    const dirty = cacheService.computeDirtyState(workspaceRoot);

    const { owner, repo } = parseRepoUrl(config.source.repository);
    const targetBranch = config.source.branch;
    const apiUrl = projectConfigManager.getApiUrl(workspaceRoot);
    const client = new CliApiClient(apiUrl);

    let filesToDiff: Array<{ file: string; type: 'modified' | 'added' | 'deleted' }> = [];

    if (targetPath) {
      const cleanTarget = normalizePosixPath(targetPath);
      if (dirty.modified.includes(cleanTarget)) {
        filesToDiff.push({ file: cleanTarget, type: 'modified' });
      } else if (dirty.added.includes(cleanTarget)) {
        filesToDiff.push({ file: cleanTarget, type: 'added' });
      } else if (dirty.deleted.includes(cleanTarget)) {
        filesToDiff.push({ file: cleanTarget, type: 'deleted' });
      } else {
        // Unmodified
        outputResult({ diffs: [], message: `File '${cleanTarget}' is clean.` }, options, () => {
          console.log(`File '${cleanTarget}' has no uncommitted modifications.`);
        });
        return;
      }
    } else {
      for (const m of dirty.modified) filesToDiff.push({ file: m, type: 'modified' });
      for (const a of dirty.added) filesToDiff.push({ file: a, type: 'added' });
      for (const d of dirty.deleted) filesToDiff.push({ file: d, type: 'deleted' });
    }

    if (filesToDiff.length === 0) {
      outputResult({ diffs: [], isDirty: false }, options, () => {
        console.log(pc.green('No local modifications (working tree clean).'));
      });
      return;
    }

    const diffEntries: DiffEntry[] = [];

    for (const item of filesToDiff) {
      let oldContent = '';
      let newContent = '';

      if (item.type === 'modified') {
        try {
          const remote = await client.getFileContent(owner, repo, item.file, targetBranch);
          oldContent = remote.content;
        } catch {
          oldContent = '';
        }
        newContent = cacheService.readCachedFile(workspaceRoot, item.file) || '';
      } else if (item.type === 'added') {
        oldContent = '';
        newContent = cacheService.readCachedFile(workspaceRoot, item.file) || '';
      } else if (item.type === 'deleted') {
        try {
          const remote = await client.getFileContent(owner, repo, item.file, targetBranch);
          oldContent = remote.content;
        } catch {
          oldContent = '';
        }
        newContent = '';
      }

      const patch = createTwoFilesPatch(
        `a/${item.file}`,
        `b/${item.file}`,
        oldContent,
        newContent,
        `remote:${targetBranch}`,
        'local:cache'
      );

      diffEntries.push({
        file: item.file,
        type: item.type,
        patch,
      });
    }

    outputResult({ diffs: diffEntries, isDirty: true }, options, () => {
      for (const entry of diffEntries) {
        console.log(pc.bold(pc.cyan(`\n--- diff: ${entry.file} (${entry.type}) ---`)));
        const lines = entry.patch.split('\n');
        for (const line of lines) {
          if (line.startsWith('+') && !line.startsWith('+++')) {
            console.log(pc.green(line));
          } else if (line.startsWith('-') && !line.startsWith('---')) {
            console.log(pc.red(line));
          } else if (line.startsWith('@@')) {
            console.log(pc.cyan(line));
          } else {
            console.log(pc.dim(line));
          }
        }
      }
    });
  } catch (err: unknown) {
    const message = err instanceof Error ? err.message : String(err);
    outputError(message, 'DIFF_ERROR', options);
  }
}