Compare commits

..

56 Commits

Author SHA1 Message Date
6543
55e159ca5f Changelog v1.14.0 (#15360)
* clean & merge & update v1.14.0 changelog

* backport v1.13.x changelogs
2021-04-11 06:07:02 +02:00
Lunny Xiao
87074ec860 Fix delete nonexist oauth application 500 and prevent deadlock (#15384) (#15396)
* Fix delete nonexist oauth application 500

* Fix test

* Close the session

* Fix more missed sess.Close

* Remove unnecessary blank line

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
2021-04-11 04:57:44 +02:00
zeripath
1fe5fe419e Always set the merge base used to merge the commit (#15352) (#15385)
Backport #15352

The issue is that the TestPatch will reset the PR MergeBase - and it is possible for TestPatch to update the MergeBase whilst a merge is ongoing. The ensuing merge will then complete but it doesn't re-set the MergeBase it used to merge the PR.

Fixes the intermittent error in git test.

Signed-off-by: Andrew Thornton art27@cantab.net
2021-04-10 14:08:30 +02:00
zeripath
67a12b8fac Turn RepoRef and RepoAssignment back into func(*Context) (#15372) (#15377)
Backport #15372

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-04-09 22:24:40 -04:00
silverwind
e861dcbbaf Dropzone styling improvements (#15291) (#15374)
* Dropzone styling improvements

- Move all dropzone styles to separate file
- Fix white background in arc-green
- Fix rendering of non-square images and previews

* increase thumbnail quality, set contain in js, replace blur effect with opacity

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-04-09 19:43:36 -04:00
zeripath
53c2136a9a Upgrade to bluemonday 1.0.7 (#15379) (#15380)
* Upgrade to bluemonday 1.0.7 (#15379)

Backport #15379

Fix #15349

Signed-off-by: Andrew Thornton <art27@cantab.net>

* resolve CI

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-04-09 19:41:30 -04:00
zeripath
24ebc7e517 Move FCGI req.URL.Path fix-up to the FCGI listener (#15292) (#15361)
Backport #15292

Simplify the web.go FCGI path by moving the req.URL.Path fix-up to listener

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-09 17:45:02 +01:00
6543
b072907987 Fix admin user list (#15358) (#15359)
* Fix `admin user list` (#15358)

* fix routers/api/v1/repo/issue.go
2021-04-09 12:39:40 +02:00
silverwind
942b0360ad Fix button border issue (#15351) 2021-04-09 05:38:06 +02:00
silverwind
1ec4913add Disable cssnano's colormin plugin (#15348)
It produces odd rgba values which also seem to cause issues in monaco's
color parser where the scoll shadow went red for some reason.

Regression by: https://github.com/go-gitea/gitea/pull/15333
2021-04-09 03:54:24 +02:00
zeripath
16e34025b4 Show diff on rename with diff changes (#15338) (#15339)
Backport #15338

More recent versions of git have increased support for detection of renames meaning
that a rename with diff changes is now supported.

Although ParsePatch supports this - our templates do not and the simplest solution
is simply to show the diff.

Fix #15335

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: 6543 <6543@obermui.de>
2021-04-08 15:36:17 -04:00
zeripath
456d63b6cf Prepend AppSubUrl to links for default avatar (#15341) (#15342)
Backport #15341

Fix #15334

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-08 17:51:10 +01:00
zeripath
798ac3f85a Fix handling of logout event (#15323) (#15337)
Backport #15323

It appears that there is a slight bug in the handling of the data of logout event -
the javascript should be testing the data field of the data field for the logout
instruction.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-08 17:28:30 +02:00
silverwind
460093b952 Monaco improvements (#15333) (#15345)
- Create theme at runtime which follows the CSS variables of the site
- Disable a few opinionated Monaco defaults like minimap and word highlights
- Move styles to separate file
2021-04-08 13:24:23 +02:00
6543
38d184d518 Fix CanCreateRepo check (#15311) (#15321)
Signed-off-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2021-04-07 22:14:11 +02:00
6543
80b55263d8 Fix xorm log stack level (#15285) (#15316)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-04-07 08:36:15 +01:00
6543
32232db55f Reduce memory usage in testgit (#15306) (#15310)
* reduce memory use in rawtest

* just use hashsum for diffs

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-04-07 11:07:39 +08:00
KN4CK3R
cf9b6c281f Close file on invalid range (Addition to #15166) (#15268) (#15308)
* Close file on invalid range.

* Close on seek error

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Moved 'Seek' into server.

* io.ReadSeekCloser is only available in Go 1.16

Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-04-06 15:25:31 -04:00
6543
a8c6a4a70e Fix bug in Wrap (#15302) (#15309)
Whilst doing other work I have noticed that there is an issue with Wrap when passing an
http.Handler - the next should be the next handler in line not empty.

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
2021-04-06 18:44:24 +02:00
6543
e6050e80f7 Update to bluemonday-1.0.6 (#15294) (#15297)
Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: zeripath <art27@cantab.net>
2021-04-06 01:36:58 +01:00
zeripath
3803b15d76 Drop the event source if we are unauthorized (#15275) (#15280)
Backport #15275

A previous commit that sent unauthorized if the user is unauthorized
simply leads to the repeated reopening of the eventsource. #

This PR changes the event returned to tell the client to close the
eventsource and thus prevents the repeated reopening.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-04 20:39:22 -04:00
zeripath
af73e1ee35 Add size to Save function (#15264) (#15270)
This PR proposes an alternative solution to #15255 - just add the size to the
save function. Yes it is less apparently clean but it may be more correct.

Close #15255
Fix #15253

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-04-04 12:04:55 -04:00
silverwind
4bc8dfc6a3 Branch page and misc css improvements (#15208) (#15274)
- Improve branches page, increase icon size, use octicons, use css vars
- Style placeholder color via css var
- Slightly increase contrast of input fields and active/hover states
- Add styling for select boxes in arc-green
2021-04-04 16:31:54 +03:00
techknowlogick
33c4e246fe update golang libraries (#15258) (#15259) 2021-04-03 10:42:18 +02:00
KN4CK3R
8ec7beb9f4 Fix graph pagination (#15225) (#15249)
* Fixed invalid HTML tag.

* Fixed pagination.

* Update templates/repo/graph/commits.tmpl

Co-authored-by: zeripath <art27@cantab.net>
2021-04-02 04:29:14 +01:00
a1012112796
c6eb9b30ae response 404 for diff/patch of a commit that not exist (#15221) (#15237)
* response 404 for diff/patch of a commit that not exist

fix #15217

Signed-off-by: a1012112796 <1012112796@qq.com>

* Update routers/repo/commit.go

Co-authored-by: silverwind <me@silverwind.io>

* use ctx.NotFound()

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: silverwind <me@silverwind.io>

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: 6543 <6543@obermui.de>
2021-04-01 19:57:05 -04:00
zeripath
f75a9b27b0 Speed up enry.IsVendor (#15213) (#15245)
Backport #15213

`enry.IsVendor` is kinda slow as it simply iterates across all regexps.
This PR ajdusts the regexps to combine them to make this process a
little quicker.

Related #15143

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-02 01:16:00 +02:00
zeripath
2705696d4d Prevent NPE in CommentMustAsDiff if no hunk header (#15199) (#15200)
Backport #15199

I do not understand how this can happen or why.

There is an apparent possibility for a comment.Patch to be missing a hunk header
- this should not happen and do not understand how. But it appears to happen on
1.13 at least in some case.

This PR will simply add a new section if the cursection is empty
thus preventing the NPE.

Fix #15198

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: 6543 <6543@obermui.de>
2021-04-01 15:14:56 -04:00
mayswind
2b68f66e0e Fix timezone bug when clicking heatmap (#15141) (#15231) 2021-04-01 18:22:54 +08:00
silverwind
5c7d30cf52 Diff box fixes (#15214) (#15227)
- Fix misaligned "Show Outdated" buttons via flexbox
- Add hover effect to "Show Outdated" buttons
- Remove overreaching margin from selector .diff-file-box and handle
  cases individually.

Fixes: https://github.com/go-gitea/gitea/issues/15097

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-04-01 08:04:47 +03:00
zeripath
e520dff4da Improve /api/v1/repos/issues/search by just getting repo ids (#15179) (#15192)
Backport #15179

/api/v1/repos/issues/search is a highly inefficient search which is unfortunately
the basis for our dependency searching algorithm. In particular it currently loads
all of the repositories and their owners and their primary coding language all of
which is immediately thrown away.

This PR makes one simple change - just get the IDs.

Related #14560
Related #12827

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-01 01:15:08 +02:00
zeripath
2bc759518e Fix regression from #14623 - use debug SVC handler only on interactive sessions (#15210) (#15211)
Backport #15210

Unfortunately #14623 changed from the deprecated IsInteractiveSession to
IsWindowsService without recognising that they are the complement of
each other.

This means that Windows SVC control is not working correctly. This PR
adds some Tracing statements but also fixes the bug.

Fix #15159

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-31 20:49:46 +01:00
a1012112796
92b2883058 add 'fonts' into 'KnownPublicEntries' (#15188) (#15218)
fix #15184

Signed-off-by: a1012112796 <1012112796@qq.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-03-31 04:56:19 +02:00
silverwind
0ebfc1405c Fix webhook delivery and issue checklist for arc-green (#15195) (#15204)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-03-30 19:15:12 +08:00
silverwind
fd5c67226e Fix margin between avatars on org pages (#15194) (#15197)
Fixes: https://github.com/go-gitea/gitea/issues/15191
2021-03-29 23:36:00 +02:00
a1012112796
61308825a6 should run RetrieveRepoMetas() for empty pr (#15187) (#15190)
Signed-off-by: a1012112796 <1012112796@qq.com>
2021-03-29 17:02:01 +01:00
Norwin
0cccad04f0 fix org navbar (#15174)
Co-authored-by: Jimmy Praet <jimmy.praet@telenet.be>
2021-03-27 15:57:02 +01:00
zeripath
a0e5c49ac3 Clusterfuzz found another way (#15160) (#15168)
Backport #15160

Clusterfuzz found another way so I found another way to stop it

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-26 22:48:38 -04:00
sotho
3558310c1f Fix wrong user returned in API (#15139) (#15151)
The API call: GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments
returns always the reviewer, but should return the poster.

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
2021-03-26 04:20:52 +01:00
zeripath
e99534cfd2 Fix Migration 176 yet again (#15132)
Backport #15131

Whilst creating a test for v176 in the migrations_test PR
it has become clear that this was still wrong.

This is now fixed. Genuinely.

Also fix repo transfer

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-23 23:18:05 +00:00
6543
27acf6165e update changelog for rc2 release (#15130) 2021-03-23 15:52:43 -04:00
zeripath
f286a28568 Fix consistency check (#15120) (#15128)
In my last fix I missed adding the label_ prefix to the
consistency check count.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-23 20:20:34 +01:00
6543
b5c4cb1bde Fix bug on avatar middleware (#15124) (#15126)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-03-23 18:44:37 +00:00
6543
26b98417ad [Vendor] update gitea-sdk v0.14.0 (#15103) (#15107)
* upgraded code.gitea.io/sdk/gitea v0.13.2 => v0.14.0

* rm workaround
2021-03-23 10:10:32 +00:00
zeripath
8b0cf88c0c Changelog for v1.14-rc2 (#15115)
Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-22 22:00:51 +01:00
zeripath
23db3375df Fix another clusterfuzz identified issue (#15096) (#15113)
Backport #15096

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: 6543 <6543@obermui.de>
2021-03-22 15:16:08 -04:00
zeripath
14011d77c9 Fix the v176 migration (#15110) (#15111)
Backport #15110

There is a serious issue with the v176 migration where there is a mistaken missing
label_id selection.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-22 14:47:58 -04:00
silverwind
5519e26c2f Fix lock modal content rendering outside modal (#15095) (#15100)
* Fix lock modal content rendering outside modal

The .content was not a child to .modal so was rendering outside. This is
a recent regression but I'm not certain when it was introduced.

* remove extraneous closing div

Co-authored-by: zeripath <art27@cantab.net>
2021-03-22 02:00:42 +01:00
zeripath
6feb435867 Place wrapper around comment as diff to catch panics (#15085) (#15094)
Backport #15085

There are a few recurrent issues with comment as diff reporting panics that are resistant to fixing due to the fact that the panic occurs in the template render and is swallowed by the template renderer.

This PR just adds some logging to force the panic to properly logged and re-propagates back up to the template renderer so we can actually detect what the issue is.

Signed-off-by: Andrew Thornton art27@cantab.net
2021-03-21 23:41:40 +01:00
silverwind
61444ed8ca Fix markdown rendering in milestone content (#15056) (#15091)
- Add missing markdown class for rendered markdown.
- Increase font size of milestone name in list.

Fixes: https://github.com/go-gitea/gitea/issues/15046
2021-03-21 18:57:06 +01:00
6543
d770cc9886 Remove possible resource leak (#15067) (#15082)
* move "copy uploaded lfs files 2 repo" to own function for "defer file.Close()"

* rm type overload

Co-authored-by: zeripath <art27@cantab.net>
2021-03-21 17:07:37 +01:00
a1012112796
fbaa01998a fix double 'push tag' action feed (#15078) (#15083)
Signed-off-by: a1012112796 <1012112796@qq.com>
2021-03-21 14:51:31 +00:00
Lauris BH
ac2ae66ae7 Handle unauthorized user events gracefully (#15071) (#15074) 2021-03-21 10:21:28 +00:00
Lauris BH
ed60fe0986 Update release date (#15065)
* Update release date

* Remove unneeded entry
2021-03-20 21:29:01 +08:00
zeripath
29e0d62790 Update to goldmark 1.3.3 (#15059) (#15060)
Backport #15059

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-20 12:24:09 +01:00
6543
7b464fa67b Fix bug when upload on web (#15042) (#15054)
* Fix bug when upload on web

* move into own function

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
2021-03-20 09:37:57 +08:00
9970 changed files with 2695812 additions and 248727 deletions

9
.air.conf Normal file
View File

@@ -0,0 +1,9 @@
root = "."
tmp_dir = ".air"
[build]
cmd = "make backend"
bin = "gitea"
include_ext = ["go", "tmpl"]
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]

View File

@@ -1,10 +0,0 @@
root = "."
tmp_dir = ".air"
[build]
cmd = "make backend"
bin = "gitea"
include_ext = ["go", "tmpl"]
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]
exclude_regex = ["_test.go$"]

View File

@@ -1,6 +1,3 @@
# config for changelog tool
# source: https://gitea.com/gitea/changelog
# The full repository name # The full repository name
repo: go-gitea/gitea repo: go-gitea/gitea
@@ -17,32 +14,28 @@ groups:
name: BREAKING name: BREAKING
labels: labels:
- kind/breaking - kind/breaking
-
name: SECURITY
labels:
- kind/security
-
name: FEDERATION
labels:
- theme/federation
- -
name: FEATURES name: FEATURES
labels: labels:
- kind/feature - kind/feature
-
name: SECURITY
labels:
- kind/security
- -
name: API name: API
labels: labels:
- kind/api - kind/api
-
name: BUGFIXES
labels:
- kind/bug
- -
name: ENHANCEMENTS name: ENHANCEMENTS
labels: labels:
- kind/enhancement - kind/enhancement
- kind/refactor - kind/refactor
- kind/ui - kind/ui
-
name: BUGFIXES
labels:
- kind/bug
- -
name: TESTING name: TESTING
labels: labels:

File diff suppressed because it is too large Load Diff

View File

@@ -12,15 +12,6 @@ insert_final_newline = true
[*.{go,tmpl,html}] [*.{go,tmpl,html}]
indent_style = tab indent_style = tab
[templates/custom/*.tmpl]
insert_final_newline = false
[templates/swagger/v1_json.tmpl]
indent_style = space
[templates/user/auth/oidc_wellknown.tmpl]
indent_style = space
[Makefile] [Makefile]
indent_style = tab indent_style = tab

120
.eslintrc
View File

@@ -3,27 +3,33 @@ reportUnusedDisableDirectives: true
ignorePatterns: ignorePatterns:
- /web_src/js/vendor - /web_src/js/vendor
- /templates/base/head.tmpl
- /templates/repo/activity.tmpl
- /templates/repo/view_file.tmpl
parserOptions: parserOptions:
sourceType: module sourceType: module
ecmaVersion: latest ecmaVersion: 2021
plugins: plugins:
- eslint-plugin-unicorn - eslint-plugin-unicorn
- eslint-plugin-import - eslint-plugin-import
- eslint-plugin-vue - eslint-plugin-vue
- eslint-plugin-html - eslint-plugin-html
- eslint-plugin-jquery
extends: extends:
- plugin:vue/recommended - plugin:vue/recommended
env: env:
es2022: true es2021: true
node: true node: true
globals: globals:
__webpack_public_path__: true __webpack_public_path__: true
CodeMirror: false
Dropzone: false
SimpleMDE: false
u2fApi: false
settings: settings:
html/html-extensions: [".tmpl"] html/html-extensions: [".tmpl"]
@@ -32,6 +38,7 @@ overrides:
- files: ["web_src/**/*.js", "web_src/**/*.vue", "templates/**/*.tmpl"] - files: ["web_src/**/*.js", "web_src/**/*.vue", "templates/**/*.tmpl"]
env: env:
browser: true browser: true
jquery: true
node: false node: false
- files: ["templates/**/*.tmpl"] - files: ["templates/**/*.tmpl"]
rules: rules:
@@ -46,12 +53,6 @@ overrides:
rules: rules:
import/no-unresolved: [0] import/no-unresolved: [0]
import/no-extraneous-dependencies: [0] import/no-extraneous-dependencies: [0]
- files: ["*.test.js"]
env:
jest: true
- files: ["*.config.js"]
rules:
import/no-unused-modules: [0]
rules: rules:
accessor-pairs: [2] accessor-pairs: [2]
@@ -113,12 +114,11 @@ rules:
import/no-amd: [0] import/no-amd: [0]
import/no-anonymous-default-export: [0] import/no-anonymous-default-export: [0]
import/no-commonjs: [0] import/no-commonjs: [0]
import/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] import/no-cycle: [2, {ignoreExternal: true}]
import/no-default-export: [0] import/no-default-export: [0]
import/no-deprecated: [0] import/no-deprecated: [0]
import/no-dynamic-require: [0] import/no-dynamic-require: [0]
import/no-extraneous-dependencies: [2] import/no-extraneous-dependencies: [2]
import/no-import-module-exports: [0]
import/no-internal-modules: [0] import/no-internal-modules: [0]
import/no-mutable-exports: [2] import/no-mutable-exports: [2]
import/no-named-as-default-member: [0] import/no-named-as-default-member: [0]
@@ -127,7 +127,6 @@ rules:
import/no-named-export: [0] import/no-named-export: [0]
import/no-namespace: [0] import/no-namespace: [0]
import/no-nodejs-modules: [0] import/no-nodejs-modules: [0]
import/no-relative-packages: [0]
import/no-relative-parent-imports: [0] import/no-relative-parent-imports: [0]
import/no-restricted-paths: [0] import/no-restricted-paths: [0]
import/no-self-import: [2] import/no-self-import: [2]
@@ -141,55 +140,6 @@ rules:
import/unambiguous: [0] import/unambiguous: [0]
indent: [2, 2, {SwitchCase: 1}] indent: [2, 2, {SwitchCase: 1}]
init-declarations: [0] init-declarations: [0]
jquery/no-ajax-events: [2]
jquery/no-ajax: [0]
jquery/no-animate: [2]
jquery/no-attr: [0]
jquery/no-bind: [2]
jquery/no-class: [0]
jquery/no-clone: [2]
jquery/no-closest: [0]
jquery/no-css: [0]
jquery/no-data: [0]
jquery/no-deferred: [2]
jquery/no-delegate: [2]
jquery/no-each: [0]
jquery/no-extend: [2]
jquery/no-fade: [0]
jquery/no-filter: [0]
jquery/no-find: [0]
jquery/no-global-eval: [2]
jquery/no-grep: [2]
jquery/no-has: [2]
jquery/no-hide: [0]
jquery/no-html: [0]
jquery/no-in-array: [2]
jquery/no-is-array: [2]
jquery/no-is-function: [2]
jquery/no-is: [0]
jquery/no-load: [2]
jquery/no-map: [0]
jquery/no-merge: [2]
jquery/no-param: [2]
jquery/no-parent: [0]
jquery/no-parents: [0]
jquery/no-parse-html: [2]
jquery/no-prop: [0]
jquery/no-proxy: [2]
jquery/no-ready: [0]
jquery/no-serialize: [2]
jquery/no-show: [0]
jquery/no-size: [2]
jquery/no-sizzle: [0]
jquery/no-slide: [0]
jquery/no-submit: [0]
jquery/no-text: [0]
jquery/no-toggle: [0]
jquery/no-trigger: [0]
jquery/no-trim: [2]
jquery/no-val: [0]
jquery/no-when: [2]
jquery/no-wrap: [2]
key-spacing: [2] key-spacing: [2]
keyword-spacing: [2] keyword-spacing: [2]
line-comment-position: [0] line-comment-position: [0]
@@ -224,7 +174,6 @@ rules:
no-confusing-arrow: [0] no-confusing-arrow: [0]
no-console: [1, {allow: [info, warn, error]}] no-console: [1, {allow: [info, warn, error]}]
no-const-assign: [2] no-const-assign: [2]
no-constant-binary-expression: [2]
no-constant-condition: [0] no-constant-condition: [0]
no-constructor-return: [2] no-constructor-return: [2]
no-continue: [0] no-continue: [0]
@@ -330,8 +279,7 @@ rules:
no-unsafe-negation: [2] no-unsafe-negation: [2]
no-unused-expressions: [2] no-unused-expressions: [2]
no-unused-labels: [2] no-unused-labels: [2]
no-unused-private-class-members: [2] no-unused-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, ignoreRestSiblings: false}]
no-unused-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_, ignoreRestSiblings: false}]
no-use-before-define: [2, nofunc] no-use-before-define: [2, nofunc]
no-useless-backreference: [0] no-useless-backreference: [0]
no-useless-call: [2] no-useless-call: [2]
@@ -363,7 +311,6 @@ rules:
prefer-exponentiation-operator: [2] prefer-exponentiation-operator: [2]
prefer-named-capture-group: [0] prefer-named-capture-group: [0]
prefer-numeric-literals: [2] prefer-numeric-literals: [2]
prefer-object-has-own: [0]
prefer-object-spread: [0] prefer-object-spread: [0]
prefer-promise-reject-errors: [2, {allowEmptyReject: false}] prefer-promise-reject-errors: [2, {allowEmptyReject: false}]
prefer-regex-literals: [2] prefer-regex-literals: [2]
@@ -397,7 +344,6 @@ rules:
unicode-bom: [2, never] unicode-bom: [2, never]
unicorn/better-regex: [0] unicorn/better-regex: [0]
unicorn/catch-error-name: [0] unicorn/catch-error-name: [0]
unicorn/consistent-destructuring: [2]
unicorn/consistent-function-scoping: [2] unicorn/consistent-function-scoping: [2]
unicorn/custom-error-definition: [0] unicorn/custom-error-definition: [0]
unicorn/empty-brace-spaces: [2] unicorn/empty-brace-spaces: [2]
@@ -410,94 +356,52 @@ rules:
unicorn/import-style: [0] unicorn/import-style: [0]
unicorn/new-for-builtins: [2] unicorn/new-for-builtins: [2]
unicorn/no-abusive-eslint-disable: [0] unicorn/no-abusive-eslint-disable: [0]
unicorn/no-array-for-each: [2]
unicorn/no-array-instanceof: [0] unicorn/no-array-instanceof: [0]
unicorn/no-array-method-this-argument: [2]
unicorn/no-array-push-push: [2]
unicorn/no-await-expression-member: [0]
unicorn/no-console-spaces: [0] unicorn/no-console-spaces: [0]
unicorn/no-document-cookie: [2]
unicorn/no-empty-file: [2]
unicorn/no-fn-reference-in-iterator: [0] unicorn/no-fn-reference-in-iterator: [0]
unicorn/no-for-loop: [0] unicorn/no-for-loop: [0]
unicorn/no-hex-escape: [0] unicorn/no-hex-escape: [0]
unicorn/no-invalid-remove-event-listener: [2]
unicorn/no-keyword-prefix: [0] unicorn/no-keyword-prefix: [0]
unicorn/no-lonely-if: [2] unicorn/no-lonely-if: [2]
unicorn/no-nested-ternary: [0] unicorn/no-nested-ternary: [0]
unicorn/no-new-array: [0]
unicorn/no-new-buffer: [0] unicorn/no-new-buffer: [0]
unicorn/no-null: [0] unicorn/no-null: [0]
unicorn/no-object-as-default-parameter: [2] unicorn/no-object-as-default-parameter: [2]
unicorn/no-process-exit: [0] unicorn/no-process-exit: [0]
unicorn/no-reduce: [2] unicorn/no-reduce: [2]
unicorn/no-static-only-class: [2]
unicorn/no-thenable: [2]
unicorn/no-this-assignment: [2]
unicorn/no-unreadable-array-destructuring: [0] unicorn/no-unreadable-array-destructuring: [0]
unicorn/no-unreadable-iife: [2]
unicorn/no-unsafe-regex: [0] unicorn/no-unsafe-regex: [0]
unicorn/no-unused-properties: [2] unicorn/no-unused-properties: [2]
unicorn/no-useless-fallback-in-spread: [2]
unicorn/no-useless-length-check: [2]
unicorn/no-useless-promise-resolve-reject: [2]
unicorn/no-useless-spread: [2]
unicorn/no-useless-switch-case: [2]
unicorn/no-useless-undefined: [0] unicorn/no-useless-undefined: [0]
unicorn/no-zero-fractions: [2] unicorn/no-zero-fractions: [2]
unicorn/number-literal-case: [0] unicorn/number-literal-case: [0]
unicorn/numeric-separators-style: [0] unicorn/numeric-separators-style: [0]
unicorn/prefer-add-event-listener: [2] unicorn/prefer-add-event-listener: [2]
unicorn/prefer-array-find: [2] unicorn/prefer-array-find: [2]
unicorn/prefer-array-flat-map: [2]
unicorn/prefer-array-flat: [2]
unicorn/prefer-array-index-of: [2]
unicorn/prefer-array-some: [2]
unicorn/prefer-at: [0]
unicorn/prefer-code-point: [2]
unicorn/prefer-dataset: [2] unicorn/prefer-dataset: [2]
unicorn/prefer-date-now: [2] unicorn/prefer-date-now: [2]
unicorn/prefer-default-parameters: [0]
unicorn/prefer-event-key: [2] unicorn/prefer-event-key: [2]
unicorn/prefer-export-from: [2]
unicorn/prefer-includes: [2] unicorn/prefer-includes: [2]
unicorn/prefer-json-parse-buffer: [0]
unicorn/prefer-math-trunc: [2] unicorn/prefer-math-trunc: [2]
unicorn/prefer-modern-dom-apis: [0] unicorn/prefer-modern-dom-apis: [0]
unicorn/prefer-modern-math-apis: [2]
unicorn/prefer-module: [2]
unicorn/prefer-native-coercion-functions: [2]
unicorn/prefer-negative-index: [2] unicorn/prefer-negative-index: [2]
unicorn/prefer-node-append: [0] unicorn/prefer-node-append: [0]
unicorn/prefer-node-protocol: [0]
unicorn/prefer-node-remove: [0] unicorn/prefer-node-remove: [0]
unicorn/prefer-number-properties: [0] unicorn/prefer-number-properties: [0]
unicorn/prefer-object-from-entries: [2]
unicorn/prefer-object-has-own: [0]
unicorn/prefer-optional-catch-binding: [2] unicorn/prefer-optional-catch-binding: [2]
unicorn/prefer-prototype-methods: [0]
unicorn/prefer-query-selector: [0] unicorn/prefer-query-selector: [0]
unicorn/prefer-reflect-apply: [0] unicorn/prefer-reflect-apply: [0]
unicorn/prefer-regexp-test: [2]
unicorn/prefer-replace-all: [0] unicorn/prefer-replace-all: [0]
unicorn/prefer-set-has: [0] unicorn/prefer-set-has: [0]
unicorn/prefer-spread: [0] unicorn/prefer-spread: [0]
unicorn/prefer-starts-ends-with: [2] unicorn/prefer-starts-ends-with: [2]
unicorn/prefer-string-slice: [0] unicorn/prefer-string-slice: [0]
unicorn/prefer-switch: [0]
unicorn/prefer-ternary: [0] unicorn/prefer-ternary: [0]
unicorn/prefer-text-content: [2] unicorn/prefer-text-content: [2]
unicorn/prefer-top-level-await: [0]
unicorn/prefer-trim-start-end: [2] unicorn/prefer-trim-start-end: [2]
unicorn/prefer-type-error: [0] unicorn/prefer-type-error: [0]
unicorn/prevent-abbreviations: [0] unicorn/prevent-abbreviations: [0]
unicorn/relative-url-style: [2]
unicorn/require-array-join-separator: [2]
unicorn/require-number-to-fixed-digits-argument: [2]
unicorn/require-post-message-target-origin: [0]
unicorn/string-content: [0] unicorn/string-content: [0]
unicorn/template-indent: [2]
unicorn/text-encoding-identifier-case: [0]
unicorn/throw-new-error: [2] unicorn/throw-new-error: [2]
use-isnan: [2] use-isnan: [2]
valid-typeof: [2, {requireStringLiterals: true}] valid-typeof: [2, {requireStringLiterals: true}]

9
.gitattributes vendored
View File

@@ -1,9 +1,6 @@
* text=auto eol=lf * text=auto eol=lf
*.tmpl linguist-language=Handlebars /vendor/** -text -eol linguist-vendored
/public/vendor/** -text -eol linguist-vendored
/templates/**/*.tmpl linguist-language=Handlebars
/.eslintrc linguist-language=YAML /.eslintrc linguist-language=YAML
/.stylelintrc linguist-language=YAML /.stylelintrc linguist-language=YAML
/public/vendor/** -text -eol linguist-vendored
/vendor/** -text -eol linguist-vendored
/web_src/fomantic/build/** linguist-generated
/web_src/js/vendor/** -text -eol linguist-vendored
Dockerfile.* linguist-language=Dockerfile

View File

@@ -1,94 +0,0 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
- type: markdown
attributes:
value: |
1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report.
6. In particular it's really important to provide pertinent logs. You must give us DEBUG level logs.
Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services.
- type: input
id: gitea-ver
attributes:
label: Gitea Version
description: Gitea version (or commit reference) of your instance
validations:
required: true
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Gitea demo site?
description: |
If so, please provide a URL in the Description field
URL of Gitea demo: https://try.gitea.io
options:
- "Yes"
- "No"
validations:
required: true
- type: markdown
attributes:
value: |
It's really important to provide pertinent logs
Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
- type: input
id: logs
attributes:
label: Log Gist
description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots
- type: input
id: git-ver
attributes:
label: Git Version
description: The version of git running on the server
- type: input
id: os-ver
attributes:
label: Operating System
description: The operating system you are using to run Gitea
- type: textarea
id: run-info
attributes:
label: How are you running Gitea?
description: |
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
If you are using a package or systemd tell us what distribution you are using
validations:
required: true
- type: dropdown
id: database
attributes:
label: Database
description: What database system are you running?
options:
- PostgreSQL
- MySQL
- MSSQL
- SQLite

View File

@@ -1,17 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Security Concern
url: https://tinyurl.com/security-gitea
about: For security concerns, please send a mail to security@gitea.io instead of opening a public issue.
- name: Discord Server
url: https://discord.gg/Gitea
about: Please ask questions and discuss configuration or deployment problems here.
- name: Discourse Forum
url: https://discourse.gitea.io
about: Questions and configuration or deployment problems can also be discussed on our forum.
- name: Frequently Asked Questions
url: https://docs.gitea.io/en-us/faq
about: Please check if your question isn't mentioned here.
- name: Crowdin Translations
url: https://crowdin.com/project/gitea
about: Translations are managed here.

View File

@@ -1,24 +0,0 @@
name: Feature Request
description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here!
labels: ["kind/feature", "kind/proposal"]
body:
- type: markdown
attributes:
value: |
1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your feature hasn't already been suggested.
- type: textarea
id: description
attributes:
label: Feature Description
placeholder: |
I think it would be great if Gitea had...
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If you can, provide screenshots of an implementation on another site e.g. GitHub

View File

@@ -1,66 +0,0 @@
name: Web Interface Bug Report
description: Something doesn't look quite as it should? Report it here!
labels: ["kind/bug", "kind/ui"]
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
- type: markdown
attributes:
value: |
1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your issue doesn't already exist.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report.
6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript
error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us
DEBUG level logs. (See https://docs.gitea.io/en-us/logging-configuration/#debugging-problems)
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services.
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: Please provide at least 1 screenshot showing the issue.
validations:
required: true
- type: input
id: gitea-ver
attributes:
label: Gitea Version
description: Gitea version (or commit reference) your instance is running
validations:
required: true
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Gitea demo site?
description: |
If so, please provide a URL in the Description field
URL of Gitea demo: https://try.gitea.io
options:
- "Yes"
- "No"
validations:
required: true
- type: input
id: os-ver
attributes:
label: Operating System
description: The operating system you are using to access Gitea
- type: input
id: browser-ver
attributes:
label: Browser Version
description: The browser and version that you are using to access Gitea
validations:
required: true

View File

@@ -1,9 +1,7 @@
<!--
Please check the following: Please check the following:
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for bug fixes. 1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes.
2. Read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md 2. Read contributing guidelines: https://github.com/go-gitea/gitea/blob/master/CONTRIBUTING.md
3. Describe what your pull request does and which issue you're targeting (if any) 3. Describe what your pull request does and which issue you're targeting (if any)
--> **You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.**

9
.gitignore vendored
View File

@@ -9,8 +9,6 @@ _test
# IntelliJ # IntelliJ
.idea .idea
# Goland's output filename can not be set manually
/go_build_*
# MS VSCode # MS VSCode
.vscode .vscode
@@ -34,10 +32,7 @@ _testmain.go
*coverage.out *coverage.out
coverage.all coverage.all
cpu.out
/modules/migration/bindata.go
/modules/migration/bindata.go.hash
/modules/options/bindata.go /modules/options/bindata.go
/modules/options/bindata.go.hash /modules/options/bindata.go.hash
/modules/public/bindata.go /modules/public/bindata.go
@@ -80,15 +75,11 @@ cpu.out
/integrations/mssql.ini /integrations/mssql.ini
/node_modules /node_modules
/yarn.lock /yarn.lock
/yarn-error.log
/npm-debug.log*
/public/js /public/js
/public/serviceworker.js /public/serviceworker.js
/public/css /public/css
/public/fonts /public/fonts
/public/img/webpack /public/img/webpack
/vendor
/web_src/fomantic/node_modules
/web_src/fomantic/build/* /web_src/fomantic/build/*
!/web_src/fomantic/build/semantic.js !/web_src/fomantic/build/semantic.js
!/web_src/fomantic/build/semantic.css !/web_src/fomantic/build/semantic.css

View File

@@ -9,72 +9,24 @@ linters:
- unused - unused
- structcheck - structcheck
- varcheck - varcheck
- golint
- dupl - dupl
#- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. #- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
- gofmt - gofmt
- misspell - misspell
- gocritic - gocritic
- bidichk
- ineffassign
- revive
- gofumpt
- depguard
enable-all: false enable-all: false
disable-all: true disable-all: true
fast: false fast: false
run: run:
go: 1.18 timeout: 3m
timeout: 10m
skip-dirs:
- node_modules
- public
- web_src
linters-settings: linters-settings:
gocritic: gocritic:
disabled-checks: disabled-checks:
- ifElseChain - ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way. - singleCaseSwitch # Every time this occurred in the code, there was no other way.
revive:
ignore-generated-header: false
severity: warning
confidence: 0.8
errorCode: 1
warningCode: 1
rules:
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
- name: package-comments
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: duplicated-imports
- name: modifies-value-receiver
gofumpt:
extra-rules: true
lang-version: "1.18"
depguard:
# TODO: use depguard to replace import checks in gitea-vet
list-type: denylist
# Check the list against standard lib.
include-go-root: true
packages-with-error-message:
- encoding/json: "use gitea's modules/json instead of encoding/json"
- github.com/unknwon/com: "use gitea's util and replacements"
issues: issues:
exclude-rules: exclude-rules:
@@ -118,6 +70,9 @@ issues:
- path: modules/log/ - path: modules/log/
linters: linters:
- errcheck - errcheck
- path: routers/routes/web.go
linters:
- dupl
- path: routers/api/v1/repo/issue_subscription.go - path: routers/api/v1/repo/issue_subscription.go
linters: linters:
- dupl - dupl
@@ -155,10 +110,3 @@ issues:
- text: "exitAfterDefer:" - text: "exitAfterDefer:"
linters: linters:
- gocritic - gocritic
- path: modules/graceful/manager_windows.go
linters:
- staticcheck
text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead."
- path: models/user/openid.go
linters:
- golint

View File

@@ -1,8 +1,5 @@
*.min.css /vendor
*.min.js /public/vendor/plugins
/modules/options/bindata.go /modules/options/bindata.go
/modules/public/bindata.go /modules/public/bindata.go
/modules/templates/bindata.go /modules/templates/bindata.go
/public/vendor/plugins
/vendor
node_modules

1
.npmrc
View File

@@ -1,5 +1,4 @@
audit=false audit=false
fund=false fund=false
update-notifier=false
package-lock=true package-lock=true
save-exact=true save-exact=true

25
.revive.toml Normal file
View File

@@ -0,0 +1,25 @@
ignoreGeneratedHeader = false
severity = "warning"
confidence = 0.8
errorCode = 1
warningCode = 1
[rule.blank-imports]
[rule.context-as-argument]
[rule.context-keys-type]
[rule.dot-imports]
[rule.error-return]
[rule.error-strings]
[rule.error-naming]
[rule.exported]
[rule.if-return]
[rule.increment-decrement]
[rule.var-naming]
[rule.var-declaration]
[rule.package-comments]
[rule.range]
[rule.receiver-naming]
[rule.time-naming]
[rule.unexported-return]
[rule.indent-error-flow]
[rule.errorf]

View File

@@ -1,32 +1,15 @@
extends: stylelint-config-standard extends: stylelint-config-standard
overrides:
- files: ["**/*.less"]
customSyntax: postcss-less
rules: rules:
alpha-value-notation: null
at-rule-empty-line-before: null at-rule-empty-line-before: null
block-closing-brace-empty-line-before: null block-closing-brace-empty-line-before: null
color-function-notation: null
color-hex-length: null color-hex-length: null
comment-empty-line-before: null comment-empty-line-before: null
declaration-block-no-redundant-longhand-properties: null
declaration-block-single-line-max-declarations: null declaration-block-single-line-max-declarations: null
declaration-empty-line-before: null declaration-empty-line-before: null
function-no-unknown: null
hue-degree-notation: null
indentation: 2 indentation: 2
max-line-length: null
no-descending-specificity: null no-descending-specificity: null
no-invalid-position-at-import-rule: null
number-leading-zero: never number-leading-zero: never
number-max-precision: null
property-no-vendor-prefix: null
rule-empty-line-before: null rule-empty-line-before: null
selector-class-pattern: null
selector-id-pattern: null
selector-pseudo-element-colon-notation: double selector-pseudo-element-colon-notation: double
shorthand-property-no-redundant-values: true shorthand-property-no-redundant-values: true
string-quotes: null
value-no-vendor-prefix: null

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,12 @@
## Table of Contents ## Table of Contents
- [Contribution Guidelines](#contribution-guidelines) - [Contribution Guidelines](#contribution-guidelines)
- [Table of Contents](#table-of-contents)
- [Introduction](#introduction) - [Introduction](#introduction)
- [Bug reports](#bug-reports) - [Bug reports](#bug-reports)
- [Discuss your design](#discuss-your-design) - [Discuss your design](#discuss-your-design)
- [Testing redux](#testing-redux) - [Testing redux](#testing-redux)
- [Vendoring](#vendoring) - [Vendoring](#vendoring)
- [Translation](#translation) - [Translation](#translation)
- [Building Gitea](#building-gitea)
- [Code review](#code-review) - [Code review](#code-review)
- [Styleguide](#styleguide) - [Styleguide](#styleguide)
- [Design guideline](#design-guideline) - [Design guideline](#design-guideline)
@@ -81,22 +79,23 @@ Here's how to run the test suite:
|``make lint-frontend`` | lint frontend files | |``make lint-frontend`` | lint frontend files |
|``make lint-backend`` | lint backend files | |``make lint-backend`` | lint backend files |
- run test code (Suggest run in Linux) - run test code (Suggest run in linux)
| | | | | |
| :------------------------------------- | :----------------------------------------------- | | :------------------------------------- | :----------------------------------------------- |
|``make test[\#TestSpecificName]`` | run unit test | |``make test[\#TestSpecificName]`` | run unit test |
|``make test-sqlite[\#TestSpecificName]``| run [integration](integrations) test for SQLite | |``make test-sqlite[\#TestSpecificName]``| run [integration](integrations) test for sqlite |
|[More details about integrations](integrations/README.md) | |[More detail message about integrations](integrations/README.md) |
## Vendoring ## Vendoring
We manage dependencies via [Go Modules](https://golang.org/cmd/go/#hdr-Module_maintenance), more details: [go mod](https://go.dev/ref/mod). We keep a cached copy of dependencies within the `vendor/` directory,
managing updates via [Modules](https://golang.org/cmd/go/#hdr-Module_maintenance).
Pull requests should only include `go.mod`, `go.sum` updates if they are part of Pull requests should only include `vendor/` updates if they are part of
the same change, be it a bugfix or a feature addition. the same change, be it a bugfix or a feature addition.
The `go.mod`, `go.sum` update needs to be justified as part of the PR description, The `vendor/` update needs to be justified as part of the PR description,
and must be verified by the reviewers and/or merger to always reference and must be verified by the reviewers and/or merger to always reference
an existing upstream commit. an existing upstream commit.
@@ -105,7 +104,7 @@ You can find more information on how to get started with it on the [Modules Wiki
## Translation ## Translation
We do all translation work inside [Crowdin](https://crowdin.com/project/gitea). We do all translation work inside [Crowdin](https://crowdin.com/project/gitea).
The only translation that is maintained in this Git repository is The only translation that is maintained in this git repository is
[`en_US.ini`](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini) [`en_US.ini`](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)
and is synced regularly to Crowdin. Once a translation has reached and is synced regularly to Crowdin. Once a translation has reached
A SATISFACTORY PERCENTAGE it will be synced back into this repo and A SATISFACTORY PERCENTAGE it will be synced back into this repo and
@@ -134,15 +133,6 @@ Some of the key points:
if that is not related to your PR, please make *another* PR for that. if that is not related to your PR, please make *another* PR for that.
* Split big pull requests into multiple small ones. An incremental change * Split big pull requests into multiple small ones. An incremental change
will be faster to review than a huge PR. will be faster to review than a huge PR.
* Use the first comment as a summary explainer of your PR and you should keep this up-to-date as the PR evolves.
If your PR could cause a breaking change you must add a BREAKING section to this comment e.g.:
```
## :warning: BREAKING :warning:
```
To explain how this could affect users and how to mitigate these changes.
## Styleguide ## Styleguide
@@ -150,8 +140,8 @@ For imports you should use the following format (_without_ the comments)
```go ```go
import ( import (
// stdlib // stdlib
"encoding/json"
"fmt" "fmt"
"math"
// local packages // local packages
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@@ -165,7 +155,7 @@ import (
## Design guideline ## Design guideline
To maintain understandable code and avoid circular dependencies it is important to have a good structure of the code. The Gitea code is divided into the following parts: To maintain understandable code and avoid circular dependencies it is important to have a good structure of the code. The gitea code is divided into the following parts:
- **integration:** Integrations tests - **integration:** Integrations tests
- **models:** Contains the data structures used by xorm to construct database tables. It also contains supporting functions to query and update the database. Dependencies to other code in Gitea should be avoided although some modules might be needed (for example for logging). - **models:** Contains the data structures used by xorm to construct database tables. It also contains supporting functions to query and update the database. Dependencies to other code in Gitea should be avoided although some modules might be needed (for example for logging).
@@ -212,74 +202,9 @@ In general, HTTP methods are chosen as follows:
* **PUT** endpoints return status **No Content (204)**, used to **add/assign** existing Objects (e.g. User) to something (e.g. Org-Team) * **PUT** endpoints return status **No Content (204)**, used to **add/assign** existing Objects (e.g. User) to something (e.g. Org-Team)
* **PATCH** endpoints return changed object and status **OK (200)**, used to **edit/change** an existing object * **PATCH** endpoints return changed object and status **OK (200)**, used to **edit/change** an existing object
An endpoint which changes/edits an object expects all fields to be optional (except ones to identify the object, which are required). An endpoint which changes/edits an object expects all fields to be optional (except ones to identify the object, which are required).
### Endpoints returning lists should
* support pagination (`page` & `limit` options in query)
* set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
## Large Character Comments
Throughout the codebase there are large-text comments for sections of code, e.g.:
```go
// __________ .__
// \______ \ _______ _|__| ______ _ __
// | _// __ \ \/ / |/ __ \ \/ \/ /
// | | \ ___/\ /| \ ___/\ /
// |____|_ /\___ >\_/ |__|\___ >\/\_/
// \/ \/ \/
```
These were created using the `figlet` tool with the `graffiti` font.
A simple way of creating these is to use the following:
```bash
figlet -f graffiti Review | sed -e's+^+// +' - | xclip -sel clip -in
```
## Backports and Frontports
Occasionally backports of PRs are required.
The backported PR title should be:
```
Title of backported PR (#ORIGINAL_PR_NUMBER)
```
The first two lines of the summary of the backporting PR should be:
```
Backport #ORIGINAL_PR_NUMBER
```
with the rest of the summary matching the original PR. Similarly for frontports
---
The below is a script that may be helpful in creating backports. YMMV.
```bash
#!/bin/sh
PR="$1"
SHA="$2"
VERSION="$3"
if [ -z "$SHA" ]; then
SHA=$(gh api /repos/go-gitea/gitea/pulls/$PR -q '.merge_commit_sha')
fi
if [ -z "$VERSION" ]; then
VERSION="v1.16"
fi
echo git checkout origin/release/"$VERSION" -b backport-$PR-$VERSION
git checkout origin/release/"$VERSION" -b backport-$PR-$VERSION
git cherry-pick $SHA && git commit --amend && git push zeripath backport-$PR-$VERSION && xdg-open https://github.com/go-gitea/gitea/compare/release/"$VERSION"...zeripath:backport-$PR-$VERSION
```
## Developer Certificate of Origin (DCO) ## Developer Certificate of Origin (DCO)
@@ -292,7 +217,7 @@ Additionally you could add a line at the end of your commit message.
Signed-off-by: Joe Smith <joe.smith@email.com> Signed-off-by: Joe Smith <joe.smith@email.com>
``` ```
If you set your `user.name` and `user.email` Git configs, you can add the If you set your `user.name` and `user.email` git configs, you can add the
line to the end of your commit automatically with `git commit -s`. line to the end of your commit automatically with `git commit -s`.
We assume in good faith that the information you provide is legally binding. We assume in good faith that the information you provide is legally binding.
@@ -301,18 +226,18 @@ We assume in good faith that the information you provide is legally binding.
We adopted a release schedule to streamline the process of working We adopted a release schedule to streamline the process of working
on, finishing, and issuing releases. The overall goal is to make a on, finishing, and issuing releases. The overall goal is to make a
minor release every three or four months, which breaks down into two or three months of minor release every two months, which breaks down into one month of
general development followed by one month of testing and polishing general development followed by one month of testing and polishing
known as the release freeze. All the feature pull requests should be known as the release freeze. All the feature pull requests should be
merged before feature freeze. And, during the frozen period, a corresponding merged in the first month of one release period. And, during the frozen
release branch is open for fixes backported from main branch. Release candidates period, a corresponding release branch is open for fixes backported from
are made during this period for user testing to master. Release candidates are made during this period for user testing to
obtain a final version that is maintained in this branch. A release is obtain a final version that is maintained in this branch. A release is
maintained by issuing patch releases to only correct critical problems maintained by issuing patch releases to only correct critical problems
such as crashes or security issues. such as crashes or security issues.
Major release cycles are seasonal. They always begin on the 25th and end on Major release cycles are bimonthly. They always begin on the 25th and end on
the 24th (i.e., the 25th of December to March 24th). the 24th (i.e., the 25th of December to February 24th).
During a development cycle, we may also publish any necessary minor releases During a development cycle, we may also publish any necessary minor releases
for the previous version. For example, if the latest, published release is for the previous version. For example, if the latest, published release is
@@ -337,7 +262,7 @@ to the maintainers team. If a maintainer is inactive for more than 3
months and forgets to leave the maintainers team, the owners may move months and forgets to leave the maintainers team, the owners may move
him or her from the maintainers team to the advisors team. him or her from the maintainers team to the advisors team.
For security reasons, Maintainers should use 2FA for their accounts and For security reasons, Maintainers should use 2FA for their accounts and
if possible provide GPG signed commits. if possible provide gpg signed commits.
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/ https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
https://help.github.com/articles/signing-commits-with-gpg/ https://help.github.com/articles/signing-commits-with-gpg/
@@ -368,11 +293,6 @@ and lead the development of Gitea.
To honor the past owners, here's the history of the owners and the time To honor the past owners, here's the history of the owners and the time
they served: they served:
* 2022-01-01 ~ 2022-12-31 - https://github.com/go-gitea/gitea/issues/17872
* [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
* [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
* [Andrew Thornton](https://gitea.com/zeripath) <art27@cantab.net>
* 2021-01-01 ~ 2021-12-31 - https://github.com/go-gitea/gitea/issues/13801 * 2021-01-01 ~ 2021-12-31 - https://github.com/go-gitea/gitea/issues/13801
* [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com> * [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
* [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) <lauris@nix.lv> * [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) <lauris@nix.lv>
@@ -400,13 +320,13 @@ they served:
## Versions ## Versions
Gitea has the `main` branch as a tip branch and has version branches Gitea has the `master` branch as a tip branch and has version branches
such as `release/v0.9`. `release/v0.9` is a release branch and we will such as `release/v0.9`. `release/v0.9` is a release branch and we will
tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept
pull requests on the `release/v0.9` branch and publish a `v0.9.1` tag, pull requests on the `release/v0.9` branch and publish a `v0.9.1` tag,
after bringing the bug fix also to the main branch. after bringing the bug fix also to the master branch.
Since the `main` branch is a tip version, if you wish to use Gitea Since the `master` branch is a tip version, if you wish to use Gitea
in production, please download the latest release tag version. All the in production, please download the latest release tag version. All the
branches will be protected via GitHub, all the PRs to every branch must branches will be protected via GitHub, all the PRs to every branch must
be reviewed by two maintainers and must pass the automatic tests. be reviewed by two maintainers and must pass the automatic tests.
@@ -414,26 +334,22 @@ be reviewed by two maintainers and must pass the automatic tests.
## Releasing Gitea ## Releasing Gitea
* Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future. * Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
* Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours. * Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours.
* If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps: * If this is a big version first you have to create PR for changelog on branch `master` with PRs with label `changelog` and after it has been merged do following steps:
* Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`. * Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
* When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin` * When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`
* If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged. * If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged.
* Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`. * Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`.
* And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.) * And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically created a release and upload all the compiled binary. (But currently it didn't add the release notes automatically. Maybe we should fix that.)
* If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version. * If needed send PR for changelog on branch `master`.
* Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release. * Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
* Verify all release assets were correctly published through CI on dl.gitea.io and GitHub releases. Once ACKed:
* bump the version of https://dl.gitea.io/gitea/version.json
* merge the blog post PR
* announce the release in discord `#announcements`
## Copyright ## Copyright
Code that you contribute should use the standard copyright header: Code that you contribute should use the standard copyright header:
``` ```
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
``` ```

View File

@@ -1,5 +1,7 @@
###################################
#Build stage #Build stage
FROM golang:1.18-alpine3.16 AS build-env FROM golang:1.16-alpine3.13 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct} ENV GOPROXY ${GOPROXY:-direct}
@@ -23,7 +25,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
# Begin env-to-ini build # Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go RUN go build contrib/environment-to-ini/environment-to-ini.go
FROM alpine:3.16 FROM alpine:3.13
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000 EXPOSE 22 3000
@@ -51,7 +53,7 @@ RUN addgroup \
-u 1000 \ -u 1000 \
-G git \ -G git \
git && \ git && \
echo "git:*" | chpasswd -e echo "git:$(dd if=/dev/urandom bs=24 count=1 status=none | base64)" | chpasswd
ENV USER git ENV USER git
ENV GITEA_CUSTOM /data/gitea ENV GITEA_CUSTOM /data/gitea
@@ -64,5 +66,4 @@ CMD ["/bin/s6-svscan", "/etc/s6"]
COPY docker/root / COPY docker/root /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
RUN chmod 755 /usr/bin/entrypoint /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini RUN ln -s /app/gitea/gitea /usr/local/bin/gitea
RUN chmod 755 /etc/s6/gitea/* /etc/s6/openssh/* /etc/s6/.s6-svscan/*

View File

@@ -1,5 +1,7 @@
###################################
#Build stage #Build stage
FROM golang:1.18-alpine3.16 AS build-env FROM golang:1.16-alpine3.13 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct} ENV GOPROXY ${GOPROXY:-direct}
@@ -7,7 +9,7 @@ ENV GOPROXY ${GOPROXY:-direct}
ARG GITEA_VERSION ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS "bindata timetzdata $TAGS" ENV TAGS "bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS ARG CGO_EXTRA_CFLAGS
#Build deps #Build deps
RUN apk --no-cache add build-base git nodejs npm RUN apk --no-cache add build-base git nodejs npm
@@ -23,7 +25,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
# Begin env-to-ini build # Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go RUN go build contrib/environment-to-ini/environment-to-ini.go
FROM alpine:3.16 FROM alpine:3.13
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000 EXPOSE 2222 3000
@@ -33,7 +35,6 @@ RUN apk --no-cache add \
ca-certificates \ ca-certificates \
gettext \ gettext \
git \ git \
curl \
gnupg gnupg
RUN addgroup \ RUN addgroup \
@@ -45,23 +46,20 @@ RUN addgroup \
-s /bin/bash \ -s /bin/bash \
-u 1000 \ -u 1000 \
-G git \ -G git \
git git && \
echo "git:$(dd if=/dev/urandom bs=24 count=1 status=none | base64)" | chpasswd
RUN mkdir -p /var/lib/gitea /etc/gitea RUN mkdir -p /var/lib/gitea /etc/gitea
RUN chown git:git /var/lib/gitea /etc/gitea RUN chown git:git /var/lib/gitea /etc/gitea
COPY docker/rootless / COPY docker/rootless /
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /usr/local/bin/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh /usr/local/bin/docker-setup.sh /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini
#git:git USER git:git
USER 1000:1000
ENV GITEA_WORK_DIR /var/lib/gitea ENV GITEA_WORK_DIR /var/lib/gitea
ENV GITEA_CUSTOM /var/lib/gitea/custom ENV GITEA_CUSTOM /var/lib/gitea/custom
ENV GITEA_TEMP /tmp/gitea ENV GITEA_TEMP /tmp/gitea
ENV TMPDIR /tmp/gitea
#TODO add to docs the ability to define the ini to load (usefull to test and revert a config) #TODO add to docs the ability to define the ini to load (usefull to test and revert a config)
ENV GITEA_APP_INI /etc/gitea/app.ini ENV GITEA_APP_INI /etc/gitea/app.ini
ENV HOME "/var/lib/gitea/git" ENV HOME "/var/lib/gitea/git"

View File

@@ -1,4 +1,5 @@
Alexey Makhov <amakhov@avito.ru> (@makhov) Alexey Makhov <amakhov@avito.ru> (@makhov)
Andrey Nering <andrey.nering@gmail.com> (@andreynering)
Bo-Yi Wu <appleboy.tw@gmail.com> (@appleboy) Bo-Yi Wu <appleboy.tw@gmail.com> (@appleboy)
Ethan Koenig <ethantkoenig@gmail.com> (@ethantkoenig) Ethan Koenig <ethantkoenig@gmail.com> (@ethantkoenig)
Kees de Vries <bouwko@gmail.com> (@Bwko) Kees de Vries <bouwko@gmail.com> (@Bwko)
@@ -40,10 +41,3 @@ Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
Norwin Roosen <git@nroo.de> (@noerw) Norwin Roosen <git@nroo.de> (@noerw)
Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu) Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu)
Patrick Schratz <patrick.schratz@gmail.com> (@pat-s) Patrick Schratz <patrick.schratz@gmail.com> (@pat-s)
Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R)
Steven Kriegler <sk.bunsenbrenner@gmail.com> (@justusbunsi)
Jimmy Praet <jimmy.praet@telenet.be> (@jpraet)
Leon Hofmeister <dev.lh@web.de> (@delvh)
Gusted <williamzijl7@hotmail.com) (@Gusted)
silentcode <silentcode@senga.org> (@silentcodeg)
Wim <wim@42.be> (@42wim)

335
Makefile
View File

@@ -24,17 +24,9 @@ SHASUM ?= shasum -a 256
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
COMMA := , COMMA := ,
XGO_VERSION := go-1.18.x XGO_VERSION := go-1.16.x
MIN_GO_VERSION := 001014000
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.29.0 MIN_NODE_VERSION := 010013000
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.4.0
ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.3.1
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.46.0
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.29.0
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
DOCKER_IMAGE ?= gitea/gitea DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest DOCKER_TAG ?= latest
@@ -51,9 +43,6 @@ endif
ifeq ($(OS), Windows_NT) ifeq ($(OS), Windows_NT)
GOFLAGS := -v -buildmode=exe GOFLAGS := -v -buildmode=exe
EXECUTABLE ?= gitea.exe EXECUTABLE ?= gitea.exe
else ifeq ($(OS), Windows)
GOFLAGS := -v -buildmode=exe
EXECUTABLE ?= gitea.exe
else else
GOFLAGS := -v GOFLAGS := -v
EXECUTABLE ?= gitea EXECUTABLE ?= gitea
@@ -65,14 +54,15 @@ else
SED_INPLACE := sed -i '' SED_INPLACE := sed -i ''
endif endif
GOFMT ?= gofmt -s
EXTRA_GOFLAGS ?= EXTRA_GOFLAGS ?=
MAKE_VERSION := $(shell "$(MAKE)" -v | head -n 1) MAKE_VERSION := $(shell $(MAKE) -v | head -n 1)
MAKE_EVIDENCE_DIR := .make_evidence MAKE_EVIDENCE_DIR := .make_evidence
ifeq ($(RACE_ENABLED),true) ifneq ($(RACE_ENABLED),)
GOFLAGS += -race GOTESTFLAGS ?= -race
GOTESTFLAGS += -race
endif endif
STORED_VERSION_FILE := VERSION STORED_VERSION_FILE := VERSION
@@ -84,7 +74,7 @@ else
ifneq ($(DRONE_BRANCH),) ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH)) VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
else else
VERSION ?= main VERSION ?= master
endif endif
STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null) STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
@@ -99,9 +89,11 @@ LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(G
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/models/migrations code.gitea.io/gitea/integrations/migration-test code.gitea.io/gitea/integrations,$(shell $(GO) list ./... | grep -v /vendor/)) GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list -mod=vendor ./... | grep -v /vendor/)))
FOMANTIC_WORK_DIR := web_src/fomantic FOMANTIC_CONFIGS := semantic.json web_src/fomantic/theme.config.less web_src/fomantic/_site/globals/site.variables
FOMANTIC_DEST := web_src/fomantic/build/semantic.js web_src/fomantic/build/semantic.css
FOMANTIC_DEST_DIR := web_src/fomantic/build
WEBPACK_SOURCES := $(shell find web_src/js web_src/less -type f) WEBPACK_SOURCES := $(shell find web_src/js web_src/less -type f)
WEBPACK_CONFIGS := webpack.config.js WEBPACK_CONFIGS := webpack.config.js
@@ -121,9 +113,7 @@ TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
TEST_TAGS ?= sqlite sqlite_unlock_notify TEST_TAGS ?= sqlite sqlite_unlock_notify
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) GO_DIRS := cmd integrations models modules routers build services vendor tools
GO_DIRS := cmd integrations models modules routers build services tools
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" -not -path modules/options/bindata.go -not -path modules/public/bindata.go -not -path modules/templates/bindata.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" -not -path modules/options/bindata.go -not -path modules/public/bindata.go -not -path modules/templates/bindata.go)
@@ -132,6 +122,10 @@ ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
GO_SOURCES += $(BINDATA_DEST) GO_SOURCES += $(BINDATA_DEST)
endif endif
GO_SOURCES_OWN := $(filter-out vendor/% %/bindata.go, $(GO_SOURCES))
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger
SWAGGER := $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger
SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC := templates/swagger/v1_json.tmpl
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
@@ -171,18 +165,12 @@ help:
@echo " - watch-backend watch backend files and continuously rebuild" @echo " - watch-backend watch backend files and continuously rebuild"
@echo " - clean delete backend and integration files" @echo " - clean delete backend and integration files"
@echo " - clean-all delete backend, frontend and integration files" @echo " - clean-all delete backend, frontend and integration files"
@echo " - deps install dependencies"
@echo " - deps-frontend install frontend dependencies"
@echo " - deps-backend install backend dependencies"
@echo " - lint lint everything" @echo " - lint lint everything"
@echo " - lint-frontend lint frontend files" @echo " - lint-frontend lint frontend files"
@echo " - lint-backend lint backend files" @echo " - lint-backend lint backend files"
@echo " - checks run various consistency checks" @echo " - checks run various consistency checks"
@echo " - checks-frontend check frontend files" @echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files" @echo " - checks-backend check backend files"
@echo " - test test everything"
@echo " - test-frontend test frontend files"
@echo " - test-backend test backend files"
@echo " - webpack build webpack files" @echo " - webpack build webpack files"
@echo " - svg build svg files" @echo " - svg build svg files"
@echo " - fomantic build fomantic files" @echo " - fomantic build fomantic files"
@@ -194,19 +182,18 @@ help:
@echo " - generate-swagger generate the swagger spec from code comments" @echo " - generate-swagger generate the swagger spec from code comments"
@echo " - swagger-validate check if the swagger spec is valid" @echo " - swagger-validate check if the swagger spec is valid"
@echo " - golangci-lint run golangci-lint linter" @echo " - golangci-lint run golangci-lint linter"
@echo " - revive run revive linter"
@echo " - misspell check for misspellings"
@echo " - vet examines Go source code and reports suspicious constructs" @echo " - vet examines Go source code and reports suspicious constructs"
@echo " - tidy run go mod tidy"
@echo " - test[\#TestSpecificName] run unit test" @echo " - test[\#TestSpecificName] run unit test"
@echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite" @echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite"
@echo " - pr#<index> build and start gitea from a PR with integration test data loaded" @echo " - pr#<index> build and start gitea from a PR with integration test data loaded"
.PHONY: go-check .PHONY: go-check
go-check: go-check:
$(eval MIN_GO_VERSION_STR := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
$(eval MIN_GO_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' ')))
$(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');)) $(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \ @if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
echo "Gitea requires Go $(MIN_GO_VERSION_STR) or greater to build. You can get it at https://go.dev/dl/"; \ echo "Gitea requires Go 1.14 or greater to build. You can get it at https://golang.org/dl/"; \
exit 1; \ exit 1; \
fi fi
@@ -219,18 +206,16 @@ git-check:
.PHONY: node-check .PHONY: node-check
node-check: node-check:
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');)) $(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1)) $(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \ @if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \ echo "Gitea requires Node.js 10 or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
exit 1; \ exit 1; \
fi fi
.PHONY: clean-all .PHONY: clean-all
clean-all: clean clean-all: clean
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules rm -rf $(WEBPACK_DEST_ENTRIES)
.PHONY: clean .PHONY: clean
clean: clean:
@@ -243,13 +228,14 @@ clean:
.PHONY: fmt .PHONY: fmt
fmt: fmt:
@echo "Running gitea-fmt (with gofumpt)..." @echo "Running go fmt..."
@MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' @$(GOFMT) -w $(GO_SOURCES_OWN)
.PHONY: vet .PHONY: vet
vet: vet:
@echo "Running go vet..." @echo "Running go vet..."
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet @$(GO) vet $(GO_PACKAGES)
@GOOS= GOARCH= $(GO) build -mod=vendor code.gitea.io/gitea-vet
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES) @$(GO) vet -vettool=gitea-vet $(GO_PACKAGES)
.PHONY: $(TAGS_EVIDENCE) .PHONY: $(TAGS_EVIDENCE)
@@ -263,7 +249,7 @@ endif
.PHONY: generate-swagger .PHONY: generate-swagger
generate-swagger: generate-swagger:
$(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)' $(SWAGGER) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)' $(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
@@ -279,18 +265,41 @@ swagger-check: generate-swagger
.PHONY: swagger-validate .PHONY: swagger-validate
swagger-validate: swagger-validate:
$(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)'
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)' $(SWAGGER) validate './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
.PHONY: errcheck .PHONY: errcheck
errcheck: errcheck:
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/kisielk/errcheck; \
fi
@echo "Running errcheck..." @echo "Running errcheck..."
$(GO) run $(ERRCHECK_PACKAGE) $(GO_PACKAGES) @errcheck $(GO_PACKAGES)
.PHONY: revive
revive:
GO111MODULE=on $(GO) run -mod=vendor build/lint.go -config .revive.toml -exclude=./vendor/... ./... || exit 1
.PHONY: misspell-check
misspell-check:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
@echo "Running misspell-check..."
@misspell -error -i unknwon,destory $(GO_SOURCES_OWN)
.PHONY: misspell
misspell:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
@echo "Running go misspell..."
@misspell -w -i unknwon $(GO_SOURCES_OWN)
.PHONY: fmt-check .PHONY: fmt-check
fmt-check: fmt-check:
# get all go files and run gitea-fmt (with gofmt) on them # get all go files and run go fmt on them
@diff=$$(MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -l '{file-list}'); \ @diff=$$($(GOFMT) -d $(GO_SOURCES_OWN)); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \ echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \ echo "$${diff}"; \
@@ -301,21 +310,21 @@ fmt-check:
checks: checks-frontend checks-backend checks: checks-frontend checks-backend
.PHONY: checks-frontend .PHONY: checks-frontend
checks-frontend: lockfile-check svg-check checks-frontend: svg-check
.PHONY: checks-backend .PHONY: checks-backend
checks-backend: gomod-check swagger-check swagger-validate checks-backend: misspell-check test-vendor swagger-check swagger-validate
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint: lint-frontend lint-backend
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: node_modules lint-frontend: node_modules
npx eslint --color --max-warnings=0 web_src/js build templates *.config.js docs/assets/js npx eslint --color --max-warnings=0 web_src/js build templates webpack.config.js
npx stylelint --color --max-warnings=0 web_src/less npx stylelint --color --max-warnings=0 web_src/less
.PHONY: lint-backend .PHONY: lint-backend
lint-backend: golangci-lint vet editorconfig-checker lint-backend: golangci-lint revive vet
.PHONY: watch .PHONY: watch
watch: watch:
@@ -328,26 +337,22 @@ watch-frontend: node-check node_modules
.PHONY: watch-backend .PHONY: watch-backend
watch-backend: go-check watch-backend: go-check
$(GO) run $(AIR_PACKAGE) -c .air.toml @hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/cosmtrek/air; \
fi
air -c .air.conf
.PHONY: test .PHONY: test
test: test-frontend test-backend test:
@echo "Running go test with -tags '$(TEST_TAGS)'..."
.PHONY: test-backend @$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='$(TEST_TAGS)' $(GO_PACKAGES)
test-backend:
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_PACKAGES)
.PHONY: test-frontend
test-frontend: node_modules
@NODE_OPTIONS="--experimental-vm-modules --no-warnings" npx jest --color
.PHONY: test-check .PHONY: test-check
test-check: test-check:
@echo "Running test-check..."; @echo "Running test-check...";
@diff=$$(git status -s); \ @diff=$$(git status -s); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "make test-backend has changed files in the source tree:"; \ echo "make test has changed files in the source tree:"; \
echo "$${diff}"; \ echo "$${diff}"; \
echo "You should change the tests to create these files in a temporary directory."; \ echo "You should change the tests to create these files in a temporary directory."; \
echo "Do not simply add these files to .gitignore"; \ echo "Do not simply add these files to .gitignore"; \
@@ -357,33 +362,26 @@ test-check:
.PHONY: test\#% .PHONY: test\#%
test\#%: test\#%:
@echo "Running go test with -tags '$(TEST_TAGS)'..." @echo "Running go test with -tags '$(TEST_TAGS)'..."
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_PACKAGES) @$(GO) test -mod=vendor -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_PACKAGES)
.PHONY: coverage .PHONY: coverage
coverage: coverage:
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out GO111MODULE=on $(GO) run -mod=vendor build/gocovmerge.go integration.coverage.out $(shell find . -type f -name "coverage.out") > coverage.all
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
GO111MODULE=on $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all || (echo "gocovmerge failed"; echo "integration.coverage.out"; cat integration.coverage.out; echo "coverage.out"; cat coverage.out; exit 1)
.PHONY: unit-test-coverage .PHONY: unit-test-coverage
unit-test-coverage: unit-test-coverage:
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." @echo "Running unit-test-coverage -tags '$(TEST_TAGS)'..."
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 @$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
.PHONY: tidy
tidy:
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
.PHONY: vendor .PHONY: vendor
vendor: tidy vendor:
$(GO) mod vendor $(GO) mod tidy && $(GO) mod vendor
.PHONY: gomod-check .PHONY: test-vendor
gomod-check: tidy test-vendor: vendor
@diff=$$(git diff go.sum); \ @diff=$$(git diff vendor/); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make tidy' and commit the result:"; \ echo "Please run 'make vendor' and commit the result:"; \
echo "$${diff}"; \ echo "$${diff}"; \
exit 1; \ exit 1; \
fi fi
@@ -401,14 +399,8 @@ test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
.PHONY: test-sqlite-migration .PHONY: test-sqlite-migration
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite test-sqlite-migration: migrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
.PHONY: test-sqlite-migration\#%
test-sqlite-migration\#%: migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test -test.run $(subst .,/,$*)
generate-ini-mysql: generate-ini-mysql:
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \ sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
@@ -427,9 +419,8 @@ test-mysql\#%: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
.PHONY: test-mysql-migration .PHONY: test-mysql-migration
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test generate-ini-mysql test-mysql-migration: migrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./migrations.mysql.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./migrations.mysql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./migrations.individual.mysql.test
generate-ini-mysql8: generate-ini-mysql8:
sed -e 's|{{TEST_MYSQL8_HOST}}|${TEST_MYSQL8_HOST}|g' \ sed -e 's|{{TEST_MYSQL8_HOST}}|${TEST_MYSQL8_HOST}|g' \
@@ -448,9 +439,8 @@ test-mysql8\#%: integrations.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test -test.run $(subst .,/,$*)
.PHONY: test-mysql8-migration .PHONY: test-mysql8-migration
test-mysql8-migration: migrations.mysql8.test migrations.individual.mysql8.test generate-ini-mysql8 test-mysql8-migration: migrations.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./migrations.mysql8.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./migrations.mysql8.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./migrations.individual.mysql8.test
generate-ini-pgsql: generate-ini-pgsql:
sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \ sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \
@@ -470,9 +460,8 @@ test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
.PHONY: test-pgsql-migration .PHONY: test-pgsql-migration
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test generate-ini-pgsql test-pgsql-migration: migrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./migrations.pgsql.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./migrations.pgsql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./migrations.individual.pgsql.test
generate-ini-mssql: generate-ini-mssql:
sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \ sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \
@@ -491,9 +480,8 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*) GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
.PHONY: test-mssql-migration .PHONY: test-mssql-migration
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test generate-ini-mssql test-mssql-migration: migrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test -test.failfast GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test -test.failfast
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./migrations.individual.mssql.test -test.failfast
.PHONY: bench-sqlite .PHONY: bench-sqlite
bench-sqlite: integrations.sqlite.test generate-ini-sqlite bench-sqlite: integrations.sqlite.test generate-ini-sqlite
@@ -515,30 +503,23 @@ bench-pgsql: integrations.pgsql.test generate-ini-pgsql
integration-test-coverage: integrations.cover.test generate-ini-mysql integration-test-coverage: integrations.cover.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
.PHONY: integration-test-coverage-sqlite
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
integrations.mysql.test: git-check $(GO_SOURCES) integrations.mysql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -o integrations.mysql.test $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql.test
integrations.mysql8.test: git-check $(GO_SOURCES) integrations.mysql8.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -o integrations.mysql8.test $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql8.test
integrations.pgsql.test: git-check $(GO_SOURCES) integrations.pgsql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -o integrations.pgsql.test $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.pgsql.test
integrations.mssql.test: git-check $(GO_SOURCES) integrations.mssql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -o integrations.mssql.test $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mssql.test
integrations.sqlite.test: git-check $(GO_SOURCES) integrations.sqlite.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags '$(TEST_TAGS)' $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags '$(TEST_TAGS)'
integrations.cover.test: git-check $(GO_SOURCES) integrations.cover.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.test $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.test
integrations.cover.sqlite.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)'
.PHONY: migrations.mysql.test .PHONY: migrations.mysql.test
migrations.mysql.test: $(GO_SOURCES) migrations.mysql.test: $(GO_SOURCES)
@@ -560,26 +541,6 @@ migrations.mssql.test: $(GO_SOURCES)
migrations.sqlite.test: $(GO_SOURCES) migrations.sqlite.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)' $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
.PHONY: migrations.individual.mysql.test
migrations.individual.mysql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mysql.test
.PHONY: migrations.individual.mysql8.test
migrations.individual.mysql8.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mysql8.test
.PHONY: migrations.individual.pgsql.test
migrations.individual.pgsql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.pgsql.test
.PHONY: migrations.individual.mssql.test
migrations.individual.mssql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mssql.test
.PHONY: migrations.individual.sqlite.test
migrations.individual.sqlite.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.sqlite.test -tags '$(TEST_TAGS)'
.PHONY: check .PHONY: check
check: test check: test
@@ -591,7 +552,7 @@ install: $(wildcard *.go)
build: frontend backend build: frontend backend
.PHONY: frontend .PHONY: frontend
frontend: $(WEBPACK_DEST) frontend: node-check $(WEBPACK_DEST)
.PHONY: backend .PHONY: backend
backend: go-check generate $(EXECUTABLE) backend: go-check generate $(EXECUTABLE)
@@ -599,44 +560,50 @@ backend: go-check generate $(EXECUTABLE)
.PHONY: generate .PHONY: generate
generate: $(TAGS_PREREQ) generate: $(TAGS_PREREQ)
@echo "Running go generate..." @echo "Running go generate..."
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES) @CC= GOOS= GOARCH= $(GO) generate -mod=vendor -tags '$(TAGS)' $(GO_PACKAGES)
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build -mod=vendor $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: release .PHONY: release
release: frontend generate release-windows release-linux release-darwin release-copy release-compress vendor release-sources release-docs release-check release: frontend generate release-windows release-linux release-darwin release-copy release-compress release-sources release-docs release-check
$(DIST_DIRS): $(DIST_DIRS):
mkdir -p $(DIST_DIRS) mkdir -p $(DIST_DIRS)
.PHONY: release-windows .PHONY: release-windows
release-windows: | $(DIST_DIRS) release-windows: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
ifeq (,$(findstring gogit,$(TAGS))) $(GO) install src.techknowlogick.com/xgo@latest; \
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit . fi
endif CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq ($(CI),true) ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries cp /build/* $(DIST)/binaries
endif endif
.PHONY: release-linux .PHONY: release-linux
release-linux: | $(DIST_DIRS) release-linux: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) . @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
ifeq ($(CI),true) $(GO) install src.techknowlogick.com/xgo@latest; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries cp /build/* $(DIST)/binaries
endif endif
.PHONY: release-darwin .PHONY: release-darwin
release-darwin: | $(DIST_DIRS) release-darwin: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) . @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
ifeq ($(CI),true) $(GO) install src.techknowlogick.com/xgo@latest; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries cp /build/* $(DIST)/binaries
endif endif
.PHONY: release-copy .PHONY: release-copy
release-copy: | $(DIST_DIRS) release-copy: | $(DIST_DIRS)
cd $(DIST); for file in `find . -type f -name "*"`; do cp $${file} ./release/; done; cd $(DIST); for file in `find /build -type f -name "*"`; do cp $${file} ./release/; done;
.PHONY: release-check .PHONY: release-check
release-check: | $(DIST_DIRS) release-check: | $(DIST_DIRS)
@@ -644,16 +611,15 @@ release-check: | $(DIST_DIRS)
.PHONY: release-compress .PHONY: release-compress
release-compress: | $(DIST_DIRS) release-compress: | $(DIST_DIRS)
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PAGAGE) -k -9 $${file}; done; @hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
fi
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
.PHONY: release-sources .PHONY: release-sources
release-sources: | $(DIST_DIRS) release-sources: | $(DIST_DIRS) node_modules
echo $(VERSION) > $(STORED_VERSION_FILE) echo $(VERSION) > $(STORED_VERSION_FILE)
# bsdtar needs a ^ to prevent matching subdirectories tar --exclude=./$(DIST) --exclude=./.git --exclude=./$(MAKE_EVIDENCE_DIR) --exclude=./node_modules/.cache --exclude=./$(AIR_TMP_DIR) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
$(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./gitea-src-$(VERSION)/'" || echo "--transform 's|^./|gitea-src-$(VERSION)/|'"))
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
rm -f $(STORED_VERSION_FILE) rm -f $(STORED_VERSION_FILE)
.PHONY: release-docs .PHONY: release-docs
@@ -667,25 +633,6 @@ docs:
fi fi
cd docs; make trans-copy clean build-offline; cd docs; make trans-copy clean build-offline;
.PHONY: deps
deps: deps-frontend deps-backend
.PHONY: deps-frontend
deps-frontend: node_modules
.PHONY: deps-backend
deps-backend:
$(GO) mod download
$(GO) install $(AIR_PACKAGE)
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE)
$(GO) install $(ERRCHECK_PACKAGE)
$(GO) install $(GOFUMPT_PACKAGE)
$(GO) install $(GOLANGCI_LINT_PACKAGE)
$(GO) install $(GXZ_PAGAGE)
$(GO) install $(MISSPELL_PACKAGE)
$(GO) install $(SWAGGER_PACKAGE)
$(GO) install $(XGO_PACKAGE)
node_modules: package-lock.json node_modules: package-lock.json
npm install --no-save npm install --no-save
@touch node_modules @touch node_modules
@@ -698,20 +645,22 @@ npm-update: node-check | node_modules
@touch node_modules @touch node_modules
.PHONY: fomantic .PHONY: fomantic
fomantic: fomantic: $(FOMANTIC_DEST)
rm -rf $(FOMANTIC_WORK_DIR)/build
cd $(FOMANTIC_WORK_DIR) && npm install --no-save $(FOMANTIC_DEST): $(FOMANTIC_CONFIGS) | node_modules
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config @if [ ! -d node_modules/fomantic-ui ]; then \
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/ npm install --no-save --no-package-lock fomantic-ui@2.8.7; \
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build fi
$(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js rm -rf $(FOMANTIC_DEST_DIR)
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.* cp -f web_src/fomantic/theme.config.less node_modules/fomantic-ui/src/theme.config
cp -rf web_src/fomantic/_site/* node_modules/fomantic-ui/src/_site/
npx gulp -f node_modules/fomantic-ui/gulpfile.js build
@touch $(FOMANTIC_DEST)
.PHONY: webpack .PHONY: webpack
webpack: $(WEBPACK_DEST) webpack: $(WEBPACK_DEST)
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json | node_modules
@$(MAKE) -s node-check node_modules
rm -rf $(WEBPACK_DEST_ENTRIES) rm -rf $(WEBPACK_DEST_ENTRIES)
npx webpack npx webpack
@touch $(WEBPACK_DEST) @touch $(WEBPACK_DEST)
@@ -731,17 +680,6 @@ svg-check: svg
exit 1; \ exit 1; \
fi fi
.PHONY: lockfile-check
lockfile-check:
npm install --package-lock-only
@diff=$$(git diff package-lock.json); \
if [ -n "$$diff" ]; then \
echo "package-lock.json is inconsistent with package.json"; \
echo "Please run 'npm install --package-lock-only' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi
.PHONY: update-translations .PHONY: update-translations
update-translations: update-translations:
mkdir -p ./translations mkdir -p ./translations
@@ -761,8 +699,8 @@ generate-gitignore:
GO111MODULE=on $(GO) run build/generate-gitignores.go GO111MODULE=on $(GO) run build/generate-gitignores.go
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images:
npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7 npm install --no-save --no-package-lock fabric imagemin-zopfli
node build/generate-images.js $(TAGS) node build/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
@@ -779,18 +717,11 @@ pr\#%: clean-all
.PHONY: golangci-lint .PHONY: golangci-lint
golangci-lint: golangci-lint:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
export BINARY="golangci-lint"; \
# workaround step for the lint-backend-windows CI task because 'go run' can not curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.37.0; \
# have distinct GOOS/GOARCH for its build and run steps fi
.PHONY: golangci-lint-windows golangci-lint run --timeout 10m
golangci-lint-windows:
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
golangci-lint run
.PHONY: editorconfig-checker
editorconfig-checker:
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates
.PHONY: docker .PHONY: docker
docker: docker:

View File

@@ -1,21 +1,24 @@
<p align="center"> <p align="center">
<a href="https://gitea.io/"> <a href="https://gitea.io/">
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/> <img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/master/public/img/gitea.svg" width="220"/>
</a> </a>
</p> </p>
<h1 align="center">Gitea - Git with a cup of tea</h1> <h1 align="center">Gitea - Git with a cup of tea</h1>
<p align="center"> <p align="center">
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status"> <a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main"> <img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/master">
</a> </a>
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea"> <a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
<img src="https://img.shields.io/discord/322538954119184384.svg"> <img src="https://img.shields.io/discord/322538954119184384.svg">
</a> </a>
<a href="https://codecov.io/gh/go-gitea/gitea" title="Codecov"> <a href="https://microbadger.com/images/gitea/gitea" title="Get your own image badge on microbadger.com">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg"> <img src="https://images.microbadger.com/badges/image/gitea/gitea.svg">
</a> </a>
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card"> <a href="https://codecov.io/gh/go-gitea/gitea" title="Codecov">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg">
</a>
<a href="https://godoc.org/code.gitea.io/gitea" title="Go Report Card">
<img src="https://goreportcard.com/badge/code.gitea.io/gitea"> <img src="https://goreportcard.com/badge/code.gitea.io/gitea">
</a> </a>
<a href="https://godoc.org/code.gitea.io/gitea" title="GoDoc"> <a href="https://godoc.org/code.gitea.io/gitea" title="GoDoc">
@@ -36,8 +39,8 @@
<a href="https://crowdin.com/project/gitea" title="Crowdin"> <a href="https://crowdin.com/project/gitea" title="Crowdin">
<img src="https://badges.crowdin.net/gitea/localized.svg"> <img src="https://badges.crowdin.net/gitea/localized.svg">
</a> </a>
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs"> <a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main"> <img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea">
</a> </a>
<a href="https://www.bountysource.com/teams/gitea" title="Bountysource"> <a href="https://www.bountysource.com/teams/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity"> <img src="https://img.shields.io/bountysource/team/gitea/activity">
@@ -67,18 +70,20 @@ From the root of the source tree, run:
TAGS="bindata" make build TAGS="bindata" make build
or if SQLite support is required: or if sqlite support is required:
TAGS="bindata sqlite sqlite_unlock_notify" make build TAGS="bindata sqlite sqlite_unlock_notify" make build
The `build` target is split into two sub-targets: The `build` target is split into two sub-targets:
- `make backend` which requires [Go Stable](https://go.dev/dl/), required version is defined in [go.mod](/go.mod). - `make backend` which requires [Go 1.13](https://golang.org/dl/) or greater.
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies. - `make frontend` which requires [Node.js 10.13](https://nodejs.org/en/download/) or greater.
When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js and Internet connectivity. If pre-built frontend files are present it is possible to only build the backend:
Parallelism (`make -j <num>`) is not supported. TAGS="bindata" make backend
Parallelism is not supported for these targets, so please don't include `-j <num>`.
More info: https://docs.gitea.io/en-us/install-from-source/ More info: https://docs.gitea.io/en-us/install-from-source/
@@ -98,16 +103,6 @@ NOTES:
1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.** 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks! 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
## Translating
Translations are done through Crowdin. If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope fo fill it as questions pop up.
https://docs.gitea.io/en-us/translation-guidelines/
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## Further information ## Further information
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.io/en-us/). For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.io/en-us/).
@@ -157,7 +152,7 @@ We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
## License ## License
This project is licensed under the MIT License. This project is licensed under the MIT License.
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file See the [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) file
for the full license text. for the full license text.
## Screenshots ## Screenshots

View File

@@ -1,21 +1,24 @@
<p align="center"> <p align="center">
<a href="https://gitea.io/"> <a href="https://gitea.io/">
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/> <img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/master/public/img/gitea.svg" width="220"/>
</a> </a>
</p> </p>
<h1 align="center">Gitea - Git with a cup of tea</h1> <h1 align="center">Gitea - Git with a cup of tea</h1>
<p align="center"> <p align="center">
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status"> <a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main"> <img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/master">
</a> </a>
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea"> <a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
<img src="https://img.shields.io/discord/322538954119184384.svg"> <img src="https://img.shields.io/discord/322538954119184384.svg">
</a> </a>
<a href="https://codecov.io/gh/go-gitea/gitea" title="Codecov"> <a href="https://microbadger.com/images/gitea/gitea" title="Get your own image badge on microbadger.com">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg"> <img src="https://images.microbadger.com/badges/image/gitea/gitea.svg">
</a> </a>
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card"> <a href="https://codecov.io/gh/go-gitea/gitea" title="Codecov">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg">
</a>
<a href="https://godoc.org/code.gitea.io/gitea" title="Go Report Card">
<img src="https://goreportcard.com/badge/code.gitea.io/gitea"> <img src="https://goreportcard.com/badge/code.gitea.io/gitea">
</a> </a>
<a href="https://godoc.org/code.gitea.io/gitea" title="GoDoc"> <a href="https://godoc.org/code.gitea.io/gitea" title="GoDoc">
@@ -36,8 +39,8 @@
<a href="https://crowdin.com/project/gitea" title="Crowdin"> <a href="https://crowdin.com/project/gitea" title="Crowdin">
<img src="https://badges.crowdin.net/gitea/localized.svg"> <img src="https://badges.crowdin.net/gitea/localized.svg">
</a> </a>
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs"> <a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main"> <img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea">
</a> </a>
<a href="https://img.shields.io/bountysource/team/gitea" title="Bountysource"> <a href="https://img.shields.io/bountysource/team/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity"> <img src="https://img.shields.io/bountysource/team/gitea/activity">
@@ -68,11 +71,6 @@ Gitea 的首要目标是创建一个极易安装,运行非常快速,安装
Fork -> Patch -> Push -> Pull Request Fork -> Patch -> Push -> Pull Request
## 翻译
多语言翻译是基于Crowdin进行的.
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## 作者 ## 作者
* [Maintainers](https://github.com/orgs/go-gitea/people) * [Maintainers](https://github.com/orgs/go-gitea/people)
@@ -81,7 +79,7 @@ Fork -> Patch -> Push -> Pull Request
## 授权许可 ## 授权许可
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) 文件中。
## 截图 ## 截图

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640;" xml:space="preserve">
<g>
<path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8
c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4
c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/>
<g>
<g>
<path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2
c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5
c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5
c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3
c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1
C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4
c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7
S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55
c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8
l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/>
<path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4
c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1
c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9
c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3
c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3
c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29
c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8
C343.2,346.5,335,363.3,326.8,380.1z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build vendor //+build vendor
package main package main
@@ -10,6 +10,14 @@ package main
// These libraries will not be included in a normal compilation. // These libraries will not be included in a normal compilation.
import ( import (
// for lint
_ "github.com/mgechev/dots"
_ "github.com/mgechev/revive/formatter"
_ "github.com/mgechev/revive/lint"
_ "github.com/mgechev/revive/rule"
_ "github.com/mitchellh/go-homedir"
_ "github.com/pelletier/go-toml"
// for embed // for embed
_ "github.com/shurcooL/vfsgen" _ "github.com/shurcooL/vfsgen"

View File

@@ -1,284 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build ignore
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/build/codeformat"
)
// Windows has a limitation for command line arguments, the size can not exceed 32KB.
// So we have to feed the files to some tools (like gofmt/misspell) batch by batch
// We also introduce a `gitea-fmt` command, it does better import formatting than gofmt/goimports. `gitea-fmt` calls `gofmt` internally.
var optionLogVerbose bool
func logVerbose(msg string, args ...interface{}) {
if optionLogVerbose {
log.Printf(msg, args...)
}
}
func passThroughCmd(cmd string, args []string) error {
foundCmd, err := exec.LookPath(cmd)
if err != nil {
log.Fatalf("can not find cmd: %s", cmd)
}
c := exec.Cmd{
Path: foundCmd,
Args: append([]string{cmd}, args...),
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
return c.Run()
}
type fileCollector struct {
dirs []string
includePatterns []*regexp.Regexp
excludePatterns []*regexp.Regexp
batchSize int
}
func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error) {
co := &fileCollector{batchSize: batchSize}
if fileFilter == "go-own" {
co.dirs = []string{
"build",
"cmd",
"contrib",
"integrations",
"models",
"modules",
"routers",
"services",
"tools",
}
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`integrations/gitea-repositories-meta`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`integrations/migration-test`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
}
if co.dirs == nil {
return nil, fmt.Errorf("unknown file-filter: %s", fileFilter)
}
return co, nil
}
func (fc *fileCollector) matchPatterns(path string, regexps []*regexp.Regexp) bool {
path = strings.ReplaceAll(path, "\\", "/")
for _, re := range regexps {
if re.MatchString(path) {
return true
}
}
return false
}
func (fc *fileCollector) collectFiles() (res [][]string, err error) {
var batch []string
for _, dir := range fc.dirs {
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
include := len(fc.includePatterns) == 0 || fc.matchPatterns(path, fc.includePatterns)
exclude := fc.matchPatterns(path, fc.excludePatterns)
process := include && !exclude
if !process {
if d.IsDir() {
if exclude {
logVerbose("exclude dir %s", path)
return filepath.SkipDir
}
// for a directory, if it is not excluded explicitly, we should walk into
return nil
}
// for a file, we skip it if it shouldn't be processed
logVerbose("skip process %s", path)
return nil
}
if d.IsDir() {
// skip dir, we don't add dirs to the file list now
return nil
}
if len(batch) >= fc.batchSize {
res = append(res, batch)
batch = nil
}
batch = append(batch, path)
return nil
})
if err != nil {
return nil, err
}
}
res = append(res, batch)
return res, nil
}
// substArgFiles expands the {file-list} to a real file list for commands
func substArgFiles(args, files []string) []string {
for i, s := range args {
if s == "{file-list}" {
newArgs := append(args[:i], files...)
newArgs = append(newArgs, args[i+1:]...)
return newArgs
}
}
return args
}
func exitWithCmdErrors(subCmd string, subArgs []string, cmdErrors []error) {
for _, err := range cmdErrors {
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
exitCode := exitError.ExitCode()
log.Printf("run command failed (code=%d): %s %v", exitCode, subCmd, subArgs)
os.Exit(exitCode)
} else {
log.Fatalf("run command failed (err=%s) %s %v", err, subCmd, subArgs)
}
}
}
}
func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string) {
mainOptions = map[string]string{}
for i := 1; i < len(os.Args); i++ {
arg := os.Args[i]
if arg == "" {
break
}
if arg[0] == '-' {
arg = strings.TrimPrefix(arg, "-")
arg = strings.TrimPrefix(arg, "-")
fields := strings.SplitN(arg, "=", 2)
if len(fields) == 1 {
mainOptions[fields[0]] = "1"
} else {
mainOptions[fields[0]] = fields[1]
}
} else {
subCmd = arg
subArgs = os.Args[i+1:]
break
}
}
return
}
func showUsage() {
fmt.Printf(`Usage: %[1]s [options] {command} [arguments]
Options:
--verbose
--file-filter=go-own
--batch-size=100
Commands:
%[1]s gofmt ...
%[1]s misspell ...
Arguments:
{file-list} the file list
Example:
%[1]s gofmt -s -d {file-list}
`, "file-batch-exec")
}
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
fileFilter := mainOptions["file-filter"]
if fileFilter == "" {
fileFilter = "go-own"
}
batchSize, _ := strconv.Atoi(mainOptions["batch-size"])
if batchSize == 0 {
batchSize = 100
}
return newFileCollector(fileFilter, batchSize)
}
func containsString(a []string, s string) bool {
for _, v := range a {
if v == s {
return true
}
}
return false
}
func giteaFormatGoImports(files []string, hasChangedFiles, doWriteFile bool) error {
for _, file := range files {
if err := codeformat.FormatGoImports(file, hasChangedFiles, doWriteFile); err != nil {
log.Printf("failed to format go imports: %s, err=%v", file, err)
return err
}
}
return nil
}
func main() {
mainOptions, subCmd, subArgs := parseArgs()
if subCmd == "" {
showUsage()
os.Exit(1)
}
optionLogVerbose = mainOptions["verbose"] != ""
fc, err := newFileCollectorFromMainOptions(mainOptions)
if err != nil {
log.Fatalf("can not create file collector: %s", err.Error())
}
fileBatches, err := fc.collectFiles()
if err != nil {
log.Fatalf("can not collect files: %s", err.Error())
}
processed := 0
var cmdErrors []error
for _, files := range fileBatches {
if len(files) == 0 {
break
}
substArgs := substArgFiles(subArgs, files)
logVerbose("batch cmd: %s %v", subCmd, substArgs)
switch subCmd {
case "gitea-fmt":
if containsString(subArgs, "-d") {
log.Print("the -d option is not supported by gitea-fmt")
}
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-l"), containsString(subArgs, "-w")))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", "1.17"}, substArgs...)))
case "misspell":
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("MISSPELL_PACKAGE")}, substArgs...)))
default:
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
}
processed += len(files)
}
logVerbose("processed %d files", processed)
exitWithCmdErrors(subCmd, subArgs, cmdErrors)
}

View File

@@ -1,201 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package codeformat
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"sort"
"strings"
)
var importPackageGroupOrders = map[string]int{
"": 1, // internal
"code.gitea.io/gitea/": 2,
}
var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line")
var (
importBlockBegin = []byte("\nimport (\n")
importBlockEnd = []byte("\n)")
)
type importLineParsed struct {
group string
pkg string
content string
}
func parseImportLine(line string) (*importLineParsed, error) {
il := &importLineParsed{content: line}
p1 := strings.IndexRune(line, '"')
if p1 == -1 {
return nil, errors.New("invalid import line: " + line)
}
p1++
p := strings.IndexRune(line[p1:], '"')
if p == -1 {
return nil, errors.New("invalid import line: " + line)
}
p2 := p1 + p
il.pkg = line[p1:p2]
pDot := strings.IndexRune(il.pkg, '.')
pSlash := strings.IndexRune(il.pkg, '/')
if pDot != -1 && pDot < pSlash {
il.group = "domain-package"
}
for groupName := range importPackageGroupOrders {
if groupName == "" {
continue // skip internal
}
if strings.HasPrefix(il.pkg, groupName) {
il.group = groupName
}
}
return il, nil
}
type (
importLineGroup []*importLineParsed
importLineGroupMap map[string]importLineGroup
)
func formatGoImports(contentBytes []byte) ([]byte, error) {
p1 := bytes.Index(contentBytes, importBlockBegin)
if p1 == -1 {
return nil, nil
}
p1 += len(importBlockBegin)
p := bytes.Index(contentBytes[p1:], importBlockEnd)
if p == -1 {
return nil, nil
}
p2 := p1 + p
importGroups := importLineGroupMap{}
r := bytes.NewBuffer(contentBytes[p1:p2])
eof := false
for !eof {
line, err := r.ReadString('\n')
eof = err == io.EOF
if err != nil && !eof {
return nil, err
}
line = strings.TrimSpace(line)
if line != "" {
if strings.HasPrefix(line, "//") || strings.HasPrefix(line, "/*") {
return nil, errInvalidCommentBetweenImports
}
importLine, err := parseImportLine(line)
if err != nil {
return nil, err
}
importGroups[importLine.group] = append(importGroups[importLine.group], importLine)
}
}
var groupNames []string
for groupName, importLines := range importGroups {
groupNames = append(groupNames, groupName)
sort.Slice(importLines, func(i, j int) bool {
return strings.Compare(importLines[i].pkg, importLines[j].pkg) < 0
})
}
sort.Slice(groupNames, func(i, j int) bool {
n1 := groupNames[i]
n2 := groupNames[j]
o1 := importPackageGroupOrders[n1]
o2 := importPackageGroupOrders[n2]
if o1 != 0 && o2 != 0 {
return o1 < o2
}
if o1 == 0 && o2 == 0 {
return strings.Compare(n1, n2) < 0
}
return o1 != 0
})
formattedBlock := bytes.Buffer{}
for _, groupName := range groupNames {
hasNormalImports := false
hasDummyImports := false
// non-dummy import comes first
for _, importLine := range importGroups[groupName] {
if strings.HasPrefix(importLine.content, "_") {
hasDummyImports = true
} else {
formattedBlock.WriteString("\t" + importLine.content + "\n")
hasNormalImports = true
}
}
// dummy (_ "pkg") comes later
if hasDummyImports {
if hasNormalImports {
formattedBlock.WriteString("\n")
}
for _, importLine := range importGroups[groupName] {
if strings.HasPrefix(importLine.content, "_") {
formattedBlock.WriteString("\t" + importLine.content + "\n")
}
}
}
formattedBlock.WriteString("\n")
}
formattedBlockBytes := bytes.TrimRight(formattedBlock.Bytes(), "\n")
var formattedBytes []byte
formattedBytes = append(formattedBytes, contentBytes[:p1]...)
formattedBytes = append(formattedBytes, formattedBlockBytes...)
formattedBytes = append(formattedBytes, contentBytes[p2:]...)
return formattedBytes, nil
}
// FormatGoImports format the imports by our rules (see unit tests)
func FormatGoImports(file string, doChangedFiles, doWriteFile bool) error {
f, err := os.Open(file)
if err != nil {
return err
}
var contentBytes []byte
{
defer f.Close()
contentBytes, err = io.ReadAll(f)
if err != nil {
return err
}
}
formattedBytes, err := formatGoImports(contentBytes)
if err != nil {
return err
}
if formattedBytes == nil {
return nil
}
if bytes.Equal(contentBytes, formattedBytes) {
return nil
}
if doChangedFiles {
fmt.Println(file)
}
if doWriteFile {
f, err = os.OpenFile(file, os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(formattedBytes)
return err
}
return err
}

View File

@@ -1,125 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package codeformat
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFormatImportsSimple(t *testing.T) {
formatted, err := formatGoImports([]byte(`
package codeformat
import (
"github.com/stretchr/testify/assert"
"testing"
)
`))
expected := `
package codeformat
import (
"testing"
"github.com/stretchr/testify/assert"
)
`
assert.NoError(t, err)
assert.Equal(t, expected, string(formatted))
}
func TestFormatImportsGroup(t *testing.T) {
// gofmt/goimports won't group the packages, for example, they produce such code:
// "bytes"
// "image"
// (a blank line)
// "fmt"
// "image/color/palette"
// our formatter does better, and these packages are grouped into one.
formatted, err := formatGoImports([]byte(`
package test
import (
"bytes"
"fmt"
"image"
"image/color"
_ "image/gif" // for processing gif images
_ "image/jpeg" // for processing jpeg images
_ "image/png" // for processing png images
"code.gitea.io/other/package"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"xorm.io/the/package"
"github.com/issue9/identicon"
"github.com/nfnt/resize"
"github.com/oliamb/cutter"
)
`))
expected := `
package test
import (
"bytes"
"fmt"
"image"
"image/color"
_ "image/gif" // for processing gif images
_ "image/jpeg" // for processing jpeg images
_ "image/png" // for processing png images
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/other/package"
"github.com/issue9/identicon"
"github.com/nfnt/resize"
"github.com/oliamb/cutter"
"xorm.io/the/package"
)
`
assert.NoError(t, err)
assert.Equal(t, expected, string(formatted))
}
func TestFormatImportsInvalidComment(t *testing.T) {
// why we shouldn't write comments between imports: it breaks the grouping of imports
// for example:
// "pkg1"
// "pkg2"
// // a comment
// "pkgA"
// "pkgB"
// the comment splits the packages into two groups, pkg1/2 are sorted separately, pkgA/B are sorted separately
// we don't want such code, so the code should be:
// "pkg1"
// "pkg2"
// "pkgA" // a comment
// "pkgB"
_, err := formatGoImports([]byte(`
package test
import (
"image/jpeg"
// for processing gif images
"image/gif"
)
`))
assert.ErrorIs(t, err, errInvalidCommentBetweenImports)
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build ignore // +build ignore
package main package main
@@ -10,6 +10,7 @@ import (
"bytes" "bytes"
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@@ -19,14 +20,14 @@ import (
"github.com/shurcooL/vfsgen" "github.com/shurcooL/vfsgen"
) )
func needsUpdate(dir, filename string) (bool, []byte) { func needsUpdate(dir string, filename string) (bool, []byte) {
needRegen := false needRegen := false
_, err := os.Stat(filename) _, err := os.Stat(filename)
if err != nil { if err != nil {
needRegen = true needRegen = true
} }
oldHash, err := os.ReadFile(filename + ".hash") oldHash, err := ioutil.ReadFile(filename + ".hash")
if err != nil { if err != nil {
oldHash = []byte{} oldHash = []byte{}
} }
@@ -49,6 +50,7 @@ func needsUpdate(dir, filename string) (bool, []byte) {
newHash := hasher.Sum([]byte{}) newHash := hasher.Sum([]byte{})
if bytes.Compare(oldHash, newHash) != 0 { if bytes.Compare(oldHash, newHash) != 0 {
return true, newHash return true, newHash
} }
@@ -56,15 +58,11 @@ func needsUpdate(dir, filename string) (bool, []byte) {
} }
func main() { func main() {
if len(os.Args) < 4 { if len(os.Args) != 4 {
log.Fatal("Insufficient number of arguments. Need: directory packageName filename") log.Fatal("Insufficient number of arguments. Need: directory packageName filename")
} }
dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3] dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3]
var useGlobalModTime bool
if len(os.Args) == 5 {
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
}
update, newHash := needsUpdate(dir, filename) update, newHash := needsUpdate(dir, filename)
@@ -76,14 +74,13 @@ func main() {
fmt.Printf("generating bindata for %s\n", packageName) fmt.Printf("generating bindata for %s\n", packageName)
var fsTemplates http.FileSystem = http.Dir(dir) var fsTemplates http.FileSystem = http.Dir(dir)
err := vfsgen.Generate(fsTemplates, vfsgen.Options{ err := vfsgen.Generate(fsTemplates, vfsgen.Options{
PackageName: packageName, PackageName: packageName,
BuildTags: "bindata", BuildTags: "bindata",
VariableName: "Assets", VariableName: "Assets",
Filename: filename, Filename: filename,
UseGlobalModTime: useGlobalModTime,
}) })
if err != nil { if err != nil {
log.Fatalf("%v\n", err) log.Fatalf("%v\n", err)
} }
_ = os.WriteFile(filename+".hash", newHash, 0o666) _ = ioutil.WriteFile(filename+".hash", newHash, 0666)
} }

View File

@@ -3,7 +3,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build ignore // +build ignore
package main package main
@@ -11,17 +11,16 @@ import (
"flag" "flag"
"fmt" "fmt"
"go/format" "go/format"
"io" "io/ioutil"
"log" "log"
"net/http" "net/http"
"os"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/modules/json" jsoniter "github.com/json-iterator/go"
) )
const ( const (
@@ -29,7 +28,9 @@ const (
maxUnicodeVersion = 12 maxUnicodeVersion = 12
) )
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") var (
flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
)
// Gemoji is a set of emoji data. // Gemoji is a set of emoji data.
type Gemoji []Emoji type Gemoji []Emoji
@@ -50,6 +51,7 @@ func (e Emoji) MarshalJSON() ([]byte, error) {
x.UnicodeVersion = "" x.UnicodeVersion = ""
x.Description = "" x.Description = ""
x.SkinTones = false x.SkinTones = false
json := jsoniter.ConfigCompatibleWithStandardLibrary
return json.Marshal(x) return json.Marshal(x)
} }
@@ -65,7 +67,7 @@ func main() {
} }
// write // write
err = os.WriteFile(*flagOut, buf, 0o644) err = ioutil.WriteFile(*flagOut, buf, 0644)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -94,19 +96,20 @@ func generate() ([]byte, error) {
defer res.Body.Close() defer res.Body.Close()
// read all // read all
body, err := io.ReadAll(res.Body) body, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// unmarshal // unmarshal
var data Gemoji var data Gemoji
json := jsoniter.ConfigCompatibleWithStandardLibrary
err = json.Unmarshal(body, &data) err = json.Unmarshal(body, &data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
skinTones := make(map[string]string) var skinTones = make(map[string]string)
skinTones["\U0001f3fb"] = "Light Skin Tone" skinTones["\U0001f3fb"] = "Light Skin Tone"
skinTones["\U0001f3fc"] = "Medium-Light Skin Tone" skinTones["\U0001f3fc"] = "Medium-Light Skin Tone"
@@ -116,7 +119,7 @@ func generate() ([]byte, error) {
var tmp Gemoji var tmp Gemoji
// filter out emoji that require greater than max unicode version //filter out emoji that require greater than max unicode version
for i := range data { for i := range data {
val, _ := strconv.ParseFloat(data[i].UnicodeVersion, 64) val, _ := strconv.ParseFloat(data[i].UnicodeVersion, 64)
if int(val) <= maxUnicodeVersion { if int(val) <= maxUnicodeVersion {
@@ -155,7 +158,7 @@ func generate() ([]byte, error) {
// write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet) // write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet)
file, _ := json.Marshal(data) file, _ := json.Marshal(data)
_ = os.WriteFile("assets/emoji.json", file, 0o644) _ = ioutil.WriteFile("assets/emoji.json", file, 0644)
// Add skin tones to emoji that support it // Add skin tones to emoji that support it
var ( var (

View File

@@ -1,4 +1,4 @@
//go:build ignore // +build ignore
package main package main
@@ -8,6 +8,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@@ -32,7 +33,8 @@ func main() {
flag.StringVar(&githubApiToken, "token", "", "github api token") flag.StringVar(&githubApiToken, "token", "", "github api token")
flag.Parse() flag.Parse()
file, err := os.CreateTemp(os.TempDir(), prefix) file, err := ioutil.TempFile(os.TempDir(), prefix)
if err != nil { if err != nil {
log.Fatalf("Failed to create temp file. %s", err) log.Fatalf("Failed to create temp file. %s", err)
} }
@@ -63,6 +65,7 @@ func main() {
} }
gz, err := gzip.NewReader(file) gz, err := gzip.NewReader(file)
if err != nil { if err != nil {
log.Fatalf("Failed to gunzip the archive. %s", err) log.Fatalf("Failed to gunzip the archive. %s", err)
} }
@@ -93,6 +96,7 @@ func main() {
} }
out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".gitignore"))) out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".gitignore")))
if err != nil { if err != nil {
log.Fatalf("Failed to create new file. %s", err) log.Fatalf("Failed to create new file. %s", err)
} }
@@ -109,13 +113,13 @@ func main() {
for dst, src := range filesToCopy { for dst, src := range filesToCopy {
// Read all content of src to data // Read all content of src to data
src = path.Join(destination, src) src = path.Join(destination, src)
data, err := os.ReadFile(src) data, err := ioutil.ReadFile(src)
if err != nil { if err != nil {
log.Fatalf("Failed to read src file. %s", err) log.Fatalf("Failed to read src file. %s", err)
} }
// Write data to dst // Write data to dst
dst = path.Join(destination, dst) dst = path.Join(destination, dst)
err = os.WriteFile(dst, data, 0o644) err = ioutil.WriteFile(dst, data, 0644)
if err != nil { if err != nil {
log.Fatalf("Failed to write new file. %s", err) log.Fatalf("Failed to write new file. %s", err)
} }

View File

@@ -1,8 +1,13 @@
#!/usr/bin/env node #!/usr/bin/env node
import imageminZopfli from 'imagemin-zopfli'; 'use strict';
import {optimize} from 'svgo';
import {fabric} from 'fabric'; const imageminZopfli = require('imagemin-zopfli');
import {readFile, writeFile} from 'fs/promises'; const Svgo = require('svgo');
const {fabric} = require('fabric');
const {readFile, writeFile} = require('fs').promises;
const {resolve} = require('path');
const logoFile = resolve(__dirname, '../assets/logo.svg');
function exit(err) { function exit(err) {
if (err) console.error(err); if (err) console.error(err);
@@ -17,20 +22,16 @@ function loadSvg(svg) {
}); });
} }
async function generate(svg, path, {size, bg}) { async function generate(svg, outputFile, {size, bg}) {
const outputFile = new URL(path, import.meta.url); if (outputFile.endsWith('.svg')) {
const svgo = new Svgo({
if (String(outputFile).endsWith('.svg')) {
const {data} = optimize(svg, {
plugins: [ plugins: [
'preset-default', {removeDimensions: true},
'removeDimensions', {addAttributesToSVGElement: {attributes: [{width: size}, {height: size}]}},
{
name: 'addAttributesToSVGElement',
params: {attributes: [{width: size}, {height: size}]}
},
], ],
}); });
const {data} = await svgo.optimize(svg);
await writeFile(outputFile, data); await writeFile(outputFile, data);
return; return;
} }
@@ -65,18 +66,17 @@ async function generate(svg, path, {size, bg}) {
async function main() { async function main() {
const gitea = process.argv.slice(2).includes('gitea'); const gitea = process.argv.slice(2).includes('gitea');
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8'); const svg = await readFile(logoFile, 'utf8');
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
await Promise.all([ await Promise.all([
generate(logoSvg, '../public/img/logo.svg', {size: 32}), generate(svg, resolve(__dirname, '../public/img/logo.svg'), {size: 32}),
generate(logoSvg, '../public/img/logo.png', {size: 512}), generate(svg, resolve(__dirname, '../public/img/logo.png'), {size: 512}),
generate(faviconSvg, '../public/img/favicon.svg', {size: 32}), generate(svg, resolve(__dirname, '../public/img/favicon.png'), {size: 180}),
generate(faviconSvg, '../public/img/favicon.png', {size: 180}), generate(svg, resolve(__dirname, '../public/img/avatar_default.png'), {size: 200}),
generate(logoSvg, '../public/img/avatar_default.png', {size: 200}), generate(svg, resolve(__dirname, '../public/img/apple-touch-icon.png'), {size: 180, bg: true}),
generate(logoSvg, '../public/img/apple-touch-icon.png', {size: 180, bg: true}), gitea && generate(svg, resolve(__dirname, '../public/img/gitea.svg'), {size: 32}),
gitea && generate(logoSvg, '../public/img/gitea.svg', {size: 32}),
]); ]);
} }
main().then(exit).catch(exit); main().then(exit).catch(exit);

View File

@@ -1,4 +1,4 @@
//go:build ignore // +build ignore
package main package main
@@ -8,6 +8,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@@ -32,7 +33,8 @@ func main() {
flag.StringVar(&githubApiToken, "token", "", "github api token") flag.StringVar(&githubApiToken, "token", "", "github api token")
flag.Parse() flag.Parse()
file, err := os.CreateTemp(os.TempDir(), prefix) file, err := ioutil.TempFile(os.TempDir(), prefix)
if err != nil { if err != nil {
log.Fatalf("Failed to create temp file. %s", err) log.Fatalf("Failed to create temp file. %s", err)
} }
@@ -64,6 +66,7 @@ func main() {
} }
gz, err := gzip.NewReader(file) gz, err := gzip.NewReader(file)
if err != nil { if err != nil {
log.Fatalf("Failed to gunzip the archive. %s", err) log.Fatalf("Failed to gunzip the archive. %s", err)
} }
@@ -97,6 +100,7 @@ func main() {
continue continue
} }
out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt"))) out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt")))
if err != nil { if err != nil {
log.Fatalf("Failed to create new file. %s", err) log.Fatalf("Failed to create new file. %s", err)
} }

View File

@@ -1,14 +1,13 @@
#!/usr/bin/env node #!/usr/bin/env node
import fastGlob from 'fast-glob'; 'use strict';
import {optimize} from 'svgo';
import {parse} from 'path';
import {readFile, writeFile, mkdir} from 'fs/promises';
import {fileURLToPath} from 'url';
const glob = (pattern) => fastGlob.sync(pattern, { const fastGlob = require('fast-glob');
cwd: fileURLToPath(new URL('..', import.meta.url)), const Svgo = require('svgo');
absolute: true, const {resolve, parse} = require('path');
}); const {readFile, writeFile, mkdir} = require('fs').promises;
const glob = (pattern) => fastGlob.sync(pattern, {cwd: resolve(__dirname), absolute: true});
const outputDir = resolve(__dirname, '../public/img/svg');
function exit(err) { function exit(err) {
if (err) console.error(err); if (err) console.error(err);
@@ -17,6 +16,7 @@ function exit(err) {
async function processFile(file, {prefix, fullName} = {}) { async function processFile(file, {prefix, fullName} = {}) {
let name; let name;
if (fullName) { if (fullName) {
name = fullName; name = fullName;
} else { } else {
@@ -25,18 +25,32 @@ async function processFile(file, {prefix, fullName} = {}) {
if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons
} }
const {data} = optimize(await readFile(file, 'utf8'), { const svgo = new Svgo({
plugins: [ plugins: [
{name: 'preset-default'}, {removeXMLNS: true},
{name: 'removeXMLNS'}, {removeDimensions: true},
{name: 'removeDimensions'}, {
{name: 'prefixIds', params: {prefix: () => name}}, addClassesToSVGElement: {
{name: 'addClassesToSVGElement', params: {classNames: ['svg', name]}}, classNames: [
{name: 'addAttributesToSVGElement', params: {attributes: [{'width': '16'}, {'height': '16'}, {'aria-hidden': 'true'}]}}, 'svg',
name,
],
},
},
{
addAttributesToSVGElement: {
attributes: [
{'width': '16'},
{'height': '16'},
{'aria-hidden': 'true'},
],
},
},
], ],
}); });
await writeFile(fileURLToPath(new URL(`../public/img/svg/${name}.svg`, import.meta.url)), data); const {data} = await svgo.optimize(await readFile(file, 'utf8'));
await writeFile(resolve(outputDir, `${name}.svg`), data);
} }
function processFiles(pattern, opts) { function processFiles(pattern, opts) {
@@ -45,14 +59,15 @@ function processFiles(pattern, opts) {
async function main() { async function main() {
try { try {
await mkdir(fileURLToPath(new URL('../public/img/svg', import.meta.url)), {recursive: true}); await mkdir(outputDir);
} catch {} } catch {}
await Promise.all([ await Promise.all([
...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}), ...processFiles('../node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
...processFiles('web_src/svg/*.svg'), ...processFiles('../web_src/svg/*.svg'),
...processFiles('public/img/gitea.svg', {fullName: 'gitea-gitea'}), ...processFiles('../public/img/gitea.svg', {fullName: 'gitea-gitea'}),
]); ]);
} }
main().then(exit).catch(exit); main().then(exit).catch(exit);

View File

@@ -1,26 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build ignore
package main
import (
"log"
"os"
"code.gitea.io/gitea/build/codeformat"
)
func main() {
if len(os.Args) <= 1 {
log.Fatalf("Usage: gitea-format-imports [files...]")
}
for _, file := range os.Args[1:] {
if err := codeformat.FormatGoImports(file); err != nil {
log.Fatalf("can not format file %s, err=%v", file, err)
}
}
}

View File

@@ -6,7 +6,7 @@
// gocovmerge takes the results from multiple `go test -coverprofile` runs and // gocovmerge takes the results from multiple `go test -coverprofile` runs and
// merges them into one profile // merges them into one profile
//go:build ignore // +build ignore
package main package main
@@ -21,7 +21,7 @@ import (
"golang.org/x/tools/cover" "golang.org/x/tools/cover"
) )
func mergeProfiles(p, merge *cover.Profile) { func mergeProfiles(p *cover.Profile, merge *cover.Profile) {
if p.Mode != merge.Mode { if p.Mode != merge.Mode {
log.Fatalf("cannot merge profiles with different modes") log.Fatalf("cannot merge profiles with different modes")
} }
@@ -108,7 +108,7 @@ func main() {
for _, file := range flag.Args() { for _, file := range flag.Args() {
profiles, err := cover.ParseProfiles(file) profiles, err := cover.ParseProfiles(file)
if err != nil { if err != nil {
log.Fatalf("failed to parse profile '%s': %v", file, err) log.Fatalf("failed to parse profiles: %v", err)
} }
for _, p := range profiles { for _, p := range profiles {
merged = addProfile(merged, p) merged = addProfile(merged, p)

325
build/lint.go Normal file
View File

@@ -0,0 +1,325 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright (c) 2018 Minko Gechev. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// +build ignore
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/mgechev/dots"
"github.com/mgechev/revive/formatter"
"github.com/mgechev/revive/lint"
"github.com/mgechev/revive/rule"
"github.com/mitchellh/go-homedir"
"github.com/pelletier/go-toml"
)
func fail(err string) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var defaultRules = []lint.Rule{
&rule.VarDeclarationsRule{},
&rule.PackageCommentsRule{},
&rule.DotImportsRule{},
&rule.BlankImportsRule{},
&rule.ExportedRule{},
&rule.VarNamingRule{},
&rule.IndentErrorFlowRule{},
&rule.IfReturnRule{},
&rule.RangeRule{},
&rule.ErrorfRule{},
&rule.ErrorNamingRule{},
&rule.ErrorStringsRule{},
&rule.ReceiverNamingRule{},
&rule.IncrementDecrementRule{},
&rule.ErrorReturnRule{},
&rule.UnexportedReturnRule{},
&rule.TimeNamingRule{},
&rule.ContextKeysType{},
&rule.ContextAsArgumentRule{},
}
var allRules = append([]lint.Rule{
&rule.ArgumentsLimitRule{},
&rule.CyclomaticRule{},
&rule.FileHeaderRule{},
&rule.EmptyBlockRule{},
&rule.SuperfluousElseRule{},
&rule.ConfusingNamingRule{},
&rule.GetReturnRule{},
&rule.ModifiesParamRule{},
&rule.ConfusingResultsRule{},
&rule.DeepExitRule{},
&rule.UnusedParamRule{},
&rule.UnreachableCodeRule{},
&rule.AddConstantRule{},
&rule.FlagParamRule{},
&rule.UnnecessaryStmtRule{},
&rule.StructTagRule{},
&rule.ModifiesValRecRule{},
&rule.ConstantLogicalExprRule{},
&rule.BoolLiteralRule{},
&rule.RedefinesBuiltinIDRule{},
&rule.ImportsBlacklistRule{},
&rule.FunctionResultsLimitRule{},
&rule.MaxPublicStructsRule{},
&rule.RangeValInClosureRule{},
&rule.RangeValAddress{},
&rule.WaitGroupByValueRule{},
&rule.AtomicRule{},
&rule.EmptyLinesRule{},
&rule.LineLengthLimitRule{},
&rule.CallToGCRule{},
&rule.DuplicatedImportsRule{},
&rule.ImportShadowingRule{},
&rule.BareReturnRule{},
&rule.UnusedReceiverRule{},
&rule.UnhandledErrorRule{},
&rule.CognitiveComplexityRule{},
&rule.StringOfIntRule{},
}, defaultRules...)
var allFormatters = []lint.Formatter{
&formatter.Stylish{},
&formatter.Friendly{},
&formatter.JSON{},
&formatter.NDJSON{},
&formatter.Default{},
&formatter.Unix{},
&formatter.Checkstyle{},
&formatter.Plain{},
}
func getFormatters() map[string]lint.Formatter {
result := map[string]lint.Formatter{}
for _, f := range allFormatters {
result[f.Name()] = f
}
return result
}
func getLintingRules(config *lint.Config) []lint.Rule {
rulesMap := map[string]lint.Rule{}
for _, r := range allRules {
rulesMap[r.Name()] = r
}
lintingRules := []lint.Rule{}
for name := range config.Rules {
rule, ok := rulesMap[name]
if !ok {
fail("cannot find rule: " + name)
}
lintingRules = append(lintingRules, rule)
}
return lintingRules
}
func parseConfig(path string) *lint.Config {
config := &lint.Config{}
file, err := ioutil.ReadFile(path)
if err != nil {
fail("cannot read the config file")
}
err = toml.Unmarshal(file, config)
if err != nil {
fail("cannot parse the config file: " + err.Error())
}
return config
}
func normalizeConfig(config *lint.Config) {
if config.Confidence == 0 {
config.Confidence = 0.8
}
severity := config.Severity
if severity != "" {
for k, v := range config.Rules {
if v.Severity == "" {
v.Severity = severity
}
config.Rules[k] = v
}
for k, v := range config.Directives {
if v.Severity == "" {
v.Severity = severity
}
config.Directives[k] = v
}
}
}
func getConfig() *lint.Config {
config := defaultConfig()
if configPath != "" {
config = parseConfig(configPath)
}
normalizeConfig(config)
return config
}
func getFormatter() lint.Formatter {
formatters := getFormatters()
formatter := formatters["default"]
if formatterName != "" {
f, ok := formatters[formatterName]
if !ok {
fail("unknown formatter " + formatterName)
}
formatter = f
}
return formatter
}
func buildDefaultConfigPath() string {
var result string
if homeDir, err := homedir.Dir(); err == nil {
result = filepath.Join(homeDir, "revive.toml")
if _, err := os.Stat(result); err != nil {
result = ""
}
}
return result
}
func defaultConfig() *lint.Config {
defaultConfig := lint.Config{
Confidence: 0.0,
Severity: lint.SeverityWarning,
Rules: map[string]lint.RuleConfig{},
}
for _, r := range defaultRules {
defaultConfig.Rules[r.Name()] = lint.RuleConfig{}
}
return &defaultConfig
}
func normalizeSplit(strs []string) []string {
res := []string{}
for _, s := range strs {
t := strings.Trim(s, " \t")
if len(t) > 0 {
res = append(res, t)
}
}
return res
}
func getPackages() [][]string {
globs := normalizeSplit(flag.Args())
if len(globs) == 0 {
globs = append(globs, ".")
}
packages, err := dots.ResolvePackages(globs, normalizeSplit(excludePaths))
if err != nil {
fail(err.Error())
}
return packages
}
type arrayFlags []string
func (i *arrayFlags) String() string {
return strings.Join([]string(*i), " ")
}
func (i *arrayFlags) Set(value string) error {
*i = append(*i, value)
return nil
}
var configPath string
var excludePaths arrayFlags
var formatterName string
var help bool
var originalUsage = flag.Usage
func init() {
flag.Usage = func() {
originalUsage()
}
// command line help strings
const (
configUsage = "path to the configuration TOML file, defaults to $HOME/revive.toml, if present (i.e. -config myconf.toml)"
excludeUsage = "list of globs which specify files to be excluded (i.e. -exclude foo/...)"
formatterUsage = "formatter to be used for the output (i.e. -formatter stylish)"
)
defaultConfigPath := buildDefaultConfigPath()
flag.StringVar(&configPath, "config", defaultConfigPath, configUsage)
flag.Var(&excludePaths, "exclude", excludeUsage)
flag.StringVar(&formatterName, "formatter", "", formatterUsage)
flag.Parse()
}
func main() {
config := getConfig()
formatter := getFormatter()
packages := getPackages()
revive := lint.New(func(file string) ([]byte, error) {
return ioutil.ReadFile(file)
})
lintingRules := getLintingRules(config)
failures, err := revive.Lint(packages, lintingRules, *config)
if err != nil {
fail(err.Error())
}
formatChan := make(chan lint.Failure)
exitChan := make(chan bool)
var output string
go (func() {
output, err = formatter.Format(formatChan, *config)
if err != nil {
fail(err.Error())
}
exitChan <- true
})()
exitCode := 0
for f := range failures {
if f.Confidence < config.Confidence {
continue
}
if exitCode == 0 {
exitCode = config.WarningCode
}
if c, ok := config.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
exitCode = config.ErrorCode
}
if c, ok := config.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
exitCode = config.ErrorCode
}
formatChan <- f
}
close(formatChan)
<-exitChan
if output != "" {
fmt.Println(output)
}
os.Exit(exitCode)
}

View File

@@ -1,24 +0,0 @@
#!/bin/sh
set -e
if [ ! -f ./build/test-env-check.sh ]; then
echo "${0} can only be executed in gitea source root directory"
exit 1
fi
echo "check uid ..."
# the uid of gitea defined in "https://gitea.com/gitea/test-env" is 1000
gitea_uid=$(id -u gitea)
if [ "$gitea_uid" != "1000" ]; then
echo "The uid of linux user 'gitea' is expected to be 1000, but it is $gitea_uid"
exit 1
fi
cur_uid=$(id -u)
if [ "$cur_uid" != "0" -a "$cur_uid" != "$gitea_uid" ]; then
echo "The uid of current linux user is expected to be 0 or $gitea_uid, but it is $cur_uid"
exit 1
fi

View File

@@ -1,11 +0,0 @@
#!/bin/sh
set -e
if [ ! -f ./build/test-env-prepare.sh ]; then
echo "${0} can only be executed in gitea source root directory"
exit 1
fi
echo "change the owner of files to gitea ..."
chown -R gitea:gitea .

View File

@@ -14,24 +14,13 @@ import (
"text/tabwriter" "text/tabwriter"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
pwd "code.gitea.io/gitea/modules/password" pwd "code.gitea.io/gitea/modules/password"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/smtp"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -58,7 +47,6 @@ var (
microcmdUserList, microcmdUserList,
microcmdUserChangePassword, microcmdUserChangePassword,
microcmdUserDelete, microcmdUserDelete,
microcmdUserGenerateAccessToken,
}, },
} }
@@ -116,10 +104,6 @@ var (
Name: "access-token", Name: "access-token",
Usage: "Generate access token for the user", Usage: "Generate access token for the user",
}, },
cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},
}, },
} }
@@ -161,27 +145,6 @@ var (
Action: runDeleteUser, Action: runDeleteUser,
} }
microcmdUserGenerateAccessToken = cli.Command{
Name: "generate-access-token",
Usage: "Generate a access token for a specific user",
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Usage: "Username",
},
cli.StringFlag{
Name: "token-name,t",
Usage: "Token name",
Value: "gitea-admin",
},
cli.BoolFlag{
Name: "raw",
Usage: "Display only the token value",
},
},
Action: runGenerateAccessToken,
}
subcmdRepoSyncReleases = cli.Command{ subcmdRepoSyncReleases = cli.Command{
Name: "repo-sync-releases", Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags", Usage: "Synchronize repository releases with tags",
@@ -219,8 +182,6 @@ var (
cmdAuthUpdateLdapBindDn, cmdAuthUpdateLdapBindDn,
cmdAuthAddLdapSimpleAuth, cmdAuthAddLdapSimpleAuth,
cmdAuthUpdateLdapSimpleAuth, cmdAuthUpdateLdapSimpleAuth,
microcmdAuthAddSMTP,
microcmdAuthUpdateSMTP,
microcmdAuthList, microcmdAuthList,
microcmdAuthDelete, microcmdAuthDelete,
}, },
@@ -326,40 +287,6 @@ var (
Value: "", Value: "",
Usage: "Custom icon URL for OAuth2 login source", Usage: "Custom icon URL for OAuth2 login source",
}, },
cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
cli.StringSliceFlag{
Name: "scopes",
Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source",
},
cli.StringFlag{
Name: "required-claim-name",
Value: "",
Usage: "Claim name that has to be set to allow users to login with this source",
},
cli.StringFlag{
Name: "required-claim-value",
Value: "",
Usage: "Claim value that has to be set to allow users to login with this source",
},
cli.StringFlag{
Name: "group-claim-name",
Value: "",
Usage: "Claim name providing group names for this source",
},
cli.StringFlag{
Name: "admin-group",
Value: "",
Usage: "Group Claim value for administrator users",
},
cli.StringFlag{
Name: "restricted-group",
Value: "",
Usage: "Group Claim value for restricted users",
},
} }
microcmdAuthUpdateOauth = cli.Command{ microcmdAuthUpdateOauth = cli.Command{
@@ -397,72 +324,6 @@ var (
}, },
}, },
} }
smtpCLIFlags = []cli.Flag{
cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
cli.StringFlag{
Name: "auth-type",
Value: "PLAIN",
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
},
cli.StringFlag{
Name: "host",
Value: "",
Usage: "SMTP Host",
},
cli.IntFlag{
Name: "port",
Usage: "SMTP Port",
},
cli.BoolTFlag{
Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
},
cli.BoolTFlag{
Name: "skip-verify",
Usage: "Skip TLS verify.",
},
cli.StringFlag{
Name: "helo-hostname",
Value: "",
Usage: "Hostname sent with HELO. Leave blank to send current hostname",
},
cli.BoolTFlag{
Name: "disable-helo",
Usage: "Disable SMTP helo.",
},
cli.StringFlag{
Name: "allowed-domains",
Value: "",
Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
},
cli.BoolTFlag{
Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.",
},
cli.BoolTFlag{
Name: "active",
Usage: "This Authentication Source is Activated.",
},
}
microcmdAuthAddSMTP = cli.Command{
Name: "add-smtp",
Usage: "Add new SMTP authentication source",
Action: runAddSMTP,
Flags: smtpCLIFlags,
}
microcmdAuthUpdateSMTP = cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP,
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
}
) )
func runChangePassword(c *cli.Context) error { func runChangePassword(c *cli.Context) error {
@@ -470,16 +331,9 @@ func runChangePassword(c *cli.Context) error {
return err return err
} }
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
if len(c.String("password")) < setting.MinPasswordLength {
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
}
if !pwd.IsComplexEnough(c.String("password")) { if !pwd.IsComplexEnough(c.String("password")) {
return errors.New("Password does not meet complexity requirements") return errors.New("Password does not meet complexity requirements")
} }
@@ -491,7 +345,7 @@ func runChangePassword(c *cli.Context) error {
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords") return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
} }
uname := c.String("username") uname := c.String("username")
user, err := user_model.GetUserByName(ctx, uname) user, err := models.GetUserByName(uname)
if err != nil { if err != nil {
return err return err
} }
@@ -499,7 +353,7 @@ func runChangePassword(c *cli.Context) error {
return err return err
} }
if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil { if err = models.UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil {
return err return err
} }
@@ -531,10 +385,7 @@ func runCreateUser(c *cli.Context) error {
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n") fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
} }
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
@@ -553,11 +404,11 @@ func runCreateUser(c *cli.Context) error {
} }
// always default to true // always default to true
changePassword := true var changePassword = true
// If this is the first user being created. // If this is the first user being created.
// Take it as the admin and don't force a password update. // Take it as the admin and don't force a password update.
if n := user_model.CountUsers(nil); n == 0 { if n := models.CountUsers(); n == 0 {
changePassword = false changePassword = false
} }
@@ -565,26 +416,17 @@ func runCreateUser(c *cli.Context) error {
changePassword = c.Bool("must-change-password") changePassword = c.Bool("must-change-password")
} }
restricted := util.OptionalBoolNone u := &models.User{
if c.IsSet("restricted") {
restricted = util.OptionalBoolOf(c.Bool("restricted"))
}
u := &user_model.User{
Name: username, Name: username,
Email: c.String("email"), Email: c.String("email"),
Passwd: password, Passwd: password,
IsActive: true,
IsAdmin: c.Bool("admin"), IsAdmin: c.Bool("admin"),
MustChangePassword: changePassword, MustChangePassword: changePassword,
Theme: setting.UI.DefaultTheme,
} }
overwriteDefault := &user_model.CreateUserOverwriteOptions{ if err := models.CreateUser(u); err != nil {
IsActive: util.OptionalBoolTrue,
IsRestricted: restricted,
}
if err := user_model.CreateUser(u, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %v", err) return fmt.Errorf("CreateUser: %v", err)
} }
@@ -606,14 +448,12 @@ func runCreateUser(c *cli.Context) error {
} }
func runListUsers(c *cli.Context) error { func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
users, err := user_model.GetAllUsers() users, err := models.GetAllUsers()
if err != nil { if err != nil {
return err return err
} }
@@ -637,6 +477,7 @@ func runListUsers(c *cli.Context) error {
w.Flush() w.Flush()
return nil return nil
} }
func runDeleteUser(c *cli.Context) error { func runDeleteUser(c *cli.Context) error {
@@ -644,25 +485,18 @@ func runDeleteUser(c *cli.Context) error {
return fmt.Errorf("You must provide the id, username or email of a user to delete") return fmt.Errorf("You must provide the id, username or email of a user to delete")
} }
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if err := storage.Init(); err != nil {
return err return err
} }
var err error var err error
var user *user_model.User var user *models.User
if c.IsSet("email") { if c.IsSet("email") {
user, err = user_model.GetUserByEmail(c.String("email")) user, err = models.GetUserByEmail(c.String("email"))
} else if c.IsSet("username") { } else if c.IsSet("username") {
user, err = user_model.GetUserByName(ctx, c.String("username")) user, err = models.GetUserByName(c.String("username"))
} else { } else {
user, err = user_model.GetUserByID(c.Int64("id")) user, err = models.GetUserByID(c.Int64("id"))
} }
if err != nil { if err != nil {
return err return err
@@ -675,57 +509,19 @@ func runDeleteUser(c *cli.Context) error {
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id")) return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
} }
return user_service.DeleteUser(user) return models.DeleteUser(user)
} }
func runGenerateAccessToken(c *cli.Context) error { func runRepoSyncReleases(c *cli.Context) error {
if !c.IsSet("username") { if err := initDB(); err != nil {
return fmt.Errorf("You must provide the username to generate a token for them")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
t := &models.AccessToken{
Name: c.String("token-name"),
UID: user.ID,
}
if err := models.NewAccessToken(t); err != nil {
return err
}
if c.Bool("raw") {
fmt.Printf("%s\n", t.Token)
} else {
fmt.Printf("Access token was successfully created: %s\n", t.Token)
}
return nil
}
func runRepoSyncReleases(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
log.Trace("Synchronizing repository releases (this may take a while)") log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ { for page := 1; ; page++ {
repos, count, err := repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
ListOptions: db.ListOptions{ ListOptions: models.ListOptions{
PageSize: repo_model.RepositoryListDefaultPageSize, PageSize: models.RepositoryListDefaultPageSize,
Page: page, Page: page,
}, },
Private: true, Private: true,
@@ -739,7 +535,7 @@ func runRepoSyncReleases(_ *cli.Context) error {
log.Trace("Processing next %d repos of %d", len(repos), count) log.Trace("Processing next %d repos of %d", len(repos), count)
for _, repo := range repos { for _, repo := range repos {
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath()) log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) gitRepo, err := git.OpenRepository(repo.RepoPath())
if err != nil { if err != nil {
log.Warn("OpenRepository: %v", err) log.Warn("OpenRepository: %v", err)
continue continue
@@ -782,27 +578,21 @@ func getReleaseCount(id int64) (int64, error) {
) )
} }
func runRegenerateHooks(_ *cli.Context) error { func runRegenerateHooks(c *cli.Context) error {
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) return repo_module.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
} }
func runRegenerateKeys(_ *cli.Context) error { func runRegenerateKeys(c *cli.Context) error {
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
return asymkey_model.RewriteAllPublicKeys() return models.RewriteAllPublicKeys()
} }
func parseOAuth2Config(c *cli.Context) *oauth2.Source { func parseOAuth2Config(c *cli.Context) *models.OAuth2Config {
var customURLMapping *oauth2.CustomURLMapping var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") { if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{ customURLMapping = &oauth2.CustomURLMapping{
@@ -814,36 +604,26 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
} else { } else {
customURLMapping = nil customURLMapping = nil
} }
return &oauth2.Source{ return &models.OAuth2Config{
Provider: c.String("provider"), Provider: c.String("provider"),
ClientID: c.String("key"), ClientID: c.String("key"),
ClientSecret: c.String("secret"), ClientSecret: c.String("secret"),
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"), OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping, CustomURLMapping: customURLMapping,
IconURL: c.String("icon-url"), IconURL: c.String("icon-url"),
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
Scopes: c.StringSlice("scopes"),
RequiredClaimName: c.String("required-claim-name"),
RequiredClaimValue: c.String("required-claim-value"),
GroupClaimName: c.String("group-claim-name"),
AdminGroup: c.String("admin-group"),
RestrictedGroup: c.String("restricted-group"),
} }
} }
func runAddOauth(c *cli.Context) error { func runAddOauth(c *cli.Context) error {
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
return auth.CreateSource(&auth.Source{ return models.CreateLoginSource(&models.LoginSource{
Type: auth.OAuth2, Type: models.LoginOAuth2,
Name: c.String("name"), Name: c.String("name"),
IsActive: true, IsActived: true,
Cfg: parseOAuth2Config(c), Cfg: parseOAuth2Config(c),
}) })
} }
@@ -852,19 +632,16 @@ func runUpdateOauth(c *cli.Context) error {
return fmt.Errorf("--id flag is missing") return fmt.Errorf("--id flag is missing")
} }
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
source, err := auth.GetSourceByID(c.Int64("id")) source, err := models.GetLoginSourceByID(c.Int64("id"))
if err != nil { if err != nil {
return err return err
} }
oAuth2Config := source.Cfg.(*oauth2.Source) oAuth2Config := source.OAuth2()
if c.IsSet("name") { if c.IsSet("name") {
source.Name = c.String("name") source.Name = c.String("name")
@@ -890,29 +667,8 @@ func runUpdateOauth(c *cli.Context) error {
oAuth2Config.IconURL = c.String("icon-url") oAuth2Config.IconURL = c.String("icon-url")
} }
if c.IsSet("scopes") {
oAuth2Config.Scopes = c.StringSlice("scopes")
}
if c.IsSet("required-claim-name") {
oAuth2Config.RequiredClaimName = c.String("required-claim-name")
}
if c.IsSet("required-claim-value") {
oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
}
if c.IsSet("group-claim-name") {
oAuth2Config.GroupClaimName = c.String("group-claim-name")
}
if c.IsSet("admin-group") {
oAuth2Config.AdminGroup = c.String("admin-group")
}
if c.IsSet("restricted-group") {
oAuth2Config.RestrictedGroup = c.String("restricted-group")
}
// update custom URL mapping // update custom URL mapping
customURLMapping := &oauth2.CustomURLMapping{} var customURLMapping = &oauth2.CustomURLMapping{}
if oAuth2Config.CustomURLMapping != nil { if oAuth2Config.CustomURLMapping != nil {
customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
@@ -939,130 +695,16 @@ func runUpdateOauth(c *cli.Context) error {
oAuth2Config.CustomURLMapping = customURLMapping oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config source.Cfg = oAuth2Config
return auth.UpdateSource(source) return models.UpdateSource(source)
}
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
if !contains(validAuthTypes, strings.ToUpper(c.String("auth-type"))) {
return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5")
}
conf.Auth = c.String("auth-type")
}
if c.IsSet("host") {
conf.Host = c.String("host")
}
if c.IsSet("port") {
conf.Port = c.Int("port")
}
if c.IsSet("allowed-domains") {
conf.AllowedDomains = c.String("allowed-domains")
}
if c.IsSet("force-smtps") {
conf.ForceSMTPS = c.BoolT("force-smtps")
}
if c.IsSet("skip-verify") {
conf.SkipVerify = c.BoolT("skip-verify")
}
if c.IsSet("helo-hostname") {
conf.HeloHostname = c.String("helo-hostname")
}
if c.IsSet("disable-helo") {
conf.DisableHelo = c.BoolT("disable-helo")
}
if c.IsSet("skip-local-2fa") {
conf.SkipLocalTwoFA = c.BoolT("skip-local-2fa")
}
return nil
}
func runAddSMTP(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if !c.IsSet("name") || len(c.String("name")) == 0 {
return errors.New("name must be set")
}
if !c.IsSet("host") || len(c.String("host")) == 0 {
return errors.New("host must be set")
}
if !c.IsSet("port") {
return errors.New("port must be set")
}
active := true
if c.IsSet("active") {
active = c.BoolT("active")
}
var smtpConfig smtp.Source
if err := parseSMTPConfig(c, &smtpConfig); err != nil {
return err
}
// If not set default to PLAIN
if len(smtpConfig.Auth) == 0 {
smtpConfig.Auth = "PLAIN"
}
return auth.CreateSource(&auth.Source{
Type: auth.SMTP,
Name: c.String("name"),
IsActive: active,
Cfg: &smtpConfig,
})
}
func runUpdateSMTP(c *cli.Context) error {
if !c.IsSet("id") {
return fmt.Errorf("--id flag is missing")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
source, err := auth.GetSourceByID(c.Int64("id"))
if err != nil {
return err
}
smtpConfig := source.Cfg.(*smtp.Source)
if err := parseSMTPConfig(c, smtpConfig); err != nil {
return err
}
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("active") {
source.IsActive = c.BoolT("active")
}
source.Cfg = smtpConfig
return auth.UpdateSource(source)
} }
func runListAuth(c *cli.Context) error { func runListAuth(c *cli.Context) error {
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
authSources, err := auth.Sources() loginSources, err := models.LoginSources()
if err != nil { if err != nil {
return err return err
} }
@@ -1080,8 +722,8 @@ func runListAuth(c *cli.Context) error {
// loop through each source and print // loop through each source and print
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags) w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n") fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
for _, source := range authSources { for _, source := range loginSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive) fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived)
} }
w.Flush() w.Flush()
@@ -1093,17 +735,14 @@ func runDeleteAuth(c *cli.Context) error {
return fmt.Errorf("--id flag is missing") return fmt.Errorf("--id flag is missing")
} }
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
source, err := auth.GetSourceByID(c.Int64("id")) source, err := models.GetLoginSourceByID(c.Int64("id"))
if err != nil { if err != nil {
return err return err
} }
return auth_service.DeleteSource(source) return models.DeleteSource(source)
} }

View File

@@ -5,22 +5,21 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/modules/auth/ldap"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
type ( type (
authService struct { authService struct {
initDB func(ctx context.Context) error initDB func() error
createAuthSource func(*auth.Source) error createLoginSource func(loginSource *models.LoginSource) error
updateAuthSource func(*auth.Source) error updateLoginSource func(loginSource *models.LoginSource) error
getAuthSourceByID func(id int64) (*auth.Source, error) getLoginSourceByID func(id int64) (*models.LoginSource, error)
} }
) )
@@ -90,14 +89,6 @@ var (
Name: "public-ssh-key-attribute", Name: "public-ssh-key-attribute",
Usage: "The attribute of the users LDAP record containing the users public ssh key.", Usage: "The attribute of the users LDAP record containing the users public ssh key.",
}, },
cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
cli.StringFlag{
Name: "avatar-attribute",
Usage: "The attribute of the users LDAP record containing the users avatar.",
},
} }
ldapBindDnCLIFlags = append(commonLdapCLIFlags, ldapBindDnCLIFlags = append(commonLdapCLIFlags,
@@ -168,97 +159,91 @@ var (
// newAuthService creates a service with default functions. // newAuthService creates a service with default functions.
func newAuthService() *authService { func newAuthService() *authService {
return &authService{ return &authService{
initDB: initDB, initDB: initDB,
createAuthSource: auth.CreateSource, createLoginSource: models.CreateLoginSource,
updateAuthSource: auth.UpdateSource, updateLoginSource: models.UpdateSource,
getAuthSourceByID: auth.GetSourceByID, getLoginSourceByID: models.GetLoginSourceByID,
} }
} }
// parseAuthSource assigns values on authSource according to command line flags. // parseLoginSource assigns values on loginSource according to command line flags.
func parseAuthSource(c *cli.Context, authSource *auth.Source) { func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) {
if c.IsSet("name") { if c.IsSet("name") {
authSource.Name = c.String("name") loginSource.Name = c.String("name")
} }
if c.IsSet("not-active") { if c.IsSet("not-active") {
authSource.IsActive = !c.Bool("not-active") loginSource.IsActived = !c.Bool("not-active")
} }
if c.IsSet("synchronize-users") { if c.IsSet("synchronize-users") {
authSource.IsSyncEnabled = c.Bool("synchronize-users") loginSource.IsSyncEnabled = c.Bool("synchronize-users")
} }
} }
// parseLdapConfig assigns values on config according to command line flags. // parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Context, config *ldap.Source) error { func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
if c.IsSet("name") { if c.IsSet("name") {
config.Name = c.String("name") config.Source.Name = c.String("name")
} }
if c.IsSet("host") { if c.IsSet("host") {
config.Host = c.String("host") config.Source.Host = c.String("host")
} }
if c.IsSet("port") { if c.IsSet("port") {
config.Port = c.Int("port") config.Source.Port = c.Int("port")
} }
if c.IsSet("security-protocol") { if c.IsSet("security-protocol") {
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol")) p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
if !ok { if !ok {
return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol")) return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
} }
config.SecurityProtocol = p config.Source.SecurityProtocol = p
} }
if c.IsSet("skip-tls-verify") { if c.IsSet("skip-tls-verify") {
config.SkipVerify = c.Bool("skip-tls-verify") config.Source.SkipVerify = c.Bool("skip-tls-verify")
} }
if c.IsSet("bind-dn") { if c.IsSet("bind-dn") {
config.BindDN = c.String("bind-dn") config.Source.BindDN = c.String("bind-dn")
} }
if c.IsSet("user-dn") { if c.IsSet("user-dn") {
config.UserDN = c.String("user-dn") config.Source.UserDN = c.String("user-dn")
} }
if c.IsSet("bind-password") { if c.IsSet("bind-password") {
config.BindPassword = c.String("bind-password") config.Source.BindPassword = c.String("bind-password")
} }
if c.IsSet("user-search-base") { if c.IsSet("user-search-base") {
config.UserBase = c.String("user-search-base") config.Source.UserBase = c.String("user-search-base")
} }
if c.IsSet("username-attribute") { if c.IsSet("username-attribute") {
config.AttributeUsername = c.String("username-attribute") config.Source.AttributeUsername = c.String("username-attribute")
} }
if c.IsSet("firstname-attribute") { if c.IsSet("firstname-attribute") {
config.AttributeName = c.String("firstname-attribute") config.Source.AttributeName = c.String("firstname-attribute")
} }
if c.IsSet("surname-attribute") { if c.IsSet("surname-attribute") {
config.AttributeSurname = c.String("surname-attribute") config.Source.AttributeSurname = c.String("surname-attribute")
} }
if c.IsSet("email-attribute") { if c.IsSet("email-attribute") {
config.AttributeMail = c.String("email-attribute") config.Source.AttributeMail = c.String("email-attribute")
} }
if c.IsSet("attributes-in-bind") { if c.IsSet("attributes-in-bind") {
config.AttributesInBind = c.Bool("attributes-in-bind") config.Source.AttributesInBind = c.Bool("attributes-in-bind")
} }
if c.IsSet("public-ssh-key-attribute") { if c.IsSet("public-ssh-key-attribute") {
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") config.Source.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
}
if c.IsSet("avatar-attribute") {
config.AttributeAvatar = c.String("avatar-attribute")
} }
if c.IsSet("page-size") { if c.IsSet("page-size") {
config.SearchPageSize = uint32(c.Uint("page-size")) config.Source.SearchPageSize = uint32(c.Uint("page-size"))
} }
if c.IsSet("user-filter") { if c.IsSet("user-filter") {
config.Filter = c.String("user-filter") config.Source.Filter = c.String("user-filter")
} }
if c.IsSet("admin-filter") { if c.IsSet("admin-filter") {
config.AdminFilter = c.String("admin-filter") config.Source.AdminFilter = c.String("admin-filter")
} }
if c.IsSet("restricted-filter") { if c.IsSet("restricted-filter") {
config.RestrictedFilter = c.String("restricted-filter") config.Source.RestrictedFilter = c.String("restricted-filter")
} }
if c.IsSet("allow-deactivate-all") { if c.IsSet("allow-deactivate-all") {
config.AllowDeactivateAll = c.Bool("allow-deactivate-all") config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all")
}
if c.IsSet("skip-local-2fa") {
config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
} }
return nil return nil
} }
@@ -266,7 +251,7 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
// findLdapSecurityProtocolByName finds security protocol by its name ignoring case. // findLdapSecurityProtocolByName finds security protocol by its name ignoring case.
// It returns the value of the security protocol and if it was found. // It returns the value of the security protocol and if it was found.
func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
for i, n := range ldap.SecurityProtocolNames { for i, n := range models.SecurityProtocolNames {
if strings.EqualFold(name, n) { if strings.EqualFold(name, n) {
return i, true return i, true
} }
@@ -274,23 +259,23 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
return 0, false return 0, false
} }
// getAuthSource gets the login source by its id defined in the command line flags. // getLoginSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type. // It returns an error if the id is not set, does not match any source or if the source is not of expected type.
func (a *authService) getAuthSource(c *cli.Context, authType auth.Type) (*auth.Source, error) { func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) (*models.LoginSource, error) {
if err := argsSet(c, "id"); err != nil { if err := argsSet(c, "id"); err != nil {
return nil, err return nil, err
} }
authSource, err := a.getAuthSourceByID(c.Int64("id")) loginSource, err := a.getLoginSourceByID(c.Int64("id"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if authSource.Type != authType { if loginSource.Type != loginType {
return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String()) return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", models.LoginNames[loginType], models.LoginNames[loginSource.Type])
} }
return authSource, nil return loginSource, nil
} }
// addLdapBindDn adds a new LDAP via Bind DN authentication source. // addLdapBindDn adds a new LDAP via Bind DN authentication source.
@@ -299,49 +284,45 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
return err return err
} }
ctx, cancel := installSignals() if err := a.initDB(); err != nil {
defer cancel()
if err := a.initDB(ctx); err != nil {
return err return err
} }
authSource := &auth.Source{ loginSource := &models.LoginSource{
Type: auth.LDAP, Type: models.LoginLDAP,
IsActive: true, // active by default IsActived: true, // active by default
Cfg: &ldap.Source{ Cfg: &models.LDAPConfig{
Enabled: true, // always true Source: &ldap.Source{
Enabled: true, // always true
},
}, },
} }
parseAuthSource(c, authSource) parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err return err
} }
return a.createAuthSource(authSource) return a.createLoginSource(loginSource)
} }
// updateLdapBindDn updates a new LDAP via Bind DN authentication source. // updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(c *cli.Context) error { func (a *authService) updateLdapBindDn(c *cli.Context) error {
ctx, cancel := installSignals() if err := a.initDB(); err != nil {
defer cancel()
if err := a.initDB(ctx); err != nil {
return err return err
} }
authSource, err := a.getAuthSource(c, auth.LDAP) loginSource, err := a.getLoginSource(c, models.LoginLDAP)
if err != nil { if err != nil {
return err return err
} }
parseAuthSource(c, authSource) parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err return err
} }
return a.updateAuthSource(authSource) return a.updateLoginSource(loginSource)
} }
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
@@ -350,47 +331,43 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
return err return err
} }
ctx, cancel := installSignals() if err := a.initDB(); err != nil {
defer cancel()
if err := a.initDB(ctx); err != nil {
return err return err
} }
authSource := &auth.Source{ loginSource := &models.LoginSource{
Type: auth.DLDAP, Type: models.LoginDLDAP,
IsActive: true, // active by default IsActived: true, // active by default
Cfg: &ldap.Source{ Cfg: &models.LDAPConfig{
Enabled: true, // always true Source: &ldap.Source{
Enabled: true, // always true
},
}, },
} }
parseAuthSource(c, authSource) parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err return err
} }
return a.createAuthSource(authSource) return a.createLoginSource(loginSource)
} }
// updateLdapBindDn updates a new LDAP (simple auth) authentication source. // updateLdapBindDn updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
ctx, cancel := installSignals() if err := a.initDB(); err != nil {
defer cancel()
if err := a.initDB(ctx); err != nil {
return err return err
} }
authSource, err := a.getAuthSource(c, auth.DLDAP) loginSource, err := a.getLoginSource(c, models.LoginDLDAP)
if err != nil { if err != nil {
return err return err
} }
parseAuthSource(c, authSource) parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
return err return err
} }
return a.updateAuthSource(authSource) return a.updateLoginSource(loginSource)
} }

File diff suppressed because it is too large Load Diff

View File

@@ -180,7 +180,7 @@ func runCert(c *cli.Context) error {
} }
log.Println("Written cert.pem") log.Println("Written cert.pem")
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
log.Fatalf("Failed to open key.pem for writing: %v", err) log.Fatalf("Failed to open key.pem for writing: %v", err)
} }

View File

@@ -7,16 +7,11 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os"
"os/signal"
"strings" "strings"
"syscall"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@@ -31,7 +26,7 @@ func argsSet(c *cli.Context, args ...string) error {
return errors.New(a + " is not set") return errors.New(a + " is not set")
} }
if util.IsEmptyString(c.String(a)) { if util.IsEmptyString(a) {
return errors.New(a + " is required") return errors.New(a + " is required")
} }
} }
@@ -57,40 +52,17 @@ func confirm() (bool, error) {
} }
} }
func initDB(ctx context.Context) error { func initDB() error {
setting.LoadFromExisting() return initDBDisableConsole(false)
setting.InitDBConfig() }
setting.NewXORMLogService(false)
if setting.Database.Type == "" { func initDBDisableConsole(disableConsole bool) error {
log.Fatal(`Database settings are missing from the configuration file: %q. setting.NewContext()
Ensure you are running in the correct environment or set the correct configuration file with -c. setting.InitDBConfig()
If this is the intended configuration file complete the [database] section.`, setting.CustomConf)
} setting.NewXORMLogService(disableConsole)
if err := db.InitEngine(ctx); err != nil { if err := models.SetEngine(); err != nil {
return fmt.Errorf("unable to initialize the database using the configuration in %q. Error: %v", setting.CustomConf, err) return fmt.Errorf("models.SetEngine: %v", err)
} }
return nil return nil
} }
func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// install notify
signalChannel := make(chan os.Signal, 1)
signal.Notify(
signalChannel,
syscall.SIGINT,
syscall.SIGTERM,
)
select {
case <-signalChannel:
case <-ctx.Done():
}
cancel()
signal.Reset()
}()
return ctx, cancel
}

View File

@@ -7,7 +7,7 @@ package cmd
import ( import (
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@@ -23,25 +23,22 @@ var CmdConvert = cli.Command{
} }
func runConvert(ctx *cli.Context) error { func runConvert(ctx *cli.Context) error {
stdCtx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }
log.Info("AppPath: %s", setting.AppPath) log.Trace("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Trace("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath) log.Trace("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath) log.Trace("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf) setting.InitDBConfig()
if !setting.Database.UseMySQL { if !setting.Database.UseMySQL {
fmt.Println("This command can only be used with a MySQL database") fmt.Println("This command can only be used with a MySQL database")
return nil return nil
} }
if err := db.ConvertUtf8ToUtf8mb4(); err != nil { if err := models.ConvertUtf8ToUtf8mb4(); err != nil {
log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err)
return err return err
} }

View File

@@ -43,11 +43,7 @@ func runDocs(ctx *cli.Context) error {
// Clean up markdown. The following bug was fixed in v2, but is present in v1. // Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages) // It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040 // https://github.com/urfave/cli/issues/1040
firstHashtagIndex := strings.Index(docs, "#") docs = docs[strings.Index(docs, "#"):]
if firstHashtagIndex > 0 {
docs = docs[firstHashtagIndex:]
}
} }
out := os.Stdout out := os.Stdout

View File

@@ -5,28 +5,29 @@
package cmd package cmd
import ( import (
"errors" "context"
"fmt" "fmt"
golog "log" golog "log"
"os" "os"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/models/migrations"
"code.gitea.io/gitea/modules/doctor" "code.gitea.io/gitea/modules/doctor"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
"xorm.io/xorm" "xorm.io/xorm"
"github.com/urfave/cli"
) )
// CmdDoctor represents the available doctor sub-command. // CmdDoctor represents the available doctor sub-command.
var CmdDoctor = cli.Command{ var CmdDoctor = cli.Command{
Name: "doctor", Name: "doctor",
Usage: "Diagnose and optionally fix problems", Usage: "Diagnose problems",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", Description: "A command to diagnose problems with the current Gitea instance according to the given configuration.",
Action: runDoctor, Action: runDoctor,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
@@ -87,7 +88,7 @@ func runRecreateTable(ctx *cli.Context) error {
golog.SetPrefix("") golog.SetPrefix("")
golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT))) golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))
setting.LoadFromExisting() setting.NewContext()
setting.InitDBConfig() setting.InitDBConfig()
setting.EnableXORMLog = ctx.Bool("debug") setting.EnableXORMLog = ctx.Bool("debug")
@@ -95,10 +96,7 @@ func runRecreateTable(ctx *cli.Context) error {
setting.Cfg.Section("log").Key("XORM").SetValue(",") setting.Cfg.Section("log").Key("XORM").SetValue(",")
setting.NewXORMLogService(!ctx.Bool("debug")) setting.NewXORMLogService(!ctx.Bool("debug"))
stdCtx, cancel := installSignals() if err := models.SetEngine(); err != nil {
defer cancel()
if err := db.InitEngine(stdCtx); err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil return nil
@@ -110,25 +108,33 @@ func runRecreateTable(ctx *cli.Context) error {
names = append(names, args.Get(i)) names = append(names, args.Get(i))
} }
beans, err := db.NamesToBean(names...) beans, err := models.NamesToBean(names...)
if err != nil { if err != nil {
return err return err
} }
recreateTables := migrations.RecreateTables(beans...) recreateTables := migrations.RecreateTables(beans...)
return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error { return models.NewEngine(context.Background(), func(x *xorm.Engine) error {
if err := migrations.EnsureUpToDate(x); err != nil { if err := migrations.EnsureUpToDate(x); err != nil {
return err return err
} }
return recreateTables(x) return recreateTables(x)
}) })
} }
func setDoctorLogger(ctx *cli.Context) { func runDoctor(ctx *cli.Context) error {
// Silence the default loggers
log.DelNamedLogger("console")
log.DelNamedLogger(log.DEFAULT)
// Now setup our own
logFile := ctx.String("log-file") logFile := ctx.String("log-file")
if !ctx.IsSet("log-file") { if !ctx.IsSet("log-file") {
logFile = "doctor.log" logFile = "doctor.log"
} }
colorize := log.CanColorStdout colorize := log.CanColorStdout
if ctx.IsSet("color") { if ctx.IsSet("color") {
colorize = ctx.Bool("color") colorize = ctx.Bool("color")
@@ -136,50 +142,11 @@ func setDoctorLogger(ctx *cli.Context) {
if len(logFile) == 0 { if len(logFile) == 0 {
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize)) log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
return } else if logFile == "-" {
}
defer func() {
recovered := recover()
if recovered == nil {
return
}
err, ok := recovered.(error)
if !ok {
panic(recovered)
}
if errors.Is(err, os.ErrPermission) {
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file due to permissions error: %s\n %v\n", logFile, err)
} else {
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file: %s\n %v\n", logFile, err)
}
fmt.Fprintf(os.Stderr, "WARN: Logging will be disabled\n Use `--log-file` to configure log file location\n")
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
}()
if logFile == "-" {
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize)) log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize))
} else { } else {
log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile)) log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
} }
}
func runDoctor(ctx *cli.Context) error {
// Silence the default loggers
log.DelNamedLogger("console")
log.DelNamedLogger(log.DEFAULT)
stdCtx, cancel := installSignals()
defer cancel()
// Now setup our own
setDoctorLogger(ctx)
colorize := log.CanColorStdout
if ctx.IsSet("color") {
colorize = ctx.Bool("color")
}
// Finally redirect the default golog to here // Finally redirect the default golog to here
golog.SetFlags(0) golog.SetFlags(0)
@@ -234,7 +201,7 @@ func runDoctor(ctx *cli.Context) error {
// Now we can set up our own logger to return information about what the doctor is doing // Now we can set up our own logger to return information about what the doctor is doing
if err := log.NewNamedLogger("doctorouter", if err := log.NewNamedLogger("doctorouter",
0, 1000,
"console", "console",
"console", "console",
fmt.Sprintf(`{"level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1}`, colorize)); err != nil { fmt.Sprintf(`{"level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1}`, colorize)); err != nil {
@@ -244,5 +211,5 @@ func runDoctor(ctx *cli.Context) error {
logger := log.GetLogger("doctorouter") logger := log.GetLogger("doctorouter")
defer logger.Close() defer logger.Close()
return doctor.RunChecks(stdCtx, logger, ctx.Bool("fix"), checks) return doctor.RunChecks(logger, ctx.Bool("fix"), checks)
} }

View File

@@ -7,40 +7,29 @@ package cmd
import ( import (
"fmt" "fmt"
"io" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/go-chi/session" "gitea.com/go-chi/session"
"github.com/mholt/archiver/v3" jsoniter "github.com/json-iterator/go"
archiver "github.com/mholt/archiver/v3"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error { func addFile(w archiver.Writer, filePath string, absPath string, verbose bool) error {
if verbose { if verbose {
log.Info("Adding file %s", customName) log.Info("Adding file %s\n", filePath)
} }
return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: customName,
},
ReadCloser: r,
})
}
func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
file, err := os.Open(absPath) file, err := os.Open(absPath)
if err != nil { if err != nil {
return err return err
@@ -51,10 +40,16 @@ func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
return err return err
} }
return addReader(w, file, fileInfo, filePath, verbose) return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: fileInfo,
CustomName: filePath,
},
ReadCloser: file,
})
} }
func isSubdir(upper, lower string) (bool, error) { func isSubdir(upper string, lower string) (bool, error) {
if relPath, err := filepath.Rel(upper, lower); err != nil { if relPath, err := filepath.Rel(upper, lower); err != nil {
return false, err return false, err
} else if relPath == "." || !strings.HasPrefix(relPath, ".") { } else if relPath == "." || !strings.HasPrefix(relPath, ".") {
@@ -92,7 +87,7 @@ func (o outputType) String() string {
} }
var outputTypeEnum = &outputType{ var outputTypeEnum = &outputType{
Enum: []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4"}, Enum: []string{"zip", "tar", "tar.gz", "tar.xz", "tar.bz2"},
Default: "zip", Default: "zip",
} }
@@ -134,18 +129,6 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
Name: "skip-custom-dir", Name: "skip-custom-dir",
Usage: "Skip custom directory", Usage: "Skip custom directory",
}, },
cli.BoolFlag{
Name: "skip-lfs-data",
Usage: "Skip LFS data",
},
cli.BoolFlag{
Name: "skip-attachment-data",
Usage: "Skip attachment data",
},
cli.BoolFlag{
Name: "skip-package-data",
Usage: "Skip package data",
},
cli.GenericFlag{ cli.GenericFlag{
Name: "type", Name: "type",
Value: outputTypeEnum, Value: outputTypeEnum,
@@ -162,24 +145,14 @@ func fatal(format string, args ...interface{}) {
func runDump(ctx *cli.Context) error { func runDump(ctx *cli.Context) error {
var file *os.File var file *os.File
fileName := ctx.String("file") fileName := ctx.String("file")
outType := ctx.String("type")
if fileName == "-" { if fileName == "-" {
file = os.Stdout file = os.Stdout
err := log.DelLogger("console") err := log.DelLogger("console")
if err != nil { if err != nil {
fatal("Deleting default logger failed. Can not write to stdout: %v", err) fatal("Deleting default logger failed. Can not write to stdout: %v", err)
} }
} else {
for _, suffix := range outputTypeEnum.Enum {
if strings.HasSuffix(fileName, "."+suffix) {
fileName = strings.TrimSuffix(fileName, "."+suffix)
break
}
}
fileName += "." + outType
} }
setting.LoadFromExisting() setting.NewContext()
// make sure we are logging to the console no matter what the configuration tells us do to // make sure we are logging to the console no matter what the configuration tells us do to
if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil { if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil {
fatal("Setting logging mode to console failed: %v", err) fatal("Setting logging mode to console failed: %v", err)
@@ -193,10 +166,7 @@ func runDump(ctx *cli.Context) error {
} }
setting.NewServices() // cannot access session settings otherwise setting.NewServices() // cannot access session settings otherwise
stdCtx, cancel := installSignals() err := models.SetEngine()
defer cancel()
err := db.InitEngine(stdCtx)
if err != nil { if err != nil {
return err return err
} }
@@ -219,6 +189,7 @@ func runDump(ctx *cli.Context) error {
} }
verbose := ctx.Bool("verbose") verbose := ctx.Bool("verbose")
outType := ctx.String("type")
var iface interface{} var iface interface{}
if fileName == "-" { if fileName == "-" {
iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType)) iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
@@ -243,15 +214,19 @@ func runDump(ctx *cli.Context) error {
fatal("Failed to include repositories: %v", err) fatal("Failed to include repositories: %v", err)
} }
if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
log.Info("Skip dumping LFS data")
} else if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error {
info, err := object.Stat() info, err := object.Stat()
if err != nil { if err != nil {
return err return err
} }
return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose) return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: path.Join("data", "lfs", objPath),
},
ReadCloser: object,
})
}); err != nil { }); err != nil {
fatal("Failed to dump LFS objects: %v", err) fatal("Failed to dump LFS objects: %v", err)
} }
@@ -262,7 +237,7 @@ func runDump(ctx *cli.Context) error {
fatal("Path does not exist: %s", tmpDir) fatal("Path does not exist: %s", tmpDir)
} }
dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql") dbDump, err := ioutil.TempFile(tmpDir, "gitea-db.sql")
if err != nil { if err != nil {
fatal("Failed to create tmp file: %v", err) fatal("Failed to create tmp file: %v", err)
} }
@@ -279,7 +254,7 @@ func runDump(ctx *cli.Context) error {
log.Info("Dumping database...") log.Info("Dumping database...")
} }
if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil { if err := models.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
fatal("Failed to dump database: %v", err) fatal("Failed to dump database: %v", err)
} }
@@ -295,7 +270,7 @@ func runDump(ctx *cli.Context) error {
} }
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") { if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
log.Info("Skipping custom directory") log.Info("Skiping custom directory")
} else { } else {
customDir, err := os.Stat(setting.CustomPath) customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() { if err == nil && customDir.IsDir() {
@@ -321,6 +296,7 @@ func runDump(ctx *cli.Context) error {
var excludes []string var excludes []string
if setting.Cfg.Section("session").Key("PROVIDER").Value() == "file" { if setting.Cfg.Section("session").Key("PROVIDER").Value() == "file" {
var opts session.Options var opts session.Options
json := jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil { if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
return err return err
} }
@@ -330,7 +306,6 @@ func runDump(ctx *cli.Context) error {
excludes = append(excludes, setting.RepoRootPath) excludes = append(excludes, setting.RepoRootPath)
excludes = append(excludes, setting.LFS.Path) excludes = append(excludes, setting.LFS.Path)
excludes = append(excludes, setting.Attachment.Path) excludes = append(excludes, setting.Attachment.Path)
excludes = append(excludes, setting.Packages.Path)
excludes = append(excludes, setting.LogRootPath) excludes = append(excludes, setting.LogRootPath)
excludes = append(excludes, absFileName) excludes = append(excludes, absFileName)
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
@@ -338,32 +313,23 @@ func runDump(ctx *cli.Context) error {
} }
} }
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
log.Info("Skip dumping attachment data")
} else if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error {
info, err := object.Stat() info, err := object.Stat()
if err != nil { if err != nil {
return err return err
} }
return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose) return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: path.Join("data", "attachments", objPath),
},
ReadCloser: object,
})
}); err != nil { }); err != nil {
fatal("Failed to dump attachments: %v", err) fatal("Failed to dump attachments: %v", err)
} }
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
log.Info("Skip dumping package data")
} else if err := storage.Packages.IterateObjects(func(objPath string, object storage.Object) error {
info, err := object.Stat()
if err != nil {
return err
}
return addReader(w, object, info, path.Join("data", "packages", objPath), verbose)
}); err != nil {
fatal("Failed to dump packages: %v", err)
}
// Doesn't check if LogRootPath exists before processing --skip-log intentionally, // Doesn't check if LogRootPath exists before processing --skip-log intentionally,
// ensuring that it's clear the dump is skipped whether the directory's initialized // ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not. // yet or not.
@@ -387,7 +353,7 @@ func runDump(ctx *cli.Context) error {
fatal("Failed to save %s: %v", fileName, err) fatal("Failed to save %s: %v", fileName, err)
} }
if err := os.Chmod(fileName, 0o600); err != nil { if err := os.Chmod(fileName, 0600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err) log.Info("Can't change file access permissions mask to 0600: %v", err)
} }
} }
@@ -439,23 +405,8 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA
} }
} }
} else { } else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/... if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
shouldAdd := file.Mode().IsRegular() return err
if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
target, err := filepath.EvalSymlinks(currentAbsPath)
if err != nil {
return err
}
targetStat, err := os.Stat(target)
if err != nil {
return err
}
shouldAdd = targetStat.Mode().IsRegular()
}
if shouldAdd {
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
return err
}
} }
} }
} }

View File

@@ -7,18 +7,14 @@ package cmd
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"os"
"strings" "strings"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" "code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/migrations"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -73,30 +69,22 @@ var CmdDumpRepository = cli.Command{
cli.StringFlag{ cli.StringFlag{
Name: "units", Name: "units",
Value: "", Value: "",
Usage: `Which items will be migrated, one or more units should be separated as comma. Usage: `Which items will be migrated, one or more units should be separated as comma.
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
}, },
}, },
} }
func runDumpRepository(ctx *cli.Context) error { func runDumpRepository(ctx *cli.Context) error {
stdCtx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }
// migrations.GiteaLocalUploader depends on git module log.Trace("AppPath: %s", setting.AppPath)
if err := git.InitSimple(context.Background()); err != nil { log.Trace("AppWorkPath: %s", setting.AppWorkPath)
return err log.Trace("Custom path: %s", setting.CustomPath)
} log.Trace("Log path: %s", setting.LogRootPath)
setting.InitDBConfig()
log.Info("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf)
var ( var (
serviceType structs.GitServiceType serviceType structs.GitServiceType
@@ -116,7 +104,7 @@ func runDumpRepository(ctx *cli.Context) error {
} }
serviceType = convert.ToGitServiceType(serviceStr) serviceType = convert.ToGitServiceType(serviceStr)
opts := base.MigrateOptions{ var opts = base.MigrateOptions{
GitServiceType: serviceType, GitServiceType: serviceType,
CloneAddr: cloneAddr, CloneAddr: cloneAddr,
AuthUsername: ctx.String("auth_username"), AuthUsername: ctx.String("auth_username"),
@@ -137,9 +125,7 @@ func runDumpRepository(ctx *cli.Context) error {
} else { } else {
units := strings.Split(ctx.String("units"), ",") units := strings.Split(ctx.String("units"), ",")
for _, unit := range units { for _, unit := range units {
switch strings.ToLower(strings.TrimSpace(unit)) { switch strings.ToLower(unit) {
case "":
continue
case "wiki": case "wiki":
opts.Wiki = true opts.Wiki = true
case "issues": case "issues":
@@ -156,29 +142,13 @@ func runDumpRepository(ctx *cli.Context) error {
opts.Comments = true opts.Comments = true
case "pull_requests": case "pull_requests":
opts.PullRequests = true opts.PullRequests = true
default:
return errors.New("invalid unit: " + unit)
} }
} }
} }
// the repo_dir will be removed if error occurs in DumpRepository
// make sure the directory doesn't exist or is empty, prevent from deleting user files
repoDir := ctx.String("repo_dir")
if exists, err := util.IsExist(repoDir); err != nil {
return fmt.Errorf("unable to stat repo_dir %q: %v", repoDir, err)
} else if exists {
if isDir, _ := util.IsDir(repoDir); !isDir {
return fmt.Errorf("repo_dir %q already exists but it's not a directory", repoDir)
}
if dir, _ := os.ReadDir(repoDir); len(dir) > 0 {
return fmt.Errorf("repo_dir %q is not empty", repoDir)
}
}
if err := migrations.DumpRepository( if err := migrations.DumpRepository(
context.Background(), context.Background(),
repoDir, ctx.String("repo_dir"),
ctx.String("owner_name"), ctx.String("owner_name"),
opts, opts,
); err != nil { ); err != nil {

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build bindata // +build bindata
package cmd package cmd
@@ -19,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -108,12 +107,13 @@ type asset struct {
} }
func initEmbeddedExtractor(c *cli.Context) error { func initEmbeddedExtractor(c *cli.Context) error {
// Silence the console logger // Silence the console logger
log.DelNamedLogger("console") log.DelNamedLogger("console")
log.DelNamedLogger(log.DEFAULT) log.DelNamedLogger(log.DEFAULT)
// Read configuration file // Read configuration file
setting.LoadAllowEmpty() setting.NewContext()
pats, err := getPatterns(c.Args()) pats, err := getPatterns(c.Args())
if err != nil { if err != nil {
@@ -258,7 +258,7 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
return fmt.Errorf("%s: %v", dir, err) return fmt.Errorf("%s: %v", dir, err)
} }
perms := os.ModePerm & 0o666 perms := os.ModePerm & 0666
fi, err := os.Lstat(dest) fi, err := os.Lstat(dest)
if err != nil { if err != nil {
@@ -271,7 +271,7 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
} else if !fi.Mode().IsRegular() { } else if !fi.Mode().IsRegular() {
return fmt.Errorf("%s already exists, but it's not a regular file", dest) return fmt.Errorf("%s already exists, but it's not a regular file", dest)
} else if rename { } else if rename {
if err := util.Rename(dest, dest+".bak"); err != nil { if err := os.Rename(dest, dest+".bak"); err != nil {
return fmt.Errorf("Error creating backup for %s: %v", dest, err) return fmt.Errorf("Error creating backup for %s: %v", dest, err)
} }
// Attempt to respect file permissions mask (even if user:group will be set anew) // Attempt to respect file permissions mask (even if user:group will be set anew)
@@ -294,7 +294,7 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
} }
func buildAssetList(sec *section, globs []glob.Glob, c *cli.Context) []asset { func buildAssetList(sec *section, globs []glob.Glob, c *cli.Context) []asset {
results := make([]asset, 0, 64) var results = make([]asset, 0, 64)
for _, name := range sec.Names() { for _, name := range sec.Names() {
if isdir, err := sec.IsDir(name); !isdir && err == nil { if isdir, err := sec.IsDir(name); !isdir && err == nil {
if sec.Path == "public" && if sec.Path == "public" &&
@@ -305,11 +305,9 @@ func buildAssetList(sec *section, globs []glob.Glob, c *cli.Context) []asset {
matchName := sec.Path + "/" + name matchName := sec.Path + "/" + name
for _, g := range globs { for _, g := range globs {
if g.Match(matchName) { if g.Match(matchName) {
results = append(results, asset{ results = append(results, asset{Section: sec,
Section: sec, Name: name,
Name: name, Path: sec.Path + "/" + name})
Path: sec.Path + "/" + name,
})
break break
} }
} }

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !bindata // +build !bindata
package cmd package cmd

View File

@@ -71,7 +71,7 @@ func runGenerateInternalToken(c *cli.Context) error {
} }
func runGenerateLfsJwtSecret(c *cli.Context) error { func runGenerateLfsJwtSecret(c *cli.Context) error {
JWTSecretBase64, err := generate.NewJwtSecretBase64() JWTSecretBase64, err := generate.NewJwtSecret()
if err != nil { if err != nil {
return err return err
} }

View File

@@ -15,9 +15,9 @@ import (
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@@ -38,7 +38,6 @@ var (
subcmdHookPreReceive, subcmdHookPreReceive,
subcmdHookUpdate, subcmdHookUpdate,
subcmdHookPostReceive, subcmdHookPostReceive,
subcmdHookProcReceive,
}, },
} }
@@ -75,18 +74,6 @@ var (
}, },
}, },
} }
// Note: new hook since git 2.29
subcmdHookProcReceive = cli.Command{
Name: "proc-receive",
Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookProcReceive,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
}
) )
type delayWriter struct { type delayWriter struct {
@@ -162,30 +149,29 @@ func (n *nilWriter) WriteString(s string) (int, error) {
} }
func runHookPreReceive(c *cli.Context) error { func runHookPreReceive(c *cli.Context) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if os.Getenv(models.EnvIsInternal) == "true" {
return nil return nil
} }
ctx, cancel := installSignals()
defer cancel()
setup("hooks/pre-receive.log", c.Bool("debug")) setup("hooks/pre-receive.log", c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet { if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(`Rejecting changes as Gitea environment not set. fail(`Rejecting changes as Gitea environment not set.
If you are pushing over SSH you must push with a key managed by If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "") Gitea or set your environment appropriately.`, "")
} else {
return nil
} }
return nil
} }
// the environment is set by serv command // the environment setted on serv command
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
username := os.Getenv(repo_module.EnvRepoUsername) username := os.Getenv(models.EnvRepoUsername)
reponame := os.Getenv(repo_module.EnvRepoName) reponame := os.Getenv(models.EnvRepoName)
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) prID, _ := strconv.ParseInt(os.Getenv(models.EnvPRID), 10, 64)
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64) isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey))
hookOptions := private.HookOptions{ hookOptions := private.HookOptions{
UserID: userID, UserID: userID,
@@ -193,8 +179,8 @@ Gitea or set your environment appropriately.`, "")
GitObjectDirectory: os.Getenv(private.GitObjectDirectory), GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
GitQuarantinePath: os.Getenv(private.GitQuarantinePath), GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
GitPushOptions: pushOptions(), GitPushOptions: pushOptions(),
PullRequestID: prID, ProtectedBranchID: prID,
DeployKeyID: deployKeyID, IsDeployKey: isDeployKey,
} }
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
@@ -218,11 +204,6 @@ Gitea or set your environment appropriately.`, "")
} }
} }
supportProcRecive := false
if git.CheckGitVersionAtLeast("2.29") == nil {
supportProcRecive = true
}
for scanner.Scan() { for scanner.Scan() {
// TODO: support news feeds for wiki // TODO: support news feeds for wiki
if isWiki { if isWiki {
@@ -240,10 +221,8 @@ Gitea or set your environment appropriately.`, "")
total++ total++
lastline++ lastline++
// If the ref is a branch or tag, check if it's protected // If the ref is a branch, check if it's protected
// if supportProcRecive all ref should be checked because if strings.HasPrefix(refFullName, git.BranchPrefix) {
// permission check was delayed
if supportProcRecive || strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
oldCommitIDs[count] = oldCommitID oldCommitIDs[count] = oldCommitID
newCommitIDs[count] = newCommitID newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName refFullNames[count] = refFullName
@@ -251,19 +230,19 @@ Gitea or set your environment appropriately.`, "")
fmt.Fprintf(out, "*") fmt.Fprintf(out, "*")
if count >= hookBatchSize { if count >= hookBatchSize {
fmt.Fprintf(out, " Checking %d references\n", count) fmt.Fprintf(out, " Checking %d branches\n", count)
hookOptions.OldCommitIDs = oldCommitIDs hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames hookOptions.RefFullNames = refFullNames
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions) statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
switch statusCode { switch statusCode {
case http.StatusOK: case http.StatusOK:
// no-op // no-op
case http.StatusInternalServerError: case http.StatusInternalServerError:
return fail("Internal Server Error", msg) fail("Internal Server Error", msg)
default: default:
return fail(msg, "") fail(msg, "")
} }
count = 0 count = 0
lastline = 0 lastline = 0
@@ -282,17 +261,18 @@ Gitea or set your environment appropriately.`, "")
hookOptions.NewCommitIDs = newCommitIDs[:count] hookOptions.NewCommitIDs = newCommitIDs[:count]
hookOptions.RefFullNames = refFullNames[:count] hookOptions.RefFullNames = refFullNames[:count]
fmt.Fprintf(out, " Checking %d references\n", count) fmt.Fprintf(out, " Checking %d branches\n", count)
statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions) statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
switch statusCode { switch statusCode {
case http.StatusInternalServerError: case http.StatusInternalServerError:
return fail("Internal Server Error", msg) fail("Internal Server Error", msg)
case http.StatusForbidden: case http.StatusForbidden:
return fail(msg, "") fail(msg, "")
} }
} else if lastline > 0 { } else if lastline > 0 {
fmt.Fprintf(out, "\n") fmt.Fprintf(out, "\n")
lastline = 0
} }
fmt.Fprintf(out, "Checked %d references in total\n", total) fmt.Fprintf(out, "Checked %d references in total\n", total)
@@ -305,28 +285,26 @@ func runHookUpdate(c *cli.Context) error {
} }
func runHookPostReceive(c *cli.Context) error { func runHookPostReceive(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("hooks/post-receive.log", c.Bool("debug"))
// First of all run update-server-info no matter what // First of all run update-server-info no matter what
if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil { if _, err := git.NewCommand("update-server-info").Run(); err != nil {
return fmt.Errorf("Failed to call 'git update-server-info': %v", err) return fmt.Errorf("Failed to call 'git update-server-info': %v", err)
} }
// Now if we're an internal don't do anything else // Now if we're an internal don't do anything else
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if os.Getenv(models.EnvIsInternal) == "true" {
return nil return nil
} }
setup("hooks/post-receive.log", c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet { if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(`Rejecting changes as Gitea environment not set. fail(`Rejecting changes as Gitea environment not set.
If you are pushing over SSH you must push with a key managed by If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "") Gitea or set your environment appropriately.`, "")
} else {
return nil
} }
return nil
} }
var out io.Writer var out io.Writer
@@ -342,12 +320,12 @@ Gitea or set your environment appropriately.`, "")
} }
} }
// the environment is set by serv command // the environment setted on serv command
repoUser := os.Getenv(repo_module.EnvRepoUsername) repoUser := os.Getenv(models.EnvRepoUsername)
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
repoName := os.Getenv(repo_module.EnvRepoName) repoName := os.Getenv(models.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
pusherName := os.Getenv(repo_module.EnvPusherName) pusherName := os.Getenv(models.EnvPusherName)
hookOptions := private.HookOptions{ hookOptions := private.HookOptions{
UserName: pusherName, UserName: pusherName,
@@ -393,11 +371,11 @@ Gitea or set your environment appropriately.`, "")
hookOptions.OldCommitIDs = oldCommitIDs hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames hookOptions.RefFullNames = refFullNames
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
if resp == nil { if resp == nil {
_ = dWriter.Close() _ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
return fail("Internal Server Error", err) fail("Internal Server Error", err)
} }
wasEmpty = wasEmpty || resp.RepoWasEmpty wasEmpty = wasEmpty || resp.RepoWasEmpty
results = append(results, resp.Results...) results = append(results, resp.Results...)
@@ -408,9 +386,9 @@ Gitea or set your environment appropriately.`, "")
if count == 0 { if count == 0 {
if wasEmpty && masterPushed { if wasEmpty && masterPushed {
// We need to tell the repo to reset the default branch to master // We need to tell the repo to reset the default branch to master
err := private.SetDefaultBranch(ctx, repoUser, repoName, "master") err := private.SetDefaultBranch(repoUser, repoName, "master")
if err != nil { if err != nil {
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
} }
} }
fmt.Fprintf(out, "Processed %d references in total\n", total) fmt.Fprintf(out, "Processed %d references in total\n", total)
@@ -426,11 +404,11 @@ Gitea or set your environment appropriately.`, "")
fmt.Fprintf(out, " Processing %d references\n", count) fmt.Fprintf(out, " Processing %d references\n", count)
resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions) resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
if resp == nil { if resp == nil {
_ = dWriter.Close() _ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
return fail("Internal Server Error", err) fail("Internal Server Error", err)
} }
wasEmpty = wasEmpty || resp.RepoWasEmpty wasEmpty = wasEmpty || resp.RepoWasEmpty
results = append(results, resp.Results...) results = append(results, resp.Results...)
@@ -439,9 +417,9 @@ Gitea or set your environment appropriately.`, "")
if wasEmpty && masterPushed { if wasEmpty && masterPushed {
// We need to tell the repo to reset the default branch to master // We need to tell the repo to reset the default branch to master
err := private.SetDefaultBranch(ctx, repoUser, repoName, "master") err := private.SetDefaultBranch(repoUser, repoName, "master")
if err != nil { if err != nil {
return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
} }
} }
_ = dWriter.Close() _ = dWriter.Close()
@@ -482,327 +460,3 @@ func pushOptions() map[string]string {
} }
return opts return opts
} }
func runHookProcReceive(c *cli.Context) error {
setup("hooks/proc-receive.log", c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(`Rejecting changes as Gitea environment not set.
If you are pushing over SSH you must push with a key managed by
Gitea or set your environment appropriately.`, "")
}
return nil
}
ctx, cancel := installSignals()
defer cancel()
if git.CheckGitVersionAtLeast("2.29") != nil {
return fail("Internal Server Error", "git not support proc-receive.")
}
reader := bufio.NewReader(os.Stdin)
repoUser := os.Getenv(repo_module.EnvRepoUsername)
repoName := os.Getenv(repo_module.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
pusherName := os.Getenv(repo_module.EnvPusherName)
// 1. Version and features negotiation.
// S: PKT-LINE(version=1\0push-options atomic...) / PKT-LINE(version=1\n)
// S: flush-pkt
// H: PKT-LINE(version=1\0push-options...)
// H: flush-pkt
rs, err := readPktLine(reader, pktLineTypeData)
if err != nil {
return err
}
const VersionHead string = "version=1"
var (
hasPushOptions bool
response = []byte(VersionHead)
requestOptions []string
)
index := bytes.IndexByte(rs.Data, byte(0))
if index >= len(rs.Data) {
return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
}
if index < 0 {
if len(rs.Data) == 10 && rs.Data[9] == '\n' {
index = 9
} else {
return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
}
}
if string(rs.Data[0:index]) != VersionHead {
return fail("Internal Server Error", "Received unsupported version: %s", string(rs.Data[0:index]))
}
requestOptions = strings.Split(string(rs.Data[index+1:]), " ")
for _, option := range requestOptions {
if strings.HasPrefix(option, "push-options") {
response = append(response, byte(0))
response = append(response, []byte("push-options")...)
hasPushOptions = true
}
}
response = append(response, '\n')
_, err = readPktLine(reader, pktLineTypeFlush)
if err != nil {
return err
}
err = writeDataPktLine(os.Stdout, response)
if err != nil {
return err
}
err = writeFlushPktLine(os.Stdout)
if err != nil {
return err
}
// 2. receive commands from server.
// S: PKT-LINE(<old-oid> <new-oid> <ref>)
// S: ... ...
// S: flush-pkt
// # [receive push-options]
// S: PKT-LINE(push-option)
// S: ... ...
// S: flush-pkt
hookOptions := private.HookOptions{
UserName: pusherName,
UserID: pusherID,
}
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.RefFullNames = make([]string, 0, hookBatchSize)
for {
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
rs, err = readPktLine(reader, pktLineTypeUnknow)
if err != nil {
return err
}
if rs.Type == pktLineTypeFlush {
break
}
t := strings.SplitN(string(rs.Data), " ", 3)
if len(t) != 3 {
continue
}
hookOptions.OldCommitIDs = append(hookOptions.OldCommitIDs, t[0])
hookOptions.NewCommitIDs = append(hookOptions.NewCommitIDs, t[1])
hookOptions.RefFullNames = append(hookOptions.RefFullNames, t[2])
}
hookOptions.GitPushOptions = make(map[string]string)
if hasPushOptions {
for {
rs, err = readPktLine(reader, pktLineTypeUnknow)
if err != nil {
return err
}
if rs.Type == pktLineTypeFlush {
break
}
kv := strings.SplitN(string(rs.Data), "=", 2)
if len(kv) == 2 {
hookOptions.GitPushOptions[kv[0]] = kv[1]
}
}
}
// 3. run hook
resp, err := private.HookProcReceive(ctx, repoUser, repoName, hookOptions)
if err != nil {
return fail("Internal Server Error", "run proc-receive hook failed :%v", err)
}
// 4. response result to service
// # a. OK, but has an alternate reference. The alternate reference name
// # and other status can be given in option directives.
// H: PKT-LINE(ok <ref>)
// H: PKT-LINE(option refname <refname>)
// H: PKT-LINE(option old-oid <old-oid>)
// H: PKT-LINE(option new-oid <new-oid>)
// H: PKT-LINE(option forced-update)
// H: ... ...
// H: flush-pkt
// # b. NO, I reject it.
// H: PKT-LINE(ng <ref> <reason>)
// # c. Fall through, let 'receive-pack' to execute it.
// H: PKT-LINE(ok <ref>)
// H: PKT-LINE(option fall-through)
for _, rs := range resp.Results {
if len(rs.Err) > 0 {
err = writeDataPktLine(os.Stdout, []byte("ng "+rs.OriginalRef+" "+rs.Err))
if err != nil {
return err
}
continue
}
if rs.IsNotMatched {
err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
if err != nil {
return err
}
err = writeDataPktLine(os.Stdout, []byte("option fall-through"))
if err != nil {
return err
}
continue
}
err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
if err != nil {
return err
}
err = writeDataPktLine(os.Stdout, []byte("option refname "+rs.Ref))
if err != nil {
return err
}
if rs.OldOID != git.EmptySHA {
err = writeDataPktLine(os.Stdout, []byte("option old-oid "+rs.OldOID))
if err != nil {
return err
}
}
err = writeDataPktLine(os.Stdout, []byte("option new-oid "+rs.NewOID))
if err != nil {
return err
}
if rs.IsForcePush {
err = writeDataPktLine(os.Stdout, []byte("option forced-update"))
if err != nil {
return err
}
}
}
err = writeFlushPktLine(os.Stdout)
return err
}
// git PKT-Line api
// pktLineType message type of pkt-line
type pktLineType int64
const (
// UnKnow type
pktLineTypeUnknow pktLineType = 0
// flush-pkt "0000"
pktLineTypeFlush pktLineType = iota
// data line
pktLineTypeData
)
// gitPktLine pkt-line api
type gitPktLine struct {
Type pktLineType
Length uint64
Data []byte
}
func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
var (
err error
r *gitPktLine
)
// read prefix
lengthBytes := make([]byte, 4)
for i := 0; i < 4; i++ {
lengthBytes[i], err = in.ReadByte()
if err != nil {
return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
}
}
r = new(gitPktLine)
r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
if err != nil {
return nil, fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
}
if r.Length == 0 {
if requestType == pktLineTypeData {
return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
}
r.Type = pktLineTypeFlush
return r, nil
}
if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush {
return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
}
r.Data = make([]byte, r.Length-4)
for i := range r.Data {
r.Data[i], err = in.ReadByte()
if err != nil {
return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
}
}
r.Type = pktLineTypeData
return r, nil
}
func writeFlushPktLine(out io.Writer) error {
l, err := out.Write([]byte("0000"))
if err != nil {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
if l != 4 {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
return nil
}
func writeDataPktLine(out io.Writer, data []byte) error {
hexchar := []byte("0123456789abcdef")
hex := func(n uint64) byte {
return hexchar[(n)&15]
}
length := uint64(len(data) + 4)
tmp := make([]byte, 4)
tmp[0] = hex(length >> 12)
tmp[1] = hex(length >> 8)
tmp[2] = hex(length >> 4)
tmp[3] = hex(length)
lr, err := out.Write(tmp)
if err != nil {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
if 4 != lr {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
lr, err = out.Write(data)
if err != nil {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
if int(length-4) != lr {
return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
}
return nil
}

View File

@@ -1,41 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"bufio"
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPktLine(t *testing.T) {
// test read
s := strings.NewReader("0000")
r := bufio.NewReader(s)
result, err := readPktLine(r, pktLineTypeFlush)
assert.NoError(t, err)
assert.Equal(t, pktLineTypeFlush, result.Type)
s = strings.NewReader("0006a\n")
r = bufio.NewReader(s)
result, err = readPktLine(r, pktLineTypeData)
assert.NoError(t, err)
assert.Equal(t, pktLineTypeData, result.Type)
assert.Equal(t, []byte("a\n"), result.Data)
// test write
w := bytes.NewBuffer([]byte{})
err = writeFlushPktLine(w)
assert.NoError(t, err)
assert.Equal(t, []byte("0000"), w.Bytes())
w.Reset()
err = writeDataPktLine(w, []byte("a\nb"))
assert.NoError(t, err)
assert.Equal(t, []byte("0007a\nb"), w.Bytes())
}

View File

@@ -62,12 +62,9 @@ func runKeys(c *cli.Context) error {
return errors.New("No key type and content provided") return errors.New("No key type and content provided")
} }
ctx, cancel := installSignals()
defer cancel()
setup("keys.log", false) setup("keys.log", false)
authorizedString, err := private.AuthorizedPublicKeyByContent(ctx, content) authorizedString, err := private.AuthorizedPublicKeyByContent(content)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -10,15 +10,11 @@ import (
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func runSendMail(c *cli.Context) error { func runSendMail(c *cli.Context) error {
ctx, cancel := installSignals() setting.NewContext()
defer cancel()
setting.LoadFromExisting()
if err := argsSet(c, "title"); err != nil { if err := argsSet(c, "title"); err != nil {
return err return err
@@ -43,7 +39,7 @@ func runSendMail(c *cli.Context) error {
} }
} }
status, message := private.SendEmail(ctx, subject, body, nil) status, message := private.SendEmail(subject, body, nil)
if status != http.StatusOK { if status != http.StatusOK {
fmt.Printf("error: %s\n", message) fmt.Printf("error: %s\n", message)
return nil return nil

View File

@@ -1,23 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
)
func init() {
setting.SetCustomPathAndConf("", "", "")
setting.LoadForTest()
}
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: "..",
})
}

View File

@@ -10,6 +10,7 @@ import (
"os" "os"
"time" "time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -26,7 +27,6 @@ var (
subcmdRestart, subcmdRestart,
subcmdFlushQueues, subcmdFlushQueues,
subcmdLogging, subcmdLogging,
subCmdProcesses,
}, },
} }
subcmdShutdown = cli.Command{ subcmdShutdown = cli.Command{
@@ -58,8 +58,7 @@ var (
Name: "timeout", Name: "timeout",
Value: 60 * time.Second, Value: 60 * time.Second,
Usage: "Timeout for the flushing process", Usage: "Timeout for the flushing process",
}, }, cli.BoolFlag{
cli.BoolFlag{
Name: "non-blocking", Name: "non-blocking",
Usage: "Set to true to not wait for flush to complete before returning", Usage: "Set to true to not wait for flush to complete before returning",
}, },
@@ -68,47 +67,326 @@ var (
}, },
}, },
} }
subCmdProcesses = cli.Command{ defaultLoggingFlags = []cli.Flag{
Name: "processes", cli.StringFlag{
Usage: "Display running processes within the current process", Name: "group, g",
Action: runProcesses, Usage: "Group to add logger to - will default to \"default\"",
Flags: []cli.Flag{ }, cli.StringFlag{
cli.BoolFlag{ Name: "name, n",
Name: "debug", Usage: "Name of the new logger - will default to mode",
}, }, cli.StringFlag{
cli.BoolFlag{ Name: "level, l",
Name: "flat", Usage: "Logging level for the new logger",
Usage: "Show processes as flat table rather than as tree", }, cli.StringFlag{
}, Name: "stacktrace-level, L",
cli.BoolFlag{ Usage: "Stacktrace logging level",
Name: "no-system", }, cli.StringFlag{
Usage: "Do not show system proceses", Name: "flags, F",
}, Usage: "Flags for the logger",
cli.BoolFlag{ }, cli.StringFlag{
Name: "stacktraces", Name: "expression, e",
Usage: "Show stacktraces", Usage: "Matching expression for the logger",
}, }, cli.StringFlag{
cli.BoolFlag{ Name: "prefix, p",
Name: "json", Usage: "Prefix for the logger",
Usage: "Output as json", }, cli.BoolFlag{
}, Name: "color",
cli.StringFlag{ Usage: "Use color in the logs",
Name: "cancel", }, cli.BoolFlag{
Usage: "Process PID to cancel. (Only available for non-system processes.)", Name: "debug",
},
}
subcmdLogging = cli.Command{
Name: "logging",
Usage: "Adjust logging commands",
Subcommands: []cli.Command{
{
Name: "pause",
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runPauseLogging,
}, {
Name: "resume",
Usage: "Resume logging",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runResumeLogging,
}, {
Name: "release-and-reopen",
Usage: "Cause Gitea to release and re-open files used for logging",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runReleaseReopenLogging,
}, {
Name: "remove",
Usage: "Remove a logger",
ArgsUsage: "[name] Name of logger to remove",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
}, cli.StringFlag{
Name: "group, g",
Usage: "Group to add logger to - will default to \"default\"",
},
},
Action: runRemoveLogger,
}, {
Name: "add",
Usage: "Add a logger",
Subcommands: []cli.Command{
{
Name: "console",
Usage: "Add a console logger",
Flags: append(defaultLoggingFlags,
cli.BoolFlag{
Name: "stderr",
Usage: "Output console logs to stderr - only relevant for console",
}),
Action: runAddConsoleLogger,
}, {
Name: "file",
Usage: "Add a file logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.StringFlag{
Name: "filename, f",
Usage: "Filename for the logger - this must be set.",
}, cli.BoolTFlag{
Name: "rotate, r",
Usage: "Rotate logs",
}, cli.Int64Flag{
Name: "max-size, s",
Usage: "Maximum size in bytes before rotation",
}, cli.BoolTFlag{
Name: "daily, d",
Usage: "Rotate logs daily",
}, cli.IntFlag{
Name: "max-days, D",
Usage: "Maximum number of daily logs to keep",
}, cli.BoolTFlag{
Name: "compress, z",
Usage: "Compress rotated logs",
}, cli.IntFlag{
Name: "compression-level, Z",
Usage: "Compression level to use",
},
}...),
Action: runAddFileLogger,
}, {
Name: "conn",
Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.BoolFlag{
Name: "reconnect-on-message, R",
Usage: "Reconnect to host for every message",
}, cli.BoolFlag{
Name: "reconnect, r",
Usage: "Reconnect to host when connection is dropped",
}, cli.StringFlag{
Name: "protocol, P",
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)",
}, cli.StringFlag{
Name: "address, a",
Usage: "Host address and port to connect to (defaults to :7020)",
},
}...),
Action: runAddConnLogger,
}, {
Name: "smtp",
Usage: "Add an SMTP logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.StringFlag{
Name: "username, u",
Usage: "Mail server username",
}, cli.StringFlag{
Name: "password, P",
Usage: "Mail server password",
}, cli.StringFlag{
Name: "host, H",
Usage: "Mail server host (defaults to: 127.0.0.1:25)",
}, cli.StringSliceFlag{
Name: "send-to, s",
Usage: "Email address(es) to send to",
}, cli.StringFlag{
Name: "subject, S",
Usage: "Subject header of sent emails",
},
}...),
Action: runAddSMTPLogger,
},
},
}, },
}, },
} }
) )
func runShutdown(c *cli.Context) error { func runRemoveLogger(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug")) setup("manager", c.Bool("debug"))
statusCode, msg := private.Shutdown(ctx) group := c.String("group")
if len(group) == 0 {
group = log.DEFAULT
}
name := c.Args().First()
statusCode, msg := private.RemoveLogger(group, name)
switch statusCode { switch statusCode {
case http.StatusInternalServerError: case http.StatusInternalServerError:
return fail("InternalServerError", msg) fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runAddSMTPLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "smtp"
if c.IsSet("host") {
vals["host"] = c.String("host")
} else {
vals["host"] = "127.0.0.1:25"
}
if c.IsSet("username") {
vals["username"] = c.String("username")
}
if c.IsSet("password") {
vals["password"] = c.String("password")
}
if !c.IsSet("send-to") {
return fmt.Errorf("Some recipients must be provided")
}
vals["sendTos"] = c.StringSlice("send-to")
if c.IsSet("subject") {
vals["subject"] = c.String("subject")
} else {
vals["subject"] = "Diagnostic message from Gitea"
}
return commonAddLogger(c, mode, vals)
}
func runAddConnLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "conn"
vals["net"] = "tcp"
if c.IsSet("protocol") {
switch c.String("protocol") {
case "udp":
vals["net"] = "udp"
case "unix":
vals["net"] = "unix"
}
}
if c.IsSet("address") {
vals["address"] = c.String("address")
} else {
vals["address"] = ":7020"
}
if c.IsSet("reconnect") {
vals["reconnect"] = c.Bool("reconnect")
}
if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
}
return commonAddLogger(c, mode, vals)
}
func runAddFileLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "file"
if c.IsSet("filename") {
vals["filename"] = c.String("filename")
} else {
return fmt.Errorf("filename must be set when creating a file logger")
}
if c.IsSet("rotate") {
vals["rotate"] = c.Bool("rotate")
}
if c.IsSet("max-size") {
vals["maxsize"] = c.Int64("max-size")
}
if c.IsSet("daily") {
vals["daily"] = c.Bool("daily")
}
if c.IsSet("max-days") {
vals["maxdays"] = c.Int("max-days")
}
if c.IsSet("compress") {
vals["compress"] = c.Bool("compress")
}
if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level")
}
return commonAddLogger(c, mode, vals)
}
func runAddConsoleLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "console"
if c.IsSet("stderr") && c.Bool("stderr") {
vals["stderr"] = c.Bool("stderr")
}
return commonAddLogger(c, mode, vals)
}
func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error {
if len(c.String("level")) > 0 {
vals["level"] = log.FromString(c.String("level")).String()
}
if len(c.String("stacktrace-level")) > 0 {
vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String()
}
if len(c.String("expression")) > 0 {
vals["expression"] = c.String("expression")
}
if len(c.String("prefix")) > 0 {
vals["prefix"] = c.String("prefix")
}
if len(c.String("flags")) > 0 {
vals["flags"] = log.FlagsFromString(c.String("flags"))
}
if c.IsSet("color") {
vals["colorize"] = c.Bool("color")
}
group := "default"
if c.IsSet("group") {
group = c.String("group")
}
name := mode
if c.IsSet("name") {
name = c.String("name")
}
statusCode, msg := private.AddLogger(group, name, mode, vals)
switch statusCode {
case http.StatusInternalServerError:
fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runShutdown(c *cli.Context) error {
setup("manager", c.Bool("debug"))
statusCode, msg := private.Shutdown()
switch statusCode {
case http.StatusInternalServerError:
fail("InternalServerError", msg)
} }
fmt.Fprintln(os.Stdout, msg) fmt.Fprintln(os.Stdout, msg)
@@ -116,14 +394,11 @@ func runShutdown(c *cli.Context) error {
} }
func runRestart(c *cli.Context) error { func runRestart(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug")) setup("manager", c.Bool("debug"))
statusCode, msg := private.Restart(ctx) statusCode, msg := private.Restart()
switch statusCode { switch statusCode {
case http.StatusInternalServerError: case http.StatusInternalServerError:
return fail("InternalServerError", msg) fail("InternalServerError", msg)
} }
fmt.Fprintln(os.Stdout, msg) fmt.Fprintln(os.Stdout, msg)
@@ -131,30 +406,49 @@ func runRestart(c *cli.Context) error {
} }
func runFlushQueues(c *cli.Context) error { func runFlushQueues(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug")) setup("manager", c.Bool("debug"))
statusCode, msg := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) statusCode, msg := private.FlushQueues(c.Duration("timeout"), c.Bool("non-blocking"))
switch statusCode { switch statusCode {
case http.StatusInternalServerError: case http.StatusInternalServerError:
return fail("InternalServerError", msg) fail("InternalServerError", msg)
} }
fmt.Fprintln(os.Stdout, msg) fmt.Fprintln(os.Stdout, msg)
return nil return nil
} }
func runProcesses(c *cli.Context) error { func runPauseLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug")) setup("manager", c.Bool("debug"))
statusCode, msg := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) statusCode, msg := private.PauseLogging()
switch statusCode { switch statusCode {
case http.StatusInternalServerError: case http.StatusInternalServerError:
return fail("InternalServerError", msg) fail("InternalServerError", msg)
} }
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runResumeLogging(c *cli.Context) error {
setup("manager", c.Bool("debug"))
statusCode, msg := private.ResumeLogging()
switch statusCode {
case http.StatusInternalServerError:
fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runReleaseReopenLogging(c *cli.Context) error {
setup("manager", c.Bool("debug"))
statusCode, msg := private.ReleaseReopenLogging()
switch statusCode {
case http.StatusInternalServerError:
fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil return nil
} }

View File

@@ -1,383 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"fmt"
"net/http"
"os"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli"
)
var (
defaultLoggingFlags = []cli.Flag{
cli.StringFlag{
Name: "group, g",
Usage: "Group to add logger to - will default to \"default\"",
}, cli.StringFlag{
Name: "name, n",
Usage: "Name of the new logger - will default to mode",
}, cli.StringFlag{
Name: "level, l",
Usage: "Logging level for the new logger",
}, cli.StringFlag{
Name: "stacktrace-level, L",
Usage: "Stacktrace logging level",
}, cli.StringFlag{
Name: "flags, F",
Usage: "Flags for the logger",
}, cli.StringFlag{
Name: "expression, e",
Usage: "Matching expression for the logger",
}, cli.StringFlag{
Name: "prefix, p",
Usage: "Prefix for the logger",
}, cli.BoolFlag{
Name: "color",
Usage: "Use color in the logs",
}, cli.BoolFlag{
Name: "debug",
},
}
subcmdLogging = cli.Command{
Name: "logging",
Usage: "Adjust logging commands",
Subcommands: []cli.Command{
{
Name: "pause",
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runPauseLogging,
}, {
Name: "resume",
Usage: "Resume logging",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runResumeLogging,
}, {
Name: "release-and-reopen",
Usage: "Cause Gitea to release and re-open files used for logging",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
},
},
Action: runReleaseReopenLogging,
}, {
Name: "remove",
Usage: "Remove a logger",
ArgsUsage: "[name] Name of logger to remove",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "debug",
}, cli.StringFlag{
Name: "group, g",
Usage: "Group to add logger to - will default to \"default\"",
},
},
Action: runRemoveLogger,
}, {
Name: "add",
Usage: "Add a logger",
Subcommands: []cli.Command{
{
Name: "console",
Usage: "Add a console logger",
Flags: append(defaultLoggingFlags,
cli.BoolFlag{
Name: "stderr",
Usage: "Output console logs to stderr - only relevant for console",
}),
Action: runAddConsoleLogger,
}, {
Name: "file",
Usage: "Add a file logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.StringFlag{
Name: "filename, f",
Usage: "Filename for the logger - this must be set.",
}, cli.BoolTFlag{
Name: "rotate, r",
Usage: "Rotate logs",
}, cli.Int64Flag{
Name: "max-size, s",
Usage: "Maximum size in bytes before rotation",
}, cli.BoolTFlag{
Name: "daily, d",
Usage: "Rotate logs daily",
}, cli.IntFlag{
Name: "max-days, D",
Usage: "Maximum number of daily logs to keep",
}, cli.BoolTFlag{
Name: "compress, z",
Usage: "Compress rotated logs",
}, cli.IntFlag{
Name: "compression-level, Z",
Usage: "Compression level to use",
},
}...),
Action: runAddFileLogger,
}, {
Name: "conn",
Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.BoolFlag{
Name: "reconnect-on-message, R",
Usage: "Reconnect to host for every message",
}, cli.BoolFlag{
Name: "reconnect, r",
Usage: "Reconnect to host when connection is dropped",
}, cli.StringFlag{
Name: "protocol, P",
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)",
}, cli.StringFlag{
Name: "address, a",
Usage: "Host address and port to connect to (defaults to :7020)",
},
}...),
Action: runAddConnLogger,
}, {
Name: "smtp",
Usage: "Add an SMTP logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
cli.StringFlag{
Name: "username, u",
Usage: "Mail server username",
}, cli.StringFlag{
Name: "password, P",
Usage: "Mail server password",
}, cli.StringFlag{
Name: "host, H",
Usage: "Mail server host (defaults to: 127.0.0.1:25)",
}, cli.StringSliceFlag{
Name: "send-to, s",
Usage: "Email address(es) to send to",
}, cli.StringFlag{
Name: "subject, S",
Usage: "Subject header of sent emails",
},
}...),
Action: runAddSMTPLogger,
},
},
},
},
}
)
func runRemoveLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
group := c.String("group")
if len(group) == 0 {
group = log.DEFAULT
}
name := c.Args().First()
ctx, cancel := installSignals()
defer cancel()
statusCode, msg := private.RemoveLogger(ctx, group, name)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runAddSMTPLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "smtp"
if c.IsSet("host") {
vals["host"] = c.String("host")
} else {
vals["host"] = "127.0.0.1:25"
}
if c.IsSet("username") {
vals["username"] = c.String("username")
}
if c.IsSet("password") {
vals["password"] = c.String("password")
}
if !c.IsSet("send-to") {
return fmt.Errorf("Some recipients must be provided")
}
vals["sendTos"] = c.StringSlice("send-to")
if c.IsSet("subject") {
vals["subject"] = c.String("subject")
} else {
vals["subject"] = "Diagnostic message from Gitea"
}
return commonAddLogger(c, mode, vals)
}
func runAddConnLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "conn"
vals["net"] = "tcp"
if c.IsSet("protocol") {
switch c.String("protocol") {
case "udp":
vals["net"] = "udp"
case "unix":
vals["net"] = "unix"
}
}
if c.IsSet("address") {
vals["address"] = c.String("address")
} else {
vals["address"] = ":7020"
}
if c.IsSet("reconnect") {
vals["reconnect"] = c.Bool("reconnect")
}
if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
}
return commonAddLogger(c, mode, vals)
}
func runAddFileLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "file"
if c.IsSet("filename") {
vals["filename"] = c.String("filename")
} else {
return fmt.Errorf("filename must be set when creating a file logger")
}
if c.IsSet("rotate") {
vals["rotate"] = c.Bool("rotate")
}
if c.IsSet("max-size") {
vals["maxsize"] = c.Int64("max-size")
}
if c.IsSet("daily") {
vals["daily"] = c.Bool("daily")
}
if c.IsSet("max-days") {
vals["maxdays"] = c.Int("max-days")
}
if c.IsSet("compress") {
vals["compress"] = c.Bool("compress")
}
if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level")
}
return commonAddLogger(c, mode, vals)
}
func runAddConsoleLogger(c *cli.Context) error {
setup("manager", c.Bool("debug"))
vals := map[string]interface{}{}
mode := "console"
if c.IsSet("stderr") && c.Bool("stderr") {
vals["stderr"] = c.Bool("stderr")
}
return commonAddLogger(c, mode, vals)
}
func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error {
if len(c.String("level")) > 0 {
vals["level"] = log.FromString(c.String("level")).String()
}
if len(c.String("stacktrace-level")) > 0 {
vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String()
}
if len(c.String("expression")) > 0 {
vals["expression"] = c.String("expression")
}
if len(c.String("prefix")) > 0 {
vals["prefix"] = c.String("prefix")
}
if len(c.String("flags")) > 0 {
vals["flags"] = log.FlagsFromString(c.String("flags"))
}
if c.IsSet("color") {
vals["colorize"] = c.Bool("color")
}
group := "default"
if c.IsSet("group") {
group = c.String("group")
}
name := mode
if c.IsSet("name") {
name = c.String("name")
}
ctx, cancel := installSignals()
defer cancel()
statusCode, msg := private.AddLogger(ctx, group, name, mode, vals)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runPauseLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug"))
statusCode, msg := private.PauseLogging(ctx)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runResumeLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug"))
statusCode, msg := private.ResumeLogging(ctx)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}
func runReleaseReopenLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup("manager", c.Bool("debug"))
statusCode, msg := private.ReleaseReopenLogging(ctx)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}
fmt.Fprintln(os.Stdout, msg)
return nil
}

View File

@@ -7,7 +7,7 @@ package cmd
import ( import (
"context" "context"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/models/migrations"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@@ -24,20 +24,17 @@ var CmdMigrate = cli.Command{
} }
func runMigrate(ctx *cli.Context) error { func runMigrate(ctx *cli.Context) error {
stdCtx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }
log.Info("AppPath: %s", setting.AppPath) log.Trace("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Trace("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath) log.Trace("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath) log.Trace("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf) setting.InitDBConfig()
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil { if err := models.NewEngine(context.Background(), migrations.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err) log.Fatal("Failed to initialize ORM engine: %v", err)
return err return err
} }

View File

@@ -9,14 +9,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/models/migrations"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
@@ -27,13 +22,13 @@ import (
var CmdMigrateStorage = cli.Command{ var CmdMigrateStorage = cli.Command{
Name: "migrate-storage", Name: "migrate-storage",
Usage: "Migrate the storage", Usage: "Migrate the storage",
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", Description: "This is a command for migrating storage.",
Action: runMigrateStorage, Action: runMigrateStorage,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "type, t", Name: "type, t",
Value: "", Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages'", Usage: "Kinds of files to migrate, currently only 'attachments' is supported",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "storage, s", Name: "storage, s",
@@ -82,69 +77,52 @@ var CmdMigrateStorage = cli.Command{
}, },
} }
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateAttachments(dstStorage storage.ObjectStorage) error {
return db.IterateObjects(ctx, func(attach *repo_model.Attachment) error { return models.IterateAttachment(func(attach *models.Attachment) error {
_, err := storage.Copy(dstStorage, attach.RelativePath(), storage.Attachments, attach.RelativePath()) _, err := storage.Copy(dstStorage, attach.RelativePath(), storage.Attachments, attach.RelativePath())
return err return err
}) })
} }
func migrateLFS(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateLFS(dstStorage storage.ObjectStorage) error {
return db.IterateObjects(ctx, func(mo *git_model.LFSMetaObject) error { return models.IterateLFS(func(mo *models.LFSMetaObject) error {
_, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath()) _, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath())
return err return err
}) })
} }
func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateAvatars(dstStorage storage.ObjectStorage) error {
return db.IterateObjects(ctx, func(user *user_model.User) error { return models.IterateUser(func(user *models.User) error {
_, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath()) _, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath())
return err return err
}) })
} }
func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateRepoAvatars(dstStorage storage.ObjectStorage) error {
return db.IterateObjects(ctx, func(repo *repo_model.Repository) error { return models.IterateRepository(func(repo *models.Repository) error {
_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath()) _, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
return err return err
}) })
} }
func migrateRepoArchivers(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.IterateObjects(ctx, func(archiver *repo_model.RepoArchiver) error {
p := archiver.RelativePath()
_, err := storage.Copy(dstStorage, p, storage.RepoArchives, p)
return err
})
}
func migratePackages(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.IterateObjects(ctx, func(pb *packages_model.PackageBlob) error {
p := packages_module.KeyToRelativePath(packages_module.BlobHash256Key(pb.HashSHA256))
_, err := storage.Copy(dstStorage, p, storage.Packages, p)
return err
})
}
func runMigrateStorage(ctx *cli.Context) error { func runMigrateStorage(ctx *cli.Context) error {
stdCtx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }
log.Info("AppPath: %s", setting.AppPath) log.Trace("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Trace("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath) log.Trace("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath) log.Trace("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf) setting.InitDBConfig()
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil { if err := models.NewEngine(context.Background(), migrations.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err) log.Fatal("Failed to initialize ORM engine: %v", err)
return err return err
} }
goCtx := context.Background()
if err := storage.Init(); err != nil { if err := storage.Init(); err != nil {
return err return err
} }
@@ -161,13 +139,13 @@ func runMigrateStorage(ctx *cli.Context) error {
return nil return nil
} }
dstStorage, err = storage.NewLocalStorage( dstStorage, err = storage.NewLocalStorage(
stdCtx, goCtx,
storage.LocalStorageConfig{ storage.LocalStorageConfig{
Path: p, Path: p,
}) })
case string(storage.MinioStorageType): case string(storage.MinioStorageType):
dstStorage, err = storage.NewMinioStorage( dstStorage, err = storage.NewMinioStorage(
stdCtx, goCtx,
storage.MinioStorageConfig{ storage.MinioStorageConfig{
Endpoint: ctx.String("minio-endpoint"), Endpoint: ctx.String("minio-endpoint"),
AccessKeyID: ctx.String("minio-access-key-id"), AccessKeyID: ctx.String("minio-access-key-id"),
@@ -178,29 +156,35 @@ func runMigrateStorage(ctx *cli.Context) error {
UseSSL: ctx.Bool("minio-use-ssl"), UseSSL: ctx.Bool("minio-use-ssl"),
}) })
default: default:
return fmt.Errorf("unsupported storage type: %s", ctx.String("storage")) return fmt.Errorf("Unsupported storage type: %s", ctx.String("storage"))
} }
if err != nil { if err != nil {
return err return err
} }
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
"attachments": migrateAttachments,
"lfs": migrateLFS,
"avatars": migrateAvatars,
"repo-avatars": migrateRepoAvatars,
"repo-archivers": migrateRepoArchivers,
"packages": migratePackages,
}
tp := strings.ToLower(ctx.String("type")) tp := strings.ToLower(ctx.String("type"))
if m, ok := migratedMethods[tp]; ok { switch tp {
if err := m(stdCtx, dstStorage); err != nil { case "attachments":
if err := migrateAttachments(dstStorage); err != nil {
return err return err
} }
log.Info("%s files have successfully been copied to the new storage.", tp) case "lfs":
return nil if err := migrateLFS(dstStorage); err != nil {
return err
}
case "avatars":
if err := migrateAvatars(dstStorage); err != nil {
return err
}
case "repo-avatars":
if err := migrateRepoAvatars(dstStorage); err != nil {
return err
}
default:
return fmt.Errorf("Unsupported storage: %s", ctx.String("type"))
} }
return fmt.Errorf("unsupported storage: %s", ctx.String("type")) log.Warn("All files have been copied to the new placement but old files are still on the orignial placement.")
return nil
} }

View File

@@ -1,74 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"context"
"os"
"strings"
"testing"
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/storage"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/stretchr/testify/assert"
)
func TestMigratePackages(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
creator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
content := "package main\n\nfunc main() {\nfmt.Println(\"hi\")\n}\n"
buf, err := packages_module.CreateHashedBufferFromReader(strings.NewReader(content), 1024)
assert.NoError(t, err)
defer buf.Close()
v, f, err := packages_service.CreatePackageAndAddFile(&packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{
Owner: creator,
PackageType: packages.TypeGeneric,
Name: "test",
Version: "1.0.0",
},
Creator: creator,
SemverCompatible: true,
VersionProperties: map[string]string{},
}, &packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: "a.go",
},
Data: buf,
IsLead: true,
})
assert.NoError(t, err)
assert.NotNil(t, v)
assert.NotNil(t, f)
ctx := context.Background()
p, err := os.MkdirTemp(os.TempDir(), "migrated_packages")
assert.NoError(t, err)
dstStorage, err := storage.NewLocalStorage(
ctx,
storage.LocalStorageConfig{
Path: p,
})
assert.NoError(t, err)
err = migratePackages(ctx, dstStorage)
assert.NoError(t, err)
entries, err := os.ReadDir(p)
assert.NoError(t, err)
assert.EqualValues(t, 2, len(entries))
assert.EqualValues(t, "01", entries[0].Name())
assert.EqualValues(t, "tmp", entries[1].Name())
}

View File

@@ -5,13 +5,15 @@
package cmd package cmd
import ( import (
"errors" "context"
"net/http"
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
pull_service "code.gitea.io/gitea/services/pull"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -41,37 +43,77 @@ var CmdRestoreRepository = cli.Command{
cli.StringFlag{ cli.StringFlag{
Name: "units", Name: "units",
Value: "", Value: "",
Usage: `Which items will be restored, one or more units should be separated as comma. Usage: `Which items will be restored, one or more units should be separated as comma.
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
}, },
cli.BoolFlag{
Name: "validation",
Usage: "Sanity check the content of the files before trying to load them",
},
}, },
} }
func runRestoreRepository(c *cli.Context) error { func runRestoreRepository(ctx *cli.Context) error {
ctx, cancel := installSignals() if err := initDB(); err != nil {
defer cancel() return err
setting.LoadFromExisting()
var units []string
if s := c.String("units"); s != "" {
units = strings.Split(s, ",")
}
statusCode, errStr := private.RestoreRepo(
ctx,
c.String("repo_dir"),
c.String("owner_name"),
c.String("repo_name"),
units,
c.Bool("validation"),
)
if statusCode == http.StatusOK {
return nil
} }
log.Fatal("Failed to restore repository: %v", errStr) log.Trace("AppPath: %s", setting.AppPath)
return errors.New(errStr) log.Trace("AppWorkPath: %s", setting.AppWorkPath)
log.Trace("Custom path: %s", setting.CustomPath)
log.Trace("Log path: %s", setting.LogRootPath)
setting.InitDBConfig()
if err := storage.Init(); err != nil {
return err
}
if err := pull_service.Init(); err != nil {
return err
}
var opts = base.MigrateOptions{
RepoName: ctx.String("repo_name"),
}
if len(ctx.String("units")) == 0 {
opts.Wiki = true
opts.Issues = true
opts.Milestones = true
opts.Labels = true
opts.Releases = true
opts.Comments = true
opts.PullRequests = true
opts.ReleaseAssets = true
} else {
units := strings.Split(ctx.String("units"), ",")
for _, unit := range units {
switch strings.ToLower(unit) {
case "wiki":
opts.Wiki = true
case "issues":
opts.Issues = true
case "milestones":
opts.Milestones = true
case "labels":
opts.Labels = true
case "releases":
opts.Releases = true
case "release_assets":
opts.ReleaseAssets = true
case "comments":
opts.Comments = true
case "pull_requests":
opts.PullRequests = true
}
}
}
if err := migrations.RestoreRepository(
context.Background(),
ctx.String("repo_dir"),
ctx.String("owner_name"),
ctx.String("repo_name"),
); err != nil {
log.Fatal("Failed to restore repository: %v", err)
return err
}
return nil
} }

View File

@@ -6,7 +6,6 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@@ -17,20 +16,15 @@ import (
"strings" "strings"
"time" "time"
asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/pprof" "code.gitea.io/gitea/modules/pprof"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/process"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/lfs"
"github.com/golang-jwt/jwt/v4" "github.com/dgrijalva/jwt-go"
jsoniter "github.com/json-iterator/go"
"github.com/kballard/go-shellquote" "github.com/kballard/go-shellquote"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -62,61 +56,35 @@ func setup(logPath string, debug bool) {
} else { } else {
_ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`) _ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`)
} }
setting.LoadFromExisting() setting.NewContext()
if debug { if debug {
setting.RunMode = "dev" setting.RunMode = "dev"
} }
// Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
// `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
if _, err := os.Stat(setting.RepoRootPath); err != nil {
if os.IsNotExist(err) {
_ = fail("Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath)
} else {
_ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err)
}
return
}
if err := git.InitSimple(context.Background()); err != nil {
_ = fail("Failed to init git", "Failed to init git, err: %v", err)
}
} }
var ( var (
allowedCommands = map[string]perm.AccessMode{ allowedCommands = map[string]models.AccessMode{
"git-upload-pack": perm.AccessModeRead, "git-upload-pack": models.AccessModeRead,
"git-upload-archive": perm.AccessModeRead, "git-upload-archive": models.AccessModeRead,
"git-receive-pack": perm.AccessModeWrite, "git-receive-pack": models.AccessModeWrite,
lfsAuthenticateVerb: perm.AccessModeNone, lfsAuthenticateVerb: models.AccessModeNone,
} }
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`) alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
) )
func fail(userMessage, logMessage string, args ...interface{}) error { func fail(userMessage, logMessage string, args ...interface{}) {
// There appears to be a chance to cause a zombie process and failure to read the Exit status fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
// if nothing is outputted on stdout.
_, _ = fmt.Fprintln(os.Stdout, "")
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
if len(logMessage) > 0 { if len(logMessage) > 0 {
if !setting.IsProd { if !setting.IsProd() {
_, _ = fmt.Fprintf(os.Stderr, logMessage+"\n", args...) fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
} }
} }
ctx, cancel := installSignals()
defer cancel()
if len(logMessage) > 0 { os.Exit(1)
_ = private.SSHLog(ctx, true, fmt.Sprintf(logMessage+": ", args...))
}
return cli.NewExitError("", 1)
} }
func runServ(c *cli.Context) error { func runServ(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
// FIXME: This needs to internationalised // FIXME: This needs to internationalised
setup("serv.log", c.Bool("debug")) setup("serv.log", c.Bool("debug"))
@@ -134,23 +102,23 @@ func runServ(c *cli.Context) error {
keys := strings.Split(c.Args()[0], "-") keys := strings.Split(c.Args()[0], "-")
if len(keys) != 2 || keys[0] != "key" { if len(keys) != 2 || keys[0] != "key" {
return fail("Key ID format error", "Invalid key argument: %s", c.Args()[0]) fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
} }
keyID, err := strconv.ParseInt(keys[1], 10, 64) keyID, err := strconv.ParseInt(keys[1], 10, 64)
if err != nil { if err != nil {
return fail("Key ID format error", "Invalid key argument: %s", c.Args()[1]) fail("Key ID format error", "Invalid key argument: %s", c.Args()[1])
} }
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
key, user, err := private.ServNoCommand(ctx, keyID) key, user, err := private.ServNoCommand(keyID)
if err != nil { if err != nil {
return fail("Internal error", "Failed to check provided key: %v", err) fail("Internal error", "Failed to check provided key: %v", err)
} }
switch key.Type { switch key.Type {
case asymkey_model.KeyTypeDeploy: case models.KeyTypeDeploy:
println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.") println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.")
case asymkey_model.KeyTypePrincipal: case models.KeyTypePrincipal:
println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Gitea does not provide shell access.") println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Gitea does not provide shell access.")
default: default:
println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.") println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.")
@@ -163,18 +131,11 @@ func runServ(c *cli.Context) error {
words, err := shellquote.Split(cmd) words, err := shellquote.Split(cmd)
if err != nil { if err != nil {
return fail("Error parsing arguments", "Failed to parse arguments: %v", err) fail("Error parsing arguments", "Failed to parse arguments: %v", err)
} }
if len(words) < 2 { if len(words) < 2 {
if git.CheckGitVersionAtLeast("2.29") == nil { fail("Too few arguments", "Too few arguments in cmd: %s", cmd)
// for AGit Flow
if cmd == "ssh_info" {
fmt.Print(`{"type":"gitea","version":1}`)
return nil
}
}
return fail("Too few arguments", "Too few arguments in cmd: %s", cmd)
} }
verb := words[0] verb := words[0]
@@ -186,7 +147,7 @@ func runServ(c *cli.Context) error {
var lfsVerb string var lfsVerb string
if verb == lfsAuthenticateVerb { if verb == lfsAuthenticateVerb {
if !setting.LFS.StartServer { if !setting.LFS.StartServer {
return fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled") fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
} }
if len(words) > 2 { if len(words) > 2 {
@@ -199,70 +160,82 @@ func runServ(c *cli.Context) error {
rr := strings.SplitN(repoPath, "/", 2) rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 { if len(rr) != 2 {
return fail("Invalid repository path", "Invalid repository path: %v", repoPath) fail("Invalid repository path", "Invalid repository path: %v", repoPath)
} }
username := strings.ToLower(rr[0]) username := strings.ToLower(rr[0])
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
if alphaDashDotPattern.MatchString(reponame) { if alphaDashDotPattern.MatchString(reponame) {
return fail("Invalid repo name", "Invalid repo name: %s", reponame) fail("Invalid repo name", "Invalid repo name: %s", reponame)
} }
if c.Bool("enable-pprof") { if setting.EnablePprof || c.Bool("enable-pprof") {
if err := os.MkdirAll(setting.PprofDataPath, os.ModePerm); err != nil { if err := os.MkdirAll(setting.PprofDataPath, os.ModePerm); err != nil {
return fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err) fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err)
} }
stopCPUProfiler, err := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username) stopCPUProfiler, err := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username)
if err != nil { if err != nil {
return fail("Internal Server Error", "Unable to start CPU profile: %v", err) fail("Internal Server Error", "Unable to start CPU profile: %v", err)
} }
defer func() { defer func() {
stopCPUProfiler() stopCPUProfiler()
err := pprof.DumpMemProfileForUsername(setting.PprofDataPath, username) err := pprof.DumpMemProfileForUsername(setting.PprofDataPath, username)
if err != nil { if err != nil {
_ = fail("Internal Server Error", "Unable to dump Mem Profile: %v", err) fail("Internal Server Error", "Unable to dump Mem Profile: %v", err)
} }
}() }()
} }
requestedMode, has := allowedCommands[verb] requestedMode, has := allowedCommands[verb]
if !has { if !has {
return fail("Unknown git command", "Unknown git command %s", verb) fail("Unknown git command", "Unknown git command %s", verb)
} }
if verb == lfsAuthenticateVerb { if verb == lfsAuthenticateVerb {
if lfsVerb == "upload" { if lfsVerb == "upload" {
requestedMode = perm.AccessModeWrite requestedMode = models.AccessModeWrite
} else if lfsVerb == "download" { } else if lfsVerb == "download" {
requestedMode = perm.AccessModeRead requestedMode = models.AccessModeRead
} else { } else {
return fail("Unknown LFS verb", "Unknown lfs verb %s", lfsVerb) fail("Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
} }
} }
results, err := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb) results, err := private.ServCommand(keyID, username, reponame, requestedMode, verb, lfsVerb)
if err != nil { if err != nil {
if private.IsErrServCommand(err) { if private.IsErrServCommand(err) {
errServCommand := err.(private.ErrServCommand) errServCommand := err.(private.ErrServCommand)
if errServCommand.StatusCode != http.StatusInternalServerError { if errServCommand.StatusCode != http.StatusInternalServerError {
return fail("Unauthorized", "%s", errServCommand.Error()) fail("Unauthorized", "%s", errServCommand.Error())
} else {
fail("Internal Server Error", "%s", errServCommand.Error())
} }
return fail("Internal Server Error", "%s", errServCommand.Error())
} }
return fail("Internal Server Error", "%s", err.Error()) fail("Internal Server Error", "%s", err.Error())
} }
os.Setenv(models.EnvRepoIsWiki, strconv.FormatBool(results.IsWiki))
os.Setenv(models.EnvRepoName, results.RepoName)
os.Setenv(models.EnvRepoUsername, results.OwnerName)
os.Setenv(models.EnvPusherName, results.UserName)
os.Setenv(models.EnvPusherEmail, results.UserEmail)
os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10))
os.Setenv(models.EnvRepoID, strconv.FormatInt(results.RepoID, 10))
os.Setenv(models.EnvPRID, fmt.Sprintf("%d", 0))
os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey))
os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID))
os.Setenv(models.EnvAppURL, setting.AppURL)
// LFS token authentication //LFS token authentication
if verb == lfsAuthenticateVerb { if verb == lfsAuthenticateVerb {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName)) url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
now := time.Now() now := time.Now()
claims := lfs.Claims{ claims := lfs.Claims{
RegisteredClaims: jwt.RegisteredClaims{ StandardClaims: jwt.StandardClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)), ExpiresAt: now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
NotBefore: jwt.NewNumericDate(now), NotBefore: now.Unix(),
}, },
RepoID: results.RepoID, RepoID: results.RepoID,
Op: lfsVerb, Op: lfsVerb,
@@ -273,19 +246,20 @@ func runServ(c *cli.Context) error {
// Sign and get the complete encoded token as a string using the secret // Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes) tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
if err != nil { if err != nil {
return fail("Internal error", "Failed to sign JWT token: %v", err) fail("Internal error", "Failed to sign JWT token: %v", err)
} }
tokenAuthentication := &git_model.LFSTokenResponse{ tokenAuthentication := &models.LFSTokenResponse{
Header: make(map[string]string), Header: make(map[string]string),
Href: url, Href: url,
} }
tokenAuthentication.Header["Authorization"] = fmt.Sprintf("Bearer %s", tokenString) tokenAuthentication.Header["Authorization"] = fmt.Sprintf("Bearer %s", tokenString)
json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(os.Stdout) enc := json.NewEncoder(os.Stdout)
err = enc.Encode(tokenAuthentication) err = enc.Encode(tokenAuthentication)
if err != nil { if err != nil {
return fail("Internal error", "Failed to encode LFS json response: %v", err) fail("Internal error", "Failed to encode LFS json response: %v", err)
} }
return nil return nil
} }
@@ -298,42 +272,23 @@ func runServ(c *cli.Context) error {
var gitcmd *exec.Cmd var gitcmd *exec.Cmd
verbs := strings.Split(verb, " ") verbs := strings.Split(verb, " ")
if len(verbs) == 2 { if len(verbs) == 2 {
gitcmd = exec.CommandContext(ctx, verbs[0], verbs[1], repoPath) gitcmd = exec.Command(verbs[0], verbs[1], repoPath)
} else { } else {
gitcmd = exec.CommandContext(ctx, verb, repoPath) gitcmd = exec.Command(verb, repoPath)
} }
process.SetSysProcAttribute(gitcmd)
gitcmd.Dir = setting.RepoRootPath gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin gitcmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr gitcmd.Stderr = os.Stderr
gitcmd.Env = append(gitcmd.Env, os.Environ()...)
gitcmd.Env = append(gitcmd.Env,
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
repo_module.EnvRepoName+"="+results.RepoName,
repo_module.EnvRepoUsername+"="+results.OwnerName,
repo_module.EnvPusherName+"="+results.UserName,
repo_module.EnvPusherEmail+"="+results.UserEmail,
repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10),
repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10),
repo_module.EnvPRID+"="+fmt.Sprintf("%d", 0),
repo_module.EnvDeployKeyID+"="+fmt.Sprintf("%d", results.DeployKeyID),
repo_module.EnvKeyID+"="+fmt.Sprintf("%d", results.KeyID),
repo_module.EnvAppURL+"="+setting.AppURL,
)
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...)
if err = gitcmd.Run(); err != nil { if err = gitcmd.Run(); err != nil {
return fail("Internal error", "Failed to execute git command: %v", err) fail("Internal error", "Failed to execute git command: %v", err)
} }
// Update user key activity. // Update user key activity.
if results.KeyID > 0 { if results.KeyID > 0 {
if err = private.UpdatePublicKeyInRepo(ctx, results.KeyID, results.RepoID); err != nil { if err = private.UpdatePublicKeyInRepo(results.KeyID, results.RepoID); err != nil {
return fail("Internal error", "UpdatePublicKeyInRepo: %v", err) fail("Internal error", "UpdatePublicKeyInRepo: %v", err)
} }
} }

View File

@@ -9,19 +9,18 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
"os" "os"
"strings" "strings"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/install" "code.gitea.io/gitea/routers/routes"
"github.com/felixge/fgprof" context2 "github.com/gorilla/context"
"github.com/urfave/cli" "github.com/urfave/cli"
ini "gopkg.in/ini.v1" ini "gopkg.in/ini.v1"
) )
@@ -49,21 +48,10 @@ and it takes care of all the other things for you`,
Value: setting.PIDFile, Value: setting.PIDFile,
Usage: "Custom pid file path", Usage: "Custom pid file path",
}, },
cli.BoolFlag{
Name: "quiet, q",
Usage: "Only display Fatal logging errors until logging is set-up",
},
cli.BoolFlag{
Name: "verbose",
Usage: "Set initial logging to TRACE level until logging is properly set-up",
},
}, },
} }
func runHTTPRedirector() { func runHTTPRedirector() {
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: HTTP Redirector", process.SystemProcessType, true)
defer finished()
source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect) source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect)
dest := strings.TrimSuffix(setting.AppURL, "/") dest := strings.TrimSuffix(setting.AppURL, "/")
log.Info("Redirecting: %s to %s", source, dest) log.Info("Redirecting: %s to %s", source, dest)
@@ -76,26 +64,14 @@ func runHTTPRedirector() {
http.Redirect(w, r, target, http.StatusTemporaryRedirect) http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}) })
err := runHTTP("tcp", source, "HTTP Redirector", handler) var err = runHTTP("tcp", source, "HTTP Redirector", context2.ClearHandler(handler))
if err != nil { if err != nil {
log.Fatal("Failed to start port redirection: %v", err) log.Fatal("Failed to start port redirection: %v", err)
} }
} }
func runWeb(ctx *cli.Context) error { func runWeb(ctx *cli.Context) error {
if ctx.Bool("verbose") {
_ = log.DelLogger("console")
log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "trace", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
} else if ctx.Bool("quiet") {
_ = log.DelLogger("console")
log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "fatal", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
}
defer func() {
if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
}
}()
managerCtx, cancel := context.WithCancel(context.Background()) managerCtx, cancel := context.WithCancel(context.Background())
graceful.InitManager(managerCtx) graceful.InitManager(managerCtx)
defer cancel() defer cancel()
@@ -113,7 +89,7 @@ func runWeb(ctx *cli.Context) error {
} }
// Perform pre-initialization // Perform pre-initialization
needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext()) needsInstall := routers.PreInstallInit(graceful.GetManager().HammerContext())
if needsInstall { if needsInstall {
// Flag for port number in case first time run conflict // Flag for port number in case first time run conflict
if ctx.IsSet("port") { if ctx.IsSet("port") {
@@ -126,12 +102,8 @@ func runWeb(ctx *cli.Context) error {
return err return err
} }
} }
c := install.Routes() c := routes.InstallRoutes()
err := listen(c, false) err := listen(c, false)
if err != nil {
log.Critical("Unable to open listener for installer. Is Gitea already running?")
graceful.GetManager().DoGracefulShutdown()
}
select { select {
case <-graceful.GetManager().IsShutdown(): case <-graceful.GetManager().IsShutdown():
<-graceful.GetManager().Done() <-graceful.GetManager().Done()
@@ -146,25 +118,14 @@ func runWeb(ctx *cli.Context) error {
if setting.EnablePprof { if setting.EnablePprof {
go func() { go func() {
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
log.Info("Starting pprof server on localhost:6060") log.Info("Starting pprof server on localhost:6060")
log.Info("%v", http.ListenAndServe("localhost:6060", nil)) log.Info("%v", http.ListenAndServe("localhost:6060", nil))
finished()
}() }()
} }
log.Info("Global init") log.Info("Global init")
// Perform global initialization // Perform global initialization
setting.LoadFromExisting() routers.GlobalInit(graceful.GetManager().HammerContext())
routers.GlobalInitInstalled(graceful.GetManager().HammerContext())
// We check that AppDataPath exists here (it should have been created during installation)
// We can't check it in `GlobalInitInstalled`, because some integration tests
// use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests.
if _, err := os.Stat(setting.AppDataPath); err != nil {
log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath)
}
// Override the provided port number within the configuration // Override the provided port number within the configuration
if ctx.IsSet("port") { if ctx.IsSet("port") {
@@ -174,7 +135,7 @@ func runWeb(ctx *cli.Context) error {
} }
// Set up Chi routes // Set up Chi routes
c := routers.NormalRoutes() c := routes.NormalRoutes()
err := listen(c, true) err := listen(c, true)
<-graceful.GetManager().Done() <-graceful.GetManager().Done()
log.Info("PID: %d Gitea Web Finished", os.Getpid()) log.Info("PID: %d Gitea Web Finished", os.Getpid())
@@ -187,10 +148,23 @@ func setPort(port string) error {
setting.HTTPPort = port setting.HTTPPort = port
switch setting.Protocol { switch setting.Protocol {
case setting.HTTPUnix: case setting.UnixSocket:
case setting.FCGI: case setting.FCGI:
case setting.FCGIUnix: case setting.FCGIUnix:
default: default:
// Save LOCAL_ROOT_URL if port changed
cfg := ini.Empty()
isFile, err := util.IsFile(setting.CustomConf)
if err != nil {
log.Fatal("Unable to check if %s is a file", err)
}
if isFile {
// Keeps custom settings if there is already something.
if err := cfg.Append(setting.CustomConf); err != nil {
return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err)
}
}
defaultLocalURL := string(setting.Protocol) + "://" defaultLocalURL := string(setting.Protocol) + "://"
if setting.HTTPAddr == "0.0.0.0" { if setting.HTTPAddr == "0.0.0.0" {
defaultLocalURL += "localhost" defaultLocalURL += "localhost"
@@ -199,26 +173,20 @@ func setPort(port string) error {
} }
defaultLocalURL += ":" + setting.HTTPPort + "/" defaultLocalURL += ":" + setting.HTTPPort + "/"
// Save LOCAL_ROOT_URL if port changed cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
setting.CreateOrAppendToCustomConf(func(cfg *ini.File) { if err := cfg.SaveTo(setting.CustomConf); err != nil {
cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
}) }
} }
return nil return nil
} }
func listen(m http.Handler, handleRedirector bool) error { func listen(m http.Handler, handleRedirector bool) error {
listenAddr := setting.HTTPAddr listenAddr := setting.HTTPAddr
if setting.Protocol != setting.HTTPUnix && setting.Protocol != setting.FCGIUnix { if setting.Protocol != setting.UnixSocket && setting.Protocol != setting.FCGIUnix {
listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort) listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort)
} }
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: Gitea Server", process.SystemProcessType, true)
defer finished()
log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL) log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
// This can be useful for users, many users do wrong to their config and get strange behaviors behind a reverse-proxy.
// A user may fix the configuration mistake when he sees this log.
// And this is also very helpful to maintainers to provide help to users to resolve their configuration problems.
log.Info("AppURL(ROOT_URL): %s", setting.AppURL)
if setting.LFS.StartServer { if setting.LFS.StartServer {
log.Info("LFS server enabled") log.Info("LFS server enabled")
@@ -230,36 +198,35 @@ func listen(m http.Handler, handleRedirector bool) error {
if handleRedirector { if handleRedirector {
NoHTTPRedirector() NoHTTPRedirector()
} }
err = runHTTP("tcp", listenAddr, "Web", m) err = runHTTP("tcp", listenAddr, "Web", context2.ClearHandler(m))
case setting.HTTPS: case setting.HTTPS:
if setting.EnableAcme { if setting.EnableLetsEncrypt {
err = runACME(listenAddr, m) err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m))
break break
} else {
if handleRedirector {
if setting.RedirectOtherPort {
go runHTTPRedirector()
} else {
NoHTTPRedirector()
}
}
err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m)
} }
if handleRedirector {
if setting.RedirectOtherPort {
go runHTTPRedirector()
} else {
NoHTTPRedirector()
}
}
err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
case setting.FCGI: case setting.FCGI:
if handleRedirector { if handleRedirector {
NoHTTPRedirector() NoHTTPRedirector()
} }
err = runFCGI("tcp", listenAddr, "FCGI Web", m) err = runFCGI("tcp", listenAddr, "FCGI Web", context2.ClearHandler(m))
case setting.HTTPUnix: case setting.UnixSocket:
if handleRedirector { if handleRedirector {
NoHTTPRedirector() NoHTTPRedirector()
} }
err = runHTTP("unix", listenAddr, "Web", m) err = runHTTP("unix", listenAddr, "Web", context2.ClearHandler(m))
case setting.FCGIUnix: case setting.FCGIUnix:
if handleRedirector { if handleRedirector {
NoHTTPRedirector() NoHTTPRedirector()
} }
err = runFCGI("unix", listenAddr, "Web", m) err = runFCGI("unix", listenAddr, "Web", context2.ClearHandler(m))
default: default:
log.Fatal("Invalid protocol: %s", setting.Protocol) log.Fatal("Invalid protocol: %s", setting.Protocol)
} }

View File

@@ -1,136 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"crypto/x509"
"encoding/pem"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"github.com/caddyserver/certmagic"
)
func getCARoot(path string) (*x509.CertPool, error) {
r, err := os.ReadFile(path)
if err != nil {
return nil, err
}
block, _ := pem.Decode(r)
if block == nil {
return nil, fmt.Errorf("no PEM found in the file %s", path)
}
caRoot, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
certPool.AddCert(caRoot)
return certPool, nil
}
func runACME(listenAddr string, m http.Handler) error {
// If HTTP Challenge enabled, needs to be serving on port 80. For TLSALPN needs 443.
// Due to docker port mapping this can't be checked programmatically
// TODO: these are placeholders until we add options for each in settings with appropriate warning
enableHTTPChallenge := true
enableTLSALPNChallenge := true
altHTTPPort := 0
altTLSALPNPort := 0
if p, err := strconv.Atoi(setting.PortToRedirect); err == nil {
altHTTPPort = p
}
if p, err := strconv.Atoi(setting.HTTPPort); err == nil {
altTLSALPNPort = p
}
magic := certmagic.NewDefault()
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
// Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool
if setting.AcmeCARoot != "" {
var err error
certPool, err = getCARoot(setting.AcmeCARoot)
if err != nil {
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
}
}
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
CA: setting.AcmeURL,
TrustedRoots: certPool,
Email: setting.AcmeEmail,
Agreed: setting.AcmeTOS,
DisableHTTPChallenge: !enableHTTPChallenge,
DisableTLSALPNChallenge: !enableTLSALPNChallenge,
ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort,
})
magic.Issuers = []certmagic.Issuer{myACME}
// this obtains certificates or renews them if necessary
err := magic.ManageSync(graceful.GetManager().HammerContext(), []string{setting.Domain})
if err != nil {
return err
}
tlsConfig := magic.TLSConfig()
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
tlsConfig.MinVersion = version
}
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
tlsConfig.MaxVersion = version
}
// Set curve preferences
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
tlsConfig.CurvePreferences = curves
}
// Set cipher suites
if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
tlsConfig.CipherSuites = ciphers
}
if enableHTTPChallenge {
go func() {
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: ACME HTTP challenge server", process.SystemProcessType, true)
defer finished()
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)))
if err != nil {
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
}
}()
}
return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m)
}
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" && r.Method != "HEAD" {
http.Error(w, "Use HTTPS", http.StatusBadRequest)
return
}
// Remove the trailing slash at the end of setting.AppURL, the request
// URI always contains a leading slash, which would result in a double
// slash
target := strings.TrimSuffix(setting.AppURL, "/") + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}

View File

@@ -5,6 +5,7 @@
package cmd package cmd
import ( import (
"crypto/tls"
"net" "net"
"net/http" "net/http"
"net/http/fcgi" "net/http/fcgi"
@@ -19,6 +20,14 @@ func runHTTP(network, listenAddr, name string, m http.Handler) error {
return graceful.HTTPListenAndServe(network, listenAddr, name, m) return graceful.HTTPListenAndServe(network, listenAddr, name, m)
} }
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
return graceful.HTTPListenAndServeTLS(network, listenAddr, name, certFile, keyFile, m)
}
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
}
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector // NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
func NoHTTPRedirector() { func NoHTTPRedirector() {
graceful.GetManager().InformCleanup() graceful.GetManager().InformCleanup()

View File

@@ -1,192 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"crypto/tls"
"net/http"
"os"
"strings"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/klauspost/cpuid/v2"
)
var tlsVersionStringMap = map[string]uint16{
"": tls.VersionTLS12, // Default to tls.VersionTLS12
"tlsv1.0": tls.VersionTLS10,
"tlsv1.1": tls.VersionTLS11,
"tlsv1.2": tls.VersionTLS12,
"tlsv1.3": tls.VersionTLS13,
}
func toTLSVersion(version string) uint16 {
tlsVersion, ok := tlsVersionStringMap[strings.TrimSpace(strings.ToLower(version))]
if !ok {
log.Warn("Unknown tls version: %s", version)
return 0
}
return tlsVersion
}
var curveStringMap = map[string]tls.CurveID{
"x25519": tls.X25519,
"p256": tls.CurveP256,
"p384": tls.CurveP384,
"p521": tls.CurveP521,
}
func toCurvePreferences(preferences []string) []tls.CurveID {
ids := make([]tls.CurveID, 0, len(preferences))
for _, pref := range preferences {
id, ok := curveStringMap[strings.TrimSpace(strings.ToLower(pref))]
if !ok {
log.Warn("Unknown curve: %s", pref)
}
if id != 0 {
ids = append(ids, id)
}
}
return ids
}
var cipherStringMap = map[string]uint16{
"rsa_with_rc4_128_sha": tls.TLS_RSA_WITH_RC4_128_SHA,
"rsa_with_3des_ede_cbc_sha": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"rsa_with_aes_128_cbc_sha": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"rsa_with_aes_256_cbc_sha": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"rsa_with_aes_128_cbc_sha256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"rsa_with_aes_128_gcm_sha256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"rsa_with_aes_256_gcm_sha384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"ecdhe_ecdsa_with_rc4_128_sha": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"ecdhe_ecdsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"ecdhe_ecdsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"ecdhe_rsa_with_rc4_128_sha": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"ecdhe_rsa_with_3des_ede_cbc_sha": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"ecdhe_rsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"ecdhe_rsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"ecdhe_ecdsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"ecdhe_rsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"ecdhe_rsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"ecdhe_ecdsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"ecdhe_rsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"ecdhe_ecdsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"ecdhe_rsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
"ecdhe_ecdsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
"ecdhe_rsa_with_chacha20_poly1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"ecdhe_ecdsa_with_chacha20_poly1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
"aes_128_gcm_sha256": tls.TLS_AES_128_GCM_SHA256,
"aes_256_gcm_sha384": tls.TLS_AES_256_GCM_SHA384,
"chacha20_poly1305_sha256": tls.TLS_CHACHA20_POLY1305_SHA256,
}
func toTLSCiphers(cipherStrings []string) []uint16 {
ciphers := make([]uint16, 0, len(cipherStrings))
for _, cipherString := range cipherStrings {
cipher, ok := cipherStringMap[strings.TrimSpace(strings.ToLower(cipherString))]
if !ok {
log.Warn("Unknown cipher: %s", cipherString)
}
if cipher != 0 {
ciphers = append(ciphers, cipher)
}
}
return ciphers
}
// defaultCiphers uses hardware support to check if AES is specifically
// supported by the CPU.
//
// If AES is supported AES ciphers will be preferred over ChaCha based ciphers
// (This code is directly inspired by the certmagic code.)
func defaultCiphers() []uint16 {
if cpuid.CPU.Supports(cpuid.AESNI) {
return defaultCiphersAESfirst
}
return defaultCiphersChaChaFirst
}
var (
defaultCiphersAES = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
defaultCiphersChaCha = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
}
defaultCiphersAESfirst = append(defaultCiphersAES, defaultCiphersChaCha...)
defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...)
)
// runHTTPs listens on the provided network address and then calls
// Serve to handle requests on incoming TLS connections.
//
// Filenames containing a certificate and matching private key for the server must
// be provided. If the certificate is signed by a certificate authority, the
// certFile should be the concatenation of the server's certificate followed by the
// CA's certificate.
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
tlsConfig := &tls.Config{}
if tlsConfig.NextProtos == nil {
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
}
if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 {
tlsConfig.MinVersion = version
}
if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 {
tlsConfig.MaxVersion = version
}
// Set curve preferences
tlsConfig.CurvePreferences = []tls.CurveID{
tls.X25519,
tls.CurveP256,
}
if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 {
tlsConfig.CurvePreferences = curves
}
// Set cipher suites
tlsConfig.CipherSuites = defaultCiphers()
if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 {
tlsConfig.CipherSuites = ciphers
}
tlsConfig.Certificates = make([]tls.Certificate, 1)
certPEMBlock, err := os.ReadFile(certFile)
if err != nil {
log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, network, listenAddr, err)
return err
}
keyPEMBlock, err := os.ReadFile(keyFile)
if err != nil {
log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, network, listenAddr, err)
return err
}
tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
log.Error("Failed to create certificate from cert file %s and key file %s for %s:%s: %v", certFile, keyFile, network, listenAddr, err)
return err
}
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
}
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
}

69
cmd/web_letsencrypt.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"net/http"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/caddyserver/certmagic"
context2 "github.com/gorilla/context"
)
func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) error {
// If HTTP Challenge enabled, needs to be serving on port 80. For TLSALPN needs 443.
// Due to docker port mapping this can't be checked programatically
// TODO: these are placeholders until we add options for each in settings with appropriate warning
enableHTTPChallenge := true
enableTLSALPNChallenge := true
magic := certmagic.NewDefault()
magic.Storage = &certmagic.FileStorage{Path: directory}
myACME := certmagic.NewACMEManager(magic, certmagic.ACMEManager{
Email: email,
Agreed: setting.LetsEncryptTOS,
DisableHTTPChallenge: !enableHTTPChallenge,
DisableTLSALPNChallenge: !enableTLSALPNChallenge,
})
magic.Issuer = myACME
// this obtains certificates or renews them if necessary
err := magic.ManageSync([]string{domain})
if err != nil {
return err
}
tlsConfig := magic.TLSConfig()
if enableHTTPChallenge {
go func() {
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)))
if err != nil {
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
}
}()
}
return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, context2.ClearHandler(m))
}
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" && r.Method != "HEAD" {
http.Error(w, "Use HTTPS", http.StatusBadRequest)
return
}
// Remove the trailing slash at the end of setting.AppURL, the request
// URI always contains a leading slash, which would result in a double
// slash
target := strings.TrimSuffix(setting.AppURL, "/") + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusFound)
}

View File

@@ -110,8 +110,6 @@ func runEnvironmentToIni(c *cli.Context) error {
} }
cfg.NameMapper = ini.SnackCase cfg.NameMapper = ini.SnackCase
changed := false
prefix := c.String("prefix") + "__" prefix := c.String("prefix") + "__"
for _, kv := range os.Environ() { for _, kv := range os.Environ() {
@@ -145,22 +143,15 @@ func runEnvironmentToIni(c *cli.Context) error {
continue continue
} }
} }
oldValue := key.Value()
if !changed && oldValue != value {
changed = true
}
key.SetValue(value) key.SetValue(value)
} }
destination := c.String("out") destination := c.String("out")
if len(destination) == 0 { if len(destination) == 0 {
destination = setting.CustomConf destination = setting.CustomConf
} }
if destination != setting.CustomConf || changed { err = cfg.SaveTo(destination)
log.Info("Settings saved to: %q", destination) if err != nil {
err = cfg.SaveTo(destination) return err
if err != nil {
return err
}
} }
if c.Bool("clear") { if c.Bool("clear") {
for _, kv := range os.Environ() { for _, kv := range os.Environ() {
@@ -225,6 +216,7 @@ func DecodeSectionKey(encoded string) (string, string) {
if !inKey { if !inKey {
if splitter := strings.Index(remaining, "__"); splitter > -1 { if splitter := strings.Index(remaining, "__"); splitter > -1 {
section += remaining[:splitter] section += remaining[:splitter]
inKey = true
key += remaining[splitter+2:] key += remaining[splitter+2:]
} else { } else {
section += remaining section += remaining

View File

@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
############################################################################# ########################################################################
# This script sets some defaults for gitea to run in a FHS compliant manner # # This script some defaults for gitea to run in a FHS compliant manner #
############################################################################# ########################################################################
# It assumes that you place this script as gitea in /usr/bin # It assumes that you place this script as gitea in /usr/bin
# #
@@ -33,8 +33,10 @@ for i in "$@"; do
done done
if [ -z "$APP_INI_SET" ]; then if [ -z "$APP_INI_SET" ]; then
CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}") CONF_ARG="-c \"$APP_INI\""
fi fi
# Provide FHS compliant defaults # Provide FHS compliant defaults to
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}" "$@" GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" "$GITEA" $CONF_ARG "$@"

View File

@@ -6,11 +6,11 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/unittest"
) )
// To generate derivative fixtures, execute the following from Gitea's repository base dir: // To generate derivative fixtures, execute the following from Gitea's repository base dir:
@@ -31,13 +31,11 @@ var (
func main() { func main() {
pathToGiteaRoot := "." pathToGiteaRoot := "."
fixturesDir = filepath.Join(pathToGiteaRoot, "models", "fixtures") fixturesDir = filepath.Join(pathToGiteaRoot, "models", "fixtures")
if err := unittest.CreateTestEngine(unittest.FixturesOptions{ if err := models.CreateTestEngine(fixturesDir); err != nil {
Dir: fixturesDir,
}); err != nil {
fmt.Printf("CreateTestEngine: %+v", err) fmt.Printf("CreateTestEngine: %+v", err)
os.Exit(1) os.Exit(1)
} }
if err := unittest.PrepareTestDatabase(); err != nil { if err := models.PrepareTestDatabase(); err != nil {
fmt.Printf("PrepareTestDatabase: %+v\n", err) fmt.Printf("PrepareTestDatabase: %+v\n", err)
os.Exit(1) os.Exit(1)
} }
@@ -66,7 +64,7 @@ func generate(name string) error {
return err return err
} }
path := filepath.Join(fixturesDir, name+".yml") path := filepath.Join(fixturesDir, name+".yml")
if err := os.WriteFile(path, []byte(data), 0o644); err != nil { if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil {
return fmt.Errorf("%s: %+v", path, err) return fmt.Errorf("%s: %+v", path, err)
} }
fmt.Printf("%s created.\n", path) fmt.Printf("%s created.\n", path)

View File

@@ -1,2 +0,0 @@
dashboards_out
vendor

View File

@@ -1,31 +0,0 @@
JSONNET_FMT := jsonnetfmt -n 2 --max-blank-lines 1 --string-style s --comment-style s
.PHONY: all
all: build dashboards_out
vendor: jsonnetfile.json
jb install
.PHONY: build
build: vendor
.PHONY: fmt
fmt:
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
xargs -n 1 -- $(JSONNET_FMT) -i
.PHONY: lint
lint: build
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
while read f; do \
$(JSONNET_FMT) "$$f" | diff -u "$$f" -; \
done
mixtool lint mixin.libsonnet
dashboards_out: mixin.libsonnet config.libsonnet $(wildcard dashboards/*)
@mkdir -p dashboards_out
jsonnet -J vendor -m dashboards_out lib/dashboards.jsonnet
.PHONY: clean
clean:
rm -rf dashboards_out

View File

@@ -1,33 +0,0 @@
# Gitea Mixin
Gitea Mixin is a set of configurable Grafana dashboards based on the metrics exported by the Gitea built-in metrics endpoint.
## Generate config files
You can manually generate dashboards, but first you should install some tools:
```bash
go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest
go install github.com/google/go-jsonnet/cmd/jsonnet@latest
# or in brew: brew install go-jsonnet
```
For linting and formatting, you would also need `mixtool` and `jsonnetfmt` installed. If you
have a working Go development environment, it's easiest to run the following:
```bash
go install github.com/monitoring-mixins/mixtool/cmd/mixtool@latest
go install github.com/google/go-jsonnet/cmd/jsonnetfmt@latest
```
The files in `dashboards_out` need to be imported
into your Grafana server. The exact details will be depending on your environment.
Edit `config.libsonnet` if required and then build JSON dashboard files for Grafana:
```bash
make
```
For more advanced uses of mixins, see
https://github.com/monitoring-mixins/docs.

View File

@@ -1,99 +0,0 @@
{
_config+:: {
local c = self,
dashboardNamePrefix: 'Gitea',
dashboardTags: ['gitea'],
dashboardPeriod: 'now-1h',
dashboardTimezone: 'default',
dashboardRefresh: '1m',
// please see https://docs.gitea.io/en-us/config-cheat-sheet/#metrics-metrics
// Show issue by repository metrics with format gitea_issues_by_repository{repository="org/repo"} 5.
// Requires Gitea 1.16.0 with ENABLED_ISSUE_BY_REPOSITORY set to true.
showIssuesByRepository: true,
// Show graphs for issue by label metrics with format gitea_issues_by_label{label="bug"} 2.
// Requires Gitea 1.16.0 with ENABLED_ISSUE_BY_LABEL set to true.
showIssuesByLabel: true,
// Requires Gitea 1.16.0.
showIssuesOpenClose: true,
// add or remove metrics from dashboard
giteaStatMetrics:
[
{
name: 'gitea_organizations',
description: 'Organizations',
},
{
name: 'gitea_teams',
description: 'Teams',
},
{
name: 'gitea_users',
description: 'Users',
},
{
name: 'gitea_repositories',
description: 'Repositories',
},
{
name: 'gitea_milestones',
description: 'Milestones',
},
{
name: 'gitea_stars',
description: 'Stars',
},
{
name: 'gitea_releases',
description: 'Releases',
},
]
+
if c.showIssuesOpenClose then
[
{
name: 'gitea_issues_open',
description: 'Issues opened',
},
{
name: 'gitea_issues_closed',
description: 'Issues closed',
},
] else
[
{
name: 'gitea_issues',
description: 'Issues',
},
],
//set this for using label colors on graphs
issueLabels: [
{
label: 'bug',
color: '#ee0701',
},
{
label: 'duplicate',
color: '#cccccc',
},
{
label: 'invalid',
color: '#e6e6e6',
},
{
label: 'enhancement',
color: '#84b6eb',
},
{
label: 'help wanted',
color: '#128a0c',
},
{
label: 'question',
color: '#cc317c',
},
],
},
}

View File

@@ -1 +0,0 @@
(import 'overview.libsonnet')

View File

@@ -1,461 +0,0 @@
local grafana = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;
local addIssueLabelsOverrides(labels) =
{
fieldConfig+: {
overrides+: [
{
matcher: {
id: 'byRegexp',
options: label.label,
},
properties: [
{
id: 'color',
value: {
fixedColor: label.color,
mode: 'fixed',
},
},
],
}
for label in labels
],
},
};
{
grafanaDashboards+:: {
local giteaSelector = 'job="$job", instance="$instance"',
local giteaStatsPanel =
grafana.statPanel.new(
'Gitea stats',
datasource='$datasource',
reducerFunction='lastNotNull',
graphMode='none',
colorMode='value',
)
.addTargets(
[
prometheus.target(expr='%s{%s}' % [metric.name, giteaSelector], legendFormat=metric.description, intervalFactor=10)
for metric in $._config.giteaStatMetrics
]
)
+ {
fieldConfig+: {
defaults+: {
color: {
fixedColor: 'blue',
mode: 'fixed',
},
},
},
},
local giteaUptimePanel =
grafana.statPanel.new(
'Uptime',
datasource='$datasource',
reducerFunction='last',
graphMode='area',
colorMode='value',
)
.addTarget(prometheus.target(expr='time()-process_start_time_seconds{%s}' % giteaSelector, intervalFactor=1))
+ {
fieldConfig+: {
defaults+: {
color: {
fixedColor: 'blue',
mode: 'fixed',
},
unit: 's',
},
},
},
local giteaMemoryPanel =
grafana.graphPanel.new(
'Memory usage',
datasource='$datasource'
)
.addTarget(prometheus.target(expr='process_resident_memory_bytes{%s}' % giteaSelector, intervalFactor=2))
+ {
type: 'timeseries',
options+: {
tooltip: {
mode: 'multi',
},
legend+: {
displayMode: 'hidden',
},
},
fieldConfig+: {
defaults+: {
custom+: {
lineInterpolation: 'smooth',
fillOpacity: 15,
},
color: {
fixedColor: 'green',
mode: 'fixed',
},
unit: 'decbytes',
},
},
},
local giteaCpuPanel =
grafana.graphPanel.new(
'CPU usage',
datasource='$datasource'
)
.addTarget(prometheus.target(expr='rate(process_cpu_seconds_total{%s}[$__rate_interval])*100' % giteaSelector, intervalFactor=2))
+ {
type: 'timeseries',
options+: {
tooltip: {
mode: 'multi',
},
legend+: {
displayMode: 'hidden',
},
},
fieldConfig+: {
defaults+: {
custom+: {
lineInterpolation: 'smooth',
gradientMode: 'scheme',
fillOpacity: 15,
axisSoftMin: 0,
axisSoftMax: 0,
},
color: {
mode: 'continuous-GrYlRd', // from green to red (100%)
},
unit: 'percent',
},
overrides: [
{
matcher: {
id: 'byRegexp',
options: '.+',
},
properties: [
{
id: 'max',
value: 100,
},
{
id: 'min',
value: 0,
},
],
},
],
},
},
local giteaFileDescriptorsPanel =
grafana.graphPanel.new(
'File descriptors usage',
datasource='$datasource',
)
.addTarget(prometheus.target(expr='process_open_fds{%s}' % giteaSelector, intervalFactor=2))
.addTarget(prometheus.target(expr='process_max_fds{%s}' % giteaSelector, intervalFactor=2))
.addSeriesOverride(
{
alias: '/process_max_fds.+/',
color: '#F2495C', // red
dashes: true,
fill: 0,
},
)
+ {
type: 'timeseries',
options+: {
tooltip: {
mode: 'multi',
},
legend+: {
displayMode: 'hidden',
},
},
fieldConfig+: {
defaults+: {
custom+: {
lineInterpolation: 'smooth',
gradientMode: 'scheme',
fillOpacity: 0,
},
color: {
fixedColor: 'green',
mode: 'fixed',
},
unit: '',
},
overrides: [
{
matcher: {
id: 'byFrameRefID',
options: 'B',
},
properties: [
{
id: 'custom.lineStyle',
value: {
fill: 'dash',
dash: [
10,
10,
],
},
},
{
id: 'color',
value: {
mode: 'fixed',
fixedColor: 'red',
},
},
],
},
],
},
},
local giteaChangesPanelPrototype =
grafana.graphPanel.new(
'',
datasource='$datasource',
interval='$agg_interval',
maxDataPoints=10000,
)
+ {
type: 'timeseries',
options+: {
tooltip: {
mode: 'multi',
},
legend+: {
calcs+: [
'sum',
],
},
},
fieldConfig+: {
defaults+: {
noValue: '0',
custom+: {
drawStyle: 'bars',
barAlignment: -1,
fillOpacity: 50,
gradientMode: 'hue',
pointSize: 1,
lineWidth: 0,
stacking: {
group: 'A',
mode: 'normal',
},
},
},
},
},
local giteaChangesPanelAll =
giteaChangesPanelPrototype
.addTarget(prometheus.target(expr='changes(process_start_time_seconds{%s}[$__interval]) > 0' % [giteaSelector], legendFormat='Restarts', intervalFactor=1))
.addTargets(
[
prometheus.target(expr='floor(delta(%s{%s}[$__interval])) > 0' % [metric.name, giteaSelector], legendFormat=metric.description, intervalFactor=1)
for metric in $._config.giteaStatMetrics
]
) + { id: 200 }, // some unique number, beyond the maximum number of panels in the dashboard,
local giteaChangesPanelTotal =
grafana.statPanel.new(
'Changes',
datasource='-- Dashboard --',
reducerFunction='sum',
graphMode='none',
textMode='value_and_name',
colorMode='value',
)
+ {
targets+: [
{
panelId: giteaChangesPanelAll.id,
refId: 'A',
},
],
}
+ {
fieldConfig+: {
defaults+: {
color: {
mode: 'palette-classic',
},
},
},
},
local giteaChangesByRepositories =
giteaChangesPanelPrototype
.addTarget(prometheus.target(expr='floor(increase(gitea_issues_by_repository{%s}[$__interval])) > 0' % [giteaSelector], legendFormat='{{ repository }}', intervalFactor=1))
+ { id: 210 }, // some unique number, beyond the maximum number of panels in the dashboard,
local giteaChangesByRepositoriesTotal =
grafana.statPanel.new(
'Issues by repository',
datasource='-- Dashboard --',
reducerFunction='sum',
graphMode='none',
textMode='value_and_name',
colorMode='value',
)
+ {
id: 211,
targets+: [
{
panelId: giteaChangesByRepositories.id,
refId: 'A',
},
],
}
+ {
fieldConfig+: {
defaults+: {
color: {
mode: 'palette-classic',
},
},
},
},
local giteaChangesByLabel =
giteaChangesPanelPrototype
.addTarget(prometheus.target(expr='floor(increase(gitea_issues_by_label{%s}[$__interval])) > 0' % [giteaSelector], legendFormat='{{ label }}', intervalFactor=1))
+ addIssueLabelsOverrides($._config.issueLabels)
+ { id: 220 }, // some unique number, beyond the maximum number of panels in the dashboard,
local giteaChangesByLabelTotal =
grafana.statPanel.new(
'Issues by labels',
datasource='-- Dashboard --',
reducerFunction='sum',
graphMode='none',
textMode='value_and_name',
colorMode='value',
)
+ addIssueLabelsOverrides($._config.issueLabels)
+ {
id: 221,
targets+: [
{
panelId: giteaChangesByLabel.id,
refId: 'A',
},
],
}
+ {
fieldConfig+: {
defaults+: {
color: {
mode: 'palette-classic',
},
},
},
},
'gitea-overview.json':
grafana.dashboard.new(
'%s Overview' % $._config.dashboardNamePrefix,
time_from='%s' % $._config.dashboardPeriod,
editable=false,
tags=($._config.dashboardTags),
timezone='%s' % $._config.dashboardTimezone,
refresh='%s' % $._config.dashboardRefresh,
graphTooltip='shared_crosshair',
uid='gitea-overview'
)
.addTemplate(
{
current: {
text: 'Prometheus',
value: 'Prometheus',
},
hide: 0,
label: 'Data Source',
name: 'datasource',
options: [],
query: 'prometheus',
refresh: 1,
regex: '',
type: 'datasource',
},
)
.addTemplate(
{
hide: 0,
label: null,
name: 'job',
options: [],
query: 'label_values(gitea_organizations, job)',
refresh: 1,
regex: '',
type: 'query',
},
)
.addTemplate(
{
hide: 0,
label: null,
name: 'instance',
options: [],
query: 'label_values(gitea_organizations{job="$job"}, instance)',
refresh: 1,
regex: '',
type: 'query',
},
)
.addTemplate(
{
hide: 0,
label: 'aggregation interval',
name: 'agg_interval',
auto_min: '1m',
auto: true,
query: '1m,10m,1h,1d,7d',
type: 'interval',
},
)
.addPanel(grafana.row.new(title='General'), gridPos={ x: 0, y: 0, w: 0, h: 0 },)
.addPanel(giteaStatsPanel, gridPos={ x: 0, y: 0, w: 16, h: 4 })
.addPanel(giteaUptimePanel, gridPos={ x: 16, y: 0, w: 8, h: 4 })
.addPanel(giteaMemoryPanel, gridPos={ x: 0, y: 4, w: 8, h: 6 })
.addPanel(giteaCpuPanel, gridPos={ x: 8, y: 4, w: 8, h: 6 })
.addPanel(giteaFileDescriptorsPanel, gridPos={ x: 16, y: 4, w: 8, h: 6 })
.addPanel(grafana.row.new(title='Changes', collapse=false), gridPos={ x: 0, y: 10, w: 24, h: 8 })
.addPanel(giteaChangesPanelTotal, gridPos={ x: 0, y: 12, w: 6, h: 8 })
+ // use patching instead of .addPanel() to keep static ids
{
panels+: std.flattenArrays([
[
giteaChangesPanelAll { gridPos: { x: 6, y: 12, w: 18, h: 8 } },
],
if $._config.showIssuesByRepository then
[
giteaChangesByRepositoriesTotal { gridPos: { x: 0, y: 20, w: 6, h: 8 } },
giteaChangesByRepositories { gridPos: { x: 6, y: 20, w: 18, h: 8 } },
] else [],
if $._config.showIssuesByLabel then
[
giteaChangesByLabelTotal { gridPos: { x: 0, y: 28, w: 6, h: 8 } },
giteaChangesByLabel { gridPos: { x: 6, y: 28, w: 18, h: 8 } },
] else [],
]),
},
},
}

View File

@@ -1,15 +0,0 @@
{
"version": 1,
"dependencies": [
{
"source": {
"git": {
"remote": "https://github.com/grafana/grafonnet-lib.git",
"subdir": "grafonnet"
}
},
"version": "master"
}
],
"legacyImports": false
}

View File

@@ -1,16 +0,0 @@
{
"version": 1,
"dependencies": [
{
"source": {
"git": {
"remote": "https://github.com/grafana/grafonnet-lib.git",
"subdir": "grafonnet"
}
},
"version": "3626fc4dc2326931c530861ac5bebe39444f6cbf",
"sum": "gF8foHByYcB25jcUOBqP6jxk0OPifQMjPvKY0HaCk6w="
}
],
"legacyImports": false
}

View File

@@ -1 +0,0 @@
std.manifestYamlDoc((import '../mixin.libsonnet').prometheusAlerts)

View File

@@ -1,6 +0,0 @@
local dashboards = (import '../mixin.libsonnet').grafanaDashboards;
{
[name]: dashboards[name]
for name in std.objectFields(dashboards)
}

View File

@@ -1 +0,0 @@
std.manifestYamlDoc((import '../mixin.libsonnet').prometheusRules)

View File

@@ -1,2 +0,0 @@
(import 'dashboards/dashboards.libsonnet') +
(import 'config.libsonnet')

View File

@@ -7,10 +7,10 @@
"request": "launch", "request": "launch",
"mode": "debug", "mode": "debug",
"buildFlags": "", "buildFlags": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceRoot}/main.go", "program": "${workspaceRoot}/main.go",
"env": { "env": {},
"GITEA_WORK_DIR": "${workspaceRoot}",
},
"args": ["web"], "args": ["web"],
"showLog": true "showLog": true
}, },
@@ -20,10 +20,10 @@
"request": "launch", "request": "launch",
"mode": "debug", "mode": "debug",
"buildFlags": "-tags='sqlite sqlite_unlock_notify'", "buildFlags": "-tags='sqlite sqlite_unlock_notify'",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceRoot}/main.go", "program": "${workspaceRoot}/main.go",
"env": { "env": {},
"GITEA_WORK_DIR": "${workspaceRoot}",
},
"args": ["web"], "args": ["web"],
"showLog": true "showLog": true
} }

View File

@@ -1,35 +0,0 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
# PROCD_DEBUG=1
START=90
STOP=10
PROG=/opt/gitea/gitea
GITEA_WORK_DIR=/opt/gitea
CONF_FILE=$GITEA_WORK_DIR/app.ini
start_service(){
procd_open_instance gitea
procd_set_param env GITEA_WORK_DIR=$GITEA_WORK_DIR
procd_set_param env HOME=$GITEA_WORK_DIR
procd_set_param command $PROG web -c $CONF_FILE
procd_set_param file $CONF_FILE
procd_set_param user git
procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} # respawn automatically if something died, be careful if you have an alternative process supervisor
procd_close_instance
}
start(){
service_start $PROG
}
stop(){
service_stop $PROG
}
reload(){
service_reload $PROG
}

View File

@@ -12,6 +12,7 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
@@ -24,19 +25,18 @@ import (
"strconv" "strconv"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/unittest"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/external" "code.gitea.io/gitea/modules/markup/external"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
context2 "github.com/gorilla/context"
"xorm.io/xorm" "xorm.io/xorm"
) )
@@ -49,13 +49,13 @@ func runPR() {
log.Fatal(err) log.Fatal(err)
} }
setting.SetCustomPathAndConf("", "", "") setting.SetCustomPathAndConf("", "", "")
setting.LoadAllowEmpty() setting.NewContext()
setting.RepoRootPath, err = os.MkdirTemp(os.TempDir(), "repos") setting.RepoRootPath, err = ioutil.TempDir(os.TempDir(), "repos")
if err != nil { if err != nil {
log.Fatalf("TempDir: %v\n", err) log.Fatalf("TempDir: %v\n", err)
} }
setting.AppDataPath, err = os.MkdirTemp(os.TempDir(), "appdata") setting.AppDataPath, err = ioutil.TempDir(os.TempDir(), "appdata")
if err != nil { if err != nil {
log.Fatalf("TempDir: %v\n", err) log.Fatalf("TempDir: %v\n", err)
} }
@@ -80,45 +80,43 @@ func runPR() {
setting.RunUser = curUser.Username setting.RunUser = curUser.Username
log.Printf("[PR] Loading fixtures data ...\n") log.Printf("[PR] Loading fixtures data ...\n")
gitea_git.CheckLFSVersion() setting.CheckLFSVersion()
//models.LoadConfigs() //models.LoadConfigs()
/* /*
setting.Database.Type = "sqlite3" setting.Database.Type = "sqlite3"
setting.Database.Path = ":memory:" setting.Database.Path = ":memory:"
setting.Database.Timeout = 500 setting.Database.Timeout = 500
*/ */
dbCfg := setting.Cfg.Section("database") db := setting.Cfg.Section("database")
dbCfg.NewKey("DB_TYPE", "sqlite3") db.NewKey("DB_TYPE", "sqlite3")
dbCfg.NewKey("PATH", ":memory:") db.NewKey("PATH", ":memory:")
routers.InitGitServices() routers.NewServices()
setting.Database.LogSQL = true setting.Database.LogSQL = true
// x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") //x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
db.InitEngineWithMigration(context.Background(), func(_ *xorm.Engine) error { models.NewEngine(context.Background(), func(_ *xorm.Engine) error {
return nil return nil
}) })
db.HasEngine = true models.HasEngine = true
// x.ShowSQL(true) //x.ShowSQL(true)
err = unittest.InitFixtures( err = models.InitFixtures(
unittest.FixturesOptions{ path.Join(curDir, "models/fixtures/"),
Dir: path.Join(curDir, "models/fixtures/"),
},
) )
if err != nil { if err != nil {
fmt.Printf("Error initializing test database: %v\n", err) fmt.Printf("Error initializing test database: %v\n", err)
os.Exit(1) os.Exit(1)
} }
unittest.LoadFixtures() models.LoadFixtures()
util.RemoveAll(setting.RepoRootPath) util.RemoveAll(setting.RepoRootPath)
util.RemoveAll(repo_module.LocalCopyPath()) util.RemoveAll(models.LocalCopyPath())
unittest.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath) util.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath)
log.Printf("[PR] Setting up router\n") log.Printf("[PR] Setting up router\n")
// routers.GlobalInit() //routers.GlobalInit()
external.RegisterRenderers() external.RegisterParsers()
markup.Init() markup.Init()
c := routers.NormalRoutes() c := routes.NormalRoutes()
log.Printf("[PR] Ready for testing !\n") log.Printf("[PR] Ready for testing !\n")
log.Printf("[PR] Login with user1, user2, user3, ... with pass: password\n") log.Printf("[PR] Login with user1, user2, user3, ... with pass: password\n")
@@ -137,8 +135,8 @@ func runPR() {
} }
*/ */
// Start the server //Start the server
http.ListenAndServe(":8080", c) http.ListenAndServe(":8080", context2.ClearHandler(c))
log.Printf("[PR] Cleaning up ...\n") log.Printf("[PR] Cleaning up ...\n")
/* /*
@@ -160,7 +158,7 @@ func runPR() {
} }
func main() { func main() {
runPRFlag := flag.Bool("run", false, "Run the PR code") var runPRFlag = flag.Bool("run", false, "Run the PR code")
flag.Parse() flag.Parse()
if *runPRFlag { if *runPRFlag {
runPR() runPR()
@@ -173,16 +171,16 @@ func main() {
force = false force = false
} }
// Otherwise checkout PR //Otherwise checkout PR
if len(os.Args) != 2 { if len(os.Args) != 2 {
log.Fatal("Need only one arg: the PR number") log.Fatal("Need only one arg: the PR number")
} }
pr := os.Args[1] pr := os.Args[1]
codeFilePath = filepath.FromSlash(codeFilePath) // Convert to running OS codeFilePath = filepath.FromSlash(codeFilePath) //Convert to running OS
// Copy this file if it will not exist in the PR branch //Copy this file if it will not exist in the PR branch
dat, err := os.ReadFile(codeFilePath) dat, err := ioutil.ReadFile(codeFilePath)
if err != nil { if err != nil {
log.Fatalf("Failed to cache this code file : %v", err) log.Fatalf("Failed to cache this code file : %v", err)
} }
@@ -192,16 +190,16 @@ func main() {
log.Fatalf("Failed to open the repo : %v", err) log.Fatalf("Failed to open the repo : %v", err)
} }
// Find remote upstream //Find remote upstream
remotes, err := repo.Remotes() remotes, err := repo.Remotes()
if err != nil { if err != nil {
log.Fatalf("Failed to list remotes of repo : %v", err) log.Fatalf("Failed to list remotes of repo : %v", err)
} }
remoteUpstream := "origin" // Default remoteUpstream := "origin" //Default
for _, r := range remotes { for _, r := range remotes {
if r.Config().URLs[0] == "https://github.com/go-gitea/gitea.git" || if r.Config().URLs[0] == "https://github.com/go-gitea/gitea.git" ||
r.Config().URLs[0] == "https://github.com/go-gitea/gitea" || r.Config().URLs[0] == "https://github.com/go-gitea/gitea" ||
r.Config().URLs[0] == "git@github.com:go-gitea/gitea.git" { // fetch at index 0 r.Config().URLs[0] == "git@github.com:go-gitea/gitea.git" { //fetch at index 0
remoteUpstream = r.Config().Name remoteUpstream = r.Config().Name
break break
} }
@@ -212,10 +210,10 @@ func main() {
log.Printf("Fetching PR #%s in %s\n", pr, branch) log.Printf("Fetching PR #%s in %s\n", pr, branch)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Use git cli command for windows //Use git cli command for windows
runCmd("git", "fetch", remoteUpstream, fmt.Sprintf("pull/%s/head:%s", pr, branch)) runCmd("git", "fetch", remoteUpstream, fmt.Sprintf("pull/%s/head:%s", pr, branch))
} else { } else {
ref := fmt.Sprintf("%s%s/head:%s", gitea_git.PullPrefix, pr, branchRef) ref := fmt.Sprintf("refs/pull/%s/head:%s", pr, branchRef)
err = repo.Fetch(&git.FetchOptions{ err = repo.Fetch(&git.FetchOptions{
RemoteName: remoteUpstream, RemoteName: remoteUpstream,
RefSpecs: []config.RefSpec{ RefSpecs: []config.RefSpec{
@@ -240,23 +238,22 @@ func main() {
log.Fatalf("Failed to checkout %s : %v", branch, err) log.Fatalf("Failed to checkout %s : %v", branch, err)
} }
// Copy this file if not exist //Copy this file if not exist
if _, err := os.Stat(codeFilePath); os.IsNotExist(err) { if _, err := os.Stat(codeFilePath); os.IsNotExist(err) {
err = os.MkdirAll(filepath.Dir(codeFilePath), 0o755) err = os.MkdirAll(filepath.Dir(codeFilePath), 0755)
if err != nil { if err != nil {
log.Fatalf("Failed to duplicate this code file in PR : %v", err) log.Fatalf("Failed to duplicate this code file in PR : %v", err)
} }
err = os.WriteFile(codeFilePath, dat, 0o644) err = ioutil.WriteFile(codeFilePath, dat, 0644)
if err != nil { if err != nil {
log.Fatalf("Failed to duplicate this code file in PR : %v", err) log.Fatalf("Failed to duplicate this code file in PR : %v", err)
} }
} }
// Force build of js, css, bin, ... //Force build of js, css, bin, ...
runCmd("make", "build") runCmd("make", "build")
// Start with integration test //Start with integration test
runCmd("go", "run", "-mod", "vendor", "-tags", "sqlite sqlite_unlock_notify", codeFilePath, "-run") runCmd("go", "run", "-mod", "vendor", "-tags", "sqlite sqlite_unlock_notify", codeFilePath, "-run")
} }
func runCmd(cmd ...string) { func runCmd(cmd ...string) {
log.Printf("Executing : %s ...\n", cmd) log.Printf("Executing : %s ...\n", cmd)
c := exec.Command(cmd[0], cmd[1:]...) c := exec.Command(cmd[0], cmd[1:]...)

View File

@@ -3,23 +3,14 @@ Description=Gitea (Git with a cup of tea)
After=syslog.target After=syslog.target
After=network.target After=network.target
### ###
# Don't forget to add the database service dependencies # Don't forget to add the database service requirements
### ###
# #
#Wants=mysql.service #Requires=mysql.service
#After=mysql.service #Requires=mariadb.service
# #Requires=postgresql.service
#Wants=mariadb.service #Requires=memcached.service
#After=mariadb.service #Requires=redis.service
#
#Wants=postgresql.service
#After=postgresql.service
#
#Wants=memcached.service
#After=memcached.service
#
#Wants=redis.service
#After=redis.service
# #
### ###
# If using socket activation for main http/s # If using socket activation for main http/s

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
grep 'git' go.mod | grep '\.com' | grep -v indirect | grep -v replace | cut -f 2 | cut -d ' ' -f 1 | while read line; do
go get -u "$line"
make vendor
git add .
git commit -m "update $line"
done

View File

@@ -1,126 +0,0 @@
#!/usr/bin/env bash
# This is an update script for gitea installed via the binary distribution
# from dl.gitea.io on linux as systemd service. It performs a backup and updates
# Gitea in place.
# NOTE: This adds the GPG Signing Key of the Gitea maintainers to the keyring.
# Depends on: bash, curl, xz, sha256sum. optionally jq, gpg
# See section below for available environment vars.
# When no version is specified, updates to the latest release.
# Examples:
# upgrade.sh 1.15.10
# giteahome=/opt/gitea giteaconf=$giteahome/app.ini upgrade.sh
# apply variables from environment
: "${giteabin:="/usr/local/bin/gitea"}"
: "${giteahome:="/var/lib/gitea"}"
: "${giteaconf:="/etc/gitea/app.ini"}"
: "${giteauser:="git"}"
: "${sudocmd:="sudo"}"
: "${arch:="linux-amd64"}"
: "${service_start:="$sudocmd systemctl start gitea"}"
: "${service_stop:="$sudocmd systemctl stop gitea"}"
: "${service_status:="$sudocmd systemctl status gitea"}"
: "${backupopts:=""}" # see `gitea dump --help` for available options
function giteacmd {
if [[ $sudocmd = "su" ]]; then
# `-c` only accept one string as argument.
"$sudocmd" - "$giteauser" -c "$(printf "%q " "$giteabin" "--config" "$giteaconf" "--work-path" "$giteahome" "$@")"
else
"$sudocmd" --user "$giteauser" "$giteabin" --config "$giteaconf" --work-path "$giteahome" "$@"
fi
}
function require {
for exe in "$@"; do
command -v "$exe" &>/dev/null || (echo "missing dependency '$exe'"; exit 1)
done
}
# parse command line arguments
while true; do
case "$1" in
-v | --version ) giteaversion="$2"; shift 2 ;;
-y | --yes ) no_confirm="yes"; shift ;;
--ignore-gpg) ignore_gpg="yes"; shift ;;
"" | -- ) shift; break ;;
* ) echo "Usage: [<environment vars>] upgrade.sh [-v <version>] [-y] [--ignore-gpg]"; exit 1;;
esac
done
# exit once any command fails. this means that each step should be idempotent!
set -euo pipefail
if [[ -f /etc/os-release ]]; then
os_release=$(cat /etc/os-release)
if [[ "$os_release" =~ "OpenWrt" ]]; then
sudocmd="su"
service_start="/etc/init.d/gitea start"
service_stop="/etc/init.d/gitea stop"
service_status="/etc/init.d/gitea status"
else
require systemctl
fi
fi
require curl xz sha256sum "$sudocmd"
# select version to install
if [[ -z "${giteaversion:-}" ]]; then
require jq
giteaversion=$(curl --connect-timeout 10 -sL https://dl.gitea.io/gitea/version.json | jq -r .latest.version)
echo "Latest available version is $giteaversion"
fi
# confirm update
echo "Checking currently installed version..."
current=$(giteacmd --version | cut -d ' ' -f 3)
[[ "$current" == "$giteaversion" ]] && echo "$current is already installed, stopping." && exit 1
if [[ -z "${no_confirm:-}" ]]; then
echo "Make sure to read the changelog first: https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md"
echo "Are you ready to update Gitea from ${current} to ${giteaversion}? (y/N)"
read -r confirm
[[ "$confirm" == "y" ]] || [[ "$confirm" == "Y" ]] || exit 1
fi
echo "Upgrading gitea from $current to $giteaversion ..."
pushd "$(pwd)" &>/dev/null
cd "$giteahome" # needed for gitea dump later
# download new binary
binname="gitea-${giteaversion}-${arch}"
binurl="https://dl.gitea.io/gitea/${giteaversion}/${binname}.xz"
echo "Downloading $binurl..."
curl --connect-timeout 10 --silent --show-error --fail --location -O "$binurl{,.sha256,.asc}"
# validate checksum & gpg signature
sha256sum -c "${binname}.xz.sha256"
if [[ -z "${ignore_gpg:-}" ]]; then
require gpg
gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2
gpg --verify "${binname}.xz.asc" "${binname}.xz" || { echo 'Signature does not match'; exit 1; }
fi
rm "${binname}".xz.{sha256,asc}
# unpack binary + make executable
xz --decompress --force "${binname}.xz"
chown "$giteauser" "$binname"
chmod +x "$binname"
# stop gitea, create backup, replace binary, restart gitea
echo "Flushing gitea queues at $(date)"
giteacmd manager flush-queues
echo "Stopping gitea at $(date)"
$service_stop
echo "Creating backup in $giteahome"
giteacmd dump $backupopts
echo "Updating binary at $giteabin"
cp -f "$giteabin" "$giteabin.bak" && mv -f "$binname" "$giteabin"
$service_start
$service_status
echo "Upgrade to $giteaversion successful!"
popd

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,18 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}-rootless
{{#if build.tags}} {{#if build.tags}}
tags: tags:
{{#each build.tags}} {{#each build.tags}}
- {{this}}-rootless - {{this}}-rootless
{{/each}} {{/each}}
- "latest-rootless"
{{/if}} {{/if}}
manifests: manifests:
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64-rootless image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64-rootless
platform: platform:
architecture: amd64 architecture: amd64
os: linux os: linux
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64-rootless image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64-rootless
platform: platform:
architecture: arm64 architecture: arm64
os: linux os: linux

View File

@@ -1,19 +1,18 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}} image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}} {{#if build.tags}}
tags: tags:
{{#each build.tags}} {{#each build.tags}}
- {{this}} - {{this}}
{{/each}} {{/each}}
- "latest"
{{/if}} {{/if}}
manifests: manifests:
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64 image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform: platform:
architecture: amd64 architecture: amd64
os: linux os: linux
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64 image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform: platform:
architecture: arm64 architecture: arm64
os: linux os: linux

View File

@@ -2,5 +2,5 @@
[[ -f ./setup ]] && source ./setup [[ -f ./setup ]] && source ./setup
pushd /app/gitea >/dev/null pushd /app/gitea >/dev/null
exec su-exec $USER /usr/local/bin/gitea web exec su-exec $USER /app/gitea/gitea web
popd popd

View File

@@ -23,7 +23,7 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then
INSTALL_LOCK=true INSTALL_LOCK=true
fi fi
# Substitute the environment variables in the template # Substitude the environment variables in the template
APP_NAME=${APP_NAME:-"Gitea: Git with a cup of tea"} \ APP_NAME=${APP_NAME:-"Gitea: Git with a cup of tea"} \
RUN_MODE=${RUN_MODE:-"prod"} \ RUN_MODE=${RUN_MODE:-"prod"} \
DOMAIN=${DOMAIN:-"localhost"} \ DOMAIN=${DOMAIN:-"localhost"} \

View File

@@ -24,33 +24,9 @@ if [ ! -f /data/ssh/ssh_host_ecdsa_key ]; then
ssh-keygen -t ecdsa -b 256 -f /data/ssh/ssh_host_ecdsa_key -N "" > /dev/null ssh-keygen -t ecdsa -b 256 -f /data/ssh/ssh_host_ecdsa_key -N "" > /dev/null
fi fi
if [ -e /data/ssh/ssh_host_ed25519_cert ]; then
SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_cert"}
fi
if [ -e /data/ssh/ssh_host_rsa_cert ]; then
SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_cert"}
fi
if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
fi
if [ -e /data/ssh/ssh_host_dsa_cert ]; then
SSH_DSA_CERT=${SSH_DSA_CERT:-"/data/ssh/ssh_host_dsa_cert"}
fi
if [ -d /etc/ssh ]; then if [ -d /etc/ssh ]; then
SSH_PORT=${SSH_PORT:-"22"} \ SSH_PORT=${SSH_PORT:-"22"} \
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \ SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
SSH_ED25519_CERT="${SSH_ED25519_CERT:+"HostCertificate "}${SSH_ED25519_CERT}" \
SSH_RSA_CERT="${SSH_RSA_CERT:+"HostCertificate "}${SSH_RSA_CERT}" \
SSH_ECDSA_CERT="${SSH_ECDSA_CERT:+"HostCertificate "}${SSH_ECDSA_CERT}" \
SSH_DSA_CERT="${SSH_DSA_CERT:+"HostCertificate "}${SSH_DSA_CERT}" \
SSH_MAX_STARTUPS="${SSH_MAX_STARTUPS:+"MaxStartups "}${SSH_MAX_STARTUPS}" \
SSH_MAX_SESSIONS="${SSH_MAX_SESSIONS:+"MaxSessions "}${SSH_MAX_SESSIONS}" \
SSH_INCLUDE_FILE="${SSH_INCLUDE_FILE:+"Include "}${SSH_INCLUDE_FILE}" \
SSH_LOG_LEVEL=${SSH_LOG_LEVEL:-"INFO"} \
envsubst < /etc/templates/sshd_config > /etc/ssh/sshd_config envsubst < /etc/templates/sshd_config > /etc/ssh/sshd_config
chmod 0644 /etc/ssh/sshd_config chmod 0644 /etc/ssh/sshd_config

Some files were not shown because too many files have changed in this diff Show More