Skip to content

Commit

Permalink
fix: respect gitattributes (#342)
Browse files Browse the repository at this point in the history
* fix: respect gitattributes

Check gitattributes for file attrs before displaying files.

Fixes: #238

* chore: add tests
  • Loading branch information
aymanbagabas authored Jul 20, 2023
1 parent 0e9abaf commit d0afaa0
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 5 deletions.
62 changes: 62 additions & 0 deletions git/attr.go
Original file line number Diff line number Diff line change
@@ -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
}
91 changes: 91 additions & 0 deletions git/attr_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
38 changes: 33 additions & 5 deletions server/ui/pages/repo/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit d0afaa0

Please sign in to comment.