| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- package git
- import (
- "os"
- "path/filepath"
- "strings"
- "testing"
- )
- func TestSplitLines(t *testing.T) {
- tests := []struct {
- name string
- input string
- want []string
- }{
- {name: "empty string", input: "", want: []string{}},
- {name: "single line no newline", input: "abc", want: []string{"abc"}},
- {name: "single line trailing newline", input: "abc\n", want: []string{"abc"}},
- {name: "two lines", input: "a\nb\n", want: []string{"a", "b"}},
- {name: "blank line preserved", input: "a\n\nb\n", want: []string{"a", "", "b"}},
- {name: "crlf normalised", input: "a\r\nb\r\n", want: []string{"a", "b"}},
- {name: "only newline", input: "\n", want: []string{""}},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- got := splitLines(test.input)
- if len(got) != len(test.want) {
- t.Fatalf("splitLines(%q) = %q, want %q", test.input, got, test.want)
- }
- for i := range got {
- if got[i] != test.want[i] {
- t.Errorf("splitLines(%q)[%d] = %q, want %q", test.input, i, got[i], test.want[i])
- }
- }
- })
- }
- }
- func TestDiffLines(t *testing.T) {
- tests := []struct {
- name string
- oldText []string
- newText []string
- want []string //"kind:text" pairs, in order
- }{
- {
- name: "identical",
- oldText: []string{"a", "b"},
- newText: []string{"a", "b"},
- want: []string{"context:a", "context:b"},
- },
- {
- name: "append a line",
- oldText: []string{"a"},
- newText: []string{"a", "b"},
- want: []string{"context:a", "add:b"},
- },
- {
- name: "delete a line",
- oldText: []string{"a", "b"},
- newText: []string{"a"},
- want: []string{"context:a", "del:b"},
- },
- {
- name: "replace middle line",
- oldText: []string{"a", "b", "c"},
- newText: []string{"a", "x", "c"},
- want: []string{"context:a", "del:b", "add:x", "context:c"},
- },
- {
- name: "from empty",
- oldText: []string{},
- newText: []string{"a", "b"},
- want: []string{"add:a", "add:b"},
- },
- {
- name: "to empty",
- oldText: []string{"a", "b"},
- newText: []string{},
- want: []string{"del:a", "del:b"},
- },
- {
- name: "both empty",
- oldText: []string{},
- newText: []string{},
- want: []string{},
- },
- {
- name: "insert at start",
- oldText: []string{"b"},
- newText: []string{"a", "b"},
- want: []string{"add:a", "context:b"},
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- operations := diffLines(test.oldText, test.newText)
- got := []string{}
- for _, operation := range operations {
- got = append(got, operation.kind+":"+operation.text)
- }
- if strings.Join(got, "|") != strings.Join(test.want, "|") {
- t.Errorf("diffLines() = %v, want %v", got, test.want)
- }
- })
- }
- }
- func TestDiffLinesLargeRegionFallsBackToReplace(t *testing.T) {
- oldText := make([]string, maxLCSRegion+10)
- newText := make([]string, maxLCSRegion+10)
- for i := range oldText {
- oldText[i] = "old line"
- newText[i] = "new line"
- }
- operations := diffLines(oldText, newText)
- deletions, additions := 0, 0
- for _, operation := range operations {
- switch operation.kind {
- case "del":
- deletions++
- case "add":
- additions++
- }
- }
- if deletions != len(oldText) || additions != len(newText) {
- t.Errorf("large region diff = %d deletions / %d additions, want %d / %d",
- deletions, additions, len(oldText), len(newText))
- }
- }
- func TestBuildHunksLineNumbering(t *testing.T) {
- operations := []diffOp{
- {kind: "context", text: "a"},
- {kind: "del", text: "b"},
- {kind: "add", text: "B"},
- {kind: "context", text: "c"},
- }
- hunks := buildHunks(operations)
- if len(hunks) != 1 {
- t.Fatalf("buildHunks() = %d hunks, want 1", len(hunks))
- }
- hunk := hunks[0]
- if hunk.OldStart != 1 || hunk.NewStart != 1 {
- t.Errorf("hunk start = -%d +%d, want -1 +1", hunk.OldStart, hunk.NewStart)
- }
- if hunk.OldLines != 3 || hunk.NewLines != 3 {
- t.Errorf("hunk length = -%d +%d, want -3 +3", hunk.OldLines, hunk.NewLines)
- }
- if !strings.HasPrefix(hunk.Header, "@@ -1,3 +1,3 @@") {
- t.Errorf("hunk Header = %q, want it to start with %q", hunk.Header, "@@ -1,3 +1,3 @@")
- }
- wantLines := []DiffLine{
- {Type: "context", OldLine: 1, NewLine: 1, Content: "a"},
- {Type: "del", OldLine: 2, NewLine: 0, Content: "b"},
- {Type: "add", OldLine: 0, NewLine: 2, Content: "B"},
- {Type: "context", OldLine: 3, NewLine: 3, Content: "c"},
- }
- if len(hunk.Lines) != len(wantLines) {
- t.Fatalf("hunk has %d lines, want %d", len(hunk.Lines), len(wantLines))
- }
- for i, want := range wantLines {
- got := hunk.Lines[i]
- if got != want {
- t.Errorf("line %d = %+v, want %+v", i, got, want)
- }
- }
- }
- func TestBuildHunksNoChangesProducesNoHunks(t *testing.T) {
- operations := []diffOp{
- {kind: "context", text: "a"},
- {kind: "context", text: "b"},
- }
- if hunks := buildHunks(operations); len(hunks) != 0 {
- t.Errorf("buildHunks() with no changes = %d hunks, want 0", len(hunks))
- }
- }
- func TestBuildHunksSplitsDistantChanges(t *testing.T) {
- operations := []diffOp{{kind: "add", text: "start"}}
- for i := 0; i < 20; i++ {
- operations = append(operations, diffOp{kind: "context", text: "filler"})
- }
- operations = append(operations, diffOp{kind: "add", text: "end"})
- hunks := buildHunks(operations)
- if len(hunks) != 2 {
- t.Errorf("buildHunks() with two distant changes = %d hunks, want 2", len(hunks))
- }
- }
- func TestIsBinaryContent(t *testing.T) {
- //A NUL past the 8000 byte probe window is deliberately not detected, which
- //is the same trade-off git itself makes.
- lateNul := append([]byte(strings.Repeat("a", 8000)), 0x00)
- tests := []struct {
- name string
- content []byte
- want bool
- }{
- {name: "text", content: []byte("hello"), want: false},
- {name: "empty", content: []byte{}, want: false},
- {name: "nul at start", content: []byte{0x00, 'a'}, want: true},
- {name: "nul in the middle", content: []byte("abc\x00def"), want: true},
- {name: "nul beyond the probe window", content: lateNul, want: false},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- if got := isBinaryContent(test.content); got != test.want {
- t.Errorf("isBinaryContent(%s) = %v, want %v", test.name, got, test.want)
- }
- })
- }
- }
- func TestBuildFileDiffFlags(t *testing.T) {
- tests := []struct {
- name string
- oldContent []byte
- newContent []byte
- oldExists bool
- newExists bool
- wantNew bool
- wantDeleted bool
- wantBinary bool
- wantAdditions int
- wantDeletions int
- }{
- {
- name: "new file",
- oldContent: []byte{},
- newContent: []byte("a\nb\n"),
- oldExists: false,
- newExists: true,
- wantNew: true,
- wantAdditions: 2,
- },
- {
- name: "deleted file",
- oldContent: []byte("a\n"),
- newContent: []byte{},
- oldExists: true,
- newExists: false,
- wantDeleted: true,
- wantDeletions: 1,
- },
- {
- name: "binary file",
- oldContent: []byte{0x00, 0x01},
- newContent: []byte{0x00, 0x02},
- oldExists: true,
- newExists: true,
- wantBinary: true,
- },
- {
- name: "one line changed",
- oldContent: []byte("a\nb\nc\n"),
- newContent: []byte("a\nB\nc\n"),
- oldExists: true,
- newExists: true,
- wantAdditions: 1,
- wantDeletions: 1,
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- diff := buildFileDiff("file.txt", test.oldContent, test.newContent, test.oldExists, test.newExists)
- if diff.IsNew != test.wantNew {
- t.Errorf("IsNew = %v, want %v", diff.IsNew, test.wantNew)
- }
- if diff.IsDeleted != test.wantDeleted {
- t.Errorf("IsDeleted = %v, want %v", diff.IsDeleted, test.wantDeleted)
- }
- if diff.Binary != test.wantBinary {
- t.Errorf("Binary = %v, want %v", diff.Binary, test.wantBinary)
- }
- if diff.Additions != test.wantAdditions {
- t.Errorf("Additions = %d, want %d", diff.Additions, test.wantAdditions)
- }
- if diff.Deletions != test.wantDeletions {
- t.Errorf("Deletions = %d, want %d", diff.Deletions, test.wantDeletions)
- }
- })
- }
- }
- func TestBuildFileDiffTooLarge(t *testing.T) {
- diff := buildFileDiff("big.bin", nil, []byte("x"), true, true)
- if !diff.TooLarge {
- t.Errorf("TooLarge = false for an oversized side, want true")
- }
- if len(diff.Hunks) != 0 {
- t.Errorf("Hunks = %d, want 0 for an oversized diff", len(diff.Hunks))
- }
- }
- func TestDiffAgainstWorkingTree(t *testing.T) {
- manager := newTestManager(t)
- repoPath := newTestRepo(t, manager)
- commitFile(t, manager, repoPath, "a.txt", "one\ntwo\nthree\n", "first")
- writeFile(t, repoPath, "a.txt", "one\nTWO\nthree\n")
- diff, err := manager.Diff(repoPath, "a.txt")
- if err != nil {
- t.Fatalf("Diff() returned error: %v", err)
- }
- if diff.Additions != 1 || diff.Deletions != 1 {
- t.Errorf("Diff() = +%d -%d, want +1 -1", diff.Additions, diff.Deletions)
- }
- if diff.IsNew || diff.IsDeleted || diff.Binary {
- t.Errorf("Diff() flags = new:%v deleted:%v binary:%v, want all false",
- diff.IsNew, diff.IsDeleted, diff.Binary)
- }
- if len(diff.Hunks) != 1 {
- t.Fatalf("Diff() = %d hunks, want 1", len(diff.Hunks))
- }
- }
- func TestDiffNewUntrackedFile(t *testing.T) {
- manager := newTestManager(t)
- repoPath := newTestRepo(t, manager)
- commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
- writeFile(t, repoPath, "new.txt", "fresh\ncontent\n")
- diff, err := manager.Diff(repoPath, "new.txt")
- if err != nil {
- t.Fatalf("Diff() returned error: %v", err)
- }
- if !diff.IsNew {
- t.Errorf("IsNew = false for an untracked file, want true")
- }
- if diff.Additions != 2 {
- t.Errorf("Additions = %d, want 2", diff.Additions)
- }
- }
- func TestDiffMissingFileFails(t *testing.T) {
- manager := newTestManager(t)
- repoPath := newTestRepo(t, manager)
- commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
- if _, err := manager.Diff(repoPath, "nowhere.txt"); err == nil {
- t.Errorf("Diff() on a nonexistent path = nil error, want an error")
- }
- }
- func TestDiffRejectsEscapingPath(t *testing.T) {
- manager := newTestManager(t)
- repoPath := newTestRepo(t, manager)
- commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
- if _, err := manager.Diff(repoPath, "../../secret.txt"); err == nil {
- t.Errorf("Diff() with an escaping path = nil error, want an error")
- }
- }
- func TestDiffCommitAndCommitFiles(t *testing.T) {
- manager := newTestManager(t)
- repoPath := newTestRepo(t, manager)
- commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
- second := commitFile(t, manager, repoPath, "a.txt", "one\ntwo\n", "second")
- files, err := manager.CommitFiles(repoPath, second)
- if err != nil {
- t.Fatalf("CommitFiles() returned error: %v", err)
- }
- if len(files) != 1 || files[0].Path != "a.txt" || files[0].Status != "modified" {
- t.Fatalf("CommitFiles() = %+v, want a single modified a.txt", files)
- }
- diff, err := manager.DiffCommit(repoPath, second, "a.txt")
- if err != nil {
- t.Fatalf("DiffCommit() returned error: %v", err)
- }
- if diff.Additions != 1 || diff.Deletions != 0 {
- t.Errorf("DiffCommit() = +%d -%d, want +1 -0", diff.Additions, diff.Deletions)
- }
- }
- func TestCommitFilesOnInitialCommit(t *testing.T) {
- manager := newTestManager(t)
- repoPath := newTestRepo(t, manager)
- first := commitFile(t, manager, repoPath, "a.txt", "one\n", "first")
- files, err := manager.CommitFiles(repoPath, first)
- if err != nil {
- t.Fatalf("CommitFiles() returned error: %v", err)
- }
- if len(files) != 1 || files[0].Status != "added" {
- t.Errorf("CommitFiles() on the initial commit = %+v, want a single added file", files)
- }
- }
- func TestWorktreeContentHonoursSizeLimit(t *testing.T) {
- folder := t.TempDir()
- bigPath := filepath.Join(folder, "big.txt")
- big := make([]byte, maxDiffBytes+1)
- for i := range big {
- big[i] = 'a'
- }
- if err := os.WriteFile(bigPath, big, 0664); err != nil {
- t.Fatalf("cannot write oversized file: %v", err)
- }
- content, exists, err := worktreeContent(folder, "big.txt")
- if err != nil {
- t.Fatalf("worktreeContent() returned error: %v", err)
- }
- if !exists {
- t.Errorf("exists = false for an oversized file, want true")
- }
- if content != nil {
- t.Errorf("content = %d bytes for an oversized file, want nil", len(content))
- }
- }
|