mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into dev/hezi/add-raw-diff-patch
commit
23e534d723
|
@ -13,6 +13,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
@ -161,6 +162,41 @@ func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr
|
||||||
return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil)
|
return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddCommitMessageTailer(message, tailerKey, tailerValue string) string {
|
||||||
|
tailerLine := tailerKey + ": " + tailerValue
|
||||||
|
message = strings.ReplaceAll(message, "\r\n", "\n")
|
||||||
|
message = strings.ReplaceAll(message, "\r", "\n")
|
||||||
|
if strings.Contains(message, "\n"+tailerLine+"\n") || strings.HasSuffix(message, "\n"+tailerLine) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(message, "\n") {
|
||||||
|
message += "\n"
|
||||||
|
}
|
||||||
|
pos1 := strings.LastIndexByte(message[:len(message)-1], '\n')
|
||||||
|
pos2 := -1
|
||||||
|
if pos1 != -1 {
|
||||||
|
pos2 = strings.IndexByte(message[pos1:], ':')
|
||||||
|
if pos2 != -1 {
|
||||||
|
pos2 += pos1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var lastLineKey string
|
||||||
|
if pos1 != -1 && pos2 != -1 {
|
||||||
|
lastLineKey = message[pos1+1 : pos2]
|
||||||
|
}
|
||||||
|
|
||||||
|
isLikelyTailerLine := lastLineKey != "" && unicode.IsUpper(rune(lastLineKey[0])) && strings.Contains(message, "-")
|
||||||
|
for i := 0; isLikelyTailerLine && i < len(lastLineKey); i++ {
|
||||||
|
r := rune(lastLineKey[i])
|
||||||
|
isLikelyTailerLine = unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-'
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(message, "\n\n") && !isLikelyTailerLine {
|
||||||
|
message += "\n"
|
||||||
|
}
|
||||||
|
return message + tailerLine
|
||||||
|
}
|
||||||
|
|
||||||
// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy
|
// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy
|
||||||
type ErrInvalidMergeStyle struct {
|
type ErrInvalidMergeStyle struct {
|
||||||
ID int64
|
ID int64
|
||||||
|
|
|
@ -5,7 +5,6 @@ package pull
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -66,10 +65,8 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error {
|
||||||
|
|
||||||
if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() {
|
if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() {
|
||||||
// add trailer
|
// add trailer
|
||||||
if !strings.Contains(message, "Co-authored-by: "+sig.String()) {
|
message = AddCommitMessageTailer(message, "Co-authored-by", sig.String())
|
||||||
message += "\nCo-authored-by: " + sig.String()
|
message = AddCommitMessageTailer(message, "Co-committed-by", sig.String()) // FIXME: this one should be removed, it is not really used or widely used
|
||||||
}
|
|
||||||
message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String())
|
|
||||||
}
|
}
|
||||||
cmdCommit := git.NewCommand("commit").
|
cmdCommit := git.NewCommand("commit").
|
||||||
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email).
|
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email).
|
||||||
|
|
|
@ -65,3 +65,28 @@ func Test_expandDefaultMergeMessage(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddCommitMessageTailer(t *testing.T) {
|
||||||
|
// add tailer for empty message
|
||||||
|
assert.Equal(t, "\n\nTest-tailer: TestValue", AddCommitMessageTailer("", "Test-tailer", "TestValue"))
|
||||||
|
|
||||||
|
// add tailer for message without newlines
|
||||||
|
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title", "Test-tailer", "TestValue"))
|
||||||
|
assert.Equal(t, "title\n\nNot tailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nNot tailer: xxx", "Test-tailer", "TestValue"))
|
||||||
|
assert.Equal(t, "title\n\nNotTailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nNotTailer: xxx", "Test-tailer", "TestValue"))
|
||||||
|
assert.Equal(t, "title\n\nnot-tailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nnot-tailer: xxx", "Test-tailer", "TestValue"))
|
||||||
|
|
||||||
|
// add tailer for message with one EOL
|
||||||
|
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n", "Test-tailer", "TestValue"))
|
||||||
|
|
||||||
|
// add tailer for message with two EOLs
|
||||||
|
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\n", "Test-tailer", "TestValue"))
|
||||||
|
|
||||||
|
// add tailer for message with existing tailer (won't duplicate)
|
||||||
|
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nTest-tailer: TestValue", "Test-tailer", "TestValue"))
|
||||||
|
assert.Equal(t, "title\n\nTest-tailer: TestValue\n", AddCommitMessageTailer("title\n\nTest-tailer: TestValue\n", "Test-tailer", "TestValue"))
|
||||||
|
|
||||||
|
// add tailer for message with existing tailer and different value (will append)
|
||||||
|
assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTailer("title\n\nTest-tailer: v1", "Test-tailer", "v2"))
|
||||||
|
assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTailer("title\n\nTest-tailer: v1\n", "Test-tailer", "v2"))
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<input type="hidden" name="project" value="{{$.ProjectID}}">
|
<input type="hidden" name="project" value="{{$.ProjectID}}">
|
||||||
<input type="hidden" name="assignee" value="{{$.AssigneeID}}">
|
<input type="hidden" name="assignee" value="{{$.AssigneeID}}">
|
||||||
<input type="hidden" name="poster" value="{{$.PosterUsername}}">
|
<input type="hidden" name="poster" value="{{$.PosterUsername}}">
|
||||||
|
<input type="hidden" name="sort" value="{{$.SortType}}">
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "shared/search/input" dict "Value" .Keyword}}
|
{{template "shared/search/input" dict "Value" .Keyword}}
|
||||||
{{if .PageIsIssueList}}
|
{{if .PageIsIssueList}}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
.file-view tr.active {
|
.file-view tr.active .lines-num,
|
||||||
|
.file-view tr.active .lines-escape,
|
||||||
|
.file-view tr.active .lines-code {
|
||||||
background: var(--color-highlight-bg);
|
background: var(--color-highlight-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* set correct border radius on the last active lines, to avoid border overflow */
|
||||||
.file-view tr.active:last-of-type .lines-code {
|
.file-view tr.active:last-of-type .lines-code {
|
||||||
border-bottom-right-radius: var(--border-radius);
|
border-bottom-right-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +13,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* add a darker "handler" at the beginning of the active line */
|
||||||
.file-view tr.active .lines-num::before {
|
.file-view tr.active .lines-num::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -110,10 +110,15 @@ function showLineButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoCodeView() {
|
export function initRepoCodeView() {
|
||||||
if (!document.querySelector('.code-view .lines-num')) return;
|
// When viewing a file or blame, there is always a ".file-view" element,
|
||||||
|
// but the ".code-view" class is only present when viewing the "code" of a file; it is not present when viewing a PDF file.
|
||||||
|
// Since the ".file-view" will be dynamically reloaded when navigating via the left file tree (eg: view a PDF file, then view a source code file, etc.)
|
||||||
|
// the "code-view" related event listeners should always be added when the current page contains ".file-view" element.
|
||||||
|
if (!document.querySelector('.repo-view-container .file-view')) return;
|
||||||
|
|
||||||
|
// "file code view" and "blame" pages need this "line number button" feature
|
||||||
let selRangeStart: string;
|
let selRangeStart: string;
|
||||||
addDelegatedEventListener(document, 'click', '.lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
|
addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
|
||||||
if (!selRangeStart || !e.shiftKey) {
|
if (!selRangeStart || !e.shiftKey) {
|
||||||
selRangeStart = el.getAttribute('id');
|
selRangeStart = el.getAttribute('id');
|
||||||
selectRange(selRangeStart);
|
selectRange(selRangeStart);
|
||||||
|
@ -125,12 +130,14 @@ export function initRepoCodeView() {
|
||||||
showLineButton();
|
showLineButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// apply the selected range from the URL hash
|
||||||
const onHashChange = () => {
|
const onHashChange = () => {
|
||||||
if (!window.location.hash) return;
|
if (!window.location.hash) return;
|
||||||
|
if (!document.querySelector('.code-view .lines-num')) return;
|
||||||
const range = window.location.hash.substring(1);
|
const range = window.location.hash.substring(1);
|
||||||
const first = selectRange(range);
|
const first = selectRange(range);
|
||||||
if (first) {
|
if (first) {
|
||||||
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
|
// set scrollRestoration to 'manual' when there is a hash in the URL, so that the scroll position will not be remembered after refreshing
|
||||||
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
|
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
|
||||||
first.scrollIntoView({block: 'start'});
|
first.scrollIntoView({block: 'start'});
|
||||||
showLineButton();
|
showLineButton();
|
||||||
|
|
|
@ -40,6 +40,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visibleInstances.add(instance);
|
visibleInstances.add(instance);
|
||||||
|
target.setAttribute('aria-controls', instance.popper.id);
|
||||||
return onShow?.(instance);
|
return onShow?.(instance);
|
||||||
},
|
},
|
||||||
arrow: arrow ?? (theme === 'bare' ? false : arrowSvg),
|
arrow: arrow ?? (theme === 'bare' ? false : arrowSvg),
|
||||||
|
@ -180,13 +181,25 @@ export function initGlobalTooltips(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showTemporaryTooltip(target: Element, content: Content): void {
|
export function showTemporaryTooltip(target: Element, content: Content): void {
|
||||||
// if the target is inside a dropdown, the menu will be hidden soon
|
// if the target is inside a dropdown or tippy popup, the menu will be hidden soon
|
||||||
// so display the tooltip on the dropdown instead
|
// so display the tooltip on the "aria-controls" element or dropdown instead
|
||||||
target = target.closest('.ui.dropdown') || target;
|
let refClientRect: DOMRect;
|
||||||
const tippy = target._tippy ?? attachTooltip(target, content);
|
const popupTippyId = target.closest(`[data-tippy-root]`)?.id;
|
||||||
tippy.setContent(content);
|
if (popupTippyId) {
|
||||||
if (!tippy.state.isShown) tippy.show();
|
// for example, the "Copy Permalink" button in the "File View" page for the selected lines
|
||||||
tippy.setProps({
|
target = document.body;
|
||||||
|
refClientRect = document.querySelector(`[aria-controls="${CSS.escape(popupTippyId)}"]`)?.getBoundingClientRect();
|
||||||
|
refClientRect = refClientRect ?? new DOMRect(0, 0, 0, 0); // fallback to empty rect if not found, tippy doesn't accept null
|
||||||
|
} else {
|
||||||
|
// for example, the "Copy Link" button in the issue header dropdown menu
|
||||||
|
target = target.closest('.ui.dropdown') ?? target;
|
||||||
|
refClientRect = target.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
const tooltipTippy = target._tippy ?? attachTooltip(target, content);
|
||||||
|
tooltipTippy.setContent(content);
|
||||||
|
tooltipTippy.setProps({getReferenceClientRect: () => refClientRect});
|
||||||
|
if (!tooltipTippy.state.isShown) tooltipTippy.show();
|
||||||
|
tooltipTippy.setProps({
|
||||||
onHidden: (tippy) => {
|
onHidden: (tippy) => {
|
||||||
// reset the default tooltip content, if no default, then this temporary tooltip could be destroyed
|
// reset the default tooltip content, if no default, then this temporary tooltip could be destroyed
|
||||||
if (!attachTooltip(target)) {
|
if (!attachTooltip(target)) {
|
||||||
|
|
Loading…
Reference in New Issue