* Properly generate ref URLs Tags used to not generate correct URLs (src/branch/tags/1.0.0 instead of src/tags/1.0.0). Also cleans up some code around it with the created helper functions. * Fix formatting and create migration * Add copyright head to utils_test * Use a raw query for the ref migration * Remove semicolon * Quote column and table names in migration SQL * Change || to CONCAT, since MSSQL does not support || * Make migration engine aware * Add missing import * Move ref EndName and URL to the issue service * Fix tests * Add test for commit refs * Update issue.go * Use the right command for building JavaScript bundles * Prepare for merge * Check for refs/* before prepending in migration * Update services/issue/issue_test.go * Update modules/git/utils_test.go Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: techknowlogick <matti@mdranta.net>tags/v1.13.0-dev
NewMigration("Add Branch Protection Block Outdated Branch", addBlockOnOutdatedBranch), | NewMigration("Add Branch Protection Block Outdated Branch", addBlockOnOutdatedBranch), | ||||
// v138 -> v139 | // v138 -> v139 | ||||
NewMigration("Add ResolveDoerID to Comment table", addResolveDoerIDCommentColumn), | NewMigration("Add ResolveDoerID to Comment table", addResolveDoerIDCommentColumn), | ||||
// v139 -> v140 | |||||
NewMigration("prepend refs/heads/ to issue refs", prependRefsHeadsToIssueRefs), | |||||
} | } | ||||
// GetCurrentDBVersion returns the current db version | // GetCurrentDBVersion returns the current db version |
// 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 migrations | |||||
import ( | |||||
"code.gitea.io/gitea/modules/setting" | |||||
"xorm.io/xorm" | |||||
) | |||||
func prependRefsHeadsToIssueRefs(x *xorm.Engine) error { | |||||
var query string | |||||
switch { | |||||
case setting.Database.UseMSSQL: | |||||
query = "UPDATE `issue` SET `ref` = 'refs/heads/' + `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'" | |||||
default: | |||||
query = "UPDATE `issue` SET `ref` = 'refs/heads/' || `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'" | |||||
} | |||||
_, err := x.Exec(query) | |||||
return err | |||||
} |
return refStr | return refStr | ||||
} | } | ||||
// RefURL returns the absolute URL for a ref in a repository | |||||
func RefURL(repoURL, ref string) string { | |||||
refName := RefEndName(ref) | |||||
switch { | |||||
case strings.HasPrefix(ref, BranchPrefix): | |||||
return repoURL + "/src/branch/" + refName | |||||
case strings.HasPrefix(ref, TagPrefix): | |||||
return repoURL + "/src/tag/" + refName | |||||
default: | |||||
return repoURL + "/src/commit/" + refName | |||||
} | |||||
} | |||||
// SplitRefName splits a full refname to reftype and simple refname | // SplitRefName splits a full refname to reftype and simple refname | ||||
func SplitRefName(refStr string) (string, string) { | func SplitRefName(refStr string) (string, string) { | ||||
if strings.HasPrefix(refStr, BranchPrefix) { | if strings.HasPrefix(refStr, BranchPrefix) { |
// Copyright 2020 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 ( | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
) | |||||
func TestRefEndName(t *testing.T) { | |||||
// Test branch names (with and without slash). | |||||
assert.Equal(t, "foo", RefEndName("refs/heads/foo")) | |||||
assert.Equal(t, "feature/foo", RefEndName("refs/heads/feature/foo")) | |||||
// Test tag names (with and without slash). | |||||
assert.Equal(t, "foo", RefEndName("refs/tags/foo")) | |||||
assert.Equal(t, "release/foo", RefEndName("refs/tags/release/foo")) | |||||
// Test commit hashes. | |||||
assert.Equal(t, "c0ffee", RefEndName("c0ffee")) | |||||
} | |||||
func TestRefURL(t *testing.T) { | |||||
repoURL := "/user/repo" | |||||
assert.Equal(t, repoURL+"/src/branch/foo", RefURL(repoURL, "refs/heads/foo")) | |||||
assert.Equal(t, repoURL+"/src/tag/foo", RefURL(repoURL, "refs/tags/foo")) | |||||
assert.Equal(t, repoURL+"/src/commit/c0ffee", RefURL(repoURL, "c0ffee")) | |||||
} |
// SlackLinkToRef slack-formatter link to a repo ref | // SlackLinkToRef slack-formatter link to a repo ref | ||||
func SlackLinkToRef(repoURL, ref string) string { | func SlackLinkToRef(repoURL, ref string) string { | ||||
url := git.RefURL(repoURL, ref) | |||||
refName := git.RefEndName(ref) | refName := git.RefEndName(ref) | ||||
switch { | |||||
case strings.HasPrefix(ref, git.BranchPrefix): | |||||
return SlackLinkFormatter(repoURL+"/src/branch/"+refName, refName) | |||||
case strings.HasPrefix(ref, git.TagPrefix): | |||||
return SlackLinkFormatter(repoURL+"/src/tag/"+refName, refName) | |||||
default: | |||||
return SlackLinkFormatter(repoURL+"/src/commit/"+refName, refName) | |||||
} | |||||
return SlackLinkFormatter(url, refName) | |||||
} | } | ||||
func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) { | func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) { |
assigneeID = 0 // Reset ID to prevent unexpected selection of assignee. | assigneeID = 0 // Reset ID to prevent unexpected selection of assignee. | ||||
} | } | ||||
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = | |||||
issue_service.GetRefEndNamesAndURLs(issues, ctx.Repo.RepoLink) | |||||
ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 { | ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 { | ||||
counts, ok := approvalCounts[issueID] | counts, ok := approvalCounts[issueID] | ||||
if !ok || len(counts) == 0 { | if !ok || len(counts) == 0 { | ||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) | ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) | ||||
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin) | ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin) | ||||
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons | ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons | ||||
ctx.Data["RefEndName"] = git.RefEndName(issue.Ref) | |||||
ctx.HTML(200, tplIssueView) | ctx.HTML(200, tplIssueView) | ||||
} | } | ||||
"code.gitea.io/gitea/modules/markup/markdown" | "code.gitea.io/gitea/modules/markup/markdown" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"code.gitea.io/gitea/modules/util" | "code.gitea.io/gitea/modules/util" | ||||
issue_service "code.gitea.io/gitea/services/issue" | |||||
pull_service "code.gitea.io/gitea/services/pull" | pull_service "code.gitea.io/gitea/services/pull" | ||||
"github.com/keybase/go-crypto/openpgp" | "github.com/keybase/go-crypto/openpgp" | ||||
totalIssues = int(allIssueStats.ClosedCount) | totalIssues = int(allIssueStats.ClosedCount) | ||||
} | } | ||||
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = | |||||
issue_service.GetRefEndNamesAndURLs(issues, ctx.Query("RepoLink")) | |||||
ctx.Data["Issues"] = issues | ctx.Data["Issues"] = issues | ||||
ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 { | ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 { | ||||
counts, ok := approvalCounts[issueID] | counts, ok := approvalCounts[issueID] |
import ( | import ( | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/git" | |||||
"code.gitea.io/gitea/modules/notification" | "code.gitea.io/gitea/modules/notification" | ||||
"code.gitea.io/gitea/modules/util" | |||||
) | ) | ||||
// NewIssue creates new issue with labels for repository. | // NewIssue creates new issue with labels for repository. | ||||
return nil | return nil | ||||
} | } | ||||
// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name) | |||||
// and their respective URLs. | |||||
func GetRefEndNamesAndURLs(issues []*models.Issue, repoLink string) (map[int64]string, map[int64]string) { | |||||
var issueRefEndNames = make(map[int64]string, len(issues)) | |||||
var issueRefURLs = make(map[int64]string, len(issues)) | |||||
for _, issue := range issues { | |||||
if issue.Ref != "" { | |||||
issueRefEndNames[issue.ID] = git.RefEndName(issue.Ref) | |||||
issueRefURLs[issue.ID] = git.RefURL(repoLink, util.PathEscapeSegments(issue.Ref)) | |||||
} | |||||
} | |||||
return issueRefEndNames, issueRefURLs | |||||
} |
// Copyright 2020 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 issue | |||||
import ( | |||||
"testing" | |||||
"code.gitea.io/gitea/models" | |||||
"github.com/stretchr/testify/assert" | |||||
) | |||||
func TestGetRefEndNamesAndURLs(t *testing.T) { | |||||
issues := []*models.Issue{ | |||||
{ID: 1, Ref: "refs/heads/branch1"}, | |||||
{ID: 2, Ref: "refs/tags/tag1"}, | |||||
{ID: 3, Ref: "c0ffee"}, | |||||
} | |||||
repoLink := "/foo/bar" | |||||
endNames, urls := GetRefEndNamesAndURLs(issues, repoLink) | |||||
assert.EqualValues(t, map[int64]string{1: "branch1", 2: "tag1", 3: "c0ffee"}, endNames) | |||||
assert.EqualValues(t, map[int64]string{ | |||||
1: repoLink + "/src/branch/branch1", | |||||
2: repoLink + "/src/tag/tag1", | |||||
3: repoLink + "/src/commit/c0ffee", | |||||
}, urls) | |||||
} |
<input id="ref_selector" name="ref" type="hidden" value="{{.Issue.Ref}}"> | <input id="ref_selector" name="ref" type="hidden" value="{{.Issue.Ref}}"> | ||||
<div class="ui {{if .ReadOnly}}disabled{{end}} floating filter select-branch dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}"> | <div class="ui {{if .ReadOnly}}disabled{{end}} floating filter select-branch dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}"> | ||||
<div class="ui basic small button"> | <div class="ui basic small button"> | ||||
<span class="text branch-name">{{if .Issue.Ref}}{{.Issue.Ref}}{{else}}{{.i18n.Tr "repo.issues.no_ref"}}{{end}}</span> | |||||
<span class="text branch-name">{{if .Issue.Ref}}{{$.RefEndName}}{{else}}{{.i18n.Tr "repo.issues.no_ref"}}{{end}}</span> | |||||
<i class="dropdown icon"></i> | <i class="dropdown icon"></i> | ||||
</div> | </div> | ||||
<div class="menu"> | <div class="menu"> | ||||
</div> | </div> | ||||
<div id="branch-list" class="scrolling menu reference-list-menu"> | <div id="branch-list" class="scrolling menu reference-list-menu"> | ||||
{{range .Branches}} | {{range .Branches}} | ||||
<div class="item" data-id="{{.}}" data-id-selector="#ref_selector">{{.}}</div> | |||||
<div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector">{{.}}</div> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
<div id="tag-list" class="scrolling menu reference-list-menu" style="display: none"> | <div id="tag-list" class="scrolling menu reference-list-menu" style="display: none"> | ||||
{{range .Tags}} | {{range .Tags}} | ||||
<div class="item" data-id="{{.}}" data-id-selector="#ref_selector">{{.}}</div> | |||||
<div class="item" data-id="refs/tags/{{.}}" data-name="tags/{{.}}" data-id-selector="#ref_selector">{{.}}</div> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> |
</a> | </a> | ||||
{{end}} | {{end}} | ||||
{{if .Ref}} | {{if .Ref}} | ||||
<a class="ref" href="{{$.RepoLink}}/src/branch/{{.Ref | PathEscapeSegments}}"> | |||||
{{svg "octicon-git-branch" 16}} {{.Ref}} | |||||
<a class="ref" href="{{index $.IssueRefURLs .ID}}"> | |||||
{{svg "octicon-git-branch" 16}} {{index $.IssueRefEndNames .ID}} | |||||
</a> | </a> | ||||
{{end}} | {{end}} | ||||
{{$tasks := .GetTasks}} | {{$tasks := .GetTasks}} |
{{end}} | {{end}} | ||||
{{if .Ref}} | {{if .Ref}} | ||||
<a class="ref" href="{{$.RepoLink}}/src/branch/{{.Ref}}"> | |||||
{{svg "octicon-git-branch" 16}} {{.Ref}} | |||||
<a class="ref" href="{{index $.IssueRefURLs .ID}}"> | |||||
{{svg "octicon-git-branch" 16}} {{index $.IssueRefEndNames .ID}} | |||||
</a> | </a> | ||||
{{end}} | {{end}} | ||||
{{$tasks := .GetTasks}} | {{$tasks := .GetTasks}} |
</a> | </a> | ||||
{{end}} | {{end}} | ||||
{{if .Ref}} | {{if .Ref}} | ||||
<a class="ref" href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/src/branch/{{.Ref}}"> | |||||
{{svg "octicon-git-branch" 16}} {{.Ref}} | |||||
<a class="ref" href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}{{index $.IssueRefURLs .ID}}"> | |||||
{{svg "octicon-git-branch" 16}} {{index $.IssueRefEndNames .ID}} | |||||
</a> | </a> | ||||
{{end}} | {{end}} | ||||
{{range .Assignees}} | {{range .Assignees}} |
function initBranchSelector() { | function initBranchSelector() { | ||||
const $selectBranch = $('.ui.select-branch'); | const $selectBranch = $('.ui.select-branch'); | ||||
const $branchMenu = $selectBranch.find('.reference-list-menu'); | const $branchMenu = $selectBranch.find('.reference-list-menu'); | ||||
$branchMenu.find('.item:not(.no-select)').on('click', function () { | |||||
const selectedValue = $(this).data('id'); | |||||
$($(this).data('id-selector')).val(selectedValue); | |||||
$selectBranch.find('.ui .branch-name').text(selectedValue); | |||||
$branchMenu.find('.item:not(.no-select)').click(function () { | |||||
$($(this).data('id-selector')).val($(this).data('id')); | |||||
$selectBranch.find('.ui .branch-name').text($(this).data('name')); | |||||
}); | }); | ||||
$selectBranch.find('.reference.column').on('click', function () { | $selectBranch.find('.reference.column').on('click', function () { | ||||
$selectBranch.find('.scrolling.reference-list-menu').css('display', 'none'); | $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none'); |