-
Notifications
You must be signed in to change notification settings - Fork 534
Panic when calling Patch.String #523
Comments
Hi, Using go-git master, I'm not able to reproduce this panic. Here is the code: package main
import (
"fmt"
"gopkg.in/src-d/go-git.v4"
. "gopkg.in/src-d/go-git.v4/_examples"
"gopkg.in/src-d/go-git.v4/plumbing"
)
func main() {
url := "https://github.com/emersion/matcha.git"
directory := "./test"
r, err := git.PlainClone(directory, false, &git.CloneOptions{
URL: url,
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
})
CheckIfError(err)
// ... retrieving the branch being pointed by HEAD
ref, err := r.Head()
CheckIfError(err)
// ... retrieving the commit object
commit, err := r.CommitObject(plumbing.NewHash("cb9c85f3b53438246a18499bcdf32ce4f6d7b468"))
CheckIfError(err)
head, err := r.CommitObject(ref.Hash())
CheckIfError(err)
p, err := commit.Patch(head)
CheckIfError(err)
fmt.Println(p.String())
} And here is the result: (Click to expand)/usr/local/go/bin/go run /home/antonio/work/src/gopkg.in/src-d/go-git.v4/_examples/clone/main.go
diff --git a/README.md b/README.md
index b0d4bdd35403f65b2188d622a43e13ac044654fc..30edbcbf337b6cbde8be6b12882eaca54f72033b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +-1,16 @@
# matcha
-A read-only web interface for Git repositories
+
+A simple read-only web interface for Git repositories.
+
+## Usage
+
+```sh
+go get -u github.com/emersion/matcha/cmd/matcha
+cd $GOPATH/src/github.com/emersion/matcha
+(cd public && npm install)
+matcha
+```
+
+## License
+
+MIT
diff --git a/matcha.go b/matcha.go
index 31428fff1e9c865590dc40d1eb6bdc14c8357d92..e496976ea042c275d000e59d854c21c87113a72b 100644
--- a/matcha.go
+++ b/matcha.go
@@ -2,111 +2,501 @@ package matcha
import (
"html/template"
- "io"
"net/http"
+ "path"
"path/filepath"
+ "sort"
+ "strings"
"github.com/labstack/echo"
+ "github.com/russross/blackfriday"
"gopkg.in/src-d/go-git.v4"
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/filemode"
"gopkg.in/src-d/go-git.v4/plumbing/object"
+ "gopkg.in/src-d/go-git.v4/plumbing/storer"
)
-type templateRenderer struct {
- templates *template.Template
+type server struct {
+ dir string
+ r *git.Repository
+}
+
+type headerData struct {
+ RepoName string
}
-func (r *templateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
- return r.templates.ExecuteTemplate(w, name, data)
+func (s *server) headerData() *headerData {
+ return &headerData{
+ RepoName: filepath.Base(s.dir),
+ }
}
-func New(e *echo.Echo, dir string) error {
- dir, err := filepath.Abs(dir)
+func (s *server) commitFromRev(revName string) (*object.Commit, error) {
+ // First try to resolve a hash
+ commit, err := s.r.CommitObject(plumbing.NewHash(revName))
+ if err != plumbing.ErrObjectNotFound {
+ return commit, err
+ }
+
+ // Then a branch
+ ref, err := s.r.Reference(plumbing.ReferenceName("refs/heads/"+revName), true)
+ if err == nil {
+ return s.r.CommitObject(ref.Hash())
+ } else if err != plumbing.ErrReferenceNotFound {
+ return nil, err
+ }
+
+ // Finally a tag
+ ref, err = s.r.Reference(plumbing.ReferenceName("refs/tags/"+revName), true)
+ if err != nil {
+ return nil, err
+ }
+
+ tag, err := s.r.TagObject(ref.Hash())
if err != nil {
- return err
+ return nil, err
}
- t := template.Must(template.ParseGlob("public/views/*.html"))
- e.Renderer = &templateRenderer{t}
+ return tag.Commit()
+}
- r, err := git.PlainOpen(dir)
- if err == git.ErrRepositoryNotExists {
- return err //return c.String(http.StatusNotFound, "No such repository")
- } else if err != nil {
- return err
+func (s *server) lastCommits(from *object.Commit, patterns []string) ([]*object.Commit, error) {
+ last := make([]*object.Commit, len(patterns))
+ /*for i := range last {
+ last[i] = from
}
+ return last, nil*/
- e.GET("/", func(c echo.Context) error {
- ref, err := r.Head()
+ remaining := len(patterns)
+
+ commits, err := s.r.Log(&git.LogOptions{From: from.Hash})
+ if err != nil {
+ return nil, err
+ }
+
+ err = commits.ForEach(func(c *object.Commit) error {
+ ctree, err := c.Tree()
if err != nil {
return err
}
- commit, err := r.CommitObject(ref.Hash())
+ parents := 0
+ err = c.Parents().ForEach(func(p *object.Commit) error {
+ parents++
+
+ ptree, err := p.Tree()
+ if err != nil {
+ return err
+ }
+
+ changes, err := ptree.Diff(ctree)
+ if err != nil {
+ return err
+ }
+
+ for _, change := range changes {
+ for i, pattern := range patterns {
+ if last[i] == nil && strings.HasPrefix(change.To.Name, pattern) {
+ last[i] = c
+ remaining--
+ if remaining == 0 {
+ return storer.ErrStop
+ }
+ }
+ }
+ }
+ return nil
+ })
if err != nil {
return err
}
- tree, err := commit.Tree()
- if err != nil {
- return err
+ if parents == 0 {
+ for i, l := range last {
+ if l == nil {
+ last[i] = c
+ remaining--
+ }
+ }
}
- var data struct{
- RepoName string
- Files []string
+ if remaining == 0 {
+ return storer.ErrStop
}
+ return nil
+ })
- data.RepoName = filepath.Base(dir)
+ return last, err
+}
- err = tree.Files().ForEach(func(f *object.File) error {
- data.Files = append(data.Files, f.Name)
- return nil
- })
- if err != nil {
+type treeEntry struct {
+ *object.TreeEntry
+ LastCommit *object.Commit
+}
+
+func (s *server) tree(c echo.Context, revName, p string) error {
+ commit, err := s.commitFromRev(revName)
+ if err == plumbing.ErrReferenceNotFound {
+ return c.String(http.StatusNotFound, "No such revision")
+ } else if err != nil {
+ return err
+ }
+
+ tree, err := commit.Tree()
+ if err != nil {
+ return err
+ }
+
+ if p == "" {
+ p = "/"
+ }
+ if p != "/" {
+ tree, err = tree.Tree(p)
+ if err == object.ErrDirectoryNotFound {
+ return c.String(http.StatusNotFound, "No such directory")
+ } else if err != nil {
return err
}
+ }
- return c.Render(http.StatusOK, "tree-dir.html", data)
+ var data struct{
+ *headerData
+ Revision string
+ DirName, DirSep string
+ Parents []breadcumbItem
+ Entries []treeEntry
+ LastCommit *object.Commit
+ ReadMe template.HTML
+ }
+
+ data.headerData = s.headerData()
+ data.Revision = revName
+
+ sort.Slice(tree.Entries, func(i, j int) bool {
+ a, b := &tree.Entries[i], &tree.Entries[j]
+ if a.Mode & filemode.Dir != 0 {
+ return true
+ }
+ if b.Mode & filemode.Dir != 0 {
+ return false
+ }
+ return a.Name < b.Name
})
- e.GET("/blob/:branch/*", func(c echo.Context) error {
- if branch := c.Param("branch"); branch != "master" {
- // TODO
- return c.String(http.StatusNotFound, "No such branch")
+ patterns := make([]string, 0, len(tree.Entries) + 1)
+ pathPattern := p + "/"
+ if p == "/" {
+ pathPattern = ""
+ }
+ patterns = append(patterns, pathPattern)
+ for _, e := range tree.Entries {
+ pattern := e.Name
+ if p != "/" {
+ pattern = path.Join(p, pattern)
}
- path := c.Param("*")
+ if e.Mode & filemode.Dir != 0 {
+ pattern += "/"
+ }
+ patterns = append(patterns, pattern)
+ }
+
+ lastCommits, err := s.lastCommits(commit, patterns)
+ if err != nil {
+ return err
+ }
+ for _, c := range lastCommits {
+ c.Message = cleanupCommitMessage(c.Message)
+ }
+
+ data.Entries = make([]treeEntry, len(tree.Entries))
+ data.LastCommit = lastCommits[0]
+ for i := range tree.Entries {
+ data.Entries[i] = treeEntry{&tree.Entries[i], lastCommits[i+1]}
+ }
- ref, err := r.Head()
- if err != nil {
- return err
+ for _, e := range tree.Entries {
+ name := strings.TrimSuffix(e.Name, path.Ext(e.Name))
+ if strings.EqualFold(name, "README") && e.Mode & filemode.Regular != 0 {
+ f, err := tree.TreeEntryFile(&e)
+ if err != nil {
+ return err
+ }
+
+ raw, err := f.Contents()
+ if err != nil {
+ return err
+ }
+
+ rendered := blackfriday.MarkdownCommon([]byte(raw))
+ data.ReadMe = template.HTML(string(rendered))
+ break
}
+ }
- commit, err := r.CommitObject(ref.Hash())
+ dirpath, filepath := path.Split(p)
+ data.DirName = filepath
+ data.Parents = pathBreadcumb(dirpath)
+
+ data.DirSep = "/"+p+"/"
+ if p == "/" {
+ data.DirSep = "/"
+ }
+
+ return c.Render(http.StatusOK, "tree.html", data)
+}
+
+func (s *server) blob(c echo.Context, revName, p string) error {
+ commit, err := s.commitFromRev(revName)
+ if err == plumbing.ErrReferenceNotFound {
+ return c.String(http.StatusNotFound, "No such revision")
+ } else if err != nil {
+ return err
+ }
+
+ f, err := commit.File(p)
+ if err == object.ErrFileNotFound {
+ return c.String(http.StatusNotFound, "No such file")
+ } else if err != nil {
+ return err
+ }
+
+ var data struct{
+ *headerData
+ Revision string
+ Filepath, Filename, Extension string
+ Parents []breadcumbItem
+ IsBinary bool
+ Rendered template.HTML
+ Contents string
+ }
+
+ data.headerData = s.headerData()
+ data.Revision = revName
+
+ dirpath, filename := path.Split(p)
+ data.Filepath = p
+ data.Filename = filename
+ data.Extension = strings.TrimLeft(path.Ext(p), ".")
+ data.Parents = pathBreadcumb(dirpath)
+
+ if f.Size > 1024*1024 {
+ data.IsBinary = true
+ } else if binary, err := f.IsBinary(); err != nil || binary {
+ data.IsBinary = true
+ }
+
+ if !data.IsBinary {
+ contents, err := f.Contents()
if err != nil {
return err
}
+ data.Contents = contents
- f, err := commit.File(path)
- if err == object.ErrFileNotFound {
- return c.String(http.StatusNotFound, "No such file")
- } else if err != nil {
- return err
+ switch data.Extension {
+ case "md", "markdown":
+ rendered := blackfriday.MarkdownCommon([]byte(contents))
+ data.Rendered = template.HTML(string(rendered))
}
+ }
- r, err := f.Reader()
+ return c.Render(http.StatusOK, "blob.html", data)
+}
+
+func (s *server) raw(c echo.Context, revName, p string) error {
+ commit, err := s.commitFromRev(revName)
+ if err == plumbing.ErrReferenceNotFound {
+ return c.String(http.StatusNotFound, "No such revision")
+ } else if err != nil {
+ return err
+ }
+
+ f, err := commit.File(p)
+ if err == object.ErrFileNotFound {
+ return c.String(http.StatusNotFound, "No such file")
+ } else if err != nil {
+ return err
+ }
+
+ r, err := f.Reader()
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ // TODO: autodetect file type
+ mediaType := "application/octet-stream"
+ if binary, err := f.IsBinary(); err == nil && !binary {
+ mediaType = "text/plain"
+ }
+
+ // TODO: set filename
+ return c.Stream(http.StatusOK, mediaType, r)
+}
+
+func (s *server) branches(c echo.Context) error {
+ branches, err := s.r.Branches()
+ if err != nil {
+ return err
+ }
+ defer branches.Close()
+
+ var data struct{
+ *headerData
+ Branches []string
+ }
+
+ data.headerData = s.headerData()
+
+ err = branches.ForEach(func(ref *plumbing.Reference) error {
+ data.Branches = append(data.Branches, ref.Name().Short())
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return c.Render(http.StatusOK, "branches.html", data)
+}
+
+func (s *server) tags(c echo.Context) error {
+ tags, err := s.r.TagObjects()
+ if err != nil {
+ return err
+ }
+ defer tags.Close()
+
+ var data struct{
+ *headerData
+ Tags []*object.Tag
+ }
+
+ data.headerData = s.headerData()
+
+ err = tags.ForEach(func(t *object.Tag) error {
+ data.Tags = append(data.Tags, t)
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return c.Render(http.StatusOK, "tags.html", data)
+}
+
+func (s *server) commits(c echo.Context, revName string) error {
+ commit, err := s.commitFromRev(revName)
+ if err == plumbing.ErrReferenceNotFound {
+ return c.String(http.StatusNotFound, "No such revision")
+ } else if err != nil {
+ return err
+ }
+
+ commits, err := s.r.Log(&git.LogOptions{From: commit.Hash})
+ if err != nil {
+ return err
+ }
+ defer commits.Close()
+
+ var data struct{
+ *headerData
+ Commits []*object.Commit
+ }
+
+ data.headerData = s.headerData()
+
+ err = commits.ForEach(func(c *object.Commit) error {
+ c.Message = cleanupCommitMessage(c.Message)
+
+ data.Commits = append(data.Commits, c)
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return c.Render(http.StatusOK, "commits.html", data)
+}
+
+func (s *server) commit(c echo.Context, hash string) error {
+ commit, err := s.r.CommitObject(plumbing.NewHash(hash))
+ if err == plumbing.ErrObjectNotFound {
+ return c.String(http.StatusNotFound, "No such commit")
+ } else if err != nil {
+ return err
+ }
+
+ var data struct{
+ *headerData
+ Commit *object.Commit
+ Diff string
+ }
+
+ data.headerData = s.headerData()
+
+ commit.Message = cleanupCommitMessage(commit.Message)
+ data.Commit = commit
+
+ if len(commit.ParentHashes) > 0 {
+ // TODO
+ parent, err := s.r.CommitObject(commit.ParentHashes[0])
if err != nil {
return err
}
- defer r.Close()
- // TODO: autodetect file type
- mediaType := "application/octet-stream"
- if binary, err := f.IsBinary(); err == nil && !binary {
- mediaType = "text/plain"
+ patch, err := parent.Patch(commit)
+ if err != nil {
+ return err
}
- // TODO: set filename
- return c.Stream(http.StatusOK, mediaType, r)
+ data.Diff = patch.String()
+ }
+
+ return c.Render(http.StatusOK, "commit.html", data)
+}
+
+func New(e *echo.Echo, dir string) error {
+ dir, err := filepath.Abs(dir)
+ if err != nil {
+ return err
+ }
+
+ e.Renderer, err = loadTemplateRenderer()
+ if err != nil {
+ return err
+ }
+
+ r, err := git.PlainOpen(dir)
+ if err == git.ErrRepositoryNotExists {
+ return err //return c.String(http.StatusNotFound, "No such repository")
+ } else if err != nil {
+ return err
+ }
+
+ s := &server{dir, r}
+
+ e.GET("/", func(c echo.Context) error {
+ return s.tree(c, "master", "/")
+ })
+ e.GET("/tree/:ref", func(c echo.Context) error {
+ return s.tree(c, c.Param("ref"), "")
+ })
+ e.GET("/tree/:ref/*", func(c echo.Context) error {
+ return s.tree(c, c.Param("ref"), c.Param("*"))
+ })
+ e.GET("/blob/:ref/*", func(c echo.Context) error {
+ return s.blob(c, c.Param("ref"), c.Param("*"))
+ })
+ e.GET("/raw/:ref/*", func(c echo.Context) error {
+ return s.raw(c, c.Param("ref"), c.Param("*"))
+ })
+ e.GET("/branches", s.branches)
+ e.GET("/tags", s.tags)
+ e.GET("/commits/:ref", func(c echo.Context) error {
+ return s.commits(c, c.Param("ref"))
+ })
+ e.GET("/commit/:hash", func(c echo.Context) error {
+ return s.commit(c, c.Param("hash"))
})
e.Static("/static", "public/node_modules")
diff --git a/public/package-lock.json b/public/package-lock.json
index 410c8347f8fda6486b6c9697bfd34890c2b3b682..f4d7eaafbd5bef0030ac1b026bae9ce25f34f051 100644
--- a/public/package-lock.json
+++ b/public/package-lock.json
@@ -4,6 +4,11 @@ "version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "octicons": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/octicons/-/octicons-5.0.1.tgz",
+ "integrity": "sha1-D6DE/TaA0lEmbwc3NlOTF8uEbk8="
+ },
"primer-alerts": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/primer-alerts/-/primer-alerts-1.1.8.tgz",
diff --git a/public/package.json b/public/package.json
index 24908180a4a53fbd033b44be82efe87d2988b0c9..13de70375891d684eb77369857340b8e33af573c 100644
--- a/public/package.json
+++ b/public/package.json
@@ -3,6 +3,7 @@ "name": "matcha",
"version": "0.0.0",
"license": "MIT",
"dependencies": {
+ "octicons": "^5.0.1",
"primer-css": "^9.0.0"
}
}
diff --git a/public/views/blob.html b/public/views/blob.html
new file mode 100644
index 0000000000000000000000000000000000000000..48d80021baf860b41bd2eef9186a33de1fefd2f6
--- /dev/null
+++ b/public/views/blob.html
@@ -0,0 +1,34 @@
+{{template "header.html" .}}
+
+<nav>
+ <p>
+ <a href="/tree/{{$.Revision}}"><strong>{{.RepoName}}</strong></a>
+ {{range .Parents}} / <a href="/tree/{{$.Revision}}/{{.Path}}">{{.Name}}</a>{{end}}
+ / <strong>{{.Filename}}</strong>
+ </p>
+</nav>
+
+<p>
+ <a href="/raw/{{$.Revision}}/{{.Filepath}}" class="btn btn-sm">Raw</a>
+</p>
+
+{{if .IsBinary}}
+ <div class="blankslate">
+ <h3>Cannot preview file</h3>
+ <p>You can download it by clicking on "Raw".</p>
+ </div>
+{{else}}
+ {{if .Rendered}}
+ <div class="Box Box--condensed">
+ <div class="Box-body markdown-body">
+ {{.Rendered}}
+ </div>
+ </div>
+ {{else}}
+ <div class="markdown-body">
+ <pre><code class="language-{{.Extension}}">{{.Contents}}</code></pre>
+ </div>
+ {{end}}
+{{end}}
+
+{{template "footer.html"}}
diff --git a/public/views/branches.html b/public/views/branches.html
new file mode 100644
index 0000000000000000000000000000000000000000..42b8453a7447c95c03c2b64d4a7138b21ab09fe1
--- /dev/null
+++ b/public/views/branches.html
@@ -0,0 +1,9 @@
+{{template "header.html" .}}
+
+<nav class="menu">
+ {{range .Branches}}
+ <a class="menu-item" href="/tree/{{.}}">{{.}}</a>
+ {{end}}
+</nav>
+
+{{template "footer.html"}}
diff --git a/public/views/commit.html b/public/views/commit.html
new file mode 100644
index 0000000000000000000000000000000000000000..3d2331be92b313b95bb69a5a00d9bb16115c5c6e
--- /dev/null
+++ b/public/views/commit.html
@@ -0,0 +1,22 @@
+{{template "header.html" .}}
+
+{{with .Commit}}
+ <div class="Box Box--blue">
+ <div class="Box-header">
+ <a class="btn btn-outline float-right" type="button" href="/tree/{{.Hash}}">Browse files</a>
+ <h3>{{.Message}}</h3>
+ </div>
+ <div class="Box-body">
+ <code class="float-right">{{.Hash}}</code>
+ <strong>{{.Author.Name}}</strong> commited {{date .Author.When}}
+ </div>
+ </div>
+{{end}}
+
+<p></p>
+
+<div class="markdown-body">
+ <pre><code class="language-diff">{{.Diff}}</code></pre>
+</div>
+
+{{template "footer.html"}}
diff --git a/public/views/commits.html b/public/views/commits.html
new file mode 100644
index 0000000000000000000000000000000000000000..52d914837854e31257f2e60d805406cb6bb1fe9d
--- /dev/null
+++ b/public/views/commits.html
@@ -0,0 +1,16 @@
+{{template "header.html" .}}
+
+<nav class="menu">
+ {{range .Commits}}
+ <div class="menu-item">
+ <span class="float-right">
+ <a href="/commit/{{.Hash}}" class="btn btn-outline">{{.Hash}}</a>
+ <a href="/tree/{{.Hash}}" class="btn btn-outline">{{icon "code"}}</a>
+ </span>
+ <strong>{{.Message}}</strong><br>
+ <small><strong>{{.Author.Name}}</strong> committed {{date .Author.When}}</small>
+ </div>
+ {{end}}
+</nav>
+
+{{template "footer.html"}}
diff --git a/public/views/footer.html b/public/views/footer.html
new file mode 100644
index 0000000000000000000000000000000000000000..6a44d9af2836f1681f9ae2809a094dc3142be1ff
--- /dev/null
+++ b/public/views/footer.html
@@ -0,0 +1,3 @@
+<p></p>
+
+{{template "foot.html"}}
diff --git a/public/views/head.html b/public/views/head.html
index abb7461ebce0c321d4768189e0416717323800a8..8c2dcfa421119cbc2b5f415b70c8b58e2860cb55 100644
--- a/public/views/head.html
+++ b/public/views/head.html
@@ -5,6 +5,13 @@ <meta charset="utf-8">
<title>Matcha</title>
<link rel="stylesheet" href="/static/primer-css/build/build.css">
+ <link rel="stylesheet" href="/static/primer-utilities/build/build.css">
+ <link rel="stylesheet" href="/static/octicons/build/octicons.min.css">
+
+ <!-- TODO: use local version of highlight.js, or switch to server-side rendering -->
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
+ <script>hljs.initHighlightingOnLoad();</script>
</head>
<body>
<div class="container">
diff --git a/public/views/header.html b/public/views/header.html
new file mode 100644
index 0000000000000000000000000000000000000000..4d8414de4d59f8e03fb2ce079f4c08308a3accc5
--- /dev/null
+++ b/public/views/header.html
@@ -0,0 +1,3 @@
+{{template "head.html"}}
+
+<h1><a href="/">{{.RepoName}}</a></h1>
diff --git a/public/views/tags.html b/public/views/tags.html
new file mode 100644
index 0000000000000000000000000000000000000000..f21bdd381fbabfd451bc4a52756e4c1e7e673739
--- /dev/null
+++ b/public/views/tags.html
@@ -0,0 +1,19 @@
+{{template "header.html" .}}
+
+<nav>
+ {{range .Tags}}
+ <div>
+ <span class="float-right">
+ <a href="/commit/{{.Target}}" class="btn btn-outline">{{.Target}}</a>
+ <a href="/tree/{{.Name}}" class="btn btn-outline">{{icon "code"}}</a>
+ </span>
+
+ <h3>{{.Name}}</h3>
+ <p><strong>{{.Tagger.Name}}</strong> tagged this {{date .Tagger.When}}</p>
+ <p>{{.Message}}</p>
+ </div>
+ <hr>
+ {{end}}
+</nav>
+
+{{template "footer.html"}}
diff --git a/public/views/tree-dir.html b/public/views/tree-dir.html
deleted file mode 100644
index 7b787783aad7425cb76b3e34ec77c75f4095af63..0000000000000000000000000000000000000000
--- a/public/views/tree-dir.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{{template "head.html"}}
-
-<h1>{{.RepoName}}</h1>
-
-<nav class="menu">
- {{range .Files}}
- <a class="menu-item" href="/blob/master/{{.}}">{{.}}</a>
- {{end}}
-</nav>
-
-{{template "foot.html"}}
diff --git a/public/views/tree.html b/public/views/tree.html
new file mode 100644
index 0000000000000000000000000000000000000000..36c522f7b34dd040fa0fd0f6adc556cd0afd8d61
--- /dev/null
+++ b/public/views/tree.html
@@ -0,0 +1,57 @@
+{{template "header.html" .}}
+
+{{if eq .DirName ""}}
+ <nav class="subnav" aria-label="Repository">
+ <a href="/commits/{{$.Revision}}" class="tabnav-tab">{{icon "git-commit"}} Commits</a>
+ <a href="/branches" class="tabnav-tab">{{icon "git-branch"}} Branches</a>
+ <a href="/tags" class="tabnav-tab">{{icon "tag"}} Tags</a>
+ </nav>
+{{else}}
+ <nav>
+ <p>
+ <a href="/tree/{{$.Revision}}"><strong>{{.RepoName}}</strong></a>
+ {{range .Parents}} / <a href="/tree/{{$.Revision}}/{{.Path}}">{{.Name}}</a>{{end}}
+ / <strong>{{.DirName}}</strong>
+ </p>
+ </nav>
+{{end}}
+
+<div class="Box Box--condensed Box--blue">
+ {{with .LastCommit}}
+ <div class="Box-header">
+ <span class="text-gray float-right">{{date .Author.When}}</span>
+ <strong>{{.Author.Name}}</strong> <a href="/commit/{{.Hash}}" class="muted-link">{{.Message}}</a>
+ </div>
+ {{end}}
+ <ul>
+ {{range .Entries}}
+ <li class="Box-row">
+ {{with .LastCommit}}
+ <span class="text-gray float-right">{{date .Author.When}}</span>
+ {{end}}
+ {{if .Mode.ToOSFileMode.IsDir}}
+ <span class="text-blue">{{icon "file-directory"}}</span> <a href="/tree/{{$.Revision}}{{$.DirSep}}{{.Name}}">{{.Name}}</a>
+ {{else}}
+ <span class="text-blue">{{icon "file"}}</span> <a href="/blob/{{$.Revision}}{{$.DirSep}}{{.Name}}">{{.Name}}</a>
+ {{end}}
+ {{with .LastCommit}}
+ <a href="/commit/{{.Hash}}" class="muted-link">{{.Message}}</a>
+ {{end}}
+ </li>
+ {{end}}
+ </ul>
+</div>
+
+{{if .ReadMe}}
+ <p></p>
+ <div class="Box Box--condensed">
+ <div class="Box-header">
+ {{icon "book"}} README
+ </div>
+ <div class="Box-body markdown-body">
+ {{.ReadMe}}
+ </div>
+ </div>
+{{end}}
+
+{{template "footer.html"}}
diff --git a/template.go b/template.go
new file mode 100644
index 0000000000000000000000000000000000000000..6f497c943a9de0dcf5ba96277f24a2ad3aa6579e
--- /dev/null
+++ b/template.go
@@ -0,0 +1,103 @@
+package matcha
+
+import (
+ "bytes"
+ "fmt"
+ "html/template"
+ "io"
+ "path"
+ "strings"
+ "time"
+
+ "github.com/labstack/echo"
+ "github.com/shurcooL/octiconssvg"
+ nethtml "golang.org/x/net/html"
+)
+
+const pgpSigEndTag = "-----END PGP SIGNATURE-----"
+
+func cleanupCommitMessage(msg string) string {
+ if i := strings.Index(msg, pgpSigEndTag); i >= 0 {
+ msg = msg[i+len(pgpSigEndTag):]
+ }
+ return msg
+}
+
+type breadcumbItem struct {
+ Name string
+ Path string
+}
+
+func pathBreadcumb(p string) []breadcumbItem {
+ var breadcumb []breadcumbItem
+ if p := strings.Trim(p, "/"); p != "" {
+ names := strings.Split(p, "/")
+ breadcumb = make([]breadcumbItem, len(names))
+ for i, name := range names {
+ breadcumb[i] = breadcumbItem{
+ Name: name,
+ Path: path.Join(names[:i+1]...),
+ }
+ }
+ }
+ return breadcumb
+}
+
+func formatDuration(d time.Duration) string {
+ if d < time.Minute {
+ return fmt.Sprintf("%d seconds", d / time.Second)
+ }
+ if d < time.Hour {
+ return fmt.Sprintf("%d minutes", d / time.Minute)
+ }
+ if d < 24 * time.Hour {
+ return fmt.Sprintf("%d hours", d / time.Hour)
+ }
+ return fmt.Sprintf("%d days", d / (24 * time.Hour))
+}
+
+func formatDate(t time.Time, d time.Duration) string {
+ if d < 365 * 24 * time.Hour { // 1 year
+ return t.Format("Jan 2")
+ }
+ return t.Format("Jan 2, 2006")
+}
+
+type templateRenderer struct {
+ templates *template.Template
+}
+
+func (r *templateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+ return r.templates.ExecuteTemplate(w, name, data)
+}
+
+func loadTemplateRenderer() (echo.Renderer, error) {
+ funcs := template.FuncMap{
+ "icon": func(name string) template.HTML {
+ var b bytes.Buffer
+ nethtml.Render(&b, octiconssvg.Icon(name))
+ return template.HTML(b.String())
+ },
+ "date": func(t time.Time) template.HTML {
+ d := time.Since(t)
+
+ var s string
+ if d >= 0 && d < 30 * 24 * time.Hour { // 1 month
+ s = formatDuration(d) + " ago"
+ } else {
+ s = "on " + formatDate(t, d)
+ }
+
+ full := t.Format("Jan 02, 2006, 15:04 -0700")
+ s = `<relative-time datetime="`+t.Format(time.RFC3339)+`" title="`+full+`">`+s+`</relative-time>`
+ return template.HTML(s)
+ },
+ }
+
+ t, err := template.New("").Funcs(funcs).ParseGlob("public/views/*.html")
+ if err != nil {
+ return nil, err
+ }
+
+ return &templateRenderer{t}, nil
+}
Process finished with exit code 0
Can you check again using go-git master? thanks! |
Please reopen if you can replicate with master |
running into this issue as well, was wondering if there was a solution. Go-git(e9247/4.1) |
Can you provide a repository and code? |
@mcuadros Here's a repo (https://github.com/jimdoescode/git-snitch/) where this fails consistently for me. I'm using go-git at commit c17627c which was the HEAD commit of the v4 branch I got off gopkg.
This script just diffs its own commits and spits out only the lines that changed. Oddly enough if you change the "from" hash passed to |
I can confirm that switching to the master branch does seem to fix the issue. |
When calling
Patch.String
on commit emersion/matcha@cb9c85f, this happens:The text was updated successfully, but these errors were encountered: