@tanagram/cli 0.4.10 → 0.4.11
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.
- package/git/diff.go +34 -0
- package/package.json +1 -1
- package/snapshot/snapshot.go +102 -11
package/git/diff.go
CHANGED
|
@@ -66,6 +66,40 @@ func GetAllChanges() (*DiffResult, error) {
|
|
|
66
66
|
return result, nil
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// DiffFiles compares two files using git diff --no-index
|
|
70
|
+
func DiffFiles(oldFile, newFile, displayPath string) (*DiffResult, error) {
|
|
71
|
+
cmd := exec.Command("git", "diff", "--no-index", "--unified=0", oldFile, newFile)
|
|
72
|
+
output, err := cmd.Output()
|
|
73
|
+
|
|
74
|
+
// git diff --no-index returns exit code 1 if there are differences
|
|
75
|
+
// It returns 0 if identical
|
|
76
|
+
if err != nil {
|
|
77
|
+
if exitError, ok := err.(*exec.ExitError); ok {
|
|
78
|
+
if exitError.ExitCode() != 1 {
|
|
79
|
+
// Anything other than 0 or 1 is an actual error
|
|
80
|
+
return nil, fmt.Errorf("failed to run git diff: %w", err)
|
|
81
|
+
}
|
|
82
|
+
// Exit code 1 means differences found, proceed to parse
|
|
83
|
+
} else {
|
|
84
|
+
return nil, fmt.Errorf("failed to run git diff: %w", err)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse the diff output
|
|
89
|
+
result, err := parseDiff(string(output))
|
|
90
|
+
if err != nil {
|
|
91
|
+
return nil, err
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Fix up the file paths in the result to match the displayPath
|
|
95
|
+
// git diff --no-index outputs temp file paths like "b/tmp/..."
|
|
96
|
+
for i := range result.Changes {
|
|
97
|
+
result.Changes[i].File = displayPath
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result, nil
|
|
101
|
+
}
|
|
102
|
+
|
|
69
103
|
// parseDiff parses unified diff format and extracts changed lines
|
|
70
104
|
func parseDiff(diffText string) (*DiffResult, error) {
|
|
71
105
|
result := &DiffResult{
|
package/package.json
CHANGED
package/snapshot/snapshot.go
CHANGED
|
@@ -12,6 +12,7 @@ import (
|
|
|
12
12
|
"strings"
|
|
13
13
|
"time"
|
|
14
14
|
|
|
15
|
+
"github.com/tanagram/cli/git"
|
|
15
16
|
"github.com/tanagram/cli/storage"
|
|
16
17
|
)
|
|
17
18
|
|
|
@@ -21,6 +22,7 @@ type FileState struct {
|
|
|
21
22
|
Hash string `json:"hash"`
|
|
22
23
|
Size int64 `json:"size"`
|
|
23
24
|
ModifiedTime time.Time `json:"modified_time"`
|
|
25
|
+
Content string `json:"content,omitempty"`
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// Snapshot represents a snapshot of the working directory
|
|
@@ -179,11 +181,18 @@ func CreateOptimized(gitRoot string, targetFiles []string) error {
|
|
|
179
181
|
continue
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
// Read content
|
|
185
|
+
contentBytes, err := os.ReadFile(fullPath)
|
|
186
|
+
if err != nil {
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
|
|
182
190
|
snapshot.Files[relPath] = &FileState{
|
|
183
191
|
Path: relPath,
|
|
184
192
|
Hash: hash,
|
|
185
193
|
Size: info.Size(),
|
|
186
194
|
ModifiedTime: info.ModTime(),
|
|
195
|
+
Content: string(contentBytes),
|
|
187
196
|
}
|
|
188
197
|
}
|
|
189
198
|
|
|
@@ -490,31 +499,113 @@ type ChangedLine struct {
|
|
|
490
499
|
func GetChangedLinesForChecker(gitRoot string, snapshot *Snapshot, compareResult *CompareResult) ([]ChangedLine, error) {
|
|
491
500
|
var changes []ChangedLine
|
|
492
501
|
|
|
493
|
-
// For
|
|
494
|
-
// (we can't easily determine which specific lines changed without doing a full diff)
|
|
502
|
+
// For modified files, we diff against the snapshot version
|
|
495
503
|
for _, relPath := range compareResult.ModifiedFiles {
|
|
496
504
|
fullPath := filepath.Join(gitRoot, relPath)
|
|
497
505
|
|
|
498
|
-
//
|
|
506
|
+
// Get New Content (Current)
|
|
499
507
|
newData, err := os.ReadFile(fullPath)
|
|
500
508
|
if err != nil {
|
|
501
509
|
continue
|
|
502
510
|
}
|
|
503
|
-
|
|
504
|
-
// Skip binary files
|
|
505
511
|
if !isText(newData) {
|
|
506
512
|
continue
|
|
507
513
|
}
|
|
508
514
|
|
|
509
|
-
|
|
515
|
+
// Get Old Content (Snapshot)
|
|
516
|
+
var oldData []byte
|
|
517
|
+
|
|
518
|
+
if fileState, ok := snapshot.Files[relPath]; ok && fileState.Content != "" {
|
|
519
|
+
// Was dirty -> use stored content
|
|
520
|
+
oldData = []byte(fileState.Content)
|
|
521
|
+
} else {
|
|
522
|
+
// Was clean -> use git show
|
|
523
|
+
// If it wasn't in snapshot.Files but is in ModifiedFiles, it must have been clean at HEAD
|
|
524
|
+
// and now is modified.
|
|
525
|
+
// Note: If snapshot.Files has it but Content is empty, that means it was clean but we stored it?
|
|
526
|
+
// No, CreateOptimized only stores Content for dirty files.
|
|
527
|
+
// But wait, CreateOptimized only adds files to snapshot.Files if they are dirty or targetted.
|
|
528
|
+
// If a file is NOT in snapshot.Files, it is assumed clean at HeadCommit.
|
|
529
|
+
|
|
530
|
+
// However, compareResult.ModifiedFiles includes files that:
|
|
531
|
+
// 1. Were in snapshot (hash changed)
|
|
532
|
+
// 2. Were NOT in snapshot (current hash != HEAD hash)
|
|
533
|
+
|
|
534
|
+
// Case 1: In snapshot
|
|
535
|
+
if fileState, ok := snapshot.Files[relPath]; ok {
|
|
536
|
+
if fileState.Content != "" {
|
|
537
|
+
oldData = []byte(fileState.Content)
|
|
538
|
+
} else {
|
|
539
|
+
// It was in snapshot but no content? This happens if it was > 1MB
|
|
540
|
+
// or if we used the legacy Create method which doesn't store content.
|
|
541
|
+
// In that case we fall back to reading from HEAD (imperfect but best effort)
|
|
542
|
+
// OR we just treat all lines as changed (current behavior) if we can't get old content.
|
|
543
|
+
|
|
544
|
+
// Try to get from HEAD just in case it matches
|
|
545
|
+
hash, _ := getHashAtCommit(gitRoot, snapshot.HeadCommit, relPath)
|
|
546
|
+
if hash == fileState.Hash {
|
|
547
|
+
// It matches HEAD, so we can get content from HEAD
|
|
548
|
+
out, err := runGit(gitRoot, "show", fmt.Sprintf("%s:%s", snapshot.HeadCommit, relPath))
|
|
549
|
+
if err == nil {
|
|
550
|
+
oldData = []byte(out)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
// Case 2: Not in snapshot (was clean at HEAD)
|
|
556
|
+
out, err := runGit(gitRoot, "show", fmt.Sprintf("%s:%s", snapshot.HeadCommit, relPath))
|
|
557
|
+
if err == nil {
|
|
558
|
+
oldData = []byte(out)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// If we couldn't get old data, fallback to treating all lines as new (safest)
|
|
564
|
+
if len(oldData) == 0 {
|
|
565
|
+
newLines := strings.Split(string(newData), "\n")
|
|
566
|
+
for i, line := range newLines {
|
|
567
|
+
if strings.TrimSpace(line) != "" {
|
|
568
|
+
changes = append(changes, ChangedLine{
|
|
569
|
+
File: relPath,
|
|
570
|
+
LineNumber: i + 1,
|
|
571
|
+
Content: line,
|
|
572
|
+
ChangeType: "+",
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
continue
|
|
577
|
+
}
|
|
510
578
|
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
|
|
579
|
+
// Do the diff
|
|
580
|
+
// Write to temp files
|
|
581
|
+
tmpOld, err := os.CreateTemp("", "tanagram-old-*")
|
|
582
|
+
if err != nil {
|
|
583
|
+
continue
|
|
584
|
+
}
|
|
585
|
+
defer os.Remove(tmpOld.Name())
|
|
586
|
+
tmpOld.Write(oldData)
|
|
587
|
+
tmpOld.Close()
|
|
588
|
+
|
|
589
|
+
tmpNew, err := os.CreateTemp("", "tanagram-new-*")
|
|
590
|
+
if err != nil {
|
|
591
|
+
continue
|
|
592
|
+
}
|
|
593
|
+
defer os.Remove(tmpNew.Name())
|
|
594
|
+
tmpNew.Write(newData)
|
|
595
|
+
tmpNew.Close()
|
|
596
|
+
|
|
597
|
+
diffResult, err := git.DiffFiles(tmpOld.Name(), tmpNew.Name(), relPath)
|
|
598
|
+
if err != nil {
|
|
599
|
+
continue
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Convert git.ChangedLine to snapshot.ChangedLine
|
|
603
|
+
for _, c := range diffResult.Changes {
|
|
604
|
+
if c.ChangeType == "+" {
|
|
514
605
|
changes = append(changes, ChangedLine{
|
|
515
606
|
File: relPath,
|
|
516
|
-
LineNumber:
|
|
517
|
-
Content:
|
|
607
|
+
LineNumber: c.LineNumber,
|
|
608
|
+
Content: c.Content,
|
|
518
609
|
ChangeType: "+",
|
|
519
610
|
})
|
|
520
611
|
}
|