package gitcmd import ( "context" "strings" "testing" ) func TestGetCommit(t *testing.T) { f := newFixture(t) c, err := f.repo.GetCommit(f.c1) if err != nil { t.Fatal(err) } if c.ID != f.c1 { t.Errorf("ID = %q, want %q", c.ID, f.c1) } if !strings.HasPrefix(f.c1, c.ShortSHA) || len(c.ShortSHA) < 7 { t.Errorf("ShortSHA = %q (full %q)", c.ShortSHA, f.c1) } if c.Message != "add readme" { t.Errorf("Message = %q, want 'add readme'", c.Message) } if c.Author != aliceName || c.AuthorEmail != aliceEmail { t.Errorf("Author = %q <%q>, want Alice", c.Author, c.AuthorEmail) } if !c.When.Equal(mustParseTime(t, d1)) { t.Errorf("When = %v, want %v", c.When, mustParseTime(t, d1)) } if len(c.Parents) != 0 { t.Errorf("root commit Parents = %v, want empty", c.Parents) } h, err := f.repo.GetCommit("HEAD") if err != nil { t.Fatal(err) } if h.ID != f.c5 { t.Errorf("HEAD ID = %q, want %q", h.ID, f.c5) } if h.Message != "remove temp" { t.Errorf("HEAD Message = %q, want 'remove temp'", h.Message) } if len(h.Parents) != 1 || h.Parents[0] != f.c4 { t.Errorf("HEAD Parents = %v, want [%s]", h.Parents, f.c4) } } func TestGetCommitMissingRef(t *testing.T) { f := newFixture(t) _, err := f.repo.GetCommit("no-such-ref") if err == nil { t.Fatal("expected error for missing ref") } } func TestListCommits(t *testing.T) { f := newFixture(t) cs, err := f.repo.ListCommits("HEAD", 2) if err != nil { t.Fatal(err) } if len(cs) != 2 { t.Fatalf("limit=2: len = %d, want 2", len(cs)) } if cs[0].ID != f.c5 || cs[1].ID != f.c4 { t.Errorf("limit=2 order: %s, %s; want %s, %s", cs[0].ID, cs[1].ID, f.c5, f.c4) } all, err := f.repo.ListCommits("HEAD", 0) if err != nil { t.Fatal(err) } if len(all) != 5 { t.Errorf("limit=0: len = %d, want 5", len(all)) } if all[0].ID != f.c5 || all[4].ID != f.c1 { t.Errorf("full order: first=%s last=%s; want %s %s", all[0].ID, all[4].ID, f.c5, f.c1) } neg, err := f.repo.ListCommits("HEAD", -1) if err != nil { t.Fatal(err) } if len(neg) != 5 { t.Errorf("limit=-1: len = %d, want 5", len(neg)) } } func TestListCommitsEmptyRepo(t *testing.T) { dir := t.TempDir() bare := dir + "/empty.git" gitRun(t, "", nil, "init", "-q", "--bare", bare) r, err := OpenRepository(context.Background(), bare) if err != nil { t.Fatal(err) } cs, err := r.ListCommits("HEAD", 20) if err != nil { t.Fatalf("ListCommits on empty repo: %v", err) } if len(cs) != 0 { t.Errorf("empty repo: len = %d, want 0", len(cs)) } } func TestGetCommitDetail(t *testing.T) { f := newFixture(t) d1, err := f.repo.GetCommitDetail(f.c1) if err != nil { t.Fatal(err) } if len(d1.Files) != 1 || d1.Files[0].Path != "README.md" { t.Fatalf("c1 files = %+v", d1.Files) } if d1.Files[0].Status != "A" || d1.Files[0].Additions != 2 || d1.Files[0].Deletions != 0 { t.Errorf("c1 file = %+v, want Status A / +2 / -0", d1.Files[0]) } if d1.Stats != (DiffStats{Additions: 2, Deletions: 0, Total: 2, Files: 1}) { t.Errorf("c1 stats = %+v", d1.Stats) } d3, err := f.repo.GetCommitDetail(f.c3) if err != nil { t.Fatal(err) } if len(d3.Files) != 1 || d3.Files[0].Status != "M" { t.Fatalf("c3 files = %+v, want Status M", d3.Files) } if d3.Stats != (DiffStats{Additions: 1, Deletions: 1, Total: 2, Files: 1}) { t.Errorf("c3 stats = %+v", d3.Stats) } d5, err := f.repo.GetCommitDetail(f.c5) if err != nil { t.Fatal(err) } if len(d5.Files) != 1 || d5.Files[0].Path != "deleted.txt" || d5.Files[0].Status != "D" { t.Fatalf("c5 files = %+v, want deleted.txt Status D", d5.Files) } if d5.Stats != (DiffStats{Additions: 0, Deletions: 1, Total: 1, Files: 1}) { t.Errorf("c5 stats = %+v", d5.Stats) } } func TestParseCommitLine(t *testing.T) { line := "FULLSHAabcdef1234567890abcdef1234567890ab\x1fSHORT12\x1fthe subject\x1fAlice\x1fa@x\x1f2024-01-01T10:00:00+00:00\x1fparentAAA" c := parseCommitLine(line) if c.ID != "FULLSHAabcdef1234567890abcdef1234567890ab" { t.Errorf("ID = %q", c.ID) } if c.ShortSHA != "SHORT12" { t.Errorf("ShortSHA = %q", c.ShortSHA) } if c.Message != "the subject" { t.Errorf("Message = %q", c.Message) } if c.Author != "Alice" || c.AuthorEmail != "a@x" { t.Errorf("Author = %q <%q>", c.Author, c.AuthorEmail) } if !c.When.Equal(mustParseTime(t, "2024-01-01T10:00:00+00:00")) { t.Errorf("When = %v", c.When) } if len(c.Parents) != 1 || c.Parents[0] != "parentAAA" { t.Errorf("Parents = %v, want [parentAAA]", c.Parents) } line2 := "id\x1fsh\x1fmsg\x1fan\x1fae\x1f2024-02-01T00:00:00+00:00\x1fp1 p2" c2 := parseCommitLine(line2) if len(c2.Parents) != 2 || c2.Parents[0] != "p1" || c2.Parents[1] != "p2" { t.Errorf("multi Parents = %v, want [p1 p2]", c2.Parents) } line3 := "id\x1fsh\x1fmsg\x1fan\x1fae\x1f2024-03-01T00:00:00+00:00\x1f" c3 := parseCommitLine(line3) if len(c3.Parents) != 0 { t.Errorf("root Parents = %v, want empty", c3.Parents) } line4 := "a\x1fb\x1fc" c4 := parseCommitLine(line4) if c4.Message != line4 { t.Errorf("malformed Message = %q, want %q", c4.Message, line4) } if c4.ID != "" { t.Errorf("malformed ID = %q, want empty", c4.ID) } } func TestMergeCommit(t *testing.T) { mf := newMergeFixture(t) mc, err := mf.repo.GetCommit(mf.c4) if err != nil { t.Fatal(err) } if len(mc.Parents) != 2 { t.Fatalf("merge Parents = %v, want 2", mc.Parents) } got := map[string]bool{mc.Parents[0]: true, mc.Parents[1]: true} if !got[mf.c3] || !got[mf.c2] { t.Errorf("merge Parents = %v, want {%s, %s}", mc.Parents, mf.c3, mf.c2) } md, err := mf.repo.GetCommitDetail(mf.c4) if err != nil { t.Fatal(err) } if len(md.Files) != 1 { t.Fatalf("merge detail Files = %+v, want 1 file (b.txt)", md.Files) } if md.Files[0].Path != "b.txt" || md.Files[0].Status != "A" || md.Files[0].Additions != 1 { t.Errorf("merge detail file = %+v, want b.txt / Status A / +1", md.Files[0]) } }