@@ -0,0 +1,62 @@
+package git
+
+import (
+ "math/rand"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Attribute represents a Git attribute.
+type Attribute struct {
+ Name string
+ Value string
+}
+
+// CheckAttributes checks the attributes of the given ref and path.
+func (r *Repository) CheckAttributes(ref *Reference, path string) ([]Attribute, error) {
+ rnd := rand.NewSource(time.Now().UnixNano())
+ fn := "soft-serve-index-" + strconv.Itoa(rand.New(rnd).Int()) // nolint: gosec
+ tmpindex := filepath.Join(os.TempDir(), fn)
+
+ defer os.Remove(tmpindex) // nolint: errcheck
+
+ readTree := NewCommand("read-tree", "--reset", "-i", ref.Name().String()).
+ AddEnvs("GIT_INDEX_FILE=" + tmpindex)
+ if _, err := readTree.RunInDir(r.Path); err != nil {
+ return nil, err
+ }
+
+ checkAttr := NewCommand("check-attr", "--cached", "-a", "--", path).
+ AddEnvs("GIT_INDEX_FILE=" + tmpindex)
+ out, err := checkAttr.RunInDir(r.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ return parseAttributes(path, out), nil
+}
+
+func parseAttributes(path string, buf []byte) []Attribute {
+ attrs := make([]Attribute, 0)
+ for _, line := range strings.Split(string(buf), "\n") {
+ if line == "" {
+ continue
+ }
+
+ line = strings.TrimPrefix(line, path+": ")
+ parts := strings.SplitN(line, ": ", 2)
+ if len(parts) != 2 {
+ continue
+ }
+
+ attrs = append(attrs, Attribute{
+ Name: parts[0],
+ Value: parts[1],
+ })
+ }
+
+ return attrs
+}
@@ -0,0 +1,91 @@
+package git
+
+import (
+ "testing"
+
+ "github.com/matryer/is"
+)
+
+func TestParseAttr(t *testing.T) {
+ cases := []struct {
+ in string
+ file string
+ want []Attribute
+ }{
+ {
+ in: "org/example/MyClass.java: diff: java\n",
+ file: "org/example/MyClass.java",
+ want: []Attribute{
+ {
+ Name: "diff",
+ Value: "java",
+ },
+ },
+ },
+ {
+ in: `org/example/MyClass.java: crlf: unset
+org/example/MyClass.java: diff: java
+org/example/MyClass.java: myAttr: set`,
+ file: "org/example/MyClass.java",
+ want: []Attribute{
+ {
+ Name: "crlf",
+ Value: "unset",
+ },
+ {
+ Name: "diff",
+ Value: "java",
+ },
+ {
+ Name: "myAttr",
+ Value: "set",
+ },
+ },
+ },
+ {
+ in: `org/example/MyClass.java: diff: java
+org/example/MyClass.java: myAttr: set`,
+ file: "org/example/MyClass.java",
+ want: []Attribute{
+ {
+ Name: "diff",
+ Value: "java",
+ },
+ {
+ Name: "myAttr",
+ Value: "set",
+ },
+ },
+ },
+ {
+ in: `README: caveat: unspecified`,
+ file: "README",
+ want: []Attribute{
+ {
+ Name: "caveat",
+ Value: "unspecified",
+ },
+ },
+ },
+ {
+ in: "",
+ file: "foo",
+ want: []Attribute{},
+ },
+ {
+ in: "\n",
+ file: "foo",
+ want: []Attribute{},
+ },
+ }
+
+ is := is.New(t)
+ for _, c := range cases {
+ attrs := parseAttributes(c.file, []byte(c.in))
+ if len(attrs) != len(c.want) {
+ t.Fatalf("parseAttributes(%q, %q) = %v, want %v", c.file, c.in, attrs, c.want)
+ }
+
+ is.Equal(attrs, c.want)
+ }
+}
@@ -376,26 +376,54 @@ func (f *Files) selectFileCmd() tea.Msg {
log.Printf("ui: files: current item is not a file")
return common.ErrorMsg(errInvalidFile)
}
- bin, err := fi.IsBinary()
- if err != nil {
- f.path = filepath.Dir(f.path)
- log.Printf("ui: files: error checking if file is binary %v", err)
- return common.ErrorMsg(err)
+
+ var err error
+ var bin bool
+
+ r, err := f.repo.Open()
+ if err == nil {
+ attrs, err := r.CheckAttributes(f.ref, fi.Path())
+ if err == nil {
+ for _, attr := range attrs {
+ if (attr.Name == "binary" && attr.Value == "set") ||
+ (attr.Name == "text" && attr.Value == "unset") {
+ bin = true
+ break
+ }
+ }
+ } else {
+ log.Printf("ui: files: error checking attributes %v", err)
+ }
+ } else {
+ log.Printf("ui: files: error opening repo %v", err)
+ }
+
+ if !bin {
+ bin, err = fi.IsBinary()
+ if err != nil {
+ f.path = filepath.Dir(f.path)
+ log.Printf("ui: files: error checking if file is binary %v", err)
+ return common.ErrorMsg(err)
+ }
}
+
if bin {
f.path = filepath.Dir(f.path)
log.Printf("ui: files: file is binary")
return common.ErrorMsg(errBinaryFile)
}
+
c, err := fi.Bytes()
if err != nil {
f.path = filepath.Dir(f.path)
log.Printf("ui: files: error reading file %v", err)
return common.ErrorMsg(err)
}
+
f.lastSelected = append(f.lastSelected, f.selector.Index())
return FileContentMsg{string(c), i.entry.Name()}
}
+
log.Printf("ui: files: current item is not a file")
return common.ErrorMsg(errNoFileSelected)
}