1330ffab05
Support for Git macro attributes was added in a series of commits in PR #3391, including commit 9d3e52d6871ff4e793c4825622e3b2dc53a47eb5, where the Line structure of the "git/gitattr" package was updated to include a Macro element which would be set non-nil for macro definition lines in a .gitattributes file file, while the existing Pattern element would be set non-nil for all other lines. The "git lfs track" command, among others, was then adjusted to create a MacroProcessor structure (from the same "git/gitattr" package) and call its ProcessLines() method to resolve any macro references and thus convert the "raw" parsed Line structures into a set for which the Pattern element was always non-nil, and no Macro elements appeared. Later, the "git lfs fsck" command gained the ability to process macro definitions in .gitattributes files, in PR #4525. However, the "git lfs migrate import" command was not adjusted, specifically in the implementation of its "--fixup" option, which initializes a Tree structure (also of the "git/gitattr" package) for the root tree of each commit in a repository's history using the package's New() function. This function traverses all the trees in the hierarchy and finds and parses all the .gitattributes files in them. Then, when the command visits each file within the commit's tree using the Rewrite() method of the Rewriter structure in the "git/githistory" package, it calls the (*Tree).Applied() method to match the file's path against any applicable Git attributes, to see if the file should be treated as a Git LFS object. This lack of support for macro attributes in the "git lfs migrate import --fixup" command was then propagated to the "git lfs migrate info --fixup" command in commit 4800c5e9882f9cf1e271a855baeb32dbe1f767ec of PR #4501, when the "git lfs migrate info" command was updated to respect the --fixup option. As a result, both of these commands (when used with the --fixup option) panic if they encounter a .gitattributes file with any macro definitions, as they call the (*Tree).Applied() method and it attempts to access the nil Pattern element of the lines with non-nil Macro elements. (Prior to the changes in commit c374d1f5df22fb164a8c4608cd8ad9a895ac4ec2 of PR #5375 the "git lfs migrate import --fixup" command would then stall indefinitely, but it now also exits after the panic condition.) These problems were reported in issue #5332. To resolve this problem and avoid similar ones in the future, we refactor the Line structure into a Line interface, which only provides an Attrs() method to retrieve a slice of Attr attributes, and no other methods. We then also define two additional interfaces, each of which embeds the Line interface, PatternLine and MacroLine, with corresponding getter methods for their respective elements. The ParseLine() function of the "git/gitattr" package now returns a slice of generic Line types, each of which is either a PatternLine or a MacroLine, but never both. Callers like the Applied() method of the Tree structure therefore need to perform type assertions or switches to determine which type of Line they are handling, which ensures they always access the line's data through safe methods. We then update the Go tests for the "git/gitattr" package as appropriate, and also add two tests each to the t/t-migrate-fixup.sh and t/t-migrate-import.sh test suites. All four of these new shell tests fail without the changes in this commit. In particular, several of these tests make sure to run the "git lfs migrate" commands outside of any shell pipeline so the test will fail if the command panics and produces no output, even if no output is the expected condition for a successful execution of the command.
189 lines
5.9 KiB
Go
189 lines
5.9 KiB
Go
package gitattr
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestParseLines(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader("*.dat filter=lfs"))
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, lines, 1)
|
|
|
|
assert.Implements(t, (*PatternLine)(nil), lines[0])
|
|
|
|
assert.Equal(t, lines[0].(PatternLine).Pattern().String(), "*.dat")
|
|
assert.Equal(t, lines[0].Attrs()[0], &Attr{
|
|
K: "filter", V: "lfs",
|
|
})
|
|
}
|
|
|
|
func TestParseLinesManyAttrs(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader(
|
|
"*.dat filter=lfs diff=lfs merge=lfs -text crlf"))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, lines, 1)
|
|
|
|
assert.Implements(t, (*PatternLine)(nil), lines[0])
|
|
|
|
assert.Equal(t, lines[0].(PatternLine).Pattern().String(), "*.dat")
|
|
|
|
assert.Len(t, lines[0].Attrs(), 5)
|
|
assert.Equal(t, lines[0].Attrs()[0], &Attr{K: "filter", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[1], &Attr{K: "diff", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[2], &Attr{K: "merge", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[3], &Attr{K: "text", V: "false"})
|
|
assert.Equal(t, lines[0].Attrs()[4], &Attr{K: "crlf", V: "true"})
|
|
}
|
|
|
|
func TestParseLinesManyLines(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader(strings.Join([]string{
|
|
"*.dat filter=lfs diff=lfs merge=lfs -text",
|
|
"*.jpg filter=lfs diff=lfs merge=lfs -text",
|
|
"# *.pdf filter=lfs diff=lfs merge=lfs -text",
|
|
"*.png filter=lfs diff=lfs merge=lfs -text",
|
|
"*.txt text"}, "\n")))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, lines, 4)
|
|
|
|
assert.Implements(t, (*PatternLine)(nil), lines[0])
|
|
assert.Implements(t, (*PatternLine)(nil), lines[1])
|
|
assert.Implements(t, (*PatternLine)(nil), lines[2])
|
|
assert.Implements(t, (*PatternLine)(nil), lines[3])
|
|
|
|
assert.Equal(t, lines[0].(PatternLine).Pattern().String(), "*.dat")
|
|
assert.Equal(t, lines[1].(PatternLine).Pattern().String(), "*.jpg")
|
|
assert.Equal(t, lines[2].(PatternLine).Pattern().String(), "*.png")
|
|
assert.Equal(t, lines[3].(PatternLine).Pattern().String(), "*.txt")
|
|
|
|
assert.Len(t, lines[0].Attrs(), 4)
|
|
assert.Equal(t, lines[0].Attrs()[0], &Attr{K: "filter", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[1], &Attr{K: "diff", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[2], &Attr{K: "merge", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[3], &Attr{K: "text", V: "false"})
|
|
|
|
assert.Len(t, lines[1].Attrs(), 4)
|
|
assert.Equal(t, lines[1].Attrs()[0], &Attr{K: "filter", V: "lfs"})
|
|
assert.Equal(t, lines[1].Attrs()[1], &Attr{K: "diff", V: "lfs"})
|
|
assert.Equal(t, lines[1].Attrs()[2], &Attr{K: "merge", V: "lfs"})
|
|
assert.Equal(t, lines[1].Attrs()[3], &Attr{K: "text", V: "false"})
|
|
|
|
assert.Len(t, lines[2].Attrs(), 4)
|
|
assert.Equal(t, lines[2].Attrs()[0], &Attr{K: "filter", V: "lfs"})
|
|
assert.Equal(t, lines[2].Attrs()[1], &Attr{K: "diff", V: "lfs"})
|
|
assert.Equal(t, lines[2].Attrs()[2], &Attr{K: "merge", V: "lfs"})
|
|
assert.Equal(t, lines[2].Attrs()[3], &Attr{K: "text", V: "false"})
|
|
|
|
assert.Len(t, lines[3].Attrs(), 1)
|
|
assert.Equal(t, lines[3].Attrs()[0], &Attr{K: "text", V: "true"})
|
|
}
|
|
|
|
func TestParseLinesUnset(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader("*.dat -filter"))
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, lines, 1)
|
|
|
|
assert.Implements(t, (*PatternLine)(nil), lines[0])
|
|
|
|
assert.Equal(t, lines[0].(PatternLine).Pattern().String(), "*.dat")
|
|
assert.Equal(t, lines[0].Attrs()[0], &Attr{
|
|
K: "filter", V: "false",
|
|
})
|
|
}
|
|
|
|
func TestParseLinesUnspecified(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader("*.dat !filter"))
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, lines, 1)
|
|
|
|
assert.Implements(t, (*PatternLine)(nil), lines[0])
|
|
|
|
assert.Equal(t, lines[0].(PatternLine).Pattern().String(), "*.dat")
|
|
assert.Equal(t, lines[0].Attrs()[0], &Attr{
|
|
K: "filter", Unspecified: true,
|
|
})
|
|
}
|
|
|
|
func TestParseLinesQuotedPattern(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader(
|
|
"\"space *.dat\" filter=lfs"))
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, lines, 1)
|
|
|
|
assert.Implements(t, (*PatternLine)(nil), lines[0])
|
|
|
|
assert.Equal(t, lines[0].(PatternLine).Pattern().String(), "space *.dat")
|
|
assert.Equal(t, lines[0].Attrs()[0], &Attr{
|
|
K: "filter", V: "lfs",
|
|
})
|
|
}
|
|
|
|
func TestParseLinesCommented(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader(
|
|
"# \"space *.dat\" filter=lfs"))
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, lines, 0)
|
|
}
|
|
|
|
func TestParseLinesUnbalancedQuotes(t *testing.T) {
|
|
const text = "\"space *.dat filter=lfs"
|
|
lines, _, err := ParseLines(strings.NewReader(text))
|
|
|
|
assert.Empty(t, lines)
|
|
assert.EqualError(t, err, fmt.Sprintf(
|
|
"unbalanced quote: %s", text))
|
|
}
|
|
|
|
func TestParseLinesWithNoAttributes(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader("*.dat"))
|
|
|
|
assert.Len(t, lines, 1)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Implements(t, (*PatternLine)(nil), lines[0])
|
|
|
|
assert.Equal(t, lines[0].(PatternLine).Pattern().String(), "*.dat")
|
|
assert.Empty(t, lines[0].Attrs())
|
|
}
|
|
|
|
func TestParseLinesWithMacros(t *testing.T) {
|
|
lines, _, err := ParseLines(strings.NewReader(strings.Join([]string{
|
|
"[attr]lfs filter=lfs diff=lfs merge=lfs -text",
|
|
"*.dat lfs",
|
|
"*.txt text"}, "\n")))
|
|
|
|
assert.Len(t, lines, 3)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Implements(t, (*MacroLine)(nil), lines[0])
|
|
assert.Implements(t, (*PatternLine)(nil), lines[1])
|
|
assert.Implements(t, (*PatternLine)(nil), lines[2])
|
|
|
|
assert.Equal(t, lines[0].(MacroLine).Macro(), "lfs")
|
|
assert.Len(t, lines[0].Attrs(), 4)
|
|
assert.Equal(t, lines[0].Attrs()[0], &Attr{K: "filter", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[1], &Attr{K: "diff", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[2], &Attr{K: "merge", V: "lfs"})
|
|
assert.Equal(t, lines[0].Attrs()[3], &Attr{K: "text", V: "false"})
|
|
|
|
assert.Equal(t, lines[1].(PatternLine).Pattern().String(), "*.dat")
|
|
assert.Len(t, lines[1].Attrs(), 1)
|
|
assert.Equal(t, lines[1].Attrs()[0], &Attr{K: "lfs", V: "true"})
|
|
|
|
assert.Equal(t, lines[2].(PatternLine).Pattern().String(), "*.txt")
|
|
assert.Len(t, lines[2].Attrs(), 1)
|
|
assert.Equal(t, lines[2].Attrs()[0], &Attr{K: "text", V: "true"})
|
|
}
|