mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	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 c0951fe0e3.
* 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:
		
				
					committed by
					
						 Lauris BH
						Lauris BH
					
				
			
			
				
	
			
			
			
						parent
						
							d5a98a2969
						
					
				
				
					commit
					a98e085031
				
			
							
								
								
									
										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<01><>M | ||||||
|  | <EFBFBD>0F]<5D><14><>B<EFBFBD>&&m"<22>@\<5C>Of<4F>6<EFBFBD>HG<48><47><EFBFBD><EFBFBD> | ||||||
|  | ~˷x<CBB7><78>y<1C><><EFBFBD><EFBFBD> <20><19>?[<5B><><EFBFBD><EFBFBD>B<EFBFBD>& | ||||||
|  | H<b<>yߙNGt<47><74>ڨ<EFBFBD><DAA8>~.<2E>"<22>1x<>Ix`<60><><EFBFBD><EFBFBD><EFBFBD>&=㚸,}<7D><>{<7B>X<EFBFBD><58><EFBFBD><EFBFBD><EFBFBD>	<09>p<><70>)<29><><EFBFBD>j<7F>}^ 1AZ<41><5A><0E><>3<EFBFBD>,<2C><><01><>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 New Issue
	
	Block a user