mirror of
https://github.com/go-gitea/gitea
synced 2025-02-08 07:44:44 +00:00
Merge branch 'main' into lunny/issue_dev
This commit is contained in:
commit
a81c785918
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,7 +28,7 @@ _testmain.go
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.tsbuildInfo
|
||||
*.tsbuildinfo
|
||||
|
||||
*coverage.out
|
||||
coverage.all
|
||||
|
393
CHANGELOG.md
393
CHANGELOG.md
@ -4,6 +4,399 @@ 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.22.4](https://github.com/go-gitea/gitea/releases/tag/v1.22.4) - 2024-11-14
|
||||
|
||||
* SECURITY
|
||||
* Fix basic auth with webauthn (#32531) (#32536)
|
||||
* Refactor internal routers (partial backport, auth token const time comparing) (#32473) (#32479)
|
||||
* PERFORMANCE
|
||||
* Remove transaction for archive download (#32186) (#32520)
|
||||
* BUGFIXES
|
||||
* Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled (#32365) (#32397)
|
||||
* Fix get reviewers fails when selecting user without pull request permissions unit (#32415) (#32616)
|
||||
* Fix adding index files to tmp directory (#32360) (#32593)
|
||||
* Fix PR creation on forked repositories via API (#31863) (#32591)
|
||||
* Fix missing menu tabs in organization project view page (#32313) (#32592)
|
||||
* Support HTTP POST requests to `/userinfo`, aligning to OpenID Core specification (#32578) (#32594)
|
||||
* Fix debian package clean up cron job (#32351) (#32590)
|
||||
* Fix GetInactiveUsers (#32540) (#32588)
|
||||
* Allow the actions user to login via the jwt token (#32527) (#32580)
|
||||
* Fix submodule parsing (#32571) (#32577)
|
||||
* Refactor find forks and fix possible bugs that weaken permissions check (#32528) (#32547)
|
||||
* Fix some places that don't respect org full name setting (#32243) (#32550)
|
||||
* Refactor push mirror find and add check for updating push mirror (#32539) (#32549)
|
||||
* Fix basic auth with webauthn (#32531) (#32536)
|
||||
* Fix artifact v4 upload above 8MB (#31664) (#32523)
|
||||
* Fix oauth2 error handle not return immediately (#32514) (#32516)
|
||||
* Fix action not triggered when commit message is too long (#32498) (#32507)
|
||||
* Fix `GetRepoLink` nil pointer dereference on dashboard feed page when repo is deleted with actions enabled (#32501) (#32502)
|
||||
* Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled (#32397) (#32397)
|
||||
* Fix the permission check for user search API and limit the number of returned users for `/user/search` (#32310)
|
||||
* Fix SearchIssues swagger docs (#32208) (#32298)
|
||||
* Fix dropdown content overflow (#31610) (#32250)
|
||||
* Disable Oauth check if oauth disabled (#32368) (#32480)
|
||||
* Respect renamed dependencies of Cargo registry (#32430) (#32478)
|
||||
* Fix mermaid diagram height when initially hidden (#32457) (#32464)
|
||||
* Fix broken releases when re-pushing tags (#32435) (#32449)
|
||||
* Only provide the commit summary for Discord webhook push events (#32432) (#32447)
|
||||
* Only query team tables if repository is under org when getting assignees (#32414) (#32426)
|
||||
* Fix created_unix for mirroring (#32342) (#32406)
|
||||
* Respect UI.ExploreDefaultSort setting again (#32357) (#32385)
|
||||
* Fix broken image when editing comment with non-image attachments (#32319) (#32345)
|
||||
* Fix disable 2fa bug (#32320) (#32330)
|
||||
* Always update expiration time when creating an artifact (#32281) (#32285)
|
||||
* Fix null errors on conversation holder (#32258) (#32266) (#32282)
|
||||
* Only rename a user when they should receive a different name (#32247) (#32249)
|
||||
* Fix checkbox bug on private/archive filter (#32236) (#32240)
|
||||
* Add a doctor check to disable the "Actions" unit for mirrors (#32424) (#32497)
|
||||
* Quick fix milestone deadline 9999 (#32423)
|
||||
* Make `show stats` work when only one file changed (#32244) (#32268)
|
||||
* Make `owner/repo/pulls` handlers use "PR reader" permission (#32254) (#32265)
|
||||
* Update scheduled tasks even if changes are pushed by "ActionsUser" (#32246) (#32252)
|
||||
* MISC
|
||||
* Remove unnecessary code: `GetPushMirrorsByRepoID` called on all repo pages (#32560) (#32567)
|
||||
* Improve some sanitizer rules (#32534)
|
||||
* Update nix development environment vor v1.22.x (#32495)
|
||||
* Add warn log when deleting inactive users (#32318) (#32321)
|
||||
* Update github.com/go-enry/go-enry to v2.9.1 (#32295) (#32296)
|
||||
* Warn users when they try to use a non-root-url to sign in/up (#32272) (#32273)
|
||||
|
||||
## [1.22.3](https://github.com/go-gitea/gitea/releases/tag/v1.22.3) - 2024-10-08
|
||||
|
||||
* SECURITY
|
||||
* Fix bug when a token is given public only (#32204) (#32218)
|
||||
* PERFORMANCE
|
||||
* Increase `cacheContextLifetime` to reduce false reports (#32011) (#32023)
|
||||
* Don't join repository when loading action table data (#32127) (#32143)
|
||||
* BUGFIXES
|
||||
* Fix javascript error when an anonymous user visits migration page (#32144) (#32179)
|
||||
* Don't init signing keys if oauth2 provider is disabled (#32177)
|
||||
* Fix wrong status of `Set up Job` when first step is skipped (#32120) (#32125)
|
||||
* Fix bug when deleting a migrated branch (#32075) (#32123)
|
||||
* Truncate commit message during Discord webhook push events (#31970) (#32121)
|
||||
* Allow to set branch protection in an empty repository (#32095) (#32119)
|
||||
* Fix panic when cloning with wrong ssh format. (#32076) (#32118)
|
||||
* Fix rename branch permission bug (#32066) (#32108)
|
||||
* Fix: database not update release when using `git push --tags --force` (#32040) (#32074)
|
||||
* Add missing comment reply handling (#32050) (#32065)
|
||||
* Do not escape relative path in RPM primary index (#32038) (#32054)
|
||||
* Fix `/repos/{owner}/{repo}/pulls/{index}/files` endpoint not populating `previous_filename` (#32017) (#32028)
|
||||
* Support allowed hosts for migrations to work with proxy (#32025) (#32026)
|
||||
* Fix the logic of finding the latest pull review commit ID (#32139) (#32165)
|
||||
* Fix bug in getting merged pull request by commit (#32079) (#32117)
|
||||
* Fix wrong last modify time (#32102) (#32104)
|
||||
* Fix incorrect `/tokens` api (#32085) (#32092)
|
||||
* Handle invalid target when creating releases using API (#31841) (#32043)
|
||||
* Check if the `due_date` is nil when editing issues (#32035) (#32042)
|
||||
* Fix container parallel upload bugs (#32022)
|
||||
* Fixed race condition when deleting documents by repoId in ElasticSearch (#32185) (#32188)
|
||||
* Refactor CSRF protector (#32057) (#32069)
|
||||
* Fix Bug in Issue/pulls list (#32081) (#32115)
|
||||
* Include collaboration repositories on dashboard source/forks/mirrors list (#31946) (#32122)
|
||||
* Add null check for responseData.invalidTopics (#32212) (#32217)
|
||||
* TESTING
|
||||
* Fix mssql ci with a new mssql version on ci (#32094)
|
||||
* MISC
|
||||
* Upgrade some dependencies include minio-go (#32166)
|
||||
* Add bin to Composer Metadata (#32099) (#32106)
|
||||
* Lazy load avatar images (#32051) (#32063)
|
||||
* Upgrade cache to v0.2.1 (#32003) (#32009)
|
||||
|
||||
## [1.22.2](https://github.com/go-gitea/gitea/releases/tag/v1.22.2) - 2024-08-28
|
||||
|
||||
* Security
|
||||
* Replace v-html with v-text in search inputbox (#31966) (#31973)
|
||||
* Fix nuget/conan/container packages upload bugs (#31967) (#31982)
|
||||
* PERFORMANCE
|
||||
* Refactor the usage of batch catfile (#31754) (#31889)
|
||||
* BUGFIXES
|
||||
* Fix overflowing content in action run log (#31842) (#31853)
|
||||
* Scroll images in project issues separately from the remaining issue (#31683) (#31823)
|
||||
* Add `:focus-visible` style to buttons (#31799) (#31819)
|
||||
* Fix the display of project type for deleted projects (#31732) (#31734)
|
||||
* Fix API owner ID should be zero when created repo secret (#31715) (#31811)
|
||||
* Set owner id to zero when GetRegistrationToken for repo (#31725) (#31729)
|
||||
* Fix API endpoint for registration-token (#31722) (#31728)
|
||||
* Add permission check when creating PR (#31033) (#31720)
|
||||
* Don't return 500 if mirror url contains special chars (#31859) (#31895)
|
||||
* Fix agit automerge (#31207) (#31881)
|
||||
* Add CfTurnstileSitekey context data to all captcha templates (#31874) (#31876)
|
||||
* Avoid returning without written ctx when posting PR (#31843) (#31848)
|
||||
* Fix raw wiki links (#31825) (#31845)
|
||||
* Fix panic of ssh public key page after deletion of auth source (#31829) (#31836)
|
||||
* Fixes for unreachable project issues when transfer repository from organization (#31770) (#31828)
|
||||
* Show lock owner instead of repo owner on LFS setting page (#31788) (#31817)
|
||||
* Fix `IsObjectExist` with gogit (#31790) (#31806)
|
||||
* Fix protected branch files detection on pre_receive hook (#31778) (#31796)
|
||||
* Add `TAGS` to `TEST_TAGS` and fix bugs found with gogit (#31791) (#31795)
|
||||
* Rename head branch of pull requests when renaming a branch (#31759) (#31774)
|
||||
* Fix wiki revision pagination (#31760) (#31772)
|
||||
* Bump vue-bar-graph (#31705) (#31753)
|
||||
* Distinguish LFS object errors to ignore missing objects during migration (#31702) (#31745)
|
||||
* Make GetRepositoryByName more safer (#31712) (#31718)
|
||||
* Fix a branch divergence cache bug (#31659) (#31661)
|
||||
* Allow org team names of length 255 in create team form (#31564) (#31603)
|
||||
* Use old behavior for telegram webhook (#31588)
|
||||
* Bug fix for translation in ru (#31892)
|
||||
* Fix actions notify bug (#31866) (#31875)
|
||||
* Fix the component of access token list not mounted (#31824) (#31868)
|
||||
* Add missing repository type filter parameters to pager (#31832) (#31837)
|
||||
* Fix dates displaying in a wrong manner when we're close to the end of… (#31750)
|
||||
* Fix "Filter by commit" Dropdown (#31695) (#31696)
|
||||
* Properly filter issue list given no assignees filter (#31522) (#31685)
|
||||
* Prevent update pull refs manually and will not affect other refs update (#31931)(#31955)
|
||||
* Fix sort order for organization home and user profile page (#31921) (#31922)
|
||||
* Fix search team (#31923) (#31942)
|
||||
* Fix 500 error when state params is set when editing issue/PR by API (#31880) (#31952)
|
||||
* Fix index too many file names bug (#31903) (#31953)
|
||||
* Add lock for parallel maven upload (#31851) (#31954)
|
||||
* MISC
|
||||
* Remove "dsa-1024" testcases from Test_SSHParsePublicKey and Test_calcFingerprint (#31905) (#31914)
|
||||
* Upgrade bleve to 2.4.2 (#31894)
|
||||
* Remove unneccessary uses of `word-break: break-all` (#31637) (#31652)
|
||||
* Return an empty string when a repo has no avatar in the repo API (#31187) (#31567)
|
||||
* Upgrade micromatch to 4.0.8 (#31944)
|
||||
* Update webpack to 5.94.0 (#31941)
|
||||
|
||||
## [1.22.1](https://github.com/go-gitea/gitea/releases/tag/v1.22.1) - 2024-07-04
|
||||
|
||||
* SECURITY
|
||||
* Add replacement module for `mholt/archiver` (#31267) (#31270)
|
||||
* API
|
||||
* Fix missing images in editor preview due to wrong links (#31299) (#31393)
|
||||
* Fix duplicate sub-path for avatars (#31365) (#31368)
|
||||
* Reduce memory usage for chunked artifact uploads to MinIO (#31325) (#31338)
|
||||
* Remove sub-path from container registry realm (#31293) (#31300)
|
||||
* Fix NuGet Package API for $filter with Id equality (#31188) (#31242)
|
||||
* Add an immutable tarball link to archive download headers for Nix (#31139) (#31145)
|
||||
* Add missed return after `ctx.ServerError` (#31130) (#31133)
|
||||
* BUGFIXES
|
||||
* Fix avatar radius problem on the new issue page (#31506) (#31508)
|
||||
* Fix overflow menu flickering on mobile (#31484) (#31488)
|
||||
* Fix poor table column width due to breaking words (#31473) (#31477)
|
||||
* Support relative paths to videos from Wiki pages (#31061) (#31453)
|
||||
* Fix new issue/pr avatar (#31419) (#31424)
|
||||
* Increase max length of org team names from 30 to 255 characters (#31410) (#31421)
|
||||
* Fix line number width in code preview (#31307) (#31316)
|
||||
* Optimize runner-tags layout to enhance visual experience (#31258) (#31263)
|
||||
* Fix overflow on push notification (#31179) (#31238)
|
||||
* Fix overflow on notifications (#31178) (#31237)
|
||||
* Fix overflow in issue card (#31203) (#31225)
|
||||
* Split sanitizer functions and fine-tune some tests (#31192) (#31200)
|
||||
* use correct l10n string (#31487) (#31490)
|
||||
* Fix dropzone JS error when attachment is disabled (#31486)
|
||||
* Fix web notification icon not updated once you read all notifications (#31447) (#31466)
|
||||
* Switch to "Write" tab when edit comment again (#31445) (#31461)
|
||||
* Fix the link for .git-blame-ignore-revs bypass (#31432) (#31442)
|
||||
* Fix the wrong line number in the diff view page when expanded twice. (#31431) (#31440)
|
||||
* Fix labels and projects menu overflow on issue page (#31435) (#31439)
|
||||
* Fix Account Linking UpdateMigrationsByType (#31428) (#31434)
|
||||
* Fix markdown math brackets render problem (#31420) (#31430)
|
||||
* Fix rendered wiki page link (#31398) (#31407)
|
||||
* Fix natural sort (#31384) (#31394)
|
||||
* Allow downloading attachments of draft releases (#31369) (#31380)
|
||||
* Fix repo graph JS (#31377)
|
||||
* Fix incorrect localization `explorer.go` (#31348) (#31350)
|
||||
* Fix hash render end with colon (#31319) (#31346)
|
||||
* Fix line number widths (#31341) (#31343)
|
||||
* Fix navbar `+` menu flashing on page load (#31281) (#31342)
|
||||
* Fix adopt repository has empty object name in database (#31333) (#31335)
|
||||
* Delete legacy cookie before setting new cookie (#31306) (#31317)
|
||||
* Fix some URLs whose sub-path is missing (#31289) (#31292)
|
||||
* Fix admin oauth2 custom URL settings (#31246) (#31247)
|
||||
* Make pasted "img" tag has the same behavior as markdown image (#31235) (#31243)
|
||||
* Fix agit checkout command line hint & fix ShowMergeInstructions checking (#31219) (#31222)
|
||||
* Fix the possible migration failure on 286 with postgres 16 (#31209) (#31218)
|
||||
* Fix branch order (#31174) (#31193)
|
||||
* Fix markup preview (#31158) (#31166)
|
||||
* Fix push multiple branches error with tests (#31151) (#31153)
|
||||
* Fix API repository object format missed (#31118) (#31132)
|
||||
* Fix missing memcache import (#31105) (#31109)
|
||||
* Upgrade `github.com/hashicorp/go-retryablehttp` (#31499)
|
||||
* Fix double border in system status table (#31363) (#31401)
|
||||
* Fix bug filtering issues which have no project (#31337) (#31367)
|
||||
* Fix #31185 try fix lfs download from bitbucket failed (#31201) (#31329)
|
||||
* Add nix flake for dev shell (#30967) (#31310)
|
||||
* Fix and clean up `ConfirmModal` (#31283) (#31291)
|
||||
* Optimize repo-list layout to enhance visual experience (#31272) (#31276)
|
||||
* fixed the dropdown menu for the top New button to expand to the left (#31273) (#31275)
|
||||
* Fix Activity Page Contributors dropdown (#31264) (#31269)
|
||||
* fix: allow actions artifacts storage migration to complete succesfully (#31251) (#31257)
|
||||
* Make blockquote attention recognize more syntaxes (#31240) (#31250)
|
||||
* Remove .segment from .project-column (#31204) (#31239)
|
||||
* Ignore FindRecentlyPushedNewBranches err (#31164) (#31171)
|
||||
* Use vertical layout for multiple code expander buttons (#31122) (#31152)
|
||||
* Remove duplicate `ProxyPreserveHost` in Apache httpd doc (#31143) (#31147)
|
||||
* Improve mobile review ui (#31091) (#31136)
|
||||
* Fix DashboardRepoList margin (#31121) (#31128)
|
||||
* Update pip related commands for docker (#31106) (#31111)
|
||||
|
||||
## [1.22.0](https://github.com/go-gitea/gitea/releases/tag/v1.22.0) - 2024-05-27
|
||||
|
||||
This release stands as a monumental milestone in our development journey with a record-breaking incorporation of [1528](https://github.com/go-gitea/gitea/pulls?q=is%3Apr+milestone%3A1.22.0+is%3Amerged) pull requests. It marks the most extensive update in Gitea's history, showcasing a plethora of new features and infrastructure improvements.
|
||||
|
||||
Noteworthy advancements in this release include the introduction of `HTMX` and `Tailwind`, signaling a strategic shift as we gradually phase out `jquery` and `Fomantic UI`. These changes reflect our commitment to embracing modern technologies and enhancing the user experience.
|
||||
|
||||
Key highlights of this release encompass significant changes categorized under `BREAKING`, `FEATURES`, `ENHANCEMENTS`, and `PERFORMANCE`, each contributing to a more robust and efficient Gitea platform.
|
||||
|
||||
* BREAKING
|
||||
* Improve reverse proxy documents and clarify the AppURL guessing behavior (#31003) (#31020)
|
||||
* Remember log in for a month by default (#30150)
|
||||
* Breaking summary for template refactoring (#29395)
|
||||
* All custom templates need to follow these changes
|
||||
* Recommend/convert to use case-sensitive collation for MySQL/MSSQL (#28662)
|
||||
* Make offline mode as default to not connect external avatar service by default (#28548)
|
||||
* Include public repos in the doer's dashboard for issue search (#28304)
|
||||
* Use restricted sanitizer for repository description (#28141)
|
||||
* Support storage base path as prefix (#27827)
|
||||
* Enhanced auth token / remember me (#27606)
|
||||
* Rename the default themes to `gitea-light`, `gitea-dark`, `gitea-auto` (#27419)
|
||||
* If you didn't see the new themes, please remove the `[ui].THEMES` config option from `app.ini`
|
||||
* Require MySQL 8.0, PostgreSQL 12, MSSQL 2012 (#27337)
|
||||
* FEATURES
|
||||
* Allow everyone to read or write a wiki by a repo unit setting (#30495)
|
||||
* Use raw Wiki links for non-renderable Wiki files (#30273)
|
||||
* Render embedded code preview by permalink in markdown (#30234) (#30249)
|
||||
* Support repo code search without setting up an indexer (#29998)
|
||||
* Support pasting URLs over markdown text (#29566)
|
||||
* Allow to change primary email before account activation (#29412)
|
||||
* Customizable "Open with" applications for repository clone (#29320)
|
||||
* Allow options to disable user deletion from the interface on app.ini (#29275)
|
||||
* Extend issue template YAML engine (#29274)
|
||||
* Add support for `linguist-detectable` and `linguist-documentation` (#29267)
|
||||
* Implement code frequency graph (#29191)
|
||||
* Show commit status for releases (#29149)
|
||||
* Add user blocking (#29028)
|
||||
* Actions Artifacts v4 backend (#28965)
|
||||
* Add merge style `fast-forward-only` (#28954)
|
||||
* Retarget depending pulls when the parent branch is deleted (#28686)
|
||||
* Add global setting on how timestamps should be rendered (#28657)
|
||||
* Implement actions badge SVGs (#28102)
|
||||
* Add skip ci functionality (#28075)
|
||||
* Show latest commit for file (#28067)
|
||||
* Allow to sync tags from the admin dashboard (#28045)
|
||||
* Add Profile Readme for Organisations (#27955)
|
||||
* Implement contributors graph (#27882)
|
||||
* Artifact deletion in actions ui (#27172)
|
||||
* Add API routes to get runner registration token (#27144)
|
||||
* Add support for forking single branch (#25821)
|
||||
* Add support for sha256 repositories (#23894)
|
||||
* Add admin API route for managing user's badges (#23106)
|
||||
* ENHANCEMENTS
|
||||
* Make gitea webhooks openproject compatible (#28435) (#31081)
|
||||
* Support using label names when changing issue labels (#30943) (#30958)
|
||||
* Fix various problems around project board view (#30696) (#30902)
|
||||
* Improve context popup rendering (#30824) (#30829)
|
||||
* Allow to save empty comment (#30706)
|
||||
* Prevent allow/reject reviews on merged/closed PRs (#30686)
|
||||
* Initial support for colorblindness-friendly themes (#30625)
|
||||
* Some NuGet package enhancements (#30280) (#30324)
|
||||
* Markup color and font size fixes (#30282) (#30310)
|
||||
* Show 12 lines in markup code preview (#30255) (#30257)
|
||||
* Add `[other].SHOW_FOOTER_POWERED_BY` setting to hide `Powered by` (#30253)
|
||||
* Pulse page improvements (#30149)
|
||||
* Render code tags in commit messages (#30146)
|
||||
* Prevent re-review and dismiss review actions on closed and merged PRs (#30065)
|
||||
* Cancel previous runs of the same PR automatically (#29961)
|
||||
* Drag-and-drop improvements for projects and issue pins (#29875)
|
||||
* Add default board to new projects, remove uncategorized pseudo-board (#29874)
|
||||
* Prevent layout shift in `<overflow-menu>` items (#29831)
|
||||
* Add skip ci support for pull request title (#29774)
|
||||
* Add more stats tables (#29730)
|
||||
* Update API to return 'source_id' for users (#29718)
|
||||
* Determine fuzziness of bleve indexer by keyword length (#29706)
|
||||
* Expose fuzzy search for issues/pulls (#29701)
|
||||
* Put an edit file button on pull request files to allow a quick operation (#29697)
|
||||
* Fix action runner offline label padding (#29691)
|
||||
* Update allowed attachment types (#29688)
|
||||
* Completely style the webkit autofill (#29683)
|
||||
* Highlight archived labels (#29680)
|
||||
* Add a warning for disallowed email domains (#29658)
|
||||
* Set user's 24h preference from their current OS locale (#29651)
|
||||
* Add setting to disable user features when user login type is not plain (#29615)
|
||||
* Improve natural sort (#29611)
|
||||
* Make wiki default branch name changeable (#29603)
|
||||
* Unify search boxes (#29530)
|
||||
* Add support for API blob upload of release attachments (#29507)
|
||||
* Detect broken git hooks (#29494)
|
||||
* Sync branches to DB immediately when handling git hook calling (#29493)
|
||||
* Allow options to disable user GPG key configuration from the interface on app.ini (#29486)
|
||||
* Allow options to disable user SSH key configuration from the interface on app.ini (#29447)
|
||||
* Use relative links for commits, mentions, and issues in markdown (#29427)
|
||||
* Add `<overflow-menu>`, rename webcomponents (#29400)
|
||||
* Include resource state events in Gitlab downloads (#29382)
|
||||
* Properly migrate target branch change GitLab comment (#29340)
|
||||
* Recolor dark theme to blue shade (#29283)
|
||||
* Partially enable MSSQL case-sensitive collation support (#29238)
|
||||
* Auto-update the system status in the admin dashboard (#29163)
|
||||
* Integrate alpine `noarch` packages into other architectures index (#29137)
|
||||
* Document how the TOC election process works (#29135)
|
||||
* Tweak repo header (#29134)
|
||||
* Make blockquote border size less aggressive (#29124)
|
||||
* Downscale pasted PNG images based on metadata (#29123)
|
||||
* Show `View at this point in history` for every commit (#29122)
|
||||
* Add support for action artifact serve direct (#29120)
|
||||
* Change webhook-type in create-view (#29114)
|
||||
* Drop "@" from the email sender to avoid spam filters (#29109)
|
||||
* Allow non-admin users to delete review requests (#29057)
|
||||
* Improve user search display name (#29002)
|
||||
* Include username in email headers (#28981)
|
||||
* Show whether a PR is WIP inside popups (#28975)
|
||||
* Also match weakly validated ETags (#28957)
|
||||
* Support nuspec manifest download for Nuget packages (#28921)
|
||||
* Fix hardcoded GitHub icon used as migrated release avatar (#28910)
|
||||
* Propagate install_if and provider_priority to APKINDEX (#28899)
|
||||
* Add artifacts v4 JWT to job message and accept it (#28885)
|
||||
* Enable/disable owner and repo projects independently (#28805)
|
||||
* Add non-JS fallback for reaction tooltips (#28785)
|
||||
* Add the ability to see open and closed issues at the same time (#28757)
|
||||
* Move sign-in labels to be above inputs (#28753)
|
||||
* Display the latest sync time for pull mirrors on the repo page (#28712)
|
||||
* Show in Web UI if the file is vendored and generated (#28620)
|
||||
* Add orphaned topic consistency check (#28507)
|
||||
* Add branch protection setting for ignoring stale approvals (#28498)
|
||||
* Add option to set language in admin user view (#28449)
|
||||
* Fix incorrect run order of action jobs (#28367)
|
||||
* Add missing exclusive in advanced label options (#28322)
|
||||
* Added instance-level variables (#28115)
|
||||
* Add edit option for README.md (#28071)
|
||||
* Fix link to `Code` tab on wiki commits (#28041)
|
||||
* Allow to set explore page default sort (#27951)
|
||||
* Improve PR diff view on mobile (#27883)
|
||||
* Properly migrate automatic merge GitLab comments (#27873)
|
||||
* Display issue task list on project cards (#27865)
|
||||
* Add Index to pull_auto_merge.doer_id (#27811)
|
||||
* Fix display member unit in the menu bar if there are no hidden members in public org (#27795)
|
||||
* List all Debian package versions in `Packages` (#27786)
|
||||
* Allow pull requests Manually Merged option to be used by non-admins (#27780)
|
||||
* Only show diff file tree when more than one file changed (#27775)
|
||||
* Show placeholder email in privacy popup (#27770)
|
||||
* Revamp repo header (#27760)
|
||||
* Add `must-change-password` command line parameter (#27626)
|
||||
* Unify password changing and invalidate auth tokens (#27625)
|
||||
* Add border to file tree 'sub-items' and add padding to 'item-file' (#27593)
|
||||
* Add slow SQL query warning (#27545)
|
||||
* Pre-register OAuth application for tea (#27509)
|
||||
* Differentiate between `push` and `pull` `mirror sync in progress` (#27390)
|
||||
* Link to file from its history (#27354)
|
||||
* Add a shortcut to user's profile page to admin user details (#27299)
|
||||
* Doctor: delete action entries without existing user (#27292)
|
||||
* Show total TrackedTime on issue/pull/milestone lists (#26672)
|
||||
* Don't show the new pull request button when the page is not compare pull (#26431)
|
||||
* Add `Hide/Show all checks` button to commit status check (#26284)
|
||||
* Improvements of releases list and tags list (#25859)
|
||||
* PERFORMANCE
|
||||
* Fix package list performance (#30520) (#30616)
|
||||
* Add commit status summary table to reduce query from commit status table (#30223)
|
||||
* Refactor markup/csv: don't read all to memory (#29760)
|
||||
* Lazy load object format with command line and don't do it in OpenRepository (#29712)
|
||||
* Add cache for branch divergence on branch list page (#29577)
|
||||
* Do some performance optimization for issues list and view issue/pull (#29515)
|
||||
* Cache repository default branch commit status to reduce query on commit status table (#29444)
|
||||
* Use `crypto/sha256` (#29386)
|
||||
* Some performance optimization on the dashboard and issues page (#29010)
|
||||
* Add combined index for issue_user.uid and issue_id (#28080)
|
||||
|
||||
## [1.21.11](https://github.com/go-gitea/gitea/releases/tag/v1.21.11) - 2024-04-07
|
||||
|
||||
* SECURITY
|
||||
|
@ -223,7 +223,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "non-fast-forward") {
|
||||
return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
|
||||
} else if strings.Contains(stderr, "! [remote rejected]") {
|
||||
} else if strings.Contains(stderr, "! [remote rejected]") || strings.Contains(stderr, "! [rejected]") {
|
||||
err := &ErrPushRejected{StdOut: stdout, StdErr: stderr, Err: err}
|
||||
err.GenerateMessage()
|
||||
return err
|
||||
|
56
modules/markup/markdown/markdown_attention_test.go
Normal file
56
modules/markup/markdown/markdown_attention_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markdown_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func TestAttention(t *testing.T) {
|
||||
defer svg.MockIcon("octicon-info")()
|
||||
defer svg.MockIcon("octicon-light-bulb")()
|
||||
defer svg.MockIcon("octicon-report")()
|
||||
defer svg.MockIcon("octicon-alert")()
|
||||
defer svg.MockIcon("octicon-stop")()
|
||||
|
||||
renderAttention := func(attention, icon string) string {
|
||||
tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
|
||||
tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
|
||||
tmpl = strings.ReplaceAll(tmpl, "{icon}", icon)
|
||||
tmpl = strings.ReplaceAll(tmpl, "{Attention}", cases.Title(language.English).String(attention))
|
||||
return tmpl
|
||||
}
|
||||
|
||||
test := func(input, expected string) {
|
||||
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
||||
}
|
||||
|
||||
test(`
|
||||
> [!NOTE]
|
||||
> text
|
||||
`, renderAttention("note", "octicon-info")+"\n<p>text</p>\n</blockquote>")
|
||||
|
||||
test(`> [!note]`, renderAttention("note", "octicon-info")+"\n</blockquote>")
|
||||
test(`> [!tip]`, renderAttention("tip", "octicon-light-bulb")+"\n</blockquote>")
|
||||
test(`> [!important]`, renderAttention("important", "octicon-report")+"\n</blockquote>")
|
||||
test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
|
||||
test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n</blockquote>")
|
||||
|
||||
// escaped by mdformat
|
||||
test(`> \[!NOTE\]`, renderAttention("note", "octicon-info")+"\n</blockquote>")
|
||||
|
||||
// legacy GitHub style
|
||||
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
|
||||
}
|
25
modules/markup/markdown/markdown_benchmark_test.go
Normal file
25
modules/markup/markdown/markdown_benchmark_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markdown_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
)
|
||||
|
||||
func BenchmarkSpecializedMarkdown(b *testing.B) {
|
||||
// 240856 4719 ns/op
|
||||
for i := 0; i < b.N; i++ {
|
||||
markdown.SpecializedMarkdown(&markup.RenderContext{})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarkdownRender(b *testing.B) {
|
||||
// 23202 50840 ns/op
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = markdown.RenderString(markup.NewTestRenderContext(), "https://example.com\n- a\n- b\n")
|
||||
}
|
||||
}
|
227
modules/markup/markdown/markdown_math_test.go
Normal file
227
modules/markup/markdown/markdown_math_test.go
Normal file
@ -0,0 +1,227 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMathRender(t *testing.T) {
|
||||
const nl = "\n"
|
||||
testcases := []struct {
|
||||
testcase string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"$a$",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$ a $",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$a$ $b$",
|
||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`\(a\) \(b\)`,
|
||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`$a$.`,
|
||||
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
|
||||
},
|
||||
{
|
||||
`.$a$`,
|
||||
`<p>.$a$</p>` + nl,
|
||||
},
|
||||
{
|
||||
`$a a$b b$`,
|
||||
`<p>$a a$b b$</p>` + nl,
|
||||
},
|
||||
{
|
||||
`a a$b b`,
|
||||
`<p>a a$b b</p>` + nl,
|
||||
},
|
||||
{
|
||||
`a$b $a a$b b$`,
|
||||
`<p>a$b $a a$b b$</p>` + nl,
|
||||
},
|
||||
{
|
||||
"a$x$",
|
||||
`<p>a$x$</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$x$a",
|
||||
`<p>$x$a</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$a$ ($b$) [$c$] {$d$}",
|
||||
`<p><code class="language-math is-loading">a</code> (<code class="language-math is-loading">b</code>) [$c$] {$d$}</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$$a$$",
|
||||
`<code class="chroma language-math display">a</code>` + nl,
|
||||
},
|
||||
{
|
||||
"$$a$$ test",
|
||||
`<p><code class="language-math display is-loading">a</code> test</p>` + nl,
|
||||
},
|
||||
{
|
||||
"test $$a$$",
|
||||
`<p>test <code class="language-math display is-loading">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`foo $x=\$$ bar`,
|
||||
`<p>foo <code class="language-math is-loading">x=\$</code> bar</p>` + nl,
|
||||
},
|
||||
{
|
||||
`$\text{$b$}$`,
|
||||
`<p><code class="language-math is-loading">\text{$b$}</code></p>` + nl,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testcases {
|
||||
t.Run(test.testcase, func(t *testing.T) {
|
||||
res, err := RenderString(markup.NewTestRenderContext(), test.testcase)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, string(res))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMathRenderBlockIndent(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
testcase string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"indent-0",
|
||||
`
|
||||
\[
|
||||
\alpha
|
||||
\]
|
||||
`,
|
||||
`<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
\alpha
|
||||
</code></pre>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"indent-1",
|
||||
`
|
||||
\[
|
||||
\alpha
|
||||
\]
|
||||
`,
|
||||
`<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
\alpha
|
||||
</code></pre>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"indent-2-mismatch",
|
||||
`
|
||||
\[
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
\]
|
||||
`,
|
||||
`<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
</code></pre>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"indent-2",
|
||||
`
|
||||
\[
|
||||
a
|
||||
b
|
||||
c
|
||||
\]
|
||||
`,
|
||||
`<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
a
|
||||
b
|
||||
c
|
||||
</code></pre>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"indent-0-oneline",
|
||||
`$$ x $$
|
||||
foo`,
|
||||
`<code class="chroma language-math display"> x </code>
|
||||
<p>foo</p>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"indent-3-oneline",
|
||||
` $$ x $$<SPACE>
|
||||
foo`,
|
||||
`<code class="chroma language-math display"> x </code>
|
||||
<p>foo</p>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"quote-block",
|
||||
`
|
||||
> \[
|
||||
> a
|
||||
> \]
|
||||
> \[
|
||||
> b
|
||||
> \]
|
||||
`,
|
||||
`<blockquote>
|
||||
<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
a
|
||||
</code></pre>
|
||||
<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
b
|
||||
</code></pre>
|
||||
</blockquote>
|
||||
`,
|
||||
},
|
||||
{
|
||||
"list-block",
|
||||
`
|
||||
1. a
|
||||
\[
|
||||
x
|
||||
\]
|
||||
2. b`,
|
||||
`<ol>
|
||||
<li>a
|
||||
<pre class="code-block is-loading"><code class="chroma language-math display">
|
||||
x
|
||||
</code></pre>
|
||||
</li>
|
||||
<li>b</li>
|
||||
</ol>
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testcases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := RenderString(markup.NewTestRenderContext(), strings.ReplaceAll(test.testcase, "<SPACE>", " "))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, string(res), "unexpected result for test case:\n%s", test.testcase)
|
||||
})
|
||||
}
|
||||
}
|
@ -13,13 +13,10 @@ import (
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -386,81 +383,6 @@ func TestColorPreview(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMathBlock(t *testing.T) {
|
||||
const nl = "\n"
|
||||
testcases := []struct {
|
||||
testcase string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"$a$",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$ a $",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$a$ $b$",
|
||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`\(a\) \(b\)`,
|
||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`$a$.`,
|
||||
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
|
||||
},
|
||||
{
|
||||
`.$a$`,
|
||||
`<p>.$a$</p>` + nl,
|
||||
},
|
||||
{
|
||||
`$a a$b b$`,
|
||||
`<p>$a a$b b$</p>` + nl,
|
||||
},
|
||||
{
|
||||
`a a$b b`,
|
||||
`<p>a a$b b</p>` + nl,
|
||||
},
|
||||
{
|
||||
`a$b $a a$b b$`,
|
||||
`<p>a$b $a a$b b$</p>` + nl,
|
||||
},
|
||||
{
|
||||
"a$x$",
|
||||
`<p>a$x$</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$x$a",
|
||||
`<p>$x$a</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$$a$$",
|
||||
`<pre class="code-block is-loading"><code class="chroma language-math display">a</code></pre>` + nl,
|
||||
},
|
||||
{
|
||||
"$a$ ($b$) [$c$] {$d$}",
|
||||
`<p><code class="language-math is-loading">a</code> (<code class="language-math is-loading">b</code>) [$c$] {$d$}</p>` + nl,
|
||||
},
|
||||
{
|
||||
"$$a$$ test",
|
||||
`<p><code class="language-math display is-loading">a</code> test</p>` + nl,
|
||||
},
|
||||
{
|
||||
"test $$a$$",
|
||||
`<p>test <code class="language-math display is-loading">a</code></p>` + nl,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testcases {
|
||||
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
|
||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
||||
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskList(t *testing.T) {
|
||||
testcases := []struct {
|
||||
testcase string
|
||||
@ -551,56 +473,3 @@ space</p>
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(result))
|
||||
}
|
||||
|
||||
func TestAttention(t *testing.T) {
|
||||
defer svg.MockIcon("octicon-info")()
|
||||
defer svg.MockIcon("octicon-light-bulb")()
|
||||
defer svg.MockIcon("octicon-report")()
|
||||
defer svg.MockIcon("octicon-alert")()
|
||||
defer svg.MockIcon("octicon-stop")()
|
||||
|
||||
renderAttention := func(attention, icon string) string {
|
||||
tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
|
||||
tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
|
||||
tmpl = strings.ReplaceAll(tmpl, "{icon}", icon)
|
||||
tmpl = strings.ReplaceAll(tmpl, "{Attention}", cases.Title(language.English).String(attention))
|
||||
return tmpl
|
||||
}
|
||||
|
||||
test := func(input, expected string) {
|
||||
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
|
||||
}
|
||||
|
||||
test(`
|
||||
> [!NOTE]
|
||||
> text
|
||||
`, renderAttention("note", "octicon-info")+"\n<p>text</p>\n</blockquote>")
|
||||
|
||||
test(`> [!note]`, renderAttention("note", "octicon-info")+"\n</blockquote>")
|
||||
test(`> [!tip]`, renderAttention("tip", "octicon-light-bulb")+"\n</blockquote>")
|
||||
test(`> [!important]`, renderAttention("important", "octicon-report")+"\n</blockquote>")
|
||||
test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
|
||||
test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n</blockquote>")
|
||||
|
||||
// escaped by mdformat
|
||||
test(`> \[!NOTE\]`, renderAttention("note", "octicon-info")+"\n</blockquote>")
|
||||
|
||||
// legacy GitHub style
|
||||
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
|
||||
}
|
||||
|
||||
func BenchmarkSpecializedMarkdown(b *testing.B) {
|
||||
// 240856 4719 ns/op
|
||||
for i := 0; i < b.N; i++ {
|
||||
markdown.SpecializedMarkdown(&markup.RenderContext{})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarkdownRender(b *testing.B) {
|
||||
// 23202 50840 ns/op
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = markdown.RenderString(markup.NewTestRenderContext(), "https://example.com\n- a\n- b\n")
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ type Block struct {
|
||||
Dollars bool
|
||||
Indent int
|
||||
Closed bool
|
||||
Inline bool
|
||||
}
|
||||
|
||||
// KindBlock is the node kind for math blocks
|
||||
|
@ -6,6 +6,8 @@ package math
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
giteaUtil "code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/text"
|
||||
@ -13,13 +15,17 @@ import (
|
||||
)
|
||||
|
||||
type blockParser struct {
|
||||
parseDollars bool
|
||||
parseDollars bool
|
||||
endBytesDollars []byte
|
||||
endBytesBracket []byte
|
||||
}
|
||||
|
||||
// NewBlockParser creates a new math BlockParser
|
||||
func NewBlockParser(parseDollarBlocks bool) parser.BlockParser {
|
||||
return &blockParser{
|
||||
parseDollars: parseDollarBlocks,
|
||||
parseDollars: parseDollarBlocks,
|
||||
endBytesDollars: []byte{'$', '$'},
|
||||
endBytesBracket: []byte{'\\', ']'},
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,28 +53,24 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex
|
||||
node := NewBlock(dollars, pos)
|
||||
|
||||
// Now we need to check if the ending block is on the segment...
|
||||
endBytes := []byte{'\\', ']'}
|
||||
if dollars {
|
||||
endBytes = []byte{'$', '$'}
|
||||
}
|
||||
endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesBracket)
|
||||
idx := bytes.Index(line[pos+2:], endBytes)
|
||||
if idx >= 0 {
|
||||
// for case $$ ... $$ any other text
|
||||
for i := pos + idx + 4; i < len(line); i++ {
|
||||
for i := pos + 2 + idx + 2; i < len(line); i++ {
|
||||
if line[i] != ' ' && line[i] != '\n' {
|
||||
return nil, parser.NoChildren
|
||||
}
|
||||
}
|
||||
segment.Stop = segment.Start + idx + 2
|
||||
reader.Advance(segment.Len() - 1)
|
||||
segment.Start += 2
|
||||
segment.Start += pos + 2
|
||||
segment.Stop = segment.Start + idx
|
||||
node.Lines().Append(segment)
|
||||
node.Closed = true
|
||||
node.Inline = true
|
||||
return node, parser.Close | parser.NoChildren
|
||||
}
|
||||
|
||||
reader.Advance(segment.Len() - 1)
|
||||
segment.Start += 2
|
||||
segment.Start += pos + 2
|
||||
node.Lines().Append(segment)
|
||||
return node, parser.NoChildren
|
||||
}
|
||||
@ -81,29 +83,20 @@ func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Cont
|
||||
}
|
||||
|
||||
line, segment := reader.PeekLine()
|
||||
w, pos := util.IndentWidth(line, 0)
|
||||
w, pos := util.IndentWidth(line, reader.LineOffset())
|
||||
if w < 4 {
|
||||
if block.Dollars {
|
||||
i := pos
|
||||
for ; i < len(line) && line[i] == '$'; i++ {
|
||||
}
|
||||
length := i - pos
|
||||
if length >= 2 && util.IsBlank(line[i:]) {
|
||||
reader.Advance(segment.Stop - segment.Start - segment.Padding)
|
||||
block.Closed = true
|
||||
endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesBracket)
|
||||
if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) {
|
||||
if util.IsBlank(line[pos+len(endBytes):]) {
|
||||
newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1)
|
||||
reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
|
||||
return parser.Close
|
||||
}
|
||||
} else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) {
|
||||
reader.Advance(segment.Stop - segment.Start - segment.Padding)
|
||||
block.Closed = true
|
||||
return parser.Close
|
||||
}
|
||||
}
|
||||
|
||||
pos, padding := util.IndentPosition(line, 0, block.Indent)
|
||||
seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
|
||||
start := segment.Start + giteaUtil.Iif(pos > block.Indent, block.Indent, pos)
|
||||
seg := text.NewSegmentPadding(start, segment.Stop, segment.Padding)
|
||||
node.Lines().Append(seg)
|
||||
reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
|
||||
return parser.Continue | parser.NoChildren
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ package math
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/markup/internal"
|
||||
giteaUtil "code.gitea.io/gitea/modules/util"
|
||||
|
||||
gast "github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
@ -37,10 +38,11 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node)
|
||||
func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
n := node.(*Block)
|
||||
if entering {
|
||||
_ = r.renderInternal.FormatWithSafeAttrs(w, `<pre class="code-block is-loading"><code class="chroma language-math display">`)
|
||||
code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="chroma language-math display">`
|
||||
_ = r.renderInternal.FormatWithSafeAttrs(w, code)
|
||||
r.writeLines(w, source, n)
|
||||
} else {
|
||||
_, _ = w.WriteString(`</code></pre>` + "\n")
|
||||
_, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ var defaultDualDollarParser = &inlineParser{
|
||||
end: []byte{'$', '$'},
|
||||
}
|
||||
|
||||
// NewInlineDollarParser returns a new inline parser
|
||||
func NewInlineDollarParser() parser.InlineParser {
|
||||
return defaultInlineDollarParser
|
||||
}
|
||||
@ -40,7 +39,6 @@ var defaultInlineBracketParser = &inlineParser{
|
||||
end: []byte{'\\', ')'},
|
||||
}
|
||||
|
||||
// NewInlineDollarParser returns a new inline parser
|
||||
func NewInlineBracketParser() parser.InlineParser {
|
||||
return defaultInlineBracketParser
|
||||
}
|
||||
@ -81,35 +79,35 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
||||
opener := len(parser.start)
|
||||
|
||||
// Now look for an ending line
|
||||
ender := opener
|
||||
for {
|
||||
pos := bytes.Index(line[ender:], parser.end)
|
||||
if pos < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ender += pos
|
||||
|
||||
// Now we want to check the character at the end of our parser section
|
||||
// that is ender + len(parser.end) and check if char before ender is '\'
|
||||
pos = ender + len(parser.end)
|
||||
if len(line) <= pos {
|
||||
depth := 0
|
||||
ender := -1
|
||||
for i := opener; i < len(line); i++ {
|
||||
if depth == 0 && bytes.HasPrefix(line[i:], parser.end) {
|
||||
succeedingCharacter := byte(0)
|
||||
if i+len(parser.end) < len(line) {
|
||||
succeedingCharacter = line[i+len(parser.end)]
|
||||
}
|
||||
// check valid ending character
|
||||
isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) ||
|
||||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
|
||||
if !isValidEndingChar {
|
||||
break
|
||||
}
|
||||
ender = i
|
||||
break
|
||||
}
|
||||
suceedingCharacter := line[pos]
|
||||
// check valid ending character
|
||||
if !isPunctuation(suceedingCharacter) &&
|
||||
!(suceedingCharacter == ' ') &&
|
||||
!(suceedingCharacter == '\n') &&
|
||||
!isBracket(suceedingCharacter) {
|
||||
return nil
|
||||
if line[i] == '\\' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if line[ender-1] != '\\' {
|
||||
break
|
||||
if line[i] == '{' {
|
||||
depth++
|
||||
} else if line[i] == '}' {
|
||||
depth--
|
||||
}
|
||||
|
||||
// move the pointer onwards
|
||||
ender += len(parser.end)
|
||||
}
|
||||
if ender == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
block.Advance(opener)
|
||||
|
@ -104,6 +104,7 @@ copy_url=Kopírovat URL
|
||||
copy_hash=Kopírovat hash
|
||||
copy_content=Kopírovat obsah
|
||||
copy_branch=Kopírovat jméno větve
|
||||
copy_path=Zkopírovat cestu
|
||||
copy_success=Zkopírováno!
|
||||
copy_error=Kopírování se nezdařilo
|
||||
copy_type_unsupported=Tento typ souboru nelze zkopírovat
|
||||
@ -159,6 +160,7 @@ filter.public=Veřejná
|
||||
filter.private=Soukromý
|
||||
|
||||
no_results_found=Nebyly nalezeny žádné výsledky.
|
||||
internal_error_skipped=Došlo k vnitřní chybě, ale je přeskočena: %s
|
||||
|
||||
[search]
|
||||
search=Hledat...
|
||||
@ -177,6 +179,8 @@ code_search_by_git_grep=Aktuální výsledky vyhledávání kódu jsou poskytov
|
||||
package_kind=Hledat balíčky...
|
||||
project_kind=Hledat projekty...
|
||||
branch_kind=Hledat větve...
|
||||
tag_kind=Prohledat značky...
|
||||
tag_tooltip=Hledat odpovídající značky. Použijte „%“ pro vyhledání libovolné posloupnosti číslic.
|
||||
commit_kind=Hledat commity...
|
||||
runner_kind=Hledat runnery...
|
||||
no_results=Nebyly nalezeny žádné odpovídající výsledky.
|
||||
@ -206,6 +210,10 @@ buttons.link.tooltip=Přidat odkaz
|
||||
buttons.list.unordered.tooltip=Přidat seznam odrážek
|
||||
buttons.list.ordered.tooltip=Přidat číslovaný seznam
|
||||
buttons.list.task.tooltip=Přidat seznam úloh
|
||||
buttons.table.add.tooltip=Přidat tabulku
|
||||
buttons.table.add.insert=Přidat
|
||||
buttons.table.rows=Řádky
|
||||
buttons.table.cols=Sloupce
|
||||
buttons.mention.tooltip=Uveďte uživatele nebo tým
|
||||
buttons.ref.tooltip=Odkaz na issue nebo pull request
|
||||
buttons.switch_to_legacy.tooltip=Místo toho použít starší editor
|
||||
@ -218,16 +226,20 @@ string.desc=Z – A
|
||||
|
||||
[error]
|
||||
occurred=Došlo k chybě
|
||||
report_message=Pokud jste si jisti, že se jedná o chybu Gitea, prosím vyhledejte problémy na <a href="%s" target="_blank">GitHub</a> a v případě potřeby založte nový problém.
|
||||
not_found=Cíl nebyl nalezen.
|
||||
network_error=Chyba sítě
|
||||
|
||||
[startpage]
|
||||
app_desc=Snadno přístupný vlastní Git
|
||||
install=Jednoduchá na instalaci
|
||||
install_desc=Jednoduše <a target="_blank" rel="noopener noreferrer" href="%s">spusťte jako binární program</a> pro vaši platformu, nasaďte jej pomocí <a target="_blank" rel="noopener noreferrer" href="%s">Docker</a>, nebo jej stáhněte jako <a target="_blank" rel="noopener noreferrer" href="%s">balíček</a>.
|
||||
platform=Multiplatformní
|
||||
platform_desc=Gitea běží všude, kde <a target="_blank" rel="noopener noreferrer" href="%s">Go</a> může kompilovat: Windows, macOS, Linux, ARM, atd. Vyberte si ten, který milujete!
|
||||
lightweight=Lehká
|
||||
lightweight_desc=Gitea má minimální požadavky a může běžet na Raspberry Pi. Šetřete energii vašeho stroje!
|
||||
license=Open Source
|
||||
license_desc=Vše je na <a target="_blank" rel="noopener noreferrer" href="%[1]s">%[2]s</a>! Připojte se tím, že <a target="_blank" rel="noopener noreferrer" href="%[3]s">přispějete</a> a uděláte tento projekt ještě lepší. Nestyďte se být přispěvatel!
|
||||
|
||||
[install]
|
||||
install=Instalace
|
||||
@ -341,6 +353,7 @@ enable_update_checker=Povolit kontrolu aktualizací
|
||||
enable_update_checker_helper=Kontroluje vydání nových verzí pravidelně připojením ke gitea.io.
|
||||
env_config_keys=Konfigurace prostředí
|
||||
env_config_keys_prompt=Následující proměnné prostředí budou také použity pro váš konfigurační soubor:
|
||||
config_write_file_prompt=Tyto možnosti konfigurace budou zapsány do: %s
|
||||
|
||||
[home]
|
||||
nav_menu=Navigační menu
|
||||
@ -381,6 +394,8 @@ relevant_repositories=Zobrazují se pouze relevantní repositáře, <a href="%s"
|
||||
|
||||
[auth]
|
||||
create_new_account=Registrovat účet
|
||||
already_have_account=Již máte účet?
|
||||
sign_in_now=Přihlásit se!
|
||||
disable_register_prompt=Registrace jsou vypnuty. Prosíme, kontaktujte správce systému.
|
||||
disable_register_mail=E-mailové potvrzení o registraci je zakázané.
|
||||
manual_activation_only=Pro dokončení aktivace kontaktujte správce webu.
|
||||
@ -388,6 +403,8 @@ remember_me=Pamatovat si toto zařízení
|
||||
remember_me.compromised=Přihlašovací token již není platný, což může znamenat napadení účtu. Zkontrolujte prosím svůj účet pro neobvyklé aktivity.
|
||||
forgot_password_title=Zapomenuté heslo
|
||||
forgot_password=Zapomenuté heslo?
|
||||
need_account=Potřebujete účet?
|
||||
sign_up_now=Zaregistruj se nyní.
|
||||
sign_up_successful=Účet byl úspěšně vytvořen. Vítejte!
|
||||
confirmation_mail_sent_prompt_ex=Nový potvrzovací e-mail byl odeslán na <b>%s</b>. Zkontrolujte prosím svou doručenou poštu během následujících %s a dokončete proces registrace. Pokud je Vaše registrační e-mailová adresa nesprávná, můžete se znovu přihlásit a změnit ji.
|
||||
must_change_password=Aktualizujte své heslo
|
||||
@ -446,8 +463,11 @@ authorize_title=Autorizovat „%s“ pro přístup k vašemu účtu?
|
||||
authorization_failed=Autorizace selhala
|
||||
authorization_failed_desc=Autorizace selhala, protože jsme detekovali neplatný požadavek. Kontaktujte prosím správce aplikace, kterou jste se pokoušeli autorizovat.
|
||||
sspi_auth_failed=SSPI autentizace selhala
|
||||
password_pwned=Heslo, které jste zvolili, je na <a target="_blank" rel="noopener noreferrer" href="%s">seznamu odcizených hesel</a> dříve odhalených při narušení veřejných dat. Zkuste to prosím znovu s jiným heslem a zvažte změnu tohoto hesla i jinde.
|
||||
password_pwned_err=Nelze dokončit požadavek na HaveIBeenPwned
|
||||
last_admin=Nelze odstranit posledního správce. Musí existovat alespoň jeden správce.
|
||||
signin_passkey=Přihlásit se pomocí přístupového klíče
|
||||
back_to_sign_in=Zpět na přihlášení
|
||||
|
||||
[mail]
|
||||
view_it_on=Zobrazit na %s
|
||||
@ -464,6 +484,7 @@ activate_email=Ověřte vaši e-mailovou adresu
|
||||
activate_email.title=%s, prosím ověřte vaši e-mailovou adresu
|
||||
activate_email.text=Pro aktivaci vašeho účtu do <b>%s</b> klikněte na následující odkaz:
|
||||
|
||||
register_notify=Vítejte v %s
|
||||
register_notify.title=%[1]s vítejte v %[2]s
|
||||
register_notify.text_1=toto je váš potvrzovací e-mail pro %s!
|
||||
register_notify.text_2=Nyní se můžete přihlásit přes uživatelské jméno: %s.
|
||||
@ -565,6 +586,8 @@ lang_select_error=Vyberte jazyk ze seznamu.
|
||||
|
||||
username_been_taken=Uživatelské jméno je již obsazeno.
|
||||
username_change_not_local_user=Uživatelé, kteří jsou ověřováni jinak než lokálně, si nemohou změnit uživatelské jméno.
|
||||
change_username_disabled=Změna uživatelského jména je zakázána.
|
||||
change_full_name_disabled=Změna celého jména je zakázána.
|
||||
username_has_not_been_changed=Uživatelské jméno nebylo změněno
|
||||
repo_name_been_taken=Název repozitáře je již použit.
|
||||
repository_force_private=Vynucené soukromí je povoleno: soukromé repozitáře nelze zveřejnit.
|
||||
@ -614,6 +637,7 @@ org_still_own_repo=Organizace stále vlastní jeden nebo více repozitářů. Ne
|
||||
org_still_own_packages=Organizace stále vlastní jeden nebo více balíčků. Nejdříve je smažte.
|
||||
|
||||
target_branch_not_exist=Cílová větev neexistuje.
|
||||
target_ref_not_exist=Cílové reference neexistuje %s
|
||||
|
||||
admin_cannot_delete_self=Nemůžete se smazat, dokud jste správce. Nejdříve prosím odeberte svá administrátorská oprávnění.
|
||||
|
||||
@ -689,6 +713,8 @@ public_profile=Veřejný profil
|
||||
biography_placeholder=Řekněte nám něco o sobě! (Můžete použít Markdown)
|
||||
location_placeholder=Sdílejte svou přibližnou polohu s ostatními
|
||||
profile_desc=Nastavte, jak bude váš profil zobrazen ostatním uživatelům. Vaše hlavní e-mailová adresa bude použita pro oznámení, obnovení hesla a operace Git.
|
||||
password_username_disabled=Nemáte oprávnění měnit jejich uživatelské jméno. Pro více informací kontaktujte svého administrátora.
|
||||
password_full_name_disabled=Nemáte oprávnění měnit jejich celé jméno. Pro více informací kontaktujte správce webu.
|
||||
full_name=Celé jméno
|
||||
website=Web
|
||||
location=Místo
|
||||
@ -899,6 +925,7 @@ create_oauth2_application_success=Úspěšně jste vytvořili novou OAuth2 aplik
|
||||
update_oauth2_application_success=Úspěšně jste aktualizovali OAuth2 aplikaci.
|
||||
oauth2_application_name=Název aplikace
|
||||
oauth2_confidential_client=Důvěrný klient. Vyberte aplikace, které zachovávají důvěrnosti v utajení, jako jsou webové aplikace. Nevybírejte pro nativní aplikace včetně stolních a mobilních aplikací.
|
||||
oauth2_skip_secondary_authorization=Přeskočit autorizaci pro veřejné klienty po udělení přístupu. <strong>Může představovat bezpečnostní riziko.</strong>
|
||||
oauth2_redirect_uris=Přesměrování URI. Použijte nový řádek pro každou URI.
|
||||
save_application=Uložit
|
||||
oauth2_client_id=ID klienta
|
||||
@ -918,21 +945,26 @@ revoke_oauth2_grant=Zrušit přístup
|
||||
revoke_oauth2_grant_description=Zrušením přístupu této aplikaci třetí strany ji zabráníte v přístupu k vašim datům. Jste si jisti?
|
||||
revoke_oauth2_grant_success=Přístup byl úspěšně zrušen.
|
||||
|
||||
twofa_desc=Chcete-li svůj účet ochránit před krádeží hesla, můžete použít chytrý telefon nebo jiné zařízení pro příjem jednorázových časových hesel („TOTP“).
|
||||
twofa_recovery_tip=Pokud ztratíte své zařízení, budete moci použít jednorázový obnovovací klíč k získání přístupu k vašemu účtu.
|
||||
twofa_is_enrolled=Váš účet aktuálně <strong>používá</strong> dvoufaktorové ověřování.
|
||||
twofa_not_enrolled=Váš účet aktuálně nepoužívá dvoufaktorové ověřování.
|
||||
twofa_disable=Zakázat dvoufaktorové ověřování
|
||||
twofa_scratch_token_regenerate=Obnovit jednorázový obnovovací klíč
|
||||
twofa_scratch_token_regenerated=Váš jednorázový obnovovací klíč je nyní %s. Uložte jej na bezpečném místě, protože se znovu nezobrazí.
|
||||
twofa_enroll=Povolit dvoufaktorové ověřování
|
||||
twofa_disable_note=Dvoufaktorové ověřování můžete zakázat, když bude potřeba.
|
||||
twofa_disable_desc=Zakážete-li dvoufaktorové ověřování, bude váš účet méně zabezpečený. Pokračovat?
|
||||
regenerate_scratch_token_desc=Jestli jste někam založili váš záložní klíč nebo jste jej již použili k přihlášení, můžete jej resetovat zde.
|
||||
twofa_disabled=Dvoufaktorové ověřování bylo zakázáno.
|
||||
scan_this_image=Naskenujte tento obrázek s vaší ověřovací aplikací:
|
||||
or_enter_secret=Nebo zadejte tajný kód: %s
|
||||
then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci:
|
||||
passcode_invalid=Přístupový kód není platný. Zkuste to znovu.
|
||||
twofa_enrolled=Váš účet byl úspěšně zaregistrován. Uložte si jednorázový obnovovací klíč (%s) na bezpečném místě, protože se již nebude zobrazovat.
|
||||
twofa_failed_get_secret=Nepodařilo se získat tajemství.
|
||||
|
||||
webauthn_desc=Bezpečnostní klíče jsou hardwarová zařízení obsahující kryptografické klíče. Mohou být použity pro dvoufaktorové ověřování. Bezpečnostní klíče musí podporovat <a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a> standard.
|
||||
webauthn_register_key=Přidat bezpečnostní klíč
|
||||
webauthn_nickname=Přezdívka
|
||||
webauthn_delete_key=Odstranit bezpečnostní klíč
|
||||
@ -1009,7 +1041,6 @@ generate_repo=Generovat repozitář
|
||||
generate_from=Generovat z
|
||||
repo_desc=Popis
|
||||
repo_desc_helper=Zadejte krátký popis (volitelné)
|
||||
repo_lang=Jazyk
|
||||
repo_gitignore_helper=Vyberte šablony .gitignore.
|
||||
repo_gitignore_helper_desc=Vyberte soubory, které nechcete sledovat ze seznamu šablon pro běžné jazyky. Typické artefakty generované nástroji pro sestavení každého jazyka jsou ve výchozím stavu součástí .gitignore.
|
||||
issue_labels=Štítky úkolů
|
||||
@ -1017,6 +1048,7 @@ issue_labels_helper=Vyberte sadu štítků úkolů.
|
||||
license=Licence
|
||||
license_helper=Vyberte licenční soubor.
|
||||
license_helper_desc=Licence řídí, co ostatní mohou a nemohou dělat s vaším kódem. Nejste si jisti, která je pro váš projekt správná? Podívejte se na <a target="_blank" rel="noopener noreferrer" href="%s">Zvolte licenci</a>
|
||||
multiple_licenses=Více licencí
|
||||
object_format=Formát objektu
|
||||
object_format_helper=Objektový formát repozitáře. Nelze později změnit. SHA1 je nejvíce kompatibilní.
|
||||
readme=README
|
||||
@ -1077,7 +1109,9 @@ tree_path_not_found_branch=Cesta %[1]s ve větvi %[2]s neexistuje
|
||||
tree_path_not_found_tag=Cesta %[1]s ve značce %[2]s neexistuje
|
||||
|
||||
transfer.accept=Přijmout převod
|
||||
transfer.accept_desc=Převést do „%s“
|
||||
transfer.reject=Odmítnout převod
|
||||
transfer.reject_desc=Zrušit převod do „%s“
|
||||
transfer.no_permission_to_accept=Nemáte oprávnění k přijetí tohoto převodu.
|
||||
transfer.no_permission_to_reject=Nemáte oprávnění k odmítnutí tohoto převodu.
|
||||
|
||||
@ -1152,6 +1186,11 @@ migrate.gogs.description=Migrovat data z notabug.com nebo jiných Gogs instancí
|
||||
migrate.onedev.description=Migrovat data z code.onedev.io nebo jiných OneDev instancí.
|
||||
migrate.codebase.description=Migrovat data z codebasehq.com.
|
||||
migrate.gitbucket.description=Migrovat data z GitBucket instancí.
|
||||
migrate.codecommit.description=Přenést data z AWS CodeCommit.
|
||||
migrate.codecommit.aws_access_key_id=AWS Access Key ID
|
||||
migrate.codecommit.aws_secret_access_key=AWS Secret Access Key
|
||||
migrate.codecommit.https_git_credentials_username=HTTPS Git uživatelské jméno
|
||||
migrate.codecommit.https_git_credentials_password=HTTPS Git heslo
|
||||
migrate.migrating_git=Migrování data gitu
|
||||
migrate.migrating_topics=Migrování témat
|
||||
migrate.migrating_milestones=Migrování milnků
|
||||
@ -1212,6 +1251,7 @@ releases=Vydání
|
||||
tag=Značka
|
||||
released_this=vydal/a toto
|
||||
tagged_this=označil/a
|
||||
file.title=%s v %s
|
||||
file_raw=Surový
|
||||
file_history=Historie
|
||||
file_view_source=Zobrazit zdroj
|
||||
@ -1228,6 +1268,7 @@ ambiguous_runes_header=`Tento soubor obsahuje nejednoznačné znaky Unicode`
|
||||
ambiguous_runes_description=`Tento soubor obsahuje znaky Unicode, které mohou být zaměněny s jinými znaky. Pokud si myslíte, že je to záměrné, můžete toto varování bezpečně ignorovat. Použijte tlačítko Escape sekvence k jejich zobrazení.`
|
||||
invisible_runes_line=`Tento řádek má neviditelné znaky Unicode`
|
||||
ambiguous_runes_line=`Tento řádek má nejednoznačné znaky Unicode`
|
||||
ambiguous_character=`%[1]c [U+%04[1]X] je zaměnitelný s %[2]c [U+%04[2]X]`
|
||||
|
||||
escape_control_characters=Escape sekvence
|
||||
unescape_control_characters=Bez escape sekvencí
|
||||
@ -1416,8 +1457,6 @@ issues.new.no_items=Žádné položky
|
||||
issues.new.milestone=Milník
|
||||
issues.new.no_milestone=Bez milníku
|
||||
issues.new.clear_milestone=Smazat milník
|
||||
issues.new.open_milestone=Otevřít milník
|
||||
issues.new.closed_milestone=Zavřené milníky
|
||||
issues.new.assignees=Zpracovatelé
|
||||
issues.new.clear_assignees=Smazat zpracovatele
|
||||
issues.new.no_assignees=Bez zpracovatelů
|
||||
@ -1450,6 +1489,7 @@ issues.remove_labels=odstranil/a %s štítky %s
|
||||
issues.add_remove_labels=přidáno %s a odebráno %s štítků %s
|
||||
issues.add_milestone_at=`přidal/a toto do milníku <b>%s</b> %s`
|
||||
issues.add_project_at=`přidal/a toto do projektu <b>%s</b> %s`
|
||||
issues.move_to_column_of_project=`přesunul/a toto do %s v %s na %s`
|
||||
issues.change_milestone_at=`upravil/a milník z <b>%s</b> na <b>%s</b> %s`
|
||||
issues.change_project_at=`upravil/a projekt z <b>%s</b> na <b>%s</b> %s`
|
||||
issues.remove_milestone_at=`odstranil/a toto z milníku <b>%s</b> %s`
|
||||
@ -1624,27 +1664,20 @@ issues.comment_on_locked=Nemůžete komentovat uzamčený úkol.
|
||||
issues.delete=Smazat
|
||||
issues.delete.title=Smazat tento úkol?
|
||||
issues.delete.text=Opravdu chcete tento úkol smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.)
|
||||
|
||||
issues.tracker=Sledování času
|
||||
issues.start_tracking_short=Spustit časovač
|
||||
issues.start_tracking=Spustit sledování času
|
||||
issues.start_tracking_history=`započal/a práci %s`
|
||||
|
||||
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu
|
||||
issues.tracking_already_started=`Již jste spustili sledování času na <a href="%s">jiném úkolu</a>!`
|
||||
issues.stop_tracking=Zastavit časovač
|
||||
issues.stop_tracking_history=`ukončil/a práci %s`
|
||||
issues.cancel_tracking=Zahodit
|
||||
issues.cancel_tracking_history=`zrušil/a sledování času %s`
|
||||
issues.add_time=Přidat čas ručně
|
||||
issues.del_time=Odstranit tento časový záznam
|
||||
issues.add_time_short=Přidat čas
|
||||
issues.add_time_cancel=Zrušit
|
||||
issues.add_time_history=`přidal/a strávený čas %s`
|
||||
issues.del_time_history=`odstranil/a strávený čas %s`
|
||||
issues.add_time_hours=Hodiny
|
||||
issues.add_time_minutes=Minuty
|
||||
issues.add_time_sum_to_small=Čas nebyl zadán.
|
||||
issues.time_spent_total=Celkový strávený čas
|
||||
issues.time_spent_from_all_authors=`Celkový strávený čas: %s`
|
||||
|
||||
issues.due_date=Termín dokončení
|
||||
issues.invalid_due_date_format=Termín dokončení musí být ve formátu 'rrrr-mm-dd'.
|
||||
issues.error_modifying_due_date=Změna termínu dokončení selhala.
|
||||
@ -1698,6 +1731,7 @@ issues.dependency.add_error_dep_not_same_repo=Oba úkoly musí být ve stejném
|
||||
issues.review.self.approval=Nemůžete schválit svůj pull request.
|
||||
issues.review.self.rejection=Nemůžete požadovat změny ve svém vlastním pull requestu.
|
||||
issues.review.approve=schválil tyto změny %s
|
||||
issues.review.comment=posoudil/a %s
|
||||
issues.review.dismissed=zamítl/a posouzení od %s %s
|
||||
issues.review.dismissed_label=Zamítnuto
|
||||
issues.review.left_comment=zanechal komentář
|
||||
@ -1705,7 +1739,7 @@ issues.review.content.empty=Je potřeba zanechat poznámku s uvedením požadova
|
||||
issues.review.reject=požadované změny %s
|
||||
issues.review.wait=byl požádán o posouzení %s
|
||||
issues.review.add_review_request=vyžádal posouzení od %s %s
|
||||
issues.review.remove_review_request=odstranil žádost o posouzení na %s %s
|
||||
issues.review.remove_review_request=odstranil/a žádost o posouzení na %s %s
|
||||
issues.review.remove_review_request_self=odmítl posoudit %s
|
||||
issues.review.pending=Čekající
|
||||
issues.review.pending.tooltip=Tento komentář není momentálně viditelný pro ostatní uživatele. Chcete-li odeslat Vaše čekající komentáře, vyberte „%s“ → „%s/%s/%s“ v horní části stránky.
|
||||
@ -1723,6 +1757,11 @@ issues.review.resolve_conversation=Vyřešit konverzaci
|
||||
issues.review.un_resolve_conversation=Nevyřešit konverzaci
|
||||
issues.review.resolved_by=označil tuto konverzaci jako vyřešenou
|
||||
issues.review.commented=Okomentovat
|
||||
issues.review.official=Schváleno
|
||||
issues.review.requested=Čeká na posouzení
|
||||
issues.review.rejected=Požadovány změny
|
||||
issues.review.stale=Aktualizováno od schválení
|
||||
issues.review.unofficial=Nezapočtené schválení
|
||||
issues.assignee.error=Ne všichni zpracovatelé byli přidáni z důvodu neočekávané chyby.
|
||||
issues.reference_issue.body=Tělo zprávy
|
||||
issues.content_history.deleted=vymazáno
|
||||
@ -1739,6 +1778,7 @@ compare.compare_head=porovnat
|
||||
pulls.desc=Povolit pull requesty a posuzování kódu.
|
||||
pulls.new=Nový pull request
|
||||
pulls.new.blocked_user=Nemůžete vytvořit pull request, protože jste zablokování vlastníkem repozitáře.
|
||||
pulls.new.must_collaborator=Musíte být spolupracovníkem pro vytvoření pull requestu.
|
||||
pulls.edit.already_changed=Nelze uložit změny v pull requestu. Zdá se, že obsah byl již změněn jiným uživatelem. Aktualizujte stránku a zkuste znovu komentář upravit, abyste se vyhnuli přepsání jejich změn
|
||||
pulls.view=Zobrazit pull request
|
||||
pulls.compare_changes=Nový pull request
|
||||
@ -1795,6 +1835,8 @@ pulls.is_empty=Změny na této větvi jsou již na cílové větvi. Toto bude pr
|
||||
pulls.required_status_check_failed=Některé požadované kontroly nebyly úspěšné.
|
||||
pulls.required_status_check_missing=Některé požadované kontroly chybí.
|
||||
pulls.required_status_check_administrator=Jako administrátor stále můžete sloučit tento pull request.
|
||||
pulls.blocked_by_approvals=Tento pull request ještě nemá dostatek schválení. Uděleno %d z %d udělených oficiálních schválení.
|
||||
pulls.blocked_by_approvals_whitelisted=Tento pull request nemá ještě nemá dostatek požadovaných schválení. %d z %d schválení udělených uživateli nebo týmy na seznamu povolených.
|
||||
pulls.blocked_by_rejection=Tento pull request obsahuje změny požadované oficiálním posuzovatelem.
|
||||
pulls.blocked_by_official_review_requests=Tento pull request obsahuje oficiální žádosti o posouzení.
|
||||
pulls.blocked_by_outdated_branch=Tento pull request je zablokován, protože je zastaralý.
|
||||
@ -1836,7 +1878,9 @@ pulls.unrelated_histories=Sloučení selhalo: Hlavní a základní revize nesdí
|
||||
pulls.merge_out_of_date=Sloučení selhalo: Základ byl aktualizován při generování sloučení. Tip: Zkuste to znovu.
|
||||
pulls.head_out_of_date=Sloučení selhalo: Hlavní revize byla aktualizován při generování sloučení. Tip: Zkuste to znovu.
|
||||
pulls.has_merged=Chyba: Pull request byl sloučen, nelze znovu sloučit nebo změnit cílovou větev.
|
||||
pulls.push_rejected=Nahrání se nezdařilo: Nahrání bylo zamítnuto. Zkontrolujte háčky Gitu pro tento repozitář.
|
||||
pulls.push_rejected_summary=Úplná zpráva o odmítnutí
|
||||
pulls.push_rejected_no_message=Nahrání se nezdařilo: Nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva. Zkontrolujte háčky gitu pro tento repozitář
|
||||
pulls.open_unmerged_pull_exists=`Nemůžete provést operaci znovuotevření protože je tu čekající pull request (#%d) s identickými vlastnostmi.`
|
||||
pulls.status_checking=Některé kontroly jsou nedořešeny
|
||||
pulls.status_checks_success=Všechny kontroly byly úspěšné
|
||||
@ -1860,6 +1904,7 @@ pulls.cmd_instruction_checkout_title=Checkout
|
||||
pulls.cmd_instruction_checkout_desc=Z vašeho repositáře projektu se podívejte na novou větev a vyzkoušejte změny.
|
||||
pulls.cmd_instruction_merge_title=Sloučit
|
||||
pulls.cmd_instruction_merge_desc=Slučte změny a aktualizujte je na Gitea.
|
||||
pulls.cmd_instruction_merge_warning=Varování: Tato operace nemůže sloučit požadavek na natažení, protože „autodetekce manuálních sloučení“ nebyla povolena
|
||||
pulls.clear_merge_message=Vymazat zprávu o sloučení
|
||||
pulls.clear_merge_message_hint=Vymazání zprávy o sloučení odstraní pouze obsah zprávy a ponechá generované přídavky gitu jako "Co-AuthoreBy …".
|
||||
|
||||
@ -1881,6 +1926,7 @@ pulls.delete.text=Opravdu chcete tento pull request smazat? (Tím se trvale odst
|
||||
pulls.recently_pushed_new_branches=Nahráli jste větev <strong>%[1]s</strong> %[2]s
|
||||
|
||||
pull.deleted_branch=(odstraněno):%s
|
||||
pull.agit_documentation=Prohlédněte si dokumentaci o AGit
|
||||
|
||||
comments.edit.already_changed=Nelze uložit změny v komentáři. Zdá se, že obsah byl již změněn jiným uživatelem. Aktualizujte stránku a zkuste znovu komentář upravit, abyste se vyhnuli přepsání jejich změn
|
||||
|
||||
@ -1891,6 +1937,7 @@ milestones.no_due_date=Bez lhůty dokončení
|
||||
milestones.open=Otevřít
|
||||
milestones.close=Zavřít
|
||||
milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok.
|
||||
milestones.completeness=<strong>%d%%</strong> Dokončeno
|
||||
milestones.create=Vytvořit milník
|
||||
milestones.title=Název
|
||||
milestones.desc=Popis
|
||||
@ -2113,6 +2160,7 @@ settings.pulls.default_delete_branch_after_merge=Ve výchozím nastavení mazat
|
||||
settings.pulls.default_allow_edits_from_maintainers=Ve výchozím nastavení povolit úpravy od správců
|
||||
settings.releases_desc=Povolit vydání v repozitáři
|
||||
settings.packages_desc=Povolit registr balíčků repozitáře
|
||||
settings.projects_desc=Povolit projekty
|
||||
settings.projects_mode_desc=Režim projektů (druhy projektů k zobrazení)
|
||||
settings.projects_mode_repo=Pouze projekty repozitáře
|
||||
settings.projects_mode_owner=Pouze projekty uživatele nebo organizace
|
||||
@ -2152,6 +2200,7 @@ settings.transfer_in_progress=V současné době probíhá převod. Zrušte jej,
|
||||
settings.transfer_notices_1=- Ztratíte přístup k repozitáři, pokud jej převedete na uživatele.
|
||||
settings.transfer_notices_2=- Zůstane vám přístup k repozitáři, pokud jej převedete na organizaci kterou (spolu)vlastníte.
|
||||
settings.transfer_notices_3=- Pokud je repozitář soukromý a je předán jednotlivému uživateli, tato akce se ujistí, že uživatel má alespoň oprávnění ke čtení (a v případě potřeby změní oprávnění).
|
||||
settings.transfer_notices_4=- Pokud repozitář patří organizaci a převádíte ho na jinou organizaci nebo jednotlivce, ztratíte odkazy mezi problémy repositáře a projektovou tabulí organizace.
|
||||
settings.transfer_owner=Nový vlastník
|
||||
settings.transfer_perform=Provést převod
|
||||
settings.transfer_started=Tento repozitář byl označen pro převod a čeká na potvrzení od „%s“
|
||||
@ -2251,6 +2300,7 @@ settings.event_wiki_desc=Wiki stránka vytvořena, přejmenována nebo smazána.
|
||||
settings.event_release=Vydání
|
||||
settings.event_release_desc=Vydání v tomto repozitáři bylo publikováno, aktualizováno nebo smazáno.
|
||||
settings.event_push=Nahrát
|
||||
settings.event_force_push=Vynucené nahrání
|
||||
settings.event_push_desc=Nahrání pomocí Gitu do repozitáře.
|
||||
settings.event_repository=Repozitář
|
||||
settings.event_repository_desc=Repozitář vytvořen nebo smazán.
|
||||
@ -2287,6 +2337,7 @@ settings.event_pull_request_merge=Sloučení pull requestu
|
||||
settings.event_package=Balíček
|
||||
settings.event_package_desc=Balíček vytvořen nebo odstraněn v repozitáři.
|
||||
settings.branch_filter=Filtr větví
|
||||
settings.branch_filter_desc=Povolené větve pro události nahrání, vytvoření větve a smazání větve jsou určeny pomocí zástupného vzoru. Pokud je prázdný nebo <code>*</code>, všechny události jsou ohlášeny. Podívejte se na dokumentaci syntaxe na <a href="%[1]s">github.com/gobwas/glob</a>. Příklady: <code>master</code>, <code>{master,release*}</code>.
|
||||
settings.authorization_header=Autorizační hlavička
|
||||
settings.authorization_header_desc=Pokud vyplněno, bude připojeno k požadavkům jako autorizační hlavička. Příklady: %s.
|
||||
settings.active=Aktivní
|
||||
@ -2337,22 +2388,48 @@ settings.protected_branch.save_rule=Uložit pravidlo
|
||||
settings.protected_branch.delete_rule=Odstranit pravidlo
|
||||
settings.protected_branch_can_push=Povolit nahrání?
|
||||
settings.protected_branch_can_push_yes=Můžete nahrávat
|
||||
settings.protected_branch_can_push_no=Nemůžete nahrávat
|
||||
settings.branch_protection=Pravidla ochrany větve pro větev „<b>%s</b>“
|
||||
settings.protect_this_branch=Povolit ochranu větví
|
||||
settings.protect_this_branch_desc=Zabraňuje smazání a omezuje gitu nahrávání a slučování do větve.
|
||||
settings.protect_disable_push=Zakázat nahrávání
|
||||
settings.protect_disable_push_desc=Žádné nahrávání do této větve nebude povoleno.
|
||||
settings.protect_disable_force_push=Zakázat vynucené nahrání
|
||||
settings.protect_disable_force_push_desc=Do této větve nebude povoleno žádné vynucené nahrání.
|
||||
settings.protect_enable_push=Povolit nahrávání
|
||||
settings.protect_enable_push_desc=Každý, kdo má přístup k zápisu, bude moci nahrávat do této větve (ale ne vynucená nahrávání).
|
||||
settings.protect_enable_force_push_all=Povolit vynucené nahrání
|
||||
settings.protect_enable_force_push_all_desc=Každý, kdo má přístup k nahrávání, bude moci vynutit nahrání do této větve.
|
||||
settings.protect_enable_force_push_allowlist=Povolit vynucené nahrání jen vyjmenovaným
|
||||
settings.protect_enable_force_push_allowlist_desc=Pouze uživatelé nebo týmy s přístupem k nahrávání budou moci vynutit nahrání do této větve.
|
||||
settings.protect_enable_merge=Povolit sloučení
|
||||
settings.protect_enable_merge_desc=Každému, kdo má přístup k zápisu, bude povoleno sloučit pull requesty do této větve.
|
||||
settings.protect_whitelist_committers=Povolit nahrání jen vyjmenovaným
|
||||
settings.protect_whitelist_committers_desc=Pouze povolení uživatelé budou moci nahrávat do této větve (ale ne vynucení nahrávání).
|
||||
settings.protect_whitelist_deploy_keys=Povolit nahrání klíčům pro nasazení s přístupem pro zápis.
|
||||
settings.protect_whitelist_users=Povolení uživatelé pro nahrávání:
|
||||
settings.protect_whitelist_teams=Povolené týmy pro nahrávání:
|
||||
settings.protect_force_push_allowlist_users=Povolení uživatelé pro vynucené nahrávání:
|
||||
settings.protect_force_push_allowlist_teams=Povolené týmy pro vynucené nahrávání:
|
||||
settings.protect_force_push_allowlist_deploy_keys=Povolte klíče pro nasazení s přístupem pro nahrávání, aby mohly provádět vynucené nahrávání.
|
||||
settings.protect_merge_whitelist_committers=Povolit vyjmenovaným slučování
|
||||
settings.protect_merge_whitelist_committers_desc=Povolit pouze vyjmenovaným uživatelům nebo týmům slučovat pull requesty do této větve.
|
||||
settings.protect_merge_whitelist_users=Povolení uživatelé pro slučování:
|
||||
settings.protect_merge_whitelist_teams=Povolené týmy pro slučování:
|
||||
settings.protect_check_status_contexts=Povolit kontrolu stavu
|
||||
settings.protect_status_check_patterns=Vzorce kontroly stavu:
|
||||
settings.protect_status_check_patterns_desc=Zadejte vzory pro určení, které kontroly stavu musí projít před sloučením větví do větve, která odpovídá tomuto pravidlu. Každý řádek určuje vzor. Vzory nemohou být prázdné.
|
||||
settings.protect_check_status_contexts_desc=Požadovat kontrolu stavu před sloučením. Pokud je povoleno, revize musí být nejprve nahrány do jiné větve, projít kontrolou stavu, a následné sloučeny nebo přímo nahrány do větve, která vyhovuje tomuto pravidlu. Pokud nejsou vybrány žádné kontexty, musí být poslední potvrzení úspěšné bez ohledu na kontext.
|
||||
settings.protect_check_status_contexts_list=Kontroly stavu pro tento repozitář zjištěné během posledního týdne
|
||||
settings.protect_status_check_matched=Odpovídá
|
||||
settings.protect_invalid_status_check_pattern=Neplatný vzor kontroly stavu: „%s“.
|
||||
settings.protect_no_valid_status_check_patterns=Žádné platné vzory kontroly stavu.
|
||||
settings.protect_required_approvals=Požadovaná schválení:
|
||||
settings.protect_required_approvals_desc=Umožňuje sloučit pouze pull requestů s dostatečným počtem požadovaných schválení. Požadovaná schválení jsou buď od uživatelů nebo týmů, které jsou na seznamu povolených, nebo od kohokoli s právem zápisu.
|
||||
settings.protect_approvals_whitelist_enabled=Omezit schválení na povolené uživatele nebo týmy
|
||||
settings.protect_approvals_whitelist_enabled_desc=Do požadovaných schválení se započítají pouze posouzení od povolených uživatelů nebo týmů. Bez seznamu povolených se započítává schválení od kohokoli s právem zápisu.
|
||||
settings.protect_approvals_whitelist_users=Povolení posuzovatelé:
|
||||
settings.protect_approvals_whitelist_teams=Povolené týmy pro posuzování:
|
||||
settings.dismiss_stale_approvals=Odmítnout nekvalitní schválení
|
||||
settings.dismiss_stale_approvals_desc=Pokud budou do větve nahrány nové revize, které mění obsah tohoto pull requestu, všechna stará schválení budou zamítnuta.
|
||||
settings.ignore_stale_approvals=Ignorovat zastaralá schválení
|
||||
@ -2360,14 +2437,18 @@ settings.ignore_stale_approvals_desc=Nezapočítávejte schválení, která byla
|
||||
settings.require_signed_commits=Vyžadovat podepsané revize
|
||||
settings.require_signed_commits_desc=Odmítnout nahrání do této větve pokud nejsou podepsaná nebo jsou neověřitelná.
|
||||
settings.protect_branch_name_pattern=Vzor jména chráněných větví
|
||||
settings.protect_branch_name_pattern_desc=Vzory jmen chráněných větví. Pro vzorovou syntaxi viz <a href="%s">dokumentace</a>. Příklady: main, release/**
|
||||
settings.protect_patterns=Vzory
|
||||
settings.protect_protected_file_patterns=Vzory chráněných souborů (oddělené středníkem „;“):
|
||||
settings.protect_protected_file_patterns_desc=Chráněné soubory, které nemají povoleno být měněny přímo, i když uživatel má právo přidávat, upravovat nebo mazat soubory v této větvi. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na <a href='%[1]s'>%[2]s</a> dokumentaci pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.protect_unprotected_file_patterns=Vzory nechráněných souborů (oddělené středníkem „;“):
|
||||
settings.protect_unprotected_file_patterns_desc=Nechráněné soubory, které je možné měnit přímo, pokud má uživatel právo zápisu, čímž se obejde omezení push. Více vzorů lze oddělit pomocí středníku („;“). Podívejte se na <a href='%[1]s'>%[2]s</a> dokumentaci pro syntaxi vzoru. Příklady: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.add_protected_branch=Zapnout ochranu
|
||||
settings.delete_protected_branch=Vypnout ochranu
|
||||
settings.update_protect_branch_success=Ochrana větví pro větev „%s“ byla aktualizována.
|
||||
settings.remove_protected_branch_success=Ochrana větví pro větev „%s“ byla zakázána.
|
||||
settings.remove_protected_branch_failed=Odstranění ochranného pravidla větve „%s“ se nezdařilo.
|
||||
settings.protected_branch_deletion=Odstranit ochranu věteve
|
||||
settings.protected_branch_deletion_desc=Zakázání ochrany větví umožní uživatelům s právem zápisu nahrávat do této větve. Pokračovat?
|
||||
settings.block_rejected_reviews=Blokovat sloučení při zamítavých posouzeních
|
||||
settings.block_rejected_reviews_desc=Slučování nebude možné, pokud o změny požádají oficiální posuzovatelé, i když je k dispozici dostatek schválení.
|
||||
@ -2375,8 +2456,11 @@ settings.block_on_official_review_requests=Blokovat sloučení při oficiální
|
||||
settings.block_on_official_review_requests_desc=Slučování nebude možné, pokud mají oficiální požadavek na posouzení, i když mají k dispozici dostatek schválení.
|
||||
settings.block_outdated_branch=Blokovat sloučení, pokud je pull request zastaralý
|
||||
settings.block_outdated_branch_desc=Slučování nebude možné, pokud je hlavní větev za základní větví.
|
||||
settings.block_admin_merge_override=Pravidla pro ochranu větví se vztahují i na administrátory
|
||||
settings.block_admin_merge_override_desc=Pravidla pro ochranu větví se vztahují i na administrátory a nesmějí je obcházet.
|
||||
settings.default_branch_desc=Vybrat výchozí větev repozitáře pro pull requesty a revize kódu:
|
||||
settings.merge_style_desc=Sloučit styly
|
||||
settings.default_merge_style_desc=Výchozí styl sloučení
|
||||
settings.choose_branch=Vyberte větev…
|
||||
settings.no_protected_branch=Nejsou tu žádné chráněné větve.
|
||||
settings.edit_protected_branch=Upravit
|
||||
@ -2392,12 +2476,25 @@ settings.tags.protection.allowed.teams=Povolené týmy
|
||||
settings.tags.protection.allowed.noone=Nikdo
|
||||
settings.tags.protection.create=Chránit značku
|
||||
settings.tags.protection.none=Neexistují žádné chráněné značky.
|
||||
settings.tags.protection.pattern.description=Můžete použít jediné jméno nebo vzor glob nebo regulární výraz, který bude odpovídat více značek. Přečtěte si více v <a target="_blank" rel="noopener" href="%s">průvodci chráněnými značkami</a>.
|
||||
settings.bot_token=Token pro robota
|
||||
settings.chat_id=ID chatu
|
||||
settings.thread_id=ID vlákna
|
||||
settings.matrix.homeserver_url=URL adresa Homeserveru
|
||||
settings.matrix.room_id=ID místnosti
|
||||
settings.matrix.message_type=Typ zprávy
|
||||
settings.visibility.private.button=Nastavit jako soukromé
|
||||
settings.visibility.private.text=Změna viditelnosti na soukromou nejen zviditelní repozitář pouze pro povolené členy, ale může odstranit vztah mezi ním a rozštěpením, sledujícími a oblíbeností.
|
||||
settings.visibility.private.bullet_title=<strong>Změna viditelnosti na soukromou způsobí:</strong>
|
||||
settings.visibility.private.bullet_one=Zviditelnit repozitář pouze pro povolené členy.
|
||||
settings.visibility.private.bullet_two=Může odstranit vztah mezi ním a <strong>rozštěpeními</strong>, <strong>sledujícími</strong>a <strong>oblíbeností</strong>.
|
||||
settings.visibility.public.button=Nastavit jako veřejné
|
||||
settings.visibility.public.text=Změna viditelnosti na veřejné učiní repozitář viditelným pro kohokoliv.
|
||||
settings.visibility.public.bullet_title=<strong>Změna viditelnosti na veřejnou způsobí:</strong>
|
||||
settings.visibility.public.bullet_one=Zviditelnit repozitář pro kohokoliv.
|
||||
settings.visibility.success=Viditelnost repozitáře se změnila.
|
||||
settings.visibility.error=Nastala chyba při pokusu o změnu viditelnosti repozitáře.
|
||||
settings.visibility.fork_error=Viditelnost rozštěpeného repozitáře nelze změnit.
|
||||
settings.archive.button=Archivovat repozitář
|
||||
settings.archive.header=Archivovat tento repozitář
|
||||
settings.archive.text=Archivace repozitáře způsobí, že bude zcela určen pouze pro čtení. Bude skryt z ovládacího panelu. Nikdo (ani vy!) nebude moci vytvářet nové revize ani otevírat nové úkoly nebo pull requesty.
|
||||
@ -2480,7 +2577,6 @@ diff.generated=vygenerováno
|
||||
diff.vendored=vendorováno
|
||||
diff.comment.add_line_comment=Přidat jednořádkový komentář
|
||||
diff.comment.placeholder=Zanechat komentář
|
||||
diff.comment.markdown_info=Je podporována úprava vzhledu pomocí markdown.
|
||||
diff.comment.add_single_comment=Přidat jeden komentář
|
||||
diff.comment.add_review_comment=Přidat komentář
|
||||
diff.comment.start_review=Začít posuzování
|
||||
@ -2594,6 +2690,7 @@ tag.create_success=Značka „%s“ byla vytvořena.
|
||||
|
||||
topic.manage_topics=Spravovat témata
|
||||
topic.done=Hotovo
|
||||
topic.count_prompt=Nelze vybrat více než 25 témat
|
||||
topic.format_prompt=Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a tečky („.“) a může být dlouhé až 35 znaků. Písmena musí být malá.
|
||||
|
||||
find_file.go_to_file=Přejít na soubor
|
||||
@ -2691,6 +2788,7 @@ teams.leave.detail=Opustit %s?
|
||||
teams.can_create_org_repo=Vytvořit repozitáře
|
||||
teams.can_create_org_repo_helper=Členové mohou vytvářet nové repozitáře v organizaci. Tvůrce získá přístup správce do nového repozitáře.
|
||||
teams.none_access=Bez přístupu
|
||||
teams.none_access_helper=Členové nemohou tuto jednotku prohlížet ani s ní provádět žádné jiné úkony. Pro veřejné repozitáře to nemá žádný vliv.
|
||||
teams.general_access=Obecný přístup
|
||||
teams.general_access_helper=O oprávnění členů bude rozhodnuto níže uvedenou tabulkou oprávnění.
|
||||
teams.read_access=Čtení
|
||||
@ -2743,6 +2841,7 @@ self_check=Samokontrola
|
||||
identity_access=Identita a přístup
|
||||
users=Uživatelské účty
|
||||
organizations=Organizace
|
||||
assets=Prostředky kódu
|
||||
repositories=Repozitáře
|
||||
hooks=Webové háčky
|
||||
integrations=Integrace
|
||||
@ -2758,6 +2857,7 @@ last_page=Poslední
|
||||
total=Celkem: %d
|
||||
settings=Nastavení správce
|
||||
|
||||
dashboard.new_version_hint=Gitea %s je nyní k dispozici, vy používáte %s. Další podrobnosti najdete na <a target="_blank" rel="noreferrer" href="%s">blogu</a>.
|
||||
dashboard.statistic=Souhrn
|
||||
dashboard.maintenance_operations=Operace údržby
|
||||
dashboard.system_status=Status systému
|
||||
@ -2800,6 +2900,7 @@ dashboard.reinit_missing_repos=Znovu inicializovat všechny chybějící repozit
|
||||
dashboard.sync_external_users=Synchronizovat externí uživatelská data
|
||||
dashboard.cleanup_hook_task_table=Vyčistit tabulku hook_task
|
||||
dashboard.cleanup_packages=Vyčistit prošlé balíčky
|
||||
dashboard.cleanup_actions=Vyčištění prostředků akcí, jejichž platnost vypršela
|
||||
dashboard.server_uptime=Doba provozu serveru
|
||||
dashboard.current_goroutine=Aktuální Goroutines
|
||||
dashboard.current_memory_usage=Aktuální využití paměti
|
||||
@ -2829,12 +2930,19 @@ dashboard.total_gc_time=Celková pauza GC
|
||||
dashboard.total_gc_pause=Celková pauza GC
|
||||
dashboard.last_gc_pause=Poslední pauza GC
|
||||
dashboard.gc_times=Časy GC
|
||||
dashboard.delete_old_actions=Odstranit všechny staré aktivity z databáze
|
||||
dashboard.delete_old_actions.started=Začalo odstraňování všech starých aktivit z databáze.
|
||||
dashboard.update_checker=Kontrola aktualizací
|
||||
dashboard.delete_old_system_notices=Odstranit všechna stará systémová upozornění z databáze
|
||||
dashboard.gc_lfs=Úklid LFS meta objektů
|
||||
dashboard.stop_zombie_tasks=Zastavit zombie úlohy akcí
|
||||
dashboard.stop_endless_tasks=Zastavit nekonečné úlohy akcí
|
||||
dashboard.cancel_abandoned_jobs=Zrušit opuštěné úlohy akcí
|
||||
dashboard.start_schedule_tasks=Spustit naplánované úlohy akcí
|
||||
dashboard.sync_branch.started=Synchronizace větví byla spuštěna
|
||||
dashboard.sync_tag.started=Synchronizace značek spuštěna
|
||||
dashboard.rebuild_issue_indexer=Znovu sestavit index úkolů
|
||||
dashboard.sync_repo_licenses=Synchronizovat licence repozitáře
|
||||
|
||||
users.user_manage_panel=Správa uživatelských účtů
|
||||
users.new_account=Vytvořit uživatelský účet
|
||||
@ -2906,6 +3014,10 @@ emails.not_updated=Aktualizace požadované e-mailové adresy se nezdařila: %v
|
||||
emails.duplicate_active=Tato e-mailová adresa je již aktivní pro jiného uživatele.
|
||||
emails.change_email_header=Aktualizovat vlastnosti e-mailu
|
||||
emails.change_email_text=Opravdu chcete aktualizovat tuto e-mailovou adresu?
|
||||
emails.delete=Odstranit e-mail
|
||||
emails.delete_desc=Opravdu chcete odstranit tuto e-mailovou adresu?
|
||||
emails.deletion_success=E-mailová adresa byla odstraněna.
|
||||
emails.delete_primary_email_error=Primární e-mail nelze odstranit.
|
||||
|
||||
orgs.org_manage_panel=Správa organizací
|
||||
orgs.name=Název
|
||||
@ -2938,10 +3050,12 @@ packages.size=Velikost
|
||||
packages.published=Publikováno
|
||||
|
||||
defaulthooks=Výchozí webové háčky
|
||||
defaulthooks.desc=Webové háčky automaticky vytvářejí HTTP POST dotazy na server při určitých Gitea událostech. Webové háčky definované zde jsou výchozí a budou zkopírovány do všech nových repozitářů. Přečtěte si více v <a target="_blank" rel="noopener" href=%s">průvodci webovými háčky</a>.
|
||||
defaulthooks.add_webhook=Přidat výchozí webový háček
|
||||
defaulthooks.update_webhook=Aktualizovat výchozí webový háček
|
||||
|
||||
systemhooks=Systémové webové háčky
|
||||
systemhooks.desc=Webové háčky automaticky vytvářejí HTTP POST dotazy na server při určitých Gitea událostech. Webové háčky definované zde budou vykonány na všech repozitářích systému, proto prosím zvažte jakékoli důsledky, které to může mít na výkon. Přečtěte si více v <a target="_blank" rel="noopener" href="%s">průvodci webovými háčky</a>.
|
||||
systemhooks.add_webhook=Přidat systémový webový háček
|
||||
systemhooks.update_webhook=Aktualizovat systémový webový háček
|
||||
|
||||
@ -3036,8 +3150,18 @@ auths.tips=Tipy
|
||||
auths.tips.oauth2.general=Ověřování OAuth2
|
||||
auths.tips.oauth2.general.tip=Při registraci nové OAuth2 autentizace by URL callbacku/přesměrování měla být:
|
||||
auths.tip.oauth2_provider=Poskytovatel OAuth2
|
||||
auths.tip.bitbucket=Vytvořte nového OAuth konzumenta na %s a přidejte oprávnění „Account“ - „Read“
|
||||
auths.tip.nextcloud=Zaregistrujte nového OAuth konzumenta na vaší instanci pomocí následujícího menu „Nastavení -> Zabezpečení -> OAuth 2.0 klient“
|
||||
auths.tip.dropbox=Vytvořte novou aplikaci na %s
|
||||
auths.tip.facebook=Registrujte novou aplikaci na %s a přidejte produkt „Facebook Login“
|
||||
auths.tip.github=Registrujte novou OAuth aplikaci na %s
|
||||
auths.tip.gitlab_new=Zaregistrujte novou aplikaci na %s
|
||||
auths.tip.google_plus=Získejte klientské pověření OAuth2 z Google API konzole na %s
|
||||
auths.tip.openid_connect=Použijte OpenID Connect URL pro objevování spojení „https://{server}/.well-known/openid-configuration“ k nastavení koncových bodů
|
||||
auths.tip.twitter=Jděte na %s, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená
|
||||
auths.tip.discord=Registrujte novou aplikaci na %s
|
||||
auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na %s
|
||||
auths.tip.yandex=Vytvořte novou aplikaci na %s. Vyberte následující oprávnění z „Yandex.Passport API“ sekce: „Přístup k e-mailové adrese“, „Přístup k uživatelskému avataru“ a „Přístup k uživatelskému jménu, jménu a příjmení, pohlaví“
|
||||
auths.tip.mastodon=Vložte vlastní URL instance pro mastodon, kterou se chcete autentizovat (nebo použijte výchozí)
|
||||
auths.edit=Upravit zdroj ověřování
|
||||
auths.activated=Tento zdroj ověřování je aktivován
|
||||
@ -3203,6 +3327,7 @@ monitor.next=Příští čas spuštění
|
||||
monitor.previous=Předešlý čas spuštění
|
||||
monitor.execute_times=Vykonání
|
||||
monitor.process=Spuštěné procesy
|
||||
monitor.stacktrace=Výpisy zásobníku
|
||||
monitor.processes_count=%d procesů
|
||||
monitor.download_diagnosis_report=Stáhnout diagnosttickou zprávu
|
||||
monitor.desc=Popis
|
||||
@ -3210,6 +3335,8 @@ monitor.start=Čas zahájení
|
||||
monitor.execute_time=Doba provádění
|
||||
monitor.last_execution_result=Výsledek
|
||||
monitor.process.cancel=Zrušit proces
|
||||
monitor.process.cancel_desc=Zrušení procesu může způsobit ztrátu dat
|
||||
monitor.process.cancel_notices=Zrušit: <strong>%s</strong>?
|
||||
monitor.process.children=Potomek
|
||||
|
||||
monitor.queues=Fronty
|
||||
@ -3311,6 +3438,7 @@ raw_minutes=minut
|
||||
|
||||
[dropzone]
|
||||
default_message=Přetáhněte soubory nebo klikněte sem pro nahrání.
|
||||
invalid_input_type=Nemůžete nahrávat soubory tohoto typu.
|
||||
file_too_big=Velikost souboru ({{filesize}} MB) je vyšší než maximální velikost ({{maxFilesize}} MB).
|
||||
remove_file=Smazat soubor
|
||||
|
||||
@ -3383,6 +3511,9 @@ alpine.repository=Informace o repozitáři
|
||||
alpine.repository.branches=Větve
|
||||
alpine.repository.repositories=Repozitáře
|
||||
alpine.repository.architectures=Architektury
|
||||
arch.repository=Informace o repozitáři
|
||||
arch.repository.repositories=Repozitáře
|
||||
arch.repository.architectures=Architektury
|
||||
cargo.registry=Nastavte tento registr v konfiguračním souboru Cargo (například <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Chcete-li nainstalovat balíček pomocí Cargo, spusťte následující příkaz:
|
||||
chef.registry=Nastavit tento registr v souboru <code>~/.chef/config.rb</code>:
|
||||
@ -3582,12 +3713,18 @@ runs.no_workflows.quick_start=Nevíte jak začít s Gitea Actions? Podívejte se
|
||||
runs.no_workflows.documentation=Další informace o Gitea Actions naleznete v <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>.
|
||||
runs.no_runs=Pracovní postup zatím nebyl spuštěn.
|
||||
runs.empty_commit_message=(prázdná zpráva commitu)
|
||||
runs.expire_log_message=Logy byly vyčištěny, protože byly příliš staré.
|
||||
|
||||
workflow.disable=Zakázat pracovní postup
|
||||
workflow.disable_success=Pracovní postup „%s“ byl úspěšně deaktivován.
|
||||
workflow.enable=Povolit pracovní postup
|
||||
workflow.enable_success=Pracovní postup „%s“ byl úspěšně aktivován.
|
||||
workflow.disabled=Pracovní postup je zakázán.
|
||||
workflow.run=Spustit pracovní postup
|
||||
workflow.not_found=Pracovní postup „%s“ nebyl nalezen.
|
||||
workflow.run_success=Pracovní postup „%s“ proběhl úspěšně.
|
||||
workflow.from_ref=Použít pracovní postup od
|
||||
workflow.has_workflow_dispatch=Tento pracovní postup má spouštěč události workflow_dispatch.
|
||||
|
||||
need_approval_desc=Potřebujete schválení pro spuštění pracovních postupů pro rozštěpený pull request.
|
||||
|
||||
@ -3608,6 +3745,7 @@ variables.update.failed=Úprava proměnné se nezdařila.
|
||||
variables.update.success=Proměnná byla upravena.
|
||||
|
||||
[projects]
|
||||
deleted.display_name=Odstraněný projekt
|
||||
type-1.display_name=Samostatný projekt
|
||||
type-2.display_name=Projekt repozitíře
|
||||
type-3.display_name=Projekt organizace
|
||||
|
@ -201,6 +201,7 @@ buttons.link.tooltip=Link hinzufügen
|
||||
buttons.list.unordered.tooltip=Liste hinzufügen
|
||||
buttons.list.ordered.tooltip=Nummerierte Liste hinzufügen
|
||||
buttons.list.task.tooltip=Aufgabenliste hinzufügen
|
||||
buttons.table.add.insert=Hinzufügen
|
||||
buttons.mention.tooltip=Benutzer oder Team erwähnen
|
||||
buttons.ref.tooltip=Issue oder Pull-Request referenzieren
|
||||
buttons.switch_to_legacy.tooltip=Legacy-Editor verwenden
|
||||
@ -997,7 +998,6 @@ generate_repo=Repository erstellen
|
||||
generate_from=Erstelle aus
|
||||
repo_desc=Beschreibung
|
||||
repo_desc_helper=Gib eine kurze Beschreibung an (optional)
|
||||
repo_lang=Sprache
|
||||
repo_gitignore_helper=Wähle eine .gitignore-Vorlage aus.
|
||||
repo_gitignore_helper_desc=Wähle aus einer Liste an Vorlagen für bekannte Sprachen, welche Dateien ignoriert werden sollen. Typische Artefakte, die durch die Build Tools der gewählten Sprache generiert werden, sind standardmäßig Bestandteil der .gitignore.
|
||||
issue_labels=Issue Label
|
||||
@ -1399,12 +1399,9 @@ issues.new.no_items=Keine Einträge
|
||||
issues.new.milestone=Meilenstein
|
||||
issues.new.no_milestone=Kein Meilenstein
|
||||
issues.new.clear_milestone=Meilenstein entfernen
|
||||
issues.new.open_milestone=Offene Meilensteine
|
||||
issues.new.closed_milestone=Geschlossene Meilensteine
|
||||
issues.new.assignees=Zuständig
|
||||
issues.new.clear_assignees=Zuständige entfernen
|
||||
issues.new.no_assignees=Niemand zuständig
|
||||
issues.new.no_reviewers=Keine Reviewer
|
||||
issues.new.blocked_user=Das Issue kann nicht erstellt werden, da du vom Repository-Eigentümer blockiert wurdest.
|
||||
issues.edit.blocked_user=Der Inhalt kann nicht bearbeitet werden, da du vom Repository-Eigentümer blockiert wurdest.
|
||||
issues.choose.get_started=Los geht's
|
||||
@ -1604,27 +1601,20 @@ issues.comment_on_locked=Du kannst einen gesperrten Issue nicht kommentieren.
|
||||
issues.delete=Löschen
|
||||
issues.delete.title=Dieses Issue löschen?
|
||||
issues.delete.text=Möchtest du dieses Issue wirklich löschen? (Dadurch wird der Inhalt dauerhaft gelöscht. Denke daran, es stattdessen zu schließen, wenn du es archivieren willst)
|
||||
|
||||
issues.tracker=Zeiterfassung
|
||||
issues.start_tracking_short=Zeiterfassung starten
|
||||
issues.start_tracking=Zeiterfassung starten
|
||||
issues.start_tracking_history=hat die Zeiterfassung %s gestartet
|
||||
|
||||
issues.tracker_auto_close=Der Timer wird automatisch gestoppt, wenn dieser Issue geschlossen wird
|
||||
issues.tracking_already_started=`Du hast die Zeiterfassung bereits in <a href="%s">diesem Issue</a> gestartet!`
|
||||
issues.stop_tracking=Zeiterfassung stoppen
|
||||
issues.stop_tracking_history=hat die Zeiterfassung %s angehalten
|
||||
issues.cancel_tracking=Verwerfen
|
||||
issues.cancel_tracking_history=`hat die Zeiterfassung %s abgebrochen`
|
||||
issues.add_time=Zeit manuell hinzufügen
|
||||
issues.del_time=Diese Zeiterfassung löschen
|
||||
issues.add_time_short=Zeit hinzufügen
|
||||
issues.add_time_cancel=Abbrechen
|
||||
issues.add_time_history=`hat %s gearbeitete Zeit hinzugefügt`
|
||||
issues.del_time_history=`hat %s gearbeitete Zeit gelöscht`
|
||||
issues.add_time_hours=Stunden
|
||||
issues.add_time_minutes=Minuten
|
||||
issues.add_time_sum_to_small=Es wurde keine Zeit eingegeben.
|
||||
issues.time_spent_total=Zeitaufwand insgesamt
|
||||
issues.time_spent_from_all_authors=`Aufgewendete Zeit: %s`
|
||||
|
||||
issues.due_date=Fällig am
|
||||
issues.invalid_due_date_format=Das Fälligkeitsdatum muss das Format „JJJJ-MM-TT“ haben.
|
||||
issues.error_modifying_due_date=Fehler beim Ändern des Fälligkeitsdatums.
|
||||
@ -2452,7 +2442,6 @@ diff.generated=generiert
|
||||
diff.vendored=vendored
|
||||
diff.comment.add_line_comment=Einzelnen Kommentar hinzufügen
|
||||
diff.comment.placeholder=Kommentieren...
|
||||
diff.comment.markdown_info=Styling mit Markdown wird unterstützt.
|
||||
diff.comment.add_single_comment=Einzelnen Kommentar hinzufügen
|
||||
diff.comment.add_review_comment=Kommentar hinzufügen
|
||||
diff.comment.start_review=Review starten
|
||||
@ -3344,6 +3333,9 @@ alpine.repository=Repository-Informationen
|
||||
alpine.repository.branches=Branches
|
||||
alpine.repository.repositories=Repositories
|
||||
alpine.repository.architectures=Architekturen
|
||||
arch.repository=Repository-Informationen
|
||||
arch.repository.repositories=Repositories
|
||||
arch.repository.architectures=Architekturen
|
||||
cargo.registry=Richte diese Registry in der Cargo-Konfigurationsdatei ein (z.B. <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Um das Paket mit Cargo zu installieren, führe den folgenden Befehl aus:
|
||||
chef.registry=Richte diese Registry in deiner <code>~/.chef/config.rb</code> Datei ein:
|
||||
|
@ -172,6 +172,7 @@ buttons.link.tooltip=Προσθήκη συνδέσμου
|
||||
buttons.list.unordered.tooltip=Προσθήκη απλής λίστας
|
||||
buttons.list.ordered.tooltip=Προσθήκη αριθμημένης λίστας
|
||||
buttons.list.task.tooltip=Προσθήκη λίστας εργασιών
|
||||
buttons.table.add.insert=Προσθήκη
|
||||
buttons.mention.tooltip=Μνημόνευση ενός χρήστη ή ομάδας
|
||||
buttons.ref.tooltip=Μνημόνευση ενός θέματος ή pull request
|
||||
buttons.switch_to_legacy.tooltip=Χρήση του κλασσικού κειμενογράφου
|
||||
@ -934,7 +935,6 @@ generate_repo=Δημιουργία Αποθετηρίου
|
||||
generate_from=Δημιουργία Από
|
||||
repo_desc=Περιγραφή
|
||||
repo_desc_helper=Εισάγετε μια σύντομη περιγραφή (προαιρετικό)
|
||||
repo_lang=Γλώσσα
|
||||
repo_gitignore_helper=Επιλέξτε πρότυπα .gitignore.
|
||||
repo_gitignore_helper_desc=Επιλέξτε ποια αρχεία δεν θα παρακολουθείτε από μια λίστα προτύπων για κοινές γλώσσες προγραμματισμού. Τυπικά αντικείμενα που δημιουργούνται από τα εργαλεία κατασκευής κάθε γλώσσας περιλαμβάνονται ήδη στο .gitignore.
|
||||
issue_labels=Σήματα Ζητήματος
|
||||
@ -1324,12 +1324,9 @@ issues.new.no_items=Δεν υπάρχουν αντικείμενα
|
||||
issues.new.milestone=Ορόσημο
|
||||
issues.new.no_milestone=Χωρίς Ορόσημο
|
||||
issues.new.clear_milestone=Καθαρισμός ορόσημου
|
||||
issues.new.open_milestone=Ανοιχτά Ορόσημα
|
||||
issues.new.closed_milestone=Κλειστά Ορόσημα
|
||||
issues.new.assignees=Αποδέκτες
|
||||
issues.new.clear_assignees=Εκκαθάριση αποδεκτών
|
||||
issues.new.no_assignees=Χωρίς Αποδέκτη
|
||||
issues.new.no_reviewers=Δεν υπάρχουν εξεταστές
|
||||
issues.choose.get_started=Ας Αρχίσουμε
|
||||
issues.choose.open_external_link=Άνοιγμα
|
||||
issues.choose.blank=Προεπιλογή
|
||||
@ -1526,27 +1523,20 @@ issues.comment_on_locked=Δεν μπορείτε να σχολιάσετε έν
|
||||
issues.delete=Διαγραφή
|
||||
issues.delete.title=Διαγραφή αυτού του ζητήματος;
|
||||
issues.delete.text=Θέλετε πραγματικά να διαγράψετε αυτό το ζήτημα; (Αυτό θα σβήσει οριστικά όλο το περιεχόμενο του. Εξετάστε αν θέλετε να το κλείσετε, αν σκοπεύεται να το αρχειοθετήσετε)
|
||||
|
||||
issues.tracker=Καταγραφή Χρόνου
|
||||
issues.start_tracking_short=Εκκίνηση Χρονομέτρου
|
||||
issues.start_tracking=Εκκίνηση Καταγραφής Χρόνου
|
||||
issues.start_tracking_history=`ξεκίνησε να εργάζεται %s`
|
||||
|
||||
issues.tracker_auto_close=Το χρονόμετρο θα σταματήσει αυτόματα όταν κλείσει αυτό το ζήτημα
|
||||
issues.tracking_already_started=`Έχετε ήδη ξεκινήσει την καταγραφή του χρόνου <a href="%s">σε ένα άλλο ζήτημα</a>!`
|
||||
issues.stop_tracking=Διακοπή Χρονομέτρου
|
||||
issues.stop_tracking_history=`σταμάτησε να εργάζεται %s`
|
||||
issues.cancel_tracking=Απόρριψη
|
||||
issues.cancel_tracking_history=`ακύρωσε τη παρακολούθηση χρόνου %s`
|
||||
issues.add_time=Χειροκίνητη Προσθήκη Ώρας
|
||||
issues.del_time=Διαγραφή αυτού του αρχείου χρόνου
|
||||
issues.add_time_short=Προσθήκη Χρόνου
|
||||
issues.add_time_cancel=Ακύρωση
|
||||
issues.add_time_history=`πρόσθεσε χρόνο που δαπανήθηκε %s`
|
||||
issues.del_time_history=`διέγραψε το χρόνο που δαπανήθηκε %s`
|
||||
issues.add_time_hours=Ώρες
|
||||
issues.add_time_minutes=Λεπτά
|
||||
issues.add_time_sum_to_small=Δεν εισήχθη χρόνος.
|
||||
issues.time_spent_total=Συνολική Δαπάνη Χρόνου
|
||||
issues.time_spent_from_all_authors=`Συνολική Δαπάνη Χρόνου: %s`
|
||||
|
||||
issues.due_date=Ημερομηνία Παράδοσης
|
||||
issues.invalid_due_date_format=Η μορφή της ημερομηνίας παράδοσης πρέπει να είναι 'yyyy-mm-dd'.
|
||||
issues.error_modifying_due_date=Αποτυχία τροποποίησης της ημερομηνίας παράδοσης.
|
||||
@ -2358,7 +2348,6 @@ diff.generated=δημιουργημένο
|
||||
diff.vendored=εξωτερικό
|
||||
diff.comment.add_line_comment=Προσθήκη σχολίου στη γραμμή
|
||||
diff.comment.placeholder=Αφήστε ένα σχόλιο
|
||||
diff.comment.markdown_info=Υποστηρίζεται στυλ με markdown.
|
||||
diff.comment.add_single_comment=Προσθέστε ένα σχόλιο
|
||||
diff.comment.add_review_comment=Προσθήκη σχολίου
|
||||
diff.comment.start_review=Έναρξη αξιολόγησης
|
||||
@ -3232,6 +3221,9 @@ alpine.repository=Πληροφορίες Αποθετηρίου
|
||||
alpine.repository.branches=Κλάδοι
|
||||
alpine.repository.repositories=Αποθετήρια
|
||||
alpine.repository.architectures=Αρχιτεκτονικές
|
||||
arch.repository=Πληροφορίες Αποθετηρίου
|
||||
arch.repository.repositories=Αποθετήρια
|
||||
arch.repository.architectures=Αρχιτεκτονικές
|
||||
cargo.registry=Ρυθμίστε αυτό το μητρώο στις ρυθμίσεις του Cargo (για παράδειγμα <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Cargo, εκτελέστε την ακόλουθη εντολή:
|
||||
chef.registry=Ρυθμίστε αυτό το μητρώο στο αρχείο <code>~/.chef/config.rb</code>:
|
||||
|
@ -145,6 +145,7 @@ confirm_delete_selected = Confirm to delete all selected items?
|
||||
|
||||
name = Name
|
||||
value = Value
|
||||
readme = Readme
|
||||
|
||||
filter = Filter
|
||||
filter.clear = Clear Filter
|
||||
@ -1045,7 +1046,8 @@ generate_repo = Generate Repository
|
||||
generate_from = Generate From
|
||||
repo_desc = Description
|
||||
repo_desc_helper = Enter short description (optional)
|
||||
repo_lang = Language
|
||||
repo_no_desc = No description provided
|
||||
repo_lang = Languages
|
||||
repo_gitignore_helper = Select .gitignore templates.
|
||||
repo_gitignore_helper_desc = Choose which files not to track from a list of templates for common languages. Typical artifacts generated by each language's build tools are included on .gitignore by default.
|
||||
issue_labels = Issue Labels
|
||||
@ -1955,6 +1957,10 @@ pulls.delete.title = Delete this pull request?
|
||||
pulls.delete.text = Do you really want to delete this pull request? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived)
|
||||
|
||||
pulls.recently_pushed_new_branches = You pushed on branch <strong>%[1]s</strong> %[2]s
|
||||
pulls.upstream_diverging_prompt_behind_1 = This branch is %d commit behind %s
|
||||
pulls.upstream_diverging_prompt_behind_n = This branch is %d commits behind %s
|
||||
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
|
||||
pulls.upstream_diverging_merge = Sync fork
|
||||
|
||||
pull.deleted_branch = (deleted):%s
|
||||
pull.agit_documentation = Review documentation about AGit
|
||||
|
@ -170,6 +170,7 @@ buttons.link.tooltip=Añadir un enlace
|
||||
buttons.list.unordered.tooltip=Añadir una lista
|
||||
buttons.list.ordered.tooltip=Añadir una lista numerada
|
||||
buttons.list.task.tooltip=Añadir una lista de tareas
|
||||
buttons.table.add.insert=Añadir
|
||||
buttons.mention.tooltip=Mencionar un usuario o equipo
|
||||
buttons.ref.tooltip=Referir a una incidencia o pull request
|
||||
buttons.switch_to_legacy.tooltip=Utilizar el editor antiguo en su lugar
|
||||
@ -924,7 +925,6 @@ generate_repo=Generar repositorio
|
||||
generate_from=Generar desde
|
||||
repo_desc=Descripción
|
||||
repo_desc_helper=Introduce una descripción corta (opcional)
|
||||
repo_lang=Idioma
|
||||
repo_gitignore_helper=Seleccionar plantillas de .gitignore.
|
||||
repo_gitignore_helper_desc=Elija qué archivos no rastrear de una lista de plantillas para idiomas comunes. Los artefactos típicos generados por las herramientas de construcción de cada idioma se incluyen por defecto en .gitignore.
|
||||
issue_labels=Etiquetas de incidencia
|
||||
@ -1314,12 +1314,9 @@ issues.new.no_items=No hay elementos
|
||||
issues.new.milestone=Milestone
|
||||
issues.new.no_milestone=Sin Milestone
|
||||
issues.new.clear_milestone=Limpiar Milestone
|
||||
issues.new.open_milestone=Milestones abiertas
|
||||
issues.new.closed_milestone=Milestones cerradas
|
||||
issues.new.assignees=Asignados
|
||||
issues.new.clear_assignees=Limpiar asignados
|
||||
issues.new.no_assignees=No asignados
|
||||
issues.new.no_reviewers=No hay revisores
|
||||
issues.choose.get_started=Comenzar
|
||||
issues.choose.open_external_link=Abrir
|
||||
issues.choose.blank=Predeterminado
|
||||
@ -1516,27 +1513,20 @@ issues.comment_on_locked=No puede comentar una incidencia bloqueada.
|
||||
issues.delete=Eliminar
|
||||
issues.delete.title=¿Eliminar esta incidencia?
|
||||
issues.delete.text=¿Realmente quieres eliminar esta incidencia? (Esto eliminará permanentemente todo el contenido. Considera cerrarlo en su lugar, si quieres mantenerlo archivado)
|
||||
|
||||
issues.tracker=Gestor de tiempo
|
||||
issues.start_tracking_short=Iniciar temporizador
|
||||
issues.start_tracking=Inicio de seguimiento de tiempo
|
||||
issues.start_tracking_history=`ha empezado a trabajar %s`
|
||||
|
||||
issues.tracker_auto_close=El temporizador se detendrá automáticamente cuando se cierre este problema
|
||||
issues.tracking_already_started=`¡Ya has iniciado el seguimiento de tiempo en <a href="%s">otro problema</a>!`
|
||||
issues.stop_tracking=Detener temporizador
|
||||
issues.stop_tracking_history=`dejó de trabajar %s`
|
||||
issues.cancel_tracking=Descartar
|
||||
issues.cancel_tracking_history=`canceló el seguimiento de tiempo %s`
|
||||
issues.add_time=Añadir tiempo gastado manualmente
|
||||
issues.del_time=Eliminar este registro de tiempo
|
||||
issues.add_time_short=Añadir tiempo gastado
|
||||
issues.add_time_cancel=Cancelar
|
||||
issues.add_time_history=`añadió tiempo gastado %s`
|
||||
issues.del_time_history=`eliminado el tiempo gastado %s`
|
||||
issues.add_time_hours=Horas
|
||||
issues.add_time_minutes=Minutos
|
||||
issues.add_time_sum_to_small=No se ha entrado tiempo.
|
||||
issues.time_spent_total=Tiempo total gastado
|
||||
issues.time_spent_from_all_authors=`Tiempo total gastado: %s`
|
||||
|
||||
issues.due_date=Fecha de vencimiento
|
||||
issues.invalid_due_date_format=El formato de la fecha de vencimiento debe ser 'aaaa-mm-dd'.
|
||||
issues.error_modifying_due_date=Fallo al modificar la fecha de vencimiento.
|
||||
@ -2339,7 +2329,6 @@ diff.generated=generado
|
||||
diff.vendored=vendido
|
||||
diff.comment.add_line_comment=Añadir comentario en línea
|
||||
diff.comment.placeholder=Deja un comentario
|
||||
diff.comment.markdown_info=Es posible estilizar con markdown.
|
||||
diff.comment.add_single_comment=Añadir solo comentario
|
||||
diff.comment.add_review_comment=Añadir comentario
|
||||
diff.comment.start_review=Comenzar revisión
|
||||
@ -3211,6 +3200,9 @@ alpine.repository=Información del repositorio
|
||||
alpine.repository.branches=Ramas
|
||||
alpine.repository.repositories=Repositorios
|
||||
alpine.repository.architectures=Arquitecturas
|
||||
arch.repository=Información del repositorio
|
||||
arch.repository.repositories=Repositorios
|
||||
arch.repository.architectures=Arquitecturas
|
||||
cargo.registry=Configurar este registro en el archivo de configuración de Cargo (por ejemplo <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Para instalar el paquete usando Cargo, ejecute el siguiente comando:
|
||||
chef.registry=Configura este registro en tu archivo <code>~/.chef/config.rb</code>:
|
||||
|
@ -114,6 +114,7 @@ filter.private=خصوصی
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=افزودن
|
||||
|
||||
[filter]
|
||||
|
||||
@ -724,7 +725,6 @@ generate_repo=ساختن مخزن
|
||||
generate_from=ساختن از
|
||||
repo_desc=توضیحات
|
||||
repo_desc_helper=توضیحات مختصر را وارد کنید(اختیاری)
|
||||
repo_lang=زبان
|
||||
repo_gitignore_helper=یک قالب برای .gitignore انتخاب کنید.
|
||||
repo_gitignore_helper_desc=از فهرست الگوهای زبانهای رایج، فایلهایی را انتخاب کنید که ردیابی نشوند. مصنوعات معمولی تولید شده توسط ابزارهای ساخت هر زبان به طور پیش فرض در gitignore. گنجانده شده است.
|
||||
issue_labels=برچسبهای مسئله
|
||||
@ -1013,12 +1013,9 @@ issues.new.no_items=موردی وجود ندارد
|
||||
issues.new.milestone=نقطه عطف
|
||||
issues.new.no_milestone=بدون نقطه عطف
|
||||
issues.new.clear_milestone=پاککردن نقطه عطف
|
||||
issues.new.open_milestone=نقاط عطف باز
|
||||
issues.new.closed_milestone=نقاط عطف بسته
|
||||
issues.new.assignees=تخصیص شده
|
||||
issues.new.clear_assignees=پاک کردن تخصیص
|
||||
issues.new.no_assignees=بدون تخصیص
|
||||
issues.new.no_reviewers=بدون بازبین گر
|
||||
issues.choose.get_started=آغاز کردن
|
||||
issues.choose.open_external_link=بازکردن
|
||||
issues.choose.blank=پیشگزیده
|
||||
@ -1168,26 +1165,19 @@ issues.lock.title=انسداد مکالمه در این مسئله.
|
||||
issues.unlock.title=رفع انسداد مکالمه در این مسئله.
|
||||
issues.comment_on_locked=شما نمیتوانید در مسئله قفل شده اظهار نظر کنید.
|
||||
issues.delete=حذف
|
||||
|
||||
issues.tracker=پیگیری زمان
|
||||
issues.start_tracking_short=تایمز شروع
|
||||
issues.start_tracking=شروع به پیگیری زمان
|
||||
issues.start_tracking_history=`شروع به کار %s`
|
||||
|
||||
issues.tracker_auto_close=زمانسنج به صورت خودکار متوقف میشود زمانی که مسئله بسته شود
|
||||
issues.tracking_already_started=`شما قبلا رهگیری زمان را روی این <a href="%s"> مسئله </a> آغاز کردهاید!`
|
||||
issues.stop_tracking=تایمز ایست
|
||||
issues.stop_tracking_history=`توقف کار در %s`
|
||||
issues.cancel_tracking=ول کردن
|
||||
issues.add_time=زمان را به صورت دستی وارد کنید
|
||||
issues.del_time=این لاگ را حذف کنید
|
||||
issues.add_time_short=افزودن زمان
|
||||
issues.add_time_cancel=انصراف
|
||||
issues.add_time_history=`زمان صرف شده اضافه شد %s`
|
||||
issues.del_time_history=`زمان صرف شده حذف شد %s`
|
||||
issues.add_time_hours=ساعت
|
||||
issues.add_time_minutes=دقیقه
|
||||
issues.add_time_sum_to_small=هیچ زمانی وارد نشده.
|
||||
issues.time_spent_total=کل زمان صرف شده
|
||||
issues.time_spent_from_all_authors=`زمان صرف شده: %s`
|
||||
|
||||
issues.due_date=موعد مقرر
|
||||
issues.invalid_due_date_format=موعد مقرر، باید به سبک 'yyyy-mm-dd' باشد.
|
||||
issues.error_modifying_due_date=تغییر موعد مقرر با شکست مواجه شد.
|
||||
@ -1814,7 +1804,6 @@ diff.load=Diff را بارگزاری کن
|
||||
diff.generated=تولید شده
|
||||
diff.vendored=فروخته شده
|
||||
diff.comment.placeholder=اظهار نظر کنید
|
||||
diff.comment.markdown_info=شیوه markdown پیشتیبانی میشود.
|
||||
diff.comment.add_single_comment=افزودن یک دیدگاه به تنهایی
|
||||
diff.comment.add_review_comment=افزودن دیدگاه
|
||||
diff.comment.start_review=شروع بازبینی
|
||||
@ -2517,6 +2506,7 @@ error.unit_not_allowed=شما اجازه دسترسی به این قسمت مخ
|
||||
filter.type=نوع
|
||||
alpine.repository.branches=شاخهها
|
||||
alpine.repository.repositories=مخازن
|
||||
arch.repository.repositories=مخازن
|
||||
conan.details.repository=مخزن
|
||||
owner.settings.cleanuprules.enabled=فعال شده
|
||||
|
||||
|
@ -128,6 +128,7 @@ filter.private=Yksityinen
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Lisää
|
||||
|
||||
[filter]
|
||||
|
||||
@ -648,7 +649,6 @@ fork_visibility_helper=Forkatun repon näkyvyyttä ei voi muuttaa.
|
||||
download_zip=Lataa ZIP
|
||||
download_tar=Lataa TAR.GZ
|
||||
repo_desc=Kuvaus
|
||||
repo_lang=Kieli
|
||||
repo_gitignore_helper=Valitse .gitignore mallit.
|
||||
issue_labels=Ongelmien tunnisteet
|
||||
issue_labels_helper=Valitse pohja ongelmien nimilapuille.
|
||||
@ -806,8 +806,6 @@ issues.new.no_items=Ei kohteita
|
||||
issues.new.milestone=Merkkipaalu
|
||||
issues.new.no_milestone=Ei merkkipaalua
|
||||
issues.new.clear_milestone=Tyhjennä merkkipaalu
|
||||
issues.new.open_milestone=Avoimet merkkipaalut
|
||||
issues.new.closed_milestone=Suljetut merkkipaalut
|
||||
issues.new.assignees=Käsittelijä
|
||||
issues.new.clear_assignees=Tyhjennä käsittelijä
|
||||
issues.new.no_assignees=Ei käsittelijää
|
||||
@ -914,21 +912,15 @@ issues.lock.reason=Lukitsemisen syy
|
||||
issues.lock.title=Lukitse keskustelu tästä ongelmasta.
|
||||
issues.unlock.title=Avaa keskustelu tästä ongelmasta.
|
||||
issues.delete=Poista
|
||||
|
||||
issues.tracker=Ajan seuranta
|
||||
issues.start_tracking_short=Aloita ajanotto
|
||||
issues.start_tracking=Aloita ajan seuranta
|
||||
issues.start_tracking_history=`aloitti työskentelyn %s`
|
||||
|
||||
issues.tracker_auto_close=Ajan seuranta pysähtyy automaattisesti kun tämä ongelma on suljettu
|
||||
issues.stop_tracking=Pysäytä ajanotto
|
||||
issues.stop_tracking_history=`lopetti työskentelyn %s`
|
||||
issues.add_time=Lisää aika käsin
|
||||
issues.add_time_short=Lisää aika
|
||||
issues.add_time_cancel=Peruuta
|
||||
issues.add_time_history=`lisäsi käytetyn ajan %s`
|
||||
issues.add_time_hours=Tuntia
|
||||
issues.add_time_minutes=Minuuttia
|
||||
issues.add_time_sum_to_small=Aikaa ei syötetty.
|
||||
issues.time_spent_from_all_authors=`Käytetty kokonaisaika: %s`
|
||||
|
||||
issues.due_date=Määräpäivä
|
||||
issues.push_commit_1=lisäsi %d commitin %s
|
||||
issues.push_commits_n=lisäsi %d committia %s
|
||||
@ -1239,7 +1231,6 @@ diff.view_file=Näytä tiedosto
|
||||
diff.file_image_width=Leveys
|
||||
diff.file_image_height=Korkeus
|
||||
diff.file_byte_size=Koko
|
||||
diff.comment.markdown_info=Muotoilu markdownilla tuettu.
|
||||
diff.comment.add_single_comment=Lisää yksittäinen kommentti
|
||||
diff.comment.add_review_comment=Lisää kommentti
|
||||
diff.comment.start_review=Aloita tarkistus
|
||||
@ -1694,6 +1685,7 @@ installation=Asennus
|
||||
details.author=Tekijä
|
||||
alpine.repository.branches=Haarat
|
||||
alpine.repository.repositories=Repot
|
||||
arch.repository.repositories=Repot
|
||||
conan.details.repository=Repo
|
||||
owner.settings.cleanuprules.enabled=Käytössä
|
||||
|
||||
|
@ -104,6 +104,7 @@ copy_url=Copier l'URL
|
||||
copy_hash=Copier le hach
|
||||
copy_content=Copier le contenu
|
||||
copy_branch=Copier le nom de la branche
|
||||
copy_path=Copier le chemin
|
||||
copy_success=Copié !
|
||||
copy_error=Échec de la copie
|
||||
copy_type_unsupported=Ce type de fichier ne peut pas être copié
|
||||
@ -209,6 +210,10 @@ buttons.link.tooltip=Ajouter un lien
|
||||
buttons.list.unordered.tooltip=Ajouter une liste à puces
|
||||
buttons.list.ordered.tooltip=Ajouter une liste numérotée
|
||||
buttons.list.task.tooltip=Ajouter une liste de tâches
|
||||
buttons.table.add.tooltip=Ajouter un tableau
|
||||
buttons.table.add.insert=Ajouter
|
||||
buttons.table.rows=Lignes
|
||||
buttons.table.cols=Colonnes
|
||||
buttons.mention.tooltip=Mentionner un utilisateur ou une équipe
|
||||
buttons.ref.tooltip=Référencer un ticket ou demande d’ajout
|
||||
buttons.switch_to_legacy.tooltip=Utiliser l’ancien éditeur à la place
|
||||
@ -348,6 +353,7 @@ enable_update_checker=Activer la vérification des mises-à-jour
|
||||
enable_update_checker_helper=Vérifie les mises à jour régulièrement en se connectant à gitea.io.
|
||||
env_config_keys=Configuration de l'environnement
|
||||
env_config_keys_prompt=Les variables d'environnement suivantes seront également ajoutées à votre fichier de configuration :
|
||||
config_write_file_prompt=Ces options de configuration seront écrites dans : %s
|
||||
|
||||
[home]
|
||||
nav_menu=Menu de navigation
|
||||
@ -453,6 +459,7 @@ authorize_application=Autoriser l'application
|
||||
authorize_redirect_notice=Vous serez redirigé vers %s si vous autorisez cette application.
|
||||
authorize_application_created_by=Cette application a été créée par %s.
|
||||
authorize_application_description=Si vous accordez l'accès, il sera en mesure d'accéder et d'écrire toutes les informations de votre compte, y compris les dépôts privés et les organisations.
|
||||
authorize_application_with_scopes=Avec des contextes : %s
|
||||
authorize_title=Autoriser "%s" à accéder à votre compte ?
|
||||
authorization_failed=L’autorisation a échoué
|
||||
authorization_failed_desc=L'autorisation a échoué car nous avons détecté une demande incorrecte. Veuillez contacter le responsable de l'application que vous avez essayé d'autoriser.
|
||||
@ -707,6 +714,8 @@ public_profile=Profil public
|
||||
biography_placeholder=Parlez-nous un peu de vous ! (Vous pouvez utiliser Markdown)
|
||||
location_placeholder=Partagez votre position approximative avec d'autres personnes
|
||||
profile_desc=Contrôlez comment votre profil est affiché aux autres utilisateurs. Votre adresse courriel principale sera utilisée pour les notifications, la récupération de mot de passe et les opérations Git basées sur le Web.
|
||||
password_username_disabled=Vous n’êtes pas autorisé à modifier leur nom d’utilisateur. Veuillez contacter l’administrateur de votre site pour plus de détails.
|
||||
password_full_name_disabled=Vous n’êtes pas autorisé à modifier leur nom complet. Veuillez contacter l’administrateur du site pour plus de détails.
|
||||
full_name=Nom complet
|
||||
website=Site Web
|
||||
location=Localisation
|
||||
@ -756,6 +765,7 @@ uploaded_avatar_not_a_image=Le fichier téléchargé n'est pas une image.
|
||||
uploaded_avatar_is_too_big=La taille du fichier téléversé (%d Kio) dépasse la taille maximale (%d Kio).
|
||||
update_avatar_success=Votre avatar a été mis à jour.
|
||||
update_user_avatar_success=L'avatar de l'utilisateur a été mis à jour.
|
||||
cropper_prompt=Vous pouvez modifier l’image avant de l’enregistrer. L’image modifiée sera enregistrée en tant que PNG.
|
||||
|
||||
change_password=Modifier le mot de passe
|
||||
old_password=Mot de passe actuel
|
||||
@ -1033,7 +1043,6 @@ generate_repo=Générer un dépôt
|
||||
generate_from=Générer depuis
|
||||
repo_desc=Description
|
||||
repo_desc_helper=Décrire brièvement votre dépôt
|
||||
repo_lang=Langue
|
||||
repo_gitignore_helper=Sélectionner quelques .gitignore prédéfinies
|
||||
repo_gitignore_helper_desc=De nombreux outils et compilateurs génèrent des fichiers résiduels qui n'ont pas besoin d'être supervisés par git. Composez un .gitignore à l’aide de cette liste des languages de programmation courants.
|
||||
issue_labels=Jeu de labels pour les tickets
|
||||
@ -1450,8 +1459,6 @@ issues.new.no_items=Pas d'élément
|
||||
issues.new.milestone=Jalon
|
||||
issues.new.no_milestone=Sans jalon
|
||||
issues.new.clear_milestone=Effacer le jalon
|
||||
issues.new.open_milestone=Ouvrir un jalon
|
||||
issues.new.closed_milestone=Jalons fermés
|
||||
issues.new.assignees=Assignés
|
||||
issues.new.clear_assignees=Supprimer les affectations
|
||||
issues.new.no_assignees=Sans assignation
|
||||
@ -1659,27 +1666,20 @@ issues.comment_on_locked=Vous ne pouvez pas commenter un ticket verrouillé.
|
||||
issues.delete=Supprimer
|
||||
issues.delete.title=Supprimer ce ticket ?
|
||||
issues.delete.text=Voulez-vous vraiment supprimer ce ticket ? (Cette opération supprimera définitivement tout le contenu. Envisagez plutôt de le fermer si vous avez l'intention de l'archiver)
|
||||
|
||||
issues.tracker=Minuteur
|
||||
issues.start_tracking_short=Démarrer la minuteuse
|
||||
issues.start_tracking=Démarrer le suivi du temps
|
||||
issues.start_tracking_history=`a commencé son travail %s.`
|
||||
|
||||
issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé.
|
||||
issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur <a href="%s">un autre ticket</a> !`
|
||||
issues.stop_tracking=Arrêter la minuteuse
|
||||
issues.stop_tracking_history=`a fini de travailler %s.`
|
||||
issues.cancel_tracking=Abandonner le minuteur
|
||||
issues.cancel_tracking_history=`a abandonné son minuteur %s.`
|
||||
issues.add_time=Ajouter du temps manuellement
|
||||
issues.del_time=Supprimer ce minuteur du journal
|
||||
issues.add_time_short=Pointer du temps
|
||||
issues.add_time_cancel=Annuler
|
||||
issues.add_time_history=`a pointé du temps de travail %s.`
|
||||
issues.del_time_history=`a supprimé son temps de travail %s.`
|
||||
issues.add_time_hours=Heures
|
||||
issues.add_time_minutes=Minutes
|
||||
issues.add_time_sum_to_small=Aucun minuteur n'a été saisi.
|
||||
issues.time_spent_total=Temps passé total
|
||||
issues.time_spent_from_all_authors=`Temps passé total : %s`
|
||||
|
||||
issues.due_date=Échéance
|
||||
issues.invalid_due_date_format=Le format de la date d'échéance est invalide, il doit être comme suit 'aaaa-mm-jj'.
|
||||
issues.error_modifying_due_date=Impossible de modifier l'échéance.
|
||||
@ -2458,6 +2458,8 @@ settings.block_on_official_review_requests=Bloquer la fusion en cas de demande d
|
||||
settings.block_on_official_review_requests_desc=La fusion ne sera pas possible tant qu’elle aura des demandes d’évaluations officielles, même s'il y a suffisamment d’approbations.
|
||||
settings.block_outdated_branch=Bloquer la fusion si la demande d'ajout est obsolète
|
||||
settings.block_outdated_branch_desc=La fusion ne sera pas possible lorsque la branche principale est derrière la branche de base.
|
||||
settings.block_admin_merge_override=Les administrateurs doivent respecter les règles de protection des branches
|
||||
settings.block_admin_merge_override_desc=Les administrateurs doivent respecter les règles de protection des branches et ne peuvent pas les contourner.
|
||||
settings.default_branch_desc=Sélectionnez une branche par défaut pour les demandes de fusion et les révisions :
|
||||
settings.merge_style_desc=Styles de fusion
|
||||
settings.default_merge_style_desc=Méthode de fusion par défaut
|
||||
@ -2577,7 +2579,6 @@ diff.generated=générée
|
||||
diff.vendored=externe
|
||||
diff.comment.add_line_comment=Commenter cette ligne
|
||||
diff.comment.placeholder=Laisser un commentaire
|
||||
diff.comment.markdown_info=Formater avec Markdown.
|
||||
diff.comment.add_single_comment=Commenter (simple)
|
||||
diff.comment.add_review_comment=Commenter
|
||||
diff.comment.start_review=Débuter une évaluation
|
||||
@ -3512,6 +3513,9 @@ alpine.repository=Informations sur le Dépôt
|
||||
alpine.repository.branches=Branches
|
||||
alpine.repository.repositories=Dépôts
|
||||
alpine.repository.architectures=Architectures
|
||||
arch.repository=Informations sur le Dépôt
|
||||
arch.repository.repositories=Dépôts
|
||||
arch.repository.architectures=Architectures
|
||||
cargo.registry=Configurez ce registre dans le fichier de configuration Cargo (par exemple <code>~/.cargo/config.toml</code>) :
|
||||
cargo.install=Pour installer le paquet en utilisant Cargo, exécutez la commande suivante :
|
||||
chef.registry=Configurer ce registre dans votre fichier <code>~/.chef/config.rb</code>:
|
||||
|
@ -104,8 +104,9 @@ copy_url=Cóipeáil URL
|
||||
copy_hash=Cóipeáil hais
|
||||
copy_content=Cóipeáil ábhair
|
||||
copy_branch=Ainm brainse cóipeáil
|
||||
copy_path=Cóipeáil cosán
|
||||
copy_success=Cóipeáil!
|
||||
copy_error=Theip ar an gcóip
|
||||
copy_error=Theip ar an gcóipeáil
|
||||
copy_type_unsupported=Ní féidir an cineál comhaid seo a chóipeáil
|
||||
|
||||
write=Scríobh
|
||||
@ -209,6 +210,10 @@ buttons.link.tooltip=Cuir nasc leis
|
||||
buttons.list.unordered.tooltip=Cuir liosta piléar leis
|
||||
buttons.list.ordered.tooltip=Cuir liosta uimhrithe
|
||||
buttons.list.task.tooltip=Cuir liosta tascanna leis
|
||||
buttons.table.add.tooltip=Cuir tábla leis
|
||||
buttons.table.add.insert=Cuir
|
||||
buttons.table.rows=Sraitheanna
|
||||
buttons.table.cols=Colúin
|
||||
buttons.mention.tooltip=Luaigh úsáideoir nó foireann
|
||||
buttons.ref.tooltip=Déan tagairt d'eisiúint nó iarratas tarraingthe
|
||||
buttons.switch_to_legacy.tooltip=Úsáid an eagarthóir oidhreachta ina ionad
|
||||
@ -348,6 +353,7 @@ enable_update_checker=Cumasaigh Seiceoir Nuashonraithe
|
||||
enable_update_checker_helper=Seiceálacha ar eisiúintí leagan nua go tréimhsiúil trí nascadh le gitea.io.
|
||||
env_config_keys=Cumraíocht Comhshaoil
|
||||
env_config_keys_prompt=Cuirfear na hathróga comhshaoil seo a leanas i bhfeidhm ar do chomhad cumraíochta freisin:
|
||||
config_write_file_prompt=Scríobhfar na roghanna cumraíochta seo isteach: %s
|
||||
|
||||
[home]
|
||||
nav_menu=Roghchlár Nascleanúint
|
||||
@ -453,6 +459,7 @@ authorize_application=Údaraigh an Feidhmchlár
|
||||
authorize_redirect_notice=Déanfar tú a atreorú chuig %s má údaraíonn tú an feidhmchlár seo.
|
||||
authorize_application_created_by=Chruthaigh %s an feidhmchlár seo.
|
||||
authorize_application_description=Má dheonaíonn tú an rochtain, beidh sé in ann rochtain a fháil agus scríobh chuig faisnéis uile do chuntais, lena n-áirítear repos príobháideacha agus eagraíochtaí.
|
||||
authorize_application_with_scopes=Le scóip: %s
|
||||
authorize_title=Údaraigh "%s" chun rochtain a fháil ar do chuntas?
|
||||
authorization_failed=Theip ar údarú
|
||||
authorization_failed_desc=Theip ar an údarú toisc gur bhraitheamar iarratas neamhbhailí. Téigh i dteagmháil le cothabhálaí an aip a rinne tú iarracht a údarú.
|
||||
@ -758,6 +765,7 @@ uploaded_avatar_not_a_image=Ní íomhá é an comhad uaslódáilte.
|
||||
uploaded_avatar_is_too_big=Sáraíonn méid an chomhaid uaslódáilte (%d KiB) an méid uasta (%d KiB).
|
||||
update_avatar_success=Tá do avatar nuashonraithe.
|
||||
update_user_avatar_success=Nuashonraíodh avatar an úsáideora.
|
||||
cropper_prompt=Is féidir leat an íomhá a chur in eagar roimh shábháil. Sábhálfar an íomhá in eagar mar PNG.
|
||||
|
||||
change_password=Nuashonrú Pasfhocal
|
||||
old_password=Pasfhocal Reatha
|
||||
@ -1035,7 +1043,6 @@ generate_repo=Cruthaigh Stóras
|
||||
generate_from=Gin Ó
|
||||
repo_desc=Cur síos
|
||||
repo_desc_helper=Cuir isteach tuairisc ghearr (roghnach)
|
||||
repo_lang=Teanga
|
||||
repo_gitignore_helper=Roghnaigh teimpléid .gitignore.
|
||||
repo_gitignore_helper_desc=Roghnaigh na comhaid nach bhfuil le rianú ó liosta teimpléid do theangacha coitianta. Cuirtear déantáin tipiciúla a ghineann uirlisí tógála gach teanga san áireamh ar.gitignore de réir réamhshocraithe.
|
||||
issue_labels=Lipéid Eisiúna
|
||||
@ -1452,12 +1459,10 @@ issues.new.no_items=Gan aon earraí
|
||||
issues.new.milestone=Cloch Mhíle
|
||||
issues.new.no_milestone=Gan Chloch Mhíle
|
||||
issues.new.clear_milestone=Cloch Mhíle soiléir
|
||||
issues.new.open_milestone=Clocha Míle Oscailte
|
||||
issues.new.closed_milestone=Clocha Míle Dúnta
|
||||
issues.new.assignees=Sannaitheoirí
|
||||
issues.new.clear_assignees=Ceannaitheoirí soiléir
|
||||
issues.new.no_assignees=Gan aon Sannaitheoirí
|
||||
issues.new.no_reviewers=Gan athbhreithnithe
|
||||
issues.new.no_reviewers=Gan Léirmheastóirí
|
||||
issues.new.blocked_user=Ní féidir saincheist a chruthú toisc go bhfuil úinéir an stórais bac ort.
|
||||
issues.edit.already_changed=Ní féidir athruithe a shábháil ar an tsaincheist. Dealraíonn sé gur athraigh úsáideoir eile an t-ábhar cheana féin. Athnuachan an leathanach agus déan iarracht eagarthóireacht arís chun a gcuid athruithe a sheachaint
|
||||
issues.edit.blocked_user=Ní féidir ábhar a chur in eagar toisc go bhfuil an póstaer nó úinéir an stórais bac ort.
|
||||
@ -1661,27 +1666,20 @@ issues.comment_on_locked=Ní féidir leat trácht a dhéanamh ar shaincheist fao
|
||||
issues.delete=Scrios
|
||||
issues.delete.title=Scrios an t-eagrán seo?
|
||||
issues.delete.text=An bhfuil tú cinnte gur mhaith leat an cheist seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann)
|
||||
|
||||
issues.tracker=Rianaitheoir Ama
|
||||
issues.start_tracking_short=Tosaigh Uaineoir
|
||||
issues.start_tracking=Rianú Am Tosaigh
|
||||
issues.start_tracking_history=`thosaigh sé ag obair %s`
|
||||
|
||||
issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo
|
||||
issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar <a href="%s">eagrán eile</a>!`
|
||||
issues.stop_tracking=Stop Uaineadóir
|
||||
issues.stop_tracking_history=`stop sé ag obair %s`
|
||||
issues.cancel_tracking=Caith amach
|
||||
issues.cancel_tracking_history=`rianú ama curtha ar ceal %s`
|
||||
issues.add_time=Láimh Cuir Am leis
|
||||
issues.del_time=Scrios an log ama seo
|
||||
issues.add_time_short=Cuir Am leis
|
||||
issues.add_time_cancel=Cealaigh
|
||||
issues.add_time_history=`am caite curtha leis %s`
|
||||
issues.del_time_history=`an t-am caite scriosta %s`
|
||||
issues.add_time_hours=Uaireanta
|
||||
issues.add_time_minutes=Miontuairi
|
||||
issues.add_time_sum_to_small=Níor iontráilíodh aon am.
|
||||
issues.time_spent_total=An t-am iomlán a chaitear
|
||||
issues.time_spent_from_all_authors=`Am Iomlán Caitear: %s`
|
||||
|
||||
issues.due_date=Dáta dlite
|
||||
issues.invalid_due_date_format=Ní mór 'bbbb-mm-ll' a bheith i bhformáid an dáta dlite.
|
||||
issues.error_modifying_due_date=Theip ar an dáta dlite a mhodhnú.
|
||||
@ -2460,6 +2458,8 @@ settings.block_on_official_review_requests=Cuir bac ar chumasc ar iarratais ar a
|
||||
settings.block_on_official_review_requests_desc=Ní bheidh sé indéanta cumasc nuair a bhíonn iarratais oifigiúla ar athbhreithniú aige, fiú má tá go leor ceadaithe ann.
|
||||
settings.block_outdated_branch=Cuir bac ar chumasc má tá an t-iarratas tarraingthe as dáta
|
||||
settings.block_outdated_branch_desc=Ní bheidh cumasc indéanta nuair a bhíonn ceannbhrainse taobh thiar de bhronnbhrainse.
|
||||
settings.block_admin_merge_override=Ní mór do riarthóirí rialacha cosanta brainse a leanúint
|
||||
settings.block_admin_merge_override_desc=Ní mór do riarthóirí rialacha cosanta brainse a leanúint agus ní féidir leo dul timpeall air.
|
||||
settings.default_branch_desc=Roghnaigh brainse stóras réamhshocraithe le haghaidh iarratas tarraingte agus geallann an cód:
|
||||
settings.merge_style_desc=Stíleanna Cumaisc
|
||||
settings.default_merge_style_desc=Stíl Cumaisc Réamhshocraithe
|
||||
@ -2579,7 +2579,6 @@ diff.generated=a ghintear
|
||||
diff.vendored=curtha ar fáil
|
||||
diff.comment.add_line_comment=Cuir trácht líne leis
|
||||
diff.comment.placeholder=Fág trácht
|
||||
diff.comment.markdown_info=Tacaítear le stíliú le marcáil.
|
||||
diff.comment.add_single_comment=Cuir trácht aonair leis
|
||||
diff.comment.add_review_comment=Cuir trácht leis
|
||||
diff.comment.start_review=Tosaigh athbhreithniú
|
||||
@ -3514,6 +3513,9 @@ alpine.repository=Eolas Stórais
|
||||
alpine.repository.branches=Brainsí
|
||||
alpine.repository.repositories=Stórais
|
||||
alpine.repository.architectures=Ailtireachtaí
|
||||
arch.repository=Eolas Stórais
|
||||
arch.repository.repositories=Stórais
|
||||
arch.repository.architectures=Ailtireachtaí
|
||||
cargo.registry=Socraigh an clárlann seo sa chomhad cumraíochta lasta (mar shampla <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Chun an pacáiste a shuiteáil ag baint úsáide as Cargo, reáchtáil an t-ordú seo a leanas:
|
||||
chef.registry=Socraigh an clárlann seo i do chomhad <code>~/.chef/config.rb</code>:
|
||||
|
@ -103,6 +103,7 @@ filter.private=Privát
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Hozzáadás
|
||||
|
||||
[filter]
|
||||
|
||||
@ -577,7 +578,6 @@ fork_visibility_helper=A másolt tárolók láthatósága nem változtatható me
|
||||
use_template=Sablon használata
|
||||
generate_repo=Új repozitórium
|
||||
repo_desc=Leírás
|
||||
repo_lang=Nyelv
|
||||
repo_gitignore_helper=Válasszon .gitignore sablont.
|
||||
issue_labels=Hibajegy címkék
|
||||
issue_labels_helper=Válasszon hibajegy címkét.
|
||||
@ -747,12 +747,9 @@ issues.new.no_items=Nincsenek elemek
|
||||
issues.new.milestone=Mérföldkő
|
||||
issues.new.no_milestone=Nincs mérföldkő
|
||||
issues.new.clear_milestone=Mérföldkő eltávolítása
|
||||
issues.new.open_milestone=Nyitott mérföldkövek
|
||||
issues.new.closed_milestone=Lezárt mérföldkövek
|
||||
issues.new.assignees=Megbízottak
|
||||
issues.new.clear_assignees=Megbízottak eltávolítása
|
||||
issues.new.no_assignees=Nincsenek megbízottak
|
||||
issues.new.no_reviewers=Nincs véleményező
|
||||
issues.no_ref=Nincsen ág/címke megadva
|
||||
issues.create=Hibajegy létrehozása
|
||||
issues.new_label=Új címke
|
||||
@ -860,19 +857,15 @@ issues.lock.title=Beszélgetés lezárása ezen a hibajegyen.
|
||||
issues.unlock.title=Hibajegy újranyitása.
|
||||
issues.comment_on_locked=Egy zárolt hibajegyhez nem lehet hozzászólni.
|
||||
issues.delete=Törlés
|
||||
|
||||
issues.tracker=Időzítő
|
||||
issues.start_tracking=Időmérés elkezdése
|
||||
issues.start_tracking_history=`elkezdett dolgozni %s`
|
||||
issues.stop_tracking_history=`abbahagyta a %s`
|
||||
issues.add_time=Idő kézi hozzáadása
|
||||
issues.add_time_short=Idő hozzáadása
|
||||
issues.add_time_cancel=Megszakítva
|
||||
issues.add_time_history=`hozzáadta %s`
|
||||
|
||||
issues.add_time_hours=Óra
|
||||
issues.add_time_minutes=Perc
|
||||
issues.add_time_sum_to_small=Nem volt idő megadva.
|
||||
issues.time_spent_total=Teljes ráfordított idő
|
||||
issues.time_spent_from_all_authors=`Teljes ráfordított idő: %s`
|
||||
|
||||
issues.due_date=Határidő
|
||||
issues.invalid_due_date_format=A határidőt 'éééé-hh-nn' formátumban kell megadni.
|
||||
issues.error_modifying_due_date=Határidő módosítása sikertelen.
|
||||
@ -1114,7 +1107,6 @@ diff.view_file=Fájl megtekintése
|
||||
diff.file_byte_size=Méret
|
||||
diff.file_suppressed=A különbségek nem kerülnek megjelenítésre, mivel a fájl túl nagy
|
||||
diff.comment.placeholder=Hozzászólás létrehozása
|
||||
diff.comment.markdown_info=Támogatja a markdown formázást.
|
||||
diff.comment.add_single_comment=Egyszerű hozzászólás hozzáadása
|
||||
diff.comment.reply=Válasz
|
||||
diff.review.comment=Hozzászólás
|
||||
@ -1600,6 +1592,7 @@ error.not_signed_commit=Nem aláírt commit
|
||||
filter.type=Típus
|
||||
alpine.repository.branches=Ágak
|
||||
alpine.repository.repositories=Tárolók
|
||||
arch.repository.repositories=Tárolók
|
||||
conan.details.repository=Tároló
|
||||
owner.settings.cleanuprules.enabled=Engedélyezett
|
||||
|
||||
|
@ -94,6 +94,7 @@ filter.private=Pribadi
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Tambah
|
||||
|
||||
[filter]
|
||||
|
||||
@ -500,7 +501,6 @@ fork_repo=Cabang Gudang penyimpanan
|
||||
fork_from=Cabang Dari
|
||||
generate_repo=Buat Repositori
|
||||
repo_desc=Deskripsi
|
||||
repo_lang=Bahasa
|
||||
issue_labels=Label Masalah
|
||||
issue_labels_helper=Pilih serangkaian label masalah.
|
||||
license=Lisensi
|
||||
@ -641,8 +641,6 @@ issues.new.clear_labels=Label yang jelas
|
||||
issues.new.milestone=Tolak ukur waktu
|
||||
issues.new.no_milestone=Tidak Ada Milestone
|
||||
issues.new.clear_milestone=Bersihkan milestone
|
||||
issues.new.open_milestone=Buka Milestone
|
||||
issues.new.closed_milestone=Tutup Milestone
|
||||
issues.no_ref=Tidak Ada Cabang/Tag Ditentukan
|
||||
issues.create=Buat Masalah
|
||||
issues.new_label=Label Baru
|
||||
@ -718,12 +716,11 @@ issues.attachment.download=`Klik untuk mengunduh "%s"`
|
||||
issues.subscribe=Berlangganan
|
||||
issues.unsubscribe=Berhenti berlangganan
|
||||
issues.delete=Hapus
|
||||
issues.start_tracking_history=`mulai bekerja %s`
|
||||
issues.stop_tracking_history=`berhenti bekerja %s`
|
||||
issues.add_time_cancel=Batalkan
|
||||
issues.add_time_history=`tambah menghabiskan waktu %s`
|
||||
|
||||
|
||||
issues.add_time_hours=Jam
|
||||
issues.add_time_minutes=Menit
|
||||
|
||||
issues.due_date_form_edit=Edit
|
||||
issues.due_date_form_remove=Menghapus
|
||||
issues.dependency.cancel=Membatalkan
|
||||
@ -1287,6 +1284,7 @@ error.unit_not_allowed=Anda tidak diizinkan untuk mengunjungi unit repositori in
|
||||
[packages]
|
||||
filter.type=Jenis
|
||||
alpine.repository.repositories=Repositori
|
||||
arch.repository.repositories=Repositori
|
||||
conan.details.repository=Repositori
|
||||
owner.settings.cleanuprules.enabled=Aktif
|
||||
|
||||
|
@ -124,6 +124,7 @@ filter.public=Opinbert
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Bæta við
|
||||
|
||||
[filter]
|
||||
|
||||
@ -566,7 +567,6 @@ download_zip=Sækja ZIP
|
||||
generate_repo=Mynda Hugbúnaðarsafn
|
||||
repo_desc=Lýsing
|
||||
repo_desc_helper=Sláðu inn stutta lýsingu (valfrjálst)
|
||||
repo_lang=Tungumál
|
||||
repo_gitignore_helper=Velja .gitignore sniðmát.
|
||||
repo_gitignore_helper_desc=Veldu hvaða skrár á ekki að rekja af lista sniðmáta fyrir algeng tungumál. Dæmagert rusl sem myndast af byggingarverkfærum hvers tungumáls er sjálfgefið í .gitignore.
|
||||
issue_labels=Vandamálslýsingar
|
||||
@ -811,17 +811,15 @@ issues.unlock_comment=aflæsti þessa umræðu %s
|
||||
issues.lock_confirm=Læsa
|
||||
issues.unlock_confirm=Aflæsa
|
||||
issues.delete=Eyða
|
||||
issues.start_tracking_short=Ræsa Tímamælir
|
||||
issues.add_time=Bæta Bið Tíma Handvirkt
|
||||
issues.add_time_short=Bæta Bið Tíma
|
||||
issues.add_time_cancel=Hætta við
|
||||
issues.add_time_history=`bætti við eyddum tíma %s`
|
||||
|
||||
|
||||
issues.del_time_history=`fjarlægði eyddum tíma %s`
|
||||
issues.add_time_hours=Klukkutímar
|
||||
issues.add_time_minutes=Mínútur
|
||||
issues.add_time_sum_to_small=Enginn tími var sleginn inn.
|
||||
issues.time_spent_total=Heildartíma Eytt
|
||||
issues.time_spent_from_all_authors=`Heildartíma Eytt: %s`
|
||||
|
||||
issues.due_date=Eindagi
|
||||
issues.push_commit_1=bætti við %d framlag %s
|
||||
issues.push_commits_n=bætti við %d framlög %s
|
||||
@ -1313,6 +1311,7 @@ dependency.id=Auðkenni
|
||||
dependency.version=Útgáfa
|
||||
alpine.repository.branches=Greinar
|
||||
alpine.repository.repositories=Hugbúnaðarsöfn
|
||||
arch.repository.repositories=Hugbúnaðarsöfn
|
||||
conan.details.repository=Hugbúnaðarsafn
|
||||
container.details.platform=Vettvangur
|
||||
container.labels=Lýsingar
|
||||
|
@ -130,6 +130,7 @@ filter.private=Privati
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Aggiungi
|
||||
|
||||
[filter]
|
||||
|
||||
@ -777,7 +778,6 @@ generate_repo=Genera repository
|
||||
generate_from=Genera da
|
||||
repo_desc=Descrizione
|
||||
repo_desc_helper=Inserisci una breve descrizione (opzionale)
|
||||
repo_lang=Lingua
|
||||
repo_gitignore_helper=Seleziona i template di .gitignore.
|
||||
repo_gitignore_helper_desc=Scegli di quali file non tenere traccia da un elenco di modelli per le lingue comuni. Gli artefatti tipici generati dagli strumenti di build di ogni lingua sono inclusi su .gitignore per impostazione predefinita.
|
||||
issue_labels=Etichette Issue
|
||||
@ -1095,12 +1095,9 @@ issues.new.no_items=Nessun elemento
|
||||
issues.new.milestone=Traguardo
|
||||
issues.new.no_milestone=Nessuna milestone
|
||||
issues.new.clear_milestone=Milestone pulita
|
||||
issues.new.open_milestone=Apri Milestone
|
||||
issues.new.closed_milestone=Milestone chiuse
|
||||
issues.new.assignees=Assegnatari
|
||||
issues.new.clear_assignees=Cancella assegnatari
|
||||
issues.new.no_assignees=Nessuna assegnatario
|
||||
issues.new.no_reviewers=Nessun revisore
|
||||
issues.choose.get_started=Inizia
|
||||
issues.choose.open_external_link=Apri
|
||||
issues.choose.blank=Default
|
||||
@ -1260,26 +1257,19 @@ issues.comment_on_locked=Non puoi commentare un problema bloccato.
|
||||
issues.delete=Elimina
|
||||
issues.delete.title=Eliminare questo problema?
|
||||
issues.delete.text=Vuoi davvero eliminare questo problema? (Questo rimuoverà permanentemente tutti i contenuti. Considera invece di chiuderlo, se vuoi tenerlo archiviato)
|
||||
|
||||
issues.tracker=Cronografo
|
||||
issues.start_tracking_short=Avvia timer
|
||||
issues.start_tracking=Avvia cronografo
|
||||
issues.start_tracking_history=ha iniziato a lavorare %s
|
||||
|
||||
issues.tracker_auto_close=Il timer verrà interrotto automaticamente una volta che il problema verrá chiuso
|
||||
issues.tracking_already_started=`Hai già avviato il monitoraggio del tempo su <a href="%s">un altro problema</a>!`
|
||||
issues.stop_tracking=Ferma timer
|
||||
issues.stop_tracking_history=`ha smesso di funzionare %s`
|
||||
issues.cancel_tracking=Scarta
|
||||
issues.add_time=Aggiungi Tempo manualmente
|
||||
issues.del_time=Elimina questo registro di tempo
|
||||
issues.add_time_short=Aggiungi tempo
|
||||
issues.add_time_cancel=Annulla
|
||||
issues.add_time_history=`aggiunto tempo trascorso %s`
|
||||
issues.del_time_history=`tempo trascorso eliminato %s`
|
||||
issues.add_time_hours=Ore
|
||||
issues.add_time_minutes=Minuti
|
||||
issues.add_time_sum_to_small=Non è stato inserito alcun tempo.
|
||||
issues.time_spent_total=Tempo totale trascorso
|
||||
issues.time_spent_from_all_authors=`Totale tempo trascorso: %s`
|
||||
|
||||
issues.due_date=Data di scadenza
|
||||
issues.invalid_due_date_format=Il formato della data di scadenza deve essere 'yyyy-mm-dd'.
|
||||
issues.error_modifying_due_date=Impossibile modificare la data di scadenza.
|
||||
@ -1965,7 +1955,6 @@ diff.load=Carica Diff
|
||||
diff.generated=generato
|
||||
diff.vendored=esterno
|
||||
diff.comment.placeholder=Lascia un commento
|
||||
diff.comment.markdown_info=Lo stile con markdown è supportato.
|
||||
diff.comment.add_single_comment=Aggiungi un commento singolo
|
||||
diff.comment.add_review_comment=Aggiungi commento
|
||||
diff.comment.start_review=Inizio revisione
|
||||
@ -2734,6 +2723,7 @@ dependency.version=Versione
|
||||
alpine.install=Per installare il pacchetto, eseguire il seguente comando:
|
||||
alpine.repository.branches=Branches
|
||||
alpine.repository.repositories=Repository
|
||||
arch.repository.repositories=Repository
|
||||
chef.install=Per installare il pacchetto, eseguire il seguente comando:
|
||||
composer.registry=Imposta questo registro nel tuo file <code>~/.composer/config.json</code>:
|
||||
composer.install=Per installare il pacchetto utilizzando Composer, eseguire il seguente comando:
|
||||
|
@ -104,6 +104,7 @@ copy_url=URLをコピー
|
||||
copy_hash=ハッシュをコピー
|
||||
copy_content=内容をコピー
|
||||
copy_branch=ブランチ名をコピー
|
||||
copy_path=パスをコピー
|
||||
copy_success=コピーされました!
|
||||
copy_error=コピーに失敗しました
|
||||
copy_type_unsupported=このファイルタイプはコピーできません
|
||||
@ -209,6 +210,10 @@ buttons.link.tooltip=リンク追加
|
||||
buttons.list.unordered.tooltip=箇条書き追加
|
||||
buttons.list.ordered.tooltip=番号付きリスト追加
|
||||
buttons.list.task.tooltip=タスクリスト追加
|
||||
buttons.table.add.tooltip=テーブルの追加
|
||||
buttons.table.add.insert=追加
|
||||
buttons.table.rows=行
|
||||
buttons.table.cols=列
|
||||
buttons.mention.tooltip=ユーザーまたはチームにメンション
|
||||
buttons.ref.tooltip=イシューまたはプルリクエストを参照
|
||||
buttons.switch_to_legacy.tooltip=レガシーエディタを使用する
|
||||
@ -348,6 +353,7 @@ enable_update_checker=アップデートチェッカーを有効にする
|
||||
enable_update_checker_helper=gitea.ioに接続して定期的に新しいバージョンのリリースを確認します。
|
||||
env_config_keys=環境設定
|
||||
env_config_keys_prompt=以下の環境変数も設定ファイルに適用されます:
|
||||
config_write_file_prompt=これらの設定オプションは %s に書き込まれます
|
||||
|
||||
[home]
|
||||
nav_menu=ナビゲーションメニュー
|
||||
@ -453,6 +459,7 @@ authorize_application=アプリケーションを許可
|
||||
authorize_redirect_notice=このアプリケーションを許可すると %s にリダイレクトします。
|
||||
authorize_application_created_by=このアプリケーションは %s が作成しました。
|
||||
authorize_application_description=アクセスを許可すると、このアプリケーションは、プライベート リポジトリや組織を含むあなたのすべてのアカウント情報に対して、アクセスと書き込みができるようになります。
|
||||
authorize_application_with_scopes=スコープ: %s
|
||||
authorize_title=`"%s"にあなたのアカウントへのアクセスを許可しますか?`
|
||||
authorization_failed=認可失敗
|
||||
authorization_failed_desc=無効なリクエストを検出したため認可が失敗しました。 認可しようとしたアプリの開発者に連絡してください。
|
||||
@ -580,6 +587,8 @@ lang_select_error=言語をリストから選択してください。
|
||||
|
||||
username_been_taken=ユーザー名が既に使用されています。
|
||||
username_change_not_local_user=非ローカルユーザーのユーザー名は変更できません。
|
||||
change_username_disabled=ユーザー名の変更は無効化されています。
|
||||
change_full_name_disabled=フルネームの変更は無効化されています。
|
||||
username_has_not_been_changed=ユーザー名は変更されていません
|
||||
repo_name_been_taken=リポジトリ名が既に使用されています。
|
||||
repository_force_private=強制プライベートが有効です。プライベートリポジトリはパブリックにできません。
|
||||
@ -705,6 +714,8 @@ public_profile=公開プロフィール
|
||||
biography_placeholder=自己紹介してください!(Markdownを使うことができます)
|
||||
location_placeholder=おおよその場所を他の人と共有
|
||||
profile_desc=あなたのプロフィールが他のユーザーにどのように表示されるかを制御します。あなたのプライマリメールアドレスは、通知、パスワードの回復、WebベースのGit操作に使用されます。
|
||||
password_username_disabled=ユーザー名の変更は許可されていません。詳細はサイト管理者にお問い合わせください。
|
||||
password_full_name_disabled=フルネームの変更は許可されていません。詳細はサイト管理者にお問い合わせください。
|
||||
full_name=フルネーム
|
||||
website=Webサイト
|
||||
location=場所
|
||||
@ -754,6 +765,7 @@ uploaded_avatar_not_a_image=アップロードしたファイルは画像ファ
|
||||
uploaded_avatar_is_too_big=アップロードされたファイルサイズ(%d KiB) が最大サイズ(%d KiB) を超えています。
|
||||
update_avatar_success=アバターを更新しました。
|
||||
update_user_avatar_success=ユーザーのアバターを更新しました。
|
||||
cropper_prompt=保存する前に画像を編集できます。 編集した画像はPNGで保存されます。
|
||||
|
||||
change_password=パスワードを更新
|
||||
old_password=現在のパスワード
|
||||
@ -1031,7 +1043,6 @@ generate_repo=リポジトリの生成
|
||||
generate_from=他からの生成
|
||||
repo_desc=説明
|
||||
repo_desc_helper=簡単な説明を入力してください (オプション)
|
||||
repo_lang=言語
|
||||
repo_gitignore_helper=.gitignoreテンプレートを選択してください。
|
||||
repo_gitignore_helper_desc=一般的な言語のテンプレートリストから、追跡しないファイルの設定を選択します。 各言語のビルドツールが生成する典型的なファイルが、デフォルトで.gitignoreに含まれます。
|
||||
issue_labels=イシューラベル
|
||||
@ -1448,8 +1459,6 @@ issues.new.no_items=項目なし
|
||||
issues.new.milestone=マイルストーン
|
||||
issues.new.no_milestone=マイルストーンなし
|
||||
issues.new.clear_milestone=マイルストーンをクリア
|
||||
issues.new.open_milestone=オープン中のマイルストーン
|
||||
issues.new.closed_milestone=クローズされたマイルストーン
|
||||
issues.new.assignees=担当者
|
||||
issues.new.clear_assignees=担当者をクリア
|
||||
issues.new.no_assignees=担当者なし
|
||||
@ -1657,27 +1666,20 @@ issues.comment_on_locked=ロックされているイシューにコメントは
|
||||
issues.delete=削除
|
||||
issues.delete.title=このイシューを削除しますか?
|
||||
issues.delete.text=本当にこのイシューを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください)
|
||||
|
||||
issues.tracker=タイムトラッカー
|
||||
issues.start_tracking_short=タイマー 開始
|
||||
issues.start_tracking=タイムトラッキングを開始
|
||||
issues.start_tracking_history=`が作業を開始 %s`
|
||||
|
||||
issues.tracker_auto_close=タイマーは、このイシューがクローズされると自動的に終了します
|
||||
issues.tracking_already_started=`<a href="%s">別のイシュー</a>で既にタイムトラッキングを開始しています!`
|
||||
issues.stop_tracking=タイマー 終了
|
||||
issues.stop_tracking_history=`が作業を終了 %s`
|
||||
issues.cancel_tracking=中止
|
||||
issues.cancel_tracking_history=`がタイムトラッキングを中止 %s`
|
||||
issues.add_time=手で時間を入力
|
||||
issues.del_time=このタイムログを削除
|
||||
issues.add_time_short=時間入力
|
||||
issues.add_time_cancel=キャンセル
|
||||
issues.add_time_history=`が作業時間を追加 %s`
|
||||
issues.del_time_history=`が作業時間を削除 %s`
|
||||
issues.add_time_hours=時間
|
||||
issues.add_time_minutes=分
|
||||
issues.add_time_sum_to_small=時間が入力されていません。
|
||||
issues.time_spent_total=かかった時間の合計
|
||||
issues.time_spent_from_all_authors=`かかった時間の合計: %s`
|
||||
|
||||
issues.due_date=期日
|
||||
issues.invalid_due_date_format=期日は 'yyyy-mm-dd' の形式で入力してください。
|
||||
issues.error_modifying_due_date=期日を変更できませんでした。
|
||||
@ -2456,6 +2458,8 @@ settings.block_on_official_review_requests=公式レビュー依頼でマージ
|
||||
settings.block_on_official_review_requests_desc=公式レビュー依頼があるときは、承認数を満たしていてもマージできないようにします。
|
||||
settings.block_outdated_branch=遅れているプルリクエストのマージをブロック
|
||||
settings.block_outdated_branch_desc=baseブランチがheadブランチより進んでいる場合、マージできないようにします。
|
||||
settings.block_admin_merge_override=管理者もブランチ保護のルールに従う
|
||||
settings.block_admin_merge_override_desc=管理者はブランチ保護のルールに従う必要があり、回避することはできません。
|
||||
settings.default_branch_desc=プルリクエストやコミット表示のデフォルトのブランチを選択:
|
||||
settings.merge_style_desc=マージ スタイル
|
||||
settings.default_merge_style_desc=デフォルトのマージスタイル
|
||||
@ -2575,7 +2579,6 @@ diff.generated=generated
|
||||
diff.vendored=vendored
|
||||
diff.comment.add_line_comment=行コメントを追加
|
||||
diff.comment.placeholder=コメントを残す
|
||||
diff.comment.markdown_info=Markdownによる書式設定をサポートしています。
|
||||
diff.comment.add_single_comment=単独のコメントを追加
|
||||
diff.comment.add_review_comment=コメントを追加
|
||||
diff.comment.start_review=レビュー開始
|
||||
@ -3510,6 +3513,9 @@ alpine.repository=リポジトリ情報
|
||||
alpine.repository.branches=Branches
|
||||
alpine.repository.repositories=Repositories
|
||||
alpine.repository.architectures=Architectures
|
||||
arch.repository=リポジトリ情報
|
||||
arch.repository.repositories=リポジトリ
|
||||
arch.repository.architectures=Architectures
|
||||
cargo.registry=Cargo 設定ファイルでこのレジストリをセットアップします。(例 <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Cargo を使用してパッケージをインストールするには、次のコマンドを実行します:
|
||||
chef.registry=あなたの <code>~/.chef/config.rb</code> ファイルに、このレジストリをセットアップします:
|
||||
|
@ -95,6 +95,7 @@ filter.private=비공개
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=추가
|
||||
|
||||
[filter]
|
||||
|
||||
@ -544,7 +545,6 @@ fork_from=원본 프로젝트 :
|
||||
fork_visibility_helper=포크된 저장소의 가시성은 변경하실 수 없습니다.
|
||||
use_template=이 템플릿을 사용
|
||||
repo_desc=설명
|
||||
repo_lang=언어
|
||||
repo_gitignore_helper=.gitignore 템플릿 선택
|
||||
license=라이센스
|
||||
license_helper=라이센스 파일을 선택해주세요.
|
||||
@ -680,8 +680,6 @@ issues.new.clear_labels=레이블 초기화
|
||||
issues.new.milestone=마일스톤
|
||||
issues.new.no_milestone=마일스톤 없음
|
||||
issues.new.clear_milestone=마일스톤 초기화
|
||||
issues.new.open_milestone=마일스톤 생성
|
||||
issues.new.closed_milestone=마일스톤 닫기
|
||||
issues.new.assignees=담당자
|
||||
issues.new.clear_assignees=담당자 초기화
|
||||
issues.new.no_assignees=담당자 없음
|
||||
@ -773,19 +771,15 @@ issues.attachment.download=' "%s"를 다운로드 하려면 클릭 하십시오
|
||||
issues.subscribe=구독하기
|
||||
issues.unsubscribe=구독 취소
|
||||
issues.delete=삭제
|
||||
|
||||
issues.tracker=타임 트래커
|
||||
issues.start_tracking=타임 트래킹 시작
|
||||
issues.start_tracking_history=`%s가 작업 시작`
|
||||
issues.stop_tracking_history=`작업 중단 %s`
|
||||
issues.add_time=수동으로 시간 입력
|
||||
issues.add_time_short=시간 입력
|
||||
issues.add_time_cancel=취소
|
||||
issues.add_time_history=`사용 시간이 추가됨 %s`
|
||||
|
||||
issues.add_time_hours=시간
|
||||
issues.add_time_minutes=분
|
||||
issues.add_time_sum_to_small=시간이 입력되지 않았습니다.
|
||||
issues.time_spent_total=총 경과된 시간
|
||||
issues.time_spent_from_all_authors=`총 경과된 시간: %s`
|
||||
|
||||
issues.due_date=마감일
|
||||
issues.invalid_due_date_format="마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다."
|
||||
issues.error_modifying_due_date="마감일 수정을 실패하였습니다."
|
||||
@ -1546,6 +1540,7 @@ error.unit_not_allowed=이 저장소 섹션에 접근할 수 없습니다.
|
||||
[packages]
|
||||
filter.type=유형
|
||||
alpine.repository.repositories=저장소
|
||||
arch.repository.repositories=저장소
|
||||
conan.details.repository=저장소
|
||||
owner.settings.cleanuprules.enabled=활성화됨
|
||||
|
||||
|
@ -175,6 +175,7 @@ buttons.link.tooltip=Pievienot saiti
|
||||
buttons.list.unordered.tooltip=Pievienot sarakstu
|
||||
buttons.list.ordered.tooltip=Pievienot numurētu sarakstu
|
||||
buttons.list.task.tooltip=Pievienot uzdevumu sarakstu
|
||||
buttons.table.add.insert=Pievienot
|
||||
buttons.mention.tooltip=Pieminēt lietotāju vai komandu
|
||||
buttons.ref.tooltip=Atsaukties uz problēmu vai izmaiņu pieprasījumu
|
||||
buttons.switch_to_legacy.tooltip=Izmantot vēsturisko redaktoru
|
||||
@ -939,7 +940,6 @@ generate_repo=Ģenerēt repozitoriju
|
||||
generate_from=Ģenerēt no
|
||||
repo_desc=Apraksts
|
||||
repo_desc_helper=Ievadiet īsu aprakstu (neobligāts)
|
||||
repo_lang=Valoda
|
||||
repo_gitignore_helper=Izvēlieties .gitignore sagatavi.
|
||||
repo_gitignore_helper_desc=Izvēlieties kādi faili netiks glabāti repozitorijā no sagatavēm biežāk lietotājām valodām. Pēc noklusējuma .gitignore iekļauj valodu kompilācijas rīku artifaktus.
|
||||
issue_labels=Problēmu iezīmes
|
||||
@ -1330,12 +1330,9 @@ issues.new.no_items=Nav vienumu
|
||||
issues.new.milestone=Atskaites punkts
|
||||
issues.new.no_milestone=Nav atskaites punktu
|
||||
issues.new.clear_milestone=Notīrīt atskaites punktus
|
||||
issues.new.open_milestone=Atvērtie atskaites punktus
|
||||
issues.new.closed_milestone=Aizvērtie atskaites punkti
|
||||
issues.new.assignees=Atbildīgie
|
||||
issues.new.clear_assignees=Noņemt atbildīgo
|
||||
issues.new.no_assignees=Nav atbildīgo
|
||||
issues.new.no_reviewers=Nav recenzentu
|
||||
issues.choose.get_started=Sākt darbu
|
||||
issues.choose.open_external_link=Atvērt
|
||||
issues.choose.blank=Noklusējuma
|
||||
@ -1532,27 +1529,20 @@ issues.comment_on_locked=Jūs nevarat komentēt slēgtai problēmai.
|
||||
issues.delete=Dzēst
|
||||
issues.delete.title=Dzēst šo problēmu?
|
||||
issues.delete.text=Vai patiešām vēlaties dzēst šo problemu? (Neatgriezeniski tiks izdzēsts viss saturs. Apsveriet iespēju to aizvērt, ja vēlaties informāciju saglabāt vēsturei)
|
||||
|
||||
issues.tracker=Laika uzskaite
|
||||
issues.start_tracking_short=Uzsākt taimeri
|
||||
issues.start_tracking=Uzsākt laika uzskaiti
|
||||
issues.start_tracking_history=` uzsāka darbu %s`
|
||||
|
||||
issues.tracker_auto_close=Taimeris tiks automātiski apturēts, kad šī problēma tiks aizvērta
|
||||
issues.tracking_already_started=`Jau ir uzsākta laika uzskaite par <a href="%s">citu problēmu</a>!`
|
||||
issues.stop_tracking=Apturēt taimeri
|
||||
issues.stop_tracking_history=` beidza strādāt %s`
|
||||
issues.cancel_tracking=Atmest
|
||||
issues.cancel_tracking_history=`atcēla laika uzskaiti %s`
|
||||
issues.add_time=Manuāli pievienot laiku
|
||||
issues.del_time=Dzēst šo laika žurnāla ierakstu
|
||||
issues.add_time_short=Pievienot laiku
|
||||
issues.add_time_cancel=Atcelt
|
||||
issues.add_time_history=` pievienoja patērēto laiku %s`
|
||||
issues.del_time_history=`dzēsts patērētais laiks %s`
|
||||
issues.add_time_hours=Stundas
|
||||
issues.add_time_minutes=Minūtes
|
||||
issues.add_time_sum_to_small=Nav norādīts laiks.
|
||||
issues.time_spent_total=Kopējais patērētais laiks
|
||||
issues.time_spent_from_all_authors=`Kopējais patērētais laiks: %s`
|
||||
|
||||
issues.due_date=Izpildes termiņš
|
||||
issues.invalid_due_date_format=Izpildes termiņam ir jābūt formāta 'yyyy-mm-dd'.
|
||||
issues.error_modifying_due_date=Neizdevās izmainīt izpildes termiņu.
|
||||
@ -2358,7 +2348,6 @@ diff.generated=ģenerēts
|
||||
diff.vendored=ārējs
|
||||
diff.comment.add_line_comment=Pievienot rindas komentāru
|
||||
diff.comment.placeholder=Ievadiet komentāru
|
||||
diff.comment.markdown_info=Tiek atbalstīta formatēšana ar Markdown.
|
||||
diff.comment.add_single_comment=Pievienot vienu komentāru
|
||||
diff.comment.add_review_comment=Pievienot komentāru
|
||||
diff.comment.start_review=Sākt recenziju
|
||||
@ -2702,7 +2691,6 @@ dashboard.gc_times=GC reizes
|
||||
dashboard.update_checker=Atjauninājumu pārbaudītājs
|
||||
dashboard.delete_old_system_notices=Dzēst vecos sistēmas paziņojumus no datubāzes
|
||||
dashboard.gc_lfs=Veikt atkritumu uzkopšanas darbus LFS meta objektiem
|
||||
dashboard.sync_branch.started=Sākta atzaru sinhronizācija
|
||||
dashboard.rebuild_issue_indexer=Pārbūvēt problēmu indeksu
|
||||
|
||||
users.user_manage_panel=Lietotāju kontu pārvaldība
|
||||
@ -3236,6 +3224,9 @@ alpine.install=Lai uzstādītu pakotni, ir jāizpilda šī komanda:
|
||||
alpine.repository=Repozitorija informācija
|
||||
alpine.repository.repositories=Repozitoriji
|
||||
alpine.repository.architectures=Arhitektūras
|
||||
arch.repository=Repozitorija informācija
|
||||
arch.repository.repositories=Repozitoriji
|
||||
arch.repository.architectures=Arhitektūras
|
||||
cargo.registry=Uzstādiet šo reģistru Cargo konfigurācijas failā, piemēram, <code>~/.cargo/config.toml</code>:
|
||||
cargo.install=Lai instalētu Cargo pakotni, izpildiet sekojošu komandu:
|
||||
chef.registry=Uzstādiet šo reģistru failā <code>~/.chef/config.rb</code>:
|
||||
|
@ -129,6 +129,7 @@ filter.private=Prive
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Toevoegen
|
||||
|
||||
[filter]
|
||||
|
||||
@ -775,7 +776,6 @@ generate_repo=Repository genereren
|
||||
generate_from=Genereer van
|
||||
repo_desc=Omschrijving
|
||||
repo_desc_helper=Voer korte beschrijving in (optioneel)
|
||||
repo_lang=Taal
|
||||
repo_gitignore_helper=Selecteer .gitignore templates.
|
||||
repo_gitignore_helper_desc=Kies welke bestanden niet bij te houden vanuit een lijst met sjablonen voor alledaagse talen. Gebruikelijke artefacten gegenereerd door de build tools van elke taal zijn standaard inbegrepen met .gitignore.
|
||||
issue_labels=Issuelabels
|
||||
@ -1093,12 +1093,9 @@ issues.new.no_items=Geen items
|
||||
issues.new.milestone=Mijlpaal
|
||||
issues.new.no_milestone=Geen mijlpaal
|
||||
issues.new.clear_milestone=Verwijder mijlpaal
|
||||
issues.new.open_milestone=Open mijlpalen
|
||||
issues.new.closed_milestone=Gesloten mijlpalen
|
||||
issues.new.assignees=Toegewezen aan
|
||||
issues.new.clear_assignees=Verwijder toegewezen aan
|
||||
issues.new.no_assignees=Niet toegewezen
|
||||
issues.new.no_reviewers=Geen beoordelaars
|
||||
issues.choose.get_started=Ga aan de slag
|
||||
issues.choose.open_external_link=Open
|
||||
issues.choose.blank=Standaard
|
||||
@ -1257,26 +1254,19 @@ issues.comment_on_locked=Je kunt geen commentaar geven op een vergrendeld proble
|
||||
issues.delete=Verwijderen
|
||||
issues.delete.title=Deze issue verwijderen?
|
||||
issues.delete.text=Wilt u deze issue echt verwijderen? (Dit is permanent en verwijdert alle inhoud. Overweeg om deze issue te sluiten, als u liever deze als archief wilt bijhouden)
|
||||
|
||||
issues.tracker=Tijdregistratie
|
||||
issues.start_tracking_short=Start timer
|
||||
issues.start_tracking=Start tijdregistratie
|
||||
issues.start_tracking_history=`%s is begonnen`
|
||||
|
||||
issues.tracker_auto_close=Timer wordt automatisch gestopt wanneer dit probleem wordt gesloten
|
||||
issues.tracking_already_started=`Je houd al tijd bij voor <a href="%s">een ander issue</a>!`
|
||||
issues.stop_tracking=Stop timer
|
||||
issues.stop_tracking_history=`gestopt met werken aan %s`
|
||||
issues.cancel_tracking=Weggooien
|
||||
issues.add_time=Tijd handmatig toevoegen
|
||||
issues.del_time=Verwijder deze tijdlog
|
||||
issues.add_time_short=Timer toevoegen
|
||||
issues.add_time_cancel=Annuleren
|
||||
issues.add_time_history=`heeft besteedde tijd toegevoegd: %s`
|
||||
issues.del_time_history=`heeft besteedde tijd verwijderd: %s`
|
||||
issues.add_time_hours=Uren
|
||||
issues.add_time_minutes=Minuten
|
||||
issues.add_time_sum_to_small=Geen tijd opgegeven.
|
||||
issues.time_spent_total=Totaal besteedde tijd
|
||||
issues.time_spent_from_all_authors=`Totaal besteedde tijd: %s`
|
||||
|
||||
issues.due_date=Vervaldatum
|
||||
issues.invalid_due_date_format=Het formaat van de deadline is moet 'jjjj-mm-dd' zijn.
|
||||
issues.error_modifying_due_date=Deadline aanpassen mislukt.
|
||||
@ -1900,7 +1890,6 @@ diff.load=Laad Diff
|
||||
diff.generated=gegenereerd
|
||||
diff.vendored=vendored
|
||||
diff.comment.placeholder=Opmerking toevoegen
|
||||
diff.comment.markdown_info=Styling met markdown wordt ondersteund.
|
||||
diff.comment.add_single_comment=Één reactie toevoegen
|
||||
diff.comment.add_review_comment=Voeg commentaar toe
|
||||
diff.comment.start_review=Review starten
|
||||
@ -2523,6 +2512,7 @@ error.unit_not_allowed=U heeft geen toegang tot deze sectie van de repository.
|
||||
filter.type=Type
|
||||
assets=Assets
|
||||
alpine.repository.repositories=Repositories
|
||||
arch.repository.repositories=Repositories
|
||||
conan.details.repository=Opslagplaats
|
||||
rubygems.required.ruby=Vereist Ruby versie
|
||||
rubygems.required.rubygems=Vereist RubyGem versie
|
||||
|
@ -126,6 +126,7 @@ filter.private=Prywatne
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Dodaj
|
||||
|
||||
[filter]
|
||||
|
||||
@ -732,7 +733,6 @@ generate_repo=Generuj repozytorium
|
||||
generate_from=Generuj z
|
||||
repo_desc=Opis
|
||||
repo_desc_helper=Wprowadź krótki opis (opcjonalnie)
|
||||
repo_lang=Język
|
||||
repo_gitignore_helper=Wybierz szablony pliku .gitignore.
|
||||
issue_labels=Etykiety zgłoszenia
|
||||
issue_labels_helper=Wybierz zestaw etykiet zgłoszeń.
|
||||
@ -1012,12 +1012,9 @@ issues.new.no_items=Brak elementów
|
||||
issues.new.milestone=Kamień milowy
|
||||
issues.new.no_milestone=Brak kamienia milowego
|
||||
issues.new.clear_milestone=Wyczyść kamień milowy
|
||||
issues.new.open_milestone=Otwarte kamienie milowe
|
||||
issues.new.closed_milestone=Zamknięte kamienie milowe
|
||||
issues.new.assignees=Przypisani
|
||||
issues.new.clear_assignees=Usuń przypisanych
|
||||
issues.new.no_assignees=Brak przypisanych
|
||||
issues.new.no_reviewers=Brak recenzentów
|
||||
issues.choose.get_started=Rozpocznij
|
||||
issues.choose.open_external_link=Otwórz
|
||||
issues.choose.blank=Domyślny
|
||||
@ -1158,23 +1155,18 @@ issues.lock.title=Zablokuj konwersację w tym zgłoszeniu.
|
||||
issues.unlock.title=Odblokuj konwersację w tym zgłoszeniu.
|
||||
issues.comment_on_locked=Nie możesz umieszczać komentarzy pod zablokowanym zgłoszeniem.
|
||||
issues.delete=Usuń
|
||||
|
||||
issues.tracker=Śledzenie czasu
|
||||
issues.start_tracking=Rozpocznij śledzenie czasu
|
||||
issues.start_tracking_history=`rozpoczął(-ęła) pracę nad %s`
|
||||
|
||||
issues.tracker_auto_close=Licznik czasu zostanie automatycznie zatrzymany w momencie zamknięcia tego zgłoszenia
|
||||
issues.stop_tracking_history=`zakończył(-a) pracę nad %s`
|
||||
issues.cancel_tracking=Odrzuć
|
||||
issues.add_time=Dodaj czas ręcznie
|
||||
issues.del_time=Usuń ten dziennik czasu
|
||||
issues.add_time_short=Dodaj czas
|
||||
issues.add_time_cancel=Anuluj
|
||||
issues.add_time_history=`dodał(-a) spędzony czas %s`
|
||||
issues.del_time_history=usunął(-ęła) spędzony czas %s
|
||||
issues.add_time_hours=Godziny
|
||||
issues.add_time_minutes=Minuty
|
||||
issues.add_time_sum_to_small=Czas nie został wprowadzony.
|
||||
issues.time_spent_total=Całkowity spędzony czas
|
||||
issues.time_spent_from_all_authors=`Całkowity spędzony czas: %s`
|
||||
|
||||
issues.due_date=Termin realizacji
|
||||
issues.invalid_due_date_format=Format terminu realizacji musi mieć wartość 'rrrr-mm-dd'.
|
||||
issues.error_modifying_due_date=Nie udało się zmodyfikować terminu realizacji.
|
||||
@ -1759,7 +1751,6 @@ diff.file_byte_size=Rozmiar
|
||||
diff.file_suppressed=Plik diff jest za duży
|
||||
diff.generated=wygenerowano
|
||||
diff.comment.placeholder=Zostaw komentarz
|
||||
diff.comment.markdown_info=Formatowanie przy użyciu Markdown jest wspierane.
|
||||
diff.comment.add_single_comment=Dodaj jeden komentarz
|
||||
diff.comment.add_review_comment=Dodaj komentarz
|
||||
diff.comment.start_review=Rozpocznij recenzję
|
||||
@ -2416,6 +2407,7 @@ error.unit_not_allowed=Nie masz uprawnień do tej sekcji repozytorium.
|
||||
[packages]
|
||||
filter.type=Typ
|
||||
alpine.repository.repositories=Repozytoria
|
||||
arch.repository.repositories=Repozytoria
|
||||
conan.details.repository=Repozytorium
|
||||
owner.settings.cleanuprules.enabled=Włączone
|
||||
|
||||
|
@ -172,6 +172,7 @@ buttons.link.tooltip=Adicionar um link
|
||||
buttons.list.unordered.tooltip=Adicionar uma lista com marcadores
|
||||
buttons.list.ordered.tooltip=Adicionar uma lista numerada
|
||||
buttons.list.task.tooltip=Adicionar uma lista de tarefas
|
||||
buttons.table.add.insert=Adicionar
|
||||
buttons.mention.tooltip=Mencionar um usuário ou equipe
|
||||
buttons.ref.tooltip=Referenciar um issue ou um pull request
|
||||
buttons.switch_to_legacy.tooltip=Em vez disso, usar o editor legado
|
||||
@ -934,7 +935,6 @@ generate_repo=Gerar repositório
|
||||
generate_from=Gerar de
|
||||
repo_desc=Descrição
|
||||
repo_desc_helper=Digite uma breve descrição (opcional)
|
||||
repo_lang=Linguagem
|
||||
repo_gitignore_helper=Selecione modelos do .gitignore.
|
||||
repo_gitignore_helper_desc=Escolha os arquivos que não serão rastreados da lista de modelos para linguagens comuns. Artefatos típicos gerados pelos compiladores de cada linguagem estão incluídos no .gitignore por padrão.
|
||||
issue_labels=Etiquetas de issue
|
||||
@ -1322,12 +1322,9 @@ issues.new.no_items=Nenhum item
|
||||
issues.new.milestone=Marco
|
||||
issues.new.no_milestone=Sem marco
|
||||
issues.new.clear_milestone=Limpar marco
|
||||
issues.new.open_milestone=Marcos abertos
|
||||
issues.new.closed_milestone=Marcos fechados
|
||||
issues.new.assignees=Responsáveis
|
||||
issues.new.clear_assignees=Limpar responsáveis
|
||||
issues.new.no_assignees=Sem responsável
|
||||
issues.new.no_reviewers=Sem revisor
|
||||
issues.choose.get_started=Primeiros passos
|
||||
issues.choose.open_external_link=Abrir
|
||||
issues.choose.blank=Padrão
|
||||
@ -1522,27 +1519,20 @@ issues.comment_on_locked=Você não pode comentar em uma issue bloqueada.
|
||||
issues.delete=Apagar
|
||||
issues.delete.title=Apagar esta issue?
|
||||
issues.delete.text=Você realmente deseja excluir esta issue? (Isto irá remover permanentemente todo o conteúdo. Considere fechá-la em vez disso, se você pretende mantê-la arquivado)
|
||||
|
||||
issues.tracker=Contador de tempo
|
||||
issues.start_tracking_short=Iniciar Cronômetro
|
||||
issues.start_tracking=Iniciar Cronômetro
|
||||
issues.start_tracking_history=`começou a trabalhar %s`
|
||||
|
||||
issues.tracker_auto_close=Contador de tempo será parado automaticamente quando esta issue for fechada
|
||||
issues.tracking_already_started=`Você já iniciou o cronômetro em <a href="%s">outra issue</a>!`
|
||||
issues.stop_tracking=Parar Cronômetro
|
||||
issues.stop_tracking_history=`parou de trabalhar %s`
|
||||
issues.cancel_tracking=Descartar
|
||||
issues.cancel_tracking_history=`cronômetro cancelado %s`
|
||||
issues.add_time=Adicionar tempo manualmente
|
||||
issues.del_time=Apagar este registro de tempo
|
||||
issues.add_time_short=Adicionar tempo
|
||||
issues.add_time_cancel=Cancelar
|
||||
issues.add_time_history=`adicionou tempo gasto %s`
|
||||
issues.del_time_history=`removeu tempo gasto %s`
|
||||
issues.add_time_hours=Horas
|
||||
issues.add_time_minutes=Minutos
|
||||
issues.add_time_sum_to_small=Nenhum tempo foi inserido.
|
||||
issues.time_spent_total=Tempo total gasto
|
||||
issues.time_spent_from_all_authors=`Tempo total gasto: %s`
|
||||
|
||||
issues.due_date=Data limite
|
||||
issues.invalid_due_date_format=Formato da data limite inválido, deve ser 'dd/mm/aaaa'.
|
||||
issues.error_modifying_due_date=Falha ao modificar a data limite.
|
||||
@ -2319,7 +2309,6 @@ diff.load=Carregar Diff
|
||||
diff.generated=gerado
|
||||
diff.vendored=externo
|
||||
diff.comment.placeholder=Deixe um comentário
|
||||
diff.comment.markdown_info=Estilo com markdown é suportado.
|
||||
diff.comment.add_single_comment=Adicionar um único comentário
|
||||
diff.comment.add_review_comment=Adicionar comentário
|
||||
diff.comment.start_review=Iniciar revisão
|
||||
@ -3173,6 +3162,9 @@ alpine.install=Para instalar o pacote, execute o seguinte comando:
|
||||
alpine.repository=Informações do repositório
|
||||
alpine.repository.repositories=Repositórios
|
||||
alpine.repository.architectures=Arquiteturas
|
||||
arch.repository=Informações do repositório
|
||||
arch.repository.repositories=Repositórios
|
||||
arch.repository.architectures=Arquiteturas
|
||||
cargo.registry=Configurar este registro no arquivo de configuração de Cargo (por exemplo <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Para instalar o pacote usando Cargo, execute o seguinte comando:
|
||||
chef.registry=Configure este registro em seu arquivo <code>~/.chef/config.rb</code>:
|
||||
|
@ -104,6 +104,7 @@ copy_url=Copiar URL
|
||||
copy_hash=Copiar hash
|
||||
copy_content=Copiar conteúdo
|
||||
copy_branch=Copiar nome do ramo
|
||||
copy_path=Copiar caminho
|
||||
copy_success=Copiado!
|
||||
copy_error=Falha ao copiar
|
||||
copy_type_unsupported=Este tipo de ficheiro não pode ser copiado
|
||||
@ -144,6 +145,7 @@ confirm_delete_selected=Confirma a exclusão de todos os itens marcados?
|
||||
|
||||
name=Nome
|
||||
value=Valor
|
||||
readme=Leia-me
|
||||
|
||||
filter=Filtro
|
||||
filter.clear=Retirar filtro
|
||||
@ -209,6 +211,10 @@ buttons.link.tooltip=Adicionar uma ligação
|
||||
buttons.list.unordered.tooltip=Adicionar uma lista de pontos
|
||||
buttons.list.ordered.tooltip=Adicionar uma lista numerada
|
||||
buttons.list.task.tooltip=Adicionar uma lista de tarefas
|
||||
buttons.table.add.tooltip=Adicionar uma tabela
|
||||
buttons.table.add.insert=Adicionar
|
||||
buttons.table.rows=Linhas
|
||||
buttons.table.cols=Colunas
|
||||
buttons.mention.tooltip=Mencionar um utilizador ou uma equipa
|
||||
buttons.ref.tooltip=Referenciar uma questão ou um pedido de integração
|
||||
buttons.switch_to_legacy.tooltip=Usar o editor clássico
|
||||
@ -348,6 +354,7 @@ enable_update_checker=Habilitar verificador de novidades
|
||||
enable_update_checker_helper=Verifica, periodicamente, se foi lançada alguma versão nova, fazendo uma ligação ao gitea.io.
|
||||
env_config_keys=Configuração do ambiente
|
||||
env_config_keys_prompt=As seguintes variáveis de ambiente também serão aplicadas ao seu ficheiro de configuração:
|
||||
config_write_file_prompt=Estas opções de configuração serão escritas em: %s
|
||||
|
||||
[home]
|
||||
nav_menu=Menu de navegação
|
||||
@ -453,6 +460,7 @@ authorize_application=Autorizar aplicação
|
||||
authorize_redirect_notice=Irá ser reencaminhado para %s se autorizar esta aplicação.
|
||||
authorize_application_created_by=Esta aplicação foi criada por %s.
|
||||
authorize_application_description=Se conceder acesso, a aplicação terá privilégios para alterar toda a informação da conta, incluindo repositórios e organizações privados.
|
||||
authorize_application_with_scopes=Com âmbitos: %s
|
||||
authorize_title=Autorizar o acesso de "%s" à sua conta?
|
||||
authorization_failed=A autorização falhou
|
||||
authorization_failed_desc=A autorização falhou porque encontrámos um pedido inválido. Entre em contacto com o responsável pela aplicação que tentou autorizar.
|
||||
@ -758,6 +766,7 @@ uploaded_avatar_not_a_image=O ficheiro carregado não é uma imagem.
|
||||
uploaded_avatar_is_too_big=O tamanho do ficheiro carregado (%d KiB) excede o tamanho máximo (%d KiB).
|
||||
update_avatar_success=O seu avatar foi substituído.
|
||||
update_user_avatar_success=O avatar do utilizador foi modificado.
|
||||
cropper_prompt=Pode editar a imagem antes de a guardar. A imagem editada será guardada como PNG.
|
||||
|
||||
change_password=Substituir a senha
|
||||
old_password=Senha corrente
|
||||
@ -1024,6 +1033,8 @@ fork_to_different_account=Fazer uma derivação para uma conta diferente
|
||||
fork_visibility_helper=A visibilidade de um repositório derivado não poderá ser alterada posteriormente.
|
||||
fork_branch=Ramo a ser clonado para a derivação
|
||||
all_branches=Todos os ramos
|
||||
view_all_branches=Ver todos os ramos
|
||||
view_all_tags=Ver todas as etiquetas
|
||||
fork_no_valid_owners=Não pode fazer uma derivação deste repositório porque não existem proprietários válidos.
|
||||
fork.blocked_user=Não pode derivar o repositório porque foi bloqueado/a pelo/a proprietário/a do repositório.
|
||||
use_template=Usar este modelo
|
||||
@ -1035,7 +1046,8 @@ generate_repo=Gerar repositório
|
||||
generate_from=Gerar a partir de
|
||||
repo_desc=Descrição
|
||||
repo_desc_helper=Insira uma descrição curta (opcional)
|
||||
repo_lang=Idioma
|
||||
repo_no_desc=Descrição não fornecida
|
||||
repo_lang=Idiomas
|
||||
repo_gitignore_helper=Escolher modelos .gitignore.
|
||||
repo_gitignore_helper_desc=Escolha os ficheiros que não são para rastrear, a partir de uma lista de modelos de linguagens comuns. Serão incluídos no ficheiro .gitignore, logo à partida, artefactos típicos gerados pelas ferramentas de construção de cada uma das linguagens.
|
||||
issue_labels=Rótulos para as questões
|
||||
@ -1452,8 +1464,6 @@ issues.new.no_items=Sem itens
|
||||
issues.new.milestone=Etapa
|
||||
issues.new.no_milestone=Sem etapa
|
||||
issues.new.clear_milestone=Limpar etapa
|
||||
issues.new.open_milestone=Etapas abertas
|
||||
issues.new.closed_milestone=Etapas fechadas
|
||||
issues.new.assignees=Encarregados
|
||||
issues.new.clear_assignees=Retirar todos os encarregados
|
||||
issues.new.no_assignees=Sem encarregados
|
||||
@ -1661,27 +1671,34 @@ issues.comment_on_locked=Não pode comentar numa questão bloqueada.
|
||||
issues.delete=Eliminar
|
||||
issues.delete.title=Pretende eliminar esta questão?
|
||||
issues.delete.text=Tem a certeza que quer eliminar esta questão? Isso irá remover todo o conteúdo permanentemente. Como alternativa considere fechá-la, se pretender mantê-la em arquivo.
|
||||
|
||||
issues.tracker=Gestor de tempo
|
||||
issues.start_tracking_short=Iniciar cronómetro
|
||||
issues.start_tracking=Iniciar contagem de tempo
|
||||
issues.start_tracking_history=`começou a trabalhar %s`
|
||||
issues.timetracker_timer_start=Iniciar cronómetro
|
||||
issues.timetracker_timer_stop=Parar cronómetro
|
||||
issues.timetracker_timer_discard=Descartar cronómetro
|
||||
issues.timetracker_timer_manually_add=Adicionar tempo
|
||||
|
||||
issues.time_estimate_placeholder=1h 2m
|
||||
issues.time_estimate_set=Definir tempo estimado
|
||||
issues.time_estimate_display=Estimativa: %s
|
||||
issues.change_time_estimate_at=alterou a estimativa de tempo para <b>%s</b> %s
|
||||
issues.remove_time_estimate_at=removeu a estimativa de tempo %s
|
||||
issues.time_estimate_invalid=O formato da estimativa de tempo é inválido
|
||||
issues.start_tracking_history=começou a trabalhar %s
|
||||
issues.tracker_auto_close=O cronómetro será parado automaticamente quando esta questão for fechada
|
||||
issues.tracking_already_started=`Você já iniciou a contagem de tempo <a href="%s">noutra questão</a>!`
|
||||
issues.stop_tracking=Parar cronómetro
|
||||
issues.stop_tracking_history=`parou de trabalhar %s`
|
||||
issues.cancel_tracking=Descartar
|
||||
issues.stop_tracking_history=trabalhou durante <b>%s</b> %s
|
||||
issues.cancel_tracking_history=`cancelou a contagem de tempo %s`
|
||||
issues.add_time=Adicionar tempo manualmente
|
||||
issues.del_time=Eliminar este registo de tempo
|
||||
issues.add_time_short=Adicionar tempo
|
||||
issues.add_time_cancel=Cancelar
|
||||
issues.add_time_history=`adicionou tempo gasto nesta questão %s`
|
||||
issues.add_time_history=adicionou <b>%s</b> de tempo gasto %s
|
||||
issues.del_time_history=`eliminou o tempo gasto nesta questão %s`
|
||||
issues.add_time_manually=Adicionar tempo manualmente
|
||||
issues.add_time_hours=Horas
|
||||
issues.add_time_minutes=Minutos
|
||||
issues.add_time_sum_to_small=Não foi inserido qualquer tempo.
|
||||
issues.time_spent_total=Total de tempo gasto
|
||||
issues.time_spent_from_all_authors=`Total de tempo gasto: %s`
|
||||
|
||||
issues.due_date=Data de vencimento
|
||||
issues.invalid_due_date_format=O formato da data de vencimento tem que ser 'aaaa-mm-dd'.
|
||||
issues.error_modifying_due_date=Falhou a modificação da data de vencimento.
|
||||
@ -1779,7 +1796,7 @@ issues.reference_link=Referência: %s
|
||||
compare.compare_base=base
|
||||
compare.compare_head=comparar
|
||||
|
||||
pulls.desc=Habilitar pedidos de integração e revisão de código.
|
||||
pulls.desc=Habilitar pedidos de integração e revisão de código-fonte.
|
||||
pulls.new=Novo pedido de integração
|
||||
pulls.new.blocked_user=Não pode criar o pedido de integração porque foi bloqueado/a pelo/a proprietário/a do repositório.
|
||||
pulls.new.must_collaborator=Tem de ser um/a colaborador/a para criar um pedido de integração.
|
||||
@ -1869,7 +1886,7 @@ pulls.rebase_merge_pull_request=Mudar a base e avançar rapidamente
|
||||
pulls.rebase_merge_commit_pull_request=Mudar a base e criar um cometimento de integração
|
||||
pulls.squash_merge_pull_request=Criar cometimento de compactação
|
||||
pulls.fast_forward_only_merge_pull_request=Avançar rapidamente apenas
|
||||
pulls.merge_manually=Integrado manualmente
|
||||
pulls.merge_manually=Integrar manualmente
|
||||
pulls.merge_commit_id=O ID de cometimento da integração
|
||||
pulls.require_signed_wont_sign=O ramo requer que os cometimentos sejam assinados mas esta integração não vai ser assinada
|
||||
|
||||
@ -1928,6 +1945,10 @@ pulls.delete.title=Eliminar este pedido de integração?
|
||||
pulls.delete.text=Tem a certeza que quer eliminar este pedido de integração? Isso irá remover todo o conteúdo permanentemente. Como alternativa considere fechá-lo, se pretender mantê-lo em arquivo.
|
||||
|
||||
pulls.recently_pushed_new_branches=Enviou para o ramo <strong>%[1]s</strong> %[2]s
|
||||
pulls.upstream_diverging_prompt_behind_1=Este ramo está %d cometimento atrás de %s
|
||||
pulls.upstream_diverging_prompt_behind_n=Este ramo está %d cometimentos atrás de %s
|
||||
pulls.upstream_diverging_prompt_base_newer=O ramo base %s tem novas modificações
|
||||
pulls.upstream_diverging_merge=Sincronizar derivação
|
||||
|
||||
pull.deleted_branch=(eliminado):%s
|
||||
pull.agit_documentation=Rever a documentação sobre o AGit
|
||||
@ -2131,7 +2152,7 @@ settings.branches.update_default_branch=Definir o ramo principal
|
||||
settings.branches.add_new_rule=Adicionar nova regra
|
||||
settings.advanced_settings=Configurações avançadas
|
||||
settings.wiki_desc=Habilitar wiki do repositório
|
||||
settings.use_internal_wiki=Usar o wiki nativo
|
||||
settings.use_internal_wiki=Usar o wiki integrado
|
||||
settings.default_wiki_branch_name=Nome do ramo predefinido do wiki
|
||||
settings.default_wiki_everyone_access=Permissão de acesso predefinida para utilizadores registados:
|
||||
settings.failed_to_change_default_wiki_branch=Falhou ao mudar o nome do ramo predefinido do wiki.
|
||||
@ -2140,7 +2161,7 @@ settings.external_wiki_url=URL do wiki externo
|
||||
settings.external_wiki_url_error=O URL do wiki externo não é um URL válido.
|
||||
settings.external_wiki_url_desc=Os visitantes são encaminhados para o URL do wiki externo ao clicar no separador do wiki.
|
||||
settings.issues_desc=Habilitar o seguidor de questões do repositório
|
||||
settings.use_internal_issue_tracker=Usar o seguidor de questões nativo
|
||||
settings.use_internal_issue_tracker=Usar o seguidor de questões integrado
|
||||
settings.use_external_issue_tracker=Usar um seguidor de questões externo
|
||||
settings.external_tracker_url=URL do gestor de questões externo
|
||||
settings.external_tracker_url_error=O URL do gestor de questões externo não é um URL válido.
|
||||
@ -2230,7 +2251,7 @@ settings.wiki_deletion_success=Os dados do repositório do wiki foram eliminados
|
||||
settings.delete=Eliminar este repositório
|
||||
settings.delete_desc=Eliminar um repositório é permanente e não pode ser revertido.
|
||||
settings.delete_notices_1=- Esta operação <strong>NÃO PODERÁ</strong> ser revertida.
|
||||
settings.delete_notices_2=- Esta operação eliminará permanentemente o repositório <strong>%s</strong> incluindo código, questões, comentários, dados do wiki e configurações dos colaboradores.
|
||||
settings.delete_notices_2=- Esta operação eliminará permanentemente o repositório <strong>%s</strong> incluindo código-fonte, questões, comentários, dados do wiki e configurações dos colaboradores.
|
||||
settings.delete_notices_fork_1=- Derivações deste repositório tornar-se-ão independentes, após a eliminação.
|
||||
settings.deletion_success=O repositório foi eliminado.
|
||||
settings.update_settings_success=As configurações do repositório foram modificadas.
|
||||
@ -2460,6 +2481,8 @@ settings.block_on_official_review_requests=Bloquear integração nos pedidos de
|
||||
settings.block_on_official_review_requests_desc=A integração não será possível quando tiver pedidos de revisão oficiais, mesmo que haja aprovações suficientes.
|
||||
settings.block_outdated_branch=Bloquear integração se o pedido de integração for obsoleto
|
||||
settings.block_outdated_branch_desc=A integração não será possível quando o ramo de topo estiver abaixo do ramo base.
|
||||
settings.block_admin_merge_override=Os administradores têm de cumprir as regras de salvaguarda dos ramos
|
||||
settings.block_admin_merge_override_desc=Os administradores têm de cumprir as regras de salvaguarda e não as podem contornar.
|
||||
settings.default_branch_desc=Escolha um ramo do repositório como sendo o predefinido para pedidos de integração e cometimentos:
|
||||
settings.merge_style_desc=Estilos de integração
|
||||
settings.default_merge_style_desc=Tipo de integração predefinido
|
||||
@ -2579,7 +2602,6 @@ diff.generated=gerado
|
||||
diff.vendored=externo
|
||||
diff.comment.add_line_comment=Adicionar comentário de linha
|
||||
diff.comment.placeholder=Deixar um comentário
|
||||
diff.comment.markdown_info=A formatação com markdown é suportada.
|
||||
diff.comment.add_single_comment=Adicionar um único comentário
|
||||
diff.comment.add_review_comment=Adicionar comentário
|
||||
diff.comment.start_review=Iniciar revisão
|
||||
@ -2721,7 +2743,7 @@ create_org=Criar organização
|
||||
repo_updated=Modificado
|
||||
members=Membros
|
||||
teams=Equipas
|
||||
code=Código
|
||||
code=Código-fonte
|
||||
lower_members=membros
|
||||
lower_repositories=repositórios
|
||||
create_new_team=Nova equipa
|
||||
@ -3514,6 +3536,11 @@ alpine.repository=Informação do repositório
|
||||
alpine.repository.branches=Ramos
|
||||
alpine.repository.repositories=Repositórios
|
||||
alpine.repository.architectures=Arquitecturas
|
||||
arch.registry=Adicionar servidor com repositório e arquitectura relacionados a <code>/etc/pacman.conf</code>:
|
||||
arch.install=Sincronizar pacote com pacman:
|
||||
arch.repository=Informação do repositório
|
||||
arch.repository.repositories=Repositórios
|
||||
arch.repository.architectures=Arquitecturas
|
||||
cargo.registry=Configurar este registo no ficheiro de configuração do Cargo (por exemplo: <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Para instalar o pacote usando o Cargo, execute o seguinte comando:
|
||||
chef.registry=Configure este registo no seu ficheiro <code>~/.chef/config.rb</code>:
|
||||
|
@ -170,6 +170,7 @@ buttons.link.tooltip=Добавить ссылку
|
||||
buttons.list.unordered.tooltip=Добавить маркированный список
|
||||
buttons.list.ordered.tooltip=Добавить нумерованный список
|
||||
buttons.list.task.tooltip=Добавить список заданий
|
||||
buttons.table.add.insert=Добавить
|
||||
buttons.mention.tooltip=Упомянуть пользователя или команду
|
||||
buttons.ref.tooltip=Сослаться на задачу или запрос на слияние
|
||||
buttons.switch_to_legacy.tooltip=Использовать старый редактор
|
||||
@ -924,7 +925,6 @@ generate_repo=Создать репозиторий
|
||||
generate_from=Создать из
|
||||
repo_desc=Описание
|
||||
repo_desc_helper=Добавьте краткое описание (необязательно)
|
||||
repo_lang=Язык
|
||||
repo_gitignore_helper=Выберите шаблон .gitignore.
|
||||
repo_gitignore_helper_desc=Выберите из списка шаблонов для популярных языков , какие файлы не надо отслеживать. По умолчанию в .gitignore включены типичные артефакты, создаваемые инструментами сборки каждого языка.
|
||||
issue_labels=Метки задач
|
||||
@ -1301,12 +1301,9 @@ issues.new.no_items=Нет элементов
|
||||
issues.new.milestone=Этап
|
||||
issues.new.no_milestone=Нет этапа
|
||||
issues.new.clear_milestone=Очистить этап
|
||||
issues.new.open_milestone=Открыть этап
|
||||
issues.new.closed_milestone=Завершенные этапы
|
||||
issues.new.assignees=Назначенные
|
||||
issues.new.clear_assignees=Убрать ответственных
|
||||
issues.new.no_assignees=Нет назначенных лиц
|
||||
issues.new.no_reviewers=Нет рецензентов
|
||||
issues.choose.get_started=Начать
|
||||
issues.choose.open_external_link=Открыть
|
||||
issues.choose.blank=По умолчанию
|
||||
@ -1502,27 +1499,20 @@ issues.comment_on_locked=Вы не можете оставить коммент
|
||||
issues.delete=Удалить
|
||||
issues.delete.title=Удалить эту задачу?
|
||||
issues.delete.text=Вы действительно хотите удалить эту задачу? Это навсегда удалит всё содержимое. Возможно лучше закрыть её в архивных целях.
|
||||
|
||||
issues.tracker=Отслеживание времени
|
||||
issues.start_tracking_short=Запустить таймер
|
||||
issues.start_tracking=Начать отслеживание времени
|
||||
issues.start_tracking_history=`начал(а) работать %s`
|
||||
|
||||
issues.tracker_auto_close=Таймер будет остановлен автоматически, когда эта проблема будет закрыта
|
||||
issues.tracking_already_started=`Вы уже начали отслеживать время для <a href="%s">другой задачи</a>!`
|
||||
issues.stop_tracking=Остановить таймер
|
||||
issues.stop_tracking_history=`перестал(а) работать %s`
|
||||
issues.cancel_tracking=Отмена
|
||||
issues.cancel_tracking_history=`отменил(а) отслеживание времени %s`
|
||||
issues.add_time=Вручную добавить время
|
||||
issues.del_time=Удалить этот журнал времени
|
||||
issues.add_time_short=Добавить время
|
||||
issues.add_time_cancel=Отмена
|
||||
issues.add_time_history=`добавил(а) к затраченному времени %s`
|
||||
issues.del_time_history=`удалил(а) потраченное время %s`
|
||||
issues.add_time_hours=Часы
|
||||
issues.add_time_minutes=Минуты
|
||||
issues.add_time_sum_to_small=Время не было введено.
|
||||
issues.time_spent_total=Общее затраченное время
|
||||
issues.time_spent_from_all_authors=`Общее затраченное время: %s`
|
||||
|
||||
issues.due_date=Срок выполнения
|
||||
issues.invalid_due_date_format=Дата окончания должна быть в формате 'гггг-мм-дд'.
|
||||
issues.error_modifying_due_date=Не удалось изменить срок выполнения.
|
||||
@ -2307,7 +2297,6 @@ diff.load=Загрузить разницу
|
||||
diff.generated=сгенерированный
|
||||
diff.vendored=поставляемый
|
||||
diff.comment.placeholder=Оставить комментарий
|
||||
diff.comment.markdown_info=Поддерживается синтаксис Markdown.
|
||||
diff.comment.add_single_comment=Добавить простой комментарий
|
||||
diff.comment.add_review_comment=Добавить комментарий
|
||||
diff.comment.start_review=Начать рецензию
|
||||
@ -3171,6 +3160,9 @@ alpine.install=Чтобы установить пакет, выполните с
|
||||
alpine.repository=О репозитории
|
||||
alpine.repository.repositories=Репозитории
|
||||
alpine.repository.architectures=Архитектуры
|
||||
arch.repository=О репозитории
|
||||
arch.repository.repositories=Репозитории
|
||||
arch.repository.architectures=Архитектуры
|
||||
cargo.registry=Настройте этот реестр в файле конфигурации Cargo (например, <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=Чтобы установить пакет с помощью Cargo, выполните следующую команду:
|
||||
chef.registry=Настройте этот реестр в своём файле <code>~/.chef/config.rb</code>:
|
||||
|
@ -114,6 +114,7 @@ filter.private=පෞද්ගලික
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=එකතු
|
||||
|
||||
[filter]
|
||||
|
||||
@ -713,7 +714,6 @@ generate_repo=ගබඩාව ජනනය කරන්න
|
||||
generate_from=සිට උත්පාදනය
|
||||
repo_desc=සවිස්තරය
|
||||
repo_desc_helper=කෙටි විස්තරයක් ඇතුලත් කරන්න (විකල්ප)
|
||||
repo_lang=භාෂාව
|
||||
repo_gitignore_helper=.gitignore සැකිලි තෝරන්න.
|
||||
repo_gitignore_helper_desc=පොදු භාෂා සඳහා සැකිලි ලැයිස්තුවෙන් සොයා නොගත යුතු ගොනු තෝරන්න. එක් එක් භාෂාව ගොඩ නැගීමේ මෙවලම් මගින් ජනනය කරන ලද සාමාන්ය කෞතුක වස්තු පෙරනිමියෙන් .gitignore මත ඇතුළත් වේ.
|
||||
issue_labels=නිකුත් ලේබල
|
||||
@ -986,12 +986,9 @@ issues.new.closed_projects=සංවෘත ව්යාපෘති
|
||||
issues.new.milestone=සන්ධිස්ථානය
|
||||
issues.new.no_milestone=සන්ධිස්ථානයක් නැත
|
||||
issues.new.clear_milestone=පැහැදිලි සන්ධිස්ථානයක්
|
||||
issues.new.open_milestone=විවෘත සන්ධිස්ථාන
|
||||
issues.new.closed_milestone=සංවෘත සන්ධිස්ථාන
|
||||
issues.new.assignees=සහස්ර
|
||||
issues.new.clear_assignees=පැහැදිලි ඇග්නස්
|
||||
issues.new.no_assignees=කිසිදු සහස්ර
|
||||
issues.new.no_reviewers=විචාරකයින් නැත
|
||||
issues.choose.get_started=ආරම්භ කරන්න
|
||||
issues.choose.open_external_link=විවෘත
|
||||
issues.choose.blank=පෙරනිමි
|
||||
@ -1133,26 +1130,19 @@ issues.lock.title=මෙම ගැටළුව පිළිබඳ ලොක්
|
||||
issues.unlock.title=මෙම ගැටළුව පිළිබඳ සංවාදය අගුළු ඇරීමට.
|
||||
issues.comment_on_locked=අගුලු දමා ඇති ගැටළුවක් පිළිබඳව ඔබට අදහස් දැක්විය නොහැක.
|
||||
issues.delete=මකන්න
|
||||
|
||||
issues.tracker=වේලාව ට්රැකර්
|
||||
issues.start_tracking_short=ටයිමරයට ගැලපෙන ලෙස
|
||||
issues.start_tracking=ආරම්භ වේලාව ට්රැකින්
|
||||
issues.start_tracking_history=`වැඩ ආරම්භ %s`
|
||||
|
||||
issues.tracker_auto_close=මෙම ගැටළුව වසා දැමූ විට ටයිමරයට ස්වයංක්රීයව නතර වේ
|
||||
issues.tracking_already_started=`ඔබ දැනටමත් <a href="%s">හි තවත් නිකුතුවක්</a>හි කාලය සොයා ගැනීම ආරම්භ කර ඇත! `
|
||||
issues.stop_tracking=ටයිමරයට නවත්වන්න
|
||||
issues.stop_tracking_history=`නතර වැඩ %s`
|
||||
issues.cancel_tracking=ඉවතලන්න
|
||||
issues.add_time=අතින් වේලාව එකතු කරන්න
|
||||
issues.del_time=මෙම කාල ලොග් මකන්න
|
||||
issues.add_time_short=කාලය එකතු කරන්න
|
||||
issues.add_time_cancel=අවලංගු කරන්න
|
||||
issues.add_time_history=`එකතු කළ කාලය %s`
|
||||
issues.del_time_history=`මකාදැමුවා කාලය ගත %s`
|
||||
issues.add_time_hours=පැය
|
||||
issues.add_time_minutes=විනාඩි
|
||||
issues.add_time_sum_to_small=කාලයක් ඇතුළු නොවීය.
|
||||
issues.time_spent_total=වැය කළ මුළු කාලය
|
||||
issues.time_spent_from_all_authors=වැය කළ මුළු කාලය: %s
|
||||
|
||||
issues.due_date=නියමිත දිනය
|
||||
issues.invalid_due_date_format=නියමිත දින ආකෘතිය 'yyy-mm-dd' විය යුතුය.
|
||||
issues.error_modifying_due_date=නියමිත දිනය වෙනස් කිරීමට අපොහොසත් විය.
|
||||
@ -1776,7 +1766,6 @@ diff.load=බර අඩු
|
||||
diff.generated=ජනනය
|
||||
diff.vendored=අලෙවි
|
||||
diff.comment.placeholder=අදහසක් හැරයන්න
|
||||
diff.comment.markdown_info=Markdown සමග මෝස්තර සහාය වේ.
|
||||
diff.comment.add_single_comment=තනි අදහස් එක් කරන්න
|
||||
diff.comment.add_review_comment=අදහස එකතු කරන්න
|
||||
diff.comment.start_review=සමාලෝචනය ආරම්භ කරන්න
|
||||
@ -2458,6 +2447,7 @@ no_read=කියවූ දැනුම්දීම් නැත.
|
||||
filter.type=වර්ගය
|
||||
alpine.repository.branches=ශාඛා
|
||||
alpine.repository.repositories=කෝෂ්ඨ
|
||||
arch.repository.repositories=කෝෂ්ඨ
|
||||
conan.details.repository=කෝෂ්ඨය
|
||||
owner.settings.cleanuprules.enabled=සබල කර ඇත
|
||||
|
||||
|
@ -169,6 +169,7 @@ buttons.link.tooltip=Pridať odkaz
|
||||
buttons.list.unordered.tooltip=Pridať zoznam
|
||||
buttons.list.ordered.tooltip=Pridať číslovaný zoznam
|
||||
buttons.list.task.tooltip=Pridať zoznam úloh
|
||||
buttons.table.add.insert=Pridať
|
||||
buttons.mention.tooltip=Spomenúť používateľa alebo tím
|
||||
buttons.ref.tooltip=Odkázať na problém alebo žiadosť o natiahnutie
|
||||
buttons.switch_to_legacy.tooltip=Použiť starší editor namiest toho
|
||||
@ -832,7 +833,6 @@ generate_repo=Generovať repozitár
|
||||
generate_from=Generovať z
|
||||
repo_desc=Popis
|
||||
repo_desc_helper=Zadajte krátky popis (voliteľné)
|
||||
repo_lang=Jazyk
|
||||
repo_gitignore_helper=Vyberte .gitignore šablóny.
|
||||
repo_gitignore_helper_desc=Zo zoznamu šablón pre bežné jazyky vyberte, ktoré súbory sa nemajú sledovať. Typické artefakty generované nástrojmi na vytváranie jednotlivých jazykov sú štandardne zahrnuté v .gitignore.
|
||||
license=Licencia
|
||||
@ -1049,7 +1049,6 @@ issues.filter_reviewers=Filtrovať revidentov
|
||||
issues.new.labels=Štítky
|
||||
issues.new.projects=Projekty
|
||||
issues.new.milestone=Míľnik
|
||||
issues.new.no_reviewers=Žiadni revidenti
|
||||
issues.choose.open_external_link=Otvoriť
|
||||
issues.create=Vytvoriť úkol
|
||||
issues.filter_label=Štítok
|
||||
@ -1070,8 +1069,9 @@ issues.dismiss_review=Zamietnuť revíziu
|
||||
issues.dismiss_review_warning=Naozaj chcete zrušiť túto revíziu?
|
||||
issues.cancel=Zrušiť
|
||||
issues.save=Uložiť
|
||||
issues.cancel_tracking=Zahodiť
|
||||
issues.add_time_cancel=Zrušiť
|
||||
|
||||
|
||||
|
||||
issues.dependency.cancel=Zrušiť
|
||||
issues.review.dismissed=zamietol revíziu od %s %s
|
||||
issues.review.wait=bol požiadaný o revidovanie %s
|
||||
@ -1311,6 +1311,7 @@ error.not_signed_commit=Nie je podpísaný commit
|
||||
|
||||
[packages]
|
||||
alpine.repository.repositories=Repozitáre
|
||||
arch.repository.repositories=Repozitáre
|
||||
conan.details.repository=Repozitár
|
||||
container.labels=Štítky
|
||||
owner.settings.cleanuprules.enabled=Povolené
|
||||
|
@ -104,6 +104,7 @@ filter.private=Privat
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Lägg till
|
||||
|
||||
[filter]
|
||||
|
||||
@ -615,7 +616,6 @@ use_template=Välj den här mallen
|
||||
generate_repo=Skapa utvecklingskatalog
|
||||
generate_from=Generera från
|
||||
repo_desc=Beskrivning
|
||||
repo_lang=Språk
|
||||
repo_gitignore_helper=Välj .gitignore-mallar.
|
||||
repo_gitignore_helper_desc=Välj vilka filer som inte ska spåras från en lista med mallar för vanliga språk. Typiska artefakter som genereras av varje språk byggverktyg ingår i .gitignore som standard.
|
||||
issue_labels=Ärendeetiketter
|
||||
@ -837,12 +837,9 @@ issues.new.no_items=Inga objekt
|
||||
issues.new.milestone=Milsten
|
||||
issues.new.no_milestone=Ingen Milsten
|
||||
issues.new.clear_milestone=Rensa milstenar
|
||||
issues.new.open_milestone=Öppna Milstenar
|
||||
issues.new.closed_milestone=Stängda Milstenar
|
||||
issues.new.assignees=Tilldelade
|
||||
issues.new.clear_assignees=Rensa tilldelade
|
||||
issues.new.no_assignees=Ingen tilldelad
|
||||
issues.new.no_reviewers=Inga granskare
|
||||
issues.choose.get_started=Kom igång
|
||||
issues.choose.open_external_link=Öppna
|
||||
issues.choose.blank=Standard
|
||||
@ -980,22 +977,18 @@ issues.comment_on_locked=Du kan inte kommentera ett låst ärende.
|
||||
issues.delete=Radera
|
||||
issues.delete.title=Radera detta ärende?
|
||||
issues.delete.text=Vill du verkligen ta bort detta ärende? (Detta kommer att permanent ta bort allt innehåll. Överväg att stänga det istället om du avser att hålla det arkiverat)
|
||||
|
||||
issues.tracker=Tidsredovisning
|
||||
issues.start_tracking=Starta tidsredovisning
|
||||
issues.start_tracking_history=`började arbeta %s`
|
||||
|
||||
issues.tracker_auto_close=Timern stoppas automatiskt när ärendet stängs
|
||||
issues.tracking_already_started=`Du har redan påbörjat tidredovisning på <a href="%s">ett annat ärende</a>!`
|
||||
issues.stop_tracking_history=`slutade arbeta %s`
|
||||
issues.add_time=Lägg till tid manuellt
|
||||
issues.add_time_short=Lägg till tid
|
||||
issues.add_time_cancel=Avbryt
|
||||
issues.add_time_history=`la till tillbringad tid %s`
|
||||
issues.del_time_history=`raderade tillbringad tid %s`
|
||||
issues.add_time_hours=Timmar
|
||||
issues.add_time_minutes=Minuter
|
||||
issues.add_time_sum_to_small=Inge tid har angivits.
|
||||
issues.time_spent_total=Total Tid Spenderad
|
||||
issues.time_spent_from_all_authors=`Total Tid Spenderad: %s`
|
||||
|
||||
issues.due_date=Förfallodatum
|
||||
issues.invalid_due_date_format=Datumsformatet för förfallodatum måste följa 'yyyy-MM-dd'.
|
||||
issues.error_modifying_due_date=Det gick inte att ändra förfallodatumet.
|
||||
@ -1443,7 +1436,6 @@ diff.file_image_height=Höjd
|
||||
diff.file_byte_size=Storlek
|
||||
diff.file_suppressed=Filskillnaden har hållits tillbaka eftersom den är för stor
|
||||
diff.comment.placeholder=Lämna en kommentar
|
||||
diff.comment.markdown_info=Styling med markdown stöds.
|
||||
diff.comment.add_single_comment=Lägg till en kommentar
|
||||
diff.comment.add_review_comment=Lägg till kommentar
|
||||
diff.comment.start_review=Starta granskning
|
||||
@ -1990,6 +1982,7 @@ error.unit_not_allowed=Du har inte åtkomst till denna del av utvecklingskatalog
|
||||
filter.type=Typ
|
||||
alpine.repository.branches=Brancher
|
||||
alpine.repository.repositories=Utvecklingskataloger
|
||||
arch.repository.repositories=Utvecklingskataloger
|
||||
conan.details.repository=Utvecklingskatalog
|
||||
owner.settings.cleanuprules.enabled=Aktiv
|
||||
|
||||
|
@ -206,6 +206,7 @@ buttons.link.tooltip=Bağlantı ekle
|
||||
buttons.list.unordered.tooltip=Maddeli liste ekle
|
||||
buttons.list.ordered.tooltip=Numaralandırılmış liste ekle
|
||||
buttons.list.task.tooltip=Görev listesi ekle
|
||||
buttons.table.add.insert=Ekle
|
||||
buttons.mention.tooltip=Bir kişiye veya takıma değin
|
||||
buttons.ref.tooltip=Bir konuya veya değişiklik isteğine değin
|
||||
buttons.switch_to_legacy.tooltip=Eski düzenleyiciyi kullan
|
||||
@ -1012,7 +1013,6 @@ generate_repo=Depo Oluştur
|
||||
generate_from=Şuradan Oluştur
|
||||
repo_desc=Açıklama
|
||||
repo_desc_helper=Kısa açıklama girin (isteğe bağlı)
|
||||
repo_lang=Dil
|
||||
repo_gitignore_helper=.gitignore şablonlarını seç.
|
||||
repo_gitignore_helper_desc=Sık kullanılan diller için bir şablon listesinden hangi dosyaların izlenmeyeceğini seçin. Her dilin oluşturma araçları tarafından oluşturulan tipik yapılar, varsayılan olarak .gitignore dosyasına dahil edilmiştir.
|
||||
issue_labels=Konu Etiketleri
|
||||
@ -1419,12 +1419,9 @@ issues.new.no_items=Öge yok
|
||||
issues.new.milestone=Kilometre Taşı
|
||||
issues.new.no_milestone=Kilometre Taşı Yok
|
||||
issues.new.clear_milestone=Kilometre Taşlarını Temizle
|
||||
issues.new.open_milestone=Kilometre Taşlarını Aç
|
||||
issues.new.closed_milestone=Kapanmış Kilometre Taşları
|
||||
issues.new.assignees=Atananlar
|
||||
issues.new.clear_assignees=Atamaları Temizle
|
||||
issues.new.no_assignees=Atanan Kişi Yok
|
||||
issues.new.no_reviewers=Gözden geçiren yok
|
||||
issues.new.blocked_user=Konu oluşturulamıyor, depo sahibi tarafından engellenmişsiniz.
|
||||
issues.edit.already_changed=Konuya yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
|
||||
issues.edit.blocked_user=İçerik düzenlenemiyor, gönderen veya depo sahibi tarafından engellenmişsiniz.
|
||||
@ -1627,27 +1624,20 @@ issues.comment_on_locked=Kilitli bir konuya yorum yapamazsınız.
|
||||
issues.delete=Sil
|
||||
issues.delete.title=Bu konu silinsin mi?
|
||||
issues.delete.text=Bu konuyu gerçekten silmek istiyor musunuz? (Bu işlem tüm içeriği kalıcı olarak silecektir. Arşivde tutma niyetiniz varsa silmek yerine kapatmayı düşünün)
|
||||
|
||||
issues.tracker=Zaman Takibi
|
||||
issues.start_tracking_short=Zamanlayıcıyı Başlat
|
||||
issues.start_tracking=Zaman İzlemeyi Başlat
|
||||
issues.start_tracking_history=`%s çalışma başlattı`
|
||||
|
||||
issues.tracker_auto_close=Bu konu kapatıldığında zamanlayıcı otomatik olarak durur
|
||||
issues.tracking_already_started=`<a href="%s">başka bir konuda</a> zaten zaman izleyici başlattınız!`
|
||||
issues.stop_tracking=Zamanlayıcıyı Bitir
|
||||
issues.stop_tracking_history=`%s çalışmayı durdurdu`
|
||||
issues.cancel_tracking=Yoksay
|
||||
issues.cancel_tracking_history=`%s zaman takibini iptal etti`
|
||||
issues.add_time=El ile Zaman Ekle
|
||||
issues.del_time=Bu zaman kaydını sil
|
||||
issues.add_time_short=Zaman Ekle
|
||||
issues.add_time_cancel=İptal
|
||||
issues.add_time_history=`%s harcanan zaman eklendi`
|
||||
issues.del_time_history=`%s harcanan zaman silindi`
|
||||
issues.add_time_hours=Saat
|
||||
issues.add_time_minutes=Dakika
|
||||
issues.add_time_sum_to_small=Zaman girilmedi.
|
||||
issues.time_spent_total=Toplam Harcanan Zaman
|
||||
issues.time_spent_from_all_authors=`Toplam Harcanan Zaman: %s`
|
||||
|
||||
issues.due_date=Bitiş Tarihi
|
||||
issues.invalid_due_date_format=Bitiş tarihinin biçimi 'yyyy-aa-gg' olmalıdır.
|
||||
issues.error_modifying_due_date=Bitiş tarihi değiştirilemedi.
|
||||
@ -2503,7 +2493,6 @@ diff.generated=üretilen
|
||||
diff.vendored=sağlanmış
|
||||
diff.comment.add_line_comment=Satır yorum ekle
|
||||
diff.comment.placeholder=Yorum Yap
|
||||
diff.comment.markdown_info=Markdown ile şekillendirme desteklenir.
|
||||
diff.comment.add_single_comment=Bir yorum ekle
|
||||
diff.comment.add_review_comment=Yorum ekle
|
||||
diff.comment.start_review=İncelemeye başla
|
||||
@ -3418,6 +3407,9 @@ alpine.repository=Depo Bilgisi
|
||||
alpine.repository.branches=Dallar
|
||||
alpine.repository.repositories=Depolar
|
||||
alpine.repository.architectures=Mimariler
|
||||
arch.repository=Depo Bilgisi
|
||||
arch.repository.repositories=Depolar
|
||||
arch.repository.architectures=Mimariler
|
||||
cargo.registry=Bu kütüğü Cargo yapılandırma dosyasına (örneğin <code>~/.cargo/config.toml</code>) ayarlayın:
|
||||
cargo.install=Paketi Cargo kullanarak kurmak için, şu komutu çalıştırın:
|
||||
chef.registry=Bu kütüğü <code>~/.chef/config.rb</code> dosyasında ayarlayın:
|
||||
|
@ -115,6 +115,7 @@ filter.private=Приватний
|
||||
[heatmap]
|
||||
|
||||
[editor]
|
||||
buttons.table.add.insert=Додати
|
||||
|
||||
[filter]
|
||||
|
||||
@ -732,7 +733,6 @@ generate_repo=Згенерувати репозиторій
|
||||
generate_from=Генерувати з
|
||||
repo_desc=Опис
|
||||
repo_desc_helper=Введіть короткий опис (опціонально)
|
||||
repo_lang=Мова
|
||||
repo_gitignore_helper=Виберіть шаблон .gitignore.
|
||||
repo_gitignore_helper_desc=Оберіть з списку мовних шаблонів файли, які не будуть відстежуватись. Типові артефакти, які генеруються за допомогою інструментів побудови кожної мови, за замовчуванням включені до .gitignor.
|
||||
issue_labels=Мітки задачі
|
||||
@ -1022,12 +1022,9 @@ issues.new.no_items=Немає елементів
|
||||
issues.new.milestone=Етап
|
||||
issues.new.no_milestone=Етап відсутній
|
||||
issues.new.clear_milestone=Очистити етап
|
||||
issues.new.open_milestone=Активні етапи
|
||||
issues.new.closed_milestone=Закриті етапи
|
||||
issues.new.assignees=Виконавці
|
||||
issues.new.clear_assignees=Прибрати виконавців
|
||||
issues.new.no_assignees=Немає виконавця
|
||||
issues.new.no_reviewers=Немає рецензентів
|
||||
issues.choose.get_started=Початок роботи
|
||||
issues.choose.open_external_link=Відкрити
|
||||
issues.choose.blank=Типово
|
||||
@ -1178,26 +1175,19 @@ issues.lock.title=Заблокувати обговорення цієї зад
|
||||
issues.unlock.title=Розблокувати обговорення цієї задачі.
|
||||
issues.comment_on_locked=Ви не можете коментувати заблоковану задачу.
|
||||
issues.delete=Видалити
|
||||
|
||||
issues.tracker=Відстеження часу
|
||||
issues.start_tracking_short=Запустити таймер
|
||||
issues.start_tracking=Почати відстеження часу
|
||||
issues.start_tracking_history=`почав працювати %s`
|
||||
|
||||
issues.tracker_auto_close=Таймер буде автоматично зупинено, коли ця задача буде закрита
|
||||
issues.tracking_already_started=`Ви вже почали відстежувати час для <a href="%s">іншої задачі</a>!`
|
||||
issues.stop_tracking=Зупинити таймер
|
||||
issues.stop_tracking_history=`перестав(-ла) працювати %s`
|
||||
issues.cancel_tracking=Скасувати
|
||||
issues.add_time=Вручну додати час
|
||||
issues.del_time=Видалити цей журнал часу
|
||||
issues.add_time_short=Додати час
|
||||
issues.add_time_cancel=Відмінити
|
||||
issues.add_time_history=`додав(-ла) витрачений час %s`
|
||||
issues.del_time_history=`видалив витрачений час %s`
|
||||
issues.add_time_hours=Години
|
||||
issues.add_time_minutes=Хвилини
|
||||
issues.add_time_sum_to_small=Час не введено.
|
||||
issues.time_spent_total=Загальний витрачений час
|
||||
issues.time_spent_from_all_authors=`Загальний витрачений час: %s`
|
||||
|
||||
issues.due_date=Дата завершення
|
||||
issues.invalid_due_date_format=Дата закінчення має бути в форматі 'ррр-мм-дд'.
|
||||
issues.error_modifying_due_date=Не вдалося змінити дату завершення.
|
||||
@ -1824,7 +1814,6 @@ diff.load=Завантажити різницю
|
||||
diff.generated=згенерований
|
||||
diff.vendored=сторонній
|
||||
diff.comment.placeholder=Залишити коментар
|
||||
diff.comment.markdown_info=Стилізація з markdown підтримується.
|
||||
diff.comment.add_single_comment=Додати простий коментар
|
||||
diff.comment.add_review_comment=Додати коментар
|
||||
diff.comment.start_review=Розпочати рецензію
|
||||
@ -2526,6 +2515,7 @@ error.unit_not_allowed=У вас немає доступу до жодного
|
||||
filter.type=Тип
|
||||
alpine.repository.branches=Гілки
|
||||
alpine.repository.repositories=Репозиторії
|
||||
arch.repository.repositories=Репозиторії
|
||||
conan.details.repository=Репозиторій
|
||||
owner.settings.cleanuprules.enabled=Увімкнено
|
||||
|
||||
|
@ -158,6 +158,7 @@ filter.public=公开
|
||||
filter.private=私有库
|
||||
|
||||
no_results_found=未找到结果
|
||||
internal_error_skipped=发生内部错误,但已被跳过: %s
|
||||
|
||||
[search]
|
||||
search=搜索...
|
||||
@ -176,6 +177,7 @@ code_search_by_git_grep=当前代码搜索结果由“git grep”提供。如果
|
||||
package_kind=搜索软件包...
|
||||
project_kind=搜索项目...
|
||||
branch_kind=搜索分支...
|
||||
tag_kind=搜索标签...
|
||||
commit_kind=搜索提交记录...
|
||||
runner_kind=搜索runners...
|
||||
no_results=未找到匹配结果
|
||||
@ -205,6 +207,10 @@ buttons.link.tooltip=添加链接
|
||||
buttons.list.unordered.tooltip=添加待办清单
|
||||
buttons.list.ordered.tooltip=添加编号列表
|
||||
buttons.list.task.tooltip=添加任务列表
|
||||
buttons.table.add.tooltip=添加表格
|
||||
buttons.table.add.insert=添加
|
||||
buttons.table.rows=行数
|
||||
buttons.table.cols=列数
|
||||
buttons.mention.tooltip=提及用户或团队
|
||||
buttons.ref.tooltip=引用一个问题或合并请求
|
||||
buttons.switch_to_legacy.tooltip=使用旧版编辑器
|
||||
@ -380,6 +386,7 @@ relevant_repositories=只显示相关的仓库, <a href="%s">显示未过滤
|
||||
|
||||
[auth]
|
||||
create_new_account=注册帐号
|
||||
sign_in_now=立即登录
|
||||
disable_register_prompt=对不起,注册功能已被关闭。请联系网站管理员。
|
||||
disable_register_mail=已禁用注册的电子邮件确认。
|
||||
manual_activation_only=请联系您的站点管理员来完成激活。
|
||||
@ -387,6 +394,7 @@ remember_me=记住此设备
|
||||
remember_me.compromised=登录令牌不再有效,因为它可能表明帐户已被破坏。请检查您的帐户是否有异常活动。
|
||||
forgot_password_title=忘记密码
|
||||
forgot_password=忘记密码?
|
||||
sign_up_now=还没账号?马上注册。
|
||||
sign_up_successful=帐户创建成功。欢迎!
|
||||
confirmation_mail_sent_prompt_ex=一封新的确认邮件已经发送到 <b>%s</b>请在下一个 %s 中检查您的收件箱以完成注册过程。 如果您的注册电子邮件地址不正确,您可以重新登录并更改它。
|
||||
must_change_password=更新您的密码
|
||||
@ -447,6 +455,7 @@ authorization_failed_desc=因为检测到无效请求,授权失败。请尝试
|
||||
sspi_auth_failed=SSPI 认证失败
|
||||
password_pwned_err=无法完成对 HaveIBeenPwned 的请求
|
||||
last_admin=您不能删除最后一个管理员。必须至少保留一个管理员。
|
||||
back_to_sign_in=返回登录页面
|
||||
|
||||
[mail]
|
||||
view_it_on=在 %s 上查看
|
||||
@ -463,6 +472,7 @@ activate_email=请验证您的邮箱地址
|
||||
activate_email.title=%s,请验证您的邮箱
|
||||
activate_email.text=请在 <b>%s</b> 时间内,点击以下链接,以验证你的电子邮件地址:
|
||||
|
||||
register_notify=欢迎来到 %s
|
||||
register_notify.title=%[1]s,欢迎来到 %[2]s
|
||||
register_notify.text_1=这是您的 %s 注册确认电子邮件 !
|
||||
register_notify.text_2=您现在可以以用户名 %s 登录。
|
||||
@ -1003,7 +1013,6 @@ generate_repo=生成仓库
|
||||
generate_from=生成自
|
||||
repo_desc=仓库描述
|
||||
repo_desc_helper=输入简要描述 (可选)
|
||||
repo_lang=仓库语言
|
||||
repo_gitignore_helper=选择 .gitignore 模板。
|
||||
repo_gitignore_helper_desc=从常见语言的模板列表中选择忽略跟踪的文件。默认情况下,由开发或构建工具生成的特殊文件都包含在 .gitignore 中。
|
||||
issue_labels=工单标签
|
||||
@ -1011,6 +1020,7 @@ issue_labels_helper=选择一个工单标签集
|
||||
license=授权许可
|
||||
license_helper=选择授权许可文件。
|
||||
license_helper_desc=许可证说明了其他人可以和不可以用您的代码做什么。不确定哪一个适合你的项目?见 <a target="_blank" rel="noopener noreferrer" href="%s">选择一个许可证</a>
|
||||
multiple_licenses=多许可证
|
||||
object_format=对象格式
|
||||
object_format_helper=仓库的对象格式。之后无法更改。SHA1 是最兼容的。
|
||||
readme=自述
|
||||
@ -1071,7 +1081,9 @@ tree_path_not_found_branch=路径 %[1]s 不存在于分支 %[2]s 中。
|
||||
tree_path_not_found_tag=路径 %[1]s 不存在于标签 %[2]s 中
|
||||
|
||||
transfer.accept=接受转移
|
||||
transfer.accept_desc=`转移到 "%s"`
|
||||
transfer.reject=拒绝转移
|
||||
transfer.reject_desc=`取消转移到 "%s"`
|
||||
transfer.no_permission_to_accept=您没有权限接受此转让。
|
||||
transfer.no_permission_to_reject=您没有权限拒绝此转让。
|
||||
|
||||
@ -1146,6 +1158,11 @@ migrate.gogs.description=从 notabug.org 或其他 Gogs 实例迁移数据。
|
||||
migrate.onedev.description=从 code.onedev.io 或其他 OneDev 实例迁移数据
|
||||
migrate.codebase.description=从 codebasehq.com 迁移数据
|
||||
migrate.gitbucket.description=从 GitBucket 实例迁移数据
|
||||
migrate.codecommit.description=从 AWS CodeCommit 迁移数据。
|
||||
migrate.codecommit.aws_access_key_id=AWS 访问密钥ID
|
||||
migrate.codecommit.aws_secret_access_key=AWS 秘密访问密钥
|
||||
migrate.codecommit.https_git_credentials_username=HTTPS Git 凭据用户名
|
||||
migrate.codecommit.https_git_credentials_password=HTTPS Git 凭据密码
|
||||
migrate.migrating_git=迁移Git数据
|
||||
migrate.migrating_topics=迁移主题
|
||||
migrate.migrating_milestones=迁移里程碑
|
||||
@ -1213,6 +1230,7 @@ file_view_rendered=渲染模式
|
||||
file_view_raw=查看原始文件
|
||||
file_permalink=永久链接
|
||||
file_too_large=文件过大,无法显示。
|
||||
file_is_empty=此文件是空的。
|
||||
code_preview_line_from_to=在 %[3]s 的第 %[1]d 行到 %[2]d 行
|
||||
code_preview_line_in=在 %[2]s 的第 %[1]d 行
|
||||
invisible_runes_header=`此文件含有不可见的 Unicode 字符`
|
||||
@ -1409,12 +1427,9 @@ issues.new.no_items=无可选项
|
||||
issues.new.milestone=里程碑
|
||||
issues.new.no_milestone=未选择里程碑
|
||||
issues.new.clear_milestone=取消选中里程碑
|
||||
issues.new.open_milestone=开启中的里程碑
|
||||
issues.new.closed_milestone=已关闭的里程碑
|
||||
issues.new.assignees=指派成员
|
||||
issues.new.clear_assignees=取消指派成员
|
||||
issues.new.no_assignees=未指派成员
|
||||
issues.new.no_reviewers=无审核者
|
||||
issues.new.blocked_user=无法创建工单,因为您已被仓库所有者屏蔽。
|
||||
issues.edit.already_changed=无法保存对工单的更改。其内容似乎已被其他用户更改。 请刷新页面并重新编辑以避免覆盖他们的更改
|
||||
issues.edit.blocked_user=无法编辑内容,因为您已被仓库所有者或工单创建者屏蔽。
|
||||
@ -1615,27 +1630,20 @@ issues.comment_on_locked=您不能对锁定的问题发表评论。
|
||||
issues.delete=删除
|
||||
issues.delete.title=是否删除工单?
|
||||
issues.delete.text=您真的要删除这个工单吗?(该操作将会永久删除所有内容。如果您需要保留,请关闭它)
|
||||
|
||||
issues.tracker=时间跟踪
|
||||
issues.start_tracking_short=启动计时器
|
||||
issues.start_tracking=开始时间跟踪
|
||||
issues.start_tracking_history=`开始工作 %s`
|
||||
|
||||
issues.tracker_auto_close=当此工单关闭时,自动停止计时器
|
||||
issues.tracking_already_started=`你已经开始对 <a href="%s">另一个工单</a> 进行时间跟踪!`
|
||||
issues.stop_tracking=停止计时器
|
||||
issues.stop_tracking_history=`停止工作 %s`
|
||||
issues.cancel_tracking=放弃
|
||||
issues.cancel_tracking_history=`取消时间跟踪 %s`
|
||||
issues.add_time=手动添加时间
|
||||
issues.del_time=删除此时间跟踪日志
|
||||
issues.add_time_short=添加时间
|
||||
issues.add_time_cancel=取消
|
||||
issues.add_time_history=`添加耗时 %s`
|
||||
issues.del_time_history=`已删除时间 %s`
|
||||
issues.add_time_hours=小时
|
||||
issues.add_time_minutes=分钟
|
||||
issues.add_time_sum_to_small=没有输入时间。
|
||||
issues.time_spent_total=总用时
|
||||
issues.time_spent_from_all_authors=`总花费时间:%s`
|
||||
|
||||
issues.due_date=到期时间
|
||||
issues.invalid_due_date_format=到期时间的格式必须是 'yyyy-mm-dd' 的形式。
|
||||
issues.error_modifying_due_date=修改到期时间失败。
|
||||
@ -1714,6 +1722,7 @@ issues.review.resolve_conversation=已解决问题
|
||||
issues.review.un_resolve_conversation=未解决问题
|
||||
issues.review.resolved_by=标记问题为已解决
|
||||
issues.review.commented=评论
|
||||
issues.review.unofficial=非官方审批数
|
||||
issues.assignee.error=因为未知原因,并非所有的指派都成功。
|
||||
issues.reference_issue.body=内容
|
||||
issues.content_history.deleted=删除于
|
||||
@ -1876,6 +1885,7 @@ pulls.delete.text=你真的要删除这个合并请求吗? (这将永久删除
|
||||
pulls.recently_pushed_new_branches=您已经于%[2]s推送了分支 <strong>%[1]s</strong>
|
||||
|
||||
pull.deleted_branch=(已删除): %s
|
||||
pull.agit_documentation=查看有关 AGit 的文档
|
||||
|
||||
comments.edit.already_changed=无法保存对评论的更改。其内容似乎已被其他用户更改。 请刷新页面并重新编辑以避免覆盖他们的更改
|
||||
|
||||
@ -1883,7 +1893,7 @@ milestones.new=新的里程碑
|
||||
milestones.closed=于 %s关闭
|
||||
milestones.update_ago=已更新 %s
|
||||
milestones.no_due_date=暂无截止日期
|
||||
milestones.open=开启中
|
||||
milestones.open=开启
|
||||
milestones.close=关闭
|
||||
milestones.new_subheader=里程碑可以帮助您组织工单并跟踪其进度。
|
||||
milestones.create=创建里程碑
|
||||
@ -1956,6 +1966,7 @@ wiki.original_git_entry_tooltip=查看原始的 Git 文件而不是使用友好
|
||||
activity=动态
|
||||
activity.navbar.pulse=活动
|
||||
activity.navbar.code_frequency=代码频率
|
||||
activity.navbar.contributors=贡献者
|
||||
activity.navbar.recent_commits=最近的提交
|
||||
activity.period.filter_label=周期:
|
||||
activity.period.daily=1 天
|
||||
@ -2245,6 +2256,7 @@ settings.event_wiki_desc=创建、重命名、编辑或删除了百科页面。
|
||||
settings.event_release=版本发布
|
||||
settings.event_release_desc=发布、更新或删除版本时。
|
||||
settings.event_push=推送
|
||||
settings.event_force_push=强制推送
|
||||
settings.event_push_desc=Git 仓库推送
|
||||
settings.event_repository=仓库
|
||||
settings.event_repository_desc=创建或删除仓库
|
||||
@ -2470,7 +2482,6 @@ diff.generated=自动生成的
|
||||
diff.vendored=vendored
|
||||
diff.comment.add_line_comment=添加行内评论
|
||||
diff.comment.placeholder=留下评论
|
||||
diff.comment.markdown_info=支持使用Markdown格式。
|
||||
diff.comment.add_single_comment=添加单条评论
|
||||
diff.comment.add_review_comment=添加评论
|
||||
diff.comment.start_review=开始评审
|
||||
@ -3143,6 +3154,7 @@ config.cache_adapter=Cache 适配器
|
||||
config.cache_interval=Cache 周期
|
||||
config.cache_conn=Cache 连接字符串
|
||||
config.cache_item_ttl=缓存项目 TTL
|
||||
config.cache_test=测试缓存
|
||||
|
||||
config.session_config=Session 配置
|
||||
config.session_provider=Session 提供者
|
||||
@ -3368,6 +3380,9 @@ alpine.repository=仓库信息
|
||||
alpine.repository.branches=分支
|
||||
alpine.repository.repositories=仓库管理
|
||||
alpine.repository.architectures=架构
|
||||
arch.repository=仓库信息
|
||||
arch.repository.repositories=仓库管理
|
||||
arch.repository.architectures=架构
|
||||
cargo.registry=在 Cargo 配置文件中设置此注册中心(例如:<code>~/.cargo/config.toml</code>):
|
||||
cargo.install=要使用 Cargo 安装软件包,请运行以下命令:
|
||||
chef.registry=在您的 <code>~/.chef/config.rb</code> 文件中设置此注册中心:
|
||||
|
@ -308,7 +308,6 @@ visibility=可見度
|
||||
fork_repo=複製儲存庫
|
||||
fork_from=複製自
|
||||
repo_desc=儲存庫描述
|
||||
repo_lang=儲存庫語言
|
||||
license=授權許可
|
||||
create_repo=建立儲存庫
|
||||
default_branch=默認分支
|
||||
@ -399,8 +398,6 @@ issues.new.clear_labels=清除已選取標籤
|
||||
issues.new.milestone=里程碑
|
||||
issues.new.no_milestone=未選擇里程碑
|
||||
issues.new.clear_milestone=清除已選取里程碑
|
||||
issues.new.open_milestone=開啟中的里程碑
|
||||
issues.new.closed_milestone=已關閉的里程碑
|
||||
issues.create=建立問題
|
||||
issues.new_label=建立標籤
|
||||
issues.new_label_desc_placeholder=組織描述
|
||||
@ -471,7 +468,9 @@ issues.attachment.open_tab=`在新的標籤頁中查看 '%s'`
|
||||
issues.attachment.download=`點擊下載 '%s'`
|
||||
issues.subscribe=訂閱
|
||||
issues.unsubscribe=取消訂閱
|
||||
issues.add_time_cancel=取消
|
||||
|
||||
|
||||
|
||||
issues.due_date_form_edit=編輯
|
||||
issues.due_date_form_remove=移除成員
|
||||
issues.dependency.cancel=取消
|
||||
@ -956,6 +955,7 @@ error.not_signed_commit=未簽名的提交
|
||||
[packages]
|
||||
filter.type=認證類型
|
||||
alpine.repository.repositories=儲存庫管理
|
||||
arch.repository.repositories=儲存庫管理
|
||||
conan.details.repository=儲存庫
|
||||
owner.settings.cleanuprules.enabled=已啟用
|
||||
|
||||
|
@ -156,6 +156,7 @@ buttons.link.tooltip=新增連結
|
||||
buttons.list.unordered.tooltip=新增項目符號清單
|
||||
buttons.list.ordered.tooltip=新增編號清單
|
||||
buttons.list.task.tooltip=新增工作項目清單
|
||||
buttons.table.add.insert=增加
|
||||
buttons.mention.tooltip=提及使用者或團隊
|
||||
buttons.ref.tooltip=參考問題或合併請求
|
||||
buttons.enable_monospace_font=啟用等寬字型
|
||||
@ -853,7 +854,6 @@ generate_repo=產生儲存庫
|
||||
generate_from=產生自
|
||||
repo_desc=描述
|
||||
repo_desc_helper=輸入簡介 (選用)
|
||||
repo_lang=儲存庫語言
|
||||
repo_gitignore_helper=選擇 .gitignore 範本
|
||||
repo_gitignore_helper_desc=從常見語言範本清單中挑選忽略追蹤的檔案。預設情況下各種語言建置工具產生的特殊檔案都包含在 .gitignore 中。
|
||||
issue_labels=問題標籤
|
||||
@ -1211,12 +1211,9 @@ issues.new.no_items=沒有項目
|
||||
issues.new.milestone=里程碑
|
||||
issues.new.no_milestone=未選擇里程碑
|
||||
issues.new.clear_milestone=清除已選取里程碑
|
||||
issues.new.open_milestone=開放中的里程碑
|
||||
issues.new.closed_milestone=已關閉的里程碑
|
||||
issues.new.assignees=負責人
|
||||
issues.new.clear_assignees=清除負責人
|
||||
issues.new.no_assignees=沒有負責人
|
||||
issues.new.no_reviewers=沒有審核者
|
||||
issues.choose.get_started=開始
|
||||
issues.choose.open_external_link=開啟
|
||||
issues.choose.blank=預設
|
||||
@ -1393,26 +1390,19 @@ issues.comment_on_locked=您無法在已鎖定的問題上留言。
|
||||
issues.delete=刪除
|
||||
issues.delete.title=刪除此問題?
|
||||
issues.delete.text=您真的要刪除此問題嗎?(這將會永久移除所有內容。若您還想保留,請考慮改為關閉它。)
|
||||
|
||||
issues.tracker=時間追蹤
|
||||
issues.start_tracking_short=開始計時
|
||||
issues.start_tracking=開始時間追蹤
|
||||
issues.start_tracking_history=`開始工作 %s`
|
||||
|
||||
issues.tracker_auto_close=當這個問題被關閉時,自動停止計時器
|
||||
issues.tracking_already_started=`您已在<a href="%s">另一個問題</a>上開始時間追蹤!`
|
||||
issues.stop_tracking=停止計時
|
||||
issues.stop_tracking_history=`結束工作 %s`
|
||||
issues.cancel_tracking=捨棄
|
||||
issues.add_time=手動新增時間
|
||||
issues.del_time=刪除此時間記錄
|
||||
issues.add_time_short=新增時間
|
||||
issues.add_time_cancel=取消
|
||||
issues.add_time_history=`加入了花費時間 %s`
|
||||
issues.del_time_history=`刪除了花費時間 %s`
|
||||
issues.add_time_hours=小時
|
||||
issues.add_time_minutes=分鐘
|
||||
issues.add_time_sum_to_small=沒有輸入時間。
|
||||
issues.time_spent_total=總花費時間
|
||||
issues.time_spent_from_all_authors=`總花費時間:%s`
|
||||
|
||||
issues.due_date=截止日期
|
||||
issues.invalid_due_date_format=截止日期的格式必須為「yyyy-mm-dd」。
|
||||
issues.error_modifying_due_date=無法修改截止日期。
|
||||
@ -2130,7 +2120,6 @@ diff.load=載入差異
|
||||
diff.generated=generated
|
||||
diff.vendored=vendored
|
||||
diff.comment.placeholder=留言...
|
||||
diff.comment.markdown_info=支援 markdown 格式。
|
||||
diff.comment.add_single_comment=加入單獨的留言
|
||||
diff.comment.add_review_comment=新增留言
|
||||
diff.comment.start_review=開始審核
|
||||
@ -2961,6 +2950,9 @@ alpine.repository=儲存庫資訊
|
||||
alpine.repository.branches=分支
|
||||
alpine.repository.repositories=儲存庫
|
||||
alpine.repository.architectures=架構
|
||||
arch.repository=儲存庫資訊
|
||||
arch.repository.repositories=儲存庫
|
||||
arch.repository.architectures=架構
|
||||
cargo.registry=在 Cargo 組態檔設定此註冊中心 (例如: <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=執行下列命令以使用 Cargo 安裝此套件:
|
||||
chef.registry=在您的 <code>~/.chef/config.rb</code> 檔設定此註冊中心:
|
||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -10,7 +10,7 @@
|
||||
"@citation-js/plugin-csl": "0.7.14",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.3",
|
||||
"@github/relative-time-element": "4.4.4",
|
||||
"@github/text-expander-element": "2.8.0",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.13.0",
|
||||
@ -3025,9 +3025,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@github/relative-time-element": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.3.tgz",
|
||||
"integrity": "sha512-EVKokqx9/DdUAZ2l9WVyY51EtRCO2gQWWMvsRIn7r4glJ91q9CXcnILVHZVCpfD52ucXUhUvtYsAjNJ4qP4uIg==",
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.4.tgz",
|
||||
"integrity": "sha512-Oi8uOL8O+ZWLD7dHRWCkm2cudcTYtB3VyOYf9BtzCgDGm+OKomyOREtItNMtWl1dxvec62BTKErq36uy+RYxQg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@github/text-expander-element": {
|
||||
|
@ -9,7 +9,7 @@
|
||||
"@citation-js/plugin-csl": "0.7.14",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.3",
|
||||
"@github/relative-time-element": "4.4.4",
|
||||
"@github/text-expander-element": "2.8.0",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.13.0",
|
||||
|
108
routers/web/devtest/mock_actions.go
Normal file
108
routers/web/devtest/mock_actions.go
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package devtest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
mathRand "math/rand/v2"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/repo/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewStepLog) {
|
||||
mockedLogs := []string{
|
||||
"::group::test group for: step={step}, cursor={cursor}",
|
||||
"in group msg for: step={step}, cursor={cursor}",
|
||||
"in group msg for: step={step}, cursor={cursor}",
|
||||
"in group msg for: step={step}, cursor={cursor}",
|
||||
"::endgroup::",
|
||||
"message for: step={step}, cursor={cursor}",
|
||||
"message for: step={step}, cursor={cursor}",
|
||||
"message for: step={step}, cursor={cursor}",
|
||||
"message for: step={step}, cursor={cursor}",
|
||||
"message for: step={step}, cursor={cursor}",
|
||||
}
|
||||
cur := logCur.Cursor // usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally
|
||||
for i := 0; i < util.Iif(logCur.Step == 0, 3, 1); i++ {
|
||||
logStr := mockedLogs[int(cur)%len(mockedLogs)]
|
||||
cur++
|
||||
logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step))
|
||||
logStr = strings.ReplaceAll(logStr, "{cursor}", fmt.Sprintf("%d", cur))
|
||||
stepsLog = append(stepsLog, &actions.ViewStepLog{
|
||||
Step: logCur.Step,
|
||||
Cursor: cur,
|
||||
Started: time.Now().Unix() - 1,
|
||||
Lines: []*actions.ViewStepLogLine{
|
||||
{Index: cur, Message: logStr, Timestamp: float64(time.Now().UnixNano()) / float64(time.Second)},
|
||||
},
|
||||
})
|
||||
}
|
||||
return stepsLog
|
||||
}
|
||||
|
||||
func MockActionsRunsJobs(ctx *context.Context) {
|
||||
req := web.GetForm(ctx).(*actions.ViewRequest)
|
||||
|
||||
resp := &actions.ViewResponse{}
|
||||
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
|
||||
Name: "artifact-a",
|
||||
Size: 100 * 1024,
|
||||
Status: "expired",
|
||||
})
|
||||
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
|
||||
Name: "artifact-b",
|
||||
Size: 1024 * 1024,
|
||||
Status: "completed",
|
||||
})
|
||||
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
|
||||
Summary: "step 0 (mock slow)",
|
||||
Duration: time.Hour.String(),
|
||||
Status: actions_model.StatusRunning.String(),
|
||||
})
|
||||
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
|
||||
Summary: "step 1 (mock fast)",
|
||||
Duration: time.Hour.String(),
|
||||
Status: actions_model.StatusRunning.String(),
|
||||
})
|
||||
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
|
||||
Summary: "step 2 (mock error)",
|
||||
Duration: time.Hour.String(),
|
||||
Status: actions_model.StatusRunning.String(),
|
||||
})
|
||||
if len(req.LogCursors) == 0 {
|
||||
ctx.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Logs.StepsLog = []*actions.ViewStepLog{}
|
||||
doSlowResponse := false
|
||||
doErrorResponse := false
|
||||
for _, logCur := range req.LogCursors {
|
||||
if !logCur.Expanded {
|
||||
continue
|
||||
}
|
||||
doSlowResponse = doSlowResponse || logCur.Step == 0
|
||||
doErrorResponse = doErrorResponse || logCur.Step == 2
|
||||
resp.Logs.StepsLog = append(resp.Logs.StepsLog, generateMockStepsLog(logCur)...)
|
||||
}
|
||||
if doErrorResponse {
|
||||
if mathRand.Float64() > 0.5 {
|
||||
ctx.Error(http.StatusInternalServerError, "devtest mock error response")
|
||||
return
|
||||
}
|
||||
}
|
||||
if doSlowResponse {
|
||||
time.Sleep(time.Duration(3000) * time.Millisecond)
|
||||
} else {
|
||||
time.Sleep(time.Duration(100) * time.Millisecond) // actually, frontend reload every 1 second, any smaller delay is fine
|
||||
}
|
||||
ctx.JSON(http.StatusOK, resp)
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -29,6 +30,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@ -66,30 +68,41 @@ func View(ctx *context_module.Context) {
|
||||
ctx.HTML(http.StatusOK, tplViewActions)
|
||||
}
|
||||
|
||||
type LogCursor struct {
|
||||
Step int `json:"step"`
|
||||
Cursor int64 `json:"cursor"`
|
||||
Expanded bool `json:"expanded"`
|
||||
}
|
||||
|
||||
type ViewRequest struct {
|
||||
LogCursors []struct {
|
||||
Step int `json:"step"`
|
||||
Cursor int64 `json:"cursor"`
|
||||
Expanded bool `json:"expanded"`
|
||||
} `json:"logCursors"`
|
||||
LogCursors []LogCursor `json:"logCursors"`
|
||||
}
|
||||
|
||||
type ArtifactsViewItem struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ViewResponse struct {
|
||||
Artifacts []*ArtifactsViewItem `json:"artifacts"`
|
||||
|
||||
State struct {
|
||||
Run struct {
|
||||
Link string `json:"link"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
CanCancel bool `json:"canCancel"`
|
||||
CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
|
||||
CanRerun bool `json:"canRerun"`
|
||||
CanDeleteArtifact bool `json:"canDeleteArtifact"`
|
||||
Done bool `json:"done"`
|
||||
WorkflowID string `json:"workflowID"`
|
||||
WorkflowLink string `json:"workflowLink"`
|
||||
IsSchedule bool `json:"isSchedule"`
|
||||
Jobs []*ViewJob `json:"jobs"`
|
||||
Commit ViewCommit `json:"commit"`
|
||||
Link string `json:"link"`
|
||||
Title string `json:"title"`
|
||||
TitleHTML template.HTML `json:"titleHTML"`
|
||||
Status string `json:"status"`
|
||||
CanCancel bool `json:"canCancel"`
|
||||
CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
|
||||
CanRerun bool `json:"canRerun"`
|
||||
CanDeleteArtifact bool `json:"canDeleteArtifact"`
|
||||
Done bool `json:"done"`
|
||||
WorkflowID string `json:"workflowID"`
|
||||
WorkflowLink string `json:"workflowLink"`
|
||||
IsSchedule bool `json:"isSchedule"`
|
||||
Jobs []*ViewJob `json:"jobs"`
|
||||
Commit ViewCommit `json:"commit"`
|
||||
} `json:"run"`
|
||||
CurrentJob struct {
|
||||
Title string `json:"title"`
|
||||
@ -146,6 +159,25 @@ type ViewStepLogLine struct {
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artifactsViewItems []*ArtifactsViewItem, err error) {
|
||||
run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifacts, err := actions_model.ListUploadedArtifactsMeta(ctx, run.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, art := range artifacts {
|
||||
artifactsViewItems = append(artifactsViewItems, &ArtifactsViewItem{
|
||||
Name: art.ArtifactName,
|
||||
Size: art.FileSize,
|
||||
Status: util.Iif(art.Status == actions_model.ArtifactStatusExpired, "expired", "completed"),
|
||||
})
|
||||
}
|
||||
return artifactsViewItems, nil
|
||||
}
|
||||
|
||||
func ViewPost(ctx *context_module.Context) {
|
||||
req := web.GetForm(ctx).(*ViewRequest)
|
||||
runIndex := getRunIndex(ctx)
|
||||
@ -157,13 +189,24 @@ func ViewPost(ctx *context_module.Context) {
|
||||
}
|
||||
run := current.Run
|
||||
if err := run.LoadAttributes(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("run.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
resp := &ViewResponse{}
|
||||
resp.Artifacts, err = getActionsViewArtifacts(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
if err != nil {
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.ServerError("getActionsViewArtifacts", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
metas := ctx.Repo.Repository.ComposeMetas(ctx)
|
||||
|
||||
resp.State.Run.Title = run.Title
|
||||
resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, metas)
|
||||
resp.State.Run.Link = run.Link()
|
||||
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
||||
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
|
||||
@ -205,12 +248,12 @@ func ViewPost(ctx *context_module.Context) {
|
||||
var err error
|
||||
task, err = actions_model.GetTaskByID(ctx, current.TaskID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("actions_model.GetTaskByID", err)
|
||||
return
|
||||
}
|
||||
task.Job = current
|
||||
if err := task.LoadAttributes(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("task.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -278,7 +321,7 @@ func ViewPost(ctx *context_module.Context) {
|
||||
offset := task.LogIndexes[index]
|
||||
logRows, err := actions.ReadLogs(ctx, task.LogInStorage, task.LogFilename, offset, length)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("actions.ReadLogs", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -555,49 +598,6 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
|
||||
return jobs[0], jobs
|
||||
}
|
||||
|
||||
type ArtifactsViewResponse struct {
|
||||
Artifacts []*ArtifactsViewItem `json:"artifacts"`
|
||||
}
|
||||
|
||||
type ArtifactsViewItem struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func ArtifactsView(ctx *context_module.Context) {
|
||||
runIndex := getRunIndex(ctx)
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.Error(http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
artifacts, err := actions_model.ListUploadedArtifactsMeta(ctx, run.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
artifactsResponse := ArtifactsViewResponse{
|
||||
Artifacts: make([]*ArtifactsViewItem, 0, len(artifacts)),
|
||||
}
|
||||
for _, art := range artifacts {
|
||||
status := "completed"
|
||||
if art.Status == actions_model.ArtifactStatusExpired {
|
||||
status = "expired"
|
||||
}
|
||||
artifactsResponse.Artifacts = append(artifactsResponse.Artifacts, &ArtifactsViewItem{
|
||||
Name: art.ArtifactName,
|
||||
Size: art.FileSize,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
ctx.JSON(http.StatusOK, artifactsResponse)
|
||||
}
|
||||
|
||||
func ArtifactsDeleteView(ctx *context_module.Context) {
|
||||
if !ctx.Repo.CanWrite(unit.TypeActions) {
|
||||
ctx.Error(http.StatusForbidden, "no permission")
|
||||
|
@ -114,12 +114,6 @@ func RefBlame(ctx *context.Context) {
|
||||
ctx.Data["UsesIgnoreRevs"] = result.UsesIgnoreRevs
|
||||
ctx.Data["FaultyIgnoreRevsFile"] = result.FaultyIgnoreRevsFile
|
||||
|
||||
// Get Topics of this repo
|
||||
renderRepoTopics(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
commitNames := processBlameParts(ctx, result.Parts)
|
||||
if ctx.Written() {
|
||||
return
|
||||
|
@ -89,7 +89,6 @@ func Branches(ctx *context.Context) {
|
||||
pager := context.NewPagination(int(branchesCount), pageSize, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
ctx.HTML(http.StatusOK, tplBranch)
|
||||
}
|
||||
|
||||
@ -260,3 +259,20 @@ func CreateBranch(ctx *context.Context) {
|
||||
ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName) + "/" + util.PathEscapeSegments(form.CurrentPath))
|
||||
}
|
||||
|
||||
func MergeUpstream(ctx *context.Context) {
|
||||
branchName := ctx.FormString("branch")
|
||||
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.JSONError(ctx.Tr("error.not_found"))
|
||||
return
|
||||
} else if models.IsErrMergeConflicts(err) {
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
|
||||
return
|
||||
}
|
||||
ctx.ServerError("MergeUpstream", err)
|
||||
return
|
||||
}
|
||||
ctx.JSONRedirect("")
|
||||
}
|
||||
|
@ -102,7 +102,6 @@ func Commits(ctx *context.Context) {
|
||||
pager := context.NewPagination(int(commitsCount), pageSize, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
ctx.HTML(http.StatusOK, tplCommits)
|
||||
}
|
||||
|
||||
@ -219,8 +218,6 @@ func SearchCommits(ctx *context.Context) {
|
||||
}
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["RefName"] = ctx.Repo.RefName
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
ctx.HTML(http.StatusOK, tplCommits)
|
||||
}
|
||||
|
||||
@ -266,7 +263,6 @@ func FileHistory(ctx *context.Context) {
|
||||
pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
ctx.HTML(http.StatusOK, tplCommits)
|
||||
}
|
||||
|
||||
|
@ -325,14 +325,6 @@ func ViewIssue(ctx *context.Context) {
|
||||
ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
|
||||
}
|
||||
|
||||
if issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) {
|
||||
ctx.Data["IssueDependencySearchType"] = "pulls"
|
||||
} else if !issue.IsPull && !ctx.Repo.CanRead(unit.TypePullRequests) {
|
||||
ctx.Data["IssueDependencySearchType"] = "issues"
|
||||
} else {
|
||||
ctx.Data["IssueDependencySearchType"] = "all"
|
||||
}
|
||||
|
||||
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(unit.TypeProjects)
|
||||
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||
upload.AddUploadContext(ctx, "comment")
|
||||
@ -349,46 +341,6 @@ func ViewIssue(ctx *context.Context) {
|
||||
|
||||
ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, emoji.ReplaceAliases(issue.Title))
|
||||
|
||||
iw := new(issues_model.IssueWatch)
|
||||
if ctx.Doer != nil {
|
||||
iw.UserID = ctx.Doer.ID
|
||||
iw.IssueID = issue.ID
|
||||
iw.IsWatching, err = issues_model.CheckIssueWatch(ctx, ctx.Doer, issue)
|
||||
if err != nil {
|
||||
ctx.ServerError("CheckIssueWatch", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["IssueWatch"] = iw
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||
issue.RenderedContent, err = markdown.RenderString(rctx, issue.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
// Get more information if it's a pull request.
|
||||
if issue.IsPull {
|
||||
if issue.PullRequest.HasMerged {
|
||||
ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
|
||||
PrepareMergedViewPullInfo(ctx, issue)
|
||||
} else {
|
||||
PrepareViewPullInfo(ctx, issue)
|
||||
ctx.Data["DisableStatusChange"] = ctx.Data["IsPullRequestBroken"] == true && issue.IsClosed
|
||||
}
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pageMetaData := retrieveRepoIssueMetaData(ctx, repo, issue, issue.IsPull)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
pageMetaData.LabelsData.SetSelectedLabels(issue.Labels)
|
||||
|
||||
if ctx.IsSigned {
|
||||
// Update issue-user.
|
||||
if err = activities_model.SetIssueReadBy(ctx, issue.ID, ctx.Doer.ID); err != nil {
|
||||
@ -397,52 +349,220 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
role issues_model.RoleDescriptor
|
||||
ok bool
|
||||
marked = make(map[int64]issues_model.RoleDescriptor)
|
||||
comment *issues_model.Comment
|
||||
participants = make([]*user_model.User, 1, 10)
|
||||
latestCloseCommentID int64
|
||||
)
|
||||
if ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
|
||||
if ctx.IsSigned {
|
||||
// Deal with the stopwatch
|
||||
ctx.Data["IsStopwatchRunning"] = issues_model.StopwatchExists(ctx, ctx.Doer.ID, issue.ID)
|
||||
if !ctx.Data["IsStopwatchRunning"].(bool) {
|
||||
var exists bool
|
||||
var swIssue *issues_model.Issue
|
||||
if exists, _, swIssue, err = issues_model.HasUserStopwatch(ctx, ctx.Doer.ID); err != nil {
|
||||
ctx.ServerError("HasUserStopwatch", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["HasUserStopwatch"] = exists
|
||||
if exists {
|
||||
// Add warning if the user has already a stopwatch
|
||||
// Add link to the issue of the already running stopwatch
|
||||
ctx.Data["OtherStopwatchURL"] = swIssue.Link()
|
||||
}
|
||||
}
|
||||
ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(ctx, issue, ctx.Doer)
|
||||
} else {
|
||||
ctx.Data["CanUseTimetracker"] = false
|
||||
}
|
||||
if ctx.Data["WorkingUsers"], err = issues_model.TotalTimesForEachUser(ctx, &issues_model.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil {
|
||||
ctx.ServerError("TotalTimesForEachUser", err)
|
||||
pageMetaData := retrieveRepoIssueMetaData(ctx, ctx.Repo.Repository, issue, issue.IsPull)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
pageMetaData.LabelsData.SetSelectedLabels(issue.Labels)
|
||||
|
||||
prepareFuncs := []func(*context.Context, *issues_model.Issue){
|
||||
prepareIssueViewContent,
|
||||
func(ctx *context.Context, issue *issues_model.Issue) {
|
||||
preparePullViewPullInfo(ctx, issue)
|
||||
},
|
||||
prepareIssueViewCommentsAndSidebarParticipants,
|
||||
preparePullViewReviewAndMerge,
|
||||
prepareIssueViewSidebarDevLinks,
|
||||
prepareIssueViewSidebarWatch,
|
||||
prepareIssueViewSidebarTimeTracker,
|
||||
prepareIssueViewSidebarDependency,
|
||||
prepareIssueViewSidebarPin,
|
||||
}
|
||||
|
||||
for _, prepareFunc := range prepareFuncs {
|
||||
prepareFunc(ctx, issue)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get more information if it's a pull request.
|
||||
if issue.IsPull {
|
||||
if issue.PullRequest.HasMerged {
|
||||
ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
|
||||
} else {
|
||||
ctx.Data["DisableStatusChange"] = ctx.Data["IsPullRequestBroken"] == true && issue.IsClosed
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Reference"] = issue.Ref
|
||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + url.QueryEscape(ctx.Data["Link"].(string))
|
||||
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
|
||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
ctx.Data["HasProjectsWritePermission"] = ctx.Repo.CanWrite(unit.TypeProjects)
|
||||
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.Doer.IsAdmin)
|
||||
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons
|
||||
ctx.Data["RefEndName"] = git.RefName(issue.Ref).ShortName()
|
||||
|
||||
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTagNamesByRepoID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tags"] = tags
|
||||
|
||||
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
|
||||
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssueView)
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model.Issue) {
|
||||
if issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) {
|
||||
ctx.Data["IssueDependencySearchType"] = "pulls"
|
||||
} else if !issue.IsPull && !ctx.Repo.CanRead(unit.TypePullRequests) {
|
||||
ctx.Data["IssueDependencySearchType"] = "issues"
|
||||
} else {
|
||||
ctx.Data["IssueDependencySearchType"] = "all"
|
||||
}
|
||||
|
||||
// Check if the user can use the dependencies
|
||||
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, issue.IsPull)
|
||||
|
||||
// check if dependencies can be created across repositories
|
||||
ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies
|
||||
|
||||
if issue.ShowRole, err = roleDescriptor(ctx, repo, issue.Poster, issue, issue.HasOriginalAuthor()); err != nil {
|
||||
ctx.ServerError("roleDescriptor", err)
|
||||
// Get Dependencies
|
||||
blockedBy, err := issue.BlockedByDependencies(ctx, db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("BlockedByDependencies", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["BlockedByDependencies"], ctx.Data["BlockedByDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blockedBy)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
blocking, err := issue.BlockingDependencies(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("BlockingDependencies", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
|
||||
}
|
||||
|
||||
func preparePullViewSigning(ctx *context.Context, issue *issues_model.Issue) {
|
||||
if !issue.IsPull {
|
||||
return
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
ctx.Data["WillSign"] = false
|
||||
if ctx.Doer != nil {
|
||||
sign, key, _, err := asymkey_service.SignMerge(ctx, pull, ctx.Doer, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
|
||||
ctx.Data["WillSign"] = sign
|
||||
ctx.Data["SigningKey"] = key
|
||||
if err != nil {
|
||||
if asymkey_service.IsErrWontSign(err) {
|
||||
ctx.Data["WontSignReason"] = err.(*asymkey_service.ErrWontSign).Reason
|
||||
} else {
|
||||
ctx.Data["WontSignReason"] = "error"
|
||||
log.Error("Error whilst checking if could sign pr %d in repo %s. Error: %v", pull.ID, pull.BaseRepo.FullName(), err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.Data["WontSignReason"] = "not_signed_in"
|
||||
}
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarWatch(ctx *context.Context, issue *issues_model.Issue) {
|
||||
iw := new(issues_model.IssueWatch)
|
||||
if ctx.Doer != nil {
|
||||
iw.UserID = ctx.Doer.ID
|
||||
iw.IssueID = issue.ID
|
||||
var err error
|
||||
iw.IsWatching, err = issues_model.CheckIssueWatch(ctx, ctx.Doer, issue)
|
||||
if err != nil {
|
||||
ctx.ServerError("CheckIssueWatch", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["IssueWatch"] = iw
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarTimeTracker(ctx *context.Context, issue *issues_model.Issue) {
|
||||
if !ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
// Deal with the stopwatch
|
||||
ctx.Data["IsStopwatchRunning"] = issues_model.StopwatchExists(ctx, ctx.Doer.ID, issue.ID)
|
||||
if !ctx.Data["IsStopwatchRunning"].(bool) {
|
||||
exists, _, swIssue, err := issues_model.HasUserStopwatch(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("HasUserStopwatch", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["HasUserStopwatch"] = exists
|
||||
if exists {
|
||||
// Add warning if the user has already a stopwatch
|
||||
// Add link to the issue of the already running stopwatch
|
||||
ctx.Data["OtherStopwatchURL"] = swIssue.Link()
|
||||
}
|
||||
}
|
||||
ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(ctx, issue, ctx.Doer)
|
||||
} else {
|
||||
ctx.Data["CanUseTimetracker"] = false
|
||||
}
|
||||
var err error
|
||||
if ctx.Data["WorkingUsers"], err = issues_model.TotalTimesForEachUser(ctx, &issues_model.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil {
|
||||
ctx.ServerError("TotalTimesForEachUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func preparePullViewDeleteBranch(ctx *context.Context, issue *issues_model.Issue, canDelete bool) {
|
||||
if !issue.IsPull {
|
||||
return
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
isPullBranchDeletable := canDelete &&
|
||||
pull.HeadRepo != nil &&
|
||||
git.IsBranchExist(ctx, pull.HeadRepo.RepoPath(), pull.HeadBranch) &&
|
||||
(!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"])
|
||||
|
||||
if isPullBranchDeletable && pull.HasMerged {
|
||||
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pull.HeadRepoID, pull.HeadBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
|
||||
return
|
||||
}
|
||||
|
||||
isPullBranchDeletable = !exist
|
||||
}
|
||||
ctx.Data["IsPullBranchDeletable"] = isPullBranchDeletable
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarPin(ctx *context.Context, issue *issues_model.Issue) {
|
||||
var pinAllowed bool
|
||||
if !issue.IsPinned() {
|
||||
var err error
|
||||
pinAllowed, err = issues_model.IsNewPinAllowed(ctx, issue.RepoID, issue.IsPull)
|
||||
if err != nil {
|
||||
ctx.ServerError("IsNewPinAllowed", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
pinAllowed = true
|
||||
}
|
||||
|
||||
ctx.Data["NewPinAllowed"] = pinAllowed
|
||||
ctx.Data["PinEnabled"] = setting.Repository.Issue.MaxPinned != 0
|
||||
}
|
||||
|
||||
func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue *issues_model.Issue) {
|
||||
var (
|
||||
role issues_model.RoleDescriptor
|
||||
ok bool
|
||||
marked = make(map[int64]issues_model.RoleDescriptor)
|
||||
comment *issues_model.Comment
|
||||
participants = make([]*user_model.User, 1, 10)
|
||||
latestCloseCommentID int64
|
||||
err error
|
||||
)
|
||||
|
||||
marked[issue.PosterID] = issue.ShowRole
|
||||
|
||||
// Render comments and fetch participants.
|
||||
@ -461,7 +581,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
comment.Issue = issue
|
||||
|
||||
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
|
||||
rctx = renderhelper.NewRenderContextRepoComment(ctx, repo)
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
|
||||
comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@ -474,7 +594,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
comment.ShowRole, err = roleDescriptor(ctx, repo, comment.Poster, issue, comment.HasOriginalAuthor())
|
||||
comment.ShowRole, err = roleDescriptor(ctx, issue.Repo, comment.Poster, issue, comment.HasOriginalAuthor())
|
||||
if err != nil {
|
||||
ctx.ServerError("roleDescriptor", err)
|
||||
return
|
||||
@ -537,7 +657,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
} else if comment.Type.HasContentSupport() {
|
||||
rctx = renderhelper.NewRenderContextRepoComment(ctx, repo)
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
|
||||
comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
@ -572,7 +692,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
c.ShowRole, err = roleDescriptor(ctx, repo, c.Poster, issue, c.HasOriginalAuthor())
|
||||
c.ShowRole, err = roleDescriptor(ctx, issue.Repo, c.Poster, issue, c.HasOriginalAuthor())
|
||||
if err != nil {
|
||||
ctx.ServerError("roleDescriptor", err)
|
||||
return
|
||||
@ -629,237 +749,6 @@ func ViewIssue(ctx *context.Context) {
|
||||
// Combine multiple label assignments into a single comment
|
||||
combineLabelComments(issue)
|
||||
|
||||
getBranchData(ctx, issue)
|
||||
if issue.IsPull {
|
||||
pull := issue.PullRequest
|
||||
pull.Issue = issue
|
||||
canDelete := false
|
||||
allowMerge := false
|
||||
canWriteToHeadRepo := false
|
||||
|
||||
if ctx.IsSigned {
|
||||
if err := pull.LoadHeadRepo(ctx); err != nil {
|
||||
log.Error("LoadHeadRepo: %v", err)
|
||||
} else if pull.HeadRepo != nil {
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
if perm.CanWrite(unit.TypeCode) {
|
||||
// Check if branch is not protected
|
||||
if pull.HeadBranch != pull.HeadRepo.DefaultBranch {
|
||||
if protected, err := git_model.IsBranchProtected(ctx, pull.HeadRepo.ID, pull.HeadBranch); err != nil {
|
||||
log.Error("IsProtectedBranch: %v", err)
|
||||
} else if !protected {
|
||||
canDelete = true
|
||||
ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup"
|
||||
}
|
||||
}
|
||||
canWriteToHeadRepo = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := pull.LoadBaseRepo(ctx); err != nil {
|
||||
log.Error("LoadBaseRepo: %v", err)
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
if !canWriteToHeadRepo { // maintainers maybe allowed to push to head repo even if they can't write to it
|
||||
canWriteToHeadRepo = pull.AllowMaintainerEdit && perm.CanWrite(unit.TypeCode)
|
||||
}
|
||||
allowMerge, err = pull_service.IsUserAllowedToMerge(ctx, pull, perm, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("IsUserAllowedToMerge", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil {
|
||||
ctx.ServerError("CanMarkConversation", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["CanWriteToHeadRepo"] = canWriteToHeadRepo
|
||||
ctx.Data["ShowMergeInstructions"] = canWriteToHeadRepo
|
||||
ctx.Data["AllowMerge"] = allowMerge
|
||||
|
||||
prUnit, err := repo.GetUnit(ctx, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUnit", err)
|
||||
return
|
||||
}
|
||||
prConfig := prUnit.PullRequestsConfig()
|
||||
|
||||
ctx.Data["AutodetectManualMerge"] = prConfig.AutodetectManualMerge
|
||||
|
||||
var mergeStyle repo_model.MergeStyle
|
||||
// Check correct values and select default
|
||||
if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok ||
|
||||
!prConfig.IsMergeStyleAllowed(ms) {
|
||||
defaultMergeStyle := prConfig.GetDefaultMergeStyle()
|
||||
if prConfig.IsMergeStyleAllowed(defaultMergeStyle) && !ok {
|
||||
mergeStyle = defaultMergeStyle
|
||||
} else if prConfig.AllowMerge {
|
||||
mergeStyle = repo_model.MergeStyleMerge
|
||||
} else if prConfig.AllowRebase {
|
||||
mergeStyle = repo_model.MergeStyleRebase
|
||||
} else if prConfig.AllowRebaseMerge {
|
||||
mergeStyle = repo_model.MergeStyleRebaseMerge
|
||||
} else if prConfig.AllowSquash {
|
||||
mergeStyle = repo_model.MergeStyleSquash
|
||||
} else if prConfig.AllowFastForwardOnly {
|
||||
mergeStyle = repo_model.MergeStyleFastForwardOnly
|
||||
} else if prConfig.AllowManualMerge {
|
||||
mergeStyle = repo_model.MergeStyleManuallyMerged
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["MergeStyle"] = mergeStyle
|
||||
|
||||
defaultMergeMessage, defaultMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDefaultMergeMessage", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DefaultMergeMessage"] = defaultMergeMessage
|
||||
ctx.Data["DefaultMergeBody"] = defaultMergeBody
|
||||
|
||||
defaultSquashMergeMessage, defaultSquashMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDefaultSquashMergeMessage", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
|
||||
ctx.Data["DefaultSquashMergeBody"] = defaultSquashMergeBody
|
||||
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadProtectedBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
if pb != nil {
|
||||
pb.Repo = pull.BaseRepo
|
||||
ctx.Data["ProtectedBranch"] = pb
|
||||
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
||||
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
|
||||
ctx.Data["RequireSigned"] = pb.RequireSignedCommits
|
||||
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
||||
ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
|
||||
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
||||
ctx.Data["RequireApprovalsWhitelist"] = pb.EnableApprovalsWhitelist
|
||||
}
|
||||
ctx.Data["WillSign"] = false
|
||||
if ctx.Doer != nil {
|
||||
sign, key, _, err := asymkey_service.SignMerge(ctx, pull, ctx.Doer, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
|
||||
ctx.Data["WillSign"] = sign
|
||||
ctx.Data["SigningKey"] = key
|
||||
if err != nil {
|
||||
if asymkey_service.IsErrWontSign(err) {
|
||||
ctx.Data["WontSignReason"] = err.(*asymkey_service.ErrWontSign).Reason
|
||||
} else {
|
||||
ctx.Data["WontSignReason"] = "error"
|
||||
log.Error("Error whilst checking if could sign pr %d in repo %s. Error: %v", pull.ID, pull.BaseRepo.FullName(), err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.Data["WontSignReason"] = "not_signed_in"
|
||||
}
|
||||
|
||||
isPullBranchDeletable := canDelete &&
|
||||
pull.HeadRepo != nil &&
|
||||
git.IsBranchExist(ctx, pull.HeadRepo.RepoPath(), pull.HeadBranch) &&
|
||||
(!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"])
|
||||
|
||||
if isPullBranchDeletable && pull.HasMerged {
|
||||
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pull.HeadRepoID, pull.HeadBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
|
||||
return
|
||||
}
|
||||
|
||||
isPullBranchDeletable = !exist
|
||||
}
|
||||
ctx.Data["IsPullBranchDeletable"] = isPullBranchDeletable
|
||||
|
||||
stillCanManualMerge := func() bool {
|
||||
if pull.HasMerged || issue.IsClosed || !ctx.IsSigned {
|
||||
return false
|
||||
}
|
||||
if pull.CanAutoMerge() || pull.IsWorkInProgress(ctx) || pull.IsChecking() {
|
||||
return false
|
||||
}
|
||||
if allowMerge && prConfig.AllowManualMerge {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
ctx.Data["StillCanManualMerge"] = stillCanManualMerge()
|
||||
|
||||
// Check if there is a pending pr merge
|
||||
ctx.Data["HasPendingPullRequestMerge"], ctx.Data["PendingPullRequestMerge"], err = pull_model.GetScheduledMergeByPullID(ctx, pull.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetScheduledMergeByPullID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get Dependencies
|
||||
blockedBy, err := issue.BlockedByDependencies(ctx, db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("BlockedByDependencies", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["BlockedByDependencies"], ctx.Data["BlockedByDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blockedBy)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
blocking, err := issue.BlockingDependencies(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("BlockingDependencies", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
var pinAllowed bool
|
||||
if !issue.IsPinned() {
|
||||
pinAllowed, err = issues_model.IsNewPinAllowed(ctx, issue.RepoID, issue.IsPull)
|
||||
if err != nil {
|
||||
ctx.ServerError("IsNewPinAllowed", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
pinAllowed = true
|
||||
}
|
||||
|
||||
ctx.Data["Participants"] = participants
|
||||
ctx.Data["NumParticipants"] = len(participants)
|
||||
ctx.Data["Issue"] = issue
|
||||
ctx.Data["Reference"] = issue.Ref
|
||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + url.QueryEscape(ctx.Data["Link"].(string))
|
||||
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
|
||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
ctx.Data["HasProjectsWritePermission"] = ctx.Repo.CanWrite(unit.TypeProjects)
|
||||
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.Doer.IsAdmin)
|
||||
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons
|
||||
ctx.Data["RefEndName"] = git.RefName(issue.Ref).ShortName()
|
||||
ctx.Data["NewPinAllowed"] = pinAllowed
|
||||
ctx.Data["PinEnabled"] = setting.Repository.Issue.MaxPinned != 0
|
||||
|
||||
var hiddenCommentTypes *big.Int
|
||||
if ctx.IsSigned {
|
||||
val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)
|
||||
@ -872,58 +761,233 @@ func ViewIssue(ctx *context.Context) {
|
||||
ctx.Data["ShouldShowCommentType"] = func(commentType issues_model.CommentType) bool {
|
||||
return hiddenCommentTypes == nil || hiddenCommentTypes.Bit(int(commentType)) == 0
|
||||
}
|
||||
// For sidebar
|
||||
PrepareBranchList(ctx)
|
||||
|
||||
if ctx.Written() {
|
||||
// prepare for sidebar participants
|
||||
ctx.Data["Participants"] = participants
|
||||
ctx.Data["NumParticipants"] = len(participants)
|
||||
}
|
||||
|
||||
func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Issue) {
|
||||
getBranchData(ctx, issue)
|
||||
if !issue.IsPull {
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTagNamesByRepoID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tags"] = tags
|
||||
|
||||
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
|
||||
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
pull.Issue = issue
|
||||
canDelete := false
|
||||
allowMerge := false
|
||||
canWriteToHeadRepo := false
|
||||
|
||||
if ctx.IsSigned {
|
||||
forkedRepos, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetForksByUserAndOrgs", err)
|
||||
return
|
||||
}
|
||||
allowedRepos := make([]*repo_model.Repository, 0, len(forkedRepos)+1)
|
||||
for _, repo := range append(forkedRepos, ctx.Repo.Repository) {
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err := pull.LoadHeadRepo(ctx); err != nil {
|
||||
log.Error("LoadHeadRepo: %v", err)
|
||||
} else if pull.HeadRepo != nil {
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
if perm.CanWrite(unit.TypeCode) {
|
||||
allowedRepos = append(allowedRepos, repo)
|
||||
// Check if branch is not protected
|
||||
if pull.HeadBranch != pull.HeadRepo.DefaultBranch {
|
||||
if protected, err := git_model.IsBranchProtected(ctx, pull.HeadRepo.ID, pull.HeadBranch); err != nil {
|
||||
log.Error("IsProtectedBranch: %v", err)
|
||||
} else if !protected {
|
||||
canDelete = true
|
||||
ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup"
|
||||
}
|
||||
}
|
||||
canWriteToHeadRepo = true
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["AllowedRepos"] = allowedRepos
|
||||
|
||||
devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue)
|
||||
if err := pull.LoadBaseRepo(ctx); err != nil {
|
||||
log.Error("LoadBaseRepo: %v", err)
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindIssueDevLinksByIssue", err)
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DevLinks"] = devLinks
|
||||
for _, link := range devLinks {
|
||||
if link.LinkType == issues_model.IssueDevLinkTypePullRequest &&
|
||||
!(link.PullRequest.Issue.IsClosed && !link.PullRequest.HasMerged) {
|
||||
ctx.Data["MaybeFixed"] = link.PullRequest
|
||||
break
|
||||
}
|
||||
if !canWriteToHeadRepo { // maintainers maybe allowed to push to head repo even if they can't write to it
|
||||
canWriteToHeadRepo = pull.AllowMaintainerEdit && perm.CanWrite(unit.TypeCode)
|
||||
}
|
||||
allowMerge, err = pull_service.IsUserAllowedToMerge(ctx, pull, perm, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("IsUserAllowedToMerge", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil {
|
||||
ctx.ServerError("CanMarkConversation", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssueView)
|
||||
ctx.Data["CanWriteToHeadRepo"] = canWriteToHeadRepo
|
||||
ctx.Data["ShowMergeInstructions"] = canWriteToHeadRepo
|
||||
ctx.Data["AllowMerge"] = allowMerge
|
||||
|
||||
prUnit, err := issue.Repo.GetUnit(ctx, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUnit", err)
|
||||
return
|
||||
}
|
||||
prConfig := prUnit.PullRequestsConfig()
|
||||
|
||||
ctx.Data["AutodetectManualMerge"] = prConfig.AutodetectManualMerge
|
||||
|
||||
var mergeStyle repo_model.MergeStyle
|
||||
// Check correct values and select default
|
||||
if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok ||
|
||||
!prConfig.IsMergeStyleAllowed(ms) {
|
||||
defaultMergeStyle := prConfig.GetDefaultMergeStyle()
|
||||
if prConfig.IsMergeStyleAllowed(defaultMergeStyle) && !ok {
|
||||
mergeStyle = defaultMergeStyle
|
||||
} else if prConfig.AllowMerge {
|
||||
mergeStyle = repo_model.MergeStyleMerge
|
||||
} else if prConfig.AllowRebase {
|
||||
mergeStyle = repo_model.MergeStyleRebase
|
||||
} else if prConfig.AllowRebaseMerge {
|
||||
mergeStyle = repo_model.MergeStyleRebaseMerge
|
||||
} else if prConfig.AllowSquash {
|
||||
mergeStyle = repo_model.MergeStyleSquash
|
||||
} else if prConfig.AllowFastForwardOnly {
|
||||
mergeStyle = repo_model.MergeStyleFastForwardOnly
|
||||
} else if prConfig.AllowManualMerge {
|
||||
mergeStyle = repo_model.MergeStyleManuallyMerged
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["MergeStyle"] = mergeStyle
|
||||
|
||||
defaultMergeMessage, defaultMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDefaultMergeMessage", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DefaultMergeMessage"] = defaultMergeMessage
|
||||
ctx.Data["DefaultMergeBody"] = defaultMergeBody
|
||||
|
||||
defaultSquashMergeMessage, defaultSquashMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDefaultSquashMergeMessage", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
|
||||
ctx.Data["DefaultSquashMergeBody"] = defaultSquashMergeBody
|
||||
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadProtectedBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
if pb != nil {
|
||||
pb.Repo = pull.BaseRepo
|
||||
ctx.Data["ProtectedBranch"] = pb
|
||||
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
||||
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
|
||||
ctx.Data["RequireSigned"] = pb.RequireSignedCommits
|
||||
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
||||
ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
|
||||
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
||||
ctx.Data["RequireApprovalsWhitelist"] = pb.EnableApprovalsWhitelist
|
||||
}
|
||||
|
||||
preparePullViewSigning(ctx, issue)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
preparePullViewDeleteBranch(ctx, issue, canDelete)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
stillCanManualMerge := func() bool {
|
||||
if pull.HasMerged || issue.IsClosed || !ctx.IsSigned {
|
||||
return false
|
||||
}
|
||||
if pull.CanAutoMerge() || pull.IsWorkInProgress(ctx) || pull.IsChecking() {
|
||||
return false
|
||||
}
|
||||
if allowMerge && prConfig.AllowManualMerge {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
ctx.Data["StillCanManualMerge"] = stillCanManualMerge()
|
||||
|
||||
// Check if there is a pending pr merge
|
||||
ctx.Data["HasPendingPullRequestMerge"], ctx.Data["PendingPullRequestMerge"], err = pull_model.GetScheduledMergeByPullID(ctx, pull.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetScheduledMergeByPullID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) {
|
||||
var err error
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||
issue.RenderedContent, err = markdown.RenderString(rctx, issue.Content)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
if issue.ShowRole, err = roleDescriptor(ctx, issue.Repo, issue.Poster, issue, issue.HasOriginalAuthor()); err != nil {
|
||||
ctx.ServerError("roleDescriptor", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Issue"] = issue
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarDevLinks(ctx *context.Context, issue *issues_model.Issue) {
|
||||
if issue.IsPull {
|
||||
return
|
||||
}
|
||||
|
||||
devLinks, err := issue_service.FindIssueDevLinksByIssue(ctx, issue)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindIssueDevLinksByIssue", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DevLinks"] = devLinks
|
||||
for _, link := range devLinks {
|
||||
if link.LinkType == issues_model.IssueDevLinkTypePullRequest &&
|
||||
!(link.PullRequest.Issue.IsClosed && !link.PullRequest.HasMerged) {
|
||||
ctx.Data["MaybeFixed"] = link.PullRequest
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !ctx.IsSigned {
|
||||
return
|
||||
}
|
||||
|
||||
// Get all possible repositories for creating branch model dropdown list
|
||||
forkedRepos, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetForksByUserAndOrgs", err)
|
||||
return
|
||||
}
|
||||
allowedRepos := make([]*repo_model.Repository, 0, len(forkedRepos)+1)
|
||||
for _, repo := range append(forkedRepos, ctx.Repo.Repository) {
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
if perm.CanWrite(unit.TypeCode) {
|
||||
allowedRepos = append(allowedRepos, repo)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["AllowedRepos"] = allowedRepos
|
||||
}
|
||||
|
@ -425,6 +425,7 @@ func ViewProject(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = project.Title
|
||||
ctx.Data["IsProjectsPage"] = true
|
||||
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
|
||||
ctx.Data["Project"] = project
|
||||
|
@ -263,8 +263,18 @@ func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) stri
|
||||
return baseCommit
|
||||
}
|
||||
|
||||
// PrepareMergedViewPullInfo show meta information for a merged pull request view page
|
||||
func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
|
||||
func preparePullViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
|
||||
if !issue.IsPull {
|
||||
return nil
|
||||
}
|
||||
if issue.PullRequest.HasMerged {
|
||||
return prepareMergedViewPullInfo(ctx, issue)
|
||||
}
|
||||
return prepareViewPullInfo(ctx, issue)
|
||||
}
|
||||
|
||||
// prepareMergedViewPullInfo show meta information for a merged pull request view page
|
||||
func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
|
||||
pull := issue.PullRequest
|
||||
|
||||
setMergeTarget(ctx, pull)
|
||||
@ -309,8 +319,8 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
|
||||
return compareInfo
|
||||
}
|
||||
|
||||
// PrepareViewPullInfo show meta information for a pull request preview page
|
||||
func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
|
||||
// prepareViewPullInfo show meta information for a pull request preview page
|
||||
func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
|
||||
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
@ -610,15 +620,8 @@ func ViewPullCommits(ctx *context.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
|
||||
var prInfo *git.CompareInfo
|
||||
if pull.HasMerged {
|
||||
prInfo = PrepareMergedViewPullInfo(ctx, issue)
|
||||
} else {
|
||||
prInfo = PrepareViewPullInfo(ctx, issue)
|
||||
}
|
||||
|
||||
prInfo := preparePullViewPullInfo(ctx, issue)
|
||||
if ctx.Written() {
|
||||
return
|
||||
} else if prInfo == nil {
|
||||
@ -662,11 +665,12 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
||||
gitRepo = ctx.Repo.GitRepo
|
||||
)
|
||||
|
||||
var prInfo *git.CompareInfo
|
||||
if pull.HasMerged {
|
||||
prInfo = PrepareMergedViewPullInfo(ctx, issue)
|
||||
} else {
|
||||
prInfo = PrepareViewPullInfo(ctx, issue)
|
||||
prInfo := preparePullViewPullInfo(ctx, issue)
|
||||
if ctx.Written() {
|
||||
return
|
||||
} else if prInfo == nil {
|
||||
ctx.NotFound("ViewPullFiles", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the given commit sha to show (if any passed)
|
||||
|
@ -31,7 +31,6 @@ import (
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
releaseservice "code.gitea.io/gitea/services/release"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -153,9 +152,6 @@ func Releases(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.release.releases")
|
||||
ctx.Data["IsViewBranch"] = false
|
||||
ctx.Data["IsViewTag"] = true
|
||||
// Disable the showCreateNewBranch form in the dropdown on this page.
|
||||
ctx.Data["CanCreateBranch"] = false
|
||||
ctx.Data["HideBranchesInDropdown"] = true
|
||||
|
||||
listOptions := db.ListOptions{
|
||||
Page: ctx.FormInt("page"),
|
||||
@ -193,9 +189,6 @@ func Releases(ctx *context.Context) {
|
||||
pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
|
||||
ctx.HTML(http.StatusOK, tplReleasesList)
|
||||
}
|
||||
|
||||
@ -205,9 +198,6 @@ func TagsList(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.release.tags")
|
||||
ctx.Data["IsViewBranch"] = false
|
||||
ctx.Data["IsViewTag"] = true
|
||||
// Disable the showCreateNewBranch form in the dropdown on this page.
|
||||
ctx.Data["CanCreateBranch"] = false
|
||||
ctx.Data["HideBranchesInDropdown"] = true
|
||||
ctx.Data["CanCreateRelease"] = ctx.Repo.CanWrite(unit.TypeReleases) && !ctx.Repo.Repository.IsArchived
|
||||
|
||||
namePattern := ctx.FormTrim("q")
|
||||
@ -254,8 +244,6 @@ func TagsList(ctx *context.Context) {
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.Data["PageIsViewCode"] = !ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeReleases)
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
|
||||
ctx.HTML(http.StatusOK, tplTagsList)
|
||||
}
|
||||
|
||||
|
@ -5,18 +5,13 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
gocontext "context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"image"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -29,33 +24,21 @@ import (
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issue_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
"code.gitea.io/gitea/models/renderhelper"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/highlight"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/web/feed"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
|
||||
_ "golang.org/x/image/bmp" // for processing bmp images
|
||||
_ "golang.org/x/image/webp" // for processing webp images
|
||||
@ -70,140 +53,6 @@ const (
|
||||
tplMigrating base.TplName = "repo/migrate/migrating"
|
||||
)
|
||||
|
||||
// locate a README for a tree in one of the supported paths.
|
||||
//
|
||||
// entries is passed to reduce calls to ListEntries(), so
|
||||
// this has precondition:
|
||||
//
|
||||
// entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
|
||||
//
|
||||
// FIXME: There has to be a more efficient way of doing this
|
||||
func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
|
||||
// Create a list of extensions in priority order
|
||||
// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
|
||||
// 2. Txt files - e.g. README.txt
|
||||
// 3. No extension - e.g. README
|
||||
exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority
|
||||
extCount := len(exts)
|
||||
readmeFiles := make([]*git.TreeEntry, extCount+1)
|
||||
|
||||
docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/)
|
||||
for _, entry := range entries {
|
||||
if tryWellKnownDirs && entry.IsDir() {
|
||||
// as a special case for the top-level repo introduction README,
|
||||
// fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ...
|
||||
// (note that docsEntries is ignored unless we are at the root)
|
||||
lowerName := strings.ToLower(entry.Name())
|
||||
switch lowerName {
|
||||
case "docs":
|
||||
if entry.Name() == "docs" || docsEntries[0] == nil {
|
||||
docsEntries[0] = entry
|
||||
}
|
||||
case ".gitea":
|
||||
if entry.Name() == ".gitea" || docsEntries[1] == nil {
|
||||
docsEntries[1] = entry
|
||||
}
|
||||
case ".github":
|
||||
if entry.Name() == ".github" || docsEntries[2] == nil {
|
||||
docsEntries[2] = entry
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok {
|
||||
log.Debug("Potential readme file: %s", entry.Name())
|
||||
if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) {
|
||||
if entry.IsLink() {
|
||||
target, err := entry.FollowLinks()
|
||||
if err != nil && !git.IsErrBadLink(err) {
|
||||
return "", nil, err
|
||||
} else if target != nil && (target.IsExecutable() || target.IsRegular()) {
|
||||
readmeFiles[i] = entry
|
||||
}
|
||||
} else {
|
||||
readmeFiles[i] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var readmeFile *git.TreeEntry
|
||||
for _, f := range readmeFiles {
|
||||
if f != nil {
|
||||
readmeFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.TreePath == "" && readmeFile == nil {
|
||||
for _, subTreeEntry := range docsEntries {
|
||||
if subTreeEntry == nil {
|
||||
continue
|
||||
}
|
||||
subTree := subTreeEntry.Tree()
|
||||
if subTree == nil {
|
||||
// this should be impossible; if subTreeEntry exists so should this.
|
||||
continue
|
||||
}
|
||||
childEntries, err := subTree.ListEntries()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, childEntries, false)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
return "", nil, err
|
||||
}
|
||||
if readmeFile != nil {
|
||||
return path.Join(subTreeEntry.Name(), subfolder), readmeFile, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", readmeFile, nil
|
||||
}
|
||||
|
||||
func renderDirectory(ctx *context.Context) {
|
||||
entries := renderDirectoryFiles(ctx, 1*time.Second)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.TreePath != "" {
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||
}
|
||||
|
||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
|
||||
if err != nil {
|
||||
ctx.ServerError("findReadmeFileInEntries", err)
|
||||
return
|
||||
}
|
||||
|
||||
renderReadmeFile(ctx, subfolder, readmeFile)
|
||||
}
|
||||
|
||||
// localizedExtensions prepends the provided language code with and without a
|
||||
// regional identifier to the provided extension.
|
||||
// Note: the language code will always be lower-cased, if a region is present it must be separated with a `-`
|
||||
// Note: ext should be prefixed with a `.`
|
||||
func localizedExtensions(ext, languageCode string) (localizedExts []string) {
|
||||
if len(languageCode) < 1 {
|
||||
return []string{ext}
|
||||
}
|
||||
|
||||
lowerLangCode := "." + strings.ToLower(languageCode)
|
||||
|
||||
if strings.Contains(lowerLangCode, "-") {
|
||||
underscoreLangCode := strings.ReplaceAll(lowerLangCode, "-", "_")
|
||||
indexOfDash := strings.Index(lowerLangCode, "-")
|
||||
// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, _zh.md, .md]
|
||||
return []string{lowerLangCode + ext, underscoreLangCode + ext, lowerLangCode[:indexOfDash] + ext, "_" + lowerLangCode[1:indexOfDash] + ext, ext}
|
||||
}
|
||||
|
||||
// e.g. [.en.md, .md]
|
||||
return []string{lowerLangCode + ext, ext}
|
||||
}
|
||||
|
||||
type fileInfo struct {
|
||||
isTextFile bool
|
||||
isLFSFile bool
|
||||
@ -261,85 +110,6 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
|
||||
return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
|
||||
}
|
||||
|
||||
func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
|
||||
target := readmeFile
|
||||
if readmeFile != nil && readmeFile.IsLink() {
|
||||
target, _ = readmeFile.FollowLinks()
|
||||
}
|
||||
if target == nil {
|
||||
// if findReadmeFile() failed and/or gave us a broken symlink (which it shouldn't)
|
||||
// simply skip rendering the README
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["RawFileLink"] = ""
|
||||
ctx.Data["ReadmeInList"] = true
|
||||
ctx.Data["ReadmeExist"] = true
|
||||
ctx.Data["FileIsSymlink"] = readmeFile.IsLink()
|
||||
|
||||
buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, target.Blob())
|
||||
if err != nil {
|
||||
ctx.ServerError("getFileReader", err)
|
||||
return
|
||||
}
|
||||
defer dataRc.Close()
|
||||
|
||||
ctx.Data["FileIsText"] = fInfo.isTextFile
|
||||
ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name())
|
||||
ctx.Data["FileSize"] = fInfo.fileSize
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
||||
|
||||
if fInfo.isLFSFile {
|
||||
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.Name()))
|
||||
ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.Link(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64))
|
||||
}
|
||||
|
||||
if !fInfo.isTextFile {
|
||||
return
|
||||
}
|
||||
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
// Pretend that this is a normal text file to display 'This file is too large to be shown'
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
ctx.Data["IsTextFile"] = true
|
||||
return
|
||||
}
|
||||
|
||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||
|
||||
if markupType := markup.DetectMarkupTypeByFileName(readmeFile.Name()); markupType != "" {
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||
CurrentTreePath: path.Join(ctx.Repo.TreePath, subfolder),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())) // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
||||
delete(ctx.Data, "IsMarkup")
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Data["IsMarkup"] != true {
|
||||
ctx.Data["IsPlainText"] = true
|
||||
content, err := io.ReadAll(rd)
|
||||
if err != nil {
|
||||
log.Error("Read readme content failed: %v", err)
|
||||
}
|
||||
contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
|
||||
}
|
||||
|
||||
if !fInfo.isLFSFile && ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
|
||||
ctx.Data["CanEditReadmeFile"] = true
|
||||
}
|
||||
}
|
||||
|
||||
func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
|
||||
// Show latest commit info of repository in table header,
|
||||
// or of directory if not in root directory.
|
||||
@ -371,287 +141,6 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
ctx.Data["IsViewFile"] = true
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
blob := entry.Blob()
|
||||
buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
|
||||
if err != nil {
|
||||
ctx.ServerError("getFileReader", err)
|
||||
return
|
||||
}
|
||||
defer dataRc.Close()
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||
ctx.Data["FileIsSymlink"] = entry.IsLink()
|
||||
ctx.Data["FileName"] = blob.Name()
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
|
||||
commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitByPath", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !loadLatestCommitData(ctx, commit) {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.TreePath == ".editorconfig" {
|
||||
_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
|
||||
if editorconfigWarning != nil {
|
||||
ctx.Data["FileWarning"] = strings.TrimSpace(editorconfigWarning.Error())
|
||||
}
|
||||
if editorconfigErr != nil {
|
||||
ctx.Data["FileError"] = strings.TrimSpace(editorconfigErr.Error())
|
||||
}
|
||||
} else if issue_service.IsTemplateConfig(ctx.Repo.TreePath) {
|
||||
_, issueConfigErr := issue_service.GetTemplateConfig(ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.Commit)
|
||||
if issueConfigErr != nil {
|
||||
ctx.Data["FileError"] = strings.TrimSpace(issueConfigErr.Error())
|
||||
}
|
||||
} else if actions.IsWorkflow(ctx.Repo.TreePath) {
|
||||
content, err := actions.GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
log.Error("actions.GetContentFromEntry: %v", err)
|
||||
}
|
||||
_, workFlowErr := model.ReadWorkflow(bytes.NewReader(content))
|
||||
if workFlowErr != nil {
|
||||
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
|
||||
}
|
||||
} else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) {
|
||||
if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
|
||||
_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
|
||||
if len(warnings) > 0 {
|
||||
ctx.Data["FileWarning"] = strings.Join(warnings, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isDisplayingSource := ctx.FormString("display") == "source"
|
||||
isDisplayingRendered := !isDisplayingSource
|
||||
|
||||
if fInfo.isLFSFile {
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
|
||||
isRepresentableAsText := fInfo.st.IsRepresentableAsText()
|
||||
if !isRepresentableAsText {
|
||||
// If we can't show plain text, always try to render.
|
||||
isDisplayingSource = false
|
||||
isDisplayingRendered = true
|
||||
}
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
||||
ctx.Data["FileSize"] = fInfo.fileSize
|
||||
ctx.Data["IsTextFile"] = fInfo.isTextFile
|
||||
ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
|
||||
ctx.Data["IsDisplayingSource"] = isDisplayingSource
|
||||
ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
|
||||
ctx.Data["IsExecutable"] = entry.IsExecutable()
|
||||
|
||||
isTextSource := fInfo.isTextFile || isDisplayingSource
|
||||
ctx.Data["IsTextSource"] = isTextSource
|
||||
if isTextSource {
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
}
|
||||
|
||||
// Check LFS Lock
|
||||
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
|
||||
ctx.Data["LFSLock"] = lfsLock
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
return
|
||||
}
|
||||
if lfsLock != nil {
|
||||
u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["LFSLockOwner"] = u.Name
|
||||
ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
|
||||
ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
}
|
||||
|
||||
// Assume file is not editable first.
|
||||
if fInfo.isLFSFile {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
|
||||
} else if !isRepresentableAsText {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
|
||||
}
|
||||
|
||||
switch {
|
||||
case isRepresentableAsText:
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
break
|
||||
}
|
||||
|
||||
if fInfo.st.IsSvgImage() {
|
||||
ctx.Data["IsImageFile"] = true
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
}
|
||||
|
||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||
|
||||
shouldRenderSource := ctx.FormString("display") == "source"
|
||||
readmeExist := util.IsReadmeFileName(blob.Name())
|
||||
ctx.Data["ReadmeExist"] = readmeExist
|
||||
|
||||
markupType := markup.DetectMarkupTypeByFileName(blob.Name())
|
||||
if markupType == "" {
|
||||
markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
|
||||
}
|
||||
if markupType != "" {
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
}
|
||||
if markupType != "" && !shouldRenderSource {
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
|
||||
metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(ctx.Repo.TreePath).
|
||||
WithMetas(metas)
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
return
|
||||
}
|
||||
// to prevent iframe load third-party url
|
||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
|
||||
} else {
|
||||
buf, _ := io.ReadAll(rd)
|
||||
|
||||
// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
|
||||
// empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
|
||||
// Gitea uses the definition (like most modern editors):
|
||||
// empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
|
||||
// When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
|
||||
// To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
|
||||
// This NumLines is only used for the display on the UI: "xxx lines"
|
||||
if len(buf) == 0 {
|
||||
ctx.Data["NumLines"] = 0
|
||||
} else {
|
||||
ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
|
||||
}
|
||||
|
||||
language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
|
||||
}
|
||||
|
||||
fileContent, lexerName, err := highlight.File(blob.Name(), language, buf)
|
||||
ctx.Data["LexerName"] = lexerName
|
||||
if err != nil {
|
||||
log.Error("highlight.File failed, fallback to plain text: %v", err)
|
||||
fileContent = highlight.PlainText(buf)
|
||||
}
|
||||
status := &charset.EscapeStatus{}
|
||||
statuses := make([]*charset.EscapeStatus, len(fileContent))
|
||||
for i, line := range fileContent {
|
||||
statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
|
||||
status = status.Or(statuses[i])
|
||||
}
|
||||
ctx.Data["EscapeStatus"] = status
|
||||
ctx.Data["FileContent"] = fileContent
|
||||
ctx.Data["LineEscapeStatus"] = statuses
|
||||
}
|
||||
if !fInfo.isLFSFile {
|
||||
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
|
||||
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
|
||||
ctx.Data["CanEditFile"] = false
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
} else {
|
||||
ctx.Data["CanEditFile"] = true
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
|
||||
}
|
||||
} else if !ctx.Repo.IsViewBranch {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
|
||||
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
|
||||
}
|
||||
}
|
||||
|
||||
case fInfo.st.IsPDF():
|
||||
ctx.Data["IsPDFFile"] = true
|
||||
case fInfo.st.IsVideo():
|
||||
ctx.Data["IsVideoFile"] = true
|
||||
case fInfo.st.IsAudio():
|
||||
ctx.Data["IsAudioFile"] = true
|
||||
case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()):
|
||||
ctx.Data["IsImageFile"] = true
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
default:
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
|
||||
// It is used by "external renders", markupRender will execute external programs to get rendered content.
|
||||
if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType != "" {
|
||||
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(ctx.Repo.TreePath)
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
checker, deferable := ctx.Repo.GitRepo.CheckAttributeReader(ctx.Repo.CommitID)
|
||||
if checker != nil {
|
||||
defer deferable()
|
||||
attrs, err := checker.CheckPath(ctx.Repo.TreePath)
|
||||
if err == nil {
|
||||
ctx.Data["IsVendored"] = git.AttributeToBool(attrs, git.AttributeLinguistVendored).Value()
|
||||
ctx.Data["IsGenerated"] = git.AttributeToBool(attrs, git.AttributeLinguistGenerated).Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() {
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(buf))
|
||||
if err == nil {
|
||||
// There are Image formats go can't decode
|
||||
// Instead of throwing an error in that case, we show the size only when we can decode
|
||||
ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
|
||||
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
|
||||
ctx.Data["CanDeleteFile"] = false
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
} else {
|
||||
ctx.Data["CanDeleteFile"] = true
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
|
||||
}
|
||||
} else if !ctx.Repo.IsViewBranch {
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
|
||||
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
|
||||
}
|
||||
}
|
||||
|
||||
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
|
||||
markupRd, markupWr := io.Pipe()
|
||||
defer markupWr.Close()
|
||||
@ -728,59 +217,6 @@ func checkHomeCodeViewable(ctx *context.Context) {
|
||||
ctx.NotFound("Home", errors.New(ctx.Locale.TrString("units.error.no_unit_allowed_repo")))
|
||||
}
|
||||
|
||||
func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
if entry.Name() != "" {
|
||||
return
|
||||
}
|
||||
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
HandleGitError(ctx, "Repo.Commit.SubTree", err)
|
||||
return
|
||||
}
|
||||
allEntries, err := tree.ListEntries()
|
||||
if err != nil {
|
||||
ctx.ServerError("ListEntries", err)
|
||||
return
|
||||
}
|
||||
for _, entry := range allEntries {
|
||||
if entry.Name() == "CITATION.cff" || entry.Name() == "CITATION.bib" {
|
||||
// Read Citation file contents
|
||||
if content, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||
log.Error("checkCitationFile: GetBlobContent: %v", err)
|
||||
} else {
|
||||
ctx.Data["CitiationExist"] = true
|
||||
ctx.PageData["citationFileContent"] = content
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Home render repository home page
|
||||
func Home(ctx *context.Context) {
|
||||
if setting.Other.EnableFeed {
|
||||
isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
|
||||
if isFeed {
|
||||
switch {
|
||||
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
|
||||
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||
case ctx.Repo.TreePath == "":
|
||||
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||
case ctx.Repo.TreePath != "":
|
||||
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
checkHomeCodeViewable(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
renderHomeCode(ctx)
|
||||
}
|
||||
|
||||
// LastCommit returns lastCommit data for the provided branch/tag/commit and directory (in url) and filenames in body
|
||||
func LastCommit(ctx *context.Context) {
|
||||
checkHomeCodeViewable(ctx)
|
||||
@ -877,220 +313,6 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
|
||||
return allEntries
|
||||
}
|
||||
|
||||
func renderLanguageStats(ctx *context.Context) {
|
||||
langs, err := repo_model.GetTopLanguageStats(ctx, ctx.Repo.Repository, 5)
|
||||
if err != nil {
|
||||
ctx.ServerError("Repo.GetTopLanguageStats", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["LanguageStats"] = langs
|
||||
}
|
||||
|
||||
func renderRepoTopics(ctx *context.Context) {
|
||||
topics, err := db.Find[repo_model.Topic](ctx, &repo_model.FindTopicOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("models.FindTopics", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Topics"] = topics
|
||||
}
|
||||
|
||||
func prepareOpenWithEditorApps(ctx *context.Context) {
|
||||
var tmplApps []map[string]any
|
||||
apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
|
||||
if len(apps) == 0 {
|
||||
apps = setting.DefaultOpenWithEditorApps()
|
||||
}
|
||||
for _, app := range apps {
|
||||
schema, _, _ := strings.Cut(app.OpenURL, ":")
|
||||
var iconHTML template.HTML
|
||||
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
|
||||
iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2")
|
||||
} else {
|
||||
iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
|
||||
}
|
||||
tmplApps = append(tmplApps, map[string]any{
|
||||
"DisplayName": app.DisplayName,
|
||||
"OpenURL": app.OpenURL,
|
||||
"IconHTML": iconHTML,
|
||||
})
|
||||
}
|
||||
ctx.Data["OpenWithEditorApps"] = tmplApps
|
||||
}
|
||||
|
||||
func renderHomeCode(ctx *context.Context) {
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled
|
||||
prepareOpenWithEditorApps(ctx)
|
||||
|
||||
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
||||
showEmpty := true
|
||||
var err error
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
|
||||
if err != nil {
|
||||
log.Error("GitRepo.IsEmpty: %v", err)
|
||||
ctx.Repo.Repository.Status = repo_model.RepositoryBroken
|
||||
showEmpty = true
|
||||
ctx.Flash.Error(ctx.Tr("error.occurred"), true)
|
||||
}
|
||||
}
|
||||
if showEmpty {
|
||||
ctx.HTML(http.StatusOK, tplRepoEMPTY)
|
||||
return
|
||||
}
|
||||
|
||||
// the repo is not really empty, so we should update the modal in database
|
||||
// such problem may be caused by:
|
||||
// 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually
|
||||
// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
|
||||
// it's possible for a repository to be non-empty by that flag but still 500
|
||||
// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
|
||||
ctx.Repo.Repository.IsEmpty = false
|
||||
if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
|
||||
ctx.ServerError("UpdateRepositoryCols", err)
|
||||
return
|
||||
}
|
||||
if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
|
||||
ctx.ServerError("UpdateRepoSize", err)
|
||||
return
|
||||
}
|
||||
|
||||
// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
|
||||
link := ctx.Link
|
||||
if ctx.Req.URL.RawQuery != "" {
|
||||
link += "?" + ctx.Req.URL.RawQuery
|
||||
}
|
||||
ctx.Redirect(link)
|
||||
return
|
||||
}
|
||||
|
||||
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||
if len(ctx.Repo.Repository.Description) > 0 {
|
||||
title += ": " + ctx.Repo.Repository.Description
|
||||
}
|
||||
ctx.Data["Title"] = title
|
||||
|
||||
// Get Topics of this repo
|
||||
renderRepoTopics(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get current entry user currently looking at.
|
||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
|
||||
return
|
||||
}
|
||||
|
||||
checkOutdatedBranch(ctx)
|
||||
|
||||
checkCitationFile(ctx, entry)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
renderLanguageStats(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
renderDirectory(ctx)
|
||||
} else {
|
||||
renderFile(ctx, entry)
|
||||
}
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Doer != nil {
|
||||
if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
|
||||
ctx.ServerError("GetBaseRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Repo: ctx.Repo.Repository,
|
||||
BaseRepo: ctx.Repo.Repository,
|
||||
}
|
||||
if ctx.Repo.Repository.IsFork {
|
||||
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
|
||||
}
|
||||
|
||||
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
|
||||
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
|
||||
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
|
||||
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
|
||||
if err != nil {
|
||||
log.Error("FindRecentlyPushedNewBranches failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var treeNames []string
|
||||
paths := make([]string, 0, 5)
|
||||
if len(ctx.Repo.TreePath) > 0 {
|
||||
treeNames = strings.Split(ctx.Repo.TreePath, "/")
|
||||
for i := range treeNames {
|
||||
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
|
||||
}
|
||||
|
||||
ctx.Data["HasParentPath"] = true
|
||||
if len(paths)-2 >= 0 {
|
||||
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Paths"] = paths
|
||||
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
treeLink := branchLink
|
||||
if len(ctx.Repo.TreePath) > 0 {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
ctx.Data["TreeLink"] = treeLink
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["BranchLink"] = branchLink
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
ctx.HTML(http.StatusOK, tplRepoHome)
|
||||
}
|
||||
|
||||
func checkOutdatedBranch(ctx *context.Context) {
|
||||
if !(ctx.Repo.IsAdmin() || ctx.Repo.IsOwner()) {
|
||||
return
|
||||
}
|
||||
|
||||
// get the head commit of the branch since ctx.Repo.CommitID is not always the head commit of `ctx.Repo.BranchName`
|
||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
|
||||
if err != nil {
|
||||
log.Error("GetBranchCommitID: %v", err)
|
||||
// Don't return an error page, as it can be rechecked the next time the user opens the page.
|
||||
return
|
||||
}
|
||||
|
||||
dbBranch, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, ctx.Repo.BranchName)
|
||||
if err != nil {
|
||||
log.Error("GetBranch: %v", err)
|
||||
// Don't return an error page, as it can be rechecked the next time the user opens the page.
|
||||
return
|
||||
}
|
||||
|
||||
if dbBranch.CommitID != commit.ID.String() {
|
||||
ctx.Flash.Warning(ctx.Tr("repo.error.broken_git_hook", "https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running"), true)
|
||||
}
|
||||
}
|
||||
|
||||
// RenderUserCards render a page show users according the input template
|
||||
func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl base.TplName) {
|
||||
page := ctx.FormInt("page")
|
||||
|
313
routers/web/repo/view_file.go
Normal file
313
routers/web/repo/view_file.go
Normal file
@ -0,0 +1,313 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issue_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/renderhelper"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/highlight"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
ctx.Data["IsViewFile"] = true
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
blob := entry.Blob()
|
||||
buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
|
||||
if err != nil {
|
||||
ctx.ServerError("getFileReader", err)
|
||||
return
|
||||
}
|
||||
defer dataRc.Close()
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||
ctx.Data["FileIsSymlink"] = entry.IsLink()
|
||||
ctx.Data["FileName"] = blob.Name()
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
|
||||
commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitByPath", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !loadLatestCommitData(ctx, commit) {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.TreePath == ".editorconfig" {
|
||||
_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
|
||||
if editorconfigWarning != nil {
|
||||
ctx.Data["FileWarning"] = strings.TrimSpace(editorconfigWarning.Error())
|
||||
}
|
||||
if editorconfigErr != nil {
|
||||
ctx.Data["FileError"] = strings.TrimSpace(editorconfigErr.Error())
|
||||
}
|
||||
} else if issue_service.IsTemplateConfig(ctx.Repo.TreePath) {
|
||||
_, issueConfigErr := issue_service.GetTemplateConfig(ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.Commit)
|
||||
if issueConfigErr != nil {
|
||||
ctx.Data["FileError"] = strings.TrimSpace(issueConfigErr.Error())
|
||||
}
|
||||
} else if actions.IsWorkflow(ctx.Repo.TreePath) {
|
||||
content, err := actions.GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
log.Error("actions.GetContentFromEntry: %v", err)
|
||||
}
|
||||
_, workFlowErr := model.ReadWorkflow(bytes.NewReader(content))
|
||||
if workFlowErr != nil {
|
||||
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
|
||||
}
|
||||
} else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) {
|
||||
if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
|
||||
_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
|
||||
if len(warnings) > 0 {
|
||||
ctx.Data["FileWarning"] = strings.Join(warnings, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isDisplayingSource := ctx.FormString("display") == "source"
|
||||
isDisplayingRendered := !isDisplayingSource
|
||||
|
||||
if fInfo.isLFSFile {
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
|
||||
isRepresentableAsText := fInfo.st.IsRepresentableAsText()
|
||||
if !isRepresentableAsText {
|
||||
// If we can't show plain text, always try to render.
|
||||
isDisplayingSource = false
|
||||
isDisplayingRendered = true
|
||||
}
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
||||
ctx.Data["FileSize"] = fInfo.fileSize
|
||||
ctx.Data["IsTextFile"] = fInfo.isTextFile
|
||||
ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
|
||||
ctx.Data["IsDisplayingSource"] = isDisplayingSource
|
||||
ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
|
||||
ctx.Data["IsExecutable"] = entry.IsExecutable()
|
||||
|
||||
isTextSource := fInfo.isTextFile || isDisplayingSource
|
||||
ctx.Data["IsTextSource"] = isTextSource
|
||||
if isTextSource {
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
}
|
||||
|
||||
// Check LFS Lock
|
||||
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
|
||||
ctx.Data["LFSLock"] = lfsLock
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
return
|
||||
}
|
||||
if lfsLock != nil {
|
||||
u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["LFSLockOwner"] = u.Name
|
||||
ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
|
||||
ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
}
|
||||
|
||||
// Assume file is not editable first.
|
||||
if fInfo.isLFSFile {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
|
||||
} else if !isRepresentableAsText {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
|
||||
}
|
||||
|
||||
switch {
|
||||
case isRepresentableAsText:
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
break
|
||||
}
|
||||
|
||||
if fInfo.st.IsSvgImage() {
|
||||
ctx.Data["IsImageFile"] = true
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
}
|
||||
|
||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||
|
||||
shouldRenderSource := ctx.FormString("display") == "source"
|
||||
readmeExist := util.IsReadmeFileName(blob.Name())
|
||||
ctx.Data["ReadmeExist"] = readmeExist
|
||||
|
||||
markupType := markup.DetectMarkupTypeByFileName(blob.Name())
|
||||
if markupType == "" {
|
||||
markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
|
||||
}
|
||||
if markupType != "" {
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
}
|
||||
if markupType != "" && !shouldRenderSource {
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
|
||||
metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(ctx.Repo.TreePath).
|
||||
WithMetas(metas)
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
return
|
||||
}
|
||||
// to prevent iframe load third-party url
|
||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
|
||||
} else {
|
||||
buf, _ := io.ReadAll(rd)
|
||||
|
||||
// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
|
||||
// empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
|
||||
// Gitea uses the definition (like most modern editors):
|
||||
// empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
|
||||
// When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
|
||||
// To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
|
||||
// This NumLines is only used for the display on the UI: "xxx lines"
|
||||
if len(buf) == 0 {
|
||||
ctx.Data["NumLines"] = 0
|
||||
} else {
|
||||
ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
|
||||
}
|
||||
|
||||
language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
|
||||
}
|
||||
|
||||
fileContent, lexerName, err := highlight.File(blob.Name(), language, buf)
|
||||
ctx.Data["LexerName"] = lexerName
|
||||
if err != nil {
|
||||
log.Error("highlight.File failed, fallback to plain text: %v", err)
|
||||
fileContent = highlight.PlainText(buf)
|
||||
}
|
||||
status := &charset.EscapeStatus{}
|
||||
statuses := make([]*charset.EscapeStatus, len(fileContent))
|
||||
for i, line := range fileContent {
|
||||
statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
|
||||
status = status.Or(statuses[i])
|
||||
}
|
||||
ctx.Data["EscapeStatus"] = status
|
||||
ctx.Data["FileContent"] = fileContent
|
||||
ctx.Data["LineEscapeStatus"] = statuses
|
||||
}
|
||||
if !fInfo.isLFSFile {
|
||||
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
|
||||
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
|
||||
ctx.Data["CanEditFile"] = false
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
} else {
|
||||
ctx.Data["CanEditFile"] = true
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
|
||||
}
|
||||
} else if !ctx.Repo.IsViewBranch {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
|
||||
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
|
||||
}
|
||||
}
|
||||
|
||||
case fInfo.st.IsPDF():
|
||||
ctx.Data["IsPDFFile"] = true
|
||||
case fInfo.st.IsVideo():
|
||||
ctx.Data["IsVideoFile"] = true
|
||||
case fInfo.st.IsAudio():
|
||||
ctx.Data["IsAudioFile"] = true
|
||||
case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()):
|
||||
ctx.Data["IsImageFile"] = true
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
default:
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
|
||||
// It is used by "external renders", markupRender will execute external programs to get rendered content.
|
||||
if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType != "" {
|
||||
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(ctx.Repo.TreePath)
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
checker, deferable := ctx.Repo.GitRepo.CheckAttributeReader(ctx.Repo.CommitID)
|
||||
if checker != nil {
|
||||
defer deferable()
|
||||
attrs, err := checker.CheckPath(ctx.Repo.TreePath)
|
||||
if err == nil {
|
||||
ctx.Data["IsVendored"] = git.AttributeToBool(attrs, git.AttributeLinguistVendored).Value()
|
||||
ctx.Data["IsGenerated"] = git.AttributeToBool(attrs, git.AttributeLinguistGenerated).Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() {
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(buf))
|
||||
if err == nil {
|
||||
// There are Image formats go can't decode
|
||||
// Instead of throwing an error in that case, we show the size only when we can decode
|
||||
ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
|
||||
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
|
||||
ctx.Data["CanDeleteFile"] = false
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
} else {
|
||||
ctx.Data["CanDeleteFile"] = true
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
|
||||
}
|
||||
} else if !ctx.Repo.IsViewBranch {
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
|
||||
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
|
||||
}
|
||||
}
|
378
routers/web/repo/view_home.go
Normal file
378
routers/web/repo/view_home.go
Normal file
@ -0,0 +1,378 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/web/feed"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
||||
func checkOutdatedBranch(ctx *context.Context) {
|
||||
if !(ctx.Repo.IsAdmin() || ctx.Repo.IsOwner()) {
|
||||
return
|
||||
}
|
||||
|
||||
// get the head commit of the branch since ctx.Repo.CommitID is not always the head commit of `ctx.Repo.BranchName`
|
||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
|
||||
if err != nil {
|
||||
log.Error("GetBranchCommitID: %v", err)
|
||||
// Don't return an error page, as it can be rechecked the next time the user opens the page.
|
||||
return
|
||||
}
|
||||
|
||||
dbBranch, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, ctx.Repo.BranchName)
|
||||
if err != nil {
|
||||
log.Error("GetBranch: %v", err)
|
||||
// Don't return an error page, as it can be rechecked the next time the user opens the page.
|
||||
return
|
||||
}
|
||||
|
||||
if dbBranch.CommitID != commit.ID.String() {
|
||||
ctx.Flash.Warning(ctx.Tr("repo.error.broken_git_hook", "https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running"), true)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareHomeSidebarRepoTopics(ctx *context.Context) {
|
||||
topics, err := db.Find[repo_model.Topic](ctx, &repo_model.FindTopicOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("models.FindTopics", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Topics"] = topics
|
||||
}
|
||||
|
||||
func prepareOpenWithEditorApps(ctx *context.Context) {
|
||||
var tmplApps []map[string]any
|
||||
apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
|
||||
if len(apps) == 0 {
|
||||
apps = setting.DefaultOpenWithEditorApps()
|
||||
}
|
||||
for _, app := range apps {
|
||||
schema, _, _ := strings.Cut(app.OpenURL, ":")
|
||||
var iconHTML template.HTML
|
||||
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
|
||||
iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2")
|
||||
} else {
|
||||
iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
|
||||
}
|
||||
tmplApps = append(tmplApps, map[string]any{
|
||||
"DisplayName": app.DisplayName,
|
||||
"OpenURL": app.OpenURL,
|
||||
"IconHTML": iconHTML,
|
||||
})
|
||||
}
|
||||
ctx.Data["OpenWithEditorApps"] = tmplApps
|
||||
}
|
||||
|
||||
func prepareHomeSidebarCitationFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
||||
return func(ctx *context.Context) {
|
||||
if entry.Name() != "" {
|
||||
return
|
||||
}
|
||||
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
HandleGitError(ctx, "Repo.Commit.SubTree", err)
|
||||
return
|
||||
}
|
||||
allEntries, err := tree.ListEntries()
|
||||
if err != nil {
|
||||
ctx.ServerError("ListEntries", err)
|
||||
return
|
||||
}
|
||||
for _, entry := range allEntries {
|
||||
if entry.Name() == "CITATION.cff" || entry.Name() == "CITATION.bib" {
|
||||
// Read Citation file contents
|
||||
if content, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||
log.Error("checkCitationFile: GetBlobContent: %v", err)
|
||||
} else {
|
||||
ctx.Data["CitiationExist"] = true
|
||||
ctx.PageData["citationFileContent"] = content
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareHomeSidebarLicenses(ctx *context.Context) {
|
||||
repoLicenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepoLicenses", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DetectedRepoLicenses"] = repoLicenses.StringList()
|
||||
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||||
}
|
||||
|
||||
func prepareToRenderDirectory(ctx *context.Context) {
|
||||
entries := renderDirectoryFiles(ctx, 1*time.Second)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.TreePath != "" {
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||||
}
|
||||
|
||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
|
||||
if err != nil {
|
||||
ctx.ServerError("findReadmeFileInEntries", err)
|
||||
return
|
||||
}
|
||||
|
||||
prepareToRenderReadmeFile(ctx, subfolder, readmeFile)
|
||||
}
|
||||
|
||||
func prepareHomeSidebarLanguageStats(ctx *context.Context) {
|
||||
langs, err := repo_model.GetTopLanguageStats(ctx, ctx.Repo.Repository, 5)
|
||||
if err != nil {
|
||||
ctx.ServerError("Repo.GetTopLanguageStats", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["LanguageStats"] = langs
|
||||
}
|
||||
|
||||
func prepareHomeSidebarLatestRelease(ctx *context.Context) {
|
||||
if !ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeReleases) {
|
||||
return
|
||||
}
|
||||
|
||||
release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.ServerError("GetLatestReleaseByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if release != nil {
|
||||
if err = release.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("release.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["LatestRelease"] = release
|
||||
}
|
||||
}
|
||||
|
||||
func prepareUpstreamDivergingInfo(ctx *context.Context) {
|
||||
if !ctx.Repo.Repository.IsFork || !ctx.Repo.IsViewBranch || ctx.Repo.TreePath != "" {
|
||||
return
|
||||
}
|
||||
upstreamDivergingInfo, err := repo_service.GetUpstreamDivergingInfo(ctx, ctx.Repo.Repository, ctx.Repo.BranchName)
|
||||
if err != nil {
|
||||
if !errors.Is(err, util.ErrNotExist) && !errors.Is(err, util.ErrInvalidArgument) {
|
||||
log.Error("GetUpstreamDivergingInfo: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Data["UpstreamDivergingInfo"] = upstreamDivergingInfo
|
||||
}
|
||||
|
||||
func prepareRecentlyPushedNewBranches(ctx *context.Context) {
|
||||
if ctx.Doer != nil {
|
||||
if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
|
||||
ctx.ServerError("GetBaseRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Repo: ctx.Repo.Repository,
|
||||
BaseRepo: ctx.Repo.Repository,
|
||||
}
|
||||
if ctx.Repo.Repository.IsFork {
|
||||
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
|
||||
}
|
||||
|
||||
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
|
||||
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
|
||||
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
|
||||
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
|
||||
if err != nil {
|
||||
log.Error("FindRecentlyPushedNewBranches failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleRepoEmptyOrBroken(ctx *context.Context) {
|
||||
showEmpty := true
|
||||
var err error
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
|
||||
if err != nil {
|
||||
log.Error("GitRepo.IsEmpty: %v", err)
|
||||
ctx.Repo.Repository.Status = repo_model.RepositoryBroken
|
||||
showEmpty = true
|
||||
ctx.Flash.Error(ctx.Tr("error.occurred"), true)
|
||||
}
|
||||
}
|
||||
if showEmpty {
|
||||
ctx.HTML(http.StatusOK, tplRepoEMPTY)
|
||||
return
|
||||
}
|
||||
|
||||
// the repo is not really empty, so we should update the modal in database
|
||||
// such problem may be caused by:
|
||||
// 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually
|
||||
// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
|
||||
// it's possible for a repository to be non-empty by that flag but still 500
|
||||
// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
|
||||
ctx.Repo.Repository.IsEmpty = false
|
||||
if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
|
||||
ctx.ServerError("UpdateRepositoryCols", err)
|
||||
return
|
||||
}
|
||||
if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
|
||||
ctx.ServerError("UpdateRepoSize", err)
|
||||
return
|
||||
}
|
||||
|
||||
// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
|
||||
link := ctx.Link
|
||||
if ctx.Req.URL.RawQuery != "" {
|
||||
link += "?" + ctx.Req.URL.RawQuery
|
||||
}
|
||||
ctx.Redirect(link)
|
||||
}
|
||||
|
||||
func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
||||
return func(ctx *context.Context) {
|
||||
if entry.IsDir() {
|
||||
prepareToRenderDirectory(ctx)
|
||||
} else {
|
||||
prepareToRenderFile(ctx, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleRepoHomeFeed(ctx *context.Context) bool {
|
||||
if setting.Other.EnableFeed {
|
||||
isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
|
||||
if isFeed {
|
||||
switch {
|
||||
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
|
||||
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||
case ctx.Repo.TreePath == "":
|
||||
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||
case ctx.Repo.TreePath != "":
|
||||
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Home render repository home page
|
||||
func Home(ctx *context.Context) {
|
||||
if handleRepoHomeFeed(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether the repo is viewable: not in migration, and the code unit should be enabled
|
||||
// Ideally the "feed" logic should be after this, but old code did so, so keep it as-is.
|
||||
checkHomeCodeViewable(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||
if len(ctx.Repo.Repository.Description) > 0 {
|
||||
title += ": " + ctx.Repo.Repository.Description
|
||||
}
|
||||
ctx.Data["Title"] = title
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show New File / Upload File buttons
|
||||
|
||||
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
||||
// empty or broken repositories need to be handled differently
|
||||
handleRepoEmptyOrBroken(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// get the current git entry which doer user is currently looking at.
|
||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
|
||||
return
|
||||
}
|
||||
|
||||
// prepare the tree path
|
||||
var treeNames, paths []string
|
||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
treeLink := branchLink
|
||||
if ctx.Repo.TreePath != "" {
|
||||
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
treeNames = strings.Split(ctx.Repo.TreePath, "/")
|
||||
for i := range treeNames {
|
||||
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
|
||||
}
|
||||
ctx.Data["HasParentPath"] = true
|
||||
if len(paths)-2 >= 0 {
|
||||
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
||||
}
|
||||
}
|
||||
ctx.Data["Paths"] = paths
|
||||
ctx.Data["TreeLink"] = treeLink
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["BranchLink"] = branchLink
|
||||
|
||||
// some UI components are only shown when the tree path is root
|
||||
isTreePathRoot := ctx.Repo.TreePath == ""
|
||||
|
||||
prepareFuncs := []func(*context.Context){
|
||||
prepareOpenWithEditorApps,
|
||||
prepareHomeSidebarRepoTopics,
|
||||
checkOutdatedBranch,
|
||||
prepareToRenderDirOrFile(entry),
|
||||
prepareRecentlyPushedNewBranches,
|
||||
}
|
||||
|
||||
if isTreePathRoot {
|
||||
prepareFuncs = append(prepareFuncs,
|
||||
prepareUpstreamDivergingInfo,
|
||||
prepareHomeSidebarLicenses,
|
||||
prepareHomeSidebarCitationFile(entry),
|
||||
prepareHomeSidebarLanguageStats,
|
||||
prepareHomeSidebarLatestRelease,
|
||||
)
|
||||
}
|
||||
|
||||
for _, prepare := range prepareFuncs {
|
||||
prepare(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplRepoHome)
|
||||
}
|
218
routers/web/repo/view_readme.go
Normal file
218
routers/web/repo/view_readme.go
Normal file
@ -0,0 +1,218 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/renderhelper"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// locate a README for a tree in one of the supported paths.
|
||||
//
|
||||
// entries is passed to reduce calls to ListEntries(), so
|
||||
// this has precondition:
|
||||
//
|
||||
// entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
|
||||
//
|
||||
// FIXME: There has to be a more efficient way of doing this
|
||||
func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
|
||||
// Create a list of extensions in priority order
|
||||
// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
|
||||
// 2. Txt files - e.g. README.txt
|
||||
// 3. No extension - e.g. README
|
||||
exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority
|
||||
extCount := len(exts)
|
||||
readmeFiles := make([]*git.TreeEntry, extCount+1)
|
||||
|
||||
docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/)
|
||||
for _, entry := range entries {
|
||||
if tryWellKnownDirs && entry.IsDir() {
|
||||
// as a special case for the top-level repo introduction README,
|
||||
// fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ...
|
||||
// (note that docsEntries is ignored unless we are at the root)
|
||||
lowerName := strings.ToLower(entry.Name())
|
||||
switch lowerName {
|
||||
case "docs":
|
||||
if entry.Name() == "docs" || docsEntries[0] == nil {
|
||||
docsEntries[0] = entry
|
||||
}
|
||||
case ".gitea":
|
||||
if entry.Name() == ".gitea" || docsEntries[1] == nil {
|
||||
docsEntries[1] = entry
|
||||
}
|
||||
case ".github":
|
||||
if entry.Name() == ".github" || docsEntries[2] == nil {
|
||||
docsEntries[2] = entry
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok {
|
||||
log.Debug("Potential readme file: %s", entry.Name())
|
||||
if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) {
|
||||
if entry.IsLink() {
|
||||
target, err := entry.FollowLinks()
|
||||
if err != nil && !git.IsErrBadLink(err) {
|
||||
return "", nil, err
|
||||
} else if target != nil && (target.IsExecutable() || target.IsRegular()) {
|
||||
readmeFiles[i] = entry
|
||||
}
|
||||
} else {
|
||||
readmeFiles[i] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var readmeFile *git.TreeEntry
|
||||
for _, f := range readmeFiles {
|
||||
if f != nil {
|
||||
readmeFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.TreePath == "" && readmeFile == nil {
|
||||
for _, subTreeEntry := range docsEntries {
|
||||
if subTreeEntry == nil {
|
||||
continue
|
||||
}
|
||||
subTree := subTreeEntry.Tree()
|
||||
if subTree == nil {
|
||||
// this should be impossible; if subTreeEntry exists so should this.
|
||||
continue
|
||||
}
|
||||
childEntries, err := subTree.ListEntries()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, childEntries, false)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
return "", nil, err
|
||||
}
|
||||
if readmeFile != nil {
|
||||
return path.Join(subTreeEntry.Name(), subfolder), readmeFile, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", readmeFile, nil
|
||||
}
|
||||
|
||||
// localizedExtensions prepends the provided language code with and without a
|
||||
// regional identifier to the provided extension.
|
||||
// Note: the language code will always be lower-cased, if a region is present it must be separated with a `-`
|
||||
// Note: ext should be prefixed with a `.`
|
||||
func localizedExtensions(ext, languageCode string) (localizedExts []string) {
|
||||
if len(languageCode) < 1 {
|
||||
return []string{ext}
|
||||
}
|
||||
|
||||
lowerLangCode := "." + strings.ToLower(languageCode)
|
||||
|
||||
if strings.Contains(lowerLangCode, "-") {
|
||||
underscoreLangCode := strings.ReplaceAll(lowerLangCode, "-", "_")
|
||||
indexOfDash := strings.Index(lowerLangCode, "-")
|
||||
// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, _zh.md, .md]
|
||||
return []string{lowerLangCode + ext, underscoreLangCode + ext, lowerLangCode[:indexOfDash] + ext, "_" + lowerLangCode[1:indexOfDash] + ext, ext}
|
||||
}
|
||||
|
||||
// e.g. [.en.md, .md]
|
||||
return []string{lowerLangCode + ext, ext}
|
||||
}
|
||||
|
||||
func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
|
||||
target := readmeFile
|
||||
if readmeFile != nil && readmeFile.IsLink() {
|
||||
target, _ = readmeFile.FollowLinks()
|
||||
}
|
||||
if target == nil {
|
||||
// if findReadmeFile() failed and/or gave us a broken symlink (which it shouldn't)
|
||||
// simply skip rendering the README
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["RawFileLink"] = ""
|
||||
ctx.Data["ReadmeInList"] = true
|
||||
ctx.Data["ReadmeExist"] = true
|
||||
ctx.Data["FileIsSymlink"] = readmeFile.IsLink()
|
||||
|
||||
buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, target.Blob())
|
||||
if err != nil {
|
||||
ctx.ServerError("getFileReader", err)
|
||||
return
|
||||
}
|
||||
defer dataRc.Close()
|
||||
|
||||
ctx.Data["FileIsText"] = fInfo.isTextFile
|
||||
ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name())
|
||||
ctx.Data["FileSize"] = fInfo.fileSize
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
||||
|
||||
if fInfo.isLFSFile {
|
||||
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.Name()))
|
||||
ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.Link(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64))
|
||||
}
|
||||
|
||||
if !fInfo.isTextFile {
|
||||
return
|
||||
}
|
||||
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
// Pretend that this is a normal text file to display 'This file is too large to be shown'
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
ctx.Data["IsTextFile"] = true
|
||||
return
|
||||
}
|
||||
|
||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||
|
||||
if markupType := markup.DetectMarkupTypeByFileName(readmeFile.Name()); markupType != "" {
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.BranchNameSubURL(),
|
||||
CurrentTreePath: path.Join(ctx.Repo.TreePath, subfolder),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())) // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
||||
delete(ctx.Data, "IsMarkup")
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Data["IsMarkup"] != true {
|
||||
ctx.Data["IsPlainText"] = true
|
||||
content, err := io.ReadAll(rd)
|
||||
if err != nil {
|
||||
log.Error("Read readme content failed: %v", err)
|
||||
}
|
||||
contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
|
||||
}
|
||||
|
||||
if !fInfo.isLFSFile && ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
|
||||
ctx.Data["CanEditReadmeFile"] = true
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
gocontext "context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -645,22 +646,32 @@ func WikiPages(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := commit.ListEntries()
|
||||
treePath := "" // To support list sub folders' pages in the future
|
||||
tree, err := commit.SubTree(treePath)
|
||||
if err != nil {
|
||||
ctx.ServerError("SubTree", err)
|
||||
return
|
||||
}
|
||||
|
||||
allEntries, err := tree.ListEntries()
|
||||
if err != nil {
|
||||
ctx.ServerError("ListEntries", err)
|
||||
return
|
||||
}
|
||||
allEntries.CustomSort(base.NaturalSortLess)
|
||||
|
||||
entries, _, err := allEntries.GetCommitsInfo(gocontext.Context(ctx), commit, treePath)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitsInfo", err)
|
||||
return
|
||||
}
|
||||
|
||||
pages := make([]PageMeta, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !entry.IsRegular() {
|
||||
if !entry.Entry.IsRegular() {
|
||||
continue
|
||||
}
|
||||
c, err := wikiRepo.GetCommitByPath(entry.Name())
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommit", err)
|
||||
return
|
||||
}
|
||||
wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
|
||||
wikiName, err := wiki_service.GitPathToWebPath(entry.Entry.Name())
|
||||
if err != nil {
|
||||
if repo_model.IsErrWikiInvalidFileName(err) {
|
||||
continue
|
||||
@ -672,8 +683,8 @@ func WikiPages(ctx *context.Context) {
|
||||
pages = append(pages, PageMeta{
|
||||
Name: displayName,
|
||||
SubURL: wiki_service.WebPathToURLPath(wikiName),
|
||||
GitEntryName: entry.Name(),
|
||||
UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()),
|
||||
GitEntryName: entry.Entry.Name(),
|
||||
UpdatedUnix: timeutil.TimeStamp(entry.Commit.Author.When.Unix()),
|
||||
})
|
||||
}
|
||||
ctx.Data["Pages"] = pages
|
||||
|
@ -1321,6 +1321,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Post("/delete", repo.DeleteBranchPost)
|
||||
m.Post("/restore", repo.RestoreBranchPost)
|
||||
m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost)
|
||||
m.Post("/merge-upstream", repo.MergeUpstream)
|
||||
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
|
||||
|
||||
m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
|
||||
@ -1425,7 +1426,6 @@ func registerRoutes(m *web.Router) {
|
||||
})
|
||||
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
|
||||
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
|
||||
m.Get("/artifacts", actions.ArtifactsView)
|
||||
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
|
||||
m.Delete("/artifacts/{artifact_name}", actions.ArtifactsDeleteView)
|
||||
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
|
||||
@ -1627,9 +1627,12 @@ func registerRoutes(m *web.Router) {
|
||||
}
|
||||
|
||||
if !setting.IsProd {
|
||||
m.Any("/devtest", devtest.List)
|
||||
m.Any("/devtest/fetch-action-test", devtest.FetchActionTest)
|
||||
m.Any("/devtest/{sub}", devtest.Tmpl)
|
||||
m.Group("/devtest", func() {
|
||||
m.Any("", devtest.List)
|
||||
m.Any("/fetch-action-test", devtest.FetchActionTest)
|
||||
m.Any("/{sub}", devtest.Tmpl)
|
||||
m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
|
||||
})
|
||||
}
|
||||
|
||||
m.NotFound(func(w http.ResponseWriter, req *http.Request) {
|
||||
|
@ -396,13 +396,6 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
||||
ctx.Repo.Repository = repo
|
||||
ctx.Data["RepoName"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
|
||||
|
||||
repoLicenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepoLicenses", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DetectedRepoLicenses"] = repoLicenses.StringList()
|
||||
}
|
||||
|
||||
// RepoAssignment returns a middleware to handle repository assignment
|
||||
@ -1036,7 +1029,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
|
||||
ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
|
||||
ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
|
||||
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
|
||||
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
|
||||
|
||||
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
|
||||
if err != nil {
|
||||
|
@ -65,7 +65,9 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
||||
return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
|
||||
}
|
||||
|
||||
// use merge functions but switch repos and branches
|
||||
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
|
||||
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
|
||||
// now use a fake reverse PR to switch head&base repos/branches
|
||||
reversePR := &issues_model.PullRequest{
|
||||
ID: pr.ID,
|
||||
|
||||
|
115
services/repository/merge_upstream.go
Normal file
115
services/repository/merge_upstream.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issue_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/pull"
|
||||
)
|
||||
|
||||
type UpstreamDivergingInfo struct {
|
||||
BaseIsNewer bool
|
||||
CommitsBehind int
|
||||
CommitsAhead int
|
||||
}
|
||||
|
||||
func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
|
||||
if err = repo.MustNotBeArchived(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = repo.GetBaseRepo(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
|
||||
Remote: repo.RepoPath(),
|
||||
Branch: fmt.Sprintf("%s:%s", branch, branch),
|
||||
Env: repo_module.PushingEnvironment(doer, repo),
|
||||
})
|
||||
if err == nil {
|
||||
return "fast-forward", nil
|
||||
}
|
||||
if !git.IsErrPushOutOfDate(err) && !git.IsErrPushRejected(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
|
||||
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
|
||||
fakeIssue := &issue_model.Issue{
|
||||
ID: -1,
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Index: -1,
|
||||
PosterID: doer.ID,
|
||||
Poster: doer,
|
||||
IsPull: true,
|
||||
}
|
||||
fakePR := &issue_model.PullRequest{
|
||||
ID: -1,
|
||||
Status: issue_model.PullRequestStatusMergeable,
|
||||
IssueID: -1,
|
||||
Issue: fakeIssue,
|
||||
Index: -1,
|
||||
HeadRepoID: repo.ID,
|
||||
HeadRepo: repo,
|
||||
BaseRepoID: repo.BaseRepo.ID,
|
||||
BaseRepo: repo.BaseRepo,
|
||||
HeadBranch: branch, // maybe HeadCommitID is not needed
|
||||
BaseBranch: branch,
|
||||
}
|
||||
fakeIssue.PullRequest = fakePR
|
||||
err = pull.Update(ctx, fakePR, doer, "merge upstream", false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "merge", nil
|
||||
}
|
||||
|
||||
func GetUpstreamDivergingInfo(ctx context.Context, repo *repo_model.Repository, branch string) (*UpstreamDivergingInfo, error) {
|
||||
if !repo.IsFork {
|
||||
return nil, util.NewInvalidArgumentErrorf("repo is not a fork")
|
||||
}
|
||||
|
||||
if repo.IsArchived {
|
||||
return nil, util.NewInvalidArgumentErrorf("repo is archived")
|
||||
}
|
||||
|
||||
if err := repo.GetBaseRepo(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forkBranch, err := git_model.GetBranch(ctx, repo.ID, branch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, branch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := &UpstreamDivergingInfo{}
|
||||
if forkBranch.CommitID == baseBranch.CommitID {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// TODO: if the fork repo has new commits, this call will fail:
|
||||
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
||||
// so at the moment, we are not able to handle this case, should be improved in the future
|
||||
diff, err := git.GetDivergingCommits(ctx, repo.BaseRepo.RepoPath(), baseBranch.CommitID, forkBranch.CommitID)
|
||||
if err != nil {
|
||||
info.BaseIsNewer = baseBranch.UpdatedUnix > forkBranch.UpdatedUnix
|
||||
return info, nil
|
||||
}
|
||||
info.CommitsBehind, info.CommitsAhead = diff.Behind, diff.Ahead
|
||||
return info, nil
|
||||
}
|
30
templates/devtest/repo-action-view.tmpl
Normal file
30
templates/devtest/repo-action-view.tmpl
Normal file
@ -0,0 +1,30 @@
|
||||
{{template "base/head" .}}
|
||||
<div class="page-content">
|
||||
<div id="repo-action-view"
|
||||
data-run-index="1"
|
||||
data-job-index="2"
|
||||
data-actions-url="{{AppSubUrl}}/devtest/actions-mock"
|
||||
data-locale-approve="approve"
|
||||
data-locale-cancel="cancel"
|
||||
data-locale-rerun="re-run"
|
||||
data-locale-rerun-all="re-run all"
|
||||
data-locale-runs-scheduled="scheduled"
|
||||
data-locale-runs-commit="commit"
|
||||
data-locale-runs-pushed-by="pushed by"
|
||||
data-locale-status-unknown="unknown"
|
||||
data-locale-status-waiting="waiting"
|
||||
data-locale-status-running="running"
|
||||
data-locale-status-success="success"
|
||||
data-locale-status-failure="failure"
|
||||
data-locale-status-cancelled="cancelled"
|
||||
data-locale-status-skipped="skipped"
|
||||
data-locale-status-blocked="blocked"
|
||||
data-locale-artifacts-title="artifacts"
|
||||
data-locale-confirm-delete-artifact="confirm delete artifact"
|
||||
data-locale-show-timestamps="show timestamps"
|
||||
data-locale-show-log-seconds="show log seconds"
|
||||
data-locale-show-full-screen="show full screen"
|
||||
data-locale-download-logs="download logs"
|
||||
></div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
@ -1,15 +1,13 @@
|
||||
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings labels")}}
|
||||
<div class="org-setting-content">
|
||||
<div class="tw-flex tw-items-center">
|
||||
<div class="tw-flex-1">
|
||||
{{ctx.Locale.Tr "org.settings.labels_desc"}}
|
||||
</div>
|
||||
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
{{template "repo/issue/labels/label_new" .}}
|
||||
{{template "repo/issue/labels/label_list" .}}
|
||||
</div>
|
||||
{{template "repo/issue/labels/edit_delete_label" .}}
|
||||
<div class="org-setting-content">
|
||||
<div class="tw-flex tw-items-center">
|
||||
<div class="tw-flex-1">
|
||||
{{ctx.Locale.Tr "org.settings.labels_desc"}}
|
||||
</div>
|
||||
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
{{template "repo/issue/labels/label_list" .}}
|
||||
{{template "repo/issue/labels/label_edit_modal" .}}
|
||||
</div>
|
||||
{{template "org/settings/layout_footer" .}}
|
||||
|
||||
|
18
templates/repo/code/upstream_diverging_info.tmpl
Normal file
18
templates/repo/code/upstream_diverging_info.tmpl
Normal file
@ -0,0 +1,18 @@
|
||||
{{if and .UpstreamDivergingInfo (or .UpstreamDivergingInfo.BaseIsNewer .UpstreamDivergingInfo.CommitsBehind)}}
|
||||
<div class="ui message flex-text-block">
|
||||
<div class="tw-flex-1">
|
||||
{{$upstreamLink := printf "%s/src/branch/%s" .Repository.BaseRepo.Link (.BranchName|PathEscapeSegments)}}
|
||||
{{$upstreamHtml := HTMLFormat `<a href="%s">%s:%s</a>` $upstreamLink .Repository.BaseRepo.FullName .BranchName}}
|
||||
{{if .UpstreamDivergingInfo.CommitsBehind}}
|
||||
{{ctx.Locale.TrN .UpstreamDivergingInfo.CommitsBehind "repo.pulls.upstream_diverging_prompt_behind_1" "repo.pulls.upstream_diverging_prompt_behind_n" .UpstreamDivergingInfo.CommitsBehind $upstreamHtml}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.pulls.upstream_diverging_prompt_base_newer" $upstreamHtml}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .CanWriteCode}}
|
||||
<button class="ui compact green button tw-m-0 link-action" data-url="{{.Repository.Link}}/branches/merge-upstream?branch={{.BranchName}}">
|
||||
{{ctx.Locale.Tr "repo.pulls.upstream_diverging_merge"}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
@ -3,35 +3,7 @@
|
||||
{{template "repo/header" .}}
|
||||
<div class="ui container {{if .IsBlame}}fluid padded{{end}}">
|
||||
{{template "base/alert" .}}
|
||||
{{template "repo/code/recently_pushed_new_branches" .}}
|
||||
{{if and (not .HideRepoInfo) (not .IsBlame)}}
|
||||
<div class="repo-description tw-break-anywhere">
|
||||
{{- $description := .Repository.DescriptionHTML ctx -}}
|
||||
{{if $description}}{{$description | RenderCodeBlock}}{{end}}
|
||||
{{if .Repository.Website}}<a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" id="repo-topics">
|
||||
{{/* it should match the code in issue-home.js */}}
|
||||
{{range .Topics}}<a class="repo-topic ui large label" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
|
||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
|
||||
<div class="ui form tw-hidden tw-flex tw-gap-2 tw-my-2" id="topic_edit">
|
||||
<div class="ui fluid multiple search selection dropdown tw-flex-wrap tw-flex-1">
|
||||
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
|
||||
{{range .Topics}}
|
||||
{{/* keep the same layout as Fomantic UI generated labels */}}
|
||||
<a class="ui label transition visible tw-cursor-default tw-inline-block" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a>
|
||||
{{end}}
|
||||
<div class="text"></div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
|
||||
<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{ctx.Locale.Tr "save"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Repository.IsArchived}}
|
||||
<div class="ui warning message tw-text-center">
|
||||
{{if .Repository.ArchivedUnix.IsZero}}
|
||||
@ -41,134 +13,141 @@
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{template "repo/sub_menu" .}}
|
||||
{{$n := len .TreeNames}}
|
||||
{{$l := Eval $n "-" 1}}
|
||||
{{$isHomepage := (eq $n 0)}}
|
||||
<div class="repo-button-row" data-is-homepage="{{$isHomepage}}">
|
||||
<div class="repo-button-row-left">
|
||||
{{$branchDropdownCurrentRefType := "branch"}}
|
||||
{{$branchDropdownCurrentRefShortName := .BranchName}}
|
||||
{{if .IsViewTag}}
|
||||
{{$branchDropdownCurrentRefType = "tag"}}
|
||||
{{$branchDropdownCurrentRefShortName = .TagName}}
|
||||
{{end}}
|
||||
{{template "repo/branch_dropdown" dict
|
||||
"Repository" .Repository
|
||||
"ShowTabBranches" true
|
||||
"ShowTabTags" true
|
||||
"CurrentRefType" $branchDropdownCurrentRefType
|
||||
"CurrentRefShortName" $branchDropdownCurrentRefShortName
|
||||
"CurrentTreePath" .TreePath
|
||||
"RefLinkTemplate" "{RepoLink}/src/{RefType}/{RefShortName}/{TreePath}"
|
||||
"AllowCreateNewRef" .CanCreateBranch
|
||||
"ShowViewAllRefsEntry" true
|
||||
}}
|
||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||
{{$cmpBranch := ""}}
|
||||
{{if ne .Repository.ID .BaseRepo.ID}}
|
||||
{{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}}
|
||||
{{end}}
|
||||
{{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}}
|
||||
{{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}}
|
||||
<a id="new-pull-request" role="button" class="ui compact basic button" href="{{$compareLink}}"
|
||||
data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}">
|
||||
{{svg "octicon-git-pull-request"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<!-- Show go to file and breadcrumbs if not on home page -->
|
||||
{{if $isHomepage}}
|
||||
<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a>
|
||||
{{end}}
|
||||
|
||||
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
|
||||
<button class="ui dropdown basic compact jump button"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
||||
{{ctx.Locale.Tr "repo.editor.add_file"}}
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{ctx.Locale.Tr "repo.editor.new_file"}}
|
||||
</a>
|
||||
{{if .RepositoryUploadEnabled}}
|
||||
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{ctx.Locale.Tr "repo.editor.upload_file"}}
|
||||
</a>
|
||||
{{template "repo/code/recently_pushed_new_branches" .}}
|
||||
|
||||
{{$treeNamesLen := len .TreeNames}}
|
||||
{{$isTreePathRoot := eq $treeNamesLen 0}}
|
||||
{{$showSidebar := $isTreePathRoot}}
|
||||
<div class="{{Iif $showSidebar "repo-grid-filelist-sidebar" "repo-grid-filelist-only"}}">
|
||||
<div class="repo-home-filelist">
|
||||
{{template "repo/sub_menu" .}}
|
||||
<div class="repo-button-row">
|
||||
<div class="repo-button-row-left">
|
||||
{{$branchDropdownCurrentRefType := "branch"}}
|
||||
{{$branchDropdownCurrentRefShortName := .BranchName}}
|
||||
{{if .IsViewTag}}
|
||||
{{$branchDropdownCurrentRefType = "tag"}}
|
||||
{{$branchDropdownCurrentRefShortName = .TagName}}
|
||||
{{end}}
|
||||
{{template "repo/branch_dropdown" dict
|
||||
"Repository" .Repository
|
||||
"ShowTabBranches" true
|
||||
"ShowTabTags" true
|
||||
"CurrentRefType" $branchDropdownCurrentRefType
|
||||
"CurrentRefShortName" $branchDropdownCurrentRefShortName
|
||||
"CurrentTreePath" .TreePath
|
||||
"RefLinkTemplate" "{RepoLink}/src/{RefType}/{RefShortName}/{TreePath}"
|
||||
"AllowCreateNewRef" .CanCreateBranch
|
||||
"ShowViewAllRefsEntry" true
|
||||
}}
|
||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||
{{$cmpBranch := ""}}
|
||||
{{if ne .Repository.ID .BaseRepo.ID}}
|
||||
{{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}}
|
||||
{{end}}
|
||||
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{ctx.Locale.Tr "repo.editor.patch"}}
|
||||
{{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}}
|
||||
{{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}}
|
||||
<a id="new-pull-request" role="button" class="ui compact basic button" href="{{$compareLink}}"
|
||||
data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}">
|
||||
{{svg "octicon-git-pull-request"}}
|
||||
</a>
|
||||
</div>
|
||||
</button>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if and $isHomepage (.Repository.IsTemplate)}}
|
||||
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
|
||||
{{ctx.Locale.Tr "repo.use_template"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if $isHomepage}}
|
||||
{{/* only show the "code search" on the repo home page, it only does global search,
|
||||
so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
|
||||
<form class="ignore-dirty tw-flex tw-flex-1" action="{{.RepoLink}}/search" method="get">
|
||||
<div class="ui small action input tw-flex-1">
|
||||
<input name="q" size="10" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||
{{template "shared/search/button"}}
|
||||
</div>
|
||||
</form>
|
||||
{{else}}
|
||||
<span class="breadcrumb repo-path tw-ml-1">
|
||||
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
|
||||
{{- range $i, $v := .TreeNames -}}
|
||||
<span class="breadcrumb-divider">/</span>
|
||||
{{- if eq $i $l -}}
|
||||
<span class="active section" title="{{$v}}">{{$v}}</span>
|
||||
<button class="btn interact-fg tw-mx-1" data-clipboard-text="{{$.TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
|
||||
{{- else -}}
|
||||
{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="repo-button-row-right">
|
||||
<!-- Only show clone panel in repository home page -->
|
||||
{{if $isHomepage}}
|
||||
<div class="clone-panel ui action tiny input">
|
||||
{{template "repo/clone_buttons" .}}
|
||||
<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
|
||||
{{svg "octicon-kebab-horizontal"}}
|
||||
<div class="menu">
|
||||
{{if not $.DisableDownloadSourceArchives}}
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
|
||||
{{end}}
|
||||
{{if .CitiationExist}}
|
||||
<a class="item" id="cite-repo-button">{{svg "octicon-cross-reference" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}</a>
|
||||
{{end}}
|
||||
{{range .OpenWithEditorApps}}
|
||||
<a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</button>
|
||||
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
|
||||
<!-- Show go to file if on home page -->
|
||||
{{if $isTreePathRoot}}
|
||||
<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a>
|
||||
{{end}}
|
||||
|
||||
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
|
||||
<button class="ui dropdown basic compact jump button"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
||||
{{ctx.Locale.Tr "repo.editor.add_file"}}
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{ctx.Locale.Tr "repo.editor.new_file"}}
|
||||
</a>
|
||||
{{if .RepositoryUploadEnabled}}
|
||||
<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{ctx.Locale.Tr "repo.editor.upload_file"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{ctx.Locale.Tr "repo.editor.patch"}}
|
||||
</a>
|
||||
</div>
|
||||
</button>
|
||||
{{end}}
|
||||
|
||||
{{if and $isTreePathRoot .Repository.IsTemplate}}
|
||||
<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
|
||||
{{ctx.Locale.Tr "repo.use_template"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
{{if not $isTreePathRoot}}
|
||||
{{$treeNameIdxLast := Eval $treeNamesLen "-" 1}}
|
||||
<span class="breadcrumb repo-path tw-ml-1">
|
||||
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
|
||||
{{- range $i, $v := .TreeNames -}}
|
||||
<span class="breadcrumb-divider">/</span>
|
||||
{{- if eq $i $treeNameIdxLast -}}
|
||||
<span class="active section" title="{{$v}}">{{$v}}</span>
|
||||
<button class="btn interact-fg tw-mx-1" data-clipboard-text="{{$.TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
|
||||
{{- else -}}
|
||||
{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "repo/cite/cite_modal" .}}
|
||||
{{end}}
|
||||
{{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
|
||||
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
|
||||
</a>
|
||||
|
||||
<div class="repo-button-row-right">
|
||||
<!-- Only show clone panel in repository home page -->
|
||||
{{if $isTreePathRoot}}
|
||||
<div class="clone-panel ui action tiny input">
|
||||
{{template "repo/clone_buttons" .}}
|
||||
<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
|
||||
{{svg "octicon-kebab-horizontal"}}
|
||||
<div class="menu">
|
||||
{{if not $.DisableDownloadSourceArchives}}
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
|
||||
{{end}}
|
||||
{{range .OpenWithEditorApps}}
|
||||
<a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</button>
|
||||
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
|
||||
</div>
|
||||
{{template "repo/cite/cite_modal" .}}
|
||||
{{end}}
|
||||
{{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
|
||||
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
|
||||
{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if .IsViewFile}}
|
||||
{{template "repo/view_file" .}}
|
||||
{{else if .IsBlame}}
|
||||
{{template "repo/blame" .}}
|
||||
{{else}}{{/* IsViewDirectory */}}
|
||||
{{if $isTreePathRoot}}
|
||||
{{template "repo/code/upstream_diverging_info" .}}
|
||||
{{end}}
|
||||
{{template "repo/view_list" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if $showSidebar}}
|
||||
<div class="repo-home-sidebar-top">{{template "repo/home_sidebar_top" .}}</div>
|
||||
<div class="repo-home-sidebar-bottom">{{template "repo/home_sidebar_bottom" .}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .IsViewFile}}
|
||||
{{template "repo/view_file" .}}
|
||||
{{else if .IsBlame}}
|
||||
{{template "repo/blame" .}}
|
||||
{{else}}{{/* IsViewDirectory */}}
|
||||
{{template "repo/view_list" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
59
templates/repo/home_sidebar_bottom.tmpl
Normal file
59
templates/repo/home_sidebar_bottom.tmpl
Normal file
@ -0,0 +1,59 @@
|
||||
<div class="flex-list">
|
||||
{{if .LatestRelease}}
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">
|
||||
<a class="item muted" href="{{.Link}}/releases">
|
||||
{{ctx.Locale.Tr "repo.releases"}}
|
||||
<span class="ui small label">{{.NumReleases}}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-icon">
|
||||
{{svg "octicon-tag" 16}}
|
||||
</div>
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-header">
|
||||
<div class="flex-item-title tw-gap-2">
|
||||
<a class="gt-ellipsis muted" href="{{.LatestRelease.Link}}" title="{{.LatestRelease.Title}}">{{.LatestRelease.Title}}</a>
|
||||
<span class="ui basic green label tw-h-100">{{ctx.Locale.Tr "latest"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item-body">
|
||||
<span class="time">{{DateUtils.TimeSince .LatestRelease.CreatedUnix}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if and (not .IsEmptyRepo) .LanguageStats}}
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">
|
||||
{{ctx.Locale.Tr "repo.repo_lang"}}
|
||||
</div>
|
||||
|
||||
<div class="flex-item-body">
|
||||
<div class="language-stats">
|
||||
{{range .LanguageStats}}
|
||||
<div class="bar" style="width: {{.Percentage}}%; background-color: {{.Color}}" data-tooltip-placement="top" data-tooltip-content={{.Language}} data-tooltip-follow-cursor="horizontal"></div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="language-stats-details">
|
||||
{{range .LanguageStats}}
|
||||
<div class="item">
|
||||
<i class="color-icon" style="background-color: {{.Color}}"></i>
|
||||
<span class="tw-font-semibold">
|
||||
{{Iif (eq .Language "other") (ctx.Locale.Tr "repo.language_other") .Language}}
|
||||
</span>
|
||||
{{.Percentage}}%
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
67
templates/repo/home_sidebar_top.tmpl
Normal file
67
templates/repo/home_sidebar_top.tmpl
Normal file
@ -0,0 +1,67 @@
|
||||
<form class="ignore-dirty tw-flex tw-flex-1 tw-mt-1" action="{{.RepoLink}}/search" method="get">
|
||||
<div class="ui small action input tw-flex-1">
|
||||
<input name="q" size="10" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||
{{template "shared/search/button"}}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="flex-list">
|
||||
<div class="flex-item">
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">
|
||||
{{ctx.Locale.Tr "repo.repo_desc"}}
|
||||
</div>
|
||||
{{if and (not .HideRepoInfo) (not .IsBlame)}}
|
||||
<div class="flex-item-body repo-description tw-break-anywhere tw-gap-2 tw-mt-2">
|
||||
{{- $description := .Repository.DescriptionHTML ctx -}}
|
||||
{{if $description}}{{$description | RenderCodeBlock}}{{else}}{{ctx.Locale.Tr "repo.repo_no_desc"}}{{end}}
|
||||
{{if .Repository.Website}}{{svg "octicon-link"}}<a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" id="repo-topics">
|
||||
{{/* !!!! it SHOULD and MUST match the code in issue-home.js */}}
|
||||
{{range .Topics}}<a class="repo-topic ui large label gt-ellipsis" title={{.Name}} href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
|
||||
</div>
|
||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
|
||||
<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
|
||||
<div class="ui form tw-hidden flex-item-body tw-gap-2 tw-my-2" id="topic_edit">
|
||||
<div class="ui fluid multiple search selection dropdown tw-flex-wrap tw-flex-1">
|
||||
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
|
||||
{{range .Topics}}
|
||||
{{/* keep the same layout as Fomantic UI generated labels */}}
|
||||
<a class="ui label transition visible tw-cursor-default tw-inline-block repo-topic" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a>
|
||||
{{end}}
|
||||
<div class="text"></div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{ctx.Locale.Tr "save"}}</button>
|
||||
<button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .ReadmeExist}}
|
||||
<div class="flex-item-body tw-mt-2">
|
||||
<a class="tw-flex tw-items-center tw-gap-2 muted" href="{{.TreeLink}}/{{.FileName}}">
|
||||
{{svg "octicon-book"}}{{ctx.Locale.Tr "readme"}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .DetectedRepoLicenses}}
|
||||
<div class="flex-item-body">
|
||||
<a class="tw-flex tw-items-center tw-gap-2 muted" href="{{.RepoLink}}/src/{{.Repository.DefaultBranch}}/{{PathEscapeSegments .LicenseFileName}}" title="{{StringUtils.Join .DetectedRepoLicenses ", "}}">
|
||||
{{svg "octicon-law"}}{{if eq (len .DetectedRepoLicenses) 1}}{{index .DetectedRepoLicenses 0}}{{else}}{{ctx.Locale.Tr "repo.multiple_licenses"}}{{end}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .CitiationExist}}
|
||||
<div class="flex-item-body">
|
||||
<a class="tw-flex tw-items-center tw-gap-2 muted" id="cite-repo-button">
|
||||
{{svg "octicon-cross-reference"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -8,15 +8,11 @@
|
||||
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
||||
{{template "repo/issue/labels/label_new" .}}
|
||||
{{end}}
|
||||
{{template "base/alert" .}}
|
||||
{{template "repo/issue/labels/label_list" .}}
|
||||
</div>
|
||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
||||
{{template "repo/issue/labels/label_edit_modal" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
||||
{{template "repo/issue/labels/edit_delete_label" .}}
|
||||
{{end}}
|
||||
{{template "base/footer" .}}
|
||||
|
@ -1,22 +1,13 @@
|
||||
<div class="ui g-modal-confirm delete modal">
|
||||
<div class="header">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "repo.issues.label_deletion"}}
|
||||
</div>
|
||||
<div class="ui small modal" id="issue-label-edit-modal"
|
||||
data-current-page-link="{{$.Link}}"{{/*will be used to construct "new label" and "edit label" URLs*/}}
|
||||
data-text-new-label="{{ctx.Locale.Tr "repo.issues.new_label"}}"
|
||||
data-text-edit-label="{{ctx.Locale.Tr "repo.issues.label_modify"}}"
|
||||
>
|
||||
<div class="header"></div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "repo.issues.label_deletion_desc"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
||||
<div class="ui small edit-label modal">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.label_modify"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<form class="ui edit-label form ignore-dirty" action="{{$.Link}}/edit" method="post">
|
||||
<form class="ui form ignore-dirty" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input id="label-modal-id" name="id" type="hidden">
|
||||
<input name="id" type="hidden">
|
||||
<div class="required field">
|
||||
<label for="name">{{ctx.Locale.Tr "repo.issues.label_title"}}</label>
|
||||
<div class="ui small input">
|
@ -27,6 +27,8 @@
|
||||
{{end}}
|
||||
|
||||
<ul class="issue-label-list">
|
||||
{{$canEditLabel := and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
|
||||
{{$canEditLabel = or $canEditLabel $.PageIsOrgSettingsLabels}}
|
||||
{{range .Labels}}
|
||||
<li class="item">
|
||||
<div class="label-title">
|
||||
@ -43,12 +45,16 @@
|
||||
<div class="label-operation tw-flex">
|
||||
{{template "repo/issue/labels/label_archived" .}}
|
||||
<div class="tw-flex tw-ml-auto">
|
||||
{{if and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
|
||||
<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} {{if gt .ArchivedUnix 0}}data-is-archived{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{ctx.Locale.Tr "repo.issues.label_edit"}}</a>
|
||||
<a class="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
|
||||
{{else if $.PageIsOrgSettingsLabels}}
|
||||
<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} {{if gt .ArchivedUnix 0}}data-is-archived{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{ctx.Locale.Tr "repo.issues.label_edit"}}</a>
|
||||
<a class="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
|
||||
{{if $canEditLabel}}
|
||||
<a class="edit-label-button" href="#"
|
||||
data-label-id="{{.ID}}" data-label-name="{{.Name}}" data-label-color="{{.Color}}"
|
||||
data-label-exclusive="{{.Exclusive}}" data-label-is-archived="{{gt .ArchivedUnix 0}}"
|
||||
data-label-num-issues="{{.NumIssues}}" data-label-description="{{.Description}}"
|
||||
>{{svg "octicon-pencil"}} {{ctx.Locale.Tr "repo.issues.label_edit"}}</a>
|
||||
<a class="link-action" href="#" data-url="{{$.Link}}/delete?id={{.ID}}"
|
||||
data-modal-confirm-header="{{ctx.Locale.Tr "repo.issues.label_deletion"}}"
|
||||
data-modal-confirm-content="{{ctx.Locale.Tr "repo.issues.label_deletion_desc"}}"
|
||||
>{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,48 +0,0 @@
|
||||
<div class="ui small new-label modal">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.new_label"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<form class="ui new-label form ignore-dirty" action="{{$.Link}}/new" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field">
|
||||
<label for="name">{{ctx.Locale.Tr "repo.issues.label_title"}}</label>
|
||||
<div class="ui small input">
|
||||
<input class="label-name-input" name="title" placeholder="{{ctx.Locale.Tr "repo.issues.new_label_placeholder"}}" autofocus required maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field label-exclusive-input-field">
|
||||
<div class="ui checkbox">
|
||||
<input class="label-exclusive-input" name="exclusive" type="checkbox">
|
||||
<label>{{ctx.Locale.Tr "repo.issues.label_exclusive"}}</label>
|
||||
</div>
|
||||
<br>
|
||||
<small class="desc">{{ctx.Locale.Tr "repo.issues.label_exclusive_desc"}}</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="description">{{ctx.Locale.Tr "repo.issues.label_description"}}</label>
|
||||
<div class="ui small fluid input">
|
||||
<input class="label-desc-input" name="description" placeholder="{{ctx.Locale.Tr "repo.issues.new_label_desc_placeholder"}}" maxlength="200">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field color-field">
|
||||
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
||||
<div class="js-color-picker-input column">
|
||||
<input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7">
|
||||
{{template "repo/issue/label_precolors"}}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="ui cancel button">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "cancel"}}
|
||||
</button>
|
||||
<button class="ui primary ok button">
|
||||
{{svg "octicon-check"}}
|
||||
{{ctx.Locale.Tr "repo.issues.create_label"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
@ -13,11 +13,6 @@
|
||||
{{svg "octicon-tag"}} <b>{{ctx.Locale.PrettyNumber .NumTags}}</b> {{ctx.Locale.TrN .NumTags "repo.tag" "repo.tags"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .DetectedRepoLicenses}}
|
||||
<a class="item muted" href="{{.RepoLink}}/src/{{.Repository.DefaultBranch}}/{{PathEscapeSegments .LicenseFileName}}" data-tooltip-placement="top" data-tooltip-content="{{StringUtils.Join .DetectedRepoLicenses ", "}}">
|
||||
{{svg "octicon-law"}} <b>{{if eq (len .DetectedRepoLicenses) 1}}{{index .DetectedRepoLicenses 0}}{{else}}{{ctx.Locale.Tr "repo.multiple_licenses"}}{{end}}</b>
|
||||
</a>
|
||||
{{end}}
|
||||
<span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-placement="top" data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>
|
||||
{{$fileSizeFormatted := FileSize .Repository.Size}}{{/* the formatted string is always "{val} {unit}" */}}
|
||||
{{$fileSizeFields := StringUtils.Split $fileSizeFormatted " "}}
|
||||
@ -25,27 +20,5 @@
|
||||
</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if and (.Permission.CanRead ctx.Consts.RepoUnitTypeCode) (not .IsEmptyRepo) .LanguageStats}}
|
||||
<div class="ui segment sub-menu language-stats-details tw-hidden">
|
||||
{{range .LanguageStats}}
|
||||
<div class="item">
|
||||
<i class="color-icon" style="background-color: {{.Color}}"></i>
|
||||
<span class="tw-font-semibold">
|
||||
{{if eq .Language "other"}}
|
||||
{{ctx.Locale.Tr "repo.language_other"}}
|
||||
{{else}}
|
||||
{{.Language}}
|
||||
{{end}}
|
||||
</span>
|
||||
{{.Percentage}}%
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<a class="ui segment language-stats show-panel toggle" data-panel=".repository-summary > .sub-menu">
|
||||
{{range .LanguageStats}}
|
||||
<div class="bar" style="width: {{.Percentage}}%; background-color: {{.Color}}" data-tooltip-placement="top" data-tooltip-content={{.Language}} data-tooltip-follow-cursor="horizontal"></div>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<table id="repo-files-table" class="ui single line table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
|
||||
<table id="repo-files-table" class="ui single line fixed table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
|
||||
<thead>
|
||||
<tr class="commit-list">
|
||||
<th class="tw-overflow-hidden" colspan="2">
|
||||
|
@ -6,15 +6,18 @@ package integration
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -50,3 +53,23 @@ func TestRepoCloneWiki(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RepoWikiPages(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
url := "/user2/repo1/wiki/?action=_pages"
|
||||
req := NewRequest(t, "GET", url)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
expectedPagePaths := []string{
|
||||
"Home", "Page-With-Image", "Page-With-Spaced-Name", "Unescaped-File",
|
||||
}
|
||||
doc.Find("tr").Each(func(i int, s *goquery.Selection) {
|
||||
firstAnchor := s.Find("a").First()
|
||||
href, _ := firstAnchor.Attr("href")
|
||||
pagePath := strings.TrimPrefix(href, "/user2/repo1/wiki/")
|
||||
|
||||
assert.EqualValues(t, expectedPagePaths[i], pagePath)
|
||||
})
|
||||
}
|
@ -65,6 +65,7 @@
|
||||
@import "./repo/linebutton.css";
|
||||
@import "./repo/wiki.css";
|
||||
@import "./repo/header.css";
|
||||
@import "./repo/home.css";
|
||||
@import "./repo/reactions.css";
|
||||
|
||||
@import "./editor/fileeditor.css";
|
||||
|
@ -422,14 +422,6 @@ td .commit-summary {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
.repository.file.list .sidebar {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.repository.file.list .sidebar .svg {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.repo-editor-header {
|
||||
width: 100%;
|
||||
}
|
||||
@ -1822,16 +1814,6 @@ td .commit-summary {
|
||||
background: var(--color-secondary);
|
||||
}
|
||||
|
||||
.repository .repository-summary .segment.language-stats {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
padding: 0;
|
||||
height: 10px;
|
||||
white-space: nowrap;
|
||||
border-radius: 0 0 3px 3px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#cite-repo-modal #citation-panel {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@ -2079,17 +2061,6 @@ td .commit-summary {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.edit-label.modal .form .column,
|
||||
.new-label.modal .form .column {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.edit-label.modal .form .buttons,
|
||||
.new-label.modal .form .buttons {
|
||||
margin-left: auto;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.stats-table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
@ -2172,11 +2143,7 @@ td .commit-summary {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.repo-button-row[data-is-homepage="false"] .repo-button-row-right {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
@media (max-width: 1200px) {
|
||||
.repository:not(.wiki) .repo-button-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
@ -2302,6 +2269,7 @@ tbody.commit-list {
|
||||
font-weight: var(--font-weight-normal);
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#new-dependency-drop-list.ui.selection.dropdown {
|
||||
@ -2820,9 +2788,9 @@ tbody.commit-list {
|
||||
/* FIXME: These media selectors are not ideal (just keep them from old code).
|
||||
There are many different pages, some need the max-width while some others don't,
|
||||
they should be tested and improved in the future. */
|
||||
@media (min-width: 768px) and (max-width: 991.98px) {
|
||||
@media (min-width: 768px) and (max-width: 1235px) {
|
||||
.branch-selector-dropdown .branch-dropdown-button {
|
||||
max-width: 185px;
|
||||
max-width: 301px;
|
||||
}
|
||||
}
|
||||
|
||||
|
77
web_src/css/repo/home.css
Normal file
77
web_src/css/repo/home.css
Normal file
@ -0,0 +1,77 @@
|
||||
.repo-grid-filelist-sidebar {
|
||||
display: grid;
|
||||
grid-template-columns: auto 300px;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
}
|
||||
|
||||
.repo-grid-filelist-sidebar .repo-home-filelist {
|
||||
min-width: 0;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 4;
|
||||
}
|
||||
|
||||
.repo-grid-filelist-sidebar .repo-home-sidebar-top {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.repo-grid-filelist-sidebar .repo-home-sidebar-bottom {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.repo-home-sidebar-bottom > :first-child {
|
||||
border-top: 1px solid var(--color-secondary); /* same to .flex-list > .flex-item + .flex-item */
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.repo-grid-filelist-sidebar {
|
||||
grid-template-columns: 100%;
|
||||
grid-template-rows: auto auto auto;
|
||||
}
|
||||
.repo-grid-filelist-sidebar .repo-home-filelist {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
.repo-grid-filelist-sidebar .repo-home-sidebar-top {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
padding-left: 0;
|
||||
}
|
||||
.repo-grid-filelist-sidebar .repo-home-sidebar-bottom {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
padding-left: 0;
|
||||
}
|
||||
.repo-home-sidebar-bottom > :first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.language-stats {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
padding: 0;
|
||||
height: 10px;
|
||||
white-space: nowrap;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.language-stats-details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.language-stats-details .item {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.25em;
|
||||
padding: 0 0.5em; /* make the UI look better for narrow (mobile) view */
|
||||
text-decoration: none;
|
||||
}
|
@ -6,8 +6,17 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
|
||||
const {appSubUrl, assetUrlPrefix, pageData} = window.config;
|
||||
|
||||
type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning';
|
||||
|
||||
type CommitStatusMap = {
|
||||
[status in CommitStatus]: {
|
||||
name: string,
|
||||
color: string,
|
||||
};
|
||||
};
|
||||
|
||||
// make sure this matches templates/repo/commit_status.tmpl
|
||||
const commitStatus = {
|
||||
const commitStatus: CommitStatusMap = {
|
||||
pending: {name: 'octicon-dot-fill', color: 'yellow'},
|
||||
success: {name: 'octicon-check', color: 'green'},
|
||||
error: {name: 'gitea-exclamation', color: 'red'},
|
||||
@ -281,18 +290,18 @@ const sfc = {
|
||||
return 'octicon-repo';
|
||||
},
|
||||
|
||||
statusIcon(status) {
|
||||
statusIcon(status: CommitStatus) {
|
||||
return commitStatus[status].name;
|
||||
},
|
||||
|
||||
statusColor(status) {
|
||||
statusColor(status: CommitStatus) {
|
||||
return commitStatus[status].color;
|
||||
},
|
||||
|
||||
reposFilterKeyControl(e) {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
document.querySelector('.repo-owner-name-list li.active a')?.click();
|
||||
document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
if (this.activeIndex > 0) {
|
||||
|
@ -14,7 +14,7 @@ export default {
|
||||
issueLink: el.getAttribute('data-issuelink'),
|
||||
locale: {
|
||||
filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'),
|
||||
},
|
||||
} as Record<string, string>,
|
||||
commits: [],
|
||||
hoverActivated: false,
|
||||
lastReviewCommitSha: null,
|
||||
@ -41,16 +41,16 @@ export default {
|
||||
this.$el.removeEventListener('keyup', this.onKeyUp);
|
||||
},
|
||||
methods: {
|
||||
onBodyClick(event) {
|
||||
onBodyClick(event: MouseEvent) {
|
||||
// close this menu on click outside of this element when the dropdown is currently visible opened
|
||||
if (this.$el.contains(event.target)) return;
|
||||
if (this.menuVisible) {
|
||||
this.toggleMenu();
|
||||
}
|
||||
},
|
||||
onKeyDown(event) {
|
||||
onKeyDown(event: KeyboardEvent) {
|
||||
if (!this.menuVisible) return;
|
||||
const item = document.activeElement;
|
||||
const item = document.activeElement as HTMLElement;
|
||||
if (!this.$el.contains(item)) return;
|
||||
switch (event.key) {
|
||||
case 'ArrowDown': // select next element
|
||||
@ -73,7 +73,7 @@ export default {
|
||||
if (commitIdx) this.highlight(this.commits[commitIdx]);
|
||||
}
|
||||
},
|
||||
onKeyUp(event) {
|
||||
onKeyUp(event: KeyboardEvent) {
|
||||
if (!this.menuVisible) return;
|
||||
const item = document.activeElement;
|
||||
if (!this.$el.contains(item)) return;
|
||||
@ -95,7 +95,7 @@ export default {
|
||||
}
|
||||
},
|
||||
/** Focus given element */
|
||||
focusElem(elem, prevElem) {
|
||||
focusElem(elem: HTMLElement, prevElem: HTMLElement) {
|
||||
if (elem) {
|
||||
elem.tabIndex = 0;
|
||||
if (prevElem) prevElem.tabIndex = -1;
|
||||
@ -149,7 +149,7 @@ export default {
|
||||
window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1).id}${this.queryParams}`);
|
||||
},
|
||||
/** Clicking on a single commit opens this specific commit */
|
||||
commitClicked(commitId, newWindow = false) {
|
||||
commitClicked(commitId: string, newWindow = false) {
|
||||
const url = `${this.issueLink}/commits/${commitId}${this.queryParams}`;
|
||||
if (newWindow) {
|
||||
window.open(url);
|
||||
|
@ -2,10 +2,22 @@
|
||||
import {SvgIcon} from '../svg.ts';
|
||||
import ActionRunStatus from './ActionRunStatus.vue';
|
||||
import {createApp} from 'vue';
|
||||
import {toggleElem} from '../utils/dom.ts';
|
||||
import {createElementFromAttrs, toggleElem} from '../utils/dom.ts';
|
||||
import {formatDatetime} from '../utils/time.ts';
|
||||
import {renderAnsi} from '../render/ansi.ts';
|
||||
import {GET, POST, DELETE} from '../modules/fetch.ts';
|
||||
import {POST, DELETE} from '../modules/fetch.ts';
|
||||
|
||||
// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
|
||||
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
|
||||
|
||||
type LogLine = {
|
||||
index: number;
|
||||
timestamp: number;
|
||||
message: string;
|
||||
};
|
||||
|
||||
const LogLinePrefixGroup = '::group::';
|
||||
const LogLinePrefixEndGroup = '::endgroup::';
|
||||
|
||||
const sfc = {
|
||||
name: 'RepoActionView',
|
||||
@ -23,7 +35,7 @@ const sfc = {
|
||||
data() {
|
||||
return {
|
||||
// internal state
|
||||
loading: false,
|
||||
loadingAbortController: null,
|
||||
intervalID: null,
|
||||
currentJobStepsStates: [],
|
||||
artifacts: [],
|
||||
@ -39,6 +51,7 @@ const sfc = {
|
||||
run: {
|
||||
link: '',
|
||||
title: '',
|
||||
titleHTML: '',
|
||||
status: '',
|
||||
canCancel: false,
|
||||
canApprove: false,
|
||||
@ -89,9 +102,7 @@ const sfc = {
|
||||
// load job data and then auto-reload periodically
|
||||
// need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
|
||||
await this.loadJob();
|
||||
this.intervalID = setInterval(() => {
|
||||
this.loadJob();
|
||||
}, 1000);
|
||||
this.intervalID = setInterval(() => this.loadJob(), 1000);
|
||||
document.body.addEventListener('click', this.closeDropdown);
|
||||
this.hashChangeListener();
|
||||
window.addEventListener('hashchange', this.hashChangeListener);
|
||||
@ -113,38 +124,44 @@ const sfc = {
|
||||
|
||||
methods: {
|
||||
// get the active container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
|
||||
getLogsContainer(idx) {
|
||||
const el = this.$refs.logs[idx];
|
||||
getLogsContainer(stepIndex: number) {
|
||||
const el = this.$refs.logs[stepIndex];
|
||||
return el._stepLogsActiveContainer ?? el;
|
||||
},
|
||||
// begin a log group
|
||||
beginLogGroup(idx) {
|
||||
const el = this.$refs.logs[idx];
|
||||
|
||||
const elJobLogGroup = document.createElement('div');
|
||||
elJobLogGroup.classList.add('job-log-group');
|
||||
|
||||
const elJobLogGroupSummary = document.createElement('div');
|
||||
elJobLogGroupSummary.classList.add('job-log-group-summary');
|
||||
|
||||
const elJobLogList = document.createElement('div');
|
||||
elJobLogList.classList.add('job-log-list');
|
||||
|
||||
elJobLogGroup.append(elJobLogGroupSummary);
|
||||
elJobLogGroup.append(elJobLogList);
|
||||
beginLogGroup(stepIndex: number, startTime: number, line: LogLine) {
|
||||
const el = this.$refs.logs[stepIndex];
|
||||
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
|
||||
this.createLogLine(stepIndex, startTime, {
|
||||
index: line.index,
|
||||
timestamp: line.timestamp,
|
||||
message: line.message.substring(LogLinePrefixGroup.length),
|
||||
}),
|
||||
);
|
||||
const elJobLogList = createElementFromAttrs('div', {class: 'job-log-list'});
|
||||
const elJobLogGroup = createElementFromAttrs('details', {class: 'job-log-group'},
|
||||
elJobLogGroupSummary,
|
||||
elJobLogList,
|
||||
);
|
||||
el.append(elJobLogGroup);
|
||||
el._stepLogsActiveContainer = elJobLogList;
|
||||
},
|
||||
// end a log group
|
||||
endLogGroup(idx) {
|
||||
const el = this.$refs.logs[idx];
|
||||
endLogGroup(stepIndex: number, startTime: number, line: LogLine) {
|
||||
const el = this.$refs.logs[stepIndex];
|
||||
el._stepLogsActiveContainer = null;
|
||||
el.append(this.createLogLine(stepIndex, startTime, {
|
||||
index: line.index,
|
||||
timestamp: line.timestamp,
|
||||
message: line.message.substring(LogLinePrefixEndGroup.length),
|
||||
}));
|
||||
},
|
||||
|
||||
// show/hide the step logs for a step
|
||||
toggleStepLogs(idx) {
|
||||
toggleStepLogs(idx: number) {
|
||||
this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded;
|
||||
if (this.currentJobStepsStates[idx].expanded) {
|
||||
this.loadJob(); // try to load the data immediately instead of waiting for next timer interval
|
||||
this.loadJobForce(); // try to load the data immediately instead of waiting for next timer interval
|
||||
}
|
||||
},
|
||||
// cancel a run
|
||||
@ -156,62 +173,53 @@ const sfc = {
|
||||
POST(`${this.run.link}/approve`);
|
||||
},
|
||||
|
||||
createLogLine(line, startTime, stepIndex) {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('job-log-line');
|
||||
div.setAttribute('id', `jobstep-${stepIndex}-${line.index}`);
|
||||
div._jobLogTime = line.timestamp;
|
||||
createLogLine(stepIndex: number, startTime: number, line: LogLine) {
|
||||
const lineNum = createElementFromAttrs('a', {class: 'line-num muted', href: `#jobstep-${stepIndex}-${line.index}`},
|
||||
String(line.index),
|
||||
);
|
||||
|
||||
const lineNumber = document.createElement('a');
|
||||
lineNumber.classList.add('line-num', 'muted');
|
||||
lineNumber.textContent = line.index;
|
||||
lineNumber.setAttribute('href', `#jobstep-${stepIndex}-${line.index}`);
|
||||
div.append(lineNumber);
|
||||
const logTimeStamp = createElementFromAttrs('span', {class: 'log-time-stamp'},
|
||||
formatDatetime(new Date(line.timestamp * 1000)), // for "Show timestamps"
|
||||
);
|
||||
|
||||
const logMsg = createElementFromAttrs('span', {class: 'log-msg'});
|
||||
logMsg.innerHTML = renderAnsi(line.message);
|
||||
|
||||
const seconds = Math.floor(line.timestamp - startTime);
|
||||
const logTimeSeconds = createElementFromAttrs('span', {class: 'log-time-seconds'},
|
||||
`${seconds}s`, // for "Show seconds"
|
||||
);
|
||||
|
||||
// for "Show timestamps"
|
||||
const logTimeStamp = document.createElement('span');
|
||||
logTimeStamp.className = 'log-time-stamp';
|
||||
const date = new Date(parseFloat(line.timestamp * 1000));
|
||||
const timeStamp = formatDatetime(date);
|
||||
logTimeStamp.textContent = timeStamp;
|
||||
toggleElem(logTimeStamp, this.timeVisible['log-time-stamp']);
|
||||
// for "Show seconds"
|
||||
const logTimeSeconds = document.createElement('span');
|
||||
logTimeSeconds.className = 'log-time-seconds';
|
||||
const seconds = Math.floor(parseFloat(line.timestamp) - parseFloat(startTime));
|
||||
logTimeSeconds.textContent = `${seconds}s`;
|
||||
toggleElem(logTimeSeconds, this.timeVisible['log-time-seconds']);
|
||||
|
||||
const logMessage = document.createElement('span');
|
||||
logMessage.className = 'log-msg';
|
||||
logMessage.innerHTML = renderAnsi(line.message);
|
||||
div.append(logTimeStamp);
|
||||
div.append(logMessage);
|
||||
div.append(logTimeSeconds);
|
||||
|
||||
return div;
|
||||
return createElementFromAttrs('div', {id: `jobstep-${stepIndex}-${line.index}`, class: 'job-log-line'},
|
||||
lineNum, logTimeStamp, logMsg, logTimeSeconds,
|
||||
);
|
||||
},
|
||||
|
||||
appendLogs(stepIndex, logLines, startTime) {
|
||||
appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) {
|
||||
for (const line of logLines) {
|
||||
// TODO: group support: ##[group]GroupTitle , ##[endgroup]
|
||||
const el = this.getLogsContainer(stepIndex);
|
||||
el.append(this.createLogLine(line, startTime, stepIndex));
|
||||
if (line.message.startsWith(LogLinePrefixGroup)) {
|
||||
this.beginLogGroup(stepIndex, startTime, line);
|
||||
continue;
|
||||
} else if (line.message.startsWith(LogLinePrefixEndGroup)) {
|
||||
this.endLogGroup(stepIndex, startTime, line);
|
||||
continue;
|
||||
}
|
||||
el.append(this.createLogLine(stepIndex, startTime, line));
|
||||
}
|
||||
},
|
||||
|
||||
async fetchArtifacts() {
|
||||
const resp = await GET(`${this.actionsURL}/runs/${this.runIndex}/artifacts`);
|
||||
return await resp.json();
|
||||
},
|
||||
|
||||
async deleteArtifact(name) {
|
||||
async deleteArtifact(name: string) {
|
||||
if (!window.confirm(this.locale.confirmDeleteArtifact.replace('%s', name))) return;
|
||||
// TODO: should escape the "name"?
|
||||
await DELETE(`${this.run.link}/artifacts/${name}`);
|
||||
await this.loadJob();
|
||||
await this.loadJobForce();
|
||||
},
|
||||
|
||||
async fetchJob() {
|
||||
async fetchJobData(abortController: AbortController) {
|
||||
const logCursors = this.currentJobStepsStates.map((it, idx) => {
|
||||
// cursor is used to indicate the last position of the logs
|
||||
// it's only used by backend, frontend just reads it and passes it back, it and can be any type.
|
||||
@ -219,30 +227,27 @@ const sfc = {
|
||||
return {step: idx, cursor: it.cursor, expanded: it.expanded};
|
||||
});
|
||||
const resp = await POST(`${this.actionsURL}/runs/${this.runIndex}/jobs/${this.jobIndex}`, {
|
||||
signal: abortController.signal,
|
||||
data: {logCursors},
|
||||
});
|
||||
return await resp.json();
|
||||
},
|
||||
|
||||
async loadJobForce() {
|
||||
this.loadingAbortController?.abort();
|
||||
this.loadingAbortController = null;
|
||||
await this.loadJob();
|
||||
},
|
||||
|
||||
async loadJob() {
|
||||
if (this.loading) return;
|
||||
if (this.loadingAbortController) return;
|
||||
const abortController = new AbortController();
|
||||
this.loadingAbortController = abortController;
|
||||
try {
|
||||
this.loading = true;
|
||||
const job = await this.fetchJobData(abortController);
|
||||
if (this.loadingAbortController !== abortController) return;
|
||||
|
||||
let job, artifacts;
|
||||
try {
|
||||
[job, artifacts] = await Promise.all([
|
||||
this.fetchJob(),
|
||||
this.fetchArtifacts(), // refresh artifacts if upload-artifact step done
|
||||
]);
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) return; // avoid network error while unloading page
|
||||
throw err;
|
||||
}
|
||||
|
||||
this.artifacts = artifacts['artifacts'] || [];
|
||||
|
||||
// save the state to Vue data, then the UI will be updated
|
||||
this.artifacts = job.artifacts || [];
|
||||
this.run = job.state.run;
|
||||
this.currentJob = job.state.currentJob;
|
||||
|
||||
@ -254,26 +259,30 @@ const sfc = {
|
||||
}
|
||||
}
|
||||
// append logs to the UI
|
||||
for (const logs of job.logs.stepsLog) {
|
||||
for (const logs of job.logs.stepsLog ?? []) {
|
||||
// save the cursor, it will be passed to backend next time
|
||||
this.currentJobStepsStates[logs.step].cursor = logs.cursor;
|
||||
this.appendLogs(logs.step, logs.lines, logs.started);
|
||||
this.appendLogs(logs.step, logs.started, logs.lines);
|
||||
}
|
||||
|
||||
if (this.run.done && this.intervalID) {
|
||||
clearInterval(this.intervalID);
|
||||
this.intervalID = null;
|
||||
}
|
||||
} catch (e) {
|
||||
// avoid network error while unloading page, and ignore "abort" error
|
||||
if (e instanceof TypeError || abortController.signal.aborted) return;
|
||||
throw e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
if (this.loadingAbortController === abortController) this.loadingAbortController = null;
|
||||
}
|
||||
},
|
||||
|
||||
isDone(status) {
|
||||
isDone(status: RunStatus) {
|
||||
return ['success', 'skipped', 'failure', 'cancelled'].includes(status);
|
||||
},
|
||||
|
||||
isExpandable(status) {
|
||||
isExpandable(status: RunStatus) {
|
||||
return ['success', 'running', 'failure', 'cancelled'].includes(status);
|
||||
},
|
||||
|
||||
@ -281,7 +290,7 @@ const sfc = {
|
||||
if (this.menuVisible) this.menuVisible = false;
|
||||
},
|
||||
|
||||
toggleTimeDisplay(type) {
|
||||
toggleTimeDisplay(type: string) {
|
||||
this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`];
|
||||
for (const el of this.$refs.steps.querySelectorAll(`.log-time-${type}`)) {
|
||||
toggleElem(el, this.timeVisible[`log-time-${type}`]);
|
||||
@ -294,7 +303,7 @@ const sfc = {
|
||||
const outerEl = document.querySelector('.full.height');
|
||||
const actionBodyEl = document.querySelector('.action-view-body');
|
||||
const headerEl = document.querySelector('#navbar');
|
||||
const contentEl = document.querySelector('.page-content.repository');
|
||||
const contentEl = document.querySelector('.page-content');
|
||||
const footerEl = document.querySelector('.page-footer');
|
||||
toggleElem(headerEl, !this.isFullScreen);
|
||||
toggleElem(contentEl, !this.isFullScreen);
|
||||
@ -332,7 +341,7 @@ export function initRepositoryActionView() {
|
||||
|
||||
// TODO: the parent element's full height doesn't work well now,
|
||||
// but we can not pollute the global style at the moment, only fix the height problem for pages with this component
|
||||
const parentFullHeight = document.querySelector('body > div.full.height');
|
||||
const parentFullHeight = document.querySelector<HTMLElement>('body > div.full.height');
|
||||
if (parentFullHeight) parentFullHeight.style.paddingBottom = '0';
|
||||
|
||||
const view = createApp(sfc, {
|
||||
@ -375,9 +384,8 @@ export function initRepositoryActionView() {
|
||||
<div class="action-info-summary">
|
||||
<div class="action-info-summary-title">
|
||||
<ActionRunStatus :locale-status="locale.status[run.status]" :status="run.status" :size="20"/>
|
||||
<h2 class="action-info-summary-title-text">
|
||||
{{ run.title }}
|
||||
</h2>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<h2 class="action-info-summary-title-text" v-html="run.titleHTML"/>
|
||||
</div>
|
||||
<button class="ui basic small compact button primary" @click="approveRun()" v-if="run.canApprove">
|
||||
{{ locale.approve }}
|
||||
@ -858,7 +866,7 @@ export function initRepositoryActionView() {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.job-step-section .job-step-logs .job-log-line .log-msg {
|
||||
.job-step-logs .job-log-line .log-msg {
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
white-space: break-spaces;
|
||||
@ -884,15 +892,18 @@ export function initRepositoryActionView() {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* TODO: group support
|
||||
|
||||
.job-log-group {
|
||||
|
||||
.job-log-group .job-log-list .job-log-line .log-msg {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.job-log-group-summary {
|
||||
|
||||
position: relative;
|
||||
}
|
||||
.job-log-list {
|
||||
|
||||
} */
|
||||
.job-log-group-summary > .job-log-line {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1; /* to avoid hiding the triangle of the "details" element */
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
PointElement,
|
||||
LineElement,
|
||||
Filler,
|
||||
type ChartOptions,
|
||||
type ChartData,
|
||||
} from 'chart.js';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {Line as ChartLine} from 'vue-chartjs';
|
||||
@ -16,6 +18,7 @@ import {
|
||||
firstStartDateAfterDate,
|
||||
fillEmptyStartDaysWithZeroes,
|
||||
type DayData,
|
||||
type DayDataObject,
|
||||
} from '../utils/time.ts';
|
||||
import {chartJsColors} from '../utils/color.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
@ -64,12 +67,12 @@ async function fetchGraphData() {
|
||||
}
|
||||
} while (response.status === 202);
|
||||
if (response.ok) {
|
||||
data.value = await response.json();
|
||||
const weekValues = Object.values(data.value);
|
||||
const dayDataObject: DayDataObject = await response.json();
|
||||
const weekValues = Object.values(dayDataObject);
|
||||
const start = weekValues[0].week;
|
||||
const end = firstStartDateAfterDate(new Date());
|
||||
const startDays = startDaysBetween(start, end);
|
||||
data.value = fillEmptyStartDaysWithZeroes(startDays, data.value);
|
||||
data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObject);
|
||||
errorText.value = '';
|
||||
} else {
|
||||
errorText.value = response.statusText;
|
||||
@ -81,7 +84,7 @@ async function fetchGraphData() {
|
||||
}
|
||||
}
|
||||
|
||||
function toGraphData(data) {
|
||||
function toGraphData(data: Array<Record<string, any>>): ChartData<'line'> {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
@ -108,10 +111,9 @@ function toGraphData(data) {
|
||||
};
|
||||
}
|
||||
|
||||
const options = {
|
||||
const options: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
|
@ -9,6 +9,9 @@ import {
|
||||
PointElement,
|
||||
LineElement,
|
||||
Filler,
|
||||
type ChartOptions,
|
||||
type ChartData,
|
||||
type Plugin,
|
||||
} from 'chart.js';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import zoomPlugin from 'chartjs-plugin-zoom';
|
||||
@ -22,8 +25,9 @@ import {chartJsColors} from '../utils/color.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import type {Entries} from 'type-fest';
|
||||
|
||||
const customEventListener = {
|
||||
const customEventListener: Plugin = {
|
||||
id: 'customEventListener',
|
||||
afterEvent: (chart, args, opts) => {
|
||||
// event will be replayed from chart.update when reset zoom,
|
||||
@ -65,10 +69,10 @@ export default {
|
||||
data: () => ({
|
||||
isLoading: false,
|
||||
errorText: '',
|
||||
totalStats: {},
|
||||
sortedContributors: {},
|
||||
totalStats: {} as Record<string, any>,
|
||||
sortedContributors: {} as Record<string, any>,
|
||||
type: 'commits',
|
||||
contributorsStats: [],
|
||||
contributorsStats: {} as Record<string, any>,
|
||||
xAxisStart: null,
|
||||
xAxisEnd: null,
|
||||
xAxisMin: null,
|
||||
@ -99,7 +103,7 @@ export default {
|
||||
async fetchGraphData() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
let response;
|
||||
let response: Response;
|
||||
do {
|
||||
response = await GET(`${this.repoLink}/activity/contributors/data`);
|
||||
if (response.status === 202) {
|
||||
@ -112,7 +116,7 @@ export default {
|
||||
// below line might be deleted if we are sure go produces map always sorted by keys
|
||||
total.weeks = Object.fromEntries(Object.entries(total.weeks).sort());
|
||||
|
||||
const weekValues = Object.values(total.weeks);
|
||||
const weekValues = Object.values(total.weeks) as any;
|
||||
this.xAxisStart = weekValues[0].week;
|
||||
this.xAxisEnd = firstStartDateAfterDate(new Date());
|
||||
const startDays = startDaysBetween(this.xAxisStart, this.xAxisEnd);
|
||||
@ -120,7 +124,7 @@ export default {
|
||||
this.xAxisMin = this.xAxisStart;
|
||||
this.xAxisMax = this.xAxisEnd;
|
||||
this.contributorsStats = {};
|
||||
for (const [email, user] of Object.entries(rest)) {
|
||||
for (const [email, user] of Object.entries(rest) as Entries<Record<string, Record<string, any>>>) {
|
||||
user.weeks = fillEmptyStartDaysWithZeroes(startDays, user.weeks);
|
||||
this.contributorsStats[email] = user;
|
||||
}
|
||||
@ -146,7 +150,7 @@ export default {
|
||||
user.total_additions = 0;
|
||||
user.total_deletions = 0;
|
||||
user.max_contribution_type = 0;
|
||||
const filteredWeeks = user.weeks.filter((week) => {
|
||||
const filteredWeeks = user.weeks.filter((week: Record<string, number>) => {
|
||||
const oneWeek = 7 * 24 * 60 * 60 * 1000;
|
||||
if (week.week >= this.xAxisMin - oneWeek && week.week <= this.xAxisMax + oneWeek) {
|
||||
user.total_commits += week.commits;
|
||||
@ -195,7 +199,7 @@ export default {
|
||||
return (1 - (coefficient % 1)) * 10 ** exp + maxValue;
|
||||
},
|
||||
|
||||
toGraphData(data) {
|
||||
toGraphData(data: Array<Record<string, any>>): ChartData<'line'> {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
@ -211,9 +215,9 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
updateOtherCharts(event, reset) {
|
||||
const minVal = event.chart.options.scales.x.min;
|
||||
const maxVal = event.chart.options.scales.x.max;
|
||||
updateOtherCharts({chart}: {chart: Chart}, reset?: boolean = false) {
|
||||
const minVal = chart.options.scales.x.min;
|
||||
const maxVal = chart.options.scales.x.max;
|
||||
if (reset) {
|
||||
this.xAxisMin = this.xAxisStart;
|
||||
this.xAxisMax = this.xAxisEnd;
|
||||
@ -225,7 +229,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getOptions(type) {
|
||||
getOptions(type: string): ChartOptions<'line'> {
|
||||
return {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
@ -238,6 +242,7 @@ export default {
|
||||
position: 'top',
|
||||
align: 'center',
|
||||
},
|
||||
// @ts-expect-error: bug in chart.js types
|
||||
customEventListener: {
|
||||
chartType: type,
|
||||
instance: this,
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
LinearScale,
|
||||
TimeScale,
|
||||
type ChartOptions,
|
||||
type ChartData,
|
||||
} from 'chart.js';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {Bar} from 'vue-chartjs';
|
||||
@ -15,6 +16,7 @@ import {
|
||||
firstStartDateAfterDate,
|
||||
fillEmptyStartDaysWithZeroes,
|
||||
type DayData,
|
||||
type DayDataObject,
|
||||
} from '../utils/time.ts';
|
||||
import {chartJsColors} from '../utils/color.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
@ -61,11 +63,11 @@ async function fetchGraphData() {
|
||||
}
|
||||
} while (response.status === 202);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const start = Object.values(data)[0].week;
|
||||
const dayDataObj: DayDataObject = await response.json();
|
||||
const start = Object.values(dayDataObj)[0].week;
|
||||
const end = firstStartDateAfterDate(new Date());
|
||||
const startDays = startDaysBetween(start, end);
|
||||
data.value = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52);
|
||||
data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObj).slice(-52);
|
||||
errorText.value = '';
|
||||
} else {
|
||||
errorText.value = response.statusText;
|
||||
@ -77,10 +79,11 @@ async function fetchGraphData() {
|
||||
}
|
||||
}
|
||||
|
||||
function toGraphData(data) {
|
||||
function toGraphData(data: DayData[]): ChartData<'bar'> {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
// @ts-expect-error -- bar chart expects one-dimensional data, but apparently x/y still works
|
||||
data: data.map((i) => ({x: i.week, y: i.commits})),
|
||||
label: 'Commits',
|
||||
backgroundColor: chartJsColors['commits'],
|
||||
@ -91,10 +94,9 @@ function toGraphData(data) {
|
||||
};
|
||||
}
|
||||
|
||||
const options = {
|
||||
const options: ChartOptions<'bar'> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
|
@ -41,35 +41,28 @@ export async function initCitationFileCopyContent() {
|
||||
citationCopyApa.classList.toggle('primary', !isBibtex);
|
||||
};
|
||||
|
||||
document.querySelector('#cite-repo-button')?.addEventListener('click', async (e: MouseEvent & {target: HTMLAnchorElement}) => {
|
||||
const dropdownBtn = e.target.closest('.ui.dropdown.button');
|
||||
dropdownBtn.classList.add('is-loading');
|
||||
|
||||
document.querySelector('#cite-repo-button')?.addEventListener('click', async () => {
|
||||
try {
|
||||
try {
|
||||
await initInputCitationValue(citationCopyApa, citationCopyBibtex);
|
||||
} catch (e) {
|
||||
console.error(`initCitationFileCopyContent error: ${e}`, e);
|
||||
return;
|
||||
}
|
||||
updateUi();
|
||||
|
||||
citationCopyApa.addEventListener('click', () => {
|
||||
localStorage.setItem('citation-copy-format', 'apa');
|
||||
updateUi();
|
||||
});
|
||||
|
||||
citationCopyBibtex.addEventListener('click', () => {
|
||||
localStorage.setItem('citation-copy-format', 'bibtex');
|
||||
updateUi();
|
||||
});
|
||||
|
||||
inputContent.addEventListener('click', () => {
|
||||
inputContent.select();
|
||||
});
|
||||
} finally {
|
||||
dropdownBtn.classList.remove('is-loading');
|
||||
await initInputCitationValue(citationCopyApa, citationCopyBibtex);
|
||||
} catch (e) {
|
||||
console.error(`initCitationFileCopyContent error: ${e}`, e);
|
||||
return;
|
||||
}
|
||||
updateUi();
|
||||
|
||||
citationCopyApa.addEventListener('click', () => {
|
||||
localStorage.setItem('citation-copy-format', 'apa');
|
||||
updateUi();
|
||||
});
|
||||
|
||||
citationCopyBibtex.addEventListener('click', () => {
|
||||
localStorage.setItem('citation-copy-format', 'bibtex');
|
||||
updateUi();
|
||||
});
|
||||
|
||||
inputContent.addEventListener('click', () => {
|
||||
inputContent.select();
|
||||
});
|
||||
|
||||
fomanticQuery('#cite-repo-modal').modal('show');
|
||||
});
|
||||
|
@ -12,5 +12,5 @@ export function initCommonOrganization() {
|
||||
});
|
||||
|
||||
// Labels
|
||||
initCompLabelEdit('.organization.settings.labels');
|
||||
initCompLabelEdit('.page-content.organization.settings.labels');
|
||||
}
|
||||
|
@ -1,96 +1,81 @@
|
||||
import $ from 'jquery';
|
||||
import {toggleElem} from '../../utils/dom.ts';
|
||||
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
||||
|
||||
function isExclusiveScopeName(name) {
|
||||
function nameHasScope(name: string): boolean {
|
||||
return /.*[^/]\/[^/].*/.test(name);
|
||||
}
|
||||
|
||||
function updateExclusiveLabelEdit(form) {
|
||||
const nameInput = document.querySelector(`${form} .label-name-input`);
|
||||
const exclusiveField = document.querySelector(`${form} .label-exclusive-input-field`);
|
||||
const exclusiveCheckbox = document.querySelector(`${form} .label-exclusive-input`);
|
||||
const exclusiveWarning = document.querySelector(`${form} .label-exclusive-warning`);
|
||||
export function initCompLabelEdit(pageSelector: string) {
|
||||
const pageContent = document.querySelector<HTMLElement>(pageSelector);
|
||||
if (!pageContent) return;
|
||||
|
||||
if (isExclusiveScopeName(nameInput.value)) {
|
||||
exclusiveField?.classList.remove('muted');
|
||||
exclusiveField?.removeAttribute('aria-disabled');
|
||||
if (exclusiveCheckbox.checked && exclusiveCheckbox.getAttribute('data-exclusive-warn')) {
|
||||
exclusiveWarning?.classList.remove('tw-hidden');
|
||||
} else {
|
||||
exclusiveWarning?.classList.add('tw-hidden');
|
||||
}
|
||||
} else {
|
||||
exclusiveField?.classList.add('muted');
|
||||
exclusiveField?.setAttribute('aria-disabled', 'true');
|
||||
exclusiveWarning?.classList.add('tw-hidden');
|
||||
// for guest view, the modal is not available, the "labels" are read-only
|
||||
const elModal = pageContent.querySelector<HTMLElement>('#issue-label-edit-modal');
|
||||
if (!elModal) return;
|
||||
|
||||
const elLabelId = elModal.querySelector<HTMLInputElement>('input[name="id"]');
|
||||
const elNameInput = elModal.querySelector<HTMLInputElement>('.label-name-input');
|
||||
const elExclusiveField = elModal.querySelector('.label-exclusive-input-field');
|
||||
const elExclusiveInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-input');
|
||||
const elExclusiveWarning = elModal.querySelector('.label-exclusive-warning');
|
||||
const elIsArchivedField = elModal.querySelector('.label-is-archived-input-field');
|
||||
const elIsArchivedInput = elModal.querySelector<HTMLInputElement>('.label-is-archived-input');
|
||||
const elDescInput = elModal.querySelector<HTMLInputElement>('.label-desc-input');
|
||||
const elColorInput = elModal.querySelector<HTMLInputElement>('.js-color-picker-input input');
|
||||
|
||||
const syncModalUi = () => {
|
||||
const hasScope = nameHasScope(elNameInput.value);
|
||||
elExclusiveField.classList.toggle('disabled', !hasScope);
|
||||
const showExclusiveWarning = hasScope && elExclusiveInput.checked && elModal.hasAttribute('data-need-warn-exclusive');
|
||||
toggleElem(elExclusiveWarning, showExclusiveWarning);
|
||||
if (!hasScope) elExclusiveInput.checked = false;
|
||||
};
|
||||
|
||||
const showLabelEditModal = (btn:HTMLElement) => {
|
||||
// the "btn" should contain the label's attributes by its `data-label-xxx` attributes
|
||||
const form = elModal.querySelector<HTMLFormElement>('form');
|
||||
elLabelId.value = btn.getAttribute('data-label-id') || '';
|
||||
elNameInput.value = btn.getAttribute('data-label-name') || '';
|
||||
elIsArchivedInput.checked = btn.getAttribute('data-label-is-archived') === 'true';
|
||||
elExclusiveInput.checked = btn.getAttribute('data-label-exclusive') === 'true';
|
||||
elDescInput.value = btn.getAttribute('data-label-description') || '';
|
||||
elColorInput.value = btn.getAttribute('data-label-color') || '';
|
||||
elColorInput.dispatchEvent(new Event('input', {bubbles: true})); // trigger the color picker
|
||||
|
||||
// if label id exists: "edit label" mode; otherwise: "new label" mode
|
||||
const isEdit = Boolean(elLabelId.value);
|
||||
|
||||
// if a label was not exclusive but has issues, then it should warn user if it will become exclusive
|
||||
const numIssues = parseInt(btn.getAttribute('data-label-num-issues') || '0');
|
||||
elModal.toggleAttribute('data-need-warn-exclusive', !elExclusiveInput.checked && numIssues > 0);
|
||||
elModal.querySelector('.header').textContent = isEdit ? elModal.getAttribute('data-text-edit-label') : elModal.getAttribute('data-text-new-label');
|
||||
|
||||
const curPageLink = elModal.getAttribute('data-current-page-link');
|
||||
form.action = isEdit ? `${curPageLink}/edit` : `${curPageLink}/new`;
|
||||
toggleElem(elIsArchivedField, isEdit);
|
||||
syncModalUi();
|
||||
fomanticQuery(elModal).modal({
|
||||
onApprove() {
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return false;
|
||||
}
|
||||
form.submit();
|
||||
},
|
||||
}).modal('show');
|
||||
};
|
||||
|
||||
elModal.addEventListener('input', () => syncModalUi());
|
||||
|
||||
// theoretically, if the modal exists, the "new label" button should also exist, just in case it doesn't, use "?."
|
||||
const elNewLabel = pageContent.querySelector<HTMLElement>('.ui.button.new-label');
|
||||
elNewLabel?.addEventListener('click', () => showLabelEditModal(elNewLabel));
|
||||
|
||||
const elEditLabelButtons = pageContent.querySelectorAll<HTMLElement>('.edit-label-button');
|
||||
for (const btn of elEditLabelButtons) {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
showLabelEditModal(btn);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function initCompLabelEdit(selector) {
|
||||
if (!$(selector).length) return;
|
||||
|
||||
// Create label
|
||||
$('.new-label.button').on('click', () => {
|
||||
updateExclusiveLabelEdit('.new-label');
|
||||
$('.new-label.modal').modal({
|
||||
onApprove() {
|
||||
const form = document.querySelector('.new-label.form');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return false;
|
||||
}
|
||||
$('.new-label.form').trigger('submit');
|
||||
},
|
||||
}).modal('show');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Edit label
|
||||
$('.edit-label-button').on('click', function () {
|
||||
$('#label-modal-id').val($(this).data('id'));
|
||||
|
||||
const $nameInput = $('.edit-label .label-name-input');
|
||||
$nameInput.val($(this).data('title'));
|
||||
|
||||
const $isArchivedCheckbox = $('.edit-label .label-is-archived-input');
|
||||
$isArchivedCheckbox[0].checked = this.hasAttribute('data-is-archived');
|
||||
|
||||
const $exclusiveCheckbox = $('.edit-label .label-exclusive-input');
|
||||
$exclusiveCheckbox[0].checked = this.hasAttribute('data-exclusive');
|
||||
// Warn when label was previously not exclusive and used in issues
|
||||
$exclusiveCheckbox.data('exclusive-warn',
|
||||
$(this).data('num-issues') > 0 &&
|
||||
(!this.hasAttribute('data-exclusive') || !isExclusiveScopeName($nameInput.val())));
|
||||
updateExclusiveLabelEdit('.edit-label');
|
||||
|
||||
$('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
|
||||
|
||||
const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
|
||||
colorInput.value = this.getAttribute('data-color');
|
||||
colorInput.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
|
||||
$('.edit-label.modal').modal({
|
||||
onApprove() {
|
||||
const form = document.querySelector('.edit-label.form');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return false;
|
||||
}
|
||||
$('.edit-label.form').trigger('submit');
|
||||
},
|
||||
}).modal('show');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.new-label .label-name-input').on('input', () => {
|
||||
updateExclusiveLabelEdit('.new-label');
|
||||
});
|
||||
$('.new-label .label-exclusive-input').on('change', () => {
|
||||
updateExclusiveLabelEdit('.new-label');
|
||||
});
|
||||
$('.edit-label .label-name-input').on('input', () => {
|
||||
updateExclusiveLabelEdit('.edit-label');
|
||||
});
|
||||
$('.edit-label .label-exclusive-input').on('change', () => {
|
||||
updateExclusiveLabelEdit('.edit-label');
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
export function initRepoTopicBar() {
|
||||
const mgrBtn = document.querySelector('#manage_topic');
|
||||
const mgrBtn = document.querySelector<HTMLButtonElement>('#manage_topic');
|
||||
if (!mgrBtn) return;
|
||||
|
||||
const editDiv = document.querySelector('#topic_edit');
|
||||
@ -18,7 +18,7 @@ export function initRepoTopicBar() {
|
||||
mgrBtn.addEventListener('click', () => {
|
||||
hideElem(viewDiv);
|
||||
showElem(editDiv);
|
||||
topicDropdown.querySelector('input.search').focus();
|
||||
topicDropdown.querySelector<HTMLInputElement>('input.search').focus();
|
||||
});
|
||||
|
||||
document.querySelector('#cancel_topic_edit').addEventListener('click', () => {
|
||||
@ -28,9 +28,9 @@ export function initRepoTopicBar() {
|
||||
mgrBtn.focus();
|
||||
});
|
||||
|
||||
document.querySelector('#save_topic').addEventListener('click', async (e) => {
|
||||
document.querySelector('#save_topic').addEventListener('click', async (e: MouseEvent & {target: HTMLButtonElement}) => {
|
||||
lastErrorToast?.hideToast();
|
||||
const topics = editDiv.querySelector('input[name=topics]').value;
|
||||
const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value;
|
||||
|
||||
const data = new FormData();
|
||||
data.append('topics', topics);
|
||||
@ -45,12 +45,13 @@ export function initRepoTopicBar() {
|
||||
const topicArray = topics.split(',');
|
||||
topicArray.sort();
|
||||
for (const topic of topicArray) {
|
||||
// it should match the code in repo/home.tmpl
|
||||
// TODO: sort items in topicDropdown, or items in edit div will have different order to the items in view div
|
||||
// !!!! it SHOULD and MUST match the code in "home_sidebar_top.tmpl" !!!!
|
||||
const link = document.createElement('a');
|
||||
link.classList.add('repo-topic', 'ui', 'large', 'label');
|
||||
link.classList.add('repo-topic', 'ui', 'large', 'label', 'gt-ellipsis');
|
||||
link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`;
|
||||
link.textContent = topic;
|
||||
mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button
|
||||
viewDiv.append(link);
|
||||
}
|
||||
}
|
||||
hideElem(editDiv);
|
||||
|
@ -43,7 +43,7 @@ export function initRepository() {
|
||||
initRepoCommentFormAndSidebar();
|
||||
|
||||
// Labels
|
||||
initCompLabelEdit('.repository.labels');
|
||||
initCompLabelEdit('.page-content.repository.labels');
|
||||
initRepoMilestone();
|
||||
initRepoNew();
|
||||
|
||||
|
@ -49,7 +49,11 @@ export type DayData = {
|
||||
commits: number,
|
||||
}
|
||||
|
||||
export function fillEmptyStartDaysWithZeroes(startDays: number[], data: DayData[]): DayData[] {
|
||||
export type DayDataObject = {
|
||||
[timestamp: string]: DayData,
|
||||
}
|
||||
|
||||
export function fillEmptyStartDaysWithZeroes(startDays: number[], data: DayDataObject): DayData[] {
|
||||
const result = {};
|
||||
|
||||
for (const startDay of startDays) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user