mirror of
https://github.com/go-gitea/gitea.git
synced 2024-11-24 02:59:42 +08:00
Merge branch 'main' into feat-32257-add-comments-unchanged-lines-and-show
This commit is contained in:
commit
a8ace204ac
@ -642,7 +642,7 @@ rules:
|
|||||||
no-this-before-super: [2]
|
no-this-before-super: [2]
|
||||||
no-throw-literal: [2]
|
no-throw-literal: [2]
|
||||||
no-undef-init: [2]
|
no-undef-init: [2]
|
||||||
no-undef: [2, {typeof: true}]
|
no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes
|
||||||
no-undefined: [0]
|
no-undefined: [0]
|
||||||
no-underscore-dangle: [0]
|
no-underscore-dangle: [0]
|
||||||
no-unexpected-multiline: [2]
|
no-unexpected-multiline: [2]
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
poetry
|
poetry
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
go_1_22
|
|
||||||
gofumpt
|
gofumpt
|
||||||
sqlite
|
sqlite
|
||||||
];
|
];
|
||||||
|
@ -1,3 +1,22 @@
|
|||||||
|
-
|
||||||
|
id: 46
|
||||||
|
attempt: 3
|
||||||
|
runner_id: 1
|
||||||
|
status: 3 # 3 is the status code for "cancelled"
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 4
|
||||||
|
owner_id: 1
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4260c64a69a2cc1508825121b7b8394e48e00b1bf8718b2aaaaa
|
||||||
|
token_salt: eeeeeeee
|
||||||
|
token_last_eight: eeeeeeee
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 707
|
||||||
|
log_size: 90179
|
||||||
|
log_expired: 0
|
||||||
-
|
-
|
||||||
id: 47
|
id: 47
|
||||||
job_id: 192
|
job_id: 192
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
-
|
-
|
||||||
id: 1
|
id: 1
|
||||||
setting_key: 'picture.disable_gravatar'
|
setting_key: 'picture.disable_gravatar'
|
||||||
setting_value: 'false'
|
setting_value: 'true'
|
||||||
version: 1
|
version: 1
|
||||||
created: 1653533198
|
created: 1653533198
|
||||||
updated: 1653533198
|
updated: 1653533198
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar1
|
avatar: ""
|
||||||
avatar_email: user1@example.com
|
avatar_email: user1@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -60,8 +60,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar2
|
avatar: ""
|
||||||
avatar_email: user2@example.com
|
avatar_email: user2@example.com
|
||||||
|
# cause a random avatar to be generated when referenced for test purposes
|
||||||
use_custom_avatar: false
|
use_custom_avatar: false
|
||||||
num_followers: 2
|
num_followers: 2
|
||||||
num_following: 1
|
num_following: 1
|
||||||
@ -97,9 +98,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar3
|
avatar: ""
|
||||||
avatar_email: org3@example.com
|
avatar_email: org3@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -134,9 +135,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar4
|
avatar: ""
|
||||||
avatar_email: user4@example.com
|
avatar_email: user4@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -171,9 +172,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: false
|
allow_create_organization: false
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar5
|
avatar: ""
|
||||||
avatar_email: user5@example.com
|
avatar_email: user5@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -208,9 +209,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar6
|
avatar: ""
|
||||||
avatar_email: org6@example.com
|
avatar_email: org6@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -245,9 +246,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar7
|
avatar: ""
|
||||||
avatar_email: org7@example.com
|
avatar_email: org7@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -282,9 +283,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar8
|
avatar: ""
|
||||||
avatar_email: user8@example.com
|
avatar_email: user8@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 1
|
num_followers: 1
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -319,9 +320,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar9
|
avatar: ""
|
||||||
avatar_email: user9@example.com
|
avatar_email: user9@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -332,6 +333,7 @@
|
|||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
keep_activity_private: false
|
keep_activity_private: false
|
||||||
|
created_unix: 1730468968
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 10
|
id: 10
|
||||||
@ -356,9 +358,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar10
|
avatar: ""
|
||||||
avatar_email: user10@example.com
|
avatar_email: user10@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 2
|
num_stars: 2
|
||||||
@ -393,9 +395,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar11
|
avatar: ""
|
||||||
avatar_email: user11@example.com
|
avatar_email: user11@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -430,9 +432,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar12
|
avatar: ""
|
||||||
avatar_email: user12@example.com
|
avatar_email: user12@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -467,9 +469,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar13
|
avatar: ""
|
||||||
avatar_email: user13@example.com
|
avatar_email: user13@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -504,9 +506,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar14
|
avatar: ""
|
||||||
avatar_email: user13@example.com
|
avatar_email: user13@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -541,9 +543,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar15
|
avatar: ""
|
||||||
avatar_email: user15@example.com
|
avatar_email: user15@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -578,9 +580,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar16
|
avatar: ""
|
||||||
avatar_email: user16@example.com
|
avatar_email: user16@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -615,9 +617,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar17
|
avatar: ""
|
||||||
avatar_email: org17@example.com
|
avatar_email: org17@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -652,9 +654,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar18
|
avatar: ""
|
||||||
avatar_email: user18@example.com
|
avatar_email: user18@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -689,9 +691,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar19
|
avatar: ""
|
||||||
avatar_email: org19@example.com
|
avatar_email: org19@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -726,9 +728,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar20
|
avatar: ""
|
||||||
avatar_email: user20@example.com
|
avatar_email: user20@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -763,9 +765,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar21
|
avatar: ""
|
||||||
avatar_email: user21@example.com
|
avatar_email: user21@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -800,9 +802,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar22
|
avatar: ""
|
||||||
avatar_email: limited_org@example.com
|
avatar_email: limited_org@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -837,9 +839,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar23
|
avatar: ""
|
||||||
avatar_email: privated_org@example.com
|
avatar_email: privated_org@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -874,9 +876,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar24
|
avatar: ""
|
||||||
avatar_email: user24@example.com
|
avatar_email: user24@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -911,9 +913,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar25
|
avatar: ""
|
||||||
avatar_email: org25@example.com
|
avatar_email: org25@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -948,9 +950,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar26
|
avatar: ""
|
||||||
avatar_email: org26@example.com
|
avatar_email: org26@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -985,9 +987,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar27
|
avatar: ""
|
||||||
avatar_email: user27@example.com
|
avatar_email: user27@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1022,9 +1024,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar28
|
avatar: ""
|
||||||
avatar_email: user28@example.com
|
avatar_email: user28@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1059,9 +1061,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user29@example.com
|
avatar_email: user29@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1096,9 +1098,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user30@example.com
|
avatar_email: user30@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1133,9 +1135,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar31
|
avatar: ""
|
||||||
avatar_email: user31@example.com
|
avatar_email: user31@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1170,9 +1172,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar32
|
avatar: ""
|
||||||
avatar_email: user30@example.com
|
avatar_email: user30@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1207,9 +1209,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar33
|
avatar: ""
|
||||||
avatar_email: user33@example.com
|
avatar_email: user33@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 1
|
num_followers: 1
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1245,7 +1247,7 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: false
|
allow_create_organization: false
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar34
|
avatar: ""
|
||||||
avatar_email: user34@example.com
|
avatar_email: user34@example.com
|
||||||
use_custom_avatar: true
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
@ -1282,9 +1284,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar35
|
avatar: ""
|
||||||
avatar_email: private_org35@example.com
|
avatar_email: private_org35@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1319,9 +1321,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar22
|
avatar: ""
|
||||||
avatar_email: abcde@gitea.com
|
avatar_email: abcde@gitea.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1356,9 +1358,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: true
|
prohibit_login: true
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user37@example.com
|
avatar_email: user37@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1393,9 +1395,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar38
|
avatar: ""
|
||||||
avatar_email: user38@example.com
|
avatar_email: user38@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1430,9 +1432,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar39
|
avatar: ""
|
||||||
avatar_email: user39@example.com
|
avatar_email: user39@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1467,9 +1469,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar40
|
avatar: ""
|
||||||
avatar_email: user40@example.com
|
avatar_email: user40@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1504,9 +1506,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar41
|
avatar: ""
|
||||||
avatar_email: org41@example.com
|
avatar_email: org41@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1541,9 +1543,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar42
|
avatar: ""
|
||||||
avatar_email: org42@example.com
|
avatar_email: org42@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
|
@ -112,14 +112,12 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
rctx := markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithRepoFacade(issue.Repo).
|
||||||
Repo: issue.Repo,
|
WithLinks(markup.Links{Base: issue.Repo.Link()}).
|
||||||
Links: markup.Links{
|
WithMetas(issue.Repo.ComposeMetas(ctx))
|
||||||
Base: issue.Repo.Link(),
|
if comment.RenderedContent, err = markdown.RenderString(rctx,
|
||||||
},
|
comment.Content); err != nil {
|
||||||
Metas: issue.Repo.ComposeMetas(ctx),
|
|
||||||
}, comment.Content); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -617,10 +617,7 @@ func (repo *Repository) CanEnableEditor() bool {
|
|||||||
|
|
||||||
// DescriptionHTML does special handles to description and return HTML string.
|
// DescriptionHTML does special handles to description and return HTML string.
|
||||||
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
||||||
desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
|
desc, err := markup.RenderDescriptionHTML(markup.NewRenderContext(ctx), repo.Description)
|
||||||
Ctx: ctx,
|
|
||||||
// Don't use Metas to speedup requests
|
|
||||||
}, repo.Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
||||||
return template.HTML(markup.SanitizeDescription(repo.Description))
|
return template.HTML(markup.SanitizeDescription(repo.Description))
|
||||||
|
@ -48,19 +48,19 @@ const (
|
|||||||
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
||||||
|
|
||||||
// UserTypeOrganization defines an organization
|
// UserTypeOrganization defines an organization
|
||||||
UserTypeOrganization
|
UserTypeOrganization // 1
|
||||||
|
|
||||||
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
||||||
UserTypeUserReserved
|
UserTypeUserReserved // 2
|
||||||
|
|
||||||
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
||||||
UserTypeOrganizationReserved
|
UserTypeOrganizationReserved // 3
|
||||||
|
|
||||||
// UserTypeBot defines a bot user
|
// UserTypeBot defines a bot user
|
||||||
UserTypeBot
|
UserTypeBot // 4
|
||||||
|
|
||||||
// UserTypeRemoteUser defines a remote user for federated users
|
// UserTypeRemoteUser defines a remote user for federated users
|
||||||
UserTypeRemoteUser
|
UserTypeRemoteUser // 5
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -884,7 +884,13 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
|
|||||||
|
|
||||||
// GetInactiveUsers gets all inactive users
|
// GetInactiveUsers gets all inactive users
|
||||||
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
||||||
var cond builder.Cond = builder.Eq{"is_active": false}
|
cond := builder.And(
|
||||||
|
builder.Eq{"is_active": false},
|
||||||
|
builder.Or( // only plain user
|
||||||
|
builder.Eq{"`type`": UserTypeIndividual},
|
||||||
|
builder.Eq{"`type`": UserTypeUserReserved},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if olderThan > 0 {
|
if olderThan > 0 {
|
||||||
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
||||||
|
@ -588,3 +588,17 @@ func TestDisabledUserFeatures(t *testing.T) {
|
|||||||
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetInactiveUsers(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
// all inactive users
|
||||||
|
// user1's createdunix is 1730468968
|
||||||
|
users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 1)
|
||||||
|
interval := time.Now().Unix() - 1730468968 + 3600*24
|
||||||
|
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 0)
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
stdcsv "encoding/csv"
|
stdcsv "encoding/csv"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func CreateReaderAndDetermineDelimiter(ctx *markup.RenderContext, rd io.Reader)
|
|||||||
func determineDelimiter(ctx *markup.RenderContext, data []byte) rune {
|
func determineDelimiter(ctx *markup.RenderContext, data []byte) rune {
|
||||||
extension := ".csv"
|
extension := ".csv"
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
extension = strings.ToLower(filepath.Ext(ctx.RelativePath))
|
extension = strings.ToLower(path.Ext(ctx.RenderOptions.RelativePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
var delimiter rune
|
var delimiter rune
|
||||||
|
@ -5,13 +5,13 @@ package csv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
|
||||||
@ -231,10 +231,7 @@ John Doe john@doe.com This,note,had,a,lot,of,commas,to,test,delimiters`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for n, c := range cases {
|
for n, c := range cases {
|
||||||
delimiter := determineDelimiter(&markup.RenderContext{
|
delimiter := determineDelimiter(markup.NewRenderContext(context.Background()).WithRelativePath(c.filename), []byte(decodeSlashes(t, c.csv)))
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
RelativePath: c.filename,
|
|
||||||
}, []byte(decodeSlashes(t, c.csv)))
|
|
||||||
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
|
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -29,7 +28,7 @@ type Commit struct {
|
|||||||
Signature *CommitSignature
|
Signature *CommitSignature
|
||||||
|
|
||||||
Parents []ObjectID // ID strings
|
Parents []ObjectID // ID strings
|
||||||
submoduleCache *ObjectCache
|
submoduleCache *ObjectCache[*SubModule]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitSignature represents a git commit signature part.
|
// CommitSignature represents a git commit signature part.
|
||||||
@ -357,69 +356,6 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
|
|||||||
return string(bytes), nil
|
return string(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubModules get all the sub modules of current revision git tree
|
|
||||||
func (c *Commit) GetSubModules() (*ObjectCache, error) {
|
|
||||||
if c.submoduleCache != nil {
|
|
||||||
return c.submoduleCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, err := c.GetTreeEntryByPath(".gitmodules")
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(ErrNotExist); ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rd, err := entry.Blob().DataAsync()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rd.Close()
|
|
||||||
scanner := bufio.NewScanner(rd)
|
|
||||||
c.submoduleCache = newObjectCache()
|
|
||||||
var ismodule bool
|
|
||||||
var path string
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.HasPrefix(scanner.Text(), "[submodule") {
|
|
||||||
ismodule = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ismodule {
|
|
||||||
fields := strings.Split(scanner.Text(), "=")
|
|
||||||
k := strings.TrimSpace(fields[0])
|
|
||||||
if k == "path" {
|
|
||||||
path = strings.TrimSpace(fields[1])
|
|
||||||
} else if k == "url" {
|
|
||||||
c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
|
|
||||||
ismodule = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("GetSubModules scan: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.submoduleCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubModule get the sub module according entryname
|
|
||||||
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
|
||||||
modules, err := c.GetSubModules()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if modules != nil {
|
|
||||||
module, has := modules.Get(entryname)
|
|
||||||
if has {
|
|
||||||
return module.(*SubModule), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||||
func (c *Commit) GetBranchName() (string, error) {
|
func (c *Commit) GetBranchName() (string, error) {
|
||||||
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
||||||
|
@ -7,5 +7,5 @@ package git
|
|||||||
type CommitInfo struct {
|
type CommitInfo struct {
|
||||||
Entry *TreeEntry
|
Entry *TreeEntry
|
||||||
Commit *Commit
|
Commit *Commit
|
||||||
SubModuleFile *SubModuleFile
|
SubModuleFile *CommitSubModuleFile
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
commitsInfo[i].Commit = entryCommit
|
commitsInfo[i].Commit = entryCommit
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the entry if a submodule add a submodule file for this
|
// If the entry is a submodule add a submodule file for this
|
||||||
if entry.IsSubModule() {
|
if entry.IsSubModule() {
|
||||||
subModuleURL := ""
|
subModuleURL := ""
|
||||||
var fullPath string
|
var fullPath string
|
||||||
@ -85,7 +85,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
} else if subModule != nil {
|
} else if subModule != nil {
|
||||||
subModuleURL = subModule.URL
|
subModuleURL = subModule.URL
|
||||||
}
|
}
|
||||||
subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
|
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
|
||||||
commitsInfo[i].SubModuleFile = subModuleFile
|
commitsInfo[i].SubModuleFile = subModuleFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
} else if subModule != nil {
|
} else if subModule != nil {
|
||||||
subModuleURL = subModule.URL
|
subModuleURL = subModule.URL
|
||||||
}
|
}
|
||||||
subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
|
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
|
||||||
commitsInfo[i].SubModuleFile = subModuleFile
|
commitsInfo[i].SubModuleFile = subModuleFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
modules/git/commit_submodule.go
Normal file
47
modules/git/commit_submodule.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
// GetSubModules get all the submodules of current revision git tree
|
||||||
|
func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
|
||||||
|
if c.submoduleCache != nil {
|
||||||
|
return c.submoduleCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := c.GetTreeEntryByPath(".gitmodules")
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(ErrNotExist); ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rd, err := entry.Blob().DataAsync()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rd.Close()
|
||||||
|
|
||||||
|
// at the moment we do not strictly limit the size of the .gitmodules file because some users would have huge .gitmodules files (>1MB)
|
||||||
|
c.submoduleCache, err = configParseSubModules(rd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.submoduleCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubModule get the submodule according entry name
|
||||||
|
func (c *Commit) GetSubModule(entryName string) (*SubModule, error) {
|
||||||
|
modules, err := c.GetSubModules()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if modules != nil {
|
||||||
|
if module, has := modules.Get(entryName); has {
|
||||||
|
return module, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -15,24 +15,15 @@ import (
|
|||||||
|
|
||||||
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
|
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
|
||||||
|
|
||||||
// SubModule submodule is a reference on git repository
|
// CommitSubModuleFile represents a file with submodule type.
|
||||||
type SubModule struct {
|
type CommitSubModuleFile struct {
|
||||||
Name string
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubModuleFile represents a file with submodule type.
|
|
||||||
type SubModuleFile struct {
|
|
||||||
*Commit
|
|
||||||
|
|
||||||
refURL string
|
refURL string
|
||||||
refID string
|
refID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSubModuleFile create a new submodule file
|
// NewCommitSubModuleFile create a new submodule file
|
||||||
func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile {
|
func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile {
|
||||||
return &SubModuleFile{
|
return &CommitSubModuleFile{
|
||||||
Commit: c,
|
|
||||||
refURL: refURL,
|
refURL: refURL,
|
||||||
refID: refID,
|
refID: refID,
|
||||||
}
|
}
|
||||||
@ -109,11 +100,12 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RefURL guesses and returns reference URL.
|
// RefURL guesses and returns reference URL.
|
||||||
func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
|
// FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore)
|
||||||
|
func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
|
||||||
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
|
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefID returns reference ID.
|
// RefID returns reference ID.
|
||||||
func (sf *SubModuleFile) RefID() string {
|
func (sf *CommitSubModuleFile) RefID() string {
|
||||||
return sf.refID
|
return sf.refID
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRefURL(t *testing.T) {
|
func TestCommitSubModuleFileGetRefURL(t *testing.T) {
|
||||||
kases := []struct {
|
kases := []struct {
|
||||||
refURL string
|
refURL string
|
||||||
prefixURL string
|
prefixURL string
|
@ -135,7 +135,7 @@ author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
|||||||
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||||
encoding ISO-8859-1
|
encoding ISO-8859-1
|
||||||
gpgsig -----BEGIN PGP SIGNATURE-----
|
gpgsig -----BEGIN PGP SIGNATURE-----
|
||||||
|
<SPACE>
|
||||||
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
||||||
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
||||||
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
||||||
@ -150,7 +150,7 @@ gpgsig -----BEGIN PGP SIGNATURE-----
|
|||||||
-----END PGP SIGNATURE-----
|
-----END PGP SIGNATURE-----
|
||||||
|
|
||||||
ISO-8859-1`
|
ISO-8859-1`
|
||||||
|
commitString = strings.ReplaceAll(commitString, "<SPACE>", " ")
|
||||||
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||||
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
|
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
187
modules/git/config.go
Normal file
187
modules/git/config.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
|
||||||
|
func syncGitConfig() (err error) {
|
||||||
|
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// first, write user's git config options to git config file
|
||||||
|
// user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
|
||||||
|
for k, v := range setting.GitConfig.Options {
|
||||||
|
if err = configSet(strings.ToLower(k), v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
||||||
|
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
||||||
|
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
||||||
|
for configKey, defaultValue := range map[string]string{
|
||||||
|
"user.name": "Gitea",
|
||||||
|
"user.email": "gitea@fake.local",
|
||||||
|
} {
|
||||||
|
if err := configSetNonExist(configKey, defaultValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set git some configurations - these must be set to these values for gitea to work correctly
|
||||||
|
if err := configSet("core.quotePath", "false"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
||||||
|
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||||
|
if err := configSet("core.commitGraph", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := configSet("gc.writeCommitGraph", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := configSet("fetch.writeCommitGraph", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if DefaultFeatures().SupportProcReceive {
|
||||||
|
// set support for AGit flow
|
||||||
|
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
||||||
|
// However, some docker users and samba users find it difficult to configure their systems correctly,
|
||||||
|
// so that Gitea's git repositories are owned by the Gitea user.
|
||||||
|
// (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
|
||||||
|
// See issue: https://github.com/go-gitea/gitea/issues/19455
|
||||||
|
// As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
|
||||||
|
// it is now safe to set "safe.directory=*" for internal usage only.
|
||||||
|
// Although this setting is only supported by some new git versions, it is also tolerated by earlier versions
|
||||||
|
if err := configAddNonExist("safe.directory", "*"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if err := configSet("core.longpaths", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if setting.Git.DisableCoreProtectNTFS {
|
||||||
|
err = configSet("core.protectNTFS", "false")
|
||||||
|
} else {
|
||||||
|
err = configUnsetAll("core.protectNTFS", "false")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default partial clones are disabled, enable them from git v2.22
|
||||||
|
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
|
||||||
|
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configSet("uploadpack.allowAnySHA1InWant", "true")
|
||||||
|
} else {
|
||||||
|
if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func configSet(key, value string) error {
|
||||||
|
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||||
|
if err != nil && !IsErrorExitCode(err, 1) {
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currValue := strings.TrimSpace(stdout)
|
||||||
|
if currValue == value {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func configSetNonExist(key, value string) error {
|
||||||
|
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||||
|
if err == nil {
|
||||||
|
// already exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if IsErrorExitCode(err, 1) {
|
||||||
|
// not exist, set new config
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configAddNonExist(key, value string) error {
|
||||||
|
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
||||||
|
if err == nil {
|
||||||
|
// already exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if IsErrorExitCode(err, 1) {
|
||||||
|
// not exist, add new config
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configUnsetAll(key, value string) error {
|
||||||
|
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||||
|
if err == nil {
|
||||||
|
// exist, need to remove
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if IsErrorExitCode(err, 1) {
|
||||||
|
// not exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
75
modules/git/config_submodule.go
Normal file
75
modules/git/config_submodule.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubModule is a reference on git repository
|
||||||
|
type SubModule struct {
|
||||||
|
Path string
|
||||||
|
URL string
|
||||||
|
Branch string // this field is newly added but not really used
|
||||||
|
}
|
||||||
|
|
||||||
|
// configParseSubModules this is not a complete parse for gitmodules file, it only
|
||||||
|
// parses the url and path of submodules. At the moment it only parses well-formed gitmodules files.
|
||||||
|
// In the future, there should be a complete implementation of https://git-scm.com/docs/git-config#_syntax
|
||||||
|
func configParseSubModules(r io.Reader) (*ObjectCache[*SubModule], error) {
|
||||||
|
var subModule *SubModule
|
||||||
|
subModules := newObjectCache[*SubModule]()
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
// Skip empty lines and comments
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section header [section]
|
||||||
|
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||||
|
if subModule != nil {
|
||||||
|
subModules.Set(subModule.Path, subModule)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "[submodule") {
|
||||||
|
subModule = &SubModule{}
|
||||||
|
} else {
|
||||||
|
subModule = nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if subModule == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
switch key {
|
||||||
|
case "path":
|
||||||
|
subModule.Path = value
|
||||||
|
case "url":
|
||||||
|
subModule.URL = value
|
||||||
|
case "branch":
|
||||||
|
subModule.Branch = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading file: %w", err)
|
||||||
|
}
|
||||||
|
if subModule != nil {
|
||||||
|
subModules.Set(subModule.Path, subModule)
|
||||||
|
}
|
||||||
|
return subModules, nil
|
||||||
|
}
|
49
modules/git/config_submodule_test.go
Normal file
49
modules/git/config_submodule_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigSubmodule(t *testing.T) {
|
||||||
|
input := `
|
||||||
|
[core]
|
||||||
|
path = test
|
||||||
|
|
||||||
|
[submodule "submodule1"]
|
||||||
|
path = path1
|
||||||
|
url = https://gitea.io/foo/foo
|
||||||
|
#branch = b1
|
||||||
|
|
||||||
|
[other1]
|
||||||
|
branch = master
|
||||||
|
|
||||||
|
[submodule "submodule2"]
|
||||||
|
path = path2
|
||||||
|
url = https://gitea.io/bar/bar
|
||||||
|
branch = b2
|
||||||
|
|
||||||
|
[other2]
|
||||||
|
branch = main
|
||||||
|
|
||||||
|
[submodule "submodule3"]
|
||||||
|
path = path3
|
||||||
|
url = https://gitea.io/xxx/xxx
|
||||||
|
`
|
||||||
|
|
||||||
|
subModules, err := configParseSubModules(strings.NewReader(input))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, subModules.cache, 3)
|
||||||
|
|
||||||
|
sm1, _ := subModules.Get("path1")
|
||||||
|
assert.Equal(t, &SubModule{Path: "path1", URL: "https://gitea.io/foo/foo", Branch: ""}, sm1)
|
||||||
|
sm2, _ := subModules.Get("path2")
|
||||||
|
assert.Equal(t, &SubModule{Path: "path2", URL: "https://gitea.io/bar/bar", Branch: "b2"}, sm2)
|
||||||
|
sm3, _ := subModules.Get("path3")
|
||||||
|
assert.Equal(t, &SubModule{Path: "path3", URL: "https://gitea.io/xxx/xxx", Branch: ""}, sm3)
|
||||||
|
}
|
66
modules/git/config_test.go
Normal file
66
modules/git/config_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gitConfigContains(sub string) bool {
|
||||||
|
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
|
||||||
|
return strings.Contains(string(b), sub)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitConfig(t *testing.T) {
|
||||||
|
assert.False(t, gitConfigContains("key-a"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSetNonExist("test.key-a", "val-a"))
|
||||||
|
assert.True(t, gitConfigContains("key-a = val-a"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed"))
|
||||||
|
assert.False(t, gitConfigContains("key-a = val-a-changed"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSet("test.key-a", "val-a-changed"))
|
||||||
|
assert.True(t, gitConfigContains("key-a = val-a-changed"))
|
||||||
|
|
||||||
|
assert.NoError(t, configAddNonExist("test.key-b", "val-b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configAddNonExist("test.key-b", "val-2b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-2b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configUnsetAll("test.key-b", "val-b"))
|
||||||
|
assert.False(t, gitConfigContains("key-b = val-b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-2b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
|
||||||
|
assert.False(t, gitConfigContains("key-b = val-2b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSet("test.key-x", "*"))
|
||||||
|
assert.True(t, gitConfigContains("key-x = *"))
|
||||||
|
assert.NoError(t, configSetNonExist("test.key-x", "*"))
|
||||||
|
assert.NoError(t, configUnsetAll("test.key-x", "*"))
|
||||||
|
assert.False(t, gitConfigContains("key-x = *"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncConfig(t *testing.T) {
|
||||||
|
oldGitConfig := setting.GitConfig
|
||||||
|
defer func() {
|
||||||
|
setting.GitConfig = oldGitConfig
|
||||||
|
}()
|
||||||
|
|
||||||
|
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
||||||
|
assert.NoError(t, syncGitConfig())
|
||||||
|
assert.True(t, gitConfigContains("[sync-test]"))
|
||||||
|
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
|
||||||
|
}
|
14
modules/git/fsck.go
Normal file
14
modules/git/fsck.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fsck verifies the connectivity and validity of the objects in the database
|
||||||
|
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
|
||||||
|
return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
|
||||||
|
}
|
@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -95,17 +94,18 @@ func parseGitVersionLine(s string) (*version.Version, error) {
|
|||||||
return version.NewVersion(versionString)
|
return version.NewVersion(versionString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExecutablePath changes the path of git executable and checks the file permission and version.
|
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
||||||
func SetExecutablePath(path string) error {
|
badVersions := []struct {
|
||||||
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
|
Version *version.Version
|
||||||
if path != "" {
|
Reason string
|
||||||
GitExecutable = path
|
}{
|
||||||
|
{version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"},
|
||||||
}
|
}
|
||||||
absPath, err := exec.LookPath(GitExecutable)
|
for _, bad := range badVersions {
|
||||||
if err != nil {
|
if gitVer.Equal(bad.Version) {
|
||||||
return fmt.Errorf("git not found: %w", err)
|
return errors.New(bad.Reason)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
GitExecutable = absPath
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +128,20 @@ func ensureGitVersion() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetExecutablePath changes the path of git executable and checks the file permission and version.
|
||||||
|
func SetExecutablePath(path string) error {
|
||||||
|
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
|
||||||
|
if path != "" {
|
||||||
|
GitExecutable = path
|
||||||
|
}
|
||||||
|
absPath, err := exec.LookPath(GitExecutable)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("git not found: %w", err)
|
||||||
|
}
|
||||||
|
GitExecutable = absPath
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// HomeDir is the home dir for git to store the global config file used by Gitea internally
|
// HomeDir is the home dir for git to store the global config file used by Gitea internally
|
||||||
func HomeDir() string {
|
func HomeDir() string {
|
||||||
if setting.Git.HomePath == "" {
|
if setting.Git.HomePath == "" {
|
||||||
@ -204,196 +218,3 @@ func InitFull(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
return syncGitConfig()
|
return syncGitConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
|
|
||||||
func syncGitConfig() (err error) {
|
|
||||||
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
|
|
||||||
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// first, write user's git config options to git config file
|
|
||||||
// user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
|
|
||||||
for k, v := range setting.GitConfig.Options {
|
|
||||||
if err = configSet(strings.ToLower(k), v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
|
||||||
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
|
||||||
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
|
||||||
for configKey, defaultValue := range map[string]string{
|
|
||||||
"user.name": "Gitea",
|
|
||||||
"user.email": "gitea@fake.local",
|
|
||||||
} {
|
|
||||||
if err := configSetNonExist(configKey, defaultValue); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set git some configurations - these must be set to these values for gitea to work correctly
|
|
||||||
if err := configSet("core.quotePath", "false"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
|
||||||
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
|
||||||
if err := configSet("core.commitGraph", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := configSet("gc.writeCommitGraph", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := configSet("fetch.writeCommitGraph", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures().SupportProcReceive {
|
|
||||||
// set support for AGit flow
|
|
||||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
|
||||||
// However, some docker users and samba users find it difficult to configure their systems correctly,
|
|
||||||
// so that Gitea's git repositories are owned by the Gitea user.
|
|
||||||
// (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
|
|
||||||
// See issue: https://github.com/go-gitea/gitea/issues/19455
|
|
||||||
// As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
|
|
||||||
// it is now safe to set "safe.directory=*" for internal usage only.
|
|
||||||
// Although this setting is only supported by some new git versions, it is also tolerated by earlier versions
|
|
||||||
if err := configAddNonExist("safe.directory", "*"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if err := configSet("core.longpaths", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if setting.Git.DisableCoreProtectNTFS {
|
|
||||||
err = configSet("core.protectNTFS", "false")
|
|
||||||
} else {
|
|
||||||
err = configUnsetAll("core.protectNTFS", "false")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default partial clones are disabled, enable them from git v2.22
|
|
||||||
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
|
|
||||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = configSet("uploadpack.allowAnySHA1InWant", "true")
|
|
||||||
} else {
|
|
||||||
if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
|
||||||
badVersions := []struct {
|
|
||||||
Version *version.Version
|
|
||||||
Reason string
|
|
||||||
}{
|
|
||||||
{version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"},
|
|
||||||
}
|
|
||||||
for _, bad := range badVersions {
|
|
||||||
if gitVer.Equal(bad.Version) {
|
|
||||||
return errors.New(bad.Reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func configSet(key, value string) error {
|
|
||||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
|
||||||
if err != nil && !IsErrorExitCode(err, 1) {
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
currValue := strings.TrimSpace(stdout)
|
|
||||||
if currValue == value {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func configSetNonExist(key, value string) error {
|
|
||||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
|
||||||
if err == nil {
|
|
||||||
// already exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if IsErrorExitCode(err, 1) {
|
|
||||||
// not exist, set new config
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configAddNonExist(key, value string) error {
|
|
||||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
|
||||||
if err == nil {
|
|
||||||
// already exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if IsErrorExitCode(err, 1) {
|
|
||||||
// not exist, add new config
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configUnsetAll(key, value string) error {
|
|
||||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
|
||||||
if err == nil {
|
|
||||||
// exist, need to remove
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if IsErrorExitCode(err, 1) {
|
|
||||||
// not exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fsck verifies the connectivity and validity of the objects in the database
|
|
||||||
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
|
|
||||||
return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -43,58 +42,6 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitConfigContains(sub string) bool {
|
|
||||||
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
|
|
||||||
return strings.Contains(string(b), sub)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitConfig(t *testing.T) {
|
|
||||||
assert.False(t, gitConfigContains("key-a"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSetNonExist("test.key-a", "val-a"))
|
|
||||||
assert.True(t, gitConfigContains("key-a = val-a"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed"))
|
|
||||||
assert.False(t, gitConfigContains("key-a = val-a-changed"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSet("test.key-a", "val-a-changed"))
|
|
||||||
assert.True(t, gitConfigContains("key-a = val-a-changed"))
|
|
||||||
|
|
||||||
assert.NoError(t, configAddNonExist("test.key-b", "val-b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configAddNonExist("test.key-b", "val-2b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-2b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configUnsetAll("test.key-b", "val-b"))
|
|
||||||
assert.False(t, gitConfigContains("key-b = val-b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-2b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
|
|
||||||
assert.False(t, gitConfigContains("key-b = val-2b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSet("test.key-x", "*"))
|
|
||||||
assert.True(t, gitConfigContains("key-x = *"))
|
|
||||||
assert.NoError(t, configSetNonExist("test.key-x", "*"))
|
|
||||||
assert.NoError(t, configUnsetAll("test.key-x", "*"))
|
|
||||||
assert.False(t, gitConfigContains("key-x = *"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncConfig(t *testing.T) {
|
|
||||||
oldGitConfig := setting.GitConfig
|
|
||||||
defer func() {
|
|
||||||
setting.GitConfig = oldGitConfig
|
|
||||||
}()
|
|
||||||
|
|
||||||
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
|
||||||
assert.NoError(t, syncGitConfig())
|
|
||||||
assert.True(t, gitConfigContains("[sync-test]"))
|
|
||||||
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseGitVersion(t *testing.T) {
|
func TestParseGitVersion(t *testing.T) {
|
||||||
v, err := parseGitVersionLine("git version 2.29.3")
|
v, err := parseGitVersionLine("git version 2.29.3")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -28,7 +28,7 @@ const isGogit = true
|
|||||||
type Repository struct {
|
type Repository struct {
|
||||||
Path string
|
Path string
|
||||||
|
|
||||||
tagCache *ObjectCache
|
tagCache *ObjectCache[*Tag]
|
||||||
|
|
||||||
gogitRepo *gogit.Repository
|
gogitRepo *gogit.Repository
|
||||||
gogitStorage *filesystem.Storage
|
gogitStorage *filesystem.Storage
|
||||||
@ -79,7 +79,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
|||||||
Path: repoPath,
|
Path: repoPath,
|
||||||
gogitRepo: gogitRepo,
|
gogitRepo: gogitRepo,
|
||||||
gogitStorage: storage,
|
gogitStorage: storage,
|
||||||
tagCache: newObjectCache(),
|
tagCache: newObjectCache[*Tag](),
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
|
objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -21,7 +21,7 @@ const isGogit = false
|
|||||||
type Repository struct {
|
type Repository struct {
|
||||||
Path string
|
Path string
|
||||||
|
|
||||||
tagCache *ObjectCache
|
tagCache *ObjectCache[*Tag]
|
||||||
|
|
||||||
gpgSettings *GPGSettings
|
gpgSettings *GPGSettings
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
|||||||
|
|
||||||
return &Repository{
|
return &Repository{
|
||||||
Path: repoPath,
|
Path: repoPath,
|
||||||
tagCache: newObjectCache(),
|
tagCache: newObjectCache[*Tag](),
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
|
|||||||
t, ok := repo.tagCache.Get(tagID.String())
|
t, ok := repo.tagCache.Get(tagID.String())
|
||||||
if ok {
|
if ok {
|
||||||
log.Debug("Hit cache: %s", tagID)
|
log.Debug("Hit cache: %s", tagID)
|
||||||
tagClone := *t.(*Tag)
|
tagClone := *t
|
||||||
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
||||||
return &tagClone, nil
|
return &tagClone, nil
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
|
|||||||
t, ok := repo.tagCache.Get(tagID.String())
|
t, ok := repo.tagCache.Get(tagID.String())
|
||||||
if ok {
|
if ok {
|
||||||
log.Debug("Hit cache: %s", tagID)
|
log.Debug("Hit cache: %s", tagID)
|
||||||
tagClone := *t.(*Tag)
|
tagClone := *t
|
||||||
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
||||||
return &tagClone, nil
|
return &tagClone, nil
|
||||||
}
|
}
|
||||||
|
@ -15,27 +15,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ObjectCache provides thread-safe cache operations.
|
// ObjectCache provides thread-safe cache operations.
|
||||||
type ObjectCache struct {
|
type ObjectCache[T any] struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
cache map[string]any
|
cache map[string]T
|
||||||
}
|
}
|
||||||
|
|
||||||
func newObjectCache() *ObjectCache {
|
func newObjectCache[T any]() *ObjectCache[T] {
|
||||||
return &ObjectCache{
|
return &ObjectCache[T]{cache: make(map[string]T, 10)}
|
||||||
cache: make(map[string]any, 10),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set add obj to cache
|
// Set adds obj to cache
|
||||||
func (oc *ObjectCache) Set(id string, obj any) {
|
func (oc *ObjectCache[T]) Set(id string, obj T) {
|
||||||
oc.lock.Lock()
|
oc.lock.Lock()
|
||||||
defer oc.lock.Unlock()
|
defer oc.lock.Unlock()
|
||||||
|
|
||||||
oc.cache[id] = obj
|
oc.cache[id] = obj
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get get cached obj by id
|
// Get gets cached obj by id
|
||||||
func (oc *ObjectCache) Get(id string) (any, bool) {
|
func (oc *ObjectCache[T]) Get(id string) (T, bool) {
|
||||||
oc.lock.RLock()
|
oc.lock.RLock()
|
||||||
defer oc.lock.RUnlock()
|
defer oc.lock.RUnlock()
|
||||||
|
|
||||||
|
@ -44,10 +44,10 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
|||||||
func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error {
|
func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error {
|
||||||
rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s",
|
rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s",
|
||||||
setting.AppSubURL,
|
setting.AppSubURL,
|
||||||
url.PathEscape(ctx.Metas["user"]),
|
url.PathEscape(ctx.RenderOptions.Metas["user"]),
|
||||||
url.PathEscape(ctx.Metas["repo"]),
|
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
|
||||||
ctx.Metas["BranchNameSubURL"],
|
ctx.RenderOptions.Metas["BranchNameSubURL"],
|
||||||
url.PathEscape(ctx.RelativePath),
|
url.PathEscape(ctx.RenderOptions.RelativePath),
|
||||||
)
|
)
|
||||||
return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL)
|
return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL)
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
package console
|
package console
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -24,8 +24,7 @@ func TestRenderConsole(t *testing.T) {
|
|||||||
canRender := render.CanRender("test", strings.NewReader(k))
|
canRender := render.CanRender("test", strings.NewReader(k))
|
||||||
assert.True(t, canRender)
|
assert.True(t, canRender)
|
||||||
|
|
||||||
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext},
|
err := render.Render(markup.NewRenderContext(context.Background()), strings.NewReader(k), &buf)
|
||||||
strings.NewReader(k), &buf)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, v, buf.String())
|
assert.EqualValues(t, v, buf.String())
|
||||||
}
|
}
|
||||||
|
@ -133,10 +133,10 @@ func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.W
|
|||||||
// Check if maxRows or maxSize is reached, and if true, warn.
|
// Check if maxRows or maxSize is reached, and if true, warn.
|
||||||
if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) {
|
if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) {
|
||||||
warn := `<table class="data-table"><tr><td>`
|
warn := `<table class="data-table"><tr><td>`
|
||||||
rawLink := ` <a href="` + ctx.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RelativePath) + `">`
|
rawLink := ` <a href="` + ctx.RenderOptions.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RenderOptions.RelativePath) + `">`
|
||||||
|
|
||||||
// Try to get the user translation
|
// Try to get the user translation
|
||||||
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
||||||
warn += locale.TrString("repo.file_too_large")
|
warn += locale.TrString("repo.file_too_large")
|
||||||
rawLink += locale.TrString("repo.file_view_raw")
|
rawLink += locale.TrString("repo.file_view_raw")
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -24,8 +24,7 @@ func TestRenderCSV(t *testing.T) {
|
|||||||
|
|
||||||
for k, v := range kases {
|
for k, v := range kases {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext},
|
err := render.Render(markup.NewRenderContext(context.Background()), strings.NewReader(k), &buf)
|
||||||
strings.NewReader(k), &buf)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, v, buf.String())
|
assert.EqualValues(t, v, buf.String())
|
||||||
}
|
}
|
||||||
|
19
modules/markup/external/external.go
vendored
19
modules/markup/external/external.go
vendored
@ -12,7 +12,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
@ -80,8 +79,8 @@ func envMark(envName string) string {
|
|||||||
func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||||
var (
|
var (
|
||||||
command = strings.NewReplacer(
|
command = strings.NewReplacer(
|
||||||
envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(),
|
envMark("GITEA_PREFIX_SRC"), ctx.RenderOptions.Links.SrcLink(),
|
||||||
envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(),
|
envMark("GITEA_PREFIX_RAW"), ctx.RenderOptions.Links.RawLink(),
|
||||||
).Replace(p.Command)
|
).Replace(p.Command)
|
||||||
commands = strings.Fields(command)
|
commands = strings.Fields(command)
|
||||||
args = commands[1:]
|
args = commands[1:]
|
||||||
@ -113,22 +112,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
|
|||||||
args = append(args, f.Name())
|
args = append(args, f.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Ctx == nil {
|
processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.RenderOptions.Links.SrcLink()))
|
||||||
if !setting.IsProd || setting.IsInTesting {
|
|
||||||
panic("RenderContext did not provide context")
|
|
||||||
}
|
|
||||||
log.Warn("RenderContext did not provide context, defaulting to Shutdown context")
|
|
||||||
ctx.Ctx = graceful.GetManager().ShutdownContext()
|
|
||||||
}
|
|
||||||
|
|
||||||
processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink()))
|
|
||||||
defer finished()
|
defer finished()
|
||||||
|
|
||||||
cmd := exec.CommandContext(processCtx, commands[0], args...)
|
cmd := exec.CommandContext(processCtx, commands[0], args...)
|
||||||
cmd.Env = append(
|
cmd.Env = append(
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(),
|
"GITEA_PREFIX_SRC="+ctx.RenderOptions.Links.SrcLink(),
|
||||||
"GITEA_PREFIX_RAW="+ctx.Links.RawLink(),
|
"GITEA_PREFIX_RAW="+ctx.RenderOptions.Links.RawLink(),
|
||||||
)
|
)
|
||||||
if !p.IsInputFile {
|
if !p.IsInputFile {
|
||||||
cmd.Stdin = input
|
cmd.Stdin = input
|
||||||
|
@ -38,7 +38,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
|
|||||||
CommitID: node.Data[m[6]:m[7]],
|
CommitID: node.Data[m[6]:m[7]],
|
||||||
FilePath: node.Data[m[8]:m[9]],
|
FilePath: node.Data[m[8]:m[9]],
|
||||||
}
|
}
|
||||||
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, opts.FullURL) {
|
if !httplib.IsCurrentGiteaSiteURL(ctx, opts.FullURL) {
|
||||||
return 0, 0, "", nil
|
return 0, 0, "", nil
|
||||||
}
|
}
|
||||||
u, err := url.Parse(opts.FilePath)
|
u, err := url.Parse(opts.FilePath)
|
||||||
@ -51,7 +51,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
|
|||||||
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
|
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
|
||||||
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
|
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
|
||||||
opts.LineStart, opts.LineStop = lineStart, lineStop
|
opts.LineStart, opts.LineStop = lineStart, lineStop
|
||||||
h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx.Ctx, opts)
|
h, err := DefaultProcessorHelper.RenderRepoFileCodePreview(ctx, opts)
|
||||||
return m[0], m[1], h, err
|
return m[0], m[1], h, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
|
||||||
@ -23,10 +22,7 @@ func TestRenderCodePreview(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
buffer, err := markup.RenderString(markup.NewRenderContext(context.Background()).WithMarkupType(markdown.MarkupName), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
MarkupType: markdown.MarkupName,
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
|
|||||||
|
|
||||||
// fullHashPatternProcessor renders SHA containing URLs
|
// fullHashPatternProcessor renders SHA containing URLs
|
||||||
func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.Metas == nil {
|
if ctx.RenderOptions.Metas == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nodeStop := node.NextSibling
|
nodeStop := node.NextSibling
|
||||||
@ -111,7 +111,7 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.Metas == nil {
|
if ctx.RenderOptions.Metas == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nodeStop := node.NextSibling
|
nodeStop := node.NextSibling
|
||||||
@ -163,14 +163,14 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
// hashCurrentPatternProcessor renders SHA1 strings to corresponding links that
|
// hashCurrentPatternProcessor renders SHA1 strings to corresponding links that
|
||||||
// are assumed to be in the same repository.
|
// are assumed to be in the same repository.
|
||||||
func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.Metas == nil || ctx.Metas["user"] == "" || ctx.Metas["repo"] == "" || (ctx.Repo == nil && ctx.GitRepo == nil) {
|
if ctx.RenderOptions.Metas == nil || ctx.RenderOptions.Metas["user"] == "" || ctx.RenderOptions.Metas["repo"] == "" || (ctx.RenderHelper.repoFacade == nil && ctx.RenderHelper.gitRepo == nil) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
start := 0
|
start := 0
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
if ctx.ShaExistCache == nil {
|
if ctx.RenderHelper.shaExistCache == nil {
|
||||||
ctx.ShaExistCache = make(map[string]bool)
|
ctx.RenderHelper.shaExistCache = make(map[string]bool)
|
||||||
}
|
}
|
||||||
for node != nil && node != next && start < len(node.Data) {
|
for node != nil && node != next && start < len(node.Data) {
|
||||||
m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
|
m := globalVars().hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
|
||||||
@ -191,25 +191,25 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
// a commit in the repository before making it a link.
|
// a commit in the repository before making it a link.
|
||||||
|
|
||||||
// check cache first
|
// check cache first
|
||||||
exist, inCache := ctx.ShaExistCache[hash]
|
exist, inCache := ctx.RenderHelper.shaExistCache[hash]
|
||||||
if !inCache {
|
if !inCache {
|
||||||
if ctx.GitRepo == nil {
|
if ctx.RenderHelper.gitRepo == nil {
|
||||||
var err error
|
var err error
|
||||||
var closer io.Closer
|
var closer io.Closer
|
||||||
ctx.GitRepo, closer, err = gitrepo.RepositoryFromContextOrOpen(ctx.Ctx, ctx.Repo)
|
ctx.RenderHelper.gitRepo, closer, err = gitrepo.RepositoryFromContextOrOpen(ctx, ctx.RenderHelper.repoFacade)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(ctx.Repo), err)
|
log.Error("unable to open repository: %s Error: %v", gitrepo.RepoGitURL(ctx.RenderHelper.repoFacade), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.AddCancel(func() {
|
ctx.AddCancel(func() {
|
||||||
_ = closer.Close()
|
_ = closer.Close()
|
||||||
ctx.GitRepo = nil
|
ctx.RenderHelper.gitRepo = nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
|
// Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
|
||||||
exist = ctx.GitRepo.IsReferenceExist(hash)
|
exist = ctx.RenderHelper.gitRepo.IsReferenceExist(hash)
|
||||||
ctx.ShaExistCache[hash] = exist
|
ctx.RenderHelper.shaExistCache[hash] = exist
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exist {
|
if !exist {
|
||||||
@ -217,7 +217,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
link := util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], "commit", hash)
|
link := util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash)
|
||||||
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit"))
|
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit"))
|
||||||
start = 0
|
start = 0
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
testModule "code.gitea.io/gitea/modules/test"
|
testModule "code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -79,11 +79,11 @@ func TestRender_IssueIndexPattern(t *testing.T) {
|
|||||||
// numeric: render inputs without valid mentions
|
// numeric: render inputs without valid mentions
|
||||||
test := func(s string) {
|
test := func(s string) {
|
||||||
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
})
|
})
|
||||||
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: numericMetas,
|
RenderOptions: RenderOptions{Metas: numericMetas},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,8 +133,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
expectedNil := fmt.Sprintf(expectedFmt, links...)
|
expectedNil := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: localMetas,
|
RenderOptions: RenderOptions{Metas: localMetas},
|
||||||
})
|
})
|
||||||
|
|
||||||
class := "ref-issue"
|
class := "ref-issue"
|
||||||
@ -147,8 +147,8 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
expectedNum := fmt.Sprintf(expectedFmt, links...)
|
expectedNum := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: numericMetas,
|
RenderOptions: RenderOptions{Metas: numericMetas},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +184,8 @@ func TestRender_IssueIndexPattern3(t *testing.T) {
|
|||||||
// alphanumeric: render inputs without valid mentions
|
// alphanumeric: render inputs without valid mentions
|
||||||
test := func(s string) {
|
test := func(s string) {
|
||||||
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
testRenderIssueIndexPattern(t, s, s, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: alphanumericMetas,
|
RenderOptions: RenderOptions{Metas: alphanumericMetas},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
test("")
|
test("")
|
||||||
@ -217,8 +217,8 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
|
|||||||
}
|
}
|
||||||
expected := fmt.Sprintf(expectedFmt, links...)
|
expected := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: alphanumericMetas,
|
RenderOptions: RenderOptions{Metas: alphanumericMetas},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
test("OTT-1234 test", "%s test", "OTT-1234")
|
test("OTT-1234 test", "%s test", "OTT-1234")
|
||||||
@ -240,8 +240,8 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
|
|||||||
|
|
||||||
expected := fmt.Sprintf(expectedFmt, links...)
|
expected := fmt.Sprintf(expectedFmt, links...)
|
||||||
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
|
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: metas,
|
RenderOptions: RenderOptions{Metas: metas},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,8 +264,8 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{
|
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: regexpMetas,
|
RenderOptions: RenderOptions{Metas: regexpMetas},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,16 +279,16 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{
|
testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: metas,
|
RenderOptions: RenderOptions{Metas: metas},
|
||||||
})
|
})
|
||||||
testRenderIssueIndexPattern(t, "#1312", "#1312", &RenderContext{
|
testRenderIssueIndexPattern(t, "#1312", "#1312", &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: metas,
|
RenderOptions: RenderOptions{Metas: metas},
|
||||||
})
|
})
|
||||||
testRenderIssueIndexPattern(t, "!1", "!1", &RenderContext{
|
testRenderIssueIndexPattern(t, "!1", "!1", &RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: metas,
|
RenderOptions: RenderOptions{Metas: metas},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,17 +301,17 @@ func TestRender_RenderIssueTitle(t *testing.T) {
|
|||||||
"style": IssueNameStyleNumeric,
|
"style": IssueNameStyleNumeric,
|
||||||
}
|
}
|
||||||
actual, err := RenderIssueTitle(&RenderContext{
|
actual, err := RenderIssueTitle(&RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Metas: metas,
|
RenderOptions: RenderOptions{Metas: metas},
|
||||||
}, "#1")
|
}, "#1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "#1", actual)
|
assert.Equal(t, "#1", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
||||||
ctx.Links.AbsolutePrefix = true
|
ctx.RenderOptions.Links.AbsolutePrefix = true
|
||||||
if ctx.Links.Base == "" {
|
if ctx.RenderOptions.Links.Base == "" {
|
||||||
ctx.Links.Base = TestRepoURL
|
ctx.RenderOptions.Links.Base = TestRepoURL
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
@ -326,22 +326,18 @@ func TestRender_AutoLink(t *testing.T) {
|
|||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
var buffer strings.Builder
|
var buffer strings.Builder
|
||||||
err := PostProcess(&RenderContext{
|
err := PostProcess(&RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Links: Links{
|
|
||||||
Base: TestRepoURL,
|
RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}},
|
||||||
},
|
|
||||||
Metas: localMetas,
|
|
||||||
}, strings.NewReader(input), &buffer)
|
}, strings.NewReader(input), &buffer)
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
err = PostProcess(&RenderContext{
|
err = PostProcess(&RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Links: Links{
|
|
||||||
Base: TestRepoURL,
|
RenderOptions: RenderOptions{Metas: localWikiMetas, Links: Links{Base: TestRepoURL}},
|
||||||
},
|
|
||||||
Metas: localWikiMetas,
|
|
||||||
}, strings.NewReader(input), &buffer)
|
}, strings.NewReader(input), &buffer)
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
|
||||||
@ -368,11 +364,9 @@ func TestRender_FullIssueURLs(t *testing.T) {
|
|||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
err := postProcess(&RenderContext{
|
err := postProcess(&RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
ctx: context.Background(),
|
||||||
Links: Links{
|
|
||||||
Base: TestRepoURL,
|
RenderOptions: RenderOptions{Metas: localMetas, Links: Links{Base: TestRepoURL}},
|
||||||
},
|
|
||||||
Metas: localMetas,
|
|
||||||
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
|
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, result.String())
|
assert.Equal(t, expected, result.String())
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.Metas == nil {
|
if ctx.RenderOptions.Metas == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
@ -36,14 +36,14 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
link := node.Data[m[0]:m[1]]
|
link := node.Data[m[0]:m[1]]
|
||||||
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, link) {
|
if !httplib.IsCurrentGiteaSiteURL(ctx, link) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
text := "#" + node.Data[m[2]:m[3]]
|
text := "#" + node.Data[m[2]:m[3]]
|
||||||
// if m[4] and m[5] is not -1, then link is to a comment
|
// if m[4] and m[5] is not -1, then link is to a comment
|
||||||
// indicate that in the text by appending (comment)
|
// indicate that in the text by appending (comment)
|
||||||
if m[4] != -1 && m[5] != -1 {
|
if m[4] != -1 && m[5] != -1 {
|
||||||
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
if locale, ok := ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
||||||
text += " " + locale.TrString("repo.from_comment")
|
text += " " + locale.TrString("repo.from_comment")
|
||||||
} else {
|
} else {
|
||||||
text += " (comment)"
|
text += " (comment)"
|
||||||
@ -56,7 +56,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
matchOrg := linkParts[len(linkParts)-4]
|
matchOrg := linkParts[len(linkParts)-4]
|
||||||
matchRepo := linkParts[len(linkParts)-3]
|
matchRepo := linkParts[len(linkParts)-3]
|
||||||
|
|
||||||
if matchOrg == ctx.Metas["user"] && matchRepo == ctx.Metas["repo"] {
|
if matchOrg == ctx.RenderOptions.Metas["user"] && matchRepo == ctx.RenderOptions.Metas["repo"] {
|
||||||
replaceContent(node, m[0], m[1], createLink(ctx, link, text, "ref-issue"))
|
replaceContent(node, m[0], m[1], createLink(ctx, link, text, "ref-issue"))
|
||||||
} else {
|
} else {
|
||||||
text = matchOrg + "/" + matchRepo + text
|
text = matchOrg + "/" + matchRepo + text
|
||||||
@ -67,14 +67,14 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.Metas == nil {
|
if ctx.RenderOptions.Metas == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// crossLinkOnly: do not parse "#123", only parse "owner/repo#123"
|
// crossLinkOnly: do not parse "#123", only parse "owner/repo#123"
|
||||||
// if there is no repo in the context, then the "#123" format can't be parsed
|
// if there is no repo in the context, then the "#123" format can't be parsed
|
||||||
// old logic: crossLinkOnly := ctx.Metas["mode"] == "document" && !ctx.IsWiki
|
// old logic: crossLinkOnly := ctx.RenderOptions.Metas["mode"] == "document" && !ctx.IsWiki
|
||||||
crossLinkOnly := ctx.Metas["markupAllowShortIssuePattern"] != "true"
|
crossLinkOnly := ctx.RenderOptions.Metas["markupAllowShortIssuePattern"] != "true"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
found bool
|
found bool
|
||||||
@ -84,20 +84,20 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
next := node.NextSibling
|
next := node.NextSibling
|
||||||
|
|
||||||
for node != nil && node != next {
|
for node != nil && node != next {
|
||||||
_, hasExtTrackFormat := ctx.Metas["format"]
|
_, hasExtTrackFormat := ctx.RenderOptions.Metas["format"]
|
||||||
|
|
||||||
// Repos with external issue trackers might still need to reference local PRs
|
// Repos with external issue trackers might still need to reference local PRs
|
||||||
// We need to concern with the first one that shows up in the text, whichever it is
|
// We need to concern with the first one that shows up in the text, whichever it is
|
||||||
isNumericStyle := ctx.Metas["style"] == "" || ctx.Metas["style"] == IssueNameStyleNumeric
|
isNumericStyle := ctx.RenderOptions.Metas["style"] == "" || ctx.RenderOptions.Metas["style"] == IssueNameStyleNumeric
|
||||||
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
|
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
|
||||||
|
|
||||||
switch ctx.Metas["style"] {
|
switch ctx.RenderOptions.Metas["style"] {
|
||||||
case "", IssueNameStyleNumeric:
|
case "", IssueNameStyleNumeric:
|
||||||
found, ref = foundNumeric, refNumeric
|
found, ref = foundNumeric, refNumeric
|
||||||
case IssueNameStyleAlphanumeric:
|
case IssueNameStyleAlphanumeric:
|
||||||
found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
|
found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
|
||||||
case IssueNameStyleRegexp:
|
case IssueNameStyleRegexp:
|
||||||
pattern, err := regexplru.GetCompiled(ctx.Metas["regexp"])
|
pattern, err := regexplru.GetCompiled(ctx.RenderOptions.Metas["regexp"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -121,9 +121,9 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
var link *html.Node
|
var link *html.Node
|
||||||
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
||||||
if hasExtTrackFormat && !ref.IsPull {
|
if hasExtTrackFormat && !ref.IsPull {
|
||||||
ctx.Metas["index"] = ref.Issue
|
ctx.RenderOptions.Metas["index"] = ref.Issue
|
||||||
|
|
||||||
res, err := vars.Expand(ctx.Metas["format"], ctx.Metas)
|
res, err := vars.Expand(ctx.RenderOptions.Metas["format"], ctx.RenderOptions.Metas)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// here we could just log the error and continue the rendering
|
// here we could just log the error and continue the rendering
|
||||||
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
|
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
|
||||||
@ -136,9 +136,9 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
// Gitea will redirect on click as appropriate.
|
// Gitea will redirect on click as appropriate.
|
||||||
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
|
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
|
||||||
if ref.Owner == "" {
|
if ref.Owner == "" {
|
||||||
link = createLink(ctx, util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], issuePath, ref.Issue), reftext, "ref-issue")
|
link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), reftext, "ref-issue")
|
||||||
} else {
|
} else {
|
||||||
link = createLink(ctx, util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, issuePath, ref.Issue), reftext, "ref-issue")
|
link = createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, issuePath, ref.Issue), reftext, "ref-issue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
|
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
|
||||||
link := createLink(ctx, util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit")
|
link := createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit")
|
||||||
|
|
||||||
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
|
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
|
@ -19,15 +19,15 @@ import (
|
|||||||
func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) {
|
func ResolveLink(ctx *RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) {
|
||||||
isAnchorFragment := link != "" && link[0] == '#'
|
isAnchorFragment := link != "" && link[0] == '#'
|
||||||
if !isAnchorFragment && !IsFullURLString(link) {
|
if !isAnchorFragment && !IsFullURLString(link) {
|
||||||
linkBase := ctx.Links.Base
|
linkBase := ctx.RenderOptions.Links.Base
|
||||||
if ctx.IsMarkupContentWiki() {
|
if ctx.IsMarkupContentWiki() {
|
||||||
// no need to check if the link should be resolved as a wiki link or a wiki raw link
|
// no need to check if the link should be resolved as a wiki link or a wiki raw link
|
||||||
// just use wiki link here, and it will be redirected to a wiki raw link if necessary
|
// just use wiki link here, and it will be redirected to a wiki raw link if necessary
|
||||||
linkBase = ctx.Links.WikiLink()
|
linkBase = ctx.RenderOptions.Links.WikiLink()
|
||||||
} else if ctx.Links.BranchPath != "" || ctx.Links.TreePath != "" {
|
} else if ctx.RenderOptions.Links.BranchPath != "" || ctx.RenderOptions.Links.TreePath != "" {
|
||||||
// if there is no BranchPath, then the link will be something like "/owner/repo/src/{the-file-path}"
|
// if there is no BranchPath, then the link will be something like "/owner/repo/src/{the-file-path}"
|
||||||
// and then this link will be handled by the "legacy-ref" code and be redirected to the default branch like "/owner/repo/src/branch/main/{the-file-path}"
|
// and then this link will be handled by the "legacy-ref" code and be redirected to the default branch like "/owner/repo/src/branch/main/{the-file-path}"
|
||||||
linkBase = ctx.Links.SrcLink()
|
linkBase = ctx.RenderOptions.Links.SrcLink()
|
||||||
}
|
}
|
||||||
link, resolved = util.URLJoin(linkBase, link), true
|
link, resolved = util.URLJoin(linkBase, link), true
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
if image {
|
if image {
|
||||||
if !absoluteLink {
|
if !absoluteLink {
|
||||||
link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link)
|
link = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), link)
|
||||||
}
|
}
|
||||||
title := props["title"]
|
title := props["title"]
|
||||||
if title == "" {
|
if title == "" {
|
||||||
|
@ -25,15 +25,15 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
loc.Start += start
|
loc.Start += start
|
||||||
loc.End += start
|
loc.End += start
|
||||||
mention := node.Data[loc.Start:loc.End]
|
mention := node.Data[loc.Start:loc.End]
|
||||||
teams, ok := ctx.Metas["teams"]
|
teams, ok := ctx.RenderOptions.Metas["teams"]
|
||||||
// FIXME: util.URLJoin may not be necessary here:
|
// FIXME: util.URLJoin may not be necessary here:
|
||||||
// - setting.AppURL is defined to have a terminal '/' so unless mention[1:]
|
// - setting.AppURL is defined to have a terminal '/' so unless mention[1:]
|
||||||
// is an AppSubURL link we can probably fallback to concatenation.
|
// is an AppSubURL link we can probably fallback to concatenation.
|
||||||
// team mention should follow @orgName/teamName style
|
// team mention should follow @orgName/teamName style
|
||||||
if ok && strings.Contains(mention, "/") {
|
if ok && strings.Contains(mention, "/") {
|
||||||
mentionOrgAndTeam := strings.Split(mention, "/")
|
mentionOrgAndTeam := strings.Split(mention, "/")
|
||||||
if mentionOrgAndTeam[0][1:] == ctx.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
|
if mentionOrgAndTeam[0][1:] == ctx.RenderOptions.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
|
||||||
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.Links.Prefix(), "org", ctx.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "" /*mention*/))
|
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), "org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "" /*mention*/))
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
start = 0
|
start = 0
|
||||||
continue
|
continue
|
||||||
@ -43,8 +43,8 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
mentionedUsername := mention[1:]
|
mentionedUsername := mention[1:]
|
||||||
|
|
||||||
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
|
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx, mentionedUsername) {
|
||||||
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "" /*mention*/))
|
replaceContent(node, loc.Start, loc.End, createLink(ctx, util.URLJoin(ctx.RenderOptions.Links.Prefix(), mentionedUsername), mention, "" /*mention*/))
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
start = 0
|
start = 0
|
||||||
} else {
|
} else {
|
||||||
|
@ -17,7 +17,7 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if IsNonEmptyRelativePath(attr.Val) {
|
if IsNonEmptyRelativePath(attr.Val) {
|
||||||
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
||||||
|
|
||||||
// By default, the "<img>" tag should also be clickable,
|
// By default, the "<img>" tag should also be clickable,
|
||||||
// because frontend use `<img>` to paste the re-scaled image into the markdown,
|
// because frontend use `<img>` to paste the re-scaled image into the markdown,
|
||||||
@ -53,7 +53,7 @@ func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if IsNonEmptyRelativePath(attr.Val) {
|
if IsNonEmptyRelativePath(attr.Val) {
|
||||||
attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
attr.Val = util.URLJoin(ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()), attr.Val)
|
||||||
}
|
}
|
||||||
attr.Val = camoHandleLink(attr.Val)
|
attr.Val = camoHandleLink(attr.Val)
|
||||||
node.Attr[i] = attr
|
node.Attr[i] = attr
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/emoji"
|
"code.gitea.io/gitea/modules/emoji"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
@ -57,16 +56,10 @@ func newMockRepo(ownerName, repoName string) gitrepo.Repository {
|
|||||||
func TestRender_Commits(t *testing.T) {
|
func TestRender_Commits(t *testing.T) {
|
||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas, newMockRepo(testRepoOwnerName, testRepoName), markup.Links{
|
||||||
Ctx: git.DefaultContext,
|
AbsolutePrefix: true,
|
||||||
RelativePath: ".md",
|
Base: markup.TestRepoURL,
|
||||||
Links: markup.Links{
|
}), input)
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: markup.TestRepoURL,
|
|
||||||
},
|
|
||||||
Repo: newMockRepo(testRepoOwnerName, testRepoName),
|
|
||||||
Metas: localMetas,
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -112,15 +105,11 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", localMetas,
|
||||||
Ctx: git.DefaultContext,
|
markup.Links{
|
||||||
RelativePath: "a.md",
|
|
||||||
Links: markup.Links{
|
|
||||||
AbsolutePrefix: true,
|
AbsolutePrefix: true,
|
||||||
Base: setting.AppSubURL,
|
Base: setting.AppSubURL,
|
||||||
},
|
}), input)
|
||||||
Metas: localMetas,
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -154,13 +143,7 @@ func TestRender_links(t *testing.T) {
|
|||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
RelativePath: "a.md",
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: markup.TestRepoURL,
|
|
||||||
},
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -265,13 +248,7 @@ func TestRender_email(t *testing.T) {
|
|||||||
setting.AppURL = markup.TestAppURL
|
setting.AppURL = markup.TestAppURL
|
||||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
res, err := markup.RenderString(&markup.RenderContext{
|
res, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
RelativePath: "a.md",
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: markup.TestRepoURL,
|
|
||||||
},
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
|
||||||
}
|
}
|
||||||
@ -338,13 +315,7 @@ func TestRender_emoji(t *testing.T) {
|
|||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
expected = strings.ReplaceAll(expected, "&", "&")
|
expected = strings.ReplaceAll(expected, "&", "&")
|
||||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
buffer, err := markup.RenderString(markup.NewTestRenderContext("a.md", markup.Links{Base: markup.TestRepoURL}), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
RelativePath: "a.md",
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: markup.TestRepoURL,
|
|
||||||
},
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -404,22 +375,10 @@ func TestRender_ShortLinks(t *testing.T) {
|
|||||||
tree := util.URLJoin(markup.TestRepoURL, "src", "master")
|
tree := util.URLJoin(markup.TestRepoURL, "src", "master")
|
||||||
|
|
||||||
test := func(input, expected, expectedWiki string) {
|
test := func(input, expected, expectedWiki string) {
|
||||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL, BranchPath: "master"}), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: markup.TestRepoURL,
|
|
||||||
BranchPath: "master",
|
|
||||||
},
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
||||||
buffer, err = markdown.RenderString(&markup.RenderContext{
|
buffer, err = markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: markup.TestRepoURL}, localWikiMetas), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: markup.TestRepoURL,
|
|
||||||
},
|
|
||||||
Metas: localWikiMetas,
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
||||||
}
|
}
|
||||||
@ -529,11 +488,7 @@ func TestRender_ShortLinks(t *testing.T) {
|
|||||||
|
|
||||||
func TestRender_RelativeMedias(t *testing.T) {
|
func TestRender_RelativeMedias(t *testing.T) {
|
||||||
render := func(input string, isWiki bool, links markup.Links) string {
|
render := func(input string, isWiki bool, links markup.Links) string {
|
||||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
buffer, err := markdown.RenderString(markup.NewTestRenderContext(links, util.Iif(isWiki, localWikiMetas, localMetas)), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: links,
|
|
||||||
Metas: util.Iif(isWiki, localWikiMetas, localMetas),
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return strings.TrimSpace(string(buffer))
|
return strings.TrimSpace(string(buffer))
|
||||||
}
|
}
|
||||||
@ -574,26 +529,14 @@ func Test_ParseClusterFuzz(t *testing.T) {
|
|||||||
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
||||||
|
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
err := markup.PostProcess(&markup.RenderContext{
|
err := markup.PostProcess(markup.NewTestRenderContext(markup.Links{Base: "https://example.com"}, localMetas), strings.NewReader(data), &res)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "https://example.com",
|
|
||||||
},
|
|
||||||
Metas: localMetas,
|
|
||||||
}, strings.NewReader(data), &res)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotContains(t, res.String(), "<html")
|
assert.NotContains(t, res.String(), "<html")
|
||||||
|
|
||||||
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
||||||
|
|
||||||
res.Reset()
|
res.Reset()
|
||||||
err = markup.PostProcess(&markup.RenderContext{
|
err = markup.PostProcess(markup.NewTestRenderContext(markup.Links{Base: "https://example.com"}, localMetas), strings.NewReader(data), &res)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "https://example.com",
|
|
||||||
},
|
|
||||||
Metas: localMetas,
|
|
||||||
}, strings.NewReader(data), &res)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotContains(t, res.String(), "<html")
|
assert.NotContains(t, res.String(), "<html")
|
||||||
@ -606,14 +549,13 @@ func TestPostProcess_RenderDocument(t *testing.T) {
|
|||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
err := markup.PostProcess(&markup.RenderContext{
|
err := markup.PostProcess(markup.NewTestRenderContext(
|
||||||
Ctx: git.DefaultContext,
|
markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
AbsolutePrefix: true,
|
AbsolutePrefix: true,
|
||||||
Base: "https://example.com",
|
Base: "https://example.com",
|
||||||
},
|
},
|
||||||
Metas: map[string]string{"user": "go-gitea", "repo": "gitea"},
|
map[string]string{"user": "go-gitea", "repo": "gitea"},
|
||||||
}, strings.NewReader(input), &res)
|
), strings.NewReader(input), &res)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
||||||
}
|
}
|
||||||
@ -650,10 +592,7 @@ func TestIssue16020(t *testing.T) {
|
|||||||
data := `<img src="data:image/png;base64,i//V"/>`
|
data := `<img src="data:image/png;base64,i//V"/>`
|
||||||
|
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
err := markup.PostProcess(&markup.RenderContext{
|
err := markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Metas: localMetas,
|
|
||||||
}, strings.NewReader(data), &res)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, data, res.String())
|
assert.Equal(t, data, res.String())
|
||||||
}
|
}
|
||||||
@ -666,29 +605,23 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
err := markup.PostProcess(&markup.RenderContext{
|
err := markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Metas: localMetas,
|
|
||||||
}, strings.NewReader(data), &res)
|
|
||||||
assert.NoError(b, err)
|
assert.NoError(b, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFuzz(t *testing.T) {
|
func TestFuzz(t *testing.T) {
|
||||||
s := "t/l/issues/8#/../../a"
|
s := "t/l/issues/8#/../../a"
|
||||||
renderContext := markup.RenderContext{
|
renderContext := markup.NewTestRenderContext(
|
||||||
Ctx: git.DefaultContext,
|
markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: "https://example.com/go-gitea/gitea",
|
Base: "https://example.com/go-gitea/gitea",
|
||||||
},
|
},
|
||||||
Metas: map[string]string{
|
map[string]string{
|
||||||
"user": "go-gitea",
|
"user": "go-gitea",
|
||||||
"repo": "gitea",
|
"repo": "gitea",
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
err := markup.PostProcess(renderContext, strings.NewReader(s), io.Discard)
|
||||||
err := markup.PostProcess(&renderContext, strings.NewReader(s), io.Discard)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,10 +629,7 @@ func TestIssue18471(t *testing.T) {
|
|||||||
data := `http://domain/org/repo/compare/783b039...da951ce`
|
data := `http://domain/org/repo/compare/783b039...da951ce`
|
||||||
|
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
err := markup.PostProcess(&markup.RenderContext{
|
err := markup.PostProcess(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Metas: localMetas,
|
|
||||||
}, strings.NewReader(data), &res)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code class="nohighlight">783b039...da951ce</code></a>`, res.String())
|
assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code class="nohighlight">783b039...da951ce</code></a>`, res.String())
|
||||||
|
@ -79,7 +79,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||||||
// TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }`
|
// TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }`
|
||||||
// many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting
|
// many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting
|
||||||
// especially in many tests.
|
// especially in many tests.
|
||||||
markdownLineBreakStyle := ctx.Metas["markdownLineBreakStyle"]
|
markdownLineBreakStyle := ctx.RenderOptions.Metas["markdownLineBreakStyle"]
|
||||||
if markup.RenderBehaviorForTesting.ForceHardLineBreak {
|
if markup.RenderBehaviorForTesting.ForceHardLineBreak {
|
||||||
v.SetHardLineBreak(true)
|
v.SetHardLineBreak(true)
|
||||||
} else if markdownLineBreakStyle == "comment" {
|
} else if markdownLineBreakStyle == "comment" {
|
||||||
|
@ -182,7 +182,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
|||||||
bufWithMetadataLength := len(buf)
|
bufWithMetadataLength := len(buf)
|
||||||
|
|
||||||
rc := &RenderConfig{
|
rc := &RenderConfig{
|
||||||
Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)),
|
Meta: markup.RenderMetaAsDetails,
|
||||||
Icon: "table",
|
Icon: "table",
|
||||||
Lang: "",
|
Lang: "",
|
||||||
}
|
}
|
||||||
@ -241,7 +241,7 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
|
|||||||
|
|
||||||
// Render renders Markdown to HTML with all specific handling stuff.
|
// Render renders Markdown to HTML with all specific handling stuff.
|
||||||
func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||||
ctx.MarkupType = MarkupName
|
ctx.RenderOptions.MarkupType = MarkupName
|
||||||
return markup.Render(ctx, input, output)
|
return markup.Render(ctx, input, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
package markdown_test
|
package markdown_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
@ -67,22 +65,11 @@ func TestRender_StandardLinks(t *testing.T) {
|
|||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
|
||||||
test := func(input, expected, expectedWiki string) {
|
test := func(input, expected, expectedWiki string) {
|
||||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: FullURL,
|
|
||||||
},
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
||||||
|
|
||||||
buffer, err = markdown.RenderString(&markup.RenderContext{
|
buffer, err = markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}, localWikiMetas), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: FullURL,
|
|
||||||
},
|
|
||||||
Metas: localWikiMetas,
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
|
||||||
}
|
}
|
||||||
@ -101,12 +88,7 @@ func TestRender_Images(t *testing.T) {
|
|||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
buffer, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: FullURL,
|
|
||||||
},
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
||||||
}
|
}
|
||||||
@ -308,14 +290,11 @@ func TestTotal_RenderWiki(t *testing.T) {
|
|||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
|
answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
|
||||||
for i := 0; i < len(sameCases); i++ {
|
for i := 0; i < len(sameCases); i++ {
|
||||||
line, err := markdown.RenderString(&markup.RenderContext{
|
line, err := markdown.RenderString(markup.NewTestRenderContext(
|
||||||
Ctx: git.DefaultContext,
|
markup.Links{Base: FullURL},
|
||||||
Links: markup.Links{
|
newMockRepo(testRepoOwnerName, testRepoName),
|
||||||
Base: FullURL,
|
localWikiMetas,
|
||||||
},
|
), sameCases[i])
|
||||||
Repo: newMockRepo(testRepoOwnerName, testRepoName),
|
|
||||||
Metas: localWikiMetas,
|
|
||||||
}, sameCases[i])
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, answers[i], string(line))
|
assert.Equal(t, answers[i], string(line))
|
||||||
}
|
}
|
||||||
@ -334,13 +313,7 @@ func TestTotal_RenderWiki(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCases); i += 2 {
|
for i := 0; i < len(testCases); i += 2 {
|
||||||
line, err := markdown.RenderString(&markup.RenderContext{
|
line, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}, localWikiMetas), testCases[i])
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: FullURL,
|
|
||||||
},
|
|
||||||
Metas: localWikiMetas,
|
|
||||||
}, testCases[i])
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, testCases[i+1], string(line))
|
assert.EqualValues(t, testCases[i+1], string(line))
|
||||||
}
|
}
|
||||||
@ -352,15 +325,14 @@ func TestTotal_RenderString(t *testing.T) {
|
|||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
|
answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
|
||||||
for i := 0; i < len(sameCases); i++ {
|
for i := 0; i < len(sameCases); i++ {
|
||||||
line, err := markdown.RenderString(&markup.RenderContext{
|
line, err := markdown.RenderString(markup.NewTestRenderContext(
|
||||||
Ctx: git.DefaultContext,
|
markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: FullURL,
|
Base: FullURL,
|
||||||
BranchPath: "master",
|
BranchPath: "master",
|
||||||
},
|
},
|
||||||
Repo: newMockRepo(testRepoOwnerName, testRepoName),
|
newMockRepo(testRepoOwnerName, testRepoName),
|
||||||
Metas: localMetas,
|
localMetas,
|
||||||
}, sameCases[i])
|
), sameCases[i])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, answers[i], string(line))
|
assert.Equal(t, answers[i], string(line))
|
||||||
}
|
}
|
||||||
@ -368,12 +340,7 @@ func TestTotal_RenderString(t *testing.T) {
|
|||||||
testCases := []string{}
|
testCases := []string{}
|
||||||
|
|
||||||
for i := 0; i < len(testCases); i += 2 {
|
for i := 0; i < len(testCases); i += 2 {
|
||||||
line, err := markdown.RenderString(&markup.RenderContext{
|
line, err := markdown.RenderString(markup.NewTestRenderContext(markup.Links{Base: FullURL}), testCases[i])
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: FullURL,
|
|
||||||
},
|
|
||||||
}, testCases[i])
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, template.HTML(testCases[i+1]), line)
|
assert.Equal(t, template.HTML(testCases[i+1]), line)
|
||||||
}
|
}
|
||||||
@ -381,17 +348,17 @@ func TestTotal_RenderString(t *testing.T) {
|
|||||||
|
|
||||||
func TestRender_RenderParagraphs(t *testing.T) {
|
func TestRender_RenderParagraphs(t *testing.T) {
|
||||||
test := func(t *testing.T, str string, cnt int) {
|
test := func(t *testing.T, str string, cnt int) {
|
||||||
res, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, str)
|
res, err := markdown.RenderRawString(markup.NewTestRenderContext(), str)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for unix should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
|
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for unix should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
|
||||||
|
|
||||||
mac := strings.ReplaceAll(str, "\n", "\r")
|
mac := strings.ReplaceAll(str, "\n", "\r")
|
||||||
res, err = markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, mac)
|
res, err = markdown.RenderRawString(markup.NewTestRenderContext(), mac)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for mac should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
|
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for mac should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
|
||||||
|
|
||||||
dos := strings.ReplaceAll(str, "\n", "\r\n")
|
dos := strings.ReplaceAll(str, "\n", "\r\n")
|
||||||
res, err = markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, dos)
|
res, err = markdown.RenderRawString(markup.NewTestRenderContext(), dos)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for windows should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
|
assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for windows should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res)
|
||||||
}
|
}
|
||||||
@ -419,7 +386,7 @@ func TestMarkdownRenderRaw(t *testing.T) {
|
|||||||
|
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase)
|
log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase)
|
||||||
_, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, string(testcase))
|
_, err := markdown.RenderRawString(markup.NewTestRenderContext(), string(testcase))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,7 +399,7 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) {
|
|||||||
<a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p>
|
<a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p>
|
||||||
`
|
`
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
||||||
res, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
|
res, err := markdown.RenderRawString(markup.NewTestRenderContext(), testcase)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, res)
|
assert.Equal(t, expected, res)
|
||||||
}
|
}
|
||||||
@ -441,7 +408,7 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
|
|||||||
testcase := `[Link with emoji :moon: in text](https://gitea.io)`
|
testcase := `[Link with emoji :moon: in text](https://gitea.io)`
|
||||||
expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p>
|
expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p>
|
||||||
`
|
`
|
||||||
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
|
res, err := markdown.RenderString(markup.NewTestRenderContext(), testcase)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, template.HTML(expected), res)
|
assert.Equal(t, template.HTML(expected), res)
|
||||||
}
|
}
|
||||||
@ -479,7 +446,7 @@ func TestColorPreview(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range positiveTests {
|
for _, test := range positiveTests {
|
||||||
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
|
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
||||||
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
||||||
}
|
}
|
||||||
@ -498,7 +465,7 @@ func TestColorPreview(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range negativeTests {
|
for _, test := range negativeTests {
|
||||||
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test)
|
res, err := markdown.RenderString(markup.NewTestRenderContext(), test)
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test)
|
assert.NoError(t, err, "Unexpected error in testcase: %q", test)
|
||||||
assert.NotContains(t, res, `<span class="color-preview" style="background-color: `, "Unexpected result in testcase %q", test)
|
assert.NotContains(t, res, `<span class="color-preview" style="background-color: `, "Unexpected result in testcase %q", test)
|
||||||
}
|
}
|
||||||
@ -573,7 +540,7 @@ func TestMathBlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testcases {
|
for _, test := range testcases {
|
||||||
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
|
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
||||||
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
||||||
}
|
}
|
||||||
@ -610,7 +577,7 @@ foo: bar
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testcases {
|
for _, test := range testcases {
|
||||||
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
|
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
||||||
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
||||||
}
|
}
|
||||||
@ -1003,11 +970,7 @@ space</p>
|
|||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.ForceHardLineBreak, true)()
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableInternalAttributes, true)()
|
||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
result, err := markdown.RenderString(&markup.RenderContext{
|
result, err := markdown.RenderString(markup.NewTestRenderContext(c.Links, util.Iif(c.IsWiki, map[string]string{"markupContentMode": "wiki"}, map[string]string{})), input)
|
||||||
Ctx: context.Background(),
|
|
||||||
Links: c.Links,
|
|
||||||
Metas: util.Iif(c.IsWiki, map[string]string{"markupContentMode": "wiki"}, map[string]string{}),
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %v", i)
|
assert.NoError(t, err, "Unexpected error in testcase: %v", i)
|
||||||
assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
|
assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
|
||||||
}
|
}
|
||||||
@ -1029,7 +992,7 @@ func TestAttention(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background()}, input)
|
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
||||||
}
|
}
|
||||||
@ -1062,6 +1025,6 @@ func BenchmarkSpecializedMarkdown(b *testing.B) {
|
|||||||
func BenchmarkMarkdownRender(b *testing.B) {
|
func BenchmarkMarkdownRender(b *testing.B) {
|
||||||
// 23202 50840 ns/op
|
// 23202 50840 ns/op
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = markdown.RenderString(&markup.RenderContext{Ctx: context.Background()}, "https://example.com\n- a\n- b\n")
|
_, _ = markdown.RenderString(markup.NewTestRenderContext(), "https://example.com\n- a\n- b\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image)
|
|||||||
// Check if the destination is a real link
|
// Check if the destination is a real link
|
||||||
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
|
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
|
||||||
v.Destination = []byte(giteautil.URLJoin(
|
v.Destination = []byte(giteautil.URLJoin(
|
||||||
ctx.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()),
|
ctx.RenderOptions.Links.ResolveMediaLink(ctx.IsMarkupContentWiki()),
|
||||||
strings.TrimLeft(string(v.Destination), "/"),
|
strings.TrimLeft(string(v.Destination), "/"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -143,15 +143,15 @@ func (r *Writer) resolveLink(kind, link string) string {
|
|||||||
kind = org.RegularLink{URL: link}.Kind()
|
kind = org.RegularLink{URL: link}.Kind()
|
||||||
}
|
}
|
||||||
|
|
||||||
base := r.Ctx.Links.Base
|
base := r.Ctx.RenderOptions.Links.Base
|
||||||
if r.Ctx.IsMarkupContentWiki() {
|
if r.Ctx.IsMarkupContentWiki() {
|
||||||
base = r.Ctx.Links.WikiLink()
|
base = r.Ctx.RenderOptions.Links.WikiLink()
|
||||||
} else if r.Ctx.Links.HasBranchInfo() {
|
} else if r.Ctx.RenderOptions.Links.HasBranchInfo() {
|
||||||
base = r.Ctx.Links.SrcLink()
|
base = r.Ctx.RenderOptions.Links.SrcLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "image" || kind == "video" {
|
if kind == "image" || kind == "video" {
|
||||||
base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsMarkupContentWiki())
|
base = r.Ctx.RenderOptions.Links.ResolveMediaLink(r.Ctx.IsMarkupContentWiki())
|
||||||
}
|
}
|
||||||
|
|
||||||
link = util.URLJoin(base, link)
|
link = util.URLJoin(base, link)
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -15,20 +15,21 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const AppURL = "http://localhost:3000/"
|
func TestMain(m *testing.M) {
|
||||||
|
setting.AppURL = "http://localhost:3000/"
|
||||||
|
setting.IsInTesting = true
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
func TestRender_StandardLinks(t *testing.T) {
|
func TestRender_StandardLinks(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
|
||||||
|
|
||||||
test := func(input, expected string, isWiki bool) {
|
test := func(input, expected string, isWiki bool) {
|
||||||
buffer, err := RenderString(&markup.RenderContext{
|
buffer, err := RenderString(markup.NewTestRenderContext(
|
||||||
Ctx: git.DefaultContext,
|
markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/relative-path",
|
Base: "/relative-path",
|
||||||
BranchPath: "branch/main",
|
BranchPath: "branch/main",
|
||||||
},
|
},
|
||||||
Metas: map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")},
|
map[string]string{"markupContentMode": util.Iif(isWiki, "wiki", "")},
|
||||||
}, input)
|
), input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -42,16 +43,13 @@ func TestRender_StandardLinks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_InternalLinks(t *testing.T) {
|
func TestRender_InternalLinks(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
|
||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := RenderString(&markup.RenderContext{
|
buffer, err := RenderString(markup.NewTestRenderContext(
|
||||||
Ctx: git.DefaultContext,
|
markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: "/relative-path",
|
Base: "/relative-path",
|
||||||
BranchPath: "branch/main",
|
BranchPath: "branch/main",
|
||||||
},
|
},
|
||||||
}, input)
|
), input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -67,15 +65,8 @@ func TestRender_InternalLinks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_Media(t *testing.T) {
|
func TestRender_Media(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
|
||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := RenderString(&markup.RenderContext{
|
buffer, err := RenderString(markup.NewTestRenderContext(markup.Links{Base: "./relative-path"}), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
Links: markup.Links{
|
|
||||||
Base: "./relative-path",
|
|
||||||
},
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
@ -113,12 +104,8 @@ func TestRender_Media(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_Source(t *testing.T) {
|
func TestRender_Source(t *testing.T) {
|
||||||
setting.AppURL = AppURL
|
|
||||||
|
|
||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
buffer, err := RenderString(&markup.RenderContext{
|
buffer, err := RenderString(markup.NewTestRenderContext(), input)
|
||||||
Ctx: git.DefaultContext,
|
|
||||||
}, input)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
@ -42,16 +43,16 @@ var RenderBehaviorForTesting struct {
|
|||||||
DisableInternalAttributes bool
|
DisableInternalAttributes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderContext represents a render context
|
type RenderOptions struct {
|
||||||
type RenderContext struct {
|
// relative path from tree root of the branch
|
||||||
Ctx context.Context
|
RelativePath string
|
||||||
RelativePath string // relative path from tree root of the branch
|
|
||||||
|
|
||||||
// eg: "orgmode", "asciicast", "console"
|
// eg: "orgmode", "asciicast", "console"
|
||||||
// for file mode, it could be left as empty, and will be detected by file extension in RelativePath
|
// for file mode, it could be left as empty, and will be detected by file extension in RelativePath
|
||||||
MarkupType string
|
MarkupType string
|
||||||
|
|
||||||
Links Links // special link references for rendering, especially when there is a branch/tree path
|
// special link references for rendering, especially when there is a branch/tree path
|
||||||
|
Links Links
|
||||||
|
|
||||||
// user&repo, format&style®exp (for external issue pattern), teams&org (for mention)
|
// user&repo, format&style®exp (for external issue pattern), teams&org (for mention)
|
||||||
// BranchNameSubURL (for iframe&asciicast)
|
// BranchNameSubURL (for iframe&asciicast)
|
||||||
@ -59,27 +60,95 @@ type RenderContext struct {
|
|||||||
// markdownLineBreakStyle (comment, document)
|
// markdownLineBreakStyle (comment, document)
|
||||||
Metas map[string]string
|
Metas map[string]string
|
||||||
|
|
||||||
GitRepo *git.Repository
|
// used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
|
||||||
Repo gitrepo.Repository
|
InStandalonePage bool
|
||||||
ShaExistCache map[string]bool
|
}
|
||||||
cancelFn func()
|
|
||||||
SidebarTocNode ast.Node
|
|
||||||
RenderMetaAs RenderMetaMode
|
|
||||||
InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
|
|
||||||
|
|
||||||
|
type RenderHelper struct {
|
||||||
|
gitRepo *git.Repository
|
||||||
|
repoFacade gitrepo.Repository
|
||||||
|
shaExistCache map[string]bool
|
||||||
|
cancelFn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderContext represents a render context
|
||||||
|
type RenderContext struct {
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
SidebarTocNode ast.Node
|
||||||
|
|
||||||
|
RenderHelper RenderHelper
|
||||||
|
RenderOptions RenderOptions
|
||||||
RenderInternal internal.RenderInternal
|
RenderInternal internal.RenderInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return ctx.ctx.Deadline()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) Done() <-chan struct{} {
|
||||||
|
return ctx.ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) Err() error {
|
||||||
|
return ctx.ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) Value(key any) any {
|
||||||
|
return ctx.ctx.Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ context.Context = (*RenderContext)(nil)
|
||||||
|
|
||||||
|
func NewRenderContext(ctx context.Context) *RenderContext {
|
||||||
|
return &RenderContext{ctx: ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) WithMarkupType(typ string) *RenderContext {
|
||||||
|
ctx.RenderOptions.MarkupType = typ
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) WithRelativePath(path string) *RenderContext {
|
||||||
|
ctx.RenderOptions.RelativePath = path
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) WithLinks(links Links) *RenderContext {
|
||||||
|
ctx.RenderOptions.Links = links
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) WithMetas(metas map[string]string) *RenderContext {
|
||||||
|
ctx.RenderOptions.Metas = metas
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) WithInStandalonePage(v bool) *RenderContext {
|
||||||
|
ctx.RenderOptions.InStandalonePage = v
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) WithGitRepo(r *git.Repository) *RenderContext {
|
||||||
|
ctx.RenderHelper.gitRepo = r
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContext) WithRepoFacade(r gitrepo.Repository) *RenderContext {
|
||||||
|
ctx.RenderHelper.repoFacade = r
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel runs any cleanup functions that have been registered for this Ctx
|
// Cancel runs any cleanup functions that have been registered for this Ctx
|
||||||
func (ctx *RenderContext) Cancel() {
|
func (ctx *RenderContext) Cancel() {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.ShaExistCache = map[string]bool{}
|
ctx.RenderHelper.shaExistCache = map[string]bool{}
|
||||||
if ctx.cancelFn == nil {
|
if ctx.RenderHelper.cancelFn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.cancelFn()
|
ctx.RenderHelper.cancelFn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCancel adds the provided fn as a Cleanup for this Ctx
|
// AddCancel adds the provided fn as a Cleanup for this Ctx
|
||||||
@ -87,38 +156,38 @@ func (ctx *RenderContext) AddCancel(fn func()) {
|
|||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
oldCancelFn := ctx.cancelFn
|
oldCancelFn := ctx.RenderHelper.cancelFn
|
||||||
if oldCancelFn == nil {
|
if oldCancelFn == nil {
|
||||||
ctx.cancelFn = fn
|
ctx.RenderHelper.cancelFn = fn
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.cancelFn = func() {
|
ctx.RenderHelper.cancelFn = func() {
|
||||||
defer oldCancelFn()
|
defer oldCancelFn()
|
||||||
fn()
|
fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *RenderContext) IsMarkupContentWiki() bool {
|
func (ctx *RenderContext) IsMarkupContentWiki() bool {
|
||||||
return ctx.Metas != nil && ctx.Metas["markupContentMode"] == "wiki"
|
return ctx.RenderOptions.Metas != nil && ctx.RenderOptions.Metas["markupContentMode"] == "wiki"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders markup file to HTML with all specific handling stuff.
|
// Render renders markup file to HTML with all specific handling stuff.
|
||||||
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
||||||
if ctx.MarkupType == "" && ctx.RelativePath != "" {
|
if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
|
||||||
ctx.MarkupType = DetectMarkupTypeByFileName(ctx.RelativePath)
|
ctx.RenderOptions.MarkupType = DetectMarkupTypeByFileName(ctx.RenderOptions.RelativePath)
|
||||||
if ctx.MarkupType == "" {
|
if ctx.RenderOptions.MarkupType == "" {
|
||||||
return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RelativePath)
|
return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer := renderers[ctx.MarkupType]
|
renderer := renderers[ctx.RenderOptions.MarkupType]
|
||||||
if renderer == nil {
|
if renderer == nil {
|
||||||
return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.MarkupType)
|
return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.RenderOptions.MarkupType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.RelativePath != "" {
|
if ctx.RenderOptions.RelativePath != "" {
|
||||||
if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
|
if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
|
||||||
if !ctx.InStandalonePage {
|
if !ctx.RenderOptions.InStandalonePage {
|
||||||
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
|
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
|
||||||
// otherwise, a <iframe> should be outputted to embed the external rendered page
|
// otherwise, a <iframe> should be outputted to embed the external rendered page
|
||||||
return renderIFrame(ctx, output)
|
return renderIFrame(ctx, output)
|
||||||
@ -151,10 +220,10 @@ width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden"
|
|||||||
sandbox="allow-scripts"
|
sandbox="allow-scripts"
|
||||||
></iframe>`,
|
></iframe>`,
|
||||||
setting.AppSubURL,
|
setting.AppSubURL,
|
||||||
url.PathEscape(ctx.Metas["user"]),
|
url.PathEscape(ctx.RenderOptions.Metas["user"]),
|
||||||
url.PathEscape(ctx.Metas["repo"]),
|
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
|
||||||
ctx.Metas["BranchNameSubURL"],
|
ctx.RenderOptions.Metas["BranchNameSubURL"],
|
||||||
url.PathEscape(ctx.RelativePath),
|
url.PathEscape(ctx.RenderOptions.RelativePath),
|
||||||
))
|
))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -176,7 +245,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
|
|||||||
pr1, pw1, close1 := pipes()
|
pr1, pw1, close1 := pipes()
|
||||||
defer close1()
|
defer close1()
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx.Ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
var pw2 io.WriteCloser = util.NopCloser{Writer: finalProcessor}
|
var pw2 io.WriteCloser = util.NopCloser{Writer: finalProcessor}
|
||||||
|
|
||||||
if r, ok := renderer.(ExternalRenderer); !ok || !r.SanitizerDisabled() {
|
if r, ok := renderer.(ExternalRenderer); !ok || !r.SanitizerDisabled() {
|
||||||
@ -230,3 +299,27 @@ func Init(ph *ProcessorHelper) {
|
|||||||
func ComposeSimpleDocumentMetas() map[string]string {
|
func ComposeSimpleDocumentMetas() map[string]string {
|
||||||
return map[string]string{"markdownLineBreakStyle": "document"}
|
return map[string]string{"markdownLineBreakStyle": "document"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTestRenderContext is a helper function to create a RenderContext for testing purpose
|
||||||
|
// It accepts string (RelativePath), Links, map[string]string (Metas), gitrepo.Repository
|
||||||
|
func NewTestRenderContext(a ...any) *RenderContext {
|
||||||
|
if !setting.IsInTesting {
|
||||||
|
panic("NewTestRenderContext should only be used in testing")
|
||||||
|
}
|
||||||
|
ctx := NewRenderContext(context.Background())
|
||||||
|
for _, v := range a {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
ctx = ctx.WithRelativePath(v)
|
||||||
|
case Links:
|
||||||
|
ctx = ctx.WithLinks(v)
|
||||||
|
case map[string]string:
|
||||||
|
ctx = ctx.WithMetas(v)
|
||||||
|
case gitrepo.Repository:
|
||||||
|
ctx = ctx.WithRepoFacade(v)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown type %T", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -125,15 +123,12 @@ func TestPushCommits_AvatarLink(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.GravatarSource = "https://secure.gravatar.com/avatar"
|
|
||||||
setting.OfflineMode = true
|
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
"/avatars/avatar2?size="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor),
|
"/avatars/ab53a2911ddf9b4817ac01ddcd3d975f?size="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor),
|
||||||
pushCommits.AvatarLink(db.DefaultContext, "user2@example.com"))
|
pushCommits.AvatarLink(db.DefaultContext, "user2@example.com"))
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("https://secure.gravatar.com/avatar/%x?d=identicon&s=%d", md5.Sum([]byte("nonexistent@example.com")), 28*setting.Avatar.RenderedSizeFactor),
|
"/assets/img/avatar_default.png",
|
||||||
pushCommits.AvatarLink(db.DefaultContext, "nonexistent@example.com"))
|
pushCommits.AvatarLink(db.DefaultContext, "nonexistent@example.com"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,10 +38,7 @@ func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string)
|
|||||||
cleanMsg := template.HTMLEscapeString(msg)
|
cleanMsg := template.HTMLEscapeString(msg)
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
fullMessage, err := markup.RenderCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), cleanMsg)
|
||||||
Ctx: ut.ctx,
|
|
||||||
Metas: metas,
|
|
||||||
}, cleanMsg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessage: %v", err)
|
log.Error("RenderCommitMessage: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -68,10 +65,7 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
|
|||||||
|
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
|
renderedMessage, err := markup.RenderCommitMessageSubject(markup.NewRenderContext(ut.ctx).WithMetas(metas), urlDefault, template.HTMLEscapeString(msgLine))
|
||||||
Ctx: ut.ctx,
|
|
||||||
Metas: metas,
|
|
||||||
}, urlDefault, template.HTMLEscapeString(msgLine))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessageSubject: %v", err)
|
log.Error("RenderCommitMessageSubject: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -93,10 +87,7 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
renderedMessage, err := markup.RenderCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(msgLine))
|
||||||
Ctx: ut.ctx,
|
|
||||||
Metas: metas,
|
|
||||||
}, template.HTMLEscapeString(msgLine))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessage: %v", err)
|
log.Error("RenderCommitMessage: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -115,10 +106,7 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
|
|||||||
|
|
||||||
// RenderIssueTitle renders issue/pull title with defined post processors
|
// RenderIssueTitle renders issue/pull title with defined post processors
|
||||||
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
||||||
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
renderedText, err := markup.RenderIssueTitle(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(text))
|
||||||
Ctx: ut.ctx,
|
|
||||||
Metas: metas,
|
|
||||||
}, template.HTMLEscapeString(text))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderIssueTitle: %v", err)
|
log.Error("RenderIssueTitle: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -186,7 +174,7 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
|
|||||||
|
|
||||||
// RenderEmoji renders html text with emoji post processors
|
// RenderEmoji renders html text with emoji post processors
|
||||||
func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
|
func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
|
||||||
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ut.ctx}, template.HTMLEscapeString(text))
|
renderedText, err := markup.RenderEmoji(markup.NewRenderContext(ut.ctx), template.HTMLEscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderEmoji: %v", err)
|
log.Error("RenderEmoji: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -208,10 +196,7 @@ func reactionToEmoji(reaction string) template.HTML {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
|
func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
|
||||||
output, err := markdown.RenderString(&markup.RenderContext{
|
output, err := markdown.RenderString(markup.NewRenderContext(ut.ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), input)
|
||||||
Ctx: ut.ctx,
|
|
||||||
Metas: markup.ComposeSimpleDocumentMetas(),
|
|
||||||
}, input)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderString: %v", err)
|
log.Error("RenderString: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,7 @@ copy_url = Copy URL
|
|||||||
copy_hash = Copy hash
|
copy_hash = Copy hash
|
||||||
copy_content = Copy content
|
copy_content = Copy content
|
||||||
copy_branch = Copy branch name
|
copy_branch = Copy branch name
|
||||||
|
copy_path = Copy path
|
||||||
copy_success = Copied!
|
copy_success = Copied!
|
||||||
copy_error = Copy failed
|
copy_error = Copy failed
|
||||||
copy_type_unsupported = This file type cannot be copied
|
copy_type_unsupported = This file type cannot be copied
|
||||||
@ -352,6 +353,7 @@ enable_update_checker = Enable Update Checker
|
|||||||
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
|
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
|
||||||
env_config_keys = Environment Configuration
|
env_config_keys = Environment Configuration
|
||||||
env_config_keys_prompt = The following environment variables will also be applied to your configuration file:
|
env_config_keys_prompt = The following environment variables will also be applied to your configuration file:
|
||||||
|
config_write_file_prompt = These configuration options will be written into: %s
|
||||||
|
|
||||||
[home]
|
[home]
|
||||||
nav_menu = Navigation Menu
|
nav_menu = Navigation Menu
|
||||||
@ -457,6 +459,7 @@ authorize_application = Authorize Application
|
|||||||
authorize_redirect_notice = You will be redirected to %s if you authorize this application.
|
authorize_redirect_notice = You will be redirected to %s if you authorize this application.
|
||||||
authorize_application_created_by = This application was created by %s.
|
authorize_application_created_by = This application was created by %s.
|
||||||
authorize_application_description = If you grant the access, it will be able to access and write to all your account information, including private repos and organisations.
|
authorize_application_description = If you grant the access, it will be able to access and write to all your account information, including private repos and organisations.
|
||||||
|
authorize_application_with_scopes = With scopes: %s
|
||||||
authorize_title = Authorize "%s" to access your account?
|
authorize_title = Authorize "%s" to access your account?
|
||||||
authorization_failed = Authorization failed
|
authorization_failed = Authorization failed
|
||||||
authorization_failed_desc = The authorization failed because we detected an invalid request. Please contact the maintainer of the app you have tried to authorize.
|
authorization_failed_desc = The authorization failed because we detected an invalid request. Please contact the maintainer of the app you have tried to authorize.
|
||||||
|
@ -99,9 +99,7 @@ func MarkdownRaw(ctx *context.APIContext) {
|
|||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
defer ctx.Req.Body.Close()
|
defer ctx.Req.Body.Close()
|
||||||
if err := markdown.RenderRaw(&markup.RenderContext{
|
if err := markdown.RenderRaw(markup.NewRenderContext(ctx), ctx.Req.Body, ctx.Resp); err != nil {
|
||||||
Ctx: ctx,
|
|
||||||
}, ctx.Req.Body, ctx.Resp); err != nil {
|
|
||||||
ctx.InternalServerError(err)
|
ctx.InternalServerError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -133,11 +133,6 @@ func DeleteBranch(ctx *context.APIContext) {
|
|||||||
|
|
||||||
branchName := ctx.PathParam("*")
|
branchName := ctx.PathParam("*")
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
|
||||||
ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check whether branches of this repository has been synced
|
// check whether branches of this repository has been synced
|
||||||
totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
|
totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
@ -28,13 +28,12 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
|
|||||||
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
||||||
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
||||||
|
|
||||||
renderCtx := &markup.RenderContext{
|
renderCtx := markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithLinks(markup.Links{AbsolutePrefix: true}).
|
||||||
Links: markup.Links{AbsolutePrefix: true},
|
WithMarkupType(markdown.MarkupName)
|
||||||
MarkupType: markdown.MarkupName,
|
|
||||||
}
|
|
||||||
if urlPathContext != "" {
|
if urlPathContext != "" {
|
||||||
renderCtx.Links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
renderCtx.RenderOptions.Links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == "" || mode == "markdown" {
|
if mode == "" || mode == "markdown" {
|
||||||
@ -47,15 +46,14 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
|
|||||||
switch mode {
|
switch mode {
|
||||||
case "gfm": // legacy mode, do nothing
|
case "gfm": // legacy mode, do nothing
|
||||||
case "comment":
|
case "comment":
|
||||||
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "comment"}
|
renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "comment"})
|
||||||
case "wiki":
|
case "wiki":
|
||||||
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"}
|
renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document", "markupContentMode": "wiki"})
|
||||||
case "file":
|
case "file":
|
||||||
// render the repo file content by its extension
|
// render the repo file content by its extension
|
||||||
renderCtx.Metas = map[string]string{"markdownLineBreakStyle": "document"}
|
renderCtx = renderCtx.WithMetas(map[string]string{"markdownLineBreakStyle": "document"}).
|
||||||
renderCtx.MarkupType = ""
|
WithMarkupType("").
|
||||||
renderCtx.RelativePath = filePath
|
WithRelativePath(filePath)
|
||||||
renderCtx.InStandalonePage = true
|
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
||||||
return
|
return
|
||||||
@ -70,17 +68,17 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPa
|
|||||||
refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
|
refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
|
||||||
refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
|
refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
|
||||||
|
|
||||||
renderCtx.Links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}
|
renderCtx = renderCtx.WithLinks(markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir})
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo != nil && repo.Repository != nil {
|
if repo != nil && repo.Repository != nil {
|
||||||
renderCtx.Repo = repo.Repository
|
renderCtx = renderCtx.WithRepoFacade(repo.Repository)
|
||||||
if mode == "file" {
|
if mode == "file" {
|
||||||
renderCtx.Metas = repo.Repository.ComposeDocumentMetas(ctx)
|
renderCtx = renderCtx.WithMetas(repo.Repository.ComposeDocumentMetas(ctx))
|
||||||
} else if mode == "wiki" {
|
} else if mode == "wiki" {
|
||||||
renderCtx.Metas = repo.Repository.ComposeWikiMetas(ctx)
|
renderCtx = renderCtx.WithMetas(repo.Repository.ComposeWikiMetas(ctx))
|
||||||
} else if mode == "comment" {
|
} else if mode == "comment" {
|
||||||
renderCtx.Metas = repo.Repository.ComposeMetas(ctx)
|
renderCtx = renderCtx.WithMetas(repo.Repository.ComposeMetas(ctx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil {
|
if err := markup.Render(renderCtx, strings.NewReader(text), ctx.Resp); err != nil {
|
||||||
|
@ -98,13 +98,24 @@ func InfoOAuth(ctx *context.Context) {
|
|||||||
|
|
||||||
response := &userInfoResponse{
|
response := &userInfoResponse{
|
||||||
Sub: fmt.Sprint(ctx.Doer.ID),
|
Sub: fmt.Sprint(ctx.Doer.ID),
|
||||||
Name: ctx.Doer.FullName,
|
Name: ctx.Doer.DisplayName(),
|
||||||
PreferredUsername: ctx.Doer.Name,
|
PreferredUsername: ctx.Doer.Name,
|
||||||
Email: ctx.Doer.Email,
|
Email: ctx.Doer.Email,
|
||||||
Picture: ctx.Doer.AvatarLink(ctx),
|
Picture: ctx.Doer.AvatarLink(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
groups, err := oauth2_provider.GetOAuthGroupsForUser(ctx, ctx.Doer)
|
var accessTokenScope auth.AccessTokenScope
|
||||||
|
if auHead := ctx.Req.Header.Get("Authorization"); auHead != "" {
|
||||||
|
auths := strings.Fields(auHead)
|
||||||
|
if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") {
|
||||||
|
accessTokenScope, _ = auth_service.GetOAuthAccessTokenScopeAndUserID(ctx, auths[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since version 1.22 does not verify if groups should be public-only,
|
||||||
|
// onlyPublicGroups will be set only if 'public-only' is included in a valid scope
|
||||||
|
onlyPublicGroups, _ := accessTokenScope.PublicOnly()
|
||||||
|
groups, err := oauth2_provider.GetOAuthGroupsForUser(ctx, ctx.Doer, onlyPublicGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Oauth groups for user", err)
|
ctx.ServerError("Oauth groups for user", err)
|
||||||
return
|
return
|
||||||
@ -304,6 +315,9 @@ func AuthorizeOAuth(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if additional scopes
|
||||||
|
ctx.Data["AdditionalScopes"] = oauth2_provider.GrantAdditionalScopes(form.Scope) != auth.AccessTokenScopeAll
|
||||||
|
|
||||||
// show authorize page to grant access
|
// show authorize page to grant access
|
||||||
ctx.Data["Application"] = app
|
ctx.Data["Application"] = app
|
||||||
ctx.Data["RedirectURI"] = form.RedirectURI
|
ctx.Data["RedirectURI"] = form.RedirectURI
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/services/oauth2_provider"
|
"code.gitea.io/gitea/services/oauth2_provider"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
@ -66,25 +65,7 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) {
|
|||||||
|
|
||||||
// Scopes: openid profile email
|
// Scopes: openid profile email
|
||||||
oidcToken = createAndParseToken(t, grants[0])
|
oidcToken = createAndParseToken(t, grants[0])
|
||||||
assert.Equal(t, user.Name, oidcToken.Name)
|
assert.Equal(t, user.DisplayName(), oidcToken.Name)
|
||||||
assert.Equal(t, user.Name, oidcToken.PreferredUsername)
|
|
||||||
assert.Equal(t, user.HTMLURL(), oidcToken.Profile)
|
|
||||||
assert.Equal(t, user.AvatarLink(db.DefaultContext), oidcToken.Picture)
|
|
||||||
assert.Equal(t, user.Website, oidcToken.Website)
|
|
||||||
assert.Equal(t, user.UpdatedUnix, oidcToken.UpdatedAt)
|
|
||||||
assert.Equal(t, user.Email, oidcToken.Email)
|
|
||||||
assert.Equal(t, user.IsActive, oidcToken.EmailVerified)
|
|
||||||
|
|
||||||
// set DefaultShowFullName to true
|
|
||||||
oldDefaultShowFullName := setting.UI.DefaultShowFullName
|
|
||||||
setting.UI.DefaultShowFullName = true
|
|
||||||
defer func() {
|
|
||||||
setting.UI.DefaultShowFullName = oldDefaultShowFullName
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Scopes: openid profile email
|
|
||||||
oidcToken = createAndParseToken(t, grants[0])
|
|
||||||
assert.Equal(t, user.FullName, oidcToken.Name)
|
|
||||||
assert.Equal(t, user.Name, oidcToken.PreferredUsername)
|
assert.Equal(t, user.Name, oidcToken.PreferredUsername)
|
||||||
assert.Equal(t, user.HTMLURL(), oidcToken.Profile)
|
assert.Equal(t, user.HTMLURL(), oidcToken.Profile)
|
||||||
assert.Equal(t, user.AvatarLink(db.DefaultContext), oidcToken.Picture)
|
assert.Equal(t, user.AvatarLink(db.DefaultContext), oidcToken.Picture)
|
||||||
|
@ -51,16 +51,14 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
|
|||||||
// renderMarkdown creates a minimal markdown render context from an action.
|
// renderMarkdown creates a minimal markdown render context from an action.
|
||||||
// If rendering fails, the original markdown text is returned
|
// If rendering fails, the original markdown text is returned
|
||||||
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML {
|
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) template.HTML {
|
||||||
markdownCtx := &markup.RenderContext{
|
markdownCtx := markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: act.GetRepoLink(ctx),
|
Base: act.GetRepoLink(ctx),
|
||||||
},
|
}).
|
||||||
Metas: map[string]string{ // FIXME: not right here, it should use issue to compose the metas
|
WithMetas(map[string]string{ // FIXME: not right here, it should use issue to compose the metas
|
||||||
"user": act.GetRepoUserName(ctx),
|
"user": act.GetRepoUserName(ctx),
|
||||||
"repo": act.GetRepoName(ctx),
|
"repo": act.GetRepoName(ctx),
|
||||||
},
|
})
|
||||||
}
|
|
||||||
markdown, err := markdown.RenderString(markdownCtx, content)
|
markdown, err := markdown.RenderString(markdownCtx, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return templates.SanitizeHTML(content) // old code did so: use SanitizeHTML to render in tmpl
|
return templates.SanitizeHTML(content) // old code did so: use SanitizeHTML to render in tmpl
|
||||||
@ -296,14 +294,13 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
link := &feeds.Link{Href: rel.HTMLURL()}
|
link := &feeds.Link{Href: rel.HTMLURL()}
|
||||||
content, err = markdown.RenderString(&markup.RenderContext{
|
content, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithRepoFacade(rel.Repo).
|
||||||
Repo: rel.Repo,
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: rel.Repo.Link(),
|
Base: rel.Repo.Link(),
|
||||||
},
|
}).
|
||||||
Metas: rel.Repo.ComposeMetas(ctx),
|
WithMetas(rel.Repo.ComposeMetas(ctx)),
|
||||||
}, rel.Note)
|
rel.Note)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,10 @@ func showUserFeed(ctx *context.Context, formatType string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxUserDescription, err := markdown.RenderString(&markup.RenderContext{
|
ctxUserDescription, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithLinks(markup.Links{Base: ctx.ContextUser.HTMLURL()}).
|
||||||
Links: markup.Links{
|
WithMetas(markup.ComposeSimpleDocumentMetas()),
|
||||||
Base: ctx.ContextUser.HTMLURL(),
|
ctx.ContextUser.Description)
|
||||||
},
|
|
||||||
Metas: markup.ComposeSimpleDocumentMetas(),
|
|
||||||
}, ctx.ContextUser.Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -180,17 +180,16 @@ func prepareOrgProfileReadme(ctx *context.Context, viewRepositories bool) bool {
|
|||||||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||||
log.Error("failed to GetBlobContent: %v", err)
|
log.Error("failed to GetBlobContent: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if profileContent, err := markdown.RenderString(&markup.RenderContext{
|
if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithGitRepo(profileGitRepo).
|
||||||
GitRepo: profileGitRepo,
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
// Pass repo link to markdown render for the full link of media elements.
|
// Pass repo link to markdown render for the full link of media elements.
|
||||||
// The profile of default branch would be shown.
|
// The profile of default branch would be shown.
|
||||||
Base: profileDbRepo.Link(),
|
Base: profileDbRepo.Link(),
|
||||||
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
||||||
},
|
}).
|
||||||
Metas: markup.ComposeSimpleDocumentMetas(),
|
WithMetas(markup.ComposeSimpleDocumentMetas()),
|
||||||
}, bytes); err != nil {
|
bytes); err != nil {
|
||||||
log.Error("failed to RenderString: %v", err)
|
log.Error("failed to RenderString: %v", err)
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["ProfileReadme"] = profileContent
|
ctx.Data["ProfileReadme"] = profileContent
|
||||||
|
@ -392,16 +392,15 @@ func Diff(ctx *context.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.Data["NoteCommit"] = note.Commit
|
ctx.Data["NoteCommit"] = note.Commit
|
||||||
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
||||||
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
|
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{
|
||||||
Base: ctx.Repo.RepoLink,
|
Base: ctx.Repo.RepoLink,
|
||||||
BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
|
BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
|
||||||
},
|
}).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Repo: ctx.Repo.Repository,
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
Ctx: ctx,
|
template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
||||||
}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderCommitMessage", err)
|
ctx.ServerError("RenderCommitMessage", err)
|
||||||
return
|
return
|
||||||
|
@ -151,7 +151,7 @@ func setCsvCompareContext(ctx *context.Context) {
|
|||||||
return csvReader, reader, err
|
return csvReader, reader, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseReader, baseBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.OldName}, baseBlob)
|
baseReader, baseBlobCloser, err := csvReaderFromCommit(markup.NewRenderContext(ctx).WithRelativePath(diffFile.OldName), baseBlob)
|
||||||
if baseBlobCloser != nil {
|
if baseBlobCloser != nil {
|
||||||
defer baseBlobCloser.Close()
|
defer baseBlobCloser.Close()
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ func setCsvCompareContext(ctx *context.Context) {
|
|||||||
return CsvDiffResult{nil, "unable to load file"}
|
return CsvDiffResult{nil, "unable to load file"}
|
||||||
}
|
}
|
||||||
|
|
||||||
headReader, headBlobCloser, err := csvReaderFromCommit(&markup.RenderContext{Ctx: ctx, RelativePath: diffFile.Name}, headBlob)
|
headReader, headBlobCloser, err := csvReaderFromCommit(markup.NewRenderContext(ctx).WithRelativePath(diffFile.Name), headBlob)
|
||||||
if headBlobCloser != nil {
|
if headBlobCloser != nil {
|
||||||
defer headBlobCloser.Close()
|
defer headBlobCloser.Close()
|
||||||
}
|
}
|
||||||
|
@ -366,15 +366,12 @@ func UpdateIssueContent(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := markdown.RenderString(&markup.RenderContext{
|
content, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.FormString("context")}).
|
||||||
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
issue.Content)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, issue.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -267,15 +267,12 @@ func UpdateCommentContent(ctx *context.Context) {
|
|||||||
|
|
||||||
var renderedContent template.HTML
|
var renderedContent template.HTML
|
||||||
if comment.Content != "" {
|
if comment.Content != "" {
|
||||||
renderedContent, err = markdown.RenderString(&markup.RenderContext{
|
renderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.FormString("context")}).
|
||||||
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
comment.Content)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, comment.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -359,15 +359,12 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.Data["IssueWatch"] = iw
|
ctx.Data["IssueWatch"] = iw
|
||||||
issue.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
issue.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
||||||
Base: ctx.Repo.RepoLink,
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
issue.Content)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, issue.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -467,15 +464,14 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
comment.Issue = issue
|
comment.Issue = issue
|
||||||
|
|
||||||
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
|
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
|
||||||
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{
|
||||||
Base: ctx.Repo.RepoLink,
|
Base: ctx.Repo.RepoLink,
|
||||||
},
|
}).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Repo: ctx.Repo.Repository,
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
Ctx: ctx,
|
comment.Content)
|
||||||
}, comment.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -550,15 +546,12 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if comment.Type.HasContentSupport() {
|
} else if comment.Type.HasContentSupport() {
|
||||||
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
comment.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
||||||
Base: ctx.Repo.RepoLink,
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
comment.Content)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, comment.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -79,15 +79,12 @@ func Milestones(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range miles {
|
for _, m := range miles {
|
||||||
m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
m.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
||||||
Base: ctx.Repo.RepoLink,
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
m.Content)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, m.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -268,15 +265,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
milestone.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
||||||
Base: ctx.Repo.RepoLink,
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
milestone.Content)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, milestone.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -92,15 +92,12 @@ func Projects(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range projects {
|
for i := range projects {
|
||||||
projects[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
projects[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
||||||
Base: ctx.Repo.RepoLink,
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
projects[i].Description)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, projects[i].Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
@ -425,15 +422,12 @@ func ViewProject(ctx *context.Context) {
|
|||||||
ctx.Data["SelectLabels"] = selectLabels
|
ctx.Data["SelectLabels"] = selectLabels
|
||||||
ctx.Data["AssigneeID"] = assigneeID
|
ctx.Data["AssigneeID"] = assigneeID
|
||||||
|
|
||||||
project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
project.RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
||||||
Base: ctx.Repo.RepoLink,
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
project.Description)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, project.Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -349,6 +349,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !baseGitRepo.IsBranchExist(pull.BaseBranch) {
|
if !baseGitRepo.IsBranchExist(pull.BaseBranch) {
|
||||||
|
ctx.Data["BaseBranchNotExist"] = true
|
||||||
ctx.Data["IsPullRequestBroken"] = true
|
ctx.Data["IsPullRequestBroken"] = true
|
||||||
ctx.Data["BaseTarget"] = pull.BaseBranch
|
ctx.Data["BaseTarget"] = pull.BaseBranch
|
||||||
ctx.Data["HeadTarget"] = pull.HeadBranch
|
ctx.Data["HeadTarget"] = pull.HeadBranch
|
||||||
|
@ -114,15 +114,12 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
|
|||||||
cacheUsers[r.PublisherID] = r.Publisher
|
cacheUsers[r.PublisherID] = r.Publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
r.RenderedNote, err = markdown.RenderString(&markup.RenderContext{
|
r.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink}).
|
||||||
Base: ctx.Repo.RepoLink,
|
WithMetas(ctx.Repo.Repository.ComposeMetas(ctx)).
|
||||||
},
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
WithRepoFacade(ctx.Repo.Repository),
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
r.Note)
|
||||||
Repo: ctx.Repo.Repository,
|
|
||||||
Ctx: ctx,
|
|
||||||
}, r.Note)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -56,18 +56,17 @@ func RenderFile(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = markup.Render(&markup.RenderContext{
|
err = markup.Render(markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithRelativePath(ctx.Repo.TreePath).
|
||||||
RelativePath: ctx.Repo.TreePath,
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: ctx.Repo.RepoLink,
|
Base: ctx.Repo.RepoLink,
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
TreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
},
|
}).
|
||||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
|
WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
WithGitRepo(ctx.Repo.GitRepo).
|
||||||
InStandalonePage: true,
|
WithInStandalonePage(true),
|
||||||
}, rd, ctx.Resp)
|
rd, ctx.Resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
|
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
|
||||||
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
|
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
|
||||||
|
@ -310,18 +310,17 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
|
|||||||
ctx.Data["IsMarkup"] = true
|
ctx.Data["IsMarkup"] = true
|
||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = markupType
|
||||||
|
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithMarkupType(markupType).
|
||||||
MarkupType: markupType,
|
WithRelativePath(path.Join(ctx.Repo.TreePath, readmeFile.Name())). // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
|
||||||
RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.Name()), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: ctx.Repo.RepoLink,
|
Base: ctx.Repo.RepoLink,
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||||
TreePath: path.Join(ctx.Repo.TreePath, subfolder),
|
TreePath: path.Join(ctx.Repo.TreePath, subfolder),
|
||||||
},
|
}).
|
||||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
|
WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
WithGitRepo(ctx.Repo.GitRepo),
|
||||||
}, rd)
|
rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
||||||
delete(ctx.Data, "IsMarkup")
|
delete(ctx.Data, "IsMarkup")
|
||||||
@ -514,18 +513,17 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = markupType
|
||||||
metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
|
metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
|
||||||
metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithMarkupType(markupType).
|
||||||
MarkupType: markupType,
|
WithRelativePath(ctx.Repo.TreePath).
|
||||||
RelativePath: ctx.Repo.TreePath,
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: ctx.Repo.RepoLink,
|
Base: ctx.Repo.RepoLink,
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
TreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
},
|
}).
|
||||||
Metas: metas,
|
WithMetas(metas).
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
WithGitRepo(ctx.Repo.GitRepo),
|
||||||
}, rd)
|
rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Render", err)
|
ctx.ServerError("Render", err)
|
||||||
return
|
return
|
||||||
@ -606,18 +604,17 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||||
ctx.Data["IsMarkup"] = true
|
ctx.Data["IsMarkup"] = true
|
||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = markupType
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithMarkupType(markupType).
|
||||||
MarkupType: markupType,
|
WithRelativePath(ctx.Repo.TreePath).
|
||||||
RelativePath: ctx.Repo.TreePath,
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
Base: ctx.Repo.RepoLink,
|
Base: ctx.Repo.RepoLink,
|
||||||
BranchPath: ctx.Repo.BranchNameSubURL(),
|
BranchPath: ctx.Repo.BranchNameSubURL(),
|
||||||
TreePath: path.Dir(ctx.Repo.TreePath),
|
TreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
},
|
}).
|
||||||
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
|
WithMetas(ctx.Repo.Repository.ComposeDocumentMetas(ctx)).
|
||||||
GitRepo: ctx.Repo.GitRepo,
|
WithGitRepo(ctx.Repo.GitRepo),
|
||||||
}, rd)
|
rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Render", err)
|
ctx.ServerError("Render", err)
|
||||||
return
|
return
|
||||||
|
@ -288,13 +288,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
|||||||
footerContent = data
|
footerContent = data
|
||||||
}
|
}
|
||||||
|
|
||||||
rctx := &markup.RenderContext{
|
rctx := markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithMetas(ctx.Repo.Repository.ComposeWikiMetas(ctx)).
|
||||||
Metas: ctx.Repo.Repository.ComposeWikiMetas(ctx),
|
WithLinks(markup.Links{Base: ctx.Repo.RepoLink})
|
||||||
Links: markup.Links{
|
|
||||||
Base: ctx.Repo.RepoLink,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
buf := &strings.Builder{}
|
buf := &strings.Builder{}
|
||||||
|
|
||||||
renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
|
renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
|
||||||
|
@ -49,10 +49,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
ctx.Data["OpenIDs"] = openIDs
|
ctx.Data["OpenIDs"] = openIDs
|
||||||
if len(ctx.ContextUser.Description) != 0 {
|
if len(ctx.ContextUser.Description) != 0 {
|
||||||
content, err := markdown.RenderString(&markup.RenderContext{
|
content, err := markdown.RenderString(markup.NewRenderContext(ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), ctx.ContextUser.Description)
|
||||||
Metas: markup.ComposeSimpleDocumentMetas(),
|
|
||||||
Ctx: ctx,
|
|
||||||
}, ctx.ContextUser.Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -257,14 +257,11 @@ func Milestones(ctx *context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
milestones[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
milestones[i].RenderedContent, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Links: markup.Links{
|
WithLinks(markup.Links{Base: milestones[i].Repo.Link()}).
|
||||||
Base: milestones[i].Repo.Link(),
|
WithMetas(milestones[i].Repo.ComposeMetas(ctx)).
|
||||||
},
|
WithRepoFacade(milestones[i].Repo),
|
||||||
Metas: milestones[i].Repo.ComposeMetas(ctx),
|
milestones[i].Content)
|
||||||
Ctx: ctx,
|
|
||||||
Repo: milestones[i].Repo,
|
|
||||||
}, milestones[i].Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -246,10 +246,9 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
|
|||||||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||||
log.Error("failed to GetBlobContent: %v", err)
|
log.Error("failed to GetBlobContent: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if profileContent, err := markdown.RenderString(&markup.RenderContext{
|
if profileContent, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithGitRepo(profileGitRepo).
|
||||||
GitRepo: profileGitRepo,
|
WithLinks(markup.Links{
|
||||||
Links: markup.Links{
|
|
||||||
// Give the repo link to the markdown render for the full link of media element.
|
// Give the repo link to the markdown render for the full link of media element.
|
||||||
// the media link usually be like /[user]/[repoName]/media/branch/[branchName],
|
// the media link usually be like /[user]/[repoName]/media/branch/[branchName],
|
||||||
// Eg. /Tom/.profile/media/branch/main
|
// Eg. /Tom/.profile/media/branch/main
|
||||||
@ -257,8 +256,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
|
|||||||
// https://docs.gitea.com/usage/profile-readme
|
// https://docs.gitea.com/usage/profile-readme
|
||||||
Base: profileDbRepo.Link(),
|
Base: profileDbRepo.Link(),
|
||||||
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
||||||
},
|
}),
|
||||||
}, bytes); err != nil {
|
bytes); err != nil {
|
||||||
log.Error("failed to RenderString: %v", err)
|
log.Error("failed to RenderString: %v", err)
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["ProfileReadme"] = profileContent
|
ctx.Data["ProfileReadme"] = profileContent
|
||||||
|
@ -561,7 +561,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
||||||
}, optSignInIgnoreCsrf, reqSignIn)
|
}, optSignInIgnoreCsrf, reqSignIn)
|
||||||
|
|
||||||
m.Methods("GET, OPTIONS", "/userinfo", optionsCorsHandler(), optSignInIgnoreCsrf, auth.InfoOAuth)
|
m.Methods("GET, POST, OPTIONS", "/userinfo", optionsCorsHandler(), optSignInIgnoreCsrf, auth.InfoOAuth)
|
||||||
m.Methods("POST, OPTIONS", "/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), optSignInIgnoreCsrf, auth.AccessTokenOAuth)
|
m.Methods("POST, OPTIONS", "/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), optSignInIgnoreCsrf, auth.AccessTokenOAuth)
|
||||||
m.Methods("GET, OPTIONS", "/keys", optionsCorsHandler(), optSignInIgnoreCsrf, auth.OIDCKeys)
|
m.Methods("GET, OPTIONS", "/keys", optionsCorsHandler(), optSignInIgnoreCsrf, auth.OIDCKeys)
|
||||||
m.Methods("POST, OPTIONS", "/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), optSignInIgnoreCsrf, auth.IntrospectOAuth)
|
m.Methods("POST, OPTIONS", "/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), optSignInIgnoreCsrf, auth.IntrospectOAuth)
|
||||||
|
@ -83,7 +83,12 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
|
|||||||
return 0, fmt.Errorf("split token failed")
|
return 0, fmt.Errorf("split token failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := jwt.ParseWithClaims(parts[1], &actionsClaims{}, func(t *jwt.Token) (any, error) {
|
return TokenToTaskID(parts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenToTaskID returns the TaskID associated with the provided JWT token
|
||||||
|
func TokenToTaskID(token string) (int64, error) {
|
||||||
|
parsedToken, err := jwt.ParseWithClaims(token, &actionsClaims{}, func(t *jwt.Token) (any, error) {
|
||||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||||
}
|
}
|
||||||
@ -93,8 +98,8 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c, ok := token.Claims.(*actionsClaims)
|
c, ok := parsedToken.Claims.(*actionsClaims)
|
||||||
if !token.Valid || !ok {
|
if !parsedToken.Valid || !ok {
|
||||||
return 0, fmt.Errorf("invalid token claim")
|
return 0, fmt.Errorf("invalid token claim")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,8 +77,8 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
|||||||
log.Trace("Basic Authorization: Attempting login with username as token")
|
log.Trace("Basic Authorization: Attempting login with username as token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check oauth2 token
|
// get oauth2 token's user's ID
|
||||||
uid := CheckOAuthAccessToken(req.Context(), authToken)
|
_, uid := GetOAuthAccessTokenScopeAndUserID(req.Context(), authToken)
|
||||||
if uid != 0 {
|
if uid != 0 {
|
||||||
log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
|
log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
"code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/oauth2_provider"
|
"code.gitea.io/gitea/services/oauth2_provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,33 +26,47 @@ var (
|
|||||||
_ Method = &OAuth2{}
|
_ Method = &OAuth2{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckOAuthAccessToken returns uid of user from oauth token
|
// GetOAuthAccessTokenScopeAndUserID returns access token scope and user id
|
||||||
func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 {
|
func GetOAuthAccessTokenScopeAndUserID(ctx context.Context, accessToken string) (auth_model.AccessTokenScope, int64) {
|
||||||
|
var accessTokenScope auth_model.AccessTokenScope
|
||||||
if !setting.OAuth2.Enabled {
|
if !setting.OAuth2.Enabled {
|
||||||
return 0
|
return accessTokenScope, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// JWT tokens require a ".", if the token isn't like that, return early
|
// JWT tokens require a ".", if the token isn't like that, return early
|
||||||
if !strings.Contains(accessToken, ".") {
|
if !strings.Contains(accessToken, ".") {
|
||||||
return 0
|
return accessTokenScope, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := oauth2_provider.ParseToken(accessToken, oauth2_provider.DefaultSigningKey)
|
token, err := oauth2_provider.ParseToken(accessToken, oauth2_provider.DefaultSigningKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("oauth2.ParseToken: %v", err)
|
log.Trace("oauth2.ParseToken: %v", err)
|
||||||
return 0
|
return accessTokenScope, 0
|
||||||
}
|
}
|
||||||
var grant *auth_model.OAuth2Grant
|
var grant *auth_model.OAuth2Grant
|
||||||
if grant, err = auth_model.GetOAuth2GrantByID(ctx, token.GrantID); err != nil || grant == nil {
|
if grant, err = auth_model.GetOAuth2GrantByID(ctx, token.GrantID); err != nil || grant == nil {
|
||||||
return 0
|
return accessTokenScope, 0
|
||||||
}
|
}
|
||||||
if token.Kind != oauth2_provider.KindAccessToken {
|
if token.Kind != oauth2_provider.KindAccessToken {
|
||||||
return 0
|
return accessTokenScope, 0
|
||||||
}
|
}
|
||||||
if token.ExpiresAt.Before(time.Now()) || token.IssuedAt.After(time.Now()) {
|
if token.ExpiresAt.Before(time.Now()) || token.IssuedAt.After(time.Now()) {
|
||||||
return 0
|
return accessTokenScope, 0
|
||||||
}
|
}
|
||||||
return grant.UserID
|
accessTokenScope = oauth2_provider.GrantAdditionalScopes(grant.Scope)
|
||||||
|
return accessTokenScope, grant.UserID
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckTaskIsRunning verifies that the TaskID corresponds to a running task
|
||||||
|
func CheckTaskIsRunning(ctx context.Context, taskID int64) bool {
|
||||||
|
// Verify the task exists
|
||||||
|
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that it's running
|
||||||
|
return task.Status == actions_model.StatusRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth2 implements the Auth interface and authenticates requests
|
// OAuth2 implements the Auth interface and authenticates requests
|
||||||
@ -97,10 +112,20 @@ func parseToken(req *http.Request) (string, bool) {
|
|||||||
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 {
|
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 {
|
||||||
// Let's see if token is valid.
|
// Let's see if token is valid.
|
||||||
if strings.Contains(tokenSHA, ".") {
|
if strings.Contains(tokenSHA, ".") {
|
||||||
uid := CheckOAuthAccessToken(ctx, tokenSHA)
|
// First attempt to decode an actions JWT, returning the actions user
|
||||||
|
if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil {
|
||||||
|
if CheckTaskIsRunning(ctx, taskID) {
|
||||||
|
store.GetData()["IsActionsToken"] = true
|
||||||
|
store.GetData()["ActionsTaskID"] = taskID
|
||||||
|
return user_model.ActionsUserID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, check if this is an OAuth access token
|
||||||
|
accessTokenScope, uid := GetOAuthAccessTokenScopeAndUserID(ctx, tokenSHA)
|
||||||
if uid != 0 {
|
if uid != 0 {
|
||||||
store.GetData()["IsApiToken"] = true
|
store.GetData()["IsApiToken"] = true
|
||||||
store.GetData()["ApiTokenScope"] = auth_model.AccessTokenScopeAll // fallback to all
|
store.GetData()["ApiTokenScope"] = accessTokenScope
|
||||||
}
|
}
|
||||||
return uid
|
return uid
|
||||||
}
|
}
|
||||||
|
55
services/auth/oauth2_test.go
Normal file
55
services/auth/oauth2_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
"code.gitea.io/gitea/services/actions"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserIDFromToken(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
t.Run("Actions JWT", func(t *testing.T) {
|
||||||
|
const RunningTaskID = 47
|
||||||
|
token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ds := make(middleware.ContextData)
|
||||||
|
|
||||||
|
o := OAuth2{}
|
||||||
|
uid := o.userIDFromToken(context.Background(), token, ds)
|
||||||
|
assert.Equal(t, int64(user_model.ActionsUserID), uid)
|
||||||
|
assert.Equal(t, ds["IsActionsToken"], true)
|
||||||
|
assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckTaskIsRunning(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
TaskID int64
|
||||||
|
Expected bool
|
||||||
|
}{
|
||||||
|
"Running": {TaskID: 47, Expected: true},
|
||||||
|
"Missing": {TaskID: 1, Expected: false},
|
||||||
|
"Cancelled": {TaskID: 46, Expected: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name := range cases {
|
||||||
|
c := cases[name]
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
actual := CheckTaskIsRunning(context.Background(), c.TaskID)
|
||||||
|
assert.Equal(t, c.Expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -259,9 +259,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
|||||||
|
|
||||||
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||||
if len(ctx.ContextUser.Description) != 0 {
|
if len(ctx.ContextUser.Description) != 0 {
|
||||||
content, err := markdown.RenderString(&markup.RenderContext{
|
content, err := markdown.RenderString(markup.NewRenderContext(ctx), ctx.ContextUser.Description)
|
||||||
Ctx: ctx,
|
|
||||||
}, ctx.ContextUser.Description)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderString", err)
|
ctx.ServerError("RenderString", err)
|
||||||
return
|
return
|
||||||
|
@ -393,14 +393,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetPushMirrorsByRepoID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Repo.Repository = repo
|
ctx.Repo.Repository = repo
|
||||||
ctx.Data["PushMirrors"] = pushMirrors
|
|
||||||
ctx.Data["RepoName"] = ctx.Repo.Repository.Name
|
ctx.Data["RepoName"] = ctx.Repo.Repository.Name
|
||||||
ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
|
ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
|
||||||
|
|
||||||
|
@ -219,15 +219,11 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is the body of the new issue or comment, not the mail body
|
// This is the body of the new issue or comment, not the mail body
|
||||||
body, err := markdown.RenderString(&markup.RenderContext{
|
body, err := markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithRepoFacade(ctx.Issue.Repo).
|
||||||
Repo: ctx.Issue.Repo,
|
WithLinks(markup.Links{AbsolutePrefix: true, Base: ctx.Issue.Repo.HTMLURL()}).
|
||||||
Links: markup.Links{
|
WithMetas(ctx.Issue.Repo.ComposeMetas(ctx)),
|
||||||
AbsolutePrefix: true,
|
ctx.Content)
|
||||||
Base: ctx.Issue.Repo.HTMLURL(),
|
|
||||||
},
|
|
||||||
Metas: ctx.Issue.Repo.ComposeMetas(ctx),
|
|
||||||
}, ctx.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -56,14 +56,11 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re
|
|||||||
locale := translation.NewLocale(lang)
|
locale := translation.NewLocale(lang)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
rel.RenderedNote, err = markdown.RenderString(&markup.RenderContext{
|
rel.RenderedNote, err = markdown.RenderString(markup.NewRenderContext(ctx).
|
||||||
Ctx: ctx,
|
WithRepoFacade(rel.Repo).
|
||||||
Repo: rel.Repo,
|
WithLinks(markup.Links{Base: rel.Repo.HTMLURL()}).
|
||||||
Links: markup.Links{
|
WithMetas(rel.Repo.ComposeMetas(ctx)),
|
||||||
Base: rel.Repo.HTMLURL(),
|
rel.Note)
|
||||||
},
|
|
||||||
Metas: rel.Repo.ComposeMetas(ctx),
|
|
||||||
}, rel.Note)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("markdown.RenderString(%d): %v", rel.RepoID, err)
|
log.Error("markdown.RenderString(%d): %v", rel.RepoID, err)
|
||||||
return
|
return
|
||||||
|
@ -6,6 +6,8 @@ package oauth2_provider //nolint
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
auth "code.gitea.io/gitea/models/auth"
|
auth "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@ -69,6 +71,32 @@ type AccessTokenResponse struct {
|
|||||||
IDToken string `json:"id_token,omitempty"`
|
IDToken string `json:"id_token,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GrantAdditionalScopes returns valid scopes coming from grant
|
||||||
|
func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope {
|
||||||
|
// scopes_supported from templates/user/auth/oidc_wellknown.tmpl
|
||||||
|
scopesSupported := []string{
|
||||||
|
"openid",
|
||||||
|
"profile",
|
||||||
|
"email",
|
||||||
|
"groups",
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenScopes []string
|
||||||
|
for _, tokenScope := range strings.Split(grantScopes, " ") {
|
||||||
|
if slices.Index(scopesSupported, tokenScope) == -1 {
|
||||||
|
tokenScopes = append(tokenScopes, tokenScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since version 1.22, access tokens grant full access to the API
|
||||||
|
// with this access is reduced only if additional scopes are provided
|
||||||
|
accessTokenScope := auth.AccessTokenScope(strings.Join(tokenScopes, ","))
|
||||||
|
if accessTokenWithAdditionalScopes, err := accessTokenScope.Normalize(); err == nil && len(tokenScopes) > 0 {
|
||||||
|
return accessTokenWithAdditionalScopes
|
||||||
|
}
|
||||||
|
return auth.AccessTokenScopeAll
|
||||||
|
}
|
||||||
|
|
||||||
func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, serverKey, clientKey JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
|
func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, serverKey, clientKey JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
|
||||||
if setting.OAuth2.InvalidateRefreshTokens {
|
if setting.OAuth2.InvalidateRefreshTokens {
|
||||||
if err := grant.IncreaseCounter(ctx); err != nil {
|
if err := grant.IncreaseCounter(ctx); err != nil {
|
||||||
@ -148,7 +176,7 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
|
|||||||
Nonce: grant.Nonce,
|
Nonce: grant.Nonce,
|
||||||
}
|
}
|
||||||
if grant.ScopeContains("profile") {
|
if grant.ScopeContains("profile") {
|
||||||
idToken.Name = user.GetDisplayName()
|
idToken.Name = user.DisplayName()
|
||||||
idToken.PreferredUsername = user.Name
|
idToken.PreferredUsername = user.Name
|
||||||
idToken.Profile = user.HTMLURL()
|
idToken.Profile = user.HTMLURL()
|
||||||
idToken.Picture = user.AvatarLink(ctx)
|
idToken.Picture = user.AvatarLink(ctx)
|
||||||
@ -161,7 +189,13 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
|
|||||||
idToken.EmailVerified = user.IsActive
|
idToken.EmailVerified = user.IsActive
|
||||||
}
|
}
|
||||||
if grant.ScopeContains("groups") {
|
if grant.ScopeContains("groups") {
|
||||||
groups, err := GetOAuthGroupsForUser(ctx, user)
|
accessTokenScope := GrantAdditionalScopes(grant.Scope)
|
||||||
|
|
||||||
|
// since version 1.22 does not verify if groups should be public-only,
|
||||||
|
// onlyPublicGroups will be set only if 'public-only' is included in a valid scope
|
||||||
|
onlyPublicGroups, _ := accessTokenScope.PublicOnly()
|
||||||
|
|
||||||
|
groups, err := GetOAuthGroupsForUser(ctx, user, onlyPublicGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting groups: %v", err)
|
log.Error("Error getting groups: %v", err)
|
||||||
return nil, &AccessTokenError{
|
return nil, &AccessTokenError{
|
||||||
@ -192,10 +226,10 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
|
|||||||
|
|
||||||
// returns a list of "org" and "org:team" strings,
|
// returns a list of "org" and "org:team" strings,
|
||||||
// that the given user is a part of.
|
// that the given user is a part of.
|
||||||
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User) ([]string, error) {
|
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) {
|
||||||
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
|
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
IncludePrivate: true,
|
IncludePrivate: !onlyPublicGroups,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetUserOrgList: %w", err)
|
return nil, fmt.Errorf("GetUserOrgList: %w", err)
|
||||||
|
35
services/oauth2_provider/additional_scopes_test.go
Normal file
35
services/oauth2_provider/additional_scopes_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package oauth2_provider //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGrantAdditionalScopes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
grantScopes string
|
||||||
|
expectedScopes string
|
||||||
|
}{
|
||||||
|
{"openid profile email", "all"},
|
||||||
|
{"openid profile email groups", "all"},
|
||||||
|
{"openid profile email all", "all"},
|
||||||
|
{"openid profile email read:user all", "all"},
|
||||||
|
{"openid profile email groups read:user", "read:user"},
|
||||||
|
{"read:user read:repository", "read:repository,read:user"},
|
||||||
|
{"read:user write:issue public-only", "public-only,write:issue,read:user"},
|
||||||
|
{"openid profile email read:user", "read:user"},
|
||||||
|
{"read:invalid_scope", "all"},
|
||||||
|
{"read:invalid_scope,write:scope_invalid,just-plain-wrong", "all"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.grantScopes, func(t *testing.T) {
|
||||||
|
result := GrantAdditionalScopes(test.grantScopes)
|
||||||
|
assert.Equal(t, test.expectedScopes, string(result))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
func TestRepository_ContributorsGraph(t *testing.T) {
|
func TestRepository_ContributorsGraph(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
|
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
|
||||||
mockCache, err := cache.NewStringCache(setting.Cache{})
|
mockCache, err := cache.NewStringCache(setting.Cache{})
|
||||||
@ -46,7 +47,7 @@ func TestRepository_ContributorsGraph(t *testing.T) {
|
|||||||
|
|
||||||
assert.EqualValues(t, &ContributorData{
|
assert.EqualValues(t, &ContributorData{
|
||||||
Name: "Ethan Koenig",
|
Name: "Ethan Koenig",
|
||||||
AvatarLink: "https://secure.gravatar.com/avatar/b42fb195faa8c61b8d88abfefe30e9e3?d=identicon",
|
AvatarLink: "/assets/img/avatar_default.png",
|
||||||
TotalCommits: 1,
|
TotalCommits: 1,
|
||||||
Weeks: map[int64]*WeekData{
|
Weeks: map[int64]*WeekData{
|
||||||
1511654400000: {
|
1511654400000: {
|
||||||
|
@ -338,7 +338,9 @@
|
|||||||
|
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
These configuration options will be written into: {{.CustomConfFile}}
|
{{$copyBtn := svg "octicon-copy" 14}}
|
||||||
|
{{$filePath := HTMLFormat `<span class="ui label">%s</span> <button class="btn interact-fg" data-clipboard-text="%s">%s</button>` .CustomConfFile .CustomConfFile $copyBtn}}
|
||||||
|
{{ctx.Locale.Tr "install.config_write_file_prompt" $filePath}}
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-mt-4 tw-mb-2 tw-text-center">
|
<div class="tw-mt-4 tw-mb-2 tw-text-center">
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button>
|
<button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button>
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
{{template "repo/sub_menu" .}}
|
{{template "repo/sub_menu" .}}
|
||||||
<div class="repo-button-row">
|
<div class="repo-button-row">
|
||||||
<div class="tw-flex tw-items-center">
|
<div class="repo-button-row-left">
|
||||||
{{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
|
{{template "repo/branch_dropdown" dict "root" .}}
|
||||||
<a href="{{.RepoLink}}/graph" class="ui basic small compact button">
|
<a href="{{.RepoLink}}/graph" class="ui basic small compact button">
|
||||||
{{svg "octicon-git-branch"}}
|
{{svg "octicon-git-branch"}}
|
||||||
{{ctx.Locale.Tr "repo.commit_graph"}}
|
{{ctx.Locale.Tr "repo.commit_graph"}}
|
||||||
|
@ -130,7 +130,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="file tw-flex tw-items-center tw-font-mono tw-flex-1"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}</a>
|
<span class="file tw-flex tw-items-center tw-font-mono tw-flex-1"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}</a>
|
||||||
{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}
|
{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}
|
||||||
<button class="btn interact-fg tw-p-2" data-clipboard-text="{{$file.Name}}">{{svg "octicon-copy" 14}}</button>
|
<button class="btn interact-fg tw-p-2" data-clipboard-text="{{$file.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
|
||||||
{{if $file.IsGenerated}}
|
{{if $file.IsGenerated}}
|
||||||
<span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span>
|
<span class="ui label">{{ctx.Locale.Tr "repo.diff.generated"}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
{{$isHomepage := (eq $n 0)}}
|
{{$isHomepage := (eq $n 0)}}
|
||||||
<div class="repo-button-row" data-is-homepage="{{$isHomepage}}">
|
<div class="repo-button-row" data-is-homepage="{{$isHomepage}}">
|
||||||
<div class="repo-button-row-left">
|
<div class="repo-button-row-left">
|
||||||
{{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
|
{{template "repo/branch_dropdown" dict "root" .}}
|
||||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||||
{{$cmpBranch := ""}}
|
{{$cmpBranch := ""}}
|
||||||
{{if ne .Repository.ID .BaseRepo.ID}}
|
{{if ne .Repository.ID .BaseRepo.ID}}
|
||||||
@ -106,6 +106,7 @@
|
|||||||
<span class="breadcrumb-divider">/</span>
|
<span class="breadcrumb-divider">/</span>
|
||||||
{{- if eq $i $l -}}
|
{{- if eq $i $l -}}
|
||||||
<span class="active section" title="{{$v}}">{{$v}}</span>
|
<span class="active section" title="{{$v}}">{{$v}}</span>
|
||||||
|
<button class="btn interact-fg tw-mx-1" data-clipboard-text="{{$.TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
|
{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
@ -16,12 +16,14 @@
|
|||||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignees"}}">
|
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignees"}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="item clear-selection">{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}</div>
|
<div class="item clear-selection">{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}</div>
|
||||||
{{range $data.CandidateAssignees}}
|
<div class="scrolling menu">
|
||||||
<a class="item muted" href="#" data-value="{{.ID}}">
|
{{range $data.CandidateAssignees}}
|
||||||
<span class="item-check-mark">{{svg "octicon-check"}}</span>
|
<a class="item muted" href="#" data-value="{{.ID}}">
|
||||||
{{ctx.AvatarUtils.Avatar . 20}} {{template "repo/search_name" .}}
|
<span class="item-check-mark">{{svg "octicon-check"}}</span>
|
||||||
</a>
|
{{ctx.AvatarUtils.Avatar . 20}} {{template "repo/search_name" .}}
|
||||||
{{end}}
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui list tw-flex tw-flex-row tw-gap-2">
|
<div class="ui list tw-flex tw-flex-row tw-gap-2">
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
|
<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
|
||||||
{{if not .Issue.IsPinned}}
|
{{if not .Issue.IsPinned}}
|
||||||
{{svg "octicon-pin" 16 "tw-mr-2"}}
|
{{svg "octicon-pin"}}
|
||||||
{{ctx.Locale.Tr "pin"}}
|
{{ctx.Locale.Tr "pin"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{svg "octicon-pin-slash" 16 "tw-mr-2"}}
|
{{svg "octicon-pin-slash"}}
|
||||||
{{ctx.Locale.Tr "unpin"}}
|
{{ctx.Locale.Tr "unpin"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</button>
|
</button>
|
||||||
|
@ -17,25 +17,27 @@
|
|||||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_labels"}}">
|
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_labels"}}">
|
||||||
</div>
|
</div>
|
||||||
<a class="item clear-selection" href="#">{{ctx.Locale.Tr "repo.issues.new.clear_labels"}}</a>
|
<a class="item clear-selection" href="#">{{ctx.Locale.Tr "repo.issues.new.clear_labels"}}</a>
|
||||||
{{$previousExclusiveScope := "_no_scope"}}
|
<div class="scrolling menu">
|
||||||
{{range $data.RepoLabels}}
|
{{$previousExclusiveScope := "_no_scope"}}
|
||||||
{{$exclusiveScope := .ExclusiveScope}}
|
{{range $data.RepoLabels}}
|
||||||
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
|
{{$exclusiveScope := .ExclusiveScope}}
|
||||||
<div class="divider"></div>
|
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
|
||||||
|
<div class="divider"></div>
|
||||||
|
{{end}}
|
||||||
|
{{$previousExclusiveScope = $exclusiveScope}}
|
||||||
|
{{template "repo/issue/sidebar/label_list_item" dict "Label" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$previousExclusiveScope = $exclusiveScope}}
|
{{if and $data.RepoLabels $data.OrgLabels}}<div class="divider"></div>{{end}}
|
||||||
{{template "repo/issue/sidebar/label_list_item" dict "Label" .}}
|
{{$previousExclusiveScope = "_no_scope"}}
|
||||||
{{end}}
|
{{range $data.OrgLabels}}
|
||||||
<div class="divider"></div>
|
{{$exclusiveScope := .ExclusiveScope}}
|
||||||
{{$previousExclusiveScope = "_no_scope"}}
|
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
|
||||||
{{range $data.OrgLabels}}
|
<div class="divider"></div>
|
||||||
{{$exclusiveScope := .ExclusiveScope}}
|
{{end}}
|
||||||
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
|
{{$previousExclusiveScope = $exclusiveScope}}
|
||||||
<div class="divider"></div>
|
{{template "repo/issue/sidebar/label_list_item" dict "Label" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$previousExclusiveScope = $exclusiveScope}}
|
</div>
|
||||||
{{template "repo/issue/sidebar/label_list_item" dict "Label" .}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,24 +20,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="item clear-selection">{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}</div>
|
<div class="item clear-selection">{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}</div>
|
||||||
{{if $data.OpenMilestones}}
|
<div class="scrolling menu">
|
||||||
<div class="divider"></div>
|
{{if $data.OpenMilestones}}
|
||||||
<div class="header">{{ctx.Locale.Tr "repo.issues.new.open_milestone"}}</div>
|
<div class="header">{{ctx.Locale.Tr "repo.issues.new.open_milestone"}}</div>
|
||||||
{{range $data.OpenMilestones}}
|
{{range $data.OpenMilestones}}
|
||||||
<a class="item muted" data-value="{{.ID}}" href="{{$pageMeta.RepoLink}}/issues?milestone={{.ID}}">
|
<a class="item muted" data-value="{{.ID}}" href="{{$pageMeta.RepoLink}}/issues?milestone={{.ID}}">
|
||||||
{{svg "octicon-milestone" 18}} {{.Name}}
|
{{svg "octicon-milestone" 18}} {{.Name}}
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{if and $data.OpenMilestones $data.ClosedMilestones}}<div class="divider"></div>{{end}}
|
||||||
{{if $data.ClosedMilestones}}
|
{{if $data.ClosedMilestones}}
|
||||||
<div class="divider"></div>
|
<div class="header">{{ctx.Locale.Tr "repo.issues.new.closed_milestone"}}</div>
|
||||||
<div class="header">{{ctx.Locale.Tr "repo.issues.new.closed_milestone"}}</div>
|
{{range $data.ClosedMilestones}}
|
||||||
{{range $data.ClosedMilestones}}
|
<a class="item muted" data-value="{{.ID}}" href="{{$pageMeta.RepoLink}}/issues?milestone={{.ID}}">
|
||||||
<a class="item muted" data-value="{{.ID}}" href="{{$pageMeta.RepoLink}}/issues?milestone={{.ID}}">
|
{{svg "octicon-milestone" 18}} {{.Name}}
|
||||||
{{svg "octicon-milestone" 18}} {{.Name}}
|
</a>
|
||||||
</a>
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,24 +18,25 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="item clear-selection">{{ctx.Locale.Tr "repo.issues.new.clear_projects"}}</div>
|
<div class="item clear-selection">{{ctx.Locale.Tr "repo.issues.new.clear_projects"}}</div>
|
||||||
{{if $data.OpenProjects}}
|
<div class="scrolling menu">
|
||||||
<div class="divider"></div>
|
{{if $data.OpenProjects}}
|
||||||
<div class="header">{{ctx.Locale.Tr "repo.issues.new.open_projects"}}</div>
|
<div class="header">{{ctx.Locale.Tr "repo.issues.new.open_projects"}}</div>
|
||||||
{{range $data.OpenProjects}}
|
{{range $data.OpenProjects}}
|
||||||
<a class="item muted" data-value="{{.ID}}" href="{{.Link ctx}}">
|
<a class="item muted" data-value="{{.ID}}" href="{{.Link ctx}}">
|
||||||
{{svg .IconName 18}} {{.Title}}
|
{{svg .IconName 18}} {{.Title}}
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{if and $data.OpenProjects $data.ClosedProjects}}<div class="divider"></div>{{end}}
|
||||||
{{if $data.ClosedProjects}}
|
{{if $data.ClosedProjects}}
|
||||||
<div class="divider"></div>
|
<div class="header">{{ctx.Locale.Tr "repo.issues.new.closed_projects"}}</div>
|
||||||
<div class="header">{{ctx.Locale.Tr "repo.issues.new.closed_projects"}}</div>
|
{{range $data.ClosedProjects}}
|
||||||
{{range $data.ClosedProjects}}
|
<a class="item muted" data-value="{{.ID}}" href="{{.Link ctx}}">
|
||||||
<a class="item muted" data-value="{{.ID}}" href="{{.Link ctx}}">
|
{{svg .IconName 18}} {{.Title}}
|
||||||
{{svg .IconName 18}} {{.Title}}
|
</a>
|
||||||
</a>
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui list">
|
<div class="ui list">
|
||||||
|
@ -17,27 +17,29 @@
|
|||||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
|
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range $data.Reviewers}}
|
<div class="scrolling menu flex-items-menu">
|
||||||
{{if .User}}
|
{{range $data.Reviewers}}
|
||||||
<a class="item muted {{if .Requested}}checked{{end}}" href="{{.User.HomeLink}}" data-value="{{.ItemID}}" data-can-change="{{.CanChange}}"
|
{{if .User}}
|
||||||
{{if not .CanChange}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
<a class="item muted {{if .Requested}}checked{{end}}" href="{{.User.HomeLink}}" data-value="{{.ItemID}}" data-can-change="{{.CanChange}}"
|
||||||
<span class="item-check-mark">{{svg "octicon-check"}}</span>
|
{{if not .CanChange}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
||||||
{{ctx.AvatarUtils.Avatar .User 20}} {{template "repo/search_name" .User}}
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{if $data.TeamReviewers}}
|
|
||||||
{{if $data.Reviewers}}<div class="divider"></div>{{end}}
|
|
||||||
{{range $data.TeamReviewers}}
|
|
||||||
{{if .Team}}
|
|
||||||
<a class="item muted {{if .Requested}}checked{{end}}" href="#" data-value="{{.ItemID}}" data-can-change="{{.CanChange}}"
|
|
||||||
{{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
|
||||||
<span class="item-check-mark">{{svg "octicon-check"}}</span>
|
<span class="item-check-mark">{{svg "octicon-check"}}</span>
|
||||||
{{svg "octicon-people" 20}} {{$repoOwnerName}}/{{.Team.Name}}
|
{{ctx.AvatarUtils.Avatar .User 20}} {{template "repo/search_name" .User}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{if $data.TeamReviewers}}
|
||||||
|
{{if $data.Reviewers}}<div class="divider"></div>{{end}}
|
||||||
|
{{range $data.TeamReviewers}}
|
||||||
|
{{if .Team}}
|
||||||
|
<a class="item muted {{if .Requested}}checked{{end}}" href="#" data-value="{{.ItemID}}" data-can-change="{{.CanChange}}"
|
||||||
|
{{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
||||||
|
<span class="item-check-mark">{{svg "octicon-check"}}</span>
|
||||||
|
{{svg "octicon-people" 20}} {{$repoOwnerName}}/{{.Team.Name}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user