mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-21 09:31:19 +02:00
Compare commits
124 Commits
bc78a9a38a
...
release/v1
Author | SHA1 | Date | |
---|---|---|---|
|
99596044d7 | ||
|
693d26914f | ||
|
315f197790 | ||
|
76b8f0c3a7 | ||
|
f99bbd7f3f | ||
|
f7ef657b5a | ||
|
486d274be6 | ||
|
ab3d2a944c | ||
|
12bfa9e83d | ||
|
dd661e92df | ||
|
0b31272c7e | ||
|
ec0c418719 | ||
|
6dc19fc29a | ||
|
9f1baa7d18 | ||
|
e13deb7a16 | ||
|
e5c1b8b632 | ||
|
e931b62f33 | ||
|
81ee93e5bc | ||
|
053f9186bc | ||
|
68fcdb6122 | ||
|
14ca309c39 | ||
|
4aba42519d | ||
|
9adf175df0 | ||
|
c3fa2a8729 | ||
|
89dfed32e0 | ||
|
d5062d0c27 | ||
|
90e9e79232 | ||
|
c6467edcb1 | ||
|
5d5b695527 | ||
|
0af7a7b79f | ||
|
9339661078 | ||
|
1e69f085d6 | ||
|
0bfccd8ecf | ||
|
534b9b35dd | ||
|
dbadc59b56 | ||
|
a57e2c4bc3 | ||
|
acd4e10990 | ||
|
0a1df294c8 | ||
|
52a964d1fc | ||
|
d3dbe0d9ce | ||
|
cdbbdbef06 | ||
|
79f555d465 | ||
|
ae2b795693 | ||
|
d1fdbf46bd | ||
|
f27a75564a | ||
|
958d0db4f4 | ||
|
4c2441ba5d | ||
|
6f5f0be9e3 | ||
|
23d2d224c2 | ||
|
a43d829de8 | ||
|
8ab1363fef | ||
|
178fd90852 | ||
|
b39f7a37d1 | ||
|
b9ed8fceff | ||
|
e6ce72b14a | ||
|
2eecd58bbe | ||
|
64b9b21790 | ||
|
3290aff964 | ||
|
7ed1e8987e | ||
|
f10e909fce | ||
|
a3b25436f2 | ||
|
b947bc4363 | ||
|
18dc41d6f8 | ||
|
bf5d00074d | ||
|
fb4e9f92f9 | ||
|
468d1919b5 | ||
|
1b788946a7 | ||
|
e8646ad1d8 | ||
|
29dc9c784e | ||
|
b1cc4bf77f | ||
|
d35161ceb8 | ||
|
8defca6d39 | ||
|
fac434da0a | ||
|
e18eae7129 | ||
|
c60bc26fd3 | ||
|
bacc69db83 | ||
|
c5da032193 | ||
|
3ace45c118 | ||
|
5d6c5ce71a | ||
|
7baa6fa47c | ||
|
f9a0b077a7 | ||
|
d3317ebabe | ||
|
e9481e1da3 | ||
|
8965c068e9 | ||
|
eaaa158df3 | ||
|
f5498421c4 | ||
|
a6a14c9a92 | ||
|
d0ec1788b8 | ||
|
c1202f1b57 | ||
|
1162cbccc0 | ||
|
038990e0ff | ||
|
03ff09870d | ||
|
8bf4f2cc8f | ||
|
21731c1370 | ||
|
a0e272d95a | ||
|
47537a8361 | ||
|
d018c1b4b1 | ||
|
d2cbe2fba0 | ||
|
d6233c25b5 | ||
|
2bf2d00c8a | ||
|
9bd56a8ba0 | ||
|
a1dc3c9bd1 | ||
|
47ee84d1f3 | ||
|
89f1df033a | ||
|
94b67f1967 | ||
|
0a9a84df11 | ||
|
cdac263bb8 | ||
|
a5c7df7a4c | ||
|
6d738fecc4 | ||
|
38cc7453e2 | ||
|
b44175c071 | ||
|
947358dffe | ||
|
be1090cb2d | ||
|
c8f3402841 | ||
|
a3a95a0b67 | ||
|
ed527b664d | ||
|
e4717d426e | ||
|
16f15d2f7b | ||
|
b3f5196241 | ||
|
6c5f0af45d | ||
|
c95cb7c7e2 | ||
|
6747e3e0eb | ||
|
a12b5b3640 | ||
|
834dad8cef |
@@ -7,7 +7,7 @@
|
||||
"version": "20"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
||||
"ghcr.io/devcontainers-extra/features/poetry:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"version": "3.12"
|
||||
},
|
||||
|
2
.github/workflows/pull-db-tests.yml
vendored
2
.github/workflows/pull-db-tests.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
pgsql:
|
||||
image: postgres:12
|
||||
image: postgres:14
|
||||
env:
|
||||
POSTGRES_DB: test
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
12
.gitignore
vendored
12
.gitignore
vendored
@@ -39,14 +39,10 @@ _testmain.go
|
||||
coverage.all
|
||||
cpu.out
|
||||
|
||||
/modules/migration/bindata.go
|
||||
/modules/migration/bindata.go.hash
|
||||
/modules/options/bindata.go
|
||||
/modules/options/bindata.go.hash
|
||||
/modules/public/bindata.go
|
||||
/modules/public/bindata.go.hash
|
||||
/modules/templates/bindata.go
|
||||
/modules/templates/bindata.go.hash
|
||||
/modules/migration/bindata.*
|
||||
/modules/options/bindata.*
|
||||
/modules/public/bindata.*
|
||||
/modules/templates/bindata.*
|
||||
|
||||
*.db
|
||||
*.log
|
||||
|
483
CHANGELOG.md
483
CHANGELOG.md
@@ -4,6 +4,489 @@ This changelog goes through the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.24.3](https://github.com/go-gitea/gitea/releases/tag/1.24.3) - 2025-07-15
|
||||
|
||||
* BUGFIXES
|
||||
* Fix form property assignment edge case (#35073) (#35078)
|
||||
* Improve submodule relative path handling (#35056) (#35075)
|
||||
* Fix incorrect comment diff hunk parsing, fix github asset ID nil panic (#35046) (#35055)
|
||||
* Fix updating user visibility (#35036) (#35044)
|
||||
* Support base64-encoded agit push options (#35037) (#35041)
|
||||
* Make submodule link work with relative path (#35034) (#35038)
|
||||
* Fix bug when displaying git user avatar in commits list (#35006)
|
||||
* Fix API response for swagger spec (#35029)
|
||||
* Start automerge check again after the conflict check and the schedule (#34988) (#35002)
|
||||
* Fix the response format for actions/workflows (#35009) (#35016)
|
||||
* Fix repo settings and protocol log problems (#35012) (#35013)
|
||||
* Fix project images scroll (#34971) (#34972)
|
||||
* Mark old reviews as stale on agit pr updates (#34933) (#34965)
|
||||
* Fix git graph page (#34948) (#34949)
|
||||
* Don't send trigger for a pending review's comment create/update/delete (#34928) (#34939)
|
||||
* Fix some log and UI problems (#34863) (#34868)
|
||||
* Fix archive API (#34853) (#34857)
|
||||
* Ignore force pushes for changed files in a PR review (#34837) (#34843)
|
||||
* Fix SSH LFS timeout (#34838) (#34842)
|
||||
* Fix team permissions (#34827) (#34836)
|
||||
* Fix job status aggregation logic (#34823) (#34835)
|
||||
* Fix issue filter (#34914) (#34915)
|
||||
* Fix typo in pull request merge warning message text (#34899) (#34903)
|
||||
* Support the open-icon of folder (#34168) (#34896)
|
||||
* Optimize flex layout of release attachment area (#34885) (#34886)
|
||||
* Fix the issue of abnormal interface when there is no issue-item on the project page (#34791) (#34880)
|
||||
* Skip updating timestamp when sync branch (#34875)
|
||||
* Fix required contexts and commit status matching bug (#34815) (#34829)
|
||||
|
||||
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/1.24.2) - 2025-06-20
|
||||
|
||||
* BUGFIXES
|
||||
* Fix container range bug (#34795) (#34796)
|
||||
* Upgrade chi to v5.2.2 (#34798) (#34799)
|
||||
* BUILD
|
||||
* Bump poetry feature to new url for dev container (#34787) (#34790)
|
||||
|
||||
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/1.24.1) - 2025-06-18
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Improve alignment of commit status icon on commit page (#34750) (#34757)
|
||||
* Support title and body query parameters for new PRs (#34537) (#34752)
|
||||
|
||||
* BUGFIXES
|
||||
* When using rules to delete packages, remove unclean bugs (#34632) (#34761)
|
||||
* Fix ghost user in feeds when pushing in an actions, it should be gitea-actions (#34703) (#34756)
|
||||
* Prevent double markdown link brackets when pasting URL (#34745) (#34748)
|
||||
* Prevent duplicate form submissions when creating forks (#34714) (#34735)
|
||||
* Fix markdown wrap (#34697) (#34702)
|
||||
* Fix pull requests API convert panic when head repository is deleted. (#34685) (#34687)
|
||||
* Fix commit message rendering and some UI problems (#34680) (#34683)
|
||||
* Fix container range bug (#34725) (#34732)
|
||||
* Fix incorrect cli default values (#34765) (#34766)
|
||||
* Fix dropdown filter (#34708) (#34711)
|
||||
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
|
||||
* Fix tag target (#34781) #34783
|
||||
|
||||
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
|
||||
|
||||
* BREAKING
|
||||
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
|
||||
* Improve log format (#33814)
|
||||
* Fix markdown render behaviors (#34122)
|
||||
* Add package version api endpoints (#34173)
|
||||
|
||||
* FEATURES
|
||||
* Enforce two-factor auth (2FA: TOTP or WebAuthn) (#34187)
|
||||
* Add fullscreen mode as a more efficient operation way to view projects (#34081)
|
||||
* Add anonymous access support for private/unlisted repositories (#34051)
|
||||
* Support public code/issue access for private repositories (#33127)
|
||||
* Add middleware for request prioritization (#33951)
|
||||
* Add cli flags LDAP group configuration (#33933)
|
||||
* Add file tree to file view page (#32721)
|
||||
* Add material icons for file list (#33837)
|
||||
* Artifacts download api for artifact actions v4 (#33510)
|
||||
* Support choose email when creating a commit via web UI (#33432)
|
||||
* Add basic auth support to rss/atom feeds (#33371)
|
||||
* Add sorting by exclusive labels (issue priority) (#33206)
|
||||
* Add sub issue list support (#32940)
|
||||
* Private README.md for organization (#32872)
|
||||
* Email option to embed images as base64 instead of link (#32061)
|
||||
* Option to delay conflict checking of old pull requests until page view (#27779)
|
||||
* Worktime tracking for the organization level (#19808)
|
||||
|
||||
* PERFORMANCE
|
||||
* Add cache for common package queries (#22491)
|
||||
* Move issue pin to an standalone table for querying performance (#33452)
|
||||
* Improve commits list performance to reduce unnecessary database queries (#33528)
|
||||
* Optimize total count of feed when loading activities in user dashboard. (#33841)
|
||||
* Optimize heatmap query (#33853)
|
||||
* Only use prev and next buttons for pagination on user dashboard (#33981)
|
||||
* Improve pull request list API performance (#34052)
|
||||
* Cache GPG keys, emails and users when list commits (#34086)
|
||||
* Refactor Git Attribute & performance optimization (#34154)
|
||||
* Performance optimization for tags synchronization (#34355) #34522
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Code
|
||||
* Display when a release attachment was uploaded (#34261)
|
||||
* Support creating relative link to raw path in markdown (#34105)
|
||||
* Improve code block readability and isolate copy button (#34009)
|
||||
* Improve repository commit view (#33877)
|
||||
* Full-file syntax highlighting for diff pages (#33766)
|
||||
* Clone repository with Tea CLI (#33725)
|
||||
* Improve sync fork behavior (#33319)
|
||||
* Make git clone URL could use current signed-in user (#33091)
|
||||
* Add submodule diff links (#33097)
|
||||
* Link to tree views of submodules if possible (#33424)
|
||||
* Only keep popular licenses (#33832)
|
||||
* De-emphasize signed commits (#31160)
|
||||
|
||||
* Actions
|
||||
* Add flat-square action badge style (#34062)
|
||||
* Update action status badge layout (#34018)
|
||||
* Download actions job logs from API (#33858)
|
||||
* Always show the "rerun" button for action jobs (#33692)
|
||||
* Add auto-expanding running actions step (#30058)
|
||||
* Update status check for all supported on.pull_request.types in Gitea (#33117)
|
||||
* Workflow_dispatch use workflow from trigger branch (#33098)
|
||||
* Add action auto-scroll (#30057)
|
||||
* Add workflow_job webhook (#33694)
|
||||
* Add a button editing action secret (#34462)
|
||||
|
||||
* Pull Request
|
||||
* Auto expand "New PR" form (#33971)
|
||||
* Mark parent directory as viewed when all files are viewed (#33958)
|
||||
* Show info about maintainers are allowed to edit a PR (#33738)
|
||||
* Automerge supports deleting branch automatically after merging (#32343)
|
||||
* Add additional command hints for PowerShell & CMD (#33548)
|
||||
|
||||
* Issues
|
||||
* Allow filtering issues by any assignee (#33343)
|
||||
* Show warning on navigation if currently editing comment or title (#32920)
|
||||
* Make tracked time representation display as hours (#33315)
|
||||
* Add No Results Prompt Message on Issue List Page (#33699)
|
||||
* Add sort option recentclose for issues and pulls (#34525) #34539
|
||||
|
||||
* Packages
|
||||
* Link to nuget dependencies (#26554)
|
||||
* Add composor source field (#33502)
|
||||
|
||||
* Administration
|
||||
* Improve navbar: add "admin" tip, add "active" style (#32927)
|
||||
* Add a option "--user-type bot" to admin user create, improve role display (#27885)
|
||||
* Improve admin user view page (#33735)
|
||||
* Support performance trace (#32973)
|
||||
* Change pprof labels to be prometheus compatible (#32865)
|
||||
* Allow admins and org owners to change org member public status (#28294)
|
||||
* Optimize the installation page (#32994)
|
||||
* Make public URL generation configurable (#34250)
|
||||
* Add a --fullname arg to gitea admin user create. (#34241)
|
||||
|
||||
* Others
|
||||
* Improve oauth2 error handling (#33969)
|
||||
* Fail mirroring more gracefully (#34002)
|
||||
* Align User Details Page Header Layout with Design Specifications (#34192)
|
||||
* Webhook add X-Gitea-Hook-Installation-Target-Type Header (#33752)
|
||||
* Optimize the dashboard (#32990)
|
||||
* Improve button layout on small screens (#33633)
|
||||
* Add cropping support when modifying the user/org/repo avatar (#33498)
|
||||
* Make ROOT_URL support using request Host header (#32564)
|
||||
* Add `show more` organizations icon in user's profile (#32986)
|
||||
* Introduce `--page-space-bottom` at 64px (#30692)
|
||||
* Improve theme display (#30671)
|
||||
* Add alphabetical project sorting (#33504)
|
||||
* Add global lock for migrations to make upgrade more safe with multiple replications (#33706)
|
||||
* Add descriptions for private repo public access settings and improve the UI (#34057)
|
||||
|
||||
* API
|
||||
* Actions Runner rest api (#33873)
|
||||
* Inclusion of rename organization api (#33303)
|
||||
* Add API to support link package to repository and unlink it (#33481)
|
||||
* Add API endpoint to request contents of multiple files simultaniously (#34139)
|
||||
* Actions artifacts API list/download check status upload confirmed (#34273)
|
||||
* Add API routes to lock and unlock issues (#34165)
|
||||
* Fix some user name usages (#33689)
|
||||
* Allow filtering /repos/{owner}/{repo}/pulls by target base branch queryparam (#33684)
|
||||
* Improve swagger generation (#33664)
|
||||
* Support Ephemeral action runners (#33570)
|
||||
* Support workflow event dispatch via API (#33545)
|
||||
* Support workflow event dispatch via API (#32059)
|
||||
* Added Description Field for Secrets and Variables (#33526)
|
||||
* Reject star-related requests if stars are disabled (#33208)
|
||||
* Let API create and edit system webhooks, attempt 2 (#33180)
|
||||
* Use `Project-URL` metadata field to get a PyPI package's homepage URL (#33089)
|
||||
* Add `last_committer_date` and `last_author_date` for file contents API (#32921)
|
||||
|
||||
* REFACTORS
|
||||
* Remove context from git struct (#33793)
|
||||
* Refactor admin/common.ts (#33788)
|
||||
* Refactor repo-settings.ts (#33785)
|
||||
* Refactor repo-issue.ts (#33784)
|
||||
* Small refactor to reduce unnecessary database queries and remove duplicated functions (#33779)
|
||||
* Refactor initRepoBranchTagSelector to use new init framework (#33776)
|
||||
* Refactor buttons to use new init framework (#33774)
|
||||
* Refactor markup and pdf-viewer to use new init framework (#33772)
|
||||
* Refactor error system (#33771)
|
||||
* Refactor mail code (#33768)
|
||||
* Update TypeScript types (#33799)
|
||||
* Refactor older tests to use testify (#33140)
|
||||
* Move notifywatch to service layer (#33825)
|
||||
* Decouple context from repository related structs (#33823)
|
||||
* Remove context from mail struct (#33811)
|
||||
* Refactor dropdown ellipsis (#34123)
|
||||
* Refactor functions to reduce repopath expose (#33892)
|
||||
* Refactor repo-diff.ts (#33746)
|
||||
* Refactor web route handler (#33488)
|
||||
* Refactor user & avatar (#33433)
|
||||
* Refactor user package (#33423)
|
||||
* Refactor decouple context from migration structs (#33399)
|
||||
* Refactor context flash msg and global variables (#33375)
|
||||
* Refactor response writer & access logger (#33323)
|
||||
* Refactor ref type (#33242)
|
||||
* Refactor context repository (#33202)
|
||||
* Refactor legacy JS (#33115)
|
||||
* Refactor legacy line-number and scroll code (#33094)
|
||||
* Refactor env var related code (#33075)
|
||||
* Move SetMerged to service layer (#33045)
|
||||
* Merge updatecommentattachment functions (#33044)
|
||||
* Refactor pull-request compare&create page (#33071)
|
||||
* Refactor repo-new.ts (#33070)
|
||||
* Refactor pagination (#33037)
|
||||
* Refactor tests (#33021)
|
||||
* Refactor markup render to fix various path problems (#34114)
|
||||
* Refactor Branch struct in package modules/git (#33980)
|
||||
* Don't create duplicated functions for code repositories and wiki repositories (#33924)
|
||||
* Move git references checking to gitrepo packages to reduce expose of repository path (#33891)
|
||||
* Refactor cache-control (#33861)
|
||||
* Decouple diff stats query from actual diffing (#33810)
|
||||
* Move part of updating protected branch logic to service layer (#33742)
|
||||
* Decouple Batch from git.Repository to simplify usage without requiring the creation of a Repository struct. (#34001)
|
||||
* Refactor tmpl and blob_excerpt (#32967)
|
||||
* Refactor template & test related code (#32938)
|
||||
* Refactor db package and remove unnecessary `DumpTables` (#32930)
|
||||
* Refactor pprof labels and process desc (#32909)
|
||||
* Refactor repo-projects.ts (#32892)
|
||||
* Refactor getpatch/getdiff functions and remove unnecessary fallback (#32817)
|
||||
* Uniform all temporary directories and allow customizing temp path (#32352)
|
||||
* Remove context from retry downloader (#33871)
|
||||
* Refactor global init code and add more comments (#33755)
|
||||
* Remove some unnecessary template helpers (#33069)
|
||||
* Move and rename UpdateRepository (#34136)
|
||||
* Move hooks function to gitrepo and reduce expose repopath (#33890)
|
||||
* Add abstraction layer to delete repository from disk (#33879)
|
||||
* Add abstraction layer to check if the repository exists on disk (#33874)
|
||||
* Move ParseCommitWithSSHSignature to service layer (#34087)
|
||||
* Move duplicated functions (#33977)
|
||||
* Extract code to their own functions for push update (#33944)
|
||||
* Move gitgraph from modules to services layer (#33527)
|
||||
* Move commits signature and verify functions to service layers (#33605)
|
||||
* Use `CloseIssue` and `ReopenIssue` instead of `ChangeStatus` (#32467)
|
||||
* Refactor arch route handlers (#32993)
|
||||
* Refactor "string truncate" (#32984)
|
||||
* Refactor arch route handlers (#32972)
|
||||
* Clarify path param naming (#32969)
|
||||
* Refactor request context (#32956)
|
||||
* Move some errors to their own sub packages (#32880)
|
||||
* Move RepoTransfer from models to models/repo sub package (#32506)
|
||||
* Move delete deploy keys into service layer (#32201)
|
||||
* Refactor webhook events (#33337)
|
||||
* Move some Actions related functions from `routers` to `services` (#33280)
|
||||
* Refactor RefName (#33234)
|
||||
* Refactor context RefName and RepoAssignment (#33226)
|
||||
* Refactor repository transfer (#33211)
|
||||
* Refactor error system (#33626)
|
||||
* Refactor error system (#33610)
|
||||
* Refactor package (routes and error handling, npm peer dependency) (#33111)
|
||||
* Use test context in tests and new loop system in benchmarks (#33648)
|
||||
* Some small refactors (#33144)
|
||||
* Simplify context ref name (#33267)
|
||||
|
||||
* BUGFIXES
|
||||
* Fix some dropdown problems on the issue sidebar (#34308) #34327
|
||||
* Do not return archive download URLs in API if downloads are disabled (#34324) #34338
|
||||
* Fix LFS files being editable in web UI (#34356) #34362
|
||||
* Fix only text/* being viewable in web UI (#34374) #34378
|
||||
* Fix LFS file not stored in LFS when uploaded/edited via API or web UI (#34367)
|
||||
* Grey out expired artifact on Artifacts list (#34314) #34404
|
||||
* Fix incorrect divergence cache after switching default branch (#34370) #34406
|
||||
* Refactor commit message rendering and fix bugs (#34412) #34414
|
||||
* Merge and tweak markup editor expander CSS (#34409) #34415
|
||||
* Fix GetUsersByEmails (#34423) #34425
|
||||
* Only git operations should update last changed of a repository (#34388) #34427
|
||||
* Fix comment textarea scroll issue in Firefox (#34438) #34446
|
||||
* Fix repo broken check (#34444) #34452
|
||||
* Fix remove org user failure on mssql (#34449) #34453
|
||||
* Fix Workflow run Not Found page (#34459) #34466
|
||||
* When updating comment, if the content is the same, just return and not update the database (#34422) #34464
|
||||
* Fix project board view (#34470) #34475
|
||||
* Fix get / delete runner to use consistent http 404 and 500 status (#34480) #34488
|
||||
* Fix url validation in webhook add/edit API (#34492) #34496
|
||||
* Fix edithook api can not update package, status and workflow_job events (#34495) #34499
|
||||
* Fix ephemeral runner deletion (#34447) #34513
|
||||
* Don't display error log when .git-blame-ignore-revs doesn't exist (#34457)
|
||||
* Only allow admins to rename default/protected branches (#33276)
|
||||
* Improve "lock conversation" UI (#34207)
|
||||
* Fix incorrect file links (#34189)
|
||||
* Optimize Overflow Menu (#34183)
|
||||
* Check user/org repo limit instead of doer (#34147)
|
||||
* Make markdown render match GitHub's behavior (#34129)
|
||||
* Fix team permission (#34128)
|
||||
* Correctly handle submodule view and avoid throwing 500 error (#34121)
|
||||
* Fix users being able bypass limits with repo transfers (#34031)
|
||||
* Avoid creating unnecessary temporary cat file sub process (#33942)
|
||||
* Refactor organization menu (#33928)
|
||||
* Fix various Fomantic UI and htmx problems (#33851)
|
||||
* Fix 500 error when error occurred in migration page (#33256)
|
||||
* Validate that the tag doesn't exist when creating a tag via the web (#33241)
|
||||
* Add missed transaction on setmerged (#33079)
|
||||
* Rework create/fork/adopt/generate repository to make sure resources will be cleanup once failed (#31035)
|
||||
* Valid email address should only start with alphanumeric (#28174)
|
||||
* Fix webhook url (#34186)
|
||||
* Fix "toAbsoluteLocaleDate" test when system locale is not en-US (#33939)
|
||||
* Fix file name could not be searched if the file was not a text file when using the Bleve indexer (#33959)
|
||||
* Fix cannot delete runners via the modal dialog (#33895)
|
||||
* Fix unpin hint on the pinned pull requests (#33207)
|
||||
* Fix parentCommit invalid memory address or nil pointer dereference. (#33204)
|
||||
* Fix comment header padding (#33377)
|
||||
* Fix some migration and repo name problems (#33986)
|
||||
* Fix various trivial frontend problems (#34263)
|
||||
* Fix Set Email Preference dropdown and button placement (#34255)
|
||||
* Fix quoted replies incorrectly render user input as part of the quote (#34216)
|
||||
* Fix button alignments and remove unnecessary styles (#34206)
|
||||
* Restore form inputs on organization create error (#34201)
|
||||
* Try to fix ACME (3rd) (#33807)
|
||||
* Fix incorrect ref "blob" (#33240)
|
||||
* Fix dynamic content loading init problem (#33748)
|
||||
* Fix git empty check and HEAD request (#33690)
|
||||
* Fix Untranslated Text on Actions Page (#33635)
|
||||
* Fix issue label delete incorrect labels webhook payload (#34575)
|
||||
* Fix incorrect page navigation with up and down arrow on last item of dashboard repos (#34570)
|
||||
* Fix/improve avatar sync from LDAP (#34573)
|
||||
* Fix some trivial problems (#34579)
|
||||
* Retain issue sort type when a keyword search is introduced (#34559)
|
||||
* Always use an empty line to separate the commit message and trailer (#34512)
|
||||
* Fix line-button issue after file selection in file tree (#34574)
|
||||
* Fix doctor deleting orphaned issues attachments (#34142)
|
||||
* Add webhook assigning test and fix possible bug (#34420)
|
||||
* Fix possible nil description of pull request when migrating from CodeCommit (#34541)
|
||||
* Refactor commit reader (#34542)
|
||||
* Fix possible pull request broken when leave the page immediately after clicking the update button #34509
|
||||
* Ignore "Close" error when uploading container blob (#34620)
|
||||
* Fix missed merge commit sha and time when migrating from codecommit (#34645)
|
||||
* Fix GetUsersByEmails (#34643)
|
||||
* Misc CSS fixes (#34638)
|
||||
* Add codecommit to supported services in api docs (#34626)
|
||||
* Validate hex colors when creating/editing labels (#34623)
|
||||
* Fix possible pull request broken when leave the page immediately after clicking the update button (#34509)
|
||||
* Fix margin issue in markup paragraph rendering (#34599)
|
||||
* Fix migration pull request title too long (#34577)
|
||||
* Fix footnote jump behavior on the issue page. (#34621)
|
||||
* Fix "oras" OCI client compatibility (#34666)
|
||||
* Fix last admin check when syncing users (#34649)
|
||||
* Fix skip paths check on tag push events in workflows (#34602) #34670
|
||||
|
||||
* MISC
|
||||
|
||||
* Bump to alpine 3.22 (#34613)
|
||||
* Make pull request and issue history more compact (#34588)
|
||||
* Run integration tests against postgres 14 (#34514) #34536
|
||||
* Enable addtional linters (#34085)
|
||||
* Enable testifylint rules (#34075)
|
||||
* Enable staticcheck QFxxxx rules (#34064)
|
||||
* Improve Actions test (#32883)
|
||||
* Drop fomantic build (#33845)
|
||||
* Go1.24 (#33562)
|
||||
* Run yamllint with strict mode, fix issue (#33551)
|
||||
* Disable cron task to update license (#33486)
|
||||
* Optimize makefile help information generation (#33390)
|
||||
* Convert github.com/xanzy/go-gitlab into gitlab.com/gitlab-org/api/client-go (#33126)
|
||||
* Add missed changelogs (#33649)
|
||||
* Update .changelog file to add performance label group (#33472)
|
||||
* Add missing POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES in app.example.ini (#33363)
|
||||
* Update README screenshots (#33347)
|
||||
* Update unrs-resolver (#34279)
|
||||
* Update go&js dependencies (#34262)
|
||||
* Optimize the calling code of queryElems (#34235)
|
||||
* Update protected_branch.tmpl (#34193)
|
||||
* Feat/optimize span svg layout (#34185)
|
||||
* Set MERMAID_MAX_SOURCE_CHARACTERS to 50000 (#34152)
|
||||
* Update JS and PY deps (#34143)
|
||||
* Add Chinese translations for README files (#34132)
|
||||
* Use `overflow-wrap: anywhere` to replace `word-break: break-all` (#34126)
|
||||
* Clarify ownership in password change error messages (#34092)
|
||||
* Add toggleClass function in dom.ts (#34063)
|
||||
* Update to golangci-lint v2 (#34054)
|
||||
* Update Makefile test comments (#34013)
|
||||
* Update go mod dependencies (#33988)
|
||||
* Use filepath.Join instead of path.Join for file system file operations (#33978)
|
||||
* Prepare common tmpl functions in a middleware (#33957)
|
||||
* Remove unused or abused styles (#33918)
|
||||
* Update JS and PY deps, misc tweaks (#33903)
|
||||
* Try to figure out attribute checker problem (#33901)
|
||||
* Add lock for a repository pull mirror (#33876)
|
||||
* Fine tune push mirror UI (#33866)
|
||||
* Improve issue & code search (#33860)
|
||||
* Use pullrequestlist instead of []*pullrequest (#33765)
|
||||
* Upgrade act to 0.261.4 and actions-proto-go to v0.4.1 (#33760)
|
||||
* Align sidebar gears to the right (#33721)
|
||||
* Update Go dependencies (skip blevesearch, meilisearch) (#33655)
|
||||
* Add migrations and doctor fixes (#33556)
|
||||
* Remove "class-name" from svg icon (#33540)
|
||||
* Update MAINTAINERS (#33529)
|
||||
* Add "No data available" display when list is empty (#33517)
|
||||
* Use `git diff-tree` for `DiffFileTree` on diff pages (#33514)
|
||||
* Give organisation members access to organisation feeds (#33508)
|
||||
* Update feishu icon (#33470)
|
||||
* Hide/disable unusable UI elements when a repository is archived (#33459)
|
||||
* Update `@github/text-expander-element` to 2.9.0 (#33435)
|
||||
* Do not access GitRepo when a repo is being created (#33380)
|
||||
* Fix incorrect ref usages (#33301)
|
||||
* Prepare for support performance trace (#33286)
|
||||
* Enable Typescript `noImplicitThis` (#33250)
|
||||
* Remove unused CSS styles and move some styles to proper files (#33217)
|
||||
* Add .run to gitignore (#33175)
|
||||
* Fix typo in gitea downloader test and add missing codebase in `ToGitServiceType` (#33146)
|
||||
* Remove extended glob pattern from branch protection UI (#33125)
|
||||
* Clean up legacy form CSS styles (#33081)
|
||||
* Unset XDG_HOME_CONFIG as gitea manages configuration locations (#33067)
|
||||
* Add IntelliJ Gateway's .uuid to gitignore (#33052)
|
||||
* User facing messages for AGit errors (#33012)
|
||||
* Always show assignees on right (#33006)
|
||||
* Fix eslint (#33002)
|
||||
* Update JS dependencies (#32914)
|
||||
* Bump x/net (#32896) (#32900)
|
||||
* Only activity tab needs heatmap data loading (#34652)
|
||||
|
||||
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
|
||||
|
||||
* SECURITY
|
||||
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
||||
* Update net package (#34228) (#34232)
|
||||
* BUGFIXES
|
||||
* Fix releases sidebar navigation link (#34436) #34439
|
||||
* Fix bug webhook milestone is not right. (#34419) #34429
|
||||
* Fix two missed null value checks on the wiki page. (#34205) (#34215)
|
||||
* Swift files can be passed either as file or as form value (#34068) (#34236)
|
||||
* Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
|
||||
* Upgrade github v61 -> v71 to fix migrating bug (#34389)
|
||||
* Fix bug when visiting comparation page (#34334) (#34364)
|
||||
* Fix wrong review requests when updating the pull request (#34286) (#34304)
|
||||
* Fix github migration error when using multiple tokens (#34144) (#34302)
|
||||
* Explicitly not update indexes when sync database schemas (#34281) (#34295)
|
||||
* Fix panic when comment is nil (#34257) (#34277)
|
||||
* Fix project board links to related Pull Requests (#34213) (#34222)
|
||||
* Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
|
||||
* DOCUMENTATION
|
||||
* Update token creation API swagger documentation (#34288) (#34296)
|
||||
* MISC
|
||||
* Fix CI Build (#34315)
|
||||
* Add riscv64 support (#34199) (#34204)
|
||||
* Bump go version in go.mod (#34160)
|
||||
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
||||
|
||||
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
|
||||
|
||||
* Enhancements
|
||||
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
||||
* Also check default ssh-cert location for host (#34099) (#34100) (#34116)
|
||||
* BUGFIXES
|
||||
* Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
|
||||
* Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120)
|
||||
* Fix invalid version in RPM package path (#34112) (#34115)
|
||||
* Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
|
||||
* Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
|
||||
* Try to fix check-attr bug (#34029) (#34033)
|
||||
* Git client will follow 301 but 307 (#34005) (#34010)
|
||||
* Fix block expensive for 1.23 (#34127)
|
||||
* Fix markdown frontmatter rendering (#34102) (#34107)
|
||||
* Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
|
||||
* Do not show 500 error when default branch doesn't exist (#34096) (#34097)
|
||||
* Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
|
||||
* Simplify emoji rendering (#34048) (#34049)
|
||||
* Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
|
||||
* Pull request updates will also trigger code owners review requests (#33744) (#34045)
|
||||
* Fix org repo creation being limited by user limits (#34030) (#34044)
|
||||
* Fix git client accessing renamed repo (#34034) (#34043)
|
||||
* Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036)
|
||||
* Polyfill WeakRef (#34025) (#34028)
|
||||
|
||||
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
|
||||
|
||||
* SECURITY
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Build stage
|
||||
FROM docker.io/library/golang:1.24-alpine3.21 AS build-env
|
||||
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-direct}
|
||||
@@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM docker.io/library/alpine:3.21
|
||||
FROM docker.io/library/alpine:3.22
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 22 3000
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Build stage
|
||||
FROM docker.io/library/golang:1.24-alpine3.21 AS build-env
|
||||
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-direct}
|
||||
@@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM docker.io/library/alpine:3.21
|
||||
FROM docker.io/library/alpine:3.22
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
4
assets/go-licenses.json
generated
4
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
@@ -38,12 +38,10 @@ var (
|
||||
&cli.BoolFlag{
|
||||
Name: "force-smtps",
|
||||
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "skip-verify",
|
||||
Usage: "Skip TLS verify.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "helo-hostname",
|
||||
@@ -53,7 +51,6 @@ var (
|
||||
&cli.BoolFlag{
|
||||
Name: "disable-helo",
|
||||
Usage: "Disable SMTP helo.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "allowed-domains",
|
||||
@@ -63,7 +60,6 @@ var (
|
||||
&cli.BoolFlag{
|
||||
Name: "skip-local-2fa",
|
||||
Usage: "Skip 2FA to log on.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "active",
|
||||
|
@@ -80,6 +80,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
|
||||
}
|
||||
|
||||
func runDumpRepository(ctx *cli.Context) error {
|
||||
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
||||
|
||||
setting.DisableLoggerInit()
|
||||
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
||||
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
|
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
hookBatchSize = 30
|
||||
hookBatchSize = 500
|
||||
)
|
||||
|
||||
var (
|
||||
|
@@ -118,7 +118,6 @@ var (
|
||||
Name: "rotate",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Rotate logs",
|
||||
Value: true,
|
||||
},
|
||||
&cli.Int64Flag{
|
||||
Name: "max-size",
|
||||
@@ -129,7 +128,6 @@ var (
|
||||
Name: "daily",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Rotate logs daily",
|
||||
Value: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "max-days",
|
||||
@@ -140,7 +138,6 @@ var (
|
||||
Name: "compress",
|
||||
Aliases: []string{"z"},
|
||||
Usage: "Compress rotated logs",
|
||||
Value: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "compression-level",
|
||||
|
89
cmd/serv.go
89
cmd/serv.go
@@ -11,7 +11,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -20,7 +19,7 @@ import (
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/lfstransfer"
|
||||
@@ -37,14 +36,6 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
verbUploadPack = "git-upload-pack"
|
||||
verbUploadArchive = "git-upload-archive"
|
||||
verbReceivePack = "git-receive-pack"
|
||||
verbLfsAuthenticate = "git-lfs-authenticate"
|
||||
verbLfsTransfer = "git-lfs-transfer"
|
||||
)
|
||||
|
||||
// CmdServ represents the available serv sub-command.
|
||||
var CmdServ = &cli.Command{
|
||||
Name: "serv",
|
||||
@@ -78,22 +69,6 @@ func setup(ctx context.Context, debug bool) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// keep getAccessMode() in sync
|
||||
allowedCommands = container.SetOf(
|
||||
verbUploadPack,
|
||||
verbUploadArchive,
|
||||
verbReceivePack,
|
||||
verbLfsAuthenticate,
|
||||
verbLfsTransfer,
|
||||
)
|
||||
allowedCommandsLfs = container.SetOf(
|
||||
verbLfsAuthenticate,
|
||||
verbLfsTransfer,
|
||||
)
|
||||
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
||||
)
|
||||
|
||||
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
||||
// The output will be passed to git client and shown to user.
|
||||
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
||||
@@ -139,19 +114,20 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
|
||||
|
||||
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
||||
switch verb {
|
||||
case verbUploadPack, verbUploadArchive:
|
||||
case git.CmdVerbUploadPack, git.CmdVerbUploadArchive:
|
||||
return perm.AccessModeRead
|
||||
case verbReceivePack:
|
||||
case git.CmdVerbReceivePack:
|
||||
return perm.AccessModeWrite
|
||||
case verbLfsAuthenticate, verbLfsTransfer:
|
||||
case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer:
|
||||
switch lfsVerb {
|
||||
case "upload":
|
||||
case git.CmdSubVerbLfsUpload:
|
||||
return perm.AccessModeWrite
|
||||
case "download":
|
||||
case git.CmdSubVerbLfsDownload:
|
||||
return perm.AccessModeRead
|
||||
}
|
||||
}
|
||||
// should be unreachable
|
||||
setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
|
||||
return perm.AccessModeNone
|
||||
}
|
||||
|
||||
@@ -230,12 +206,12 @@ func runServ(c *cli.Context) error {
|
||||
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
||||
}
|
||||
|
||||
words, err := shellquote.Split(cmd)
|
||||
sshCmdArgs, err := shellquote.Split(cmd)
|
||||
if err != nil {
|
||||
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
|
||||
}
|
||||
|
||||
if len(words) < 2 {
|
||||
if len(sshCmdArgs) < 2 {
|
||||
if git.DefaultFeatures().SupportProcReceive {
|
||||
// for AGit Flow
|
||||
if cmd == "ssh_info" {
|
||||
@@ -246,25 +222,21 @@ func runServ(c *cli.Context) error {
|
||||
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||
}
|
||||
|
||||
verb := words[0]
|
||||
repoPath := strings.TrimPrefix(words[1], "/")
|
||||
|
||||
var lfsVerb string
|
||||
|
||||
rr := strings.SplitN(repoPath, "/", 2)
|
||||
if len(rr) != 2 {
|
||||
repoPath := strings.TrimPrefix(sshCmdArgs[1], "/")
|
||||
repoPathFields := strings.SplitN(repoPath, "/", 2)
|
||||
if len(repoPathFields) != 2 {
|
||||
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
||||
}
|
||||
|
||||
username := rr[0]
|
||||
reponame := strings.TrimSuffix(rr[1], ".git")
|
||||
username := repoPathFields[0]
|
||||
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
|
||||
|
||||
// LowerCase and trim the repoPath as that's how they are stored.
|
||||
// This should be done after splitting the repoPath into username and reponame
|
||||
// so that username and reponame are not affected.
|
||||
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
||||
|
||||
if alphaDashDotPattern.MatchString(reponame) {
|
||||
if !repo.IsValidSSHAccessRepoName(reponame) {
|
||||
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
||||
}
|
||||
|
||||
@@ -286,22 +258,23 @@ func runServ(c *cli.Context) error {
|
||||
}()
|
||||
}
|
||||
|
||||
if allowedCommands.Contains(verb) {
|
||||
if allowedCommandsLfs.Contains(verb) {
|
||||
if !setting.LFS.StartServer {
|
||||
return fail(ctx, "LFS Server is not enabled", "")
|
||||
}
|
||||
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
||||
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
||||
}
|
||||
if len(words) > 2 {
|
||||
lfsVerb = words[2]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
verb, lfsVerb := sshCmdArgs[0], ""
|
||||
if !git.IsAllowedVerbForServe(verb) {
|
||||
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
|
||||
}
|
||||
|
||||
if git.IsAllowedVerbForServeLfs(verb) {
|
||||
if !setting.LFS.StartServer {
|
||||
return fail(ctx, "LFS Server is not enabled", "")
|
||||
}
|
||||
if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
|
||||
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
||||
}
|
||||
if len(sshCmdArgs) > 2 {
|
||||
lfsVerb = sshCmdArgs[2]
|
||||
}
|
||||
}
|
||||
|
||||
requestedMode := getAccessMode(verb, lfsVerb)
|
||||
|
||||
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
|
||||
@@ -310,7 +283,7 @@ func runServ(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// LFS SSH protocol
|
||||
if verb == verbLfsTransfer {
|
||||
if verb == git.CmdVerbLfsTransfer {
|
||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -319,7 +292,7 @@ func runServ(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// LFS token authentication
|
||||
if verb == verbLfsAuthenticate {
|
||||
if verb == git.CmdVerbLfsAuthenticate {
|
||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
||||
|
||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/go-github/v61/github"
|
||||
"github.com/google/go-github/v71/github"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739214665,
|
||||
"narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=",
|
||||
"lastModified": 1752480373,
|
||||
"narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a",
|
||||
"rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
6
go.mod
6
go.mod
@@ -51,7 +51,7 @@ require (
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.9.2
|
||||
@@ -66,7 +66,7 @@ require (
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/go-github/v61 v61.0.0
|
||||
github.com/google/go-github/v71 v71.0.0
|
||||
github.com/google/licenseclassifier/v2 v2.0.0
|
||||
github.com/google/pprof v0.0.0-20250422154841-e1f9c1950416
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -325,6 +325,8 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra
|
||||
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
||||
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
|
||||
|
||||
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
||||
|
||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||
|
||||
exclude github.com/gofrs/uuid v4.0.0+incompatible
|
||||
|
12
go.sum
12
go.sum
@@ -14,12 +14,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||
gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
|
||||
gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
||||
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||
@@ -301,8 +301,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
@@ -420,8 +420,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
|
||||
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
|
||||
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
|
||||
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
|
||||
|
@@ -171,6 +171,7 @@ func (run *ActionRun) IsSchedule() bool {
|
||||
|
||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||
NoAutoTime().
|
||||
SetExpr("num_action_runs",
|
||||
builder.Select("count(*)").From("action_run").
|
||||
Where(builder.Eq{"repo_id": repo.ID}),
|
||||
|
@@ -185,10 +185,10 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
|
||||
return StatusSuccess
|
||||
case hasCancelled:
|
||||
return StatusCancelled
|
||||
case hasFailure:
|
||||
return StatusFailure
|
||||
case hasRunning:
|
||||
return StatusRunning
|
||||
case hasFailure:
|
||||
return StatusFailure
|
||||
case hasWaiting:
|
||||
return StatusWaiting
|
||||
case hasBlocked:
|
||||
|
@@ -58,14 +58,14 @@ func TestAggregateJobStatus(t *testing.T) {
|
||||
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
|
||||
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
|
||||
|
||||
// failure with other status, fail fast
|
||||
// Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
|
||||
// failure with other status, usually fail fast, but "running" wins to match GitHub's behavior
|
||||
// another reason that we can't make "failure" wins over "running": it would cause a weird behavior that user cannot cancel a workflow or get current running workflows correctly by filter after a job fail.
|
||||
{[]Status{StatusFailure}, StatusFailure},
|
||||
{[]Status{StatusFailure, StatusSuccess}, StatusFailure},
|
||||
{[]Status{StatusFailure, StatusSkipped}, StatusFailure},
|
||||
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
|
||||
{[]Status{StatusFailure, StatusWaiting}, StatusFailure},
|
||||
{[]Status{StatusFailure, StatusRunning}, StatusFailure},
|
||||
{[]Status{StatusFailure, StatusRunning}, StatusRunning},
|
||||
{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
|
||||
|
||||
// skipped with other status
|
||||
|
@@ -5,6 +5,7 @@ package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -298,6 +299,23 @@ func DeleteRunner(ctx context.Context, id int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteEphemeralRunner deletes a ephemeral runner by given ID.
|
||||
func DeleteEphemeralRunner(ctx context.Context, id int64) error {
|
||||
runner, err := GetRunnerByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !runner.Ephemeral {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = db.DeleteByID[ActionRunner](ctx, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateRunner creates new runner.
|
||||
func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
||||
if t.OwnerID != 0 && t.RepoID != 0 {
|
||||
|
@@ -336,6 +336,11 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
||||
sess.Cols(cols...)
|
||||
}
|
||||
_, err := sess.Update(task)
|
||||
|
||||
// Automatically delete the ephemeral runner if the task is done
|
||||
if err == nil && task.Status.IsDone() && util.SliceContainsString(cols, "status") {
|
||||
return DeleteEphemeralRunner(ctx, task.RunnerID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -191,7 +191,7 @@ func (a *Action) LoadActUser(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
a.ActUser, err = user_model.GetUserByID(ctx, a.ActUserID)
|
||||
a.ActUser, err = user_model.GetPossibleUserByID(ctx, a.ActUserID)
|
||||
if err == nil {
|
||||
return
|
||||
} else if user_model.IsErrUserNotExist(err) {
|
||||
@@ -530,7 +530,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
||||
|
||||
if opts.RequestedTeam != nil {
|
||||
env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
|
||||
teamRepoIDs, err := env.RepoIDs(ctx, 1, opts.RequestedUser.NumRepos)
|
||||
teamRepoIDs, err := env.RepoIDs(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
|
||||
}
|
||||
|
@@ -66,7 +66,7 @@ func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organi
|
||||
Select(groupBy+" AS timestamp, count(user_id) as contributions").
|
||||
Table("action").
|
||||
Where(cond).
|
||||
And("created_unix > ?", timeutil.TimeStampNow()-31536000).
|
||||
And("created_unix > ?", timeutil.TimeStampNow()-(366+7)*86400). // (366+7) days to include the first week for the heatmap
|
||||
GroupBy(groupByName).
|
||||
OrderBy("timestamp").
|
||||
Find(&hdata)
|
||||
|
@@ -91,7 +91,7 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
|
||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("Unable to validate token signature. Error: %v", err)
|
||||
log.Debug("AddGPGKey CheckArmoredDetachedSignature failed: %v", err)
|
||||
return nil, ErrGPGInvalidTokenSignature{
|
||||
ID: ekeys[0].PrimaryKey.KeyIdString(),
|
||||
Wrapped: err,
|
||||
|
@@ -85,7 +85,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
|
||||
}
|
||||
|
||||
if signer == nil {
|
||||
log.Error("Unable to validate token signature. Error: %v", err)
|
||||
log.Debug("VerifyGPGKey failed: no signer")
|
||||
return "", ErrGPGInvalidTokenSignature{
|
||||
ID: key.KeyID,
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signat
|
||||
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
|
||||
// see https://github.com/PowerShell/PowerShell/issues/5974
|
||||
if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil {
|
||||
log.Error("Unable to validate token signature. Error: %v", err)
|
||||
log.Debug("VerifySSHKey sshsig.Verify failed: %v", err)
|
||||
return "", ErrSSHInvalidTokenSignature{
|
||||
Fingerprint: key.Fingerprint,
|
||||
}
|
||||
|
@@ -38,3 +38,14 @@
|
||||
repo_id: 0
|
||||
description: "This runner is going to be deleted"
|
||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||
-
|
||||
id: 34350
|
||||
name: runner_to_be_deleted-org-ephemeral
|
||||
uuid: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||
token_hash: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||
ephemeral: true
|
||||
version: "1.0.0"
|
||||
owner_id: 3
|
||||
repo_id: 0
|
||||
description: "This runner is going to be deleted"
|
||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||
|
@@ -117,3 +117,23 @@
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 52
|
||||
job_id: 196
|
||||
attempt: 1
|
||||
runner_id: 34350
|
||||
status: 6 # running
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: f8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
|
@@ -81,7 +81,7 @@
|
||||
-
|
||||
id: 11
|
||||
uid: 4
|
||||
email: user4@example.com
|
||||
email: User4@Example.Com
|
||||
lower_email: user4@example.com
|
||||
is_activated: true
|
||||
is_primary: true
|
||||
|
@@ -518,7 +518,7 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
|
||||
teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
|
||||
}
|
||||
|
@@ -719,7 +719,8 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Comment) loadReview(ctx context.Context) (err error) {
|
||||
// LoadReview loads the associated review
|
||||
func (c *Comment) LoadReview(ctx context.Context) (err error) {
|
||||
if c.ReviewID == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -736,11 +737,6 @@ func (c *Comment) loadReview(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadReview loads the associated review
|
||||
func (c *Comment) LoadReview(ctx context.Context) error {
|
||||
return c.loadReview(ctx)
|
||||
}
|
||||
|
||||
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
|
||||
func (c *Comment) DiffSide() string {
|
||||
if c.Line < 0 {
|
||||
@@ -860,7 +856,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
||||
}
|
||||
if comment.ReviewID != 0 {
|
||||
if comment.Review == nil {
|
||||
if err := comment.loadReview(ctx); err != nil {
|
||||
if err := comment.LoadReview(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ package issues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/renderhelper"
|
||||
@@ -114,7 +115,9 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
||||
}
|
||||
|
||||
var err error
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{
|
||||
FootnoteContextID: strconv.FormatInt(comment.ID, 10),
|
||||
})
|
||||
if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -206,6 +206,7 @@ func DeleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *use
|
||||
}
|
||||
|
||||
issue.Labels = nil
|
||||
issue.isLabelsLoaded = false
|
||||
return issue.LoadLabels(ctx)
|
||||
}
|
||||
|
||||
|
@@ -88,6 +88,8 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
|
||||
sess.Asc("issue.created_unix").Asc("issue.id")
|
||||
case "recentupdate":
|
||||
sess.Desc("issue.updated_unix").Desc("issue.created_unix").Desc("issue.id")
|
||||
case "recentclose":
|
||||
sess.Desc("issue.closed_unix").Desc("issue.created_unix").Desc("issue.id")
|
||||
case "leastupdate":
|
||||
sess.Asc("issue.updated_unix").Asc("issue.created_unix").Asc("issue.id")
|
||||
case "mostcomment":
|
||||
|
@@ -12,9 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
@@ -715,138 +713,13 @@ func UpdateReactionsMigrationsByType(ctx context.Context, gitServiceType api.Git
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteIssuesByRepoID deletes issues by repositories id
|
||||
func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths []string, err error) {
|
||||
// MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
|
||||
// so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
for {
|
||||
issueIDs := make([]int64, 0, db.DefaultMaxInSize)
|
||||
|
||||
err := sess.Table(&Issue{}).Where("repo_id = ?", repoID).OrderBy("id").Limit(db.DefaultMaxInSize).Cols("id").Find(&issueIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(issueIDs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Delete content histories
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&ContentHistory{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete comments and attachments
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&Comment{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Dependencies for issues in this repository
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&IssueDependency{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete dependencies for issues in other repositories
|
||||
_, err = sess.In("dependency_id", issueIDs).Delete(&IssueDependency{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&Reaction{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&Stopwatch{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&TrackedTime{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&project_model.ProjectIssue{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("dependent_issue_id", issueIDs).Delete(&Comment{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var attachments []*repo_model.Attachment
|
||||
err = sess.In("issue_id", issueIDs).Find(&attachments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for j := range attachments {
|
||||
attachmentPaths = append(attachmentPaths, attachments[j].RelativePath())
|
||||
}
|
||||
|
||||
_, err = sess.In("issue_id", issueIDs).Delete(&repo_model.Attachment{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = sess.In("id", issueIDs).Delete(&Issue{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func GetOrphanedIssueRepoIDs(ctx context.Context) ([]int64, error) {
|
||||
var repoIDs []int64
|
||||
if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id").
|
||||
Join("LEFT", "repository", "issue.repo_id=repository.id").
|
||||
Where(builder.IsNull{"repository.id"}).
|
||||
Find(&repoIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attachmentPaths, err
|
||||
}
|
||||
|
||||
// DeleteOrphanedIssues delete issues without a repo
|
||||
func DeleteOrphanedIssues(ctx context.Context) error {
|
||||
var attachmentPaths []string
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
var ids []int64
|
||||
|
||||
if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id").
|
||||
Join("LEFT", "repository", "issue.repo_id=repository.id").
|
||||
Where(builder.IsNull{"repository.id"}).GroupBy("issue.repo_id").
|
||||
Find(&ids); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range ids {
|
||||
paths, err := DeleteIssuesByRepoID(ctx, ids[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attachmentPaths = append(attachmentPaths, paths...)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove issue attachment files.
|
||||
for i := range attachmentPaths {
|
||||
// FIXME: it's not right, because the attachment might not be on local filesystem
|
||||
system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i])
|
||||
}
|
||||
return nil
|
||||
return repoIDs, nil
|
||||
}
|
||||
|
@@ -235,7 +235,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
|
||||
|
||||
// AddCrossReferences add cross references
|
||||
func (c *Comment) AddCrossReferences(stdCtx context.Context, doer *user_model.User, removeOld bool) error {
|
||||
if c.Type != CommentTypeCode && c.Type != CommentTypeComment {
|
||||
if !c.Type.HasContentSupport() {
|
||||
return nil
|
||||
}
|
||||
if err := c.LoadIssue(stdCtx); err != nil {
|
||||
|
@@ -152,7 +152,8 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
|
||||
applySorts(findSession, opts.SortType, 0)
|
||||
findSession = db.SetSessionPagination(findSession, opts)
|
||||
prs := make([]*PullRequest, 0, opts.PageSize)
|
||||
return prs, maxResults, findSession.Find(&prs)
|
||||
found := findSession.Find(&prs)
|
||||
return prs, maxResults, found
|
||||
}
|
||||
|
||||
// PullRequestList defines a list of pull requests
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPullRequest_LoadAttributes(t *testing.T) {
|
||||
@@ -76,6 +77,47 @@ func TestPullRequestsNewest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullRequests_Closed_RecentSortType(t *testing.T) {
|
||||
// Issue ID | Closed At. | Updated At
|
||||
// 2 | 1707270001 | 1707270001
|
||||
// 3 | 1707271000 | 1707279999
|
||||
// 11 | 1707279999 | 1707275555
|
||||
tests := []struct {
|
||||
sortType string
|
||||
expectedIssueIDOrder []int64
|
||||
}{
|
||||
{"recentupdate", []int64{3, 11, 2}},
|
||||
{"recentclose", []int64{11, 3, 2}},
|
||||
}
|
||||
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
_, err := db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707270001, updated_unix = 1707270001, is_closed = true WHERE id = 2")
|
||||
require.NoError(t, err)
|
||||
_, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707271000, updated_unix = 1707279999, is_closed = true WHERE id = 3")
|
||||
require.NoError(t, err)
|
||||
_, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707279999, updated_unix = 1707275555, is_closed = true WHERE id = 11")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.sortType, func(t *testing.T) {
|
||||
prs, _, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: 1,
|
||||
},
|
||||
State: "closed",
|
||||
SortType: test.sortType,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
if assert.Len(t, prs, len(test.expectedIssueIDOrder)) {
|
||||
for i := range test.expectedIssueIDOrder {
|
||||
assert.Equal(t, test.expectedIssueIDOrder[i], prs[i].IssueID)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRequestedReviewers(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
@@ -14,5 +14,8 @@ func AddIndexToActionTaskStoppedLogExpired(x *xorm.Engine) error {
|
||||
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
||||
LogExpired bool `xorm:"index(stopped_log_expired)"`
|
||||
}
|
||||
return x.Sync(new(ActionTask))
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(ActionTask))
|
||||
return err
|
||||
}
|
||||
|
51
models/migrations/v1_23/v302_test.go
Normal file
51
models/migrations/v1_23/v302_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_AddIndexToActionTaskStoppedLogExpired(t *testing.T) {
|
||||
type ActionTask struct {
|
||||
ID int64
|
||||
JobID int64
|
||||
Attempt int64
|
||||
RunnerID int64 `xorm:"index"`
|
||||
Status int `xorm:"index"`
|
||||
Started timeutil.TimeStamp `xorm:"index"`
|
||||
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
||||
|
||||
RepoID int64 `xorm:"index"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
CommitSHA string `xorm:"index"`
|
||||
IsForkPullRequest bool
|
||||
|
||||
Token string `xorm:"-"`
|
||||
TokenHash string `xorm:"UNIQUE"` // sha256 of token
|
||||
TokenSalt string
|
||||
TokenLastEight string `xorm:"index token_last_eight"`
|
||||
|
||||
LogFilename string // file name of log
|
||||
LogInStorage bool // read log from database or from storage
|
||||
LogLength int64 // lines count
|
||||
LogSize int64 // blob size
|
||||
LogIndexes []int64 `xorm:"LONGBLOB"` // line number to offset
|
||||
LogExpired bool `xorm:"index(stopped_log_expired)"` // files that are too old will be deleted
|
||||
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated index"`
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(ActionTask))
|
||||
defer deferable()
|
||||
|
||||
assert.NoError(t, AddIndexToActionTaskStoppedLogExpired(x))
|
||||
}
|
@@ -9,5 +9,8 @@ func AddIndexForReleaseSha1(x *xorm.Engine) error {
|
||||
type Release struct {
|
||||
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
||||
}
|
||||
return x.Sync(new(Release))
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(Release))
|
||||
return err
|
||||
}
|
||||
|
40
models/migrations/v1_23/v304_test.go
Normal file
40
models/migrations/v1_23/v304_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_AddIndexForReleaseSha1(t *testing.T) {
|
||||
type Release struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(n)"`
|
||||
PublisherID int64 `xorm:"INDEX"`
|
||||
TagName string `xorm:"INDEX UNIQUE(n)"`
|
||||
OriginalAuthor string
|
||||
OriginalAuthorID int64 `xorm:"index"`
|
||||
LowerTagName string
|
||||
Target string
|
||||
Title string
|
||||
Sha1 string `xorm:"VARCHAR(64)"`
|
||||
NumCommits int64
|
||||
Note string `xorm:"TEXT"`
|
||||
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(Release))
|
||||
defer deferable()
|
||||
|
||||
assert.NoError(t, AddIndexForReleaseSha1(x))
|
||||
}
|
@@ -602,8 +602,3 @@ func getUserTeamIDsQueryBuilder(orgID, userID int64) *builder.Builder {
|
||||
"team_user.uid": userID,
|
||||
})
|
||||
}
|
||||
|
||||
// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
|
||||
func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
||||
return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)
|
||||
}
|
||||
|
@@ -334,7 +334,7 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
||||
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
||||
assert.NoError(t, err)
|
||||
repoIDs, err := env.RepoIDs(db.DefaultContext, 1, 100)
|
||||
repoIDs, err := env.RepoIDs(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedRepoIDs, repoIDs)
|
||||
}
|
||||
@@ -342,25 +342,6 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||
testSuccess(4, []int64{3, 32})
|
||||
}
|
||||
|
||||
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
||||
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
||||
assert.NoError(t, err)
|
||||
repos, err := env.Repos(db.DefaultContext, 1, 100)
|
||||
assert.NoError(t, err)
|
||||
expectedRepos := make(repo_model.RepositoryList, len(expectedRepoIDs))
|
||||
for i, repoID := range expectedRepoIDs {
|
||||
expectedRepos[i] = unittest.AssertExistsAndLoadBean(t,
|
||||
&repo_model.Repository{ID: repoID})
|
||||
}
|
||||
assert.Equal(t, expectedRepos, repos)
|
||||
}
|
||||
testSuccess(2, []int64{3, 5, 32})
|
||||
testSuccess(4, []int64{3, 32})
|
||||
}
|
||||
|
||||
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
|
@@ -9,6 +9,8 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// TeamRepo represents an team-repository relation.
|
||||
@@ -48,26 +50,27 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository.
|
||||
func GetTeamsWithAccessToRepo(ctx context.Context, orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
||||
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
||||
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
|
||||
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
|
||||
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) {
|
||||
teams := make([]*Team, 0, 5)
|
||||
return teams, db.GetEngine(ctx).Where("team.authorize >= ?", mode).
|
||||
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||
And("team_repo.org_id = ?", orgID).
|
||||
And("team_repo.repo_id = ?", repoID).
|
||||
OrderBy("name").
|
||||
Find(&teams)
|
||||
}
|
||||
|
||||
// GetTeamsWithAccessToRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
||||
func GetTeamsWithAccessToRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type) ([]*Team, error) {
|
||||
teams := make([]*Team, 0, 5)
|
||||
return teams, db.GetEngine(ctx).Where("team_unit.access_mode >= ?", mode).
|
||||
sub := builder.Select("team_id").From("team_unit").
|
||||
Where(builder.Expr("team_unit.team_id = team.id")).
|
||||
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
|
||||
And(builder.Expr("team_unit.access_mode >= ?", mode))
|
||||
|
||||
err := db.GetEngine(ctx).
|
||||
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||
Join("INNER", "team_unit", "team_unit.team_id = team.id").
|
||||
And("team_repo.org_id = ?", orgID).
|
||||
And("team_repo.repo_id = ?", repoID).
|
||||
And("team_unit.type = ?", unitType).
|
||||
And(builder.Or(
|
||||
builder.Expr("team.authorize >= ?", mode),
|
||||
builder.In("team.id", sub),
|
||||
)).
|
||||
OrderBy("name").
|
||||
Find(&teams)
|
||||
|
||||
return teams, err
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ func TestGetTeamsWithAccessToRepoUnit(t *testing.T) {
|
||||
org41 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 41})
|
||||
repo61 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 61})
|
||||
|
||||
teams, err := organization.GetTeamsWithAccessToRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
|
||||
teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, teams, 2) {
|
||||
assert.EqualValues(t, 21, teams[0].ID)
|
||||
|
@@ -33,7 +33,7 @@ func SearchVersions(ctx context.Context, opts *packages_model.PackageSearchOptio
|
||||
Where(cond).
|
||||
OrderBy("package.name ASC")
|
||||
if opts.Paginator != nil {
|
||||
skip, take := opts.GetSkipTake()
|
||||
skip, take := opts.Paginator.GetSkipTake()
|
||||
inner = inner.Limit(take, skip)
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// ErrDuplicatePackageVersion indicates a duplicated package version error
|
||||
@@ -187,7 +188,7 @@ type PackageSearchOptions struct {
|
||||
HasFileWithName string // only results are found which are associated with a file with the specific name
|
||||
HasFiles optional.Option[bool] // only results are found which have associated files
|
||||
Sort VersionSort
|
||||
db.Paginator
|
||||
Paginator db.Paginator
|
||||
}
|
||||
|
||||
func (opts *PackageSearchOptions) ToConds() builder.Cond {
|
||||
@@ -282,6 +283,18 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
||||
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
|
||||
}
|
||||
|
||||
func searchVersionsBySession(sess *xorm.Session, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||
opts.configureOrderBy(sess)
|
||||
pvs := make([]*PackageVersion, 0, 10)
|
||||
if opts.Paginator != nil {
|
||||
sess = db.SetSessionPagination(sess, opts.Paginator)
|
||||
count, err := sess.FindAndCount(&pvs)
|
||||
return pvs, count, err
|
||||
}
|
||||
err := sess.Find(&pvs)
|
||||
return pvs, int64(len(pvs)), err
|
||||
}
|
||||
|
||||
// SearchVersions gets all versions of packages matching the search options
|
||||
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
@@ -289,16 +302,7 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(opts.ToConds())
|
||||
|
||||
opts.configureOrderBy(sess)
|
||||
|
||||
if opts.Paginator != nil {
|
||||
sess = db.SetSessionPagination(sess, opts)
|
||||
}
|
||||
|
||||
pvs := make([]*PackageVersion, 0, 10)
|
||||
count, err := sess.FindAndCount(&pvs)
|
||||
return pvs, count, err
|
||||
return searchVersionsBySession(sess, opts)
|
||||
}
|
||||
|
||||
// SearchLatestVersions gets the latest version of every package matching the search options
|
||||
@@ -316,15 +320,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(builder.In("package_version.id", in))
|
||||
|
||||
opts.configureOrderBy(sess)
|
||||
|
||||
if opts.Paginator != nil {
|
||||
sess = db.SetSessionPagination(sess, opts)
|
||||
}
|
||||
|
||||
pvs := make([]*PackageVersion, 0, 10)
|
||||
count, err := sess.FindAndCount(&pvs)
|
||||
return pvs, count, err
|
||||
return searchVersionsBySession(sess, opts)
|
||||
}
|
||||
|
||||
// ExistVersion checks if a version matching the search options exist
|
||||
|
@@ -42,6 +42,7 @@ func (p *Permission) IsAdmin() bool {
|
||||
|
||||
// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
|
||||
// It doesn't count the "public(anonymous/everyone) access mode".
|
||||
// TODO: most calls to this function should be replaced with `HasAnyUnitAccessOrPublicAccess`
|
||||
func (p *Permission) HasAnyUnitAccess() bool {
|
||||
for _, v := range p.unitsMode {
|
||||
if v >= perm_model.AccessModeRead {
|
||||
@@ -267,7 +268,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||
perm.units = repo.Units
|
||||
|
||||
// anonymous user visit private repo.
|
||||
// TODO: anonymous user visit public unit of private repo???
|
||||
if user == nil && repo.IsPrivate {
|
||||
perm.AccessMode = perm_model.AccessModeNone
|
||||
return perm, nil
|
||||
@@ -286,7 +286,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||
}
|
||||
|
||||
// Prevent strangers from checking out public repo of private organization/users
|
||||
// Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself
|
||||
// Allow user if they are a collaborator of a repo within a private user or a private organization but not a member of the organization itself
|
||||
// TODO: rename it to "IsOwnerVisibleToDoer"
|
||||
if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator {
|
||||
perm.AccessMode = perm_model.AccessModeNone
|
||||
return perm, nil
|
||||
@@ -304,7 +305,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||
return perm, nil
|
||||
}
|
||||
|
||||
// plain user
|
||||
// plain user TODO: this check should be replaced, only need to check collaborator access mode
|
||||
perm.AccessMode, err = accessLevel(ctx, user, repo)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
@@ -314,6 +315,19 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||
return perm, nil
|
||||
}
|
||||
|
||||
// now: the owner is visible to doer, if the repo is public, then the min access mode is read
|
||||
minAccessMode := util.Iif(!repo.IsPrivate && !user.IsRestricted, perm_model.AccessModeRead, perm_model.AccessModeNone)
|
||||
perm.AccessMode = max(perm.AccessMode, minAccessMode)
|
||||
|
||||
// get units mode from teams
|
||||
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
if len(teams) == 0 {
|
||||
return perm, nil
|
||||
}
|
||||
|
||||
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
|
||||
|
||||
// Collaborators on organization
|
||||
@@ -323,12 +337,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||
}
|
||||
}
|
||||
|
||||
// get units mode from teams
|
||||
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
|
||||
// if user in an owner team
|
||||
for _, team := range teams {
|
||||
if team.HasAdminAccess() {
|
||||
@@ -339,19 +347,12 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||
}
|
||||
|
||||
for _, u := range repo.Units {
|
||||
var found bool
|
||||
for _, team := range teams {
|
||||
unitAccessMode := minAccessMode
|
||||
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
|
||||
perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
|
||||
if !found && !repo.IsPrivate && !user.IsRestricted {
|
||||
if _, ok := perm.unitsMode[u.Type]; !ok {
|
||||
perm.unitsMode[u.Type] = perm_model.AccessModeRead
|
||||
unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode)
|
||||
}
|
||||
perm.unitsMode[u.Type] = unitAccessMode
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,16 @@ package access
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
perm_model "code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHasAnyUnitAccess(t *testing.T) {
|
||||
@@ -152,3 +156,45 @@ func TestUnitAccessMode(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
|
||||
}
|
||||
|
||||
func TestGetUserRepoPermission(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
repo32 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) // org public repo
|
||||
require.NoError(t, repo32.LoadOwner(ctx))
|
||||
require.True(t, repo32.Owner.IsOrganization())
|
||||
|
||||
require.NoError(t, db.TruncateBeans(ctx, &organization.Team{}, &organization.TeamUser{}, &organization.TeamRepo{}, &organization.TeamUnit{}))
|
||||
org := repo32.Owner
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
|
||||
require.NoError(t, db.Insert(ctx, team))
|
||||
|
||||
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
|
||||
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||
assert.Nil(t, perm.unitsMode) // doer in the team, but has no access to the repo
|
||||
})
|
||||
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo32.ID}))
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
|
||||
t.Run("DoerWithTeamUnitAccessNone", func(t *testing.T) {
|
||||
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeCode])
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
||||
})
|
||||
|
||||
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}))
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeWrite}))
|
||||
t.Run("DoerWithTeamUnitAccessWrite", func(t *testing.T) {
|
||||
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
||||
})
|
||||
}
|
||||
|
@@ -5,12 +5,14 @@ package pull
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// AutoMerge represents a pull request scheduled for merging when checks succeed
|
||||
@@ -76,7 +78,10 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
doer, err := user_model.GetUserByID(ctx, scheduledPRM.DoerID)
|
||||
doer, err := user_model.GetPossibleUserByID(ctx, scheduledPRM.DoerID)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
doer, err = user_model.NewGhostUser(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
|
||||
c.gitRepo, c.gitRepoCloser = r, closer
|
||||
}
|
||||
|
||||
exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
|
||||
exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashes with gogit edition.
|
||||
c.commitCache[commitID] = exist
|
||||
return exist
|
||||
}
|
||||
|
@@ -44,30 +44,31 @@ type RepoCommentOptions struct {
|
||||
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
||||
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||
CurrentRefPath string // eg: "branch/main" or "commit/11223344"
|
||||
FootnoteContextID string // the extra context ID for footnotes, used to avoid conflicts with other footnotes in the same page
|
||||
}
|
||||
|
||||
func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repository, opts ...RepoCommentOptions) *markup.RenderContext {
|
||||
helper := &RepoComment{
|
||||
repoLink: repo.Link(),
|
||||
opts: util.OptionalArg(opts),
|
||||
}
|
||||
helper := &RepoComment{opts: util.OptionalArg(opts)}
|
||||
rctx := markup.NewRenderContext(ctx)
|
||||
helper.ctx = rctx
|
||||
var metas map[string]string
|
||||
if repo != nil {
|
||||
helper.repoLink = repo.Link()
|
||||
helper.commitChecker = newCommitChecker(ctx, repo)
|
||||
rctx = rctx.WithMetas(repo.ComposeCommentMetas(ctx))
|
||||
metas = repo.ComposeCommentMetas(ctx)
|
||||
} else {
|
||||
// this is almost dead code, only to pass the incorrect tests
|
||||
helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName)
|
||||
rctx = rctx.WithMetas(map[string]string{
|
||||
"user": helper.opts.DeprecatedOwnerName,
|
||||
"repo": helper.opts.DeprecatedRepoName,
|
||||
|
||||
"markdownNewLineHardBreak": "true",
|
||||
"markupAllowShortIssuePattern": "true",
|
||||
})
|
||||
// repo can be nil when rendering a commit message in user's dashboard feedback whose repository has been deleted
|
||||
metas = map[string]string{}
|
||||
if helper.opts.DeprecatedOwnerName != "" {
|
||||
// this is almost dead code, only to pass the incorrect tests
|
||||
helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName)
|
||||
metas["user"] = helper.opts.DeprecatedOwnerName
|
||||
metas["repo"] = helper.opts.DeprecatedRepoName
|
||||
}
|
||||
metas["markdownNewLineHardBreak"] = "true"
|
||||
metas["markupAllowShortIssuePattern"] = "true"
|
||||
}
|
||||
rctx = rctx.WithHelper(helper)
|
||||
metas["footnoteContextId"] = helper.opts.FootnoteContextID
|
||||
rctx = rctx.WithMetas(metas).WithHelper(helper)
|
||||
return rctx
|
||||
}
|
||||
|
@@ -72,4 +72,11 @@ func TestRepoComment(t *testing.T) {
|
||||
<a href="/user2/repo1/commit/1234/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/commit/1234/image" alt="./image"/></a></p>
|
||||
`, rendered)
|
||||
})
|
||||
|
||||
t.Run("NoRepo", func(t *testing.T) {
|
||||
rctx := NewRenderContextRepoComment(t.Context(), nil).WithMarkupType(markdown.MarkupName)
|
||||
rendered, err := markup.RenderString(rctx, "any")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "<p>any</p>\n", rendered)
|
||||
})
|
||||
}
|
||||
|
@@ -48,8 +48,7 @@ func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (Repo
|
||||
// accessible to a particular user
|
||||
type AccessibleReposEnvironment interface {
|
||||
CountRepos(ctx context.Context) (int64, error)
|
||||
RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error)
|
||||
Repos(ctx context.Context, page, pageSize int) (RepositoryList, error)
|
||||
RepoIDs(ctx context.Context) ([]int64, error)
|
||||
MirrorRepos(ctx context.Context) (RepositoryList, error)
|
||||
AddKeyword(keyword string)
|
||||
SetSort(db.SearchOrderBy)
|
||||
@@ -132,40 +131,18 @@ func (env *accessibleReposEnv) CountRepos(ctx context.Context) (int64, error) {
|
||||
return repoCount, nil
|
||||
}
|
||||
|
||||
func (env *accessibleReposEnv) RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
repoIDs := make([]int64, 0, pageSize)
|
||||
func (env *accessibleReposEnv) RepoIDs(ctx context.Context) ([]int64, error) {
|
||||
var repoIDs []int64
|
||||
return repoIDs, db.GetEngine(ctx).
|
||||
Table("repository").
|
||||
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
|
||||
Where(env.cond()).
|
||||
GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]).
|
||||
GroupBy("`repository`.id,`repository`." + strings.Fields(string(env.orderBy))[0]).
|
||||
OrderBy(string(env.orderBy)).
|
||||
Limit(pageSize, (page-1)*pageSize).
|
||||
Cols("`repository`.id").
|
||||
Find(&repoIDs)
|
||||
}
|
||||
|
||||
func (env *accessibleReposEnv) Repos(ctx context.Context, page, pageSize int) (RepositoryList, error) {
|
||||
repoIDs, err := env.RepoIDs(ctx, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err)
|
||||
}
|
||||
|
||||
repos := make([]*Repository, 0, len(repoIDs))
|
||||
if len(repoIDs) == 0 {
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
return repos, db.GetEngine(ctx).
|
||||
In("`repository`.id", repoIDs).
|
||||
OrderBy(string(env.orderBy)).
|
||||
Find(&repos)
|
||||
}
|
||||
|
||||
func (env *accessibleReposEnv) MirrorRepoIDs(ctx context.Context) ([]int64, error) {
|
||||
repoIDs := make([]int64, 0, 10)
|
||||
return repoIDs, db.GetEngine(ctx).
|
||||
|
@@ -161,6 +161,11 @@ func UpdateRelease(ctx context.Context, rel *Release) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateReleaseNumCommits(ctx context.Context, rel *Release) error {
|
||||
_, err := db.GetEngine(ctx).ID(rel.ID).Cols("num_commits").Update(rel)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddReleaseAttachments adds a release attachments
|
||||
func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
|
||||
// Check attachments
|
||||
@@ -418,8 +423,8 @@ func UpdateReleasesMigrationsByType(ctx context.Context, gitServiceType structs.
|
||||
return err
|
||||
}
|
||||
|
||||
// PushUpdateDeleteTagsContext updates a number of delete tags with context
|
||||
func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []string) error {
|
||||
// PushUpdateDeleteTags updates a number of delete tags with context
|
||||
func PushUpdateDeleteTags(ctx context.Context, repo *Repository, tags []string) error {
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -448,58 +453,6 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushUpdateDeleteTag must be called for any push actions to delete tag
|
||||
func PushUpdateDeleteTag(ctx context.Context, repo *Repository, tagName string) error {
|
||||
rel, err := GetRelease(ctx, repo.ID, tagName)
|
||||
if err != nil {
|
||||
if IsErrReleaseNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("GetRelease: %w", err)
|
||||
}
|
||||
if rel.IsTag {
|
||||
if _, err = db.DeleteByID[Release](ctx, rel.ID); err != nil {
|
||||
return fmt.Errorf("Delete: %w", err)
|
||||
}
|
||||
} else {
|
||||
rel.IsDraft = true
|
||||
rel.NumCommits = 0
|
||||
rel.Sha1 = ""
|
||||
if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveOrUpdateTag must be called for any push actions to add tag
|
||||
func SaveOrUpdateTag(ctx context.Context, repo *Repository, newRel *Release) error {
|
||||
rel, err := GetRelease(ctx, repo.ID, newRel.TagName)
|
||||
if err != nil && !IsErrReleaseNotExist(err) {
|
||||
return fmt.Errorf("GetRelease: %w", err)
|
||||
}
|
||||
|
||||
if rel == nil {
|
||||
rel = newRel
|
||||
if _, err = db.GetEngine(ctx).Insert(rel); err != nil {
|
||||
return fmt.Errorf("InsertOne: %w", err)
|
||||
}
|
||||
} else {
|
||||
rel.Sha1 = newRel.Sha1
|
||||
rel.CreatedUnix = newRel.CreatedUnix
|
||||
rel.NumCommits = newRel.NumCommits
|
||||
rel.IsDraft = false
|
||||
if rel.IsTag && newRel.PublisherID > 0 {
|
||||
rel.PublisherID = newRel.PublisherID
|
||||
}
|
||||
if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemapExternalUser ExternalUserRemappable interface
|
||||
func (r *Release) RemapExternalUser(externalName string, externalID, userID int64) error {
|
||||
r.OriginalAuthor = externalName
|
||||
|
@@ -64,18 +64,18 @@ func (err ErrRepoIsArchived) Error() string {
|
||||
}
|
||||
|
||||
type globalVarsStruct struct {
|
||||
validRepoNamePattern *regexp.Regexp
|
||||
invalidRepoNamePattern *regexp.Regexp
|
||||
reservedRepoNames []string
|
||||
reservedRepoPatterns []string
|
||||
validRepoNamePattern *regexp.Regexp
|
||||
invalidRepoNamePattern *regexp.Regexp
|
||||
reservedRepoNames []string
|
||||
reservedRepoNamePatterns []string
|
||||
}
|
||||
|
||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||
return &globalVarsStruct{
|
||||
validRepoNamePattern: regexp.MustCompile(`[-.\w]+`),
|
||||
invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
|
||||
reservedRepoNames: []string{".", "..", "-"},
|
||||
reservedRepoPatterns: []string{"*.git", "*.wiki", "*.rss", "*.atom"},
|
||||
validRepoNamePattern: regexp.MustCompile(`^[-.\w]+$`),
|
||||
invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
|
||||
reservedRepoNames: []string{".", "..", "-"},
|
||||
reservedRepoNamePatterns: []string{"*.wiki", "*.git", "*.rss", "*.atom"},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -86,7 +86,16 @@ func IsUsableRepoName(name string) error {
|
||||
// Note: usually this error is normally caught up earlier in the UI
|
||||
return db.ErrNameCharsNotAllowed{Name: name}
|
||||
}
|
||||
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoPatterns, name)
|
||||
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns, name)
|
||||
}
|
||||
|
||||
// IsValidSSHAccessRepoName is like IsUsableRepoName, but it allows "*.wiki" because wiki repo needs to be accessed in SSH code
|
||||
func IsValidSSHAccessRepoName(name string) bool {
|
||||
vars := globalVars()
|
||||
if !vars.validRepoNamePattern.MatchString(name) || vars.invalidRepoNamePattern.MatchString(name) {
|
||||
return false
|
||||
}
|
||||
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns[1:], name) == nil
|
||||
}
|
||||
|
||||
// TrustModelType defines the types of trust model for this repository
|
||||
|
@@ -216,8 +216,23 @@ func TestIsUsableRepoName(t *testing.T) {
|
||||
|
||||
assert.Error(t, IsUsableRepoName("-"))
|
||||
assert.Error(t, IsUsableRepoName("🌞"))
|
||||
assert.Error(t, IsUsableRepoName("the/repo"))
|
||||
assert.Error(t, IsUsableRepoName("the..repo"))
|
||||
assert.Error(t, IsUsableRepoName("foo.wiki"))
|
||||
assert.Error(t, IsUsableRepoName("foo.git"))
|
||||
assert.Error(t, IsUsableRepoName("foo.RSS"))
|
||||
}
|
||||
|
||||
func TestIsValidSSHAccessRepoName(t *testing.T) {
|
||||
assert.True(t, IsValidSSHAccessRepoName("a"))
|
||||
assert.True(t, IsValidSSHAccessRepoName("-1_."))
|
||||
assert.True(t, IsValidSSHAccessRepoName(".profile"))
|
||||
assert.True(t, IsValidSSHAccessRepoName("foo.wiki"))
|
||||
|
||||
assert.False(t, IsValidSSHAccessRepoName("-"))
|
||||
assert.False(t, IsValidSSHAccessRepoName("🌞"))
|
||||
assert.False(t, IsValidSSHAccessRepoName("the/repo"))
|
||||
assert.False(t, IsValidSSHAccessRepoName("the..repo"))
|
||||
assert.False(t, IsValidSSHAccessRepoName("foo.git"))
|
||||
assert.False(t, IsValidSSHAccessRepoName("foo.RSS"))
|
||||
}
|
||||
|
@@ -249,7 +249,7 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m
|
||||
}
|
||||
|
||||
repo.Status = RepositoryPendingTransfer
|
||||
if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
||||
if err := UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -25,7 +25,7 @@ func UpdateRepositoryOwnerNames(ctx context.Context, ownerID int64, ownerName st
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").Update(&Repository{
|
||||
if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").NoAutoTime().Update(&Repository{
|
||||
OwnerName: ownerName,
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -40,8 +40,8 @@ func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime t
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepositoryCols updates repository's columns
|
||||
func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string) error {
|
||||
// UpdateRepositoryColsWithAutoTime updates repository's columns
|
||||
func UpdateRepositoryColsWithAutoTime(ctx context.Context, repo *Repository, cols ...string) error {
|
||||
_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo)
|
||||
return err
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"io"
|
||||
@@ -106,7 +105,7 @@ func (u *User) IsUploadAvatarChanged(data []byte) bool {
|
||||
if !u.UseCustomAvatar || len(u.Avatar) == 0 {
|
||||
return true
|
||||
}
|
||||
avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
|
||||
avatarID := avatar.HashAvatar(u.ID, data)
|
||||
return u.Avatar != avatarID
|
||||
}
|
||||
|
||||
|
@@ -1146,8 +1146,8 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([
|
||||
}
|
||||
|
||||
for _, c := range oldCommits {
|
||||
user, ok := emailUserMap[c.Author.Email]
|
||||
if !ok {
|
||||
user := emailUserMap.GetByEmail(c.Author.Email) // FIXME: why ValidateCommitsWithEmails uses "Author", but ParseCommitsWithSignature uses "Committer"?
|
||||
if user == nil {
|
||||
user = &User{
|
||||
Name: c.Author.Name,
|
||||
Email: c.Author.Email,
|
||||
@@ -1161,19 +1161,29 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([
|
||||
return newCommits, nil
|
||||
}
|
||||
|
||||
func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, error) {
|
||||
type EmailUserMap struct {
|
||||
m map[string]*User
|
||||
}
|
||||
|
||||
func (eum *EmailUserMap) GetByEmail(email string) *User {
|
||||
return eum.m[strings.ToLower(email)]
|
||||
}
|
||||
|
||||
func GetUsersByEmails(ctx context.Context, emails []string) (*EmailUserMap, error) {
|
||||
if len(emails) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
needCheckEmails := make(container.Set[string])
|
||||
needCheckUserNames := make(container.Set[string])
|
||||
noReplyAddressSuffix := "@" + strings.ToLower(setting.Service.NoReplyAddress)
|
||||
for _, email := range emails {
|
||||
if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) {
|
||||
username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress)
|
||||
needCheckUserNames.Add(username)
|
||||
emailLower := strings.ToLower(email)
|
||||
if noReplyUserNameLower, ok := strings.CutSuffix(emailLower, noReplyAddressSuffix); ok {
|
||||
needCheckUserNames.Add(noReplyUserNameLower)
|
||||
needCheckEmails.Add(emailLower)
|
||||
} else {
|
||||
needCheckEmails.Add(strings.ToLower(email))
|
||||
needCheckEmails.Add(emailLower)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1198,7 +1208,7 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
|
||||
for _, email := range emailAddresses {
|
||||
user := users[email.UID]
|
||||
if user != nil {
|
||||
results[user.GetEmail()] = user
|
||||
results[email.LowerEmail] = user
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1208,9 +1218,9 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
|
||||
return nil, err
|
||||
}
|
||||
for _, user := range users {
|
||||
results[user.GetPlaceholderEmail()] = user
|
||||
results[strings.ToLower(user.GetPlaceholderEmail())] = user
|
||||
}
|
||||
return results, nil
|
||||
return &EmailUserMap{results}, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail returns the user object by given e-mail if exists.
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsUsableUsername(t *testing.T) {
|
||||
@@ -48,14 +49,47 @@ func TestOAuth2Application_LoadUser(t *testing.T) {
|
||||
assert.NotNil(t, user)
|
||||
}
|
||||
|
||||
func TestGetUserEmailsByNames(t *testing.T) {
|
||||
func TestUserEmails(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// ignore none active user email
|
||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
|
||||
assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
|
||||
|
||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
|
||||
t.Run("GetUserEmailsByNames", func(t *testing.T) {
|
||||
// ignore none active user email
|
||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
|
||||
assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
|
||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
|
||||
})
|
||||
t.Run("GetUsersByEmails", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Service.NoReplyAddress, "NoReply.gitea.internal")()
|
||||
testGetUserByEmail := func(t *testing.T, email string, uid int64) {
|
||||
m, err := user_model.GetUsersByEmails(db.DefaultContext, []string{email})
|
||||
require.NoError(t, err)
|
||||
user := m.GetByEmail(email)
|
||||
if uid == 0 {
|
||||
require.Nil(t, user)
|
||||
return
|
||||
}
|
||||
require.NotNil(t, user)
|
||||
assert.Equal(t, uid, user.ID)
|
||||
}
|
||||
cases := []struct {
|
||||
Email string
|
||||
UID int64
|
||||
}{
|
||||
{"UseR1@example.com", 1},
|
||||
{"user1-2@example.COM", 1},
|
||||
{"USER2@" + setting.Service.NoReplyAddress, 2},
|
||||
{"user4@example.com", 4},
|
||||
{"no-such", 0},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Email, func(t *testing.T) {
|
||||
testGetUserByEmail(t, c.Email, c.UID)
|
||||
})
|
||||
}
|
||||
t.Run("NoReplyConflict", func(t *testing.T) {
|
||||
setting.Service.NoReplyAddress = "example.com"
|
||||
testGetUserByEmail(t, "user1-2@example.COM", 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCanCreateOrganization(t *testing.T) {
|
||||
|
@@ -311,6 +311,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
|
||||
matchTimes++
|
||||
}
|
||||
case "paths":
|
||||
if refName.IsTag() {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
|
||||
if err != nil {
|
||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
|
||||
@@ -324,6 +328,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
|
||||
}
|
||||
}
|
||||
case "paths-ignore":
|
||||
if refName.IsTag() {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
|
||||
if err != nil {
|
||||
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
|
||||
|
@@ -125,6 +125,24 @@ func TestDetectMatched(t *testing.T) {
|
||||
yamlOn: "on: schedule",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "push to tag matches workflow with paths condition (should skip paths check)",
|
||||
triggedEvent: webhook_module.HookEventPush,
|
||||
payload: &api.PushPayload{
|
||||
Ref: "refs/tags/v1.0.0",
|
||||
Before: "0000000",
|
||||
Commits: []*api.PayloadCommit{
|
||||
{
|
||||
ID: "abcdef123456",
|
||||
Added: []string{"src/main.go"},
|
||||
Message: "Release v1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
commit: nil,
|
||||
yamlOn: "on:\n push:\n paths:\n - src/**",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
@@ -6,22 +6,26 @@ package fileicon
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func BasicThemeIcon(entry *git.TreeEntry) template.HTML {
|
||||
func BasicEntryIconName(entry *EntryInfo) string {
|
||||
svgName := "octicon-file"
|
||||
switch {
|
||||
case entry.IsLink():
|
||||
case entry.EntryMode.IsLink():
|
||||
svgName = "octicon-file-symlink-file"
|
||||
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
|
||||
if entry.SymlinkToMode.IsDir() {
|
||||
svgName = "octicon-file-directory-symlink"
|
||||
}
|
||||
case entry.IsDir():
|
||||
svgName = "octicon-file-directory-fill"
|
||||
case entry.IsSubModule():
|
||||
case entry.EntryMode.IsDir():
|
||||
svgName = util.Iif(entry.IsOpen, "octicon-file-directory-open-fill", "octicon-file-directory-fill")
|
||||
case entry.EntryMode.IsSubModule():
|
||||
svgName = "octicon-file-submodule"
|
||||
}
|
||||
return svg.RenderHTML(svgName)
|
||||
return svgName
|
||||
}
|
||||
|
||||
func BasicEntryIconHTML(entry *EntryInfo) template.HTML {
|
||||
return svg.RenderHTML(BasicEntryIconName(entry))
|
||||
}
|
||||
|
31
modules/fileicon/entry.go
Normal file
31
modules/fileicon/entry.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fileicon
|
||||
|
||||
import "code.gitea.io/gitea/modules/git"
|
||||
|
||||
type EntryInfo struct {
|
||||
FullName string
|
||||
EntryMode git.EntryMode
|
||||
SymlinkToMode git.EntryMode
|
||||
IsOpen bool
|
||||
}
|
||||
|
||||
func EntryInfoFromGitTreeEntry(gitEntry *git.TreeEntry) *EntryInfo {
|
||||
ret := &EntryInfo{FullName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
|
||||
if gitEntry.IsLink() {
|
||||
if te, err := gitEntry.FollowLink(); err == nil && te.IsDir() {
|
||||
ret.SymlinkToMode = te.Mode()
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func EntryInfoFolder() *EntryInfo {
|
||||
return &EntryInfo{EntryMode: git.EntryModeTree}
|
||||
}
|
||||
|
||||
func EntryInfoFolderOpen() *EntryInfo {
|
||||
return &EntryInfo{EntryMode: git.EntryModeTree, IsOpen: true}
|
||||
}
|
@@ -9,11 +9,12 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type materialIconRulesData struct {
|
||||
@@ -69,41 +70,51 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg,
|
||||
}
|
||||
svgID := "svg-mfi-" + name
|
||||
svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"`
|
||||
svgHTML := template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
|
||||
if p == nil {
|
||||
return svgHTML
|
||||
}
|
||||
if p.IconSVGs[svgID] == "" {
|
||||
p.IconSVGs[svgID] = template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
|
||||
p.IconSVGs[svgID] = svgHTML
|
||||
}
|
||||
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
||||
func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||
if m.rules == nil {
|
||||
return BasicThemeIcon(entry)
|
||||
return BasicEntryIconHTML(entry)
|
||||
}
|
||||
|
||||
if entry.IsLink() {
|
||||
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
|
||||
if entry.EntryMode.IsLink() {
|
||||
if entry.SymlinkToMode.IsDir() {
|
||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||
return svg.RenderHTML("material-folder-symlink", 16, "octicon-file-directory-symlink")
|
||||
}
|
||||
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
|
||||
}
|
||||
|
||||
name := m.findIconNameByGit(entry)
|
||||
// the material icon pack's "folder" icon doesn't look good, so use our built-in one
|
||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||
if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" {
|
||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||
extraClass := "octicon-file"
|
||||
switch {
|
||||
case entry.IsDir():
|
||||
extraClass = "octicon-file-directory-fill"
|
||||
case entry.IsSubModule():
|
||||
extraClass = "octicon-file-submodule"
|
||||
name := m.FindIconName(entry)
|
||||
iconSVG := m.svgs[name]
|
||||
if iconSVG == "" {
|
||||
name = "file"
|
||||
if entry.EntryMode.IsDir() {
|
||||
name = util.Iif(entry.IsOpen, "folder-open", "folder")
|
||||
}
|
||||
iconSVG = m.svgs[name]
|
||||
if iconSVG == "" {
|
||||
setting.PanicInDevOrTesting("missing file icon for %s", name)
|
||||
}
|
||||
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
|
||||
}
|
||||
// TODO: use an interface or wrapper for git.Entry to make the code testable.
|
||||
return BasicThemeIcon(entry)
|
||||
|
||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||
extraClass := "octicon-file"
|
||||
switch {
|
||||
case entry.EntryMode.IsDir():
|
||||
extraClass = BasicEntryIconName(entry)
|
||||
case entry.EntryMode.IsSubModule():
|
||||
extraClass = "octicon-file-submodule"
|
||||
}
|
||||
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
|
||||
@@ -118,13 +129,17 @@ func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
|
||||
fileNameLower := strings.ToLower(path.Base(name))
|
||||
if isDir {
|
||||
func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
|
||||
if entry.EntryMode.IsSubModule() {
|
||||
return "folder-git"
|
||||
}
|
||||
|
||||
fileNameLower := strings.ToLower(path.Base(entry.FullName))
|
||||
if entry.EntryMode.IsDir() {
|
||||
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
|
||||
return s
|
||||
}
|
||||
return "folder"
|
||||
return util.Iif(entry.IsOpen, "folder-open", "folder")
|
||||
}
|
||||
|
||||
if s, ok := m.rules.FileNames[fileNameLower]; ok {
|
||||
@@ -146,10 +161,3 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
|
||||
|
||||
return "file"
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string {
|
||||
if entry.IsSubModule() {
|
||||
return "folder-git"
|
||||
}
|
||||
return m.FindIconName(entry.Name(), entry.IsDir())
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/fileicon"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -19,8 +20,8 @@ func TestMain(m *testing.M) {
|
||||
func TestFindIconName(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
p := fileicon.DefaultMaterialIconProvider()
|
||||
assert.Equal(t, "php", p.FindIconName("foo.php", false))
|
||||
assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
|
||||
assert.Equal(t, "javascript", p.FindIconName("foo.js", false))
|
||||
assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false))
|
||||
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.php", EntryMode: git.EntryModeBlob}))
|
||||
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.PHP", EntryMode: git.EntryModeBlob}))
|
||||
assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.js", EntryMode: git.EntryModeBlob}))
|
||||
assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.vba", EntryMode: git.EntryModeBlob}))
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
@@ -34,19 +33,9 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML {
|
||||
return template.HTML(sb.String())
|
||||
}
|
||||
|
||||
// TODO: use an interface or struct to replace "*git.TreeEntry", to decouple the fileicon module from git module
|
||||
|
||||
func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
||||
func RenderEntryIconHTML(renderedIconPool *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||
if setting.UI.FileIconTheme == "material" {
|
||||
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
|
||||
return DefaultMaterialIconProvider().EntryIconHTML(renderedIconPool, entry)
|
||||
}
|
||||
return BasicThemeIcon(entry)
|
||||
}
|
||||
|
||||
func RenderEntryIconOpen(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
||||
// TODO: add "open icon" support
|
||||
if setting.UI.FileIconTheme == "material" {
|
||||
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
|
||||
}
|
||||
return BasicThemeIcon(entry)
|
||||
return BasicEntryIconHTML(entry)
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ const (
|
||||
GitlabLanguage = "gitlab-language"
|
||||
Lockable = "lockable"
|
||||
Filter = "filter"
|
||||
Diff = "diff"
|
||||
)
|
||||
|
||||
var LinguistAttributes = []string{
|
||||
|
@@ -39,7 +39,12 @@ func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attrib
|
||||
)
|
||||
cancel = deleteTemporaryFile
|
||||
}
|
||||
} // else: no treeish, assume it is a not a bare repo, read from working directory
|
||||
} else {
|
||||
// Read from existing index, in cases where the repo is bare and has an index,
|
||||
// or the work tree contains unstaged changes that shouldn't affect the attribute check.
|
||||
// It is caller's responsibility to add changed ".gitattributes" into the index if they want to respect the new changes.
|
||||
cmd.AddArguments("--cached")
|
||||
}
|
||||
|
||||
cmd.AddDynamicArguments(attributes...)
|
||||
if len(filenames) > 0 {
|
||||
|
@@ -57,8 +57,18 @@ func Test_Checker(t *testing.T) {
|
||||
assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
|
||||
})
|
||||
|
||||
t.Run("Run git check-attr in bare repository using index", func(t *testing.T) {
|
||||
attrs, err := CheckAttributes(t.Context(), gitRepo, "", CheckAttributeOpts{
|
||||
Filenames: []string{"i-am-a-python.p"},
|
||||
Attributes: LinguistAttributes,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, attrs, 1)
|
||||
assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
|
||||
})
|
||||
|
||||
if !git.DefaultFeatures().SupportCheckAttrOnBare {
|
||||
t.Skip("git version 2.40 is required to support run check-attr on bare repo")
|
||||
t.Skip("git version 2.40 is required to support run check-attr on bare repo without using index")
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -132,18 +132,22 @@ func (r *BlameReader) Close() error {
|
||||
}
|
||||
|
||||
// CreateBlameReader creates reader for given repository, commit and file
|
||||
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
||||
reader, stdout, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (rd *BlameReader, err error) {
|
||||
var ignoreRevsFileName string
|
||||
var ignoreRevsFileCleanup func()
|
||||
defer func() {
|
||||
if err != nil && ignoreRevsFileCleanup != nil {
|
||||
ignoreRevsFileCleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
cmd := NewCommandNoGlobals("blame", "--porcelain")
|
||||
|
||||
var ignoreRevsFileName string
|
||||
var ignoreRevsFileCleanup func() // TODO: maybe it should check the returned err in a defer func to make sure the cleanup could always be executed correctly
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
|
||||
ignoreRevsFileName, ignoreRevsFileCleanup = tryCreateBlameIgnoreRevsFile(commit)
|
||||
ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit)
|
||||
if err != nil && !IsErrNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if ignoreRevsFileName != "" {
|
||||
// Possible improvement: use --ignore-revs-file /dev/stdin on unix
|
||||
// There is no equivalent on Windows. May be implemented if Gitea uses an external git backend.
|
||||
@@ -154,6 +158,10 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
|
||||
cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file)
|
||||
|
||||
done := make(chan error, 1)
|
||||
reader, stdout, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
stderr := bytes.Buffer{}
|
||||
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
|
||||
@@ -182,33 +190,29 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
|
||||
}, nil
|
||||
}
|
||||
|
||||
func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func()) {
|
||||
func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func(), error) {
|
||||
entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs")
|
||||
if err != nil {
|
||||
log.Error("Unable to get .git-blame-ignore-revs file: GetTreeEntryByPath: %v", err)
|
||||
return "", nil
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
r, err := entry.Blob().DataAsync()
|
||||
if err != nil {
|
||||
log.Error("Unable to get .git-blame-ignore-revs file data: DataAsync: %v", err)
|
||||
return "", nil
|
||||
return "", nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("git-blame-ignore-revs")
|
||||
if err != nil {
|
||||
log.Error("Unable to get .git-blame-ignore-revs file data: CreateTempFileRandom: %v", err)
|
||||
return "", nil
|
||||
return "", nil, err
|
||||
}
|
||||
filename := f.Name()
|
||||
_, err = io.Copy(f, r)
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
cleanup()
|
||||
log.Error("Unable to get .git-blame-ignore-revs file data: Copy: %v", err)
|
||||
return "", nil
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return filename, cleanup
|
||||
return filename, cleanup, nil
|
||||
}
|
||||
|
36
modules/git/cmdverb.go
Normal file
36
modules/git/cmdverb.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
const (
|
||||
CmdVerbUploadPack = "git-upload-pack"
|
||||
CmdVerbUploadArchive = "git-upload-archive"
|
||||
CmdVerbReceivePack = "git-receive-pack"
|
||||
CmdVerbLfsAuthenticate = "git-lfs-authenticate"
|
||||
CmdVerbLfsTransfer = "git-lfs-transfer"
|
||||
|
||||
CmdSubVerbLfsUpload = "upload"
|
||||
CmdSubVerbLfsDownload = "download"
|
||||
)
|
||||
|
||||
func IsAllowedVerbForServe(verb string) bool {
|
||||
switch verb {
|
||||
case CmdVerbUploadPack,
|
||||
CmdVerbUploadArchive,
|
||||
CmdVerbReceivePack,
|
||||
CmdVerbLfsAuthenticate,
|
||||
CmdVerbLfsTransfer:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsAllowedVerbForServeLfs(verb string) bool {
|
||||
switch verb {
|
||||
case CmdVerbLfsAuthenticate,
|
||||
CmdVerbLfsTransfer:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@@ -34,7 +34,7 @@ type Commit struct {
|
||||
// CommitSignature represents a git commit signature part.
|
||||
type CommitSignature struct {
|
||||
Signature string
|
||||
Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data
|
||||
Payload string
|
||||
}
|
||||
|
||||
// Message returns the commit message. Same as retrieving CommitMessage directly.
|
||||
|
@@ -3,9 +3,24 @@
|
||||
|
||||
package git
|
||||
|
||||
import "path"
|
||||
|
||||
// CommitInfo describes the first commit with the provided entry
|
||||
type CommitInfo struct {
|
||||
Entry *TreeEntry
|
||||
Commit *Commit
|
||||
SubmoduleFile *CommitSubmoduleFile
|
||||
}
|
||||
|
||||
func getCommitInfoSubmoduleFile(repoLink string, entry *TreeEntry, commit *Commit, treePathDir string) (*CommitSubmoduleFile, error) {
|
||||
fullPath := path.Join(treePathDir, entry.Name())
|
||||
submodule, err := commit.GetSubModule(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if submodule == nil {
|
||||
// unable to find submodule from ".gitmodules" file
|
||||
return NewCommitSubmoduleFile(repoLink, fullPath, "", entry.ID.String()), nil
|
||||
}
|
||||
return NewCommitSubmoduleFile(repoLink, fullPath, submodule.URL, entry.ID.String()), nil
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// GetCommitsInfo gets information of all commits that are corresponding to these entries
|
||||
func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
||||
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
||||
entryPaths := make([]string, len(tes)+1)
|
||||
// Get the commit for the treePath itself
|
||||
entryPaths[0] = ""
|
||||
@@ -71,22 +71,12 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||
commitsInfo[i].Commit = entryCommit
|
||||
}
|
||||
|
||||
// If the entry is a submodule add a submodule file for this
|
||||
// If the entry is a submodule, add a submodule file for this
|
||||
if entry.IsSubModule() {
|
||||
subModuleURL := ""
|
||||
var fullPath string
|
||||
if len(treePath) > 0 {
|
||||
fullPath = treePath + "/" + entry.Name()
|
||||
} else {
|
||||
fullPath = entry.Name()
|
||||
}
|
||||
if subModule, err := commit.GetSubModule(fullPath); err != nil {
|
||||
commitsInfo[i].SubmoduleFile, err = getCommitInfoSubmoduleFile(repoLink, entry, commit, treePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if subModule != nil {
|
||||
subModuleURL = subModule.URL
|
||||
}
|
||||
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
|
||||
commitsInfo[i].SubmoduleFile = subModuleFile
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// GetCommitsInfo gets information of all commits that are corresponding to these entries
|
||||
func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
||||
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
||||
entryPaths := make([]string, len(tes)+1)
|
||||
// Get the commit for the treePath itself
|
||||
entryPaths[0] = ""
|
||||
@@ -65,22 +65,12 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||
log.Debug("missing commit for %s", entry.Name())
|
||||
}
|
||||
|
||||
// If the entry is a submodule add a submodule file for this
|
||||
// If the entry is a submodule, add a submodule file for this
|
||||
if entry.IsSubModule() {
|
||||
subModuleURL := ""
|
||||
var fullPath string
|
||||
if len(treePath) > 0 {
|
||||
fullPath = treePath + "/" + entry.Name()
|
||||
} else {
|
||||
fullPath = entry.Name()
|
||||
}
|
||||
if subModule, err := commit.GetSubModule(fullPath); err != nil {
|
||||
commitsInfo[i].SubmoduleFile, err = getCommitInfoSubmoduleFile(repoLink, entry, commit, treePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if subModule != nil {
|
||||
subModuleURL = subModule.URL
|
||||
}
|
||||
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
|
||||
commitsInfo[i].SubmoduleFile = subModuleFile
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -82,7 +83,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
|
||||
}
|
||||
|
||||
// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
|
||||
commitsInfo, treeCommit, err := entries.GetCommitsInfo(t.Context(), commit, testCase.Path)
|
||||
commitsInfo, treeCommit, err := entries.GetCommitsInfo(t.Context(), "/any/repo-link", commit, testCase.Path)
|
||||
assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
@@ -120,6 +121,23 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
||||
defer clonedRepo1.Close()
|
||||
|
||||
testGetCommitsInfo(t, clonedRepo1)
|
||||
|
||||
t.Run("NonExistingSubmoduleAsNil", func(t *testing.T) {
|
||||
commit, err := bareRepo1.GetCommit("HEAD")
|
||||
require.NoError(t, err)
|
||||
tree, err := commit.GetTreeEntryByPath("file1.txt")
|
||||
require.NoError(t, err)
|
||||
cisf, err := getCommitInfoSubmoduleFile("/any/repo-link", tree, commit, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &CommitSubmoduleFile{
|
||||
repoLink: "/any/repo-link",
|
||||
fullPath: "file1.txt",
|
||||
refURL: "",
|
||||
refID: "e2129701f1a4d54dc44f03c93bca0a2aec7c5449",
|
||||
}, cisf)
|
||||
// since there is no refURL, it means that the submodule info doesn't exist, so it won't have a web link
|
||||
assert.Nil(t, cisf.SubmoduleWebLinkTree(t.Context()))
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
||||
@@ -159,7 +177,7 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.Run(benchmark.name, func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_, _, err := entries.GetCommitsInfo(b.Context(), commit, "")
|
||||
_, _, err := entries.GetCommitsInfo(b.Context(), "/any/repo-link", commit, "")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
@@ -6,10 +6,44 @@ package git
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
commitHeaderGpgsig = "gpgsig"
|
||||
commitHeaderGpgsigSha256 = "gpgsig-sha256"
|
||||
)
|
||||
|
||||
func assignCommitFields(gitRepo *Repository, commit *Commit, headerKey string, headerValue []byte) error {
|
||||
if len(headerValue) > 0 && headerValue[len(headerValue)-1] == '\n' {
|
||||
headerValue = headerValue[:len(headerValue)-1] // remove trailing newline
|
||||
}
|
||||
switch headerKey {
|
||||
case "tree":
|
||||
objID, err := NewIDFromString(string(headerValue))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid tree ID %q: %w", string(headerValue), err)
|
||||
}
|
||||
commit.Tree = *NewTree(gitRepo, objID)
|
||||
case "parent":
|
||||
objID, err := NewIDFromString(string(headerValue))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid parent ID %q: %w", string(headerValue), err)
|
||||
}
|
||||
commit.Parents = append(commit.Parents, objID)
|
||||
case "author":
|
||||
commit.Author.Decode(headerValue)
|
||||
case "committer":
|
||||
commit.Committer.Decode(headerValue)
|
||||
case commitHeaderGpgsig, commitHeaderGpgsigSha256:
|
||||
// if there are duplicate "gpgsig" and "gpgsig-sha256" headers, then the signature must have already been invalid
|
||||
// so we don't need to handle duplicate headers here
|
||||
commit.Signature = &CommitSignature{Signature: string(headerValue)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitFromReader will generate a Commit from a provided reader
|
||||
// We need this to interpret commits from cat-file or cat-file --batch
|
||||
//
|
||||
@@ -21,90 +55,46 @@ func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader)
|
||||
Committer: &Signature{},
|
||||
}
|
||||
|
||||
payloadSB := new(strings.Builder)
|
||||
signatureSB := new(strings.Builder)
|
||||
messageSB := new(strings.Builder)
|
||||
message := false
|
||||
pgpsig := false
|
||||
|
||||
bufReader, ok := reader.(*bufio.Reader)
|
||||
if !ok {
|
||||
bufReader = bufio.NewReader(reader)
|
||||
}
|
||||
|
||||
readLoop:
|
||||
bufReader := bufio.NewReader(reader)
|
||||
inHeader := true
|
||||
var payloadSB, messageSB bytes.Buffer
|
||||
var headerKey string
|
||||
var headerValue []byte
|
||||
for {
|
||||
line, err := bufReader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if message {
|
||||
_, _ = messageSB.Write(line)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, fmt.Errorf("unable to read commit %q: %w", objectID.String(), err)
|
||||
}
|
||||
if len(line) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if inHeader {
|
||||
inHeader = !(len(line) == 1 && line[0] == '\n') // still in header if line is not just a newline
|
||||
k, v, _ := bytes.Cut(line, []byte{' '})
|
||||
if len(k) != 0 || !inHeader {
|
||||
if headerKey != "" {
|
||||
if err = assignCommitFields(gitRepo, commit, headerKey, headerValue); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse commit %q: %w", objectID.String(), err)
|
||||
}
|
||||
}
|
||||
_, _ = payloadSB.Write(line)
|
||||
break readLoop
|
||||
headerKey = string(k) // it also resets the headerValue to empty string if not inHeader
|
||||
headerValue = v
|
||||
} else {
|
||||
headerValue = append(headerValue, v...)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if pgpsig {
|
||||
if len(line) > 0 && line[0] == ' ' {
|
||||
_, _ = signatureSB.Write(line[1:])
|
||||
continue
|
||||
}
|
||||
pgpsig = false
|
||||
}
|
||||
|
||||
if !message {
|
||||
// This is probably not correct but is copied from go-gits interpretation...
|
||||
trimmed := bytes.TrimSpace(line)
|
||||
if len(trimmed) == 0 {
|
||||
message = true
|
||||
if headerKey != commitHeaderGpgsig && headerKey != commitHeaderGpgsigSha256 {
|
||||
_, _ = payloadSB.Write(line)
|
||||
continue
|
||||
}
|
||||
|
||||
split := bytes.SplitN(trimmed, []byte{' '}, 2)
|
||||
var data []byte
|
||||
if len(split) > 1 {
|
||||
data = split[1]
|
||||
}
|
||||
|
||||
switch string(split[0]) {
|
||||
case "tree":
|
||||
commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data)))
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "parent":
|
||||
commit.Parents = append(commit.Parents, MustIDFromString(string(data)))
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "author":
|
||||
commit.Author = &Signature{}
|
||||
commit.Author.Decode(data)
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "committer":
|
||||
commit.Committer = &Signature{}
|
||||
commit.Committer.Decode(data)
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "encoding":
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "gpgsig":
|
||||
fallthrough
|
||||
case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
|
||||
_, _ = signatureSB.Write(data)
|
||||
_ = signatureSB.WriteByte('\n')
|
||||
pgpsig = true
|
||||
}
|
||||
} else {
|
||||
_, _ = messageSB.Write(line)
|
||||
_, _ = payloadSB.Write(line)
|
||||
}
|
||||
}
|
||||
commit.CommitMessage = messageSB.String()
|
||||
commit.Signature = &CommitSignature{
|
||||
Signature: signatureSB.String(),
|
||||
Payload: payloadSB.String(),
|
||||
}
|
||||
if len(commit.Signature.Signature) == 0 {
|
||||
commit.Signature = nil
|
||||
}
|
||||
|
||||
commit.CommitMessage = messageSB.String()
|
||||
if commit.Signature != nil {
|
||||
commit.Signature.Payload = payloadSB.String()
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
@@ -60,8 +60,7 @@ func TestGetFullCommitIDErrorSha256(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCommitFromReaderSha256(t *testing.T) {
|
||||
commitString := `9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 commit 1114
|
||||
tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
|
||||
commitString := `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
|
||||
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
|
||||
author Adam Majer <amajer@suse.de> 1698676906 +0100
|
||||
committer Adam Majer <amajer@suse.de> 1698676906 +0100
|
||||
@@ -112,8 +111,7 @@ VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
|
||||
HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
|
||||
8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
|
||||
=xybZ
|
||||
-----END PGP SIGNATURE-----
|
||||
`, commitFromReader.Signature.Signature)
|
||||
-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
|
||||
assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
|
||||
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
|
||||
author Adam Majer <amajer@suse.de> 1698676906 +0100
|
||||
|
@@ -35,7 +35,8 @@ func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
|
||||
return c.submoduleCache, nil
|
||||
}
|
||||
|
||||
// GetSubModule get the submodule according entry name
|
||||
// GetSubModule gets the submodule by the entry name.
|
||||
// It returns "nil, nil" if the submodule does not exist, caller should always remember to check the "nil"
|
||||
func (c *Commit) GetSubModule(entryName string) (*SubModule, error) {
|
||||
modules, err := c.GetSubModules()
|
||||
if err != nil {
|
||||
|
@@ -6,49 +6,64 @@ package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
giturl "code.gitea.io/gitea/modules/git/url"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// CommitSubmoduleFile represents a file with submodule type.
|
||||
type CommitSubmoduleFile struct {
|
||||
refURL string
|
||||
parsedURL *giturl.RepositoryURL
|
||||
parsed bool
|
||||
refID string
|
||||
repoLink string
|
||||
repoLink string
|
||||
fullPath string
|
||||
refURL string
|
||||
refID string
|
||||
|
||||
parsed bool
|
||||
parsedTargetLink string
|
||||
}
|
||||
|
||||
// NewCommitSubmoduleFile create a new submodule file
|
||||
func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile {
|
||||
return &CommitSubmoduleFile{refURL: refURL, refID: refID}
|
||||
func NewCommitSubmoduleFile(repoLink, fullPath, refURL, refID string) *CommitSubmoduleFile {
|
||||
return &CommitSubmoduleFile{repoLink: repoLink, fullPath: fullPath, refURL: refURL, refID: refID}
|
||||
}
|
||||
|
||||
// RefID returns the commit ID of the submodule, it returns empty string for nil receiver
|
||||
func (sf *CommitSubmoduleFile) RefID() string {
|
||||
return sf.refID // this function is only used in templates
|
||||
if sf == nil {
|
||||
return ""
|
||||
}
|
||||
return sf.refID
|
||||
}
|
||||
|
||||
// SubmoduleWebLink tries to make some web links for a submodule, it also works on "nil" receiver
|
||||
func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink {
|
||||
if sf == nil {
|
||||
func (sf *CommitSubmoduleFile) getWebLinkInTargetRepo(ctx context.Context, moreLinkPath string) *SubmoduleWebLink {
|
||||
if sf == nil || sf.refURL == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(sf.refURL, "../") {
|
||||
targetLink := path.Join(sf.repoLink, path.Dir(sf.fullPath), sf.refURL)
|
||||
return &SubmoduleWebLink{RepoWebLink: targetLink, CommitWebLink: targetLink + moreLinkPath}
|
||||
}
|
||||
if !sf.parsed {
|
||||
sf.parsed = true
|
||||
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
sf.parsedURL = parsedURL
|
||||
sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL)
|
||||
sf.parsedTargetLink = giturl.MakeRepositoryWebLink(parsedURL)
|
||||
}
|
||||
var commitLink string
|
||||
if len(optCommitID) == 2 {
|
||||
commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
|
||||
} else if len(optCommitID) == 1 {
|
||||
commitLink = sf.repoLink + "/tree/" + optCommitID[0]
|
||||
} else {
|
||||
commitLink = sf.repoLink + "/tree/" + sf.refID
|
||||
}
|
||||
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
|
||||
return &SubmoduleWebLink{RepoWebLink: sf.parsedTargetLink, CommitWebLink: sf.parsedTargetLink + moreLinkPath}
|
||||
}
|
||||
|
||||
// SubmoduleWebLinkTree tries to make the submodule's tree link in its own repo, it also works on "nil" receiver
|
||||
// It returns nil if the submodule does not have a valid URL or is nil
|
||||
func (sf *CommitSubmoduleFile) SubmoduleWebLinkTree(ctx context.Context, optCommitID ...string) *SubmoduleWebLink {
|
||||
return sf.getWebLinkInTargetRepo(ctx, "/tree/"+util.OptionalArg(optCommitID, sf.RefID()))
|
||||
}
|
||||
|
||||
// SubmoduleWebLinkCompare tries to make the submodule's compare link in its own repo, it also works on "nil" receiver
|
||||
// It returns nil if the submodule does not have a valid URL or is nil
|
||||
func (sf *CommitSubmoduleFile) SubmoduleWebLinkCompare(ctx context.Context, commitID1, commitID2 string) *SubmoduleWebLink {
|
||||
return sf.getWebLinkInTargetRepo(ctx, "/compare/"+commitID1+"..."+commitID2)
|
||||
}
|
||||
|
@@ -10,20 +10,31 @@ import (
|
||||
)
|
||||
|
||||
func TestCommitSubmoduleLink(t *testing.T) {
|
||||
sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa")
|
||||
assert.Nil(t, (*CommitSubmoduleFile)(nil).SubmoduleWebLinkTree(t.Context()))
|
||||
assert.Nil(t, (*CommitSubmoduleFile)(nil).SubmoduleWebLinkCompare(t.Context(), "", ""))
|
||||
assert.Nil(t, (&CommitSubmoduleFile{}).SubmoduleWebLinkTree(t.Context()))
|
||||
assert.Nil(t, (&CommitSubmoduleFile{}).SubmoduleWebLinkCompare(t.Context(), "", ""))
|
||||
|
||||
wl := sf.SubmoduleWebLink(t.Context())
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
|
||||
t.Run("GitHubRepo", func(t *testing.T) {
|
||||
sf := NewCommitSubmoduleFile("/any/repo-link", "full-path", "git@github.com:user/repo.git", "aaaa")
|
||||
wl := sf.SubmoduleWebLinkTree(t.Context())
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
|
||||
|
||||
wl = sf.SubmoduleWebLink(t.Context(), "1111")
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/tree/1111", wl.CommitWebLink)
|
||||
wl = sf.SubmoduleWebLinkCompare(t.Context(), "1111", "2222")
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
|
||||
})
|
||||
|
||||
wl = sf.SubmoduleWebLink(t.Context(), "1111", "2222")
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
|
||||
t.Run("RelativePath", func(t *testing.T) {
|
||||
sf := NewCommitSubmoduleFile("/subpath/any/repo-home-link", "full-path", "../../user/repo", "aaaa")
|
||||
wl := sf.SubmoduleWebLinkTree(t.Context())
|
||||
assert.Equal(t, "/subpath/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "/subpath/user/repo/tree/aaaa", wl.CommitWebLink)
|
||||
|
||||
wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(t.Context())
|
||||
assert.Nil(t, wl)
|
||||
sf = NewCommitSubmoduleFile("/subpath/any/repo-home-link", "dir/submodule", "../../../user/repo", "aaaa")
|
||||
wl = sf.SubmoduleWebLinkCompare(t.Context(), "1111", "2222")
|
||||
assert.Equal(t, "/subpath/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "/subpath/user/repo/compare/1111...2222", wl.CommitWebLink)
|
||||
})
|
||||
}
|
||||
|
@@ -59,8 +59,7 @@ func TestGetFullCommitIDError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCommitFromReader(t *testing.T) {
|
||||
commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
|
||||
tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
|
||||
commitString := `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
|
||||
parent 37991dec2c8e592043f47155ce4808d4580f9123
|
||||
author silverwind <me@silverwind.io> 1563741793 +0200
|
||||
committer silverwind <me@silverwind.io> 1563741793 +0200
|
||||
@@ -108,8 +107,7 @@ sD53z/f0J+We4VZjY+pidvA9BGZPFVdR3wd3xGs8/oH6UWaLJAMGkLG6dDb3qDLm
|
||||
mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i
|
||||
1pxsB104XLWjQHTjr1JtiaBQEwFh9r2OKTcpvaLcbNtYpo7CzOs=
|
||||
=FRsO
|
||||
-----END PGP SIGNATURE-----
|
||||
`, commitFromReader.Signature.Signature)
|
||||
-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
|
||||
assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
|
||||
parent 37991dec2c8e592043f47155ce4808d4580f9123
|
||||
author silverwind <me@silverwind.io> 1563741793 +0200
|
||||
@@ -126,8 +124,7 @@ empty commit`, commitFromReader.Signature.Payload)
|
||||
}
|
||||
|
||||
func TestCommitWithEncodingFromReader(t *testing.T) {
|
||||
commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
|
||||
tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
|
||||
commitString := `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
|
||||
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
|
||||
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||
@@ -172,8 +169,7 @@ SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
|
||||
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
|
||||
jw4YcO5u
|
||||
=r3UU
|
||||
-----END PGP SIGNATURE-----
|
||||
`, commitFromReader.Signature.Signature)
|
||||
-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
|
||||
assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
|
||||
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
|
||||
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||
|
@@ -99,9 +99,9 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseDiffHunkString parse the diffhunk content and return
|
||||
func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHunk int) {
|
||||
ss := strings.Split(diffhunk, "@@")
|
||||
// ParseDiffHunkString parse the diff hunk content and return
|
||||
func ParseDiffHunkString(diffHunk string) (leftLine, leftHunk, rightLine, rightHunk int) {
|
||||
ss := strings.Split(diffHunk, "@@")
|
||||
ranges := strings.Split(ss[1][1:], " ")
|
||||
leftRange := strings.Split(ranges[0], ",")
|
||||
leftLine, _ = strconv.Atoi(leftRange[0][1:])
|
||||
@@ -112,14 +112,19 @@ func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHu
|
||||
rightRange := strings.Split(ranges[1], ",")
|
||||
rightLine, _ = strconv.Atoi(rightRange[0])
|
||||
if len(rightRange) > 1 {
|
||||
righHunk, _ = strconv.Atoi(rightRange[1])
|
||||
rightHunk, _ = strconv.Atoi(rightRange[1])
|
||||
}
|
||||
} else {
|
||||
log.Debug("Parse line number failed: %v", diffhunk)
|
||||
log.Debug("Parse line number failed: %v", diffHunk)
|
||||
rightLine = leftLine
|
||||
righHunk = leftHunk
|
||||
rightHunk = leftHunk
|
||||
}
|
||||
return leftLine, leftHunk, rightLine, righHunk
|
||||
if rightLine == 0 {
|
||||
// FIXME: GIT-DIFF-CUT-BUG search this tag to see details
|
||||
// this is only a hacky patch, the rightLine&rightHunk might still be incorrect in some cases.
|
||||
rightLine++
|
||||
}
|
||||
return leftLine, leftHunk, rightLine, rightHunk
|
||||
}
|
||||
|
||||
// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
|
||||
@@ -270,6 +275,12 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
||||
oldNumOfLines++
|
||||
}
|
||||
}
|
||||
|
||||
// "git diff" outputs "@@ -1 +1,3 @@" for "OLD" => "A\nB\nC"
|
||||
// FIXME: GIT-DIFF-CUT-BUG But there is a bug in CutDiffAroundLine, then the "Patch" stored in the comment model becomes "@@ -1,1 +0,4 @@"
|
||||
// It may generate incorrect results for difference cases, for example: delete 2 line add 1 line, delete 2 line add 2 line etc, need to double check.
|
||||
// For example: "L1\nL2" => "A\nB", then the patch shows "L2" as line 1 on the left (deleted part)
|
||||
|
||||
// construct the new hunk header
|
||||
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
|
||||
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
|
||||
|
@@ -97,17 +97,17 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
|
||||
}
|
||||
|
||||
isVendored := optional.None[bool]()
|
||||
isGenerated := optional.None[bool]()
|
||||
isDocumentation := optional.None[bool]()
|
||||
isDetectable := optional.None[bool]()
|
||||
|
||||
attrs, err := checker.CheckPath(f.Name())
|
||||
attrLinguistGenerated := optional.None[bool]()
|
||||
if err == nil {
|
||||
if isVendored = attrs.GetVendored(); isVendored.ValueOrDefault(false) {
|
||||
continue
|
||||
}
|
||||
|
||||
if isGenerated = attrs.GetGenerated(); isGenerated.ValueOrDefault(false) {
|
||||
if attrLinguistGenerated = attrs.GetGenerated(); attrLinguistGenerated.ValueOrDefault(false) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -169,7 +169,15 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !isGenerated.Has() && enry.IsGenerated(f.Name(), content) {
|
||||
|
||||
// if "generated" attribute is set, use it, otherwise use enry.IsGenerated to guess
|
||||
var isGenerated bool
|
||||
if attrLinguistGenerated.Has() {
|
||||
isGenerated = attrLinguistGenerated.Value()
|
||||
} else {
|
||||
isGenerated = enry.IsGenerated(f.Name(), content)
|
||||
}
|
||||
if isGenerated {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@@ -30,6 +30,31 @@ func (e EntryMode) String() string {
|
||||
return strconv.FormatInt(int64(e), 8)
|
||||
}
|
||||
|
||||
// IsSubModule if the entry is a sub module
|
||||
func (e EntryMode) IsSubModule() bool {
|
||||
return e == EntryModeCommit
|
||||
}
|
||||
|
||||
// IsDir if the entry is a sub dir
|
||||
func (e EntryMode) IsDir() bool {
|
||||
return e == EntryModeTree
|
||||
}
|
||||
|
||||
// IsLink if the entry is a symlink
|
||||
func (e EntryMode) IsLink() bool {
|
||||
return e == EntryModeSymlink
|
||||
}
|
||||
|
||||
// IsRegular if the entry is a regular file
|
||||
func (e EntryMode) IsRegular() bool {
|
||||
return e == EntryModeBlob
|
||||
}
|
||||
|
||||
// IsExecutable if the entry is an executable file (not necessarily binary)
|
||||
func (e EntryMode) IsExecutable() bool {
|
||||
return e == EntryModeExec
|
||||
}
|
||||
|
||||
func ParseEntryMode(mode string) (EntryMode, error) {
|
||||
switch mode {
|
||||
case "000000":
|
||||
|
@@ -59,27 +59,27 @@ func (te *TreeEntry) Size() int64 {
|
||||
|
||||
// IsSubModule if the entry is a sub module
|
||||
func (te *TreeEntry) IsSubModule() bool {
|
||||
return te.entryMode == EntryModeCommit
|
||||
return te.entryMode.IsSubModule()
|
||||
}
|
||||
|
||||
// IsDir if the entry is a sub dir
|
||||
func (te *TreeEntry) IsDir() bool {
|
||||
return te.entryMode == EntryModeTree
|
||||
return te.entryMode.IsDir()
|
||||
}
|
||||
|
||||
// IsLink if the entry is a symlink
|
||||
func (te *TreeEntry) IsLink() bool {
|
||||
return te.entryMode == EntryModeSymlink
|
||||
return te.entryMode.IsLink()
|
||||
}
|
||||
|
||||
// IsRegular if the entry is a regular file
|
||||
func (te *TreeEntry) IsRegular() bool {
|
||||
return te.entryMode == EntryModeBlob
|
||||
return te.entryMode.IsRegular()
|
||||
}
|
||||
|
||||
// IsExecutable if the entry is an executable file (not necessarily binary)
|
||||
func (te *TreeEntry) IsExecutable() bool {
|
||||
return te.entryMode == EntryModeExec
|
||||
return te.entryMode.IsExecutable()
|
||||
}
|
||||
|
||||
// Blob returns the blob object the entry
|
||||
|
@@ -132,6 +132,7 @@ func newInternalRequestLFS(ctx context.Context, internalURL, method string, head
|
||||
return nil
|
||||
}
|
||||
req := private.NewInternalRequest(ctx, internalURL, method)
|
||||
req.SetReadWriteTimeout(0)
|
||||
for k, v := range headers {
|
||||
req.Header(k, v)
|
||||
}
|
||||
|
@@ -409,9 +409,9 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
|
||||
_, _ = w.Write(n.Name)
|
||||
_, _ = w.WriteString(`"><a href="#fn:`)
|
||||
_, _ = w.Write(n.Name)
|
||||
_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
|
||||
_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`) // FIXME: here and below, need to keep the classes
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`</a></sup>`)
|
||||
_, _ = w.WriteString(` </a></sup>`) // the style doesn't work at the moment, so add a space to separate the names
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
@@ -86,8 +86,8 @@ var globalVars = sync.OnceValue(func() *globalVarsType {
|
||||
// codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20"
|
||||
v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`)
|
||||
|
||||
// cleans: "<foo/bar", "<any words/", ("<html", "<head", "<script", "<style")
|
||||
v.tagCleaner = regexp.MustCompile(`(?i)<(/?\w+/\w+|/[\w ]+/|/?(html|head|script|style\b))`)
|
||||
// cleans: "<foo/bar", "<any words/", ("<html", "<head", "<script", "<style", "<?", "<%")
|
||||
v.tagCleaner = regexp.MustCompile(`(?i)<(/?\w+/\w+|/[\w ]+/|/?(html|head|script|style|%|\?)\b)`)
|
||||
v.nulCleaner = strings.NewReplacer("\000", "")
|
||||
return v
|
||||
})
|
||||
@@ -253,7 +253,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
|
||||
node, err := html.Parse(io.MultiReader(
|
||||
// prepend "<html><body>"
|
||||
strings.NewReader("<html><body>"),
|
||||
// Strip out nuls - they're always invalid
|
||||
// strip out NULLs (they're always invalid), and escape known tags
|
||||
bytes.NewReader(globalVars().tagCleaner.ReplaceAll([]byte(globalVars().nulCleaner.Replace(string(rawHTML))), []byte("<$1"))),
|
||||
// close the tags
|
||||
strings.NewReader("</body></html>"),
|
||||
@@ -320,6 +320,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
|
||||
}
|
||||
|
||||
processNodeAttrID(node)
|
||||
processFootnoteNode(ctx, node) // FIXME: the footnote processing should be done in the "footnote.go" renderer directly
|
||||
|
||||
if isEmojiNode(node) {
|
||||
// TextNode emoji will be converted to `<span class="emoji">`, then the next iteration will visit the "span"
|
||||
|
@@ -30,6 +30,7 @@ func TestRender_IssueList(t *testing.T) {
|
||||
rctx := markup.NewTestRenderContext(markup.TestAppURL, map[string]string{
|
||||
"user": "test-user", "repo": "test-repo",
|
||||
"markupAllowShortIssuePattern": "true",
|
||||
"footnoteContextId": "12345",
|
||||
})
|
||||
out, err := markdown.RenderString(rctx, input)
|
||||
require.NoError(t, err)
|
||||
@@ -69,4 +70,22 @@ func TestRender_IssueList(t *testing.T) {
|
||||
</ul>`,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("IssueFootnote", func(t *testing.T) {
|
||||
test(
|
||||
"foo[^1][^2]\n\n[^1]: bar\n[^2]: baz",
|
||||
`<p>foo<sup id="fnref:user-content-1-12345"><a href="#fn:user-content-1-12345" rel="nofollow">1 </a></sup><sup id="fnref:user-content-2-12345"><a href="#fn:user-content-2-12345" rel="nofollow">2 </a></sup></p>
|
||||
<div>
|
||||
<hr/>
|
||||
<ol>
|
||||
<li id="fn:user-content-1-12345">
|
||||
<p>bar <a href="#fnref:user-content-1-12345" rel="nofollow">↩︎</a></p>
|
||||
</li>
|
||||
<li id="fn:user-content-2-12345">
|
||||
<p>baz <a href="#fnref:user-content-2-12345" rel="nofollow">↩︎</a></p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>`,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@@ -15,6 +15,14 @@ func isAnchorIDUserContent(s string) bool {
|
||||
return strings.HasPrefix(s, "user-content-") || strings.Contains(s, ":user-content-")
|
||||
}
|
||||
|
||||
func isAnchorIDFootnote(s string) bool {
|
||||
return strings.HasPrefix(s, "fnref:user-content-") || strings.HasPrefix(s, "fn:user-content-")
|
||||
}
|
||||
|
||||
func isAnchorHrefFootnote(s string) bool {
|
||||
return strings.HasPrefix(s, "#fnref:user-content-") || strings.HasPrefix(s, "#fn:user-content-")
|
||||
}
|
||||
|
||||
func processNodeAttrID(node *html.Node) {
|
||||
// Add user-content- to IDs and "#" links if they don't already have them,
|
||||
// and convert the link href to a relative link to the host root
|
||||
@@ -27,6 +35,18 @@ func processNodeAttrID(node *html.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func processFootnoteNode(ctx *RenderContext, node *html.Node) {
|
||||
for idx, attr := range node.Attr {
|
||||
if (attr.Key == "id" && isAnchorIDFootnote(attr.Val)) ||
|
||||
(attr.Key == "href" && isAnchorHrefFootnote(attr.Val)) {
|
||||
if footnoteContextID := ctx.RenderOptions.Metas["footnoteContextId"]; footnoteContextID != "" {
|
||||
node.Attr[idx].Val = attr.Val + "-" + footnoteContextID
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processNodeA(ctx *RenderContext, node *html.Node) {
|
||||
for idx, attr := range node.Attr {
|
||||
if attr.Key == "href" {
|
||||
|
@@ -525,6 +525,10 @@ func TestPostProcess(t *testing.T) {
|
||||
test("<script>a</script>", `<script>a</script>`)
|
||||
test("<STYLE>a", `<STYLE>a`)
|
||||
test("<style>a</STYLE>", `<style>a</STYLE>`)
|
||||
|
||||
// other special tags, our special behavior
|
||||
test("<?php\nfoo", "<?php\nfoo")
|
||||
test("<%asp\nfoo", "<%asp\nfoo")
|
||||
}
|
||||
|
||||
func TestIssue16020(t *testing.T) {
|
||||
|
@@ -223,7 +223,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
|
||||
<dd>This is another definition of the second term.</dd>
|
||||
</dl>
|
||||
<h3 id="user-content-footnotes">Footnotes</h3>
|
||||
<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2</a></sup></p>
|
||||
<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1 </a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2 </a></sup></p>
|
||||
<div>
|
||||
<hr/>
|
||||
<ol>
|
||||
|
@@ -22,6 +22,13 @@ func FromPtr[T any](v *T) Option[T] {
|
||||
return Some(*v)
|
||||
}
|
||||
|
||||
func FromMapLookup[K comparable, V any](m map[K]V, k K) Option[V] {
|
||||
if v, ok := m[k]; ok {
|
||||
return Some(v)
|
||||
}
|
||||
return None[V]()
|
||||
}
|
||||
|
||||
func FromNonDefault[T comparable](v T) Option[T] {
|
||||
var zero T
|
||||
if v == zero {
|
||||
|
@@ -56,6 +56,12 @@ func TestOption(t *testing.T) {
|
||||
opt3 := optional.FromNonDefault(1)
|
||||
assert.True(t, opt3.Has())
|
||||
assert.Equal(t, int(1), opt3.Value())
|
||||
|
||||
opt4 := optional.FromMapLookup(map[string]int{"a": 1}, "a")
|
||||
assert.True(t, opt4.Has())
|
||||
assert.Equal(t, 1, opt4.Value())
|
||||
opt4 = optional.FromMapLookup(map[string]int{"a": 1}, "b")
|
||||
assert.False(t, opt4.Has())
|
||||
}
|
||||
|
||||
func Test_ParseBool(t *testing.T) {
|
||||
|
@@ -4,6 +4,7 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
@@ -83,7 +84,8 @@ func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) {
|
||||
|
||||
func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
|
||||
var image oci.Image
|
||||
if err := json.NewDecoder(r).Decode(&image); err != nil {
|
||||
// EOF means empty input, still use the default data
|
||||
if err := json.NewDecoder(r).Decode(&image); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user