mirror of
https://github.com/vee1e/gittuf.git
synced 2026-09-01 10:18:18 +00:00
267 lines
7.1 KiB
Go
267 lines
7.1 KiB
Go
// Copyright The gittuf Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package gitinterface
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestStatus(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
repo := CreateTestGitRepository(t, tmpDir, false)
|
|
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Chdir(cwd) //nolint:errcheck
|
|
|
|
// NOTE: we don't use traditional methods like WriteBlob or TreeBuilder so
|
|
// we can more closely simulate user actions, such as with updating index,
|
|
// etc.
|
|
|
|
filename := "foo"
|
|
filename2 := "bar"
|
|
|
|
if err := os.WriteFile(filename, []byte("foo"), 0o644); err != nil { //nolint:gosec
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err := repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{filename: {X: StatusCodeUntracked, Y: StatusCodeUntracked}}, statuses)
|
|
|
|
// Add item to index
|
|
if _, err := repo.executor("add", filename).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{filename: {X: StatusCodeAdded, Y: StatusCodeUnmodified}}, statuses)
|
|
|
|
// Modify file that has been staged
|
|
if err := os.WriteFile(filename, []byte("bar"), 0o644); err != nil { //nolint:gosec
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{filename: {X: StatusCodeAdded, Y: StatusCodeModified}}, statuses)
|
|
|
|
// Add and commit
|
|
if _, err := repo.executor("add", filename).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.executor("commit", "-m", "Commit\n").executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Empty(t, statuses)
|
|
|
|
// Modify file again
|
|
if err := os.WriteFile(filename, []byte("foo"), 0o644); err != nil { //nolint:gosec
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{filename: {X: StatusCodeModified, Y: StatusCodeUnmodified}}, statuses)
|
|
|
|
// Remove file
|
|
if err := os.Remove(filename); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{filename: {X: StatusCodeDeleted, Y: StatusCodeUnmodified}}, statuses)
|
|
|
|
// Commit
|
|
if _, err := repo.executor("add", filename).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.executor("commit", "-m", "Commit\n", "--allow-empty").executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Empty(t, statuses)
|
|
|
|
// Add two files, commit, then make one a symlink to the other
|
|
if err := os.WriteFile(filename, []byte("foo"), 0o644); err != nil { //nolint:gosec
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filename2, []byte("foo"), 0o644); err != nil { //nolint:gosec
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.executor("add", filename, filename2).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.executor("commit", "-m", "Commit\n").executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Empty(t, statuses)
|
|
|
|
if err := os.Remove(filename2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Symlink(filename, filename2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{filename2: {X: StatusCodeTypeChanged, Y: StatusCodeUnmodified}}, statuses)
|
|
|
|
// Add and commit
|
|
if _, err := repo.executor("add", filename2).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.executor("commit", "-m", "Commit\n").executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Empty(t, statuses)
|
|
|
|
// Rename one of the existing files
|
|
renamedPath := "baz"
|
|
if _, err := repo.executor("mv", filename, renamedPath).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{
|
|
renamedPath: {X: StatusCodeRenamed, Y: StatusCodeUnmodified},
|
|
}, statuses)
|
|
|
|
// Commit the rename
|
|
if _, err := repo.executor("commit", "-m", "Commit\n").executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Empty(t, statuses)
|
|
|
|
// Test that rename is parsed correctly alongside other changes in the same
|
|
// status output. This validates that the NUL-separated source-path token
|
|
// for the rename is consumed so subsequent entries are not mis-parsed.
|
|
renamedPath2 := "quux"
|
|
newFile := "qux"
|
|
|
|
if _, err := repo.executor("mv", renamedPath, renamedPath2).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(newFile, []byte("qux"), 0o644); err != nil { //nolint:gosec
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.executor("add", newFile).executeString(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
statuses, err = repo.Status()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, map[string]FileStatus{
|
|
renamedPath2: {X: StatusCodeRenamed, Y: StatusCodeUnmodified},
|
|
newFile: {X: StatusCodeAdded, Y: StatusCodeUnmodified},
|
|
}, statuses)
|
|
}
|
|
|
|
func TestStatusError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
repo := CreateTestGitRepository(t, tmpDir, true) // bare repo
|
|
|
|
_, err := repo.Status()
|
|
assert.ErrorContains(t, err, "unable to check status of repository")
|
|
}
|
|
func TestNewStatusCodeFromByte(t *testing.T) {
|
|
t.Run("invalid status code", func(t *testing.T) {
|
|
_, err := NewStatusCodeFromByte('X')
|
|
assert.ErrorIs(t, err, ErrInvalidStatusCode)
|
|
})
|
|
|
|
t.Run("valid status codes", func(t *testing.T) {
|
|
testCases := []struct {
|
|
input byte
|
|
expected StatusCode
|
|
}{
|
|
{' ', StatusCodeUnmodified},
|
|
{'M', StatusCodeModified},
|
|
{'T', StatusCodeTypeChanged},
|
|
{'A', StatusCodeAdded},
|
|
{'D', StatusCodeDeleted},
|
|
{'C', StatusCodeCopied},
|
|
{'U', StatusCodeUpdatedUnmerged},
|
|
{'?', StatusCodeUntracked},
|
|
{'!', StatusCodeIgnored},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
code, err := NewStatusCodeFromByte(tc.input)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tc.expected, code)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestStatusCodeString(t *testing.T) {
|
|
t.Run("invalid status code", func(t *testing.T) {
|
|
assert.Equal(t, "invalid-code", StatusCode(999).String())
|
|
})
|
|
|
|
t.Run("valid status codes", func(t *testing.T) {
|
|
testCases := []struct {
|
|
code StatusCode
|
|
expected string
|
|
}{
|
|
{StatusCodeUnmodified, " "},
|
|
{StatusCodeModified, "M"},
|
|
{StatusCodeTypeChanged, "T"},
|
|
{StatusCodeAdded, "A"},
|
|
{StatusCodeDeleted, "D"},
|
|
{StatusCodeRenamed, "R"},
|
|
{StatusCodeCopied, "C"},
|
|
{StatusCodeUpdatedUnmerged, "U"},
|
|
{StatusCodeUntracked, "?"},
|
|
{StatusCodeIgnored, "!"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
assert.Equal(t, tc.expected, tc.code.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFileStatusUntracked(t *testing.T) {
|
|
t.Run("untracked when X is untracked", func(t *testing.T) {
|
|
fs := FileStatus{X: StatusCodeUntracked, Y: StatusCodeUnmodified}
|
|
assert.True(t, fs.Untracked())
|
|
})
|
|
|
|
t.Run("untracked when Y is untracked", func(t *testing.T) {
|
|
fs := FileStatus{X: StatusCodeUnmodified, Y: StatusCodeUntracked}
|
|
assert.True(t, fs.Untracked())
|
|
})
|
|
|
|
t.Run("not untracked when neither is untracked", func(t *testing.T) {
|
|
fs := FileStatus{X: StatusCodeModified, Y: StatusCodeUnmodified}
|
|
assert.False(t, fs.Untracked())
|
|
})
|
|
}
|