Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: respect gitattributes #342

Merged
merged 2 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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