Show git-notes (#6984)
* Show git-notes * Make git-notes heading text localizable * Refactor git-notes data fetching to a separate function * Display the author and time of git notes * Move note bubble inside the commit bubble * Revert "Move note bubble inside the commit bubble" This reverts commit c0951fe0e3b4dea38064515546b1825c1bcf19e1. * Add test for git-notes * testing ui * Polish CSS * Apply suggestions from code review Co-Authored-By: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
d5a98a2969
commit
a98e085031
13 changed files with 146 additions and 1 deletions
60
modules/git/notes.go
Normal file
60
modules/git/notes.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotesRef is the git ref where Gitea will look for git-notes data.
|
||||||
|
// The value ("refs/notes/commits") is the default ref used by git-notes.
|
||||||
|
const NotesRef = "refs/notes/commits"
|
||||||
|
|
||||||
|
// Note stores information about a note created using git-notes.
|
||||||
|
type Note struct {
|
||||||
|
Message []byte
|
||||||
|
Commit *Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNote retrieves the git-notes data for a given commit.
|
||||||
|
func GetNote(repo *Repository, commitID string, note *Note) error {
|
||||||
|
notes, err := repo.GetCommit(NotesRef)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := notes.GetTreeEntryByPath(commitID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
blob := entry.Blob()
|
||||||
|
dataRc, err := blob.DataAsync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer dataRc.Close()
|
||||||
|
d, err := ioutil.ReadAll(dataRc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
note.Message = d
|
||||||
|
|
||||||
|
commit, err := repo.gogitRepo.CommitObject(plumbing.Hash(notes.ID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCommits, err := getLastCommitForPaths(commit, "", []string{commitID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
note.Commit = convertCommit(lastCommits[commitID])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
24
modules/git/notes_test.go
Normal file
24
modules/git/notes_test.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetNotes(t *testing.T) {
|
||||||
|
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||||
|
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
note := Note{}
|
||||||
|
err = GetNote(bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []byte("Note contents\n"), note.Message)
|
||||||
|
assert.Equal(t, "Vladimir Panteleev", note.Commit.Author.Name)
|
||||||
|
}
|
|
@ -19,13 +19,14 @@ func TestRepository_GetRefs(t *testing.T) {
|
||||||
refs, err := bareRepo1.GetRefs()
|
refs, err := bareRepo1.GetRefs()
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, refs, 4)
|
assert.Len(t, refs, 5)
|
||||||
|
|
||||||
expectedRefs := []string{
|
expectedRefs := []string{
|
||||||
BranchPrefix + "branch1",
|
BranchPrefix + "branch1",
|
||||||
BranchPrefix + "branch2",
|
BranchPrefix + "branch2",
|
||||||
BranchPrefix + "master",
|
BranchPrefix + "master",
|
||||||
TagPrefix + "test",
|
TagPrefix + "test",
|
||||||
|
NotesRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,4 @@
|
||||||
|
x¥ŽM
|
||||||
|
Â0F]ç³ëB<C3AB>&&m"ž@\¹Of¦6ÐHG¥··ô
|
||||||
|
~Ë·xïÃy³€Ñþ …Œ?[—Œ¶èBÓ&
|
||||||
|
H<bÛyß™NGtåÚ¨ø–~.ð"å1xÄIx`þÀå•å&=㚸,}¤ù{šX® <C2AE>ó¶ <09>p¬·)ÜãÂjÔ}^ 1AZ¡ÚÀ´3¦,•ú½ÀI0
|
1
modules/git/tests/repos/repo1_bare/refs/notes/commits
Normal file
1
modules/git/tests/repos/repo1_bare/refs/notes/commits
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ca6b5ddf303169a72d2a2971acde4f6eea194e5c
|
|
@ -125,6 +125,7 @@ func NewFuncMap() []template.FuncMap {
|
||||||
"RenderCommitMessage": RenderCommitMessage,
|
"RenderCommitMessage": RenderCommitMessage,
|
||||||
"RenderCommitMessageLink": RenderCommitMessageLink,
|
"RenderCommitMessageLink": RenderCommitMessageLink,
|
||||||
"RenderCommitBody": RenderCommitBody,
|
"RenderCommitBody": RenderCommitBody,
|
||||||
|
"RenderNote": RenderNote,
|
||||||
"IsMultilineCommitMessage": IsMultilineCommitMessage,
|
"IsMultilineCommitMessage": IsMultilineCommitMessage,
|
||||||
"ThemeColorMetaTag": func() string {
|
"ThemeColorMetaTag": func() string {
|
||||||
return setting.UI.ThemeColorMetaTag
|
return setting.UI.ThemeColorMetaTag
|
||||||
|
@ -392,6 +393,17 @@ func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.H
|
||||||
return template.HTML(strings.Join(body[1:], "\n"))
|
return template.HTML(strings.Join(body[1:], "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenderNote renders the contents of a git-notes file as a commit message.
|
||||||
|
func RenderNote(msg, urlPrefix string, metas map[string]string) template.HTML {
|
||||||
|
cleanMsg := template.HTMLEscapeString(msg)
|
||||||
|
fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, "", metas)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("RenderNote: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return template.HTML(string(fullMessage))
|
||||||
|
}
|
||||||
|
|
||||||
// IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
|
// IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
|
||||||
func IsMultilineCommitMessage(msg string) bool {
|
func IsMultilineCommitMessage(msg string) bool {
|
||||||
return strings.Count(strings.TrimSpace(msg), "\n") >= 1
|
return strings.Count(strings.TrimSpace(msg), "\n") >= 1
|
||||||
|
|
|
@ -1314,6 +1314,7 @@ settings.unarchive.error = An error occured while trying to un-archive the repo.
|
||||||
diff.browse_source = Browse Source
|
diff.browse_source = Browse Source
|
||||||
diff.parent = parent
|
diff.parent = parent
|
||||||
diff.commit = commit
|
diff.commit = commit
|
||||||
|
diff.git-notes = Notes
|
||||||
diff.data_not_available = Diff Content Not Available
|
diff.data_not_available = Diff Content Not Available
|
||||||
diff.show_diff_stats = Show Diff Stats
|
diff.show_diff_stats = Show Diff Stats
|
||||||
diff.show_split_view = Split View
|
diff.show_split_view = Split View
|
||||||
|
|
|
@ -803,6 +803,8 @@ footer .ui.left,footer .ui.right{line-height:40px}
|
||||||
.stats-table .table-cell.tiny{height:.5em}
|
.stats-table .table-cell.tiny{height:.5em}
|
||||||
tbody.commit-list{vertical-align:baseline}
|
tbody.commit-list{vertical-align:baseline}
|
||||||
.commit-body{white-space:pre-wrap}
|
.commit-body{white-space:pre-wrap}
|
||||||
|
.git-notes.top{text-align:left}
|
||||||
|
.git-notes .commit-body{margin:0}
|
||||||
@media only screen and (max-width:767px){.ui.stackable.menu.mobile--margin-between-items>.item{margin-top:5px;margin-bottom:5px}
|
@media only screen and (max-width:767px){.ui.stackable.menu.mobile--margin-between-items>.item{margin-top:5px;margin-bottom:5px}
|
||||||
.ui.stackable.menu.mobile--no-negative-margins{margin-left:0;margin-right:0}
|
.ui.stackable.menu.mobile--no-negative-margins{margin-left:0;margin-right:0}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2219,6 +2219,15 @@ tbody.commit-list {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.git-notes {
|
||||||
|
&.top {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.commit-body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 767px) {
|
@media only screen and (max-width: 767px) {
|
||||||
.ui.stackable.menu {
|
.ui.stackable.menu {
|
||||||
&.mobile--margin-between-items > .item {
|
&.mobile--margin-between-items > .item {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -246,6 +247,15 @@ func Diff(ctx *context.Context) {
|
||||||
ctx.Data["Parents"] = parents
|
ctx.Data["Parents"] = parents
|
||||||
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
||||||
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", commitID)
|
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", commitID)
|
||||||
|
|
||||||
|
note := &git.Note{}
|
||||||
|
err = git.GetNote(ctx.Repo.GitRepo, commitID, note)
|
||||||
|
if err == nil {
|
||||||
|
ctx.Data["Note"] = string(templates.ToUTF8WithFallback(note.Message))
|
||||||
|
ctx.Data["NoteCommit"] = note.Commit
|
||||||
|
ctx.Data["NoteAuthor"] = models.ValidateCommitWithEmail(note.Commit)
|
||||||
|
}
|
||||||
|
|
||||||
if commit.ParentCount() > 0 {
|
if commit.ParentCount() > 0 {
|
||||||
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", parents[0])
|
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", parents[0])
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,27 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if .Note}}
|
||||||
|
<div class="ui top attached info segment message git-notes">
|
||||||
|
<i class="sticky note icon"></i>
|
||||||
|
{{.i18n.Tr "repo.diff.git-notes"}}:
|
||||||
|
{{if .NoteAuthor}}
|
||||||
|
<a href="{{.NoteAuthor.HomeLink}}">
|
||||||
|
{{if .NoteAuthor.FullName}}
|
||||||
|
<strong>{{.NoteAuthor.FullName}}</strong>
|
||||||
|
{{else}}
|
||||||
|
<strong>{{.NoteCommit.Author.Name}}</strong>
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
<strong>{{.NoteCommit.Author.Name}}</strong>
|
||||||
|
{{end}}
|
||||||
|
<span class="text grey" id="note-authored-time">{{TimeSince .NoteCommit.Author.When $.Lang}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="ui bottom attached info segment git-notes">
|
||||||
|
<pre class="commit-body">{{RenderNote .Note $.RepoLink $.Repository.ComposeMetas}}</pre>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{template "repo/diff/box" .}}
|
{{template "repo/diff/box" .}}
|
||||||
|
|
Reference in a new issue