Skip to content

Commit 23e534d

Browse files
authored
Merge branch 'main' into dev/hezi/add-raw-diff-patch
2 parents a8ef5d5 + d5bbaee commit 23e534d

File tree

7 files changed

+99
-16
lines changed

7 files changed

+99
-16
lines changed

services/pull/merge.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"regexp"
1414
"strconv"
1515
"strings"
16+
"unicode"
1617

1718
"code.gitea.io/gitea/models/db"
1819
git_model "code.gitea.io/gitea/models/git"
@@ -161,6 +162,41 @@ func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr
161162
return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil)
162163
}
163164

165+
func AddCommitMessageTailer(message, tailerKey, tailerValue string) string {
166+
tailerLine := tailerKey + ": " + tailerValue
167+
message = strings.ReplaceAll(message, "\r\n", "\n")
168+
message = strings.ReplaceAll(message, "\r", "\n")
169+
if strings.Contains(message, "\n"+tailerLine+"\n") || strings.HasSuffix(message, "\n"+tailerLine) {
170+
return message
171+
}
172+
173+
if !strings.HasSuffix(message, "\n") {
174+
message += "\n"
175+
}
176+
pos1 := strings.LastIndexByte(message[:len(message)-1], '\n')
177+
pos2 := -1
178+
if pos1 != -1 {
179+
pos2 = strings.IndexByte(message[pos1:], ':')
180+
if pos2 != -1 {
181+
pos2 += pos1
182+
}
183+
}
184+
var lastLineKey string
185+
if pos1 != -1 && pos2 != -1 {
186+
lastLineKey = message[pos1+1 : pos2]
187+
}
188+
189+
isLikelyTailerLine := lastLineKey != "" && unicode.IsUpper(rune(lastLineKey[0])) && strings.Contains(message, "-")
190+
for i := 0; isLikelyTailerLine && i < len(lastLineKey); i++ {
191+
r := rune(lastLineKey[i])
192+
isLikelyTailerLine = unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-'
193+
}
194+
if !strings.HasSuffix(message, "\n\n") && !isLikelyTailerLine {
195+
message += "\n"
196+
}
197+
return message + tailerLine
198+
}
199+
164200
// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy
165201
type ErrInvalidMergeStyle struct {
166202
ID int64

services/pull/merge_squash.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package pull
55

66
import (
77
"fmt"
8-
"strings"
98

109
repo_model "code.gitea.io/gitea/models/repo"
1110
user_model "code.gitea.io/gitea/models/user"
@@ -66,10 +65,8 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error {
6665

6766
if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() {
6867
// add trailer
69-
if !strings.Contains(message, "Co-authored-by: "+sig.String()) {
70-
message += "\nCo-authored-by: " + sig.String()
71-
}
72-
message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String())
68+
message = AddCommitMessageTailer(message, "Co-authored-by", sig.String())
69+
message = AddCommitMessageTailer(message, "Co-committed-by", sig.String()) // FIXME: this one should be removed, it is not really used or widely used
7370
}
7471
cmdCommit := git.NewCommand("commit").
7572
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email).

services/pull/merge_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,28 @@ func Test_expandDefaultMergeMessage(t *testing.T) {
6565
})
6666
}
6767
}
68+
69+
func TestAddCommitMessageTailer(t *testing.T) {
70+
// add tailer for empty message
71+
assert.Equal(t, "\n\nTest-tailer: TestValue", AddCommitMessageTailer("", "Test-tailer", "TestValue"))
72+
73+
// add tailer for message without newlines
74+
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title", "Test-tailer", "TestValue"))
75+
assert.Equal(t, "title\n\nNot tailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nNot tailer: xxx", "Test-tailer", "TestValue"))
76+
assert.Equal(t, "title\n\nNotTailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nNotTailer: xxx", "Test-tailer", "TestValue"))
77+
assert.Equal(t, "title\n\nnot-tailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nnot-tailer: xxx", "Test-tailer", "TestValue"))
78+
79+
// add tailer for message with one EOL
80+
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n", "Test-tailer", "TestValue"))
81+
82+
// add tailer for message with two EOLs
83+
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\n", "Test-tailer", "TestValue"))
84+
85+
// add tailer for message with existing tailer (won't duplicate)
86+
assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nTest-tailer: TestValue", "Test-tailer", "TestValue"))
87+
assert.Equal(t, "title\n\nTest-tailer: TestValue\n", AddCommitMessageTailer("title\n\nTest-tailer: TestValue\n", "Test-tailer", "TestValue"))
88+
89+
// add tailer for message with existing tailer and different value (will append)
90+
assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTailer("title\n\nTest-tailer: v1", "Test-tailer", "v2"))
91+
assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTailer("title\n\nTest-tailer: v1\n", "Test-tailer", "v2"))
92+
}

templates/repo/issue/search.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<input type="hidden" name="project" value="{{$.ProjectID}}">
99
<input type="hidden" name="assignee" value="{{$.AssigneeID}}">
1010
<input type="hidden" name="poster" value="{{$.PosterUsername}}">
11+
<input type="hidden" name="sort" value="{{$.SortType}}">
1112
{{end}}
1213
{{template "shared/search/input" dict "Value" .Keyword}}
1314
{{if .PageIsIssueList}}

web_src/css/repo/file-view.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
.file-view tr.active {
1+
.file-view tr.active .lines-num,
2+
.file-view tr.active .lines-escape,
3+
.file-view tr.active .lines-code {
24
background: var(--color-highlight-bg);
35
}
46

7+
/* set correct border radius on the last active lines, to avoid border overflow */
58
.file-view tr.active:last-of-type .lines-code {
69
border-bottom-right-radius: var(--border-radius);
710
}
@@ -10,6 +13,7 @@
1013
position: relative;
1114
}
1215

16+
/* add a darker "handler" at the beginning of the active line */
1317
.file-view tr.active .lines-num::before {
1418
content: "";
1519
position: absolute;

web_src/js/features/repo-code.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,15 @@ function showLineButton() {
110110
}
111111

112112
export function initRepoCodeView() {
113-
if (!document.querySelector('.code-view .lines-num')) return;
113+
// When viewing a file or blame, there is always a ".file-view" element,
114+
// but the ".code-view" class is only present when viewing the "code" of a file; it is not present when viewing a PDF file.
115+
// 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.)
116+
// the "code-view" related event listeners should always be added when the current page contains ".file-view" element.
117+
if (!document.querySelector('.repo-view-container .file-view')) return;
114118

119+
// "file code view" and "blame" pages need this "line number button" feature
115120
let selRangeStart: string;
116-
addDelegatedEventListener(document, 'click', '.lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
121+
addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
117122
if (!selRangeStart || !e.shiftKey) {
118123
selRangeStart = el.getAttribute('id');
119124
selectRange(selRangeStart);
@@ -125,12 +130,14 @@ export function initRepoCodeView() {
125130
showLineButton();
126131
});
127132

133+
// apply the selected range from the URL hash
128134
const onHashChange = () => {
129135
if (!window.location.hash) return;
136+
if (!document.querySelector('.code-view .lines-num')) return;
130137
const range = window.location.hash.substring(1);
131138
const first = selectRange(range);
132139
if (first) {
133-
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
140+
// set scrollRestoration to 'manual' when there is a hash in the URL, so that the scroll position will not be remembered after refreshing
134141
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
135142
first.scrollIntoView({block: 'start'});
136143
showLineButton();

web_src/js/modules/tippy.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
4040
}
4141
}
4242
visibleInstances.add(instance);
43+
target.setAttribute('aria-controls', instance.popper.id);
4344
return onShow?.(instance);
4445
},
4546
arrow: arrow ?? (theme === 'bare' ? false : arrowSvg),
@@ -180,13 +181,25 @@ export function initGlobalTooltips(): void {
180181
}
181182

182183
export function showTemporaryTooltip(target: Element, content: Content): void {
183-
// if the target is inside a dropdown, the menu will be hidden soon
184-
// so display the tooltip on the dropdown instead
185-
target = target.closest('.ui.dropdown') || target;
186-
const tippy = target._tippy ?? attachTooltip(target, content);
187-
tippy.setContent(content);
188-
if (!tippy.state.isShown) tippy.show();
189-
tippy.setProps({
184+
// if the target is inside a dropdown or tippy popup, the menu will be hidden soon
185+
// so display the tooltip on the "aria-controls" element or dropdown instead
186+
let refClientRect: DOMRect;
187+
const popupTippyId = target.closest(`[data-tippy-root]`)?.id;
188+
if (popupTippyId) {
189+
// for example, the "Copy Permalink" button in the "File View" page for the selected lines
190+
target = document.body;
191+
refClientRect = document.querySelector(`[aria-controls="${CSS.escape(popupTippyId)}"]`)?.getBoundingClientRect();
192+
refClientRect = refClientRect ?? new DOMRect(0, 0, 0, 0); // fallback to empty rect if not found, tippy doesn't accept null
193+
} else {
194+
// for example, the "Copy Link" button in the issue header dropdown menu
195+
target = target.closest('.ui.dropdown') ?? target;
196+
refClientRect = target.getBoundingClientRect();
197+
}
198+
const tooltipTippy = target._tippy ?? attachTooltip(target, content);
199+
tooltipTippy.setContent(content);
200+
tooltipTippy.setProps({getReferenceClientRect: () => refClientRect});
201+
if (!tooltipTippy.state.isShown) tooltipTippy.show();
202+
tooltipTippy.setProps({
190203
onHidden: (tippy) => {
191204
// reset the default tooltip content, if no default, then this temporary tooltip could be destroyed
192205
if (!attachTooltip(target)) {

0 commit comments

Comments
 (0)