Compare commits

...

180 Commits

Author SHA1 Message Date
Unknwon
ee28fd9255 Update locales [CI SKIP] 2016-08-03 12:24:53 -07:00
Unknwon
13d492e92a Update .gopmfile and glide.lock 2016-08-03 12:12:33 -07:00
Unknwon
e7fd65f0cf #3290 better code structure and batch minor improvements 2016-08-03 11:51:22 -07:00
lstahlman
2eeb0ec9b0 Additional API support for labels (#3290)
* Add API support for labels.

* Error handling for adding/replacing multiple issue labels

* Revisions to function names and error handling. Use issue.ClearLabels in replace/clear functions

* Additional code cleanup
2016-08-03 09:24:16 -07:00
silverwind
b1133c9934 Lighter icon colors for repo files (#3351)
* Lighter icon colors for repo files

* also color submodule icon
2016-08-03 08:53:28 -07:00
Unknwon
991ce42c48 #2162 improve repository advance options UI display
Enable/disable input based on user chosen options for wiki and issue tracker.
2016-07-31 01:26:43 +08:00
Unknwon
10dc330640 #3345 dump content directly to HTTP ResponseWriter 2016-07-30 23:39:58 +08:00
Dennis Chen
dfab54d5a2 Diff patch (#3345)
* Add support for .diff and .patch

Add the ability to get text-diff and format-patch by adding .diff or
.patch in the end of a commit url. Issue #2641

* models: git_diff: various fixes

* Renames commitId to commitID.
* Writes stderr to a bytes.Buffer and displays proper error message on
command failure.
* Various style changes.

Signed-off-by: Dennis Chen <barracks510@gmail.com>
2016-07-30 23:02:22 +08:00
Unknwon
3e22ae3412 Update locales 2016-07-30 22:28:23 +08:00
Okunev Yu Dmitry
55b4e77a5e Fix of template error in "/:owner/:repo/pulls" (#3343)
If anonymous (not signed in user) requests page
"/:owner/:repo/pulls" he gets an error:

  template: repo/issue/list:11:105: executing "repo/issue/list" at <.PullRequestCtx.Base...>: nil pointer evaluating *models.Repository.Link

This commit fixes that.

Signed-off-by: Dmitry Yu Okunev <dyokunev@ut.mephi.ru>
2016-07-29 22:59:38 +08:00
Unknwon
ad7ea88923 Update glide.lock 2016-07-28 11:00:14 +08:00
Unknwon
f2884d8e31 Minor fix for pull request template [CI SKIP] 2016-07-28 10:54:52 +08:00
마누엘
36a63dd059 models/release: Update Sha1 if tag already exists (#3331)
Since the release struct is initialized with the current `HEAD` of the
current `release.Target` the commit id has to be updated if the tag
commit already exists. Otherwise the linked commit on the release page
will target the current `HEAD` at release time.
2016-07-28 10:45:35 +08:00
Unknwon
03ba257ad2 Update GitHub templates 2016-07-28 10:36:26 +08:00
마누엘
fe60ca408b routers/repo/release: Use correct branch reference (#3330)
When calculating the current behind commit count the calculation should
use the current release target to get the total commit count. Should the
release target not exist anymore the calculation will return zero for
the newest release on that target. Older releases on that target will then
use that calculated commit count as reference.

The only use case that is now somehow invalid is when the release target
was merged / deleted after a tag on that release target:

    master 1 - - - - - - - 6
            \             /
    branch   2 - 3 - 4 - 5

When `4` is the last tag on branch `branch` and the branch `branch` is not
yet deleted the calculated numbers would be:

    1 commits to branch since this release

Now if the branch `branch` gets deleted the calculation function will not
find the branch and use the commit count of the newest release (`4`) as
reference resulting in:

    0 commit to branch since this release

This fixes #3326
2016-07-27 16:57:32 +08:00
Richard Mahn
0402c803c6 Added Full Name to CreateUser api call (#3333) 2016-07-27 02:43:06 +08:00
Unknwon
29ccf047d8 routers/repo/issue: remove redundant format string 2016-07-26 18:42:18 +08:00
Unknwon
8aa0a76702 #3327 fix wrong table name in Join 2016-07-26 18:40:20 +08:00
Unknwon
2d76de2574 #3281 fix x.Iterate returns nothing inside session scope with SQLite3 2016-07-26 17:26:48 +08:00
Unknwon
4d8b905541 models/ssh_key: code cleaning 2016-07-26 10:47:25 +08:00
Unknwon
452aefd025 Fix issue event octicon CSS 2016-07-26 02:56:07 +08:00
Unknwon
899e799459 #1601 support delete issue comment 2016-07-26 02:48:17 +08:00
Unknwon
2295fafb34 repo/settings/options: take naming style examples out of locale string 2016-07-25 16:55:51 +08:00
Unknwon
a562228c5e Add org.getUserTeams to reduce redundant code 2016-07-24 18:09:45 +08:00
Unknwon
e74630ae3b #1384 add pagination for repositories 2016-07-24 14:32:46 +08:00
Unknwon
1f2e173a74 Refactor User.Id to User.ID 2016-07-24 01:08:22 +08:00
Unknwon
46e96c008c Use struct for UI settings 2016-07-24 00:23:54 +08:00
Unknwon
256cd6374a #2790 fix not detect diff style in pull request file changes 2016-07-23 23:46:49 +08:00
Unknwon
57a47bbc05 Add some tutorial links [CI SKIP] 2016-07-23 21:23:30 +08:00
Unknwon
024fcc836b Minor HTML fix for delete repository notice 2016-07-23 20:42:46 +08:00
Unknwon
250be011c7 Remove redundant Unix timestamp method call
Unix() already uses UTC as timezone
2016-07-23 20:24:44 +08:00
Unknwon
4e822c1911 conf: change default mirror checking interval to 10m 2016-07-23 20:20:09 +08:00
Unknwon
26d52ceb48 #3186 fix wrong link for new pull request button of non-fork repository 2016-07-23 19:55:53 +08:00
Unknwon
d90acacd86 Update README [CI SKIP] 2016-07-23 19:22:43 +08:00
Unknwon
69f5308761 #2903 use different reversed words and patterns for repository and user 2016-07-23 18:58:18 +08:00
Unknwon
4b5e09e4d6 #3181 detect situation when base branch is deleted in pull request 2016-07-23 18:35:16 +08:00
Unknwon
4f78abe7dc #3066 fix create organization ignores full name property 2016-07-23 17:57:22 +08:00
Unknwon
e63b2881b1 api: fix panic if anonymous user request admin API
Add sign in check before check user account level
2016-07-23 17:56:37 +08:00
Unknwon
745167d57a #3157 create user path before rename repository while transfer
os.Rename does not create parent directory automatically when not exist
2016-07-23 17:36:15 +08:00
Unknwon
d7bdc1de8d #3107 fix mention regex does not include dash 2016-07-23 17:29:34 +08:00
Unknwon
c912494609 #3076 detect invalid tag name git error 2016-07-23 15:59:19 +08:00
Richard Mahn
69dae1ec1c Added coding style modes to SimpleMDE (#3286)
* Added coding style modes to SimpleMDE

* Moved the CodeMirror addon from simplemd to codemirror directory
2016-07-23 13:29:20 +08:00
Siarhei Navatski
cf85e9eb7b add IsSubmodule field to DiffFile and hide view file button on diff page for submodules (#3097) 2016-07-23 02:18:56 +08:00
Unknwon
599716bb1b Update locales [CI SKIP] 2016-07-22 22:21:26 +08:00
Unknwon
ae88d76032 Bump version 2016-07-22 21:52:39 +08:00
Unknwon
9266f28822 Fix GitHub wrong repo language detection 2016-07-21 18:20:03 +08:00
Dennis Chen
6488ee12be avatar: make custom and generated avatars equal (#3301)
Sets all avatars to use PNG image format.
Keeps avatars consistent at 290x290px resolution.

Signed-off-by: Dennis Chen <barracks510@gmail.com>
2016-07-21 15:31:14 +08:00
Unknwon
c2fb01a257 #3299 don’t pop up confirm box when leave on signin page 2016-07-21 14:36:26 +08:00
Unknwon
5761342f32 #3291 fix SQLite3 session read/update conflict on create new issue 2016-07-21 14:26:30 +08:00
Unknwon
57af7432fc #3295 fix wrong logic judgement 2016-07-21 14:15:04 +08:00
Unknwon
1c7dcdd6b9 Update dep revision 2016-07-17 10:46:21 +08:00
lstahlman
25b3836418 Refresh repository mirror from database when the repository's name has changed. (#3276) 2016-07-17 09:30:43 +08:00
Unknwon
5aa2bf86f4 Update locales and .gopmfile 2016-07-17 09:25:30 +08:00
Unknwon
b0eb47cb1c Fix misselection of issues view type 2016-07-17 09:25:24 +08:00
lstahlman
e7a4f96fb6 Updated Issues and Issues Stats functions to include table aliases. This addresses errors involving ambiguous column references when filtering issues by the view type "Mentioning you". (#3269) 2016-07-17 09:18:35 +08:00
Unknwon
60110adc06 models/webhook: restrict deletion to be explicitly with repo and org ID 2016-07-17 08:33:59 +08:00
Dennis Chen
5ff2dfb23e api: delete repository webhooks (#3275)
Allows the deletion of a webhook from a repository at the
/:user/:repo/hooks/:id endpoint.

Solves drone/drone issue #1603.

Signed-off-by: Dennis Chen <barracks510@gmail.com>
2016-07-17 08:08:38 +08:00
Unknwon
eac32419fc templates/org/home: minor UI fix 2016-07-16 15:54:43 +08:00
Unknwon
e3d3d424b2 Minor fix for go vet 2016-07-16 15:15:00 +08:00
Unknwon
971e2c3bd6 Upgrade octicon to 4.3.0 2016-07-16 12:45:13 +08:00
Unknwon
c083d76567 #2937 able to prohibit user login 2016-07-16 10:22:16 +08:00
Unknwon
52322ef624 models/user_mail: refactor EmailAddress 2016-07-16 10:08:04 +08:00
Sandro Santilli
a4ea3bd015 Return avatar link as absolute url (#3235)
Fixes relative urls coming from api/v1

See https://github.com/drone/drone/issues/1701
2016-07-16 08:19:30 +08:00
Unknwon
3d93532c87 #3274 fix can't get webhook detail of organization 2016-07-16 01:02:55 +08:00
Unknwon
fff615d5fc Bump version 2016-07-16 00:37:21 +08:00
Unknwon
f1b8d52eb3 #2854 fix no mail notification when issue is closed/reopened 2016-07-16 00:36:39 +08:00
Unknwon
7ca5f8f119 models/repo: remove redundant info for some repo methods
RepoLink -> Link, RepoRelLink -> RelLink, FullRepoLink -> FullLink
2016-07-15 21:53:43 +08:00
Unknwon
194a742fb9 #2798 fix assign operation not take effect 2016-07-15 17:06:17 +08:00
Unknwon
160956dd31 Update Docker README 2016-07-15 14:32:42 +08:00
Unknwon
089bacd54e Update bindata and CSS 2016-07-15 14:13:30 +08:00
Kim Carlbäcker
d950bf68e3 Ignore Response Body for Slack Hooks #3169 (#3256) 2016-07-15 14:02:19 +08:00
Richard Mahn
7796c9e122 Fixes #3263 - Change for Fontawesome icons to not conflict withs semantic-ui (#3267) 2016-07-15 13:57:28 +08:00
Laica Lunasys
528ec9bffd Apply font for Japanese / Chinese (more) (#3266) 2016-07-15 13:52:37 +08:00
Atakan Goktepe
32ec4734f9 Changed Turkish language name to 'Türkçe' (#3268) 2016-07-15 13:44:49 +08:00
Andrey Arapov
6a98e7d914 docker: Support timezones (#3262) 2016-07-15 10:55:05 +08:00
Unknwon
a752f09055 #2709 validate username attribute fetched from LDAP 2016-07-12 07:07:57 +08:00
lstahlman
846bf2ca9f Add timestamps to repository api response (#3255)
Additional properties: created_at, updated_at
2016-07-12 06:28:51 +08:00
Kim Carlbäcker
f4ab50501e [Fix] Don't display way too large files #1513 (#3253)
* Add MaxDisplayFileSize setting

* Don't show files that are too large

* Localized FileTooLarge

* Change IsFileTooBig => IsFileTooLarge
2016-07-12 06:21:26 +08:00
Unknwon
de10387f41 #2586 bump git-module requirement 2016-07-12 06:20:09 +08:00
Unknwon
1faaaeb3d9 Update glide.lock 2016-07-11 07:57:15 +08:00
lstahlman
12cb84b97f Extend the API to include more repository properties (#3249)
Adds description, stars_count, forks_count, watchers_count and
open_issues_count.
2016-07-11 07:56:15 +08:00
Unknwon
fdcca9292e #2458 fix emoji been rendered inside raw content area 2016-07-11 05:28:56 +08:00
Unknwon
a1f717f65a Update locale 2016-07-09 23:59:13 +08:00
Unknwon
70a281a39b #2375 preserve cases for action content 2016-07-09 23:37:32 +08:00
Unknwon
9fcf66f0e0 Minor fix for #3246 2016-07-09 13:42:05 +08:00
Pablo Saavedra
98b152030d The pruning for the synchronized mirrors is a option now. Default value: enable_prune = true (#3246)
Executed go fmt

getEngine() not handles DB parameters (#2972) (#2974)

Uses .AllCols() for Update in updateMirror()

Spanish traslation removed

Fixed a wrong way to ommit the --prune option in process.ExecDir() for MirrorUpdate function
2016-07-09 13:22:28 +08:00
Pheng Heong TAN
467d7dacb6 Modify behaviour of repo-delete. (#3232)
Re: issues gogits/gogs#2863 and gogits/gogs#3231

As a result of modifications to the contents of the conf folder, `make bindata`
was run, causing an update to bindata.go.

Meta
-----
This commit will be rebased onto the 'develop' branch.
2016-07-09 13:13:57 +08:00
Unknwon
d62ab49978 #3057 retrieve webhook with repo_id
This prevents user retrieve arbitrary webhook by changing URL to
access webhook from other unauthorized repositories.
2016-07-08 13:57:09 +08:00
Unknwon
e30c701386 #3229 disallow repository name . and ..
Since . and .. has browser automatical behaviors, we need to disallow those names.
2016-07-08 07:34:05 +08:00
Unknwon
401bf944ef Use SecurityProtocol to replace UseSSL in LDAP config
Initially proposed by #2376 and fixes #3068 as well.
2016-07-08 07:25:09 +08:00
Unknwon
326c982660 Upgrade frontend assets 2016-07-07 06:04:55 +08:00
Unknwon
4b25bdfbc4 #3058 #3059 support correct page size and link header 2016-07-04 17:27:06 +08:00
Tom
528682a294 getEngine() not handles DB parameters (#2972) (#2974) 2016-07-02 22:39:39 +08:00
Unknwon
6aa00f7bcf #2968 use HTTP_ADDR to replace localhost 2016-07-02 18:54:48 +08:00
Unknwon
f485fcde96 #2947 fix inapproriate comment 2016-07-02 18:23:15 +08:00
Unknwon
6f6b37f148 #3078 update default app.ini 2016-07-01 22:27:52 +08:00
j.yao.SUSE
99c3a9390f change setting.go -> LocalUrl default value to (#3078) 2016-07-01 22:26:15 +08:00
Vasily Mikhaylichenko
f0df46c88a Add an OpenBSD daemon control script (#3060) 2016-07-01 22:24:41 +08:00
Unknwon
e84ac64964 Do not show filename not have suffifx .md 2016-07-01 15:34:03 +08:00
Unknwon
3a30c06345 Fix wiki vulnerabilities
- Arbitrary file creation leading to command execution
- .md file creation/deletion

Reported by Gabriel Campana.
2016-07-01 15:33:35 +08:00
Unknwon
a10ca2c5f6 #2959 update README version description 2016-06-30 15:16:29 +08:00
wanglinzhizhi
927d9f092b port 25 do not work, and port 465 is right (#3145)
QQ STMP host port is 465

host = smtp.qq.com:465

and need the Authorized
2016-06-30 04:04:15 +08:00
Unknwon
7938506e07 #518 update git-module require version 2016-06-30 02:21:23 +08:00
Unknwon
6f7276278d #3174 genetate bindata 2016-06-29 23:25:41 +08:00
Andrey Nering
743d22669a Re-work MAX_DIFF_LINES: supress diff per file, not the whole diff (#3174) 2016-06-29 23:11:00 +08:00
Cosmin Stroe
84841c8c4b Stricter parsing of issue URLs and commit URLs. (#3121) 2016-06-29 23:07:39 +08:00
Unknwon
274a2ca528 Update locale bindata 2016-06-28 00:26:18 +08:00
Sandro Santilli
d4aaef90e6 Fix typo in english/US message (#2938)
"is activate" -> "is activated"
2016-06-28 00:24:06 +08:00
Andrey Nering
6efb1e5626 Localize collaboration settings. (#3100)
Closes #2764
2016-06-28 00:22:30 +08:00
Unknwon
73b4acbb63 Update glide.yaml 2016-06-27 23:40:36 +08:00
Sandro Santilli
8a248696e9 Use a gopher as default avatar (rather than the gravatar logo) (#3208)
Also changes the avatar from a jpeg to a png, to allow for
transparent background. The indexed png is also smaller in size.

Note that at the moment the default avatar is only used when
the user requested a custom avatar and the custom avatar file
is not found (should never happen).

In the future the default avatar could be used as a default
return when by-mail avatar lookups fail too (both gravatar
and libravatar support passing a default)
2016-06-27 18:12:30 +08:00
Franz Schmidt
8b35c194ec Fixes #3110 (#3136) 2016-06-27 17:02:39 +08:00
Robin Lambertz
ac05f88641 Fix #3154 (#3155) 2016-06-27 16:58:53 +08:00
Unknwon
4bbb878d20 Minor fix for #3194
- Update locale bindata
2016-06-27 16:38:35 +08:00
Sandro Santilli
2ce60ff314 Include repository owner name and description in html title (#3194)
Closes #3192
2016-06-27 16:32:35 +08:00
SjonHortensius
17a4d8a5e5 Fix capitalisation of repo-name in news (#3203)
use 'official' repo.Name instead of incoming repoName; to enforce
correct capitalisation
2016-06-27 16:10:12 +08:00
Richard Bukovansky
04592c385b Adding myself (#3084)
As you included Czech translation to official distribution, I think I'm missing here.
I know, selfish as hell... ;)
2016-06-27 15:59:33 +08:00
Robin Lambertz
bc00da1721 Fix negative issue count (#3207) 2016-06-27 01:53:30 +08:00
Sandro Santilli
76a0e43e88 Do not assume avatar needs be changed from gravatar.com (#3209)
Always send user to settings screen to change avatar.
Drops "change_custom_avatar" localized message, keeps "change_avatar"
for the generic one.

NOTE: only changes the en-US locale, as per
https://github.com/gogits/gogs/wiki/Contributing-Code#those-we-do-not-accept

NOTE: requires rebuild of bindata.go to fully see effects
2016-06-27 01:51:09 +08:00
chriswatt
24caccccdd Fix empty space surrounding hidden issue labels (#3200) 2016-06-24 23:38:25 +08:00
Unknwon
26342b0c24 Update locale 2016-06-12 18:03:51 +08:00
Unknwon
a4eaddff81 #2958 Update dep version 2016-06-12 17:54:22 +08:00
Unknwon
c041273dd3 repo/http: clean code 2016-06-01 04:19:01 -07:00
Paul Tötterman
fb970b9d87 Add ServerName to tls.Config in LDAP auth (#3104)
From https://godoc.org/crypto/tls#Config

    ServerName is used to verify the hostname on the returned
    certificates unless InsecureSkipVerify is given. It is also included
    in the client's handshake to support virtual hosting unless it is
    an IP address.

This is needed for certificate validation without InsecureSkipVerify.
2016-06-01 01:11:28 -07:00
Unknwon
0240f520ab #2954 minor fix for when to set HTML alternative 2016-05-30 01:50:20 -07:00
Unknwon
3d105733a9 Update glide.lock 2016-05-30 01:39:49 -07:00
Unknwon
8df3ba96f3 #2954 use text/plain as default email content format 2016-05-30 01:32:01 -07:00
Sandro Santilli
d35a1c30f4 Do not write HTML in text/plain mail part (#2954)
* Do not write HTML in text/plain mail part

Fixes #2928

* Pass text/plain first, text/html second

* Do not send plain/text email if html2text failed (untested)
2016-05-30 01:18:49 -07:00
Unknwon
e9ae926e04 #809 fix wrong closed issue count when create closed issue via API
Add start count corrector for Repository.NumClosedIssues
2016-05-27 18:23:39 -07:00
Thomas Fanninger
28c03f1147 Update .gopmfile (#3133)
Build only work with the current master of the package. Otherwise, I get this error.
```
...
# github.com/gogits/gogs/routers/api/v1/convert
routers/api/v1/convert/convert.go:200: undefined: gogs.Team
routers/api/v1/convert/convert.go:201: undefined: gogs.Team
...
```
2016-05-26 19:05:50 -07:00
Alex Myasoedov
84d9aff8a2 gogs dump tempdir flag (#3086) 2016-05-23 17:10:05 -07:00
Andrey Nering
12d30255a7 Add comment note (#3093) 2016-05-23 13:24:40 -07:00
Unknwon
7826eae452 #3045 fix DEPRECATED Action signature erorr 2016-05-12 14:32:28 -04:00
Unknwon
8a2347592d locale: update Czech 2016-05-12 14:31:06 -04:00
Jean-Philippe Roemer
bcd4adb3a0 Update docker/build.sh script to use glide & make (#3079)
* docker: update build script to use glide + make

- docker/build.sh will now use glide to fetch dependencies
- glide is built from source to keep compatibility with arm
  (no pre-prebuilt binary for arm)
- docker/build.sh will also now use the provided Makefile
  It will generate an error when trying to get git build has as we do
  not ship the 88mo .git directory during the build (should not cause
  any problem as the variable it sets was not set previously)

* docker: fix docker arm build

- drop gosu version back to 1.7 as gosu binary for armhf is broken
- see tianon/gosu#19

* docker: update gosu to 1.9

Signed-off-by: Jean-Philippe Roemer <jp@roemer.im>
2016-05-11 13:11:59 -04:00
Unknwon
bf5faf76eb #809 able to set issue state to closed when create 2016-05-11 12:19:26 -04:00
Unknwon
f473895c41 bindata: follow up data clean 2016-05-11 12:17:52 -04:00
Unknwon
fbf43c31e5 Add Czech support 2016-05-11 12:00:47 -04:00
Kim Carlbäcker
3c0c7a9f83 Fix listing team members (#3048) 2016-05-06 20:02:36 -04:00
Siarhei Navatski
13216c5c20 Update glide.lock with latest go-gogs-client (#2939) 2016-05-06 19:48:02 -04:00
Julien Reichardt
6be9a2c1db Add DPlatform as an installation way (#3044) 2016-05-06 19:44:05 -04:00
Andrey Nering
d8612f7704 Fix remove folder issues, including initialization failling. (#2969)
- Prevent panic on creating notice if database is not available
- Prevent incorrect folder on Windows ("/" instead of "\")
2016-05-06 15:48:18 -04:00
Unknwon
0a78d99a4d models/release: filter input to prevent command line argument vulnerability 2016-05-06 15:40:41 -04:00
Kim Carlbäcker
3df8eb60e3 PDF-Previews in file-lists now working (#3000) 2016-04-26 21:48:44 -04:00
Unknwon
0325bec283 #2895 minor fix for bug of xorm 2016-04-26 00:22:03 -04:00
Thomas Boerger
dfad51fe9e Made the issue stats query more secure with parameterized placeholders (#2895) 2016-04-26 00:07:49 -04:00
Unknwon
7049cb9d97 Add new language support
- Update language choose dropdown to fit more languages
2016-04-25 13:40:23 -04:00
Unknwon
78b8b63774 #2992 set default style name when empty in AfterSet 2016-04-22 18:36:05 -04:00
Cosmin Stroe
ba314a7a36 Support alphanumeric issue style (ABC-1234) for external issue tracker (#2992) 2016-04-22 18:28:08 -04:00
Tamás Molnár
39356f4238 Set utf-8 charset for text files when serving raw content (#2898) 2016-04-20 19:38:11 -04:00
Kim Carlbäcker
b3c05026df [Feature/WIP] Confirm on editing forms (fixes #2881) (#2980)
* Add and start jQuery AreYouSure

* Update SimpleMDE to 1.10.1 for `forceSync` support

* Forgot to remove old version SimpleMDE 1.10.0

* formatting -.-
2016-04-19 16:45:28 -04:00
Kim Carlbäcker
ce36fd7a49 Fixed #2909 (#2979) 2016-04-19 16:35:36 -04:00
Jean-Philippe Roemer
69e00f9948 docker: update documentation for container options (#2965)
- Created a Container options section in `docker/README.md`
- Add documentation for SOCAT_LINK
- Move CROND documentation to the new section
2016-04-12 10:46:32 -04:00
Unknwon
3257df0da3 Update locales 2016-04-11 18:38:25 -04:00
Unknwon
0f2869069b Merge pull request #2920 from l2dy/patch-1
Update ISC license
2016-04-05 13:36:58 -04:00
Unknwon
a27c6f4b75 #2916 fix sort' field missing on issue pagination link 2016-04-04 19:48:10 -04:00
Unknwon
d27ca649c7 api/admin: add/remove organization team repository 2016-04-04 19:41:34 -04:00
Unknwon
d9900e4dbc Merge pull request #2914 from psychomario/linenumoptimize
Reduce line number creation to one DOM manipulation
2016-04-04 19:33:52 -04:00
l2dy
e75a1444af Update ISC license 2016-04-03 01:56:48 +00:00
PsychoMario
b35157f4ff reduce line number creation to one DOM manipulation 2016-03-30 20:28:40 +01:00
Unknwon
762ab056a2 Fix XORM IN condition table name parse 2016-03-27 18:21:37 -04:00
Unknwon
78481f0e42 Merge pull request #2894 from tboerger/feature/join-condition
Followup fix for previous query fix
2016-03-27 18:07:52 -04:00
Thomas Boerger
746c7fd4e7 Followup fix for previous query fix 2016-03-28 00:05:49 +02:00
Unknwon
7f26ae0b45 Merge pull request #2893 from tboerger/feature/sql-security
Try to make the SQL queries cleaner and more secure
2016-03-27 17:40:28 -04:00
Thomas Boerger
b5948f2e71 Made the issues query more secure and simpler 2016-03-27 23:26:45 +02:00
Thomas Boerger
79a1bfd963 Try to make the SQL queries cleaner and more secure 2016-03-27 22:59:57 +02:00
Unknwon
ac53bb593d #2878 print error of JSON unmarshal and always returns a valid object 2016-03-26 16:42:20 -04:00
Unknwon
dd36c431ec #2842 add quotes to attachment file name 2016-03-25 22:11:58 -04:00
Unknwon
b1d41cfa60 #1692 add admin APIs to add/remove a user from teams 2016-03-25 18:04:02 -04:00
Unknwon
9dda9ef07c Merge pull request #2883 from nikkomiu/develop
Fixes #2842 Parses URL through EscapePound template function
2016-03-25 17:09:21 -04:00
Nikko Miu
9a43fcb61c Changed EscapePound function with string replace function 2016-03-24 20:48:52 -05:00
Unknwon
5ec8ef0230 Update locales 2016-03-23 15:56:56 -04:00
Unknwon
d3db1b2591 Merge pull request #2879 from dakira/develop
use monospace font for wiki and issue textareas (fix #2869)
2016-03-23 10:32:24 -04:00
Matthias Niess
3decc0b3d6 use monospace font for wiki and issue textareas (fix #2869) 2016-03-23 13:55:07 +01:00
Unknwon
98b58fa050 Handle windows deletion when start
Fix #2872
2016-03-23 03:16:53 -04:00
Unknwon
5e11341232 Fix status code 2016-03-21 12:57:04 -04:00
Unknwon
90e93b1f3a Change list teams API to non-admin specific 2016-03-21 12:53:04 -04:00
Unknwon
e6f927f61a #1692 api: admin list and create team under organization 2016-03-21 12:47:54 -04:00
Unknwon
60ae8ac3d2 Add route for #2846 2016-03-21 10:49:46 -04:00
Unknwon
004fb30ebe Remove code for checking ssh-keygen 2016-03-19 20:31:23 -04:00
627 changed files with 124412 additions and 4310 deletions

View File

@@ -1,7 +1,5 @@
.git
.git/**
conf
conf/**
packager
packager/**
scripts
@@ -9,13 +7,13 @@ scripts/**
.github/
.github/**
config.codekit
LICENSE
Makefile
.dockerignore
*.yml
*.md
.bra.toml
.editorconfig
.gitignore
.gopmfile
Dockerfile*
vendor
vendor/**
gogs

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
public/assets/* linguist-vendored
public/plugins/* linguist-vendored

View File

@@ -1,18 +1,20 @@
We DO NOT take questions or config/deploy problems on GitHub, please use our forum: https://discuss.gogs.io
The issue will be closed without any reasons if it does not satisfy any of following requirements:
Please take a moment to search that an issue doesn't already exist.
1. Please do NOT post questions or config/deploy problems on GitHub, please use our forum: https://discuss.gogs.io
2. Please take a moment to search that an issue doesn't already exist.
3. Please give all relevant information below for bug reports; incomplete details considered invalid report.
For bug reports, please give the relevant info:
**You MUST delete above content including this line before posting; too lazy to take this action considered invalid report.**
- Gogs version (or commit ref):
- Git version:
- Operating system:
- Database:
- Gogs version (or commit ref):
- Git version:
- Operating system:
- Database (use `[x]`):
- [ ] PostgreSQL
- [ ] MySQL
- [ ] SQLite
- Can you reproduce the bug at http://try.gogs.io:
- [ ] Yes
- Can you reproduce the bug at https://try.gogs.io:
- [ ] Yes (provide example URL)
- [ ] No
- [ ] Not relevant
- Log gist:

View File

@@ -1,4 +1,9 @@
Please, make sure you are targeting the `develop` branch!
The pull request will be closed without any reasons if it does not satisfy any of following requirements:
More instructions about contributing with Gogs code can be found here:
1. Please make sure you are targeting the `develop` branch.
2. Please read contributing guidelines:
https://github.com/gogits/gogs/wiki/Contributing-Code
3. Please describe what your pull request does and which issue you're targeting
4. ... if it is not related to any particular issues, explain why we should not reject your pull request.
**You MUST delete above content including this line before posting; too lazy to take this action considered invalid pull request.**

View File

@@ -3,53 +3,54 @@ path = github.com/gogits/gogs
[deps]
github.com/bradfitz/gomemcache = commit:fb1f79c
github.com/codegangsta/cli = commit:aca5b04
github.com/go-macaron/binding = commit:a68f342
github.com/codegangsta/cli = commit:1efa31f
github.com/go-macaron/binding = commit:9440f33
github.com/go-macaron/cache = commit:5617353
github.com/go-macaron/captcha = commit:8aa5919
github.com/go-macaron/csrf = commit:6a9a7df
github.com/go-macaron/gzip = commit:cad1c65
github.com/go-macaron/i18n = commit:d2d3329
github.com/go-macaron/i18n = commit:ef57533
github.com/go-macaron/inject = commit:c5ab7bf
github.com/go-macaron/session = commit:66031fc
github.com/go-macaron/toolbox = commit:82b5115
github.com/go-sql-driver/mysql = commit:66312f7
github.com/go-xorm/core = commit:5021584
github.com/go-xorm/xorm = commit:769f6b3
github.com/go-sql-driver/mysql = commit:0b58b37
github.com/go-xorm/core = commit:bc1b7f8
github.com/go-xorm/xorm = commit:b8b1711
github.com/gogits/chardet = commit:2404f77
github.com/gogits/cron = commit:3abc0f8
github.com/gogits/git-module = commit:76e8cce
github.com/gogits/go-gogs-client = commit:788ec59
github.com/issue9/identicon = commit:f8c0d2c
github.com/gogits/cron = commit:96040e4
github.com/gogits/git-module = commit:53bcb73
github.com/gogits/go-gogs-client = commit:d725743
github.com/issue9/identicon = commit:d36b545
github.com/jaytaylor/html2text = commit:52d9b78
github.com/kardianos/minwinsvc = commit:cad6b2b
github.com/klauspost/compress = commit:006acde
github.com/klauspost/compress = commit:14eb9c4
github.com/klauspost/cpuid = commit:09cded8
github.com/klauspost/crc32 = commit:19b0b33
github.com/lib/pq = commit:165a352
github.com/mattn/go-sqlite3 = commit:76e335f
github.com/lib/pq = commit:4dd446e
github.com/mattn/go-sqlite3 = commit:e118d44
github.com/mcuadros/go-version = commit:d52711f
github.com/microcosm-cc/bluemonday = commit:4ac6f27
github.com/microcosm-cc/bluemonday = commit:9dc1992
github.com/msteinert/pam = commit:02ccfbf
github.com/nfnt/resize = commit:4d93a29
github.com/russross/blackfriday = commit:b43df97
github.com/satori/go.uuid = commit:e673fdd
github.com/nfnt/resize = commit:891127d
github.com/russross/blackfriday = commit:93622da
github.com/satori/go.uuid = commit:0aa62d5
github.com/sergi/go-diff = commit:ec7fdbb
github.com/shurcooL/sanitized_anchor_name = commit:10ef21a
github.com/Unknwon/cae = commit:7f5e046
github.com/Unknwon/com = commit:28b053d
github.com/Unknwon/i18n = commit:3b48b66
github.com/Unknwon/i18n = commit:39d6f27
github.com/Unknwon/paginater = commit:7748a72
golang.org/x/crypto = commit:c197bcf
golang.org/x/net = commit:35b06af
golang.org/x/sys = commit:9d4e42a
golang.org/x/text = commit:1b466db
golang.org/x/crypto = commit:bc89c49
golang.org/x/net = commit:57bfaa8
golang.org/x/sys = commit:a646d33
golang.org/x/text = commit:2910a50
gopkg.in/alexcesaro/quotedprintable.v3 = commit:2caba25
gopkg.in/asn1-ber.v1 = commit:4e86f43
gopkg.in/bufio.v1 = commit:567b2bf
gopkg.in/gomail.v2 = commit:060a5f4
gopkg.in/ini.v1 = commit:776aa73
gopkg.in/ldap.v2 = commit:07a7330
gopkg.in/macaron.v1 = commit:94a5ef7
gopkg.in/gomail.v2 = commit:81ebce5
gopkg.in/ini.v1 = commit:cf53f92
gopkg.in/ldap.v2 = commit:537128f
gopkg.in/macaron.v1 = commit:2133042
gopkg.in/redis.v2 = commit:e617904
[res]

View File

@@ -2,9 +2,9 @@ FROM alpine:3.3
MAINTAINER jp@roemer.im
# Install system utils & Gogs runtime dependencies
ADD https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64 /usr/sbin/gosu
ADD https://github.com/tianon/gosu/releases/download/1.9/gosu-amd64 /usr/sbin/gosu
RUN chmod +x /usr/sbin/gosu \
&& apk --no-cache --no-progress add ca-certificates bash git linux-pam s6 curl openssh socat
&& apk --no-cache --no-progress add ca-certificates bash git linux-pam s6 curl openssh socat tzdata
ENV GOGS_CUSTOM /data/gogs

View File

@@ -2,12 +2,12 @@ FROM hypriot/rpi-alpine-scratch:v3.2
MAINTAINER jp@roemer.im, raxetul@gmail.com
# Install system utils & Gogs runtime dependencies
ADD https://github.com/tianon/gosu/releases/download/1.7/gosu-armhf /usr/sbin/gosu
ADD https://github.com/tianon/gosu/releases/download/1.9/gosu-armhf /usr/sbin/gosu
RUN chmod +x /usr/sbin/gosu \
&& echo "http://dl-4.alpinelinux.org/alpine/v3.3/main/" | tee /etc/apk/repositories \
&& echo "http://dl-4.alpinelinux.org/alpine/v3.3/community/" | tee -a /etc/apk/repositories \
&& apk -U --no-progress upgrade && rm -f /var/cache/apk/APKINDEX.* \
&& apk --no-cache --no-progress add ca-certificates bash git linux-pam s6 curl openssh socat
&& apk --no-cache --no-progress add ca-certificates bash git linux-pam s6 curl openssh socat tzdata
ENV GOGS_CUSTOM /data/gogs

View File

@@ -24,6 +24,10 @@ govet:
go tool vet -composites=false -methods=false -structtags=false .
build-dev: $(GENERATED) govet
go install $(BUILD_FLAGS) -tags '$(TAGS)'
cp '$(GOPATH)/bin/gogs' .
build-dev-race: $(GENERATED) govet
go install $(BUILD_FLAGS) -race -tags '$(TAGS)'
cp '$(GOPATH)/bin/gogs' .

View File

@@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
##### Current version: 0.9.13
##### Current tip version: 0.9.60 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
| Web | UI | Preview |
|:-------------:|:-------:|:-------:|
@@ -49,7 +49,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
- Mail service
- Administration panel
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb) (experimental)
- Multi-language support ([15 languages](https://crowdin.com/project/gogs))
- Multi-language support ([18 languages](https://crowdin.com/project/gogs))
## System Requirements
@@ -77,6 +77,8 @@ There are 5 ways to install Gogs:
- [How To Set Up Gogs on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-gogs-on-ubuntu-14-04)
- [Run your own GitHub-like service with the help of Docker](http://blog.hypriot.com/post/run-your-own-github-like-service-with-docker/)
- [Dockerized Gogs git server and alpine postgres in 20 minutes or less](http://garthwaite.org/docker-gogs.html)
- [Host Your Own Private GitHub with Gogs.io](https://eladnava.com/host-your-own-private-github-with-gogs-io/)
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs) (Chinese)
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese)
- [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html)
@@ -96,6 +98,7 @@ There are 5 ways to install Gogs:
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
- [YunoHost](https://github.com/mbugeia/gogs_ynh)
- [DPlatform](https://github.com/j8r/DPlatform)
## Software and Service Support
@@ -119,7 +122,7 @@ There are 5 ways to install Gogs:
- Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo.
- Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
- Thanks [DigitalOcean](https://www.digitalocean.com) for hosting home and demo sites.
- Thanks [KeyCDN](https://www.keycdn.com/) for providing CDN service.
- Thanks [KeyCDN](https://www.keycdn.com/) and [QiNiu](http://www.qiniu.com/) for providing CDN service.
## Contributors

View File

@@ -30,7 +30,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
- 支持邮件服务
- 支持后台管理面板
- 支持 MySQL、PostgreSQL、SQLite3 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
- 支持多语言本地化([15 种语言]([more](https://crowdin.com/project/gogs))
- 支持多语言本地化([18 种语言]([more](https://crowdin.com/project/gogs))
## 系统要求
@@ -68,6 +68,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
- [YunoHost](https://github.com/mbugeia/gogs_ynh)
- [DPlatform](https://github.com/j8r/DPlatform)
## 软件及服务支持
@@ -91,7 +92,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
- 感谢 [DigitalOcean](https://www.digitalocean.com) 提供主站和体验站点的服务器赞助。
- 感谢 [KeyCDN](https://www.keycdn.com/) 提供 CDN 服务赞助。
- 感谢 [KeyCDN](https://www.keycdn.com/) 和 [七牛云存储](http://www.qiniu.com/) 提供 CDN 服务赞助。
## 贡献成员

View File

@@ -59,7 +59,7 @@ func pemBlockForKey(priv interface{}) *pem.Block {
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
log.Fatal("unable to marshal ECDSA private key: %v", err)
log.Fatalf("Unable to marshal ECDSA private key: %v\n", err)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
@@ -67,7 +67,7 @@ func pemBlockForKey(priv interface{}) *pem.Block {
}
}
func runCert(ctx *cli.Context) {
func runCert(ctx *cli.Context) error {
if len(ctx.String("host")) == 0 {
log.Fatal("Missing required --host parameter")
}
@@ -153,9 +153,11 @@ func runCert(ctx *cli.Context) {
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatal("failed to open key.pem for writing: %v", err)
log.Fatalf("Failed to open key.pem for writing: %v\n", err)
}
pem.Encode(keyOut, pemBlockForKey(priv))
keyOut.Close()
log.Println("Written key.pem")
return nil
}

View File

@@ -20,7 +20,9 @@ var CmdCert = cli.Command{
Action: runCert,
}
func runCert(ctx *cli.Context) {
func runCert(ctx *cli.Context) error {
fmt.Println("Command cert not available, please use build tags 'cert' to rebuild.")
os.Exit(1)
return nil
}

View File

@@ -28,11 +28,12 @@ It can be used for backup and capture Gogs server image to send to maintainer`,
Action: runDump,
Flags: []cli.Flag{
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
boolFlag("verbose, v", "show process details"),
boolFlag("verbose, v", "Show process details"),
stringFlag("tempdir, t", os.TempDir(), "Temporary dir path"),
},
}
func runDump(ctx *cli.Context) {
func runDump(ctx *cli.Context) error {
if ctx.IsSet("config") {
setting.CustomConf = ctx.String("config")
}
@@ -40,7 +41,11 @@ func runDump(ctx *cli.Context) {
models.LoadConfigs()
models.SetEngine()
TmpWorkDir, err := ioutil.TempDir(os.TempDir(), "gogs-dump-")
tmpDir := ctx.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
log.Fatalf("Path does not exist: %s", tmpDir)
}
TmpWorkDir, err := ioutil.TempDir(tmpDir, "gogs-dump-")
if err != nil {
log.Fatalf("Fail to create tmp work directory: %v", err)
}
@@ -68,21 +73,21 @@ func runDump(ctx *cli.Context) {
log.Fatalf("Fail to create %s: %v", fileName, err)
}
if err := z.AddFile("gogs-repo.zip", reposDump); err !=nil {
if err := z.AddFile("gogs-repo.zip", reposDump); err != nil {
log.Fatalf("Fail to include gogs-repo.zip: %v", err)
}
if err := z.AddFile("gogs-db.sql", dbDump); err !=nil {
if err := z.AddFile("gogs-db.sql", dbDump); err != nil {
log.Fatalf("Fail to include gogs-db.sql: %v", err)
}
customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() {
if err := z.AddDir("custom", setting.CustomPath); err !=nil {
if err := z.AddDir("custom", setting.CustomPath); err != nil {
log.Fatalf("Fail to include custom: %v", err)
}
}
} else {
log.Printf("Custom dir %s doesn't exist, skipped", setting.CustomPath)
}
if err := z.AddDir("log", setting.LogRootPath); err !=nil {
if err := z.AddDir("log", setting.LogRootPath); err != nil {
log.Fatalf("Fail to include log: %v", err)
}
// FIXME: SSH key file.
@@ -94,4 +99,6 @@ func runDump(ctx *cli.Context) {
log.Printf("Removing tmp work dir: %s", TmpWorkDir)
os.RemoveAll(TmpWorkDir)
log.Printf("Finish dumping in file %s", fileName)
return nil
}

View File

@@ -103,7 +103,7 @@ func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string,
RefName: task.RefName,
OldCommitID: task.OldCommitID,
NewCommitID: task.NewCommitID,
PusherID: user.Id,
PusherID: user.ID,
PusherName: user.Name,
RepoUserName: repoUser.Name,
RepoName: reponame,
@@ -129,7 +129,7 @@ func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string,
}
}
func runServ(c *cli.Context) {
func runServ(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
@@ -138,7 +138,7 @@ func runServ(c *cli.Context) {
if setting.SSH.Disabled {
println("Gogs: SSH has been disabled")
return
return nil
}
if len(c.Args()) < 1 {
@@ -149,7 +149,7 @@ func runServ(c *cli.Context) {
if len(cmd) == 0 {
println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.")
println("If this is unexpected, please log in with password and setup Gogs under another user.")
return
return nil
}
verb, args := parseCmd(cmd)
@@ -175,7 +175,7 @@ func runServ(c *cli.Context) {
fail("Internal error", "Failed to get repository owner (%s): %v", username, err)
}
repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
repo, err := models.GetRepositoryByName(repoUser.ID, reponame)
if err != nil {
if models.IsErrRepoNotExist(err) {
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoUser.Name, reponame)
@@ -290,4 +290,6 @@ func runServ(c *cli.Context) {
fail("Internal error", "UpdatePublicKey: %v", err)
}
}
return nil
}

View File

@@ -24,7 +24,7 @@ var CmdUpdate = cli.Command{
},
}
func runUpdate(c *cli.Context) {
func runUpdate(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
@@ -33,7 +33,7 @@ func runUpdate(c *cli.Context) {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
log.GitLogger.Trace("SSH_ORIGINAL_COMMAND is empty")
return
return nil
}
args := c.Args()
@@ -53,4 +53,6 @@ func runUpdate(c *cli.Context) {
if err := models.AddUpdateTask(&task); err != nil {
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err)
}
return nil
}

View File

@@ -78,21 +78,24 @@ func checkVersion() {
// Check dependency version.
checkers := []VerChecker{
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.5.2.0304"},
{"github.com/go-macaron/binding", binding.Version, "0.2.1"},
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.5.5"},
{"github.com/go-macaron/binding", binding.Version, "0.3.2"},
{"github.com/go-macaron/cache", cache.Version, "0.1.2"},
{"github.com/go-macaron/csrf", csrf.Version, "0.1.0"},
{"github.com/go-macaron/i18n", i18n.Version, "0.2.0"},
{"github.com/go-macaron/i18n", i18n.Version, "0.3.0"},
{"github.com/go-macaron/session", session.Version, "0.1.6"},
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
{"gopkg.in/ini.v1", ini.Version, "1.8.4"},
{"gopkg.in/macaron.v1", macaron.Version, "1.1.2"},
{"github.com/gogits/git-module", git.Version, "0.2.9"},
{"github.com/gogits/go-gogs-client", gogs.Version, "0.7.4"},
{"gopkg.in/macaron.v1", macaron.Version, "1.1.4"},
{"github.com/gogits/git-module", git.Version, "0.3.3"},
{"github.com/gogits/go-gogs-client", gogs.Version, "0.10.1"},
}
for _, c := range checkers {
if !version.Compare(c.Version(), c.Expected, ">=") {
log.Fatal(4, "Package '%s' version is too old (%s -> %s), did you forget to update?", c.ImportPath, c.Version(), c.Expected)
log.Fatal(4, `Dependency outdated!
Package '%s' current version (%s) is below requirement (%s),
please use following command to update this package and recompile Gogs:
go get -u %[1]s`, c.ImportPath, c.Version(), c.Expected)
}
}
}
@@ -123,12 +126,16 @@ func newMacaron() *macaron.Macaron {
SkipLogging: setting.DisableRouterLog,
},
))
funcMap := template.NewFuncMap()
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "templates"),
AppendDirectories: []string{path.Join(setting.CustomPath, "templates")},
Funcs: template.NewFuncMap(),
Funcs: funcMap,
IndentJSON: macaron.Env != macaron.PROD,
}))
models.InitMailRender(path.Join(setting.StaticRootPath, "templates/mail"),
path.Join(setting.CustomPath, "templates/mail"), funcMap)
localeNames, err := bindata.AssetDir("conf/locale")
if err != nil {
@@ -175,7 +182,7 @@ func newMacaron() *macaron.Macaron {
return m
}
func runWeb(ctx *cli.Context) {
func runWeb(ctx *cli.Context) error {
if ctx.IsSet("config") {
setting.CustomConf = ctx.String("config")
}
@@ -457,7 +464,10 @@ func runWeb(ctx *cli.Context) {
m.Post("/content", repo.UpdateIssueContent)
})
})
m.Post("/comments/:id", repo.UpdateCommentContent)
m.Group("/comments/:id", func() {
m.Post("", repo.UpdateCommentContent)
m.Post("/delete", repo.DeleteComment)
})
m.Group("/labels", func() {
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
@@ -520,9 +530,10 @@ func runWeb(ctx *cli.Context) {
m.Get("/src/*", repo.Home)
m.Get("/raw/*", repo.SingleDownload)
m.Get("/commits/*", repo.RefCommits)
m.Get("/commit/*", repo.Diff)
m.Get("/commit/:sha([a-z0-9]{40})$", repo.Diff)
m.Get("/forks", repo.Forks)
}, context.RepoRef())
m.Get("/commit/:sha([a-z0-9]{40})\\.:ext(patch|diff)", repo.RawDiff)
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.CompareDiff)
}, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare)
@@ -584,4 +595,6 @@ func runWeb(ctx *cli.Context) {
if err != nil {
log.Fatal(4, "Fail to start server: %v", err)
}
return nil
}

View File

@@ -12,7 +12,7 @@ RUN_MODE = dev
ROOT =
SCRIPT_TYPE = bash
; Default ANSI charset
ANSI_CHARSET =
ANSI_CHARSET =
; Force every new repository to be private
FORCE_PRIVATE = false
; Global maximum creation limit of repository per user, -1 means no limit
@@ -31,6 +31,8 @@ FEED_MAX_COMMIT_NUM = 5
; An invalid color like "none" or "disable" will have the default style
; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android
THEME_COLOR_META_TAG = `#ff5343`
; Max size of files to be displayed (defaults is 8MiB)
MAX_DISPLAY_FILE_SIZE = 8388608
[ui.admin]
; Number of users that are showed in one page
@@ -42,6 +44,10 @@ NOTICE_PAGING_NUM = 25
; Number of organization that are showed in one page
ORG_PAGING_NUM = 50
[ui.user]
; Number of repos that are showed in one page
REPO_PAGING_NUM = 15
[markdown]
; Enable hard line break extension
ENABLE_HARD_LINE_BREAK = false
@@ -53,12 +59,12 @@ CUSTOM_URL_SCHEMES =
PROTOCOL = http
DOMAIN = localhost
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
HTTP_ADDR =
HTTP_ADDR = 0.0.0.0
HTTP_PORT = 3000
; Local (DMZ) URL for Gogs workers (such as SSH update) accessing web service.
; In most cases you do not need to change the default value.
; Alter it only if your SSH server node is not the same as HTTP node.
LOCAL_ROOT_URL = http://localhost:%(HTTP_PORT)s/
LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
; Disable SSH feature when not available
DISABLE_SSH = false
; Whether use builtin SSH server or not.
@@ -70,7 +76,7 @@ SSH_PORT = 22
; Port number builtin SSH server listens on
SSH_LISTEN_PORT = %(SSH_PORT)s
; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
SSH_ROOT_PATH =
SSH_ROOT_PATH =
; Directory to create temporary files when test publick key using ssh-keygen,
; default is system temporary directory.
SSH_KEY_TEST_PATH =
@@ -167,24 +173,26 @@ SEND_BUFFER_LEN = 100
SUBJECT = %(APP_NAME)s
; Mail server
; Gmail: smtp.gmail.com:587
; QQ: smtp.qq.com:25
; QQ: smtp.qq.com:465
; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used.
HOST =
HOST =
; Disable HELO operation when hostname are different.
DISABLE_HELO =
DISABLE_HELO =
; Custom hostname for HELO operation, default is from system.
HELO_HOSTNAME =
HELO_HOSTNAME =
; Do not verify the certificate of the server. Only use this for self-signed certificates
SKIP_VERIFY =
SKIP_VERIFY =
; Use client certificate
USE_CERTIFICATE = false
CERT_FILE = custom/mailer/cert.pem
KEY_FILE = custom/mailer/key.pem
; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
FROM =
; Mailer user name and password
USER =
PASSWD =
; Use text/html as alternative format of content
ENABLE_HTML_ALTERNATIVE = false
[cache]
; Either "memory", "redis", or "memcache", default is "memory"
@@ -197,7 +205,7 @@ INTERVAL = 60
HOST =
[session]
; Either "memory", "file", "redis" or "mysql", default is "memory"
; Either "memory", "file", or "redis", default is "memory"
PROVIDER = memory
; Provider config options
; memory: not have any config yet
@@ -310,7 +318,7 @@ RUN_AT_START = false
; Update mirrors
[cron.update_mirrors]
SCHEDULE = @every 1h
SCHEDULE = @every 10m
; Repository health check
[cron.repo_health_check]
@@ -318,7 +326,7 @@ SCHEDULE = @every 24h
TIMEOUT = 60s
; Arguments for command 'git fsck', e.g. "--unreachable --tags"
; see more on http://git-scm.com/docs/git-fsck/1.7.5
ARGS =
ARGS =
; Check repository statistics
[cron.check_repo_stats]
@@ -326,10 +334,15 @@ RUN_AT_START = true
SCHEDULE = @every 24h
[git]
MAX_GIT_DIFF_LINES = 10000
; Max number of lines allowed of a single file in diff view.
MAX_GIT_DIFF_LINES = 1000
; Max number of characters of a line allowed in diff view.
MAX_GIT_DIFF_LINE_CHARACTERS = 500
; Max number of files shown in diff view.
MAX_GIT_DIFF_FILES = 100
; Arguments for command 'git gc', e.g. "--aggressive --auto"
; see more on http://git-scm.com/docs/git-gc/1.7.5
GC_ARGS =
GC_ARGS =
; Operation timeout in seconds
[git.timeout]
@@ -338,15 +351,20 @@ MIRROR = 300
CLONE = 300
PULL = 300
[api]
; Max number of items will response in a page
MAX_RESPONSE_ITEMS = 50
[i18n]
LANGS = en-US,zh-CN,zh-HK,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI
NAMES = English,简体中文,繁體中文,Deutsch,Français,Nederlands,Latviešu,Русский,日本語,Español,Português do Brasil,Polski,български,Italiano,Suomalainen
LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ
NAMES = English,简体中文,繁體中文(香港),繁體中文(台湾),Deutsch,Français,Nederlands,Latviešu,Русский,日本語,Español,Português do Brasil,Polski,български,Italiano,Suomalainen,Türkçe,čeština
; Used for datetimepicker
[i18n.datelang]
en-US = en
zh-CN = zh
zh-HK = zh-TW
zh-TW = zh-TW
de-DE = de
fr-FR = fr
nl-NL = nl
@@ -359,6 +377,8 @@ pl-PL = pl
bg-BG = bg
it-IT = it
fi-FI = fi
tr-TR = tr
cs-CZ = cs-CZ
; Extension mapping to highlight class
; e.g. .toml=ini

View File

@@ -1,6 +1,5 @@
ISC License:
Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
Copyright (c) 1995-2003 by Internet Software Consortium
Copyright (c) Year(s), Company or Person's Name
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

View File

@@ -14,6 +14,7 @@ Antoine GIRARD <sapk AT sapk DOT fr>
Arthur Aslanyan <arthur DOT e DOT aslanyan AT gmail DOT com>
Aurelien Darragon <aurelien DOT darragon AT gmail DOT com>
Barış Arda Yılmaz <ardayilmazgamer AT gmail DOT com>
Camille Baronnet <gogs AT camillebaronnet DOT fr>
Christoph Kisfeld <christoph DOT kisfeld AT gmail DOT com>
Cysioland
Daniel Speichert <daniel AT speichert DOT pl>
@@ -21,19 +22,20 @@ David Yzaguirre <dvdyzag AT gmail DOT com>
Dmitriy Nogay <me AT catwhocode DOT ga>
Enrico Testori hypertesto AT gmail DOT com
Ezequiel Gonzalez Rial <gonrial AT gmail DOT com>
Gabriel Dugny <gabriel DOT dugny AT gmail DOT com>
Gregor Santner <gdev AT live DOT de>
Halil Kaya <halil AT halilkaya DOT net>
Hamid Feizabadi <hamidfzm AT gmail DOT com>
Huimin Wang <wanghm2009 AT hotmail DOT co DOT jp>
ilko <kontact-mr.k AT outlook DOT com">
Ilya Makarov
Robert Nuske <robert DOT nuske AT web DOT de>
Robin Hübner <profan AT prfn DOT se>
Jamie Mansfield <dev AT jamierocks DOT uk>
Jean THOMAS <contact AT tibounise DOT com>
Juraj Bubniak <contact AT jbub DOT eu>
Lafriks <lafriks AT gmail DOT com>
Lauri Ojansivu <x AT xet7 DOT org>
Luc Stepniewski <luc AT stepniewski DOT fr>
Luca Bozzo <luca AT bozzo DOT it>
Luca Kröger <l DOT kroeger01 AT gmail DOT com>
Marc Schiller <marc AT schiller DOT im>
Marvin Menzerath <github AT marvin-menzerath DOT de>
@@ -45,6 +47,9 @@ Muhammad Fawwaz Orabi <mfawwaz93 AT gmail DOT com>
Nakao Takamasa <at.mattenn AT gmail DOT com>
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
Richard Bukovansky <richard DOT bukovansky @ gmail DOT com>
Robert Nuske <robert DOT nuske AT web DOT de>
Robin Hübner <profan AT prfn DOT se>
SeongJae Park <sj38 DOT park AT gmail DOT com>
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
Tilmann Bach <tilmann AT outlook DOT com>
@@ -53,3 +58,4 @@ Vladimir Jigulin mogaika AT yandex DOT ru
Vladimir Vissoultchev <wqweto AT gmail DOT com>
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
Pablo Saavedra <psaavedra AT igalia DOT com>

29
conf/locale/locale_bg-BG.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Име или ел. поща
password_holder=Парола
switch_dashboard_context=Превключи контекст на таблото
my_repos=Моите хранилища
show_more_repos=Покажи още хранилища...
collaborative_repos=Съвместни хранилища
my_orgs=Моите организации
my_mirrors=Моите огледала
@@ -148,6 +149,8 @@ forget_password=Забравена парола?
sign_up_now=Нуждаете се от профил? Регистрирайте се сега.
confirmation_mail_sent_prompt=Ново писмо за потвърждение е изпратено до <b>%s</b>. Моля проверете пощенската си кутия в рамките на следващите %d часа, за да завършите процеса на регистрация.
active_your_account=Активиране на профил
prohibit_login=Влизане забранено
prohibit_login_desc=Вашият профил е със забрана за влизане, моля свържете се с администратора.
resent_limit_prompt=За съжаление Вие съвсем наскоро изпратихте писмо за активация. Моля изчакайте 3 минути, след което опитайте отново.
has_unconfirmed_mail=Здравейте %s, имате непотвърден адрес на ел. поща (<b>%s</b>). Ако не сте получили писмо за потвърждение или имате нужда да се изпрати ново писмо, моля щракнете бутона по-долу.
resend_mail=Щракнете тук, за да се изпрати ново писмо за потвърждение
@@ -157,6 +160,7 @@ reset_password=Нулиране на паролата
invalid_code=За съжаление Вашия код за потвърждение е изтекъл или е невалиден.
reset_password_helper=Щракнете тук, за да нулирате паролата си
password_too_short=Размерът на паролата не може да бъде по-малък от 6 знака.
non_local_account=Нелокални потребители не могат да сменят паролата си през Gogs.
[mail]
activate_account=Моля активирайте Вашия профил
@@ -219,8 +223,7 @@ org_still_own_repo=Тази организация все още притежа
target_branch_not_exist=Целевият клон не съществува.
[user]
change_avatar=Сменете Вашия аватар на gravatar.com
change_custom_avatar=Сменете Вашия аватар в настройките
change_avatar=Проми своя аватар
join_on=Регистриран
repositories=Хранилища
activity=Публична дейност
@@ -350,6 +353,8 @@ readme_helper=Изберете шаблон на readme
auto_init=Инициализиране на това хранилище с избраните файлове и шаблон
create_repo=Създай хранилище
default_branch=Клон по подразбиране
mirror_prune=Окастряне
mirror_prune_desc=Премахва всички препратки за отдалечено проследяване, които не съществуват отдалечено
mirror_interval=Интервал на отразяване (часове)
mirror_address=Адрес на огледало
mirror_address_desc=Моля включете потребител и парола в адреса ако са нужни.
@@ -407,6 +412,7 @@ file_raw=Директен файл
file_history=История
file_view_raw=Виж директен файл
file_permalink=Постоянна връзка
file_too_large=Този файл е твърде голям за да се визуализира
commits.commits=Ревизии
commits.search=Търсене в ревизии
@@ -460,7 +466,8 @@ issues.next=Следваща
issues.open_title=Отворени
issues.closed_title=Затворени
issues.num_comments=%d коментара
issues.commented_at=`коментира <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`коментира <a href="#%s">%s</a>`
issues.delete_comment_confirm=Желаете ли да изтриете този коментар?
issues.no_content=Все още няма съдържание.
issues.close_issue=Затвори
issues.close_comment_issue=Kоментирай и затвори
@@ -561,6 +568,10 @@ wiki.last_updated=Последна модификация на %s
settings=Настройки
settings.options=Опции
settings.collaboration=Сътрудничество
settings.collaboration.admin=За администрация
settings.collaboration.write=За писане
settings.collaboration.read=За четене
settings.collaboration.undefined=Недефинирано
settings.hooks=Уеб-куки
settings.githooks=Git куки
settings.basic_settings=Основни настройки
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Посетителите ще бъдат пре
settings.issues_desc=Включва вградена система за проследяване на задачи
settings.use_external_issue_tracker=Използвай външна система за проследяване на задачи
settings.tracker_url_format=Формат на URL адрес на външна система за проследяване на задачи
settings.tracker_issue_style=Стил на именуване на външна система за проследяване на задачи:
settings.tracker_issue_style.numeric=Цифров
settings.tracker_issue_style.alphanumeric=Символен
settings.tracker_url_format_desc=Можете да използвате текстови маркери <code>{user} {repo} {index}</code> за потребителско име, име на хранилище и индекс на задача съответно.
settings.pulls_desc=Включва заявки за сливане за да може да се приемат външни доработки
settings.danger_zone=Опасна зона
@@ -597,9 +611,7 @@ settings.delete=Изтрий това хранилище
settings.delete_desc=След като изтриете хранилището, няма връщане назад. Моля, бъдете сигурни.
settings.delete_notices_1=- Тази операция <strong>НЕ МОЖЕ</strong> да бъде отменена в последствие.
settings.delete_notices_2=- Тази операция ще изтрие всичко от това хранилище, включително Git данни, задачи, коментари и достъпа на сътрудници.
settings.delete_notices_fork_1=- Ако това хранилище е публично, всички негови разклонения ще останат независими след изтриването му.
settings.delete_notices_fork_2=- Ако това хранилище е частно, всички негови разклонения ще бъдат премахнати по време на изтриването.
settings.delete_notices_fork_3=- Ако желаете да запазите всички разклонения след изтриването му, първо направете хранилището публично.
settings.delete_notices_fork_1=- Всички разклонения ще станат независими след изтриването.
settings.deletion_success=Хранилището е изтрито успешно!
settings.update_settings_success=Настройките на хранилището са запазени успешно.
settings.transfer_owner=Нов притежател
@@ -683,6 +695,8 @@ diff.show_unified_view=Обединен изглед
diff.stats_desc=променени са <strong>%d файла</strong>, в които са <strong>добавени %d</strong> реда и са <strong>изтрити %d</strong> реда
diff.bin=BIN
diff.view_file=Целия файл
diff.file_suppressed=Файловите разлики са ограничени, защото са твърде много
diff.too_many_files=Някои файлове не бяха показани, защото твърде много файлове са промени
release.releases=Версии
release.new_release=Нова версия
@@ -713,6 +727,7 @@ release.deletion=Изтрий версията
release.deletion_desc=При изтриване на тази версия ще се премахне и съответния Git маркер. Желаете ли да продължите?
release.deletion_success=Версията беше изтрита успешно!
release.tag_name_already_exist=Версия с това име на маркер вече съществува.
release.tag_name_invalid=Името на етикета е невалидно.
release.downloads=Изтегляния
[org]
@@ -880,6 +895,7 @@ users.edit_account=Редактирай профил
users.max_repo_creation=Макс. брой хранилища
users.max_repo_creation_desc=(Задайте -1 за да се използва глобалния лимит)
users.is_activated=Този профил е активиран
users.prohibit_login=Този профил има забрана за влизане
users.is_admin=Този профил има административни права
users.allow_git_hook=Този профил има разрешение да създава Git куки
users.allow_import_local=Този профил има права за импорт на локални хранилища
@@ -910,6 +926,7 @@ auths.enabled=Активно
auths.updated=Последна модификация
auths.auth_type=Тип на удостоверяване
auths.auth_name=Име на удостоверяване
auths.security_protocol=Протокол за защита
auths.domain=Домейн
auths.host=Сървър
auths.port=Порт

1115
conf/locale/locale_cs-CZ.ini Normal file

File diff suppressed because it is too large Load Diff

41
conf/locale/locale_de-DE.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Benutzername oder E-Mail
password_holder=Passwort
switch_dashboard_context=Kontext der Übersichtsseite wechseln
my_repos=Meine Repositories
show_more_repos=Zeige mehr Repositories...
collaborative_repos=Gemeinschaftliche Repositories
my_orgs=Meine Organisationen
my_mirrors=Meine Mirrors
@@ -148,6 +149,8 @@ forget_password=Passwort vergessen?
sign_up_now=Benötigen Sie ein Konto? Registrieren Sie sich jetzt.
confirmation_mail_sent_prompt=Eine neue Bestätigungs-E-Mail wurde an <b>%s</b> gesendet. Bitte kontrollieren Sie Ihr Postfach innerhalb der nächsten %d Stunden, um die Registrierung abzuschließen.
active_your_account=Aktivieren Sie Ihr Konto
prohibit_login=Anmelden verboten
prohibit_login_desc=Ihrem Konto ist es nicht gestattet sich anzumelden. Bitte kontaktieren Sie den Administrator.
resent_limit_prompt=Es tut uns leid, aber Sie haben bereits eine Aktivierungs-E-Mail angefordert. Bitte warten Sie 3 Minuten und probieren Sie es dann nochmal.
has_unconfirmed_mail=Hallo %s, Sie haben eine unbestätigte E-Mail-Adresse (<b>%s</b>). Wenn Sie keine Bestätigungs-E-Mail erhalten haben oder eine neue benötigen, klicken Sie bitte auf den folgenden Button.
resend_mail=Hier klicken, um die Aktivierungs-E-Mail erneut zu versenden
@@ -157,6 +160,7 @@ reset_password=Passwort zurücksetzen
invalid_code=Es tut uns leid, der Bestätigungscode ist abgelaufen oder ungültig.
reset_password_helper=Hier klicken, um das Passwort zurückzusetzen
password_too_short=Das Passwort muss mindenstens 6 Zeichen lang sein.
non_local_account=Nicht-lokale Konten können Passwörter nicht via Gogs ändern.
[mail]
activate_account=Bitte aktivieren Sie Ihr Konto
@@ -219,8 +223,7 @@ org_still_own_repo=Diese Organisation besitzt noch Repositories. Diese müssen z
target_branch_not_exist=Ziel-Branch existiert nicht
[user]
change_avatar=Ändern Sie Ihr Profilbild auf gravatar.com
change_custom_avatar=Ändern Sie Ihr Profilbild in den Einstellungen
change_avatar=Ändern Sie ihr Profilbild
join_on=Beigetreten am
repositories=Repositories
activity=Öffentliche Aktivität
@@ -326,7 +329,7 @@ delete_account=Konto löschen
delete_prompt=Diese Aktion wird Ihr Konto dauerhaft löschen und kann <strong>NICHT</strong> rückgängig gemacht werden!
confirm_delete_account=Löschvorgang bestätigen
delete_account_title=Konto löschen
delete_account_desc=Sie sind dabei dieses Konto dauerhaft zu löschen, möchten Sie wirklich fortfahren?
delete_account_desc=Sie sind dabei dieses Konto dauerhaft zu löschen. Möchten Sie wirklich fortfahren?
[repo]
owner=Besitzer
@@ -342,7 +345,7 @@ fork_from=Fork von
fork_visiblity_helper=Die Sichtbarkeit von geforkten Repositories ist nicht veränderbar.
repo_desc=Beschreibung
repo_lang=Sprache
repo_lang_helper=.gitignore Dateien auswählen
repo_lang_helper=Wählen Sie eine .gitignore-Datei aus
license=Lizenz
license_helper=Wählen Sie eine Lizenz aus
readme=Readme
@@ -350,6 +353,8 @@ readme_helper=Readme Vorlage auswählen
auto_init=Repository mit ausgewählten Dateien und Vorlagen initialisieren
create_repo=Repository erstellen
default_branch=Standard-Branch
mirror_prune=Entfernen
mirror_prune_desc=Entferne alle Verweise auf nicht mehr existierende entfernte Repositories
mirror_interval=Mirror-Intervall (in Stunden)
mirror_address=Mirror-Adresse
mirror_address_desc=Bitte die nötigen Zugangsdaten in die Adresse mit aufnehmen.
@@ -407,6 +412,7 @@ file_raw=Originalformat
file_history=Verlauf
file_view_raw=Ansicht im Originalformat
file_permalink=Permalink
file_too_large=Diese Datei ist zu groß zum Anzeigen
commits.commits=Commits
commits.search=Commits durchsuchen
@@ -460,7 +466,8 @@ issues.next=Nächste
issues.open_title=Offen
issues.closed_title=Geschlossen
issues.num_comments=%d Kommentare
issues.commented_at=`hat <a id="%[1]s" href="#%[1]s">%[2]s</a> kommentiert`
issues.commented_at=`kommentierte <a href="#%s">%s</a>`
issues.delete_comment_confirm=Sind Sie sich sicher, dass Sie diesen Kommentar löschen wollen?
issues.no_content=Hier gibt es bis jetzt noch keinen Inhalt.
issues.close_issue=Schließen
issues.close_comment_issue=Kommentieren und schließen
@@ -505,11 +512,11 @@ pulls.merged_title_desc=hat %[1]d Commits von <code>%[2]s</code> nach <code>%[3]
pulls.tab_conversation=Diskussion
pulls.tab_commits=Commits
pulls.tab_files=Geänderte Dateien
pulls.reopen_to_merge=Bitte diesen Pull-Request wiedereröffnen, um die Merge-Operation auszuführen.
pulls.reopen_to_merge=Bitte diesen Pull-Request wieder eröffnen, um die Merge-Operation auszuführen.
pulls.merged=Zusammengeführt
pulls.has_merged=Dieser Pull-Request wurde erfolgreich zusammengeführt!
pulls.data_broken=Die Daten dieses Pull-Requests sind defekt, da Fork-Informationen gelöscht wurden.
pulls.is_checking=Die Konfliktprüfung ist in Arbeit. Bitte aktualisieren Sie die Seite in wenigen Momenten.
pulls.is_checking=Die Konfliktprüfung läuft noch. Bitte aktualisieren Sie die Seite in wenigen Augenblicken.
pulls.can_auto_merge_desc=Dieser Pull-Request kann automatisch zusammengeführt werden.
pulls.cannot_auto_merge_desc=Dieser Pull-Request kann nicht automatisch zusammengeführt werden, da es Konflikte gibt.
pulls.cannot_auto_merge_helper=Bitte manuell zusammenführen, um die Konflikte zu lösen.
@@ -561,6 +568,10 @@ wiki.last_updated=Zuletzt aktualisiert %s
settings=Einstellungen
settings.options=Optionen
settings.collaboration=Zusammenarbeit
settings.collaboration.admin=Adminrechte
settings.collaboration.write=Schreibrechte
settings.collaboration.read=Leserechte
settings.collaboration.undefined=Nicht definiert
settings.hooks=Webhooks
settings.githooks=Git-Hooks
settings.basic_settings=Grundeinstellungen
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Besucher werden auf diese URL umgeleitet, wenn s
settings.issues_desc=Eingebautes einfaches Issue-System verwenden
settings.use_external_issue_tracker=Externes Issue-System verwenden
settings.tracker_url_format=URL-Format des externen Issue-Systems
settings.tracker_issue_style=Namenskonvention des externen Issue-Trackers:
settings.tracker_issue_style.numeric=Numerisch
settings.tracker_issue_style.alphanumeric=Alphanumerisch
settings.tracker_url_format_desc=Sie können die Platzhalter <code>{user} {repo} {index}</code> für den Benutzernamen, den Namen des Repositories und die Issue-Nummer verwenden.
settings.pulls_desc=Pull-Requests aktivieren, um öffentliche Mitwirkung zu ermöglichen
settings.danger_zone=Gefahrenzone
@@ -597,9 +611,7 @@ settings.delete=Dieses Repository löschen
settings.delete_desc=Wenn dieses Repository gelöscht wurde, gibt es keinen Weg zurück. Bitte seien Sie vorsichtig.
settings.delete_notices_1=- Diese Operation kann <strong>NICHT</strong> rückgängig gemacht werden.
settings.delete_notices_2=- Die Operation wird alles, was mit diesem Git-Repository verbunden ist, dauerhaft löschen, inklusive der Daten, Issues, Kommentare und Zugriffsrechte von Mitarbeitern.
settings.delete_notices_fork_1=- Wenn dies ein öffentliches Repository ist, werden nach dem Löschen alle Forks unabhängig.
settings.delete_notices_fork_2=- Wenn dies ein privates Repository ist, werden gleichzeitig alle Forks entfernt.
settings.delete_notices_fork_3=- Wenn alle Forks erhalten bleiben sollen, dann muss zuerst die Sichtbarkeit dieses Repositories auf öffentlich gesetzt werden.
settings.delete_notices_fork_1=- Nach dem Löschen werden alle Forks unabhängig.
settings.deletion_success=Repository wurde erfolgreich gelöscht!
settings.update_settings_success=Repository-Optionen aktualisiert.
settings.transfer_owner=Neuer Besitzer
@@ -683,6 +695,8 @@ diff.show_unified_view=Gesamtansicht
diff.stats_desc=<strong> %d geänderte Dateien</strong> mit <strong>%d neuen</strong> und <strong>%d gelöschten</strong> Zeilen
diff.bin=BIN
diff.view_file=Datei anzeigen
diff.file_suppressed=Datei-Diff unterdrückt, da er zu groß ist
diff.too_many_files=Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.
release.releases=Releases
release.new_release=Neues Release
@@ -713,6 +727,7 @@ release.deletion=Release löschen
release.deletion_desc=Beim Löschen dieses Releases wird das entsprechende Git-Tag gelöscht. Möchten Sie fortfahren?
release.deletion_success=Release wurde erfolgreich gelöscht!
release.tag_name_already_exist=Ein Release mit diesem Tag existiert bereits.
release.tag_name_invalid=Tag-Name ist nicht gültig.
release.downloads=Downloads
[org]
@@ -880,6 +895,7 @@ users.edit_account=Konto bearbeiten
users.max_repo_creation=Maximale Anzahl erstellbarer Repositories
users.max_repo_creation_desc=(Auf -1 setzen, um das globale Standardlimit zu verwenden)
users.is_activated=Dieses Konto ist aktiviert
users.prohibit_login=Diesem Konto ist es nicht gestattet sich anzumelden
users.is_admin=Dieses Konto hat Administratorrechte
users.allow_git_hook=Dieses Konto ist berechtigt Git-Hooks zu erstellen
users.allow_import_local=Dieses Konto ist berechtigt, lokale Repositories zu importieren
@@ -910,6 +926,7 @@ auths.enabled=Aktiviert
auths.updated=Aktualisiert
auths.auth_type=Authentifizierungstyp
auths.auth_name=Authentifizierungsname
auths.security_protocol=Sicherheitsprotokoll
auths.domain=Domain
auths.host=Host
auths.port=Port
@@ -938,7 +955,7 @@ auths.pam_service_name=PAM Dienstname
auths.enable_auto_register=Automatische Registrierung aktivieren
auths.tips=Tipps
auths.edit=Authentifizierungseinstellungen bearbeiten
auths.activated=Diese Authentifizierung ist aktiviert
auths.activated=Diese Authentifizierung ist aktiv
auths.new_success=Neue Authentifizierung '%s' wurde erfolgreich hinzugefügt.
auths.update_success=Die Authentifizierungseinstellungen wurden erfolgreich aktualisiert.
auths.update=Authentifizierungseinstellungen aktualisieren
@@ -946,7 +963,7 @@ auths.delete=Diese Authentifizierung löschen
auths.delete_auth_title=Authentifizierung löschen
auths.delete_auth_desc=Diese Authentifizierung wird gelöscht. Möchten Sie fortfahren?
auths.still_in_used=Diese Authentifizierung wird noch von einigen Benutzern verwendet. Bitte löschen Sie diese Benutzer oder ändern Sie deren Anmeldetyp.
auths.deletion_success=Authentifizierung wurde erfolgreich entfernt!
auths.deletion_success=Authentifizierung wurde erfolgreich gelöscht!
config.server_config=Serverkonfiguration
config.app_name=Anwendungsname

View File

@@ -124,6 +124,7 @@ uname_holder = Username or email
password_holder = Password
switch_dashboard_context = Switch Dashboard Context
my_repos = My Repositories
show_more_repos = Show more repositories...
collaborative_repos = Collaborative Repositories
my_orgs = My Organizations
my_mirrors = My Mirrors
@@ -148,6 +149,8 @@ forget_password = Forgot password?
sign_up_now = Need an account? Sign up now.
confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
active_your_account = Activate Your Account
prohibit_login = Login Prohibited
prohibit_login_desc = Your account is prohibited to login, please contact site admin.
resent_limit_prompt = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below.
resend_mail = Click here to resend your activation email
@@ -220,8 +223,7 @@ org_still_own_repo = This organization still has ownership of repositories, you
target_branch_not_exist = Target branch does not exist.
[user]
change_avatar = Change your avatar at gravatar.com
change_custom_avatar = Change your avatar in settings
change_avatar = Change your avatar
join_on = Joined on
repositories = Repositories
activity = Public Activity
@@ -351,6 +353,8 @@ readme_helper = Select a readme template
auto_init = Initialize this repository with selected files and template
create_repo = Create Repository
default_branch = Default Branch
mirror_prune = Prune
mirror_prune_desc = Remove any remote-tracking references that no longer exist on the remote
mirror_interval = Mirror Interval (hour)
mirror_address = Mirror Address
mirror_address_desc = Please include necessary user credentials in the address.
@@ -408,6 +412,7 @@ file_raw = Raw
file_history = History
file_view_raw = View Raw
file_permalink = Permalink
file_too_large = This file is too large to be shown
commits.commits = Commits
commits.search = Search commits
@@ -461,7 +466,8 @@ issues.next = Next
issues.open_title = Open
issues.closed_title = Closed
issues.num_comments = %d comments
issues.commented_at = `commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at = `commented <a href="#%s">%s</a>`
issues.delete_comment_confirm = Are you sure you want to delete this comment?
issues.no_content = There is no content yet.
issues.close_issue = Close
issues.close_comment_issue = Comment and close
@@ -562,6 +568,10 @@ wiki.last_updated = Last updated %s
settings = Settings
settings.options = Options
settings.collaboration = Collaboration
settings.collaboration.admin = Admin
settings.collaboration.write = Write
settings.collaboration.read = Read
settings.collaboration.undefined = Undefined
settings.hooks = Webhooks
settings.githooks = Git Hooks
settings.basic_settings = Basic Settings
@@ -569,13 +579,18 @@ settings.site = Official Site
settings.update_settings = Update Settings
settings.change_reponame_prompt = This change will affect how links relate to the repository.
settings.advanced_settings = Advanced Settings
settings.wiki_desc = Enable wiki to allow people write documents
settings.wiki_desc = Enable wiki system
settings.use_internal_wiki = Use builtin wiki
settings.use_external_wiki = Use external wiki
settings.external_wiki_url = External Wiki URL
settings.external_wiki_url_desc = Visitors will be redirected to URL when they click on the tab.
settings.issues_desc = Enable builtin lightweight issue tracker
settings.issues_desc = Enable issue tracker
settings.use_internal_issue_tracker = Use builtin lightweight issue tracker
settings.use_external_issue_tracker = Use external issue tracker
settings.tracker_url_format = External Issue Tracker URL Format
settings.tracker_issue_style = External Issue Tracker Naming Style:
settings.tracker_issue_style.numeric = Numeric
settings.tracker_issue_style.alphanumeric = Alphanumeric
settings.tracker_url_format_desc = You can use placeholder <code>{user} {repo} {index}</code> for user name, repository name and issue index.
settings.pulls_desc = Enable pull requests to accept public contributions
settings.danger_zone = Danger Zone
@@ -598,9 +613,7 @@ settings.delete = Delete This Repository
settings.delete_desc = Once you delete a repository, there is no going back. Please be certain.
settings.delete_notices_1 = - This operation <strong>CANNOT</strong> be undone.
settings.delete_notices_2 = - This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
settings.delete_notices_fork_1 = - If this repository is public, all forks will become independent after deletion.
settings.delete_notices_fork_2 = - If this repository is private, all forks will be removed at the same time.
settings.delete_notices_fork_3 = - If you want to keep all forks after deletion, please change visibility of this repository to public first.
settings.delete_notices_fork_1 = - All forks will become independent after deletion.
settings.deletion_success = Repository has been deleted successfully!
settings.update_settings_success = Repository options has been updated successfully.
settings.transfer_owner = New Owner
@@ -684,6 +697,8 @@ diff.show_unified_view = Unified View
diff.stats_desc = <strong> %d changed files</strong> with <strong>%d additions</strong> and <strong>%d deletions</strong>
diff.bin = BIN
diff.view_file = View File
diff.file_suppressed = File diff suppressed because it is too large
diff.too_many_files = Some files were not shown because too many files changed in this diff
release.releases = Releases
release.new_release = New Release
@@ -714,6 +729,7 @@ release.deletion = Release Deletion
release.deletion_desc = Deleting this release will delete the corresponding Git tag. Do you want to continue?
release.deletion_success = Release has been deleted successfully!
release.tag_name_already_exist = Release with this tag name already exists.
release.tag_name_invalid = Tag name is not valid.
release.downloads = Downloads
[org]
@@ -881,6 +897,7 @@ users.edit_account = Edit Account
users.max_repo_creation = Maximum Repository Creation Limit
users.max_repo_creation_desc = (Set -1 to use global default limit)
users.is_activated = This account is activated
users.prohibit_login = This account is prohibited to login
users.is_admin = This account has administrator permissions
users.allow_git_hook = This account has permissions to create Git hooks
users.allow_import_local = This account has permissions to import local repositories
@@ -911,6 +928,7 @@ auths.enabled = Enabled
auths.updated = Updated
auths.auth_type = Authentication Type
auths.auth_name = Authentication Name
auths.security_protocol = Security Protocol
auths.domain = Domain
auths.host = Host
auths.port = Port
@@ -939,7 +957,7 @@ auths.pam_service_name = PAM Service Name
auths.enable_auto_register = Enable Auto Registration
auths.tips = Tips
auths.edit = Edit Authentication Setting
auths.activated = This authentication is activate
auths.activated = This authentication is activated
auths.new_success = New authentication '%s' has been added successfully.
auths.update_success = Authentication setting has been updated successfully.
auths.update = Update Authentication Setting

45
conf/locale/locale_es-ES.ini Executable file → Normal file
View File

@@ -6,7 +6,7 @@ explore=Explorar
help=Ayuda
sign_in=Iniciar sesión
sign_out=Cerrar sesión
sign_up=Suscripción
sign_up=Registro
register=Registro
website=Página web
version=Versión
@@ -47,7 +47,7 @@ cancel=Cancelar
[install]
install=Instalación
title=Pasos de la instalación por primera vez
docker_helper=Si está ejecutando Gogs usando Docker, por favor lea <a target="_blank" href="%s"> estas pautas</a> antes de cambiar nada en esta página!
docker_helper=Si está ejecutando Gogs usando Docker, ¡por favor lea <a target="_blank" href="%s"> estas pautas</a> antes de cambiar nada en esta página!
requite_db_desc=Gogs requiere una base de datos MySQL, PostgreSQL, SQLite3 o TiDB.
db_title=Configuración de base de datos
db_type=Tipo de base de datos
@@ -65,7 +65,7 @@ no_admin_and_disable_registration=No puede deshabilitar el registro sin crear un
err_empty_admin_password=La contraseña de administrador no puede estar vacía.
general_title=Configuración General de Gogs
app_name=Nombre de la Aplicación
app_name=Nombre de la aplicación
app_name_helper=Pon aquí el nombre de tu organización, ¡alto y claro!
repo_path=Ruta del repositorio de Raiz (Root)
repo_path_helper=Todos los repositorios remotos de Git se guardarán en este directorio.
@@ -124,6 +124,7 @@ uname_holder=Nombre de usuario o correo electrónico
password_holder=Contraseña
switch_dashboard_context=Cambiar el contexto del Dashboard
my_repos=Mis repositorios
show_more_repos=Mostrar más repositorios...
collaborative_repos=Repositorios colaborativos
my_orgs=Mis organizaciones
my_mirrors=Mis réplicas
@@ -137,7 +138,7 @@ users=Usuarios
search=Buscar
[auth]
create_new_account=Crear una Nueva Cuenta
create_new_account=Crear una nueva cuenta
register_hepler_msg=¿Ya tienes una cuenta? ¡Inicia sesión!
social_register_hepler_msg=¿Ya tienes una cuenta? ¡Enlázala!
disable_register_prompt=Lo sentimos, el registro está deshabilitado. Por favor, contacta con el administrador del sitio.
@@ -148,6 +149,8 @@ forget_password=¿Has olvidado tu contraseña?
sign_up_now=¿Necesitas una cuenta? Regístrate ahora.
confirmation_mail_sent_prompt=Un nuevo correo de confirmación se ha enviado a <b>%s</b>. Por favor, comprueba tu bandeja de entrada en las siguientes %d horas para completar el proceso de registro.
active_your_account=Activa tu cuenta
prohibit_login=Ingreso prohibido
prohibit_login_desc=Su cuenta tiene prohibido ingresar al sistema, fovor contactar al administrador del sistema.
resent_limit_prompt=Lo sentimos, estás solicitando el reenvío del mail de activación con demasiada frecuencia. Por favor, espera 3 minutos.
has_unconfirmed_mail=Hola %s, tu correo electrónico (<b>%s</b>) no está confirmado. Si no has recibido un correo de confirmación o necesitas que lo enviemos de nuevo, por favor, haz click en el siguiente botón.
resend_mail=Haz click aquí para reenviar tu correo electrónico de activación
@@ -157,6 +160,7 @@ reset_password=Restablecer su contraseña
invalid_code=Lo sentimos, su código de confirmación ha expirado o no es valido.
reset_password_helper=Haga Clic aquí para restablecer su contraseña
password_too_short=La longitud de la contraseña no puede ser menor a 6.
non_local_account=Cuentas que no son locales no pueden cambiar las contraseñas a través de Gogs.
[mail]
activate_account=Por favor, active su cuenta
@@ -219,9 +223,8 @@ org_still_own_repo=Esta organización es dueña de uno o más repositorios, tien
target_branch_not_exist=La rama de destino no existe
[user]
change_avatar=Cambia tu avatar en gravatar.com
change_custom_avatar=Cambia tu avatar en la configuración
join_on=Registrado en
change_avatar=Cambiar tu avatar
join_on=Registrado el
repositories=Repositorios
activity=Actividad pública
followers=Seguidores
@@ -341,7 +344,7 @@ fork_repo=Hacer Fork del repositorio
fork_from=Crear un Fork desde
fork_visiblity_helper=No es posible cambiar la visibilidad de un Fork
repo_desc=Descripción
repo_lang=Lenguaje
repo_lang=Idioma
repo_lang_helper=Seleccione archivo .gitignore
license=Licencia
license_helper=Selecciona un fichero de licencia
@@ -350,6 +353,8 @@ readme_helper=Seleccione una plantilla de archivo readme
auto_init=Inicializar los archivos seleccionados y plantillas de este repositorio
create_repo=Crear repositorio
default_branch=Rama por defecto
mirror_prune=Purgar
mirror_prune_desc=Remover referencias remotas que no existan remotamente
mirror_interval=Intervalo de la réplica (en horas)
mirror_address=Dirección de la réplica
mirror_address_desc=Por favor, incluya las credenciales de usuario necesarias en la dirección.
@@ -379,12 +384,12 @@ copy_link_success=¡Copiado!
copy_link_error=Presione ⌘ + C o Ctrl-C para copiar
copied=Copiado correctamente
unwatch=Dejar de vigilar
watch=Vigilar
watch=Seguir
unstar=Eliminar destacado
star=Destacar
fork=Fork
no_desc=Sin Descripción
no_desc=Sin descripción
quick_guide=Guía Rápida
clone_this_repo=Clonar este repositorio
create_new_repo_command=Crear un nuevo repositorio desde línea de comandos
@@ -407,6 +412,7 @@ file_raw=Raw
file_history=Histórico
file_view_raw=Ver Raw
file_permalink=Permalink
file_too_large=Este archivo es demasiado grande para ser mostrado
commits.commits=Commits
commits.search=Buscar Commits
@@ -460,7 +466,8 @@ issues.next=Página Siguiente
issues.open_title=Abierta
issues.closed_title=Cerrada
issues.num_comments=%d comentarios
issues.commented_at=`comentada <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at='comentado <a href="#%s"> %s'</a>
issues.delete_comment_confirm=¿Seguro que deseas eliminar este comentario?
issues.no_content=Aún no existe contenido.
issues.close_issue=Cerrar
issues.close_comment_issue=Comentar y cerrar
@@ -561,6 +568,10 @@ wiki.last_updated=Última actualización %s
settings=Configuración
settings.options=Opciones
settings.collaboration=Colaboración
settings.collaboration.admin=Administrador
settings.collaboration.write=Escritura
settings.collaboration.read=Lectura
settings.collaboration.undefined=Indefinido
settings.hooks=Webhooks
settings.githooks=Git Hooks
settings.basic_settings=Configuración Básica
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Los visitantes serán redireccionados a la URL c
settings.issues_desc=Habilitar tracker ligero de incidencias
settings.use_external_issue_tracker=Usar tracker externo de incidencias
settings.tracker_url_format=Formato URL del tracker de incidencias externo
settings.tracker_issue_style=Estilo de etiquetado del tracker externo de incidencias:
settings.tracker_issue_style.numeric=Numérico
settings.tracker_issue_style.alphanumeric=Alfanumérico
settings.tracker_url_format_desc=Puedes usar las plantillas <code>{user} {repo} {index}</code> para el nombre de usuario, nombre del repositorio e índice de la incidencia.
settings.pulls_desc=Habilitar Pull Requests para aceptar contribuciones públicas
settings.danger_zone=Zona de Peligro
@@ -597,9 +611,7 @@ settings.delete=Eliminar este repositorio
settings.delete_desc=Una vez has eliminado un repositorio, no hay vuelta atrás. Por favor, asegúrate de que es lo que quieres.
settings.delete_notices_1=- Esta operación <strong>NO PUEDE</strong> revertirse.
settings.delete_notices_2=- Esta operación eliminará de manera permanente todo el contenido de este repositorio, incluyendo los datos de git, las incidencias, los comentarios y los permisos de acceso de los colaboradores.
settings.delete_notices_fork_1=- Si este repositorio es público, todos los forks se convertirán en repositorios independientes tras el borrado.
settings.delete_notices_fork_2=- Si este repositorio es privado, todos los forks serán eliminados simultáneamente.
settings.delete_notices_fork_3=- Si desea mantener los forks tras el borrado, por favor convierta este repositorio en público antes de proceder.
settings.delete_notices_fork_1=- Todos los forks se convertirán en independientes tras el borrado.
settings.deletion_success=¡El respositorio ha sido eliminado satisfactoriamente!
settings.update_settings_success=Las opciones del repositorio se han actualizado correctamente.
settings.transfer_owner=Nuevo Propietario
@@ -683,6 +695,8 @@ diff.show_unified_view=Unificar vista
diff.stats_desc=Se han <strong>modificado %d ficheros</strong> con <strong>%d adiciones</strong> y <strong>%d borrados</strong>
diff.bin=BIN
diff.view_file=Ver fichero
diff.file_suppressed=La diferencia del archivo ha sido suprimido porque es demasiado grande
diff.too_many_files=Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio
release.releases=Releases
release.new_release=Nueva Release
@@ -713,6 +727,7 @@ release.deletion=Eliminar Release
release.deletion_desc=Eliminar este Release eliminará la etiqueta correspondiente. ¿Desea continuar?
release.deletion_success=¡El release ha sido eliminado correctamente!
release.tag_name_already_exist=Ya existe una Release con esta etiqueta.
release.tag_name_invalid=Nombre de la etiqueta no es válido.
release.downloads=Descargas
[org]
@@ -880,6 +895,7 @@ users.edit_account=Editar Cuenta
users.max_repo_creation=Límite máximo de repositorios
users.max_repo_creation_desc=(Configura a -1 para usar el límite global por defecto)
users.is_activated=Esta cuenta está activada
users.prohibit_login=Esta cuenta no tiene permitido ingresar
users.is_admin=Esta cuenta tiene permisos de administrador
users.allow_git_hook=Esta cuenta tiene permisos para crear hooks de Git
users.allow_import_local=Esta cuenta dispone de permisos para importar repositorios locales
@@ -910,6 +926,7 @@ auths.enabled=Activo
auths.updated=Actualizado
auths.auth_type=Tipo de autenticación
auths.auth_name=Nombre de autenticación
auths.security_protocol=Protocolo de seguridad
auths.domain=Dominio
auths.host=Host
auths.port=Puerto

29
conf/locale/locale_fi-FI.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Käyttäjätunnus tai sähköposti
password_holder=Salasana
switch_dashboard_context=Vaihda kojelaudan kontekstia
my_repos=Reponi
show_more_repos=Show more repositories...
collaborative_repos=Yhteistyö repot
my_orgs=Organisaationi
my_mirrors=Peilini
@@ -148,6 +149,8 @@ forget_password=Unohtuiko salasana?
sign_up_now=Tarvitsetko tilin? Rekisteröidy nyt.
confirmation_mail_sent_prompt=Uusi varmistus sähköposti on lähetetty osoitteeseen <b>%s</b>, ole hyvä ja tarkista saapuneet seuraavan %d tunnin sisällä saadaksesi rekisteröintiprosessin valmiiksi.
active_your_account=Aktivoi tilisi
prohibit_login=Login Prohibited
prohibit_login_desc=Your account is prohibited to login, please contact site admin.
resent_limit_prompt=Sori, olet jo tilannut aktivointi sähköpostin lähiaikoina. Ole hyvä ja odota 3 minuuttia ja yritä sitten uudelleen.
has_unconfirmed_mail=Hei %s, sinulla on varmistamaton sähköposti osoite (<b>%s</b>). Jos et ole saanut varmistus sähköpostia tai tarvitset uudelleenlähetyksen, ole hyvä ja klikkaa allaolevaa painiketta.
resend_mail=Klikkaa tästä uudelleenlähettääksesi aktivointi sähköpostisi
@@ -157,6 +160,7 @@ reset_password=Nollaa salasanasi
invalid_code=Sori, varmistuskoodisi on vanhentunut tai väärä.
reset_password_helper=Klikkaa tästä nollataksesi salasanasi
password_too_short=Salasanan pituus ei voi olla vähemmän kuin 6 merkkiä.
non_local_account=Non-local accounts cannot change passwords through Gogs.
[mail]
activate_account=Ole hyvä ja aktivoi tilisi
@@ -219,8 +223,7 @@ org_still_own_repo=Tällä organisaatiolla on yhä omistajuus repoon, sinun täy
target_branch_not_exist=Kohde branchia ei ole olemassa.
[user]
change_avatar=Vaihda avatarisi osoitteeessa gravatar.com
change_custom_avatar=Vaihda avatar asetuksissa
change_avatar=Change your avatar
join_on=Liitytty
repositories=Repot
activity=Julkinen toiminta
@@ -350,6 +353,8 @@ readme_helper=Valitse Lueminut-malli
auto_init=Alusta tämä repo valituilla tiedostoilla ja mallilla
create_repo=Luo repo
default_branch=Oletus branch
mirror_prune=Prune
mirror_prune_desc=Remove any remote-tracking references that no longer exist on the remote
mirror_interval=Peili aikaväli (tuntia)
mirror_address=Peili osoite
mirror_address_desc=Ole hyvä ja liitä osoitteeseen tarvittavat käyttäjätunnukset.
@@ -407,6 +412,7 @@ file_raw=Raaka
file_history=Historia
file_view_raw=Näytä raaka
file_permalink=Pysyvä linkki
file_too_large=This file is too large to be shown
commits.commits=Commitit
commits.search=Etsi commiteista
@@ -460,7 +466,8 @@ issues.next=Seuraava
issues.open_title=Avoinna
issues.closed_title=Suljettu
issues.num_comments=%d kommenttia
issues.commented_at=`kommentoi <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`commented <a href="#%s">%s</a>`
issues.delete_comment_confirm=Are you sure you want to delete this comment?
issues.no_content=Sisältöä ei vielä ole.
issues.close_issue=Sulje
issues.close_comment_issue=Kommentoi ja sulje
@@ -561,6 +568,10 @@ wiki.last_updated=Viimeksi päivitetty: %s
settings=Asetukset
settings.options=Valinnaiset
settings.collaboration=Yhteistyö
settings.collaboration.admin=Admin
settings.collaboration.write=Write
settings.collaboration.read=Read
settings.collaboration.undefined=Undefined
settings.hooks=Webkoukut
settings.githooks=Git koukut
settings.basic_settings=Perusasetukset
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Vierailijat uudelleenohjataan URL-osoitteeseen k
settings.issues_desc=Ota käyttöön sisäänrakennettu kevyt vikaseuranta
settings.use_external_issue_tracker=Käytä ulkoista vikaseurantaa
settings.tracker_url_format=Ulkoisen vikaseurannan URL muoto
settings.tracker_issue_style=External Issue Tracker Naming Style:
settings.tracker_issue_style.numeric=Numeric
settings.tracker_issue_style.alphanumeric=Alphanumeric
settings.tracker_url_format_desc=Voit käyttää paikkamerkkiä <code>{user} {repo} {index}</code> käyttäjänimelle, reponimelle ja vikanumerolle.
settings.pulls_desc=Ota käyttöön pull-pyynnöt salliaksesi julkiset koodilahjoitukset
settings.danger_zone=Vaaravyöhyke
@@ -597,9 +611,7 @@ settings.delete=Poista tämä repo
settings.delete_desc=Huomio, kun kerran poistat repon, niin ei ole paluuta. Varmista että haluat todella tehdä tämän.
settings.delete_notices_1=- Tätä toimintoa <strong>EI VOI</strong> peruuttaa myöhemmin.
settings.delete_notices_2=- Tämä toiminto poistaa pysyvästi kaikki tästä reposta, mukaanlukien Git tiedot, ongelmat, kommentit ja yhteistyökumppanien pääsyoikeudet.
settings.delete_notices_fork_1=- Jos tämä repo on julkinen, kaikki forkit tulevat itsenäiseksi poiston jälkeen.
settings.delete_notices_fork_2=- Jos tämä repo on yksityinen, kaikki forkit poistetaan samalla.
settings.delete_notices_fork_3=- Jos haluat pitää kaikki forkit poistamisen jälkeen, ole hyvä ja muuta tämän repon näkyvyys julkiseksi ensin.
settings.delete_notices_fork_1=- All forks will become independent after deletion.
settings.deletion_success=Repo on poistettu onnistuneesti!
settings.update_settings_success=Repom asetukset on päivitetty onnistuneesti.
settings.transfer_owner=Uusi omistaja
@@ -683,6 +695,8 @@ diff.show_unified_view=Yhdistetty näkymä
diff.stats_desc=<strong>%d muutettua tiedostoa</strong> jossa <strong>%d lisäystä</strong> ja <strong>%d poistoa</strong>
diff.bin=BIN
diff.view_file=Näytä tiedosto
diff.file_suppressed=File diff suppressed because it is too large
diff.too_many_files=Some files were not shown because too many files changed in this diff
release.releases=Julkaisut
release.new_release=Uusi julkaisu
@@ -713,6 +727,7 @@ release.deletion=Version poisto
release.deletion_desc=Tämän version poistaminen poistaa vastaavan Git tagin. Haluatko jatkaa?
release.deletion_success=Versio on poistettu onnistuneesti!
release.tag_name_already_exist=Versio tällä taginimellä on jo olemassa.
release.tag_name_invalid=Tag name is not valid.
release.downloads=Lataukset
[org]
@@ -880,6 +895,7 @@ users.edit_account=Muokkaa tiliä
users.max_repo_creation=Maksimi repojen määrä jonka voi luoda
users.max_repo_creation_desc=(Aseta -1 käyttääksesi globaalia oletusrajaa)
users.is_activated=Tämä tili on aktivoitu
users.prohibit_login=This account is prohibited to login
users.is_admin=Tällä tilillä on ylläpito-oikeudet
users.allow_git_hook=Tällä tilillä on oikeudet luoda Git koukkuja
users.allow_import_local=Tällä tilillä on oikeudet tuoda paikallisia repoja
@@ -910,6 +926,7 @@ auths.enabled=Käytössä
auths.updated=Päivitetty
auths.auth_type=Todennustyyppi
auths.auth_name=Todennusnimi
auths.security_protocol=Security Protocol
auths.domain=Verkkotunnus
auths.host=Isäntä
auths.port=Portti

85
conf/locale/locale_fr-FR.ini Executable file → Normal file
View File

@@ -57,7 +57,7 @@ password=Mot de passe
db_name=Nom de base de données
db_helper=Veuillez utiliser le moteur INNODB avec le jeu de caractères utf8_general_ci pour MySQL.
ssl_mode=Mode SSL
path=Chemin
path=Emplacement
sqlite_helper=Le chemin du fichier de base de données SQLite3 ou TiDB. <br>Utilisez un chemin absolu lorsque vous démarrez en tant que service.
err_empty_db_path=Le chemin de la base de données SQLite3 ou TiDB ne peut être vide.
err_invalid_tidb_name=Le nom de la base de données TiDB ne peut contenir les caractères "." ou "-".
@@ -83,7 +83,7 @@ log_root_path=Chemin des fichiers log
log_root_path_helper=Répertoire d'écriture des fichiers de log.
optional_title=Paramètres facultatifs
email_title=Paramètres du Service de Messagerie
email_title=Paramètres du service de messagerie
smtp_host=Hôte SMTP
smtp_from=Provenant de
smtp_from_helper=Adresse de l'expéditeur, RFC 5322. Soit une adresse courriel simple, soit au format "Nom" <email@example.com>.
@@ -100,7 +100,7 @@ disable_registration=Désactiver le formulaire d'inscription
disable_registration_popup=Désactiver le formulaire d'inscription, seuls les administrateurs peuvent créer des comptes.
enable_captcha=Activez le Captcha
enable_captcha_popup=Demande la validation Captcha pour l'auto-enregistrement de l'utilisateur.
require_sign_in_view=Demander une connexion pour afficher des pages
require_sign_in_view=Exiger l'identification pour afficher les pages
require_sign_in_view_popup=Seules les personnes connectées peuvent voir les pages. Les visiteurs anonymes ne pourront voir que les pages de connexion/enregistrement.
admin_setting_desc=Vous n'avez pas besoin de créer un compte administrateur. L'utilisateur ayant l'ID = 1 aura automatiquement accès à l'administration.
admin_title=Paramètres du compte administrateur
@@ -120,13 +120,14 @@ install_success=Bienvenue ! Nous sommes heureux que vous ayez choisi Gogs, amuse
invalid_log_root_path=Le chemin principal des fichiers logs est invalide: %v
[home]
uname_holder=Nom d'Utilisateur ou E-mail
uname_holder=Nom d'utilisateur ou e-mail
password_holder=Mot de passe
switch_dashboard_context=Basculer le Contexte du Tableau de Bord
my_repos=Mes dépôts
show_more_repos=Afficher plus de dépôts...
collaborative_repos=Dépôts collaboratifs
my_orgs=Mes Organisations
my_mirrors=Mes Miroirs
my_orgs=Mes organisations
my_mirrors=Mes miroirs
view_home=Voir %s
issues.in_your_repos=Dans vos dépôts
@@ -143,11 +144,13 @@ social_register_hepler_msg=Déjà enregistré ? Associez-le !
disable_register_prompt=Désolé, les enregistrements ont été désactivés. Veuillez contacter l'administrateur du site.
disable_register_mail=Désolé, la Confirmation par Mail des Enregistrements a été désactivée.
remember_me=Se souvenir de moi
forgot_password=Mot de Passe oublié
forget_password=Mot de Passe oublié ?
forgot_password=Mot de passe oublié
forget_password=Mot de passe oublié ?
sign_up_now=Pas de compte ? Inscrivez-vous maintenant.
confirmation_mail_sent_prompt=Un nouveau mail de confirmation à été envoyé à <b>%s</b>. Veuillez vérifier votre boîte de réception dans un délai de %d heures pour compléter votre enregistrement.
active_your_account=Activer votre Compte
active_your_account=Activer votre compte
prohibit_login=Connexion interdite
prohibit_login_desc=Votre compte est interdit de se connecter, contactez ladministrateur du site.
resent_limit_prompt=Désolé, vos tentatives d'activation sont trop fréquentes. Veuillez réessayer dans 3 minutes.
has_unconfirmed_mail=Bonjour %s, votre adresse e-mail (<b>%s</b>) n'a pas été confirmée. Si vous n'avez reçu aucun mail de confirmation ou souhaitez renouveler l'envoi, cliquez sur le bouton ci-dessous.
resend_mail=Cliquez ici pour renvoyer un mail de confirmation
@@ -157,6 +160,7 @@ reset_password=Réinitialiser le mot de passe
invalid_code=Désolé, votre code de confirmation est invalide ou a expiré.
reset_password_helper=Cliquez ici pour réinitialiser votre mot de passe
password_too_short=Le mot de passe doit contenir 6 caractères minimum.
non_local_account=Les comptes non-locaux ne peuvent pas changer leur mot de passe via Gogs.
[mail]
activate_account=Veuillez activer votre compte
@@ -212,15 +216,14 @@ invalid_ssh_key=Désolé, impossible de valider votre clé SSH : %s
unable_verify_ssh_key=Gogs n'a pu vérifier la validité de votre clé SSH, même si nous partons du principe qu'elle le soit. Cela-dit, veuillez vous en assurer.
auth_failed=Échec d'authentification : %s
still_own_repo=Votre compte comporte toujours des propriétés du dépôt. Vous devez d'abord les supprimer ou les transférer.
still_has_org=Votre compte contient toujours au moins une adhésion à une organisation, vous devez quitter ou supprimer votre adhésion.
org_still_own_repo=Cette organisation comporte toujours des propriétés du dépôt. Vous devez d'abord les supprimer ou les transférer.
still_own_repo=Votre compte est toujours propriétaire d'un ou plusieurs dépôts. Vous devez d'abord les supprimer ou les transférer.
still_has_org=Votre compte fait toujours partie d'une ou plusieurs organisations. Vous devez d'abord les quitter ou les supprimer.
org_still_own_repo=Cette organisation est toujours propriétaire d'un ou plusieurs dépôts. Vous devez d'abord les supprimer ou les transférer.
target_branch_not_exist=La branche cible n'existe pas.
[user]
change_avatar=Changez d'avatar via gravatar.com
change_custom_avatar=Changez votre avatar dans les paramètres
change_avatar=Changer votre avatar
join_on=Inscrit le
repositories=Dépôts
activity=Activité publique
@@ -240,7 +243,7 @@ ssh_keys=Clés SSH
social=Réseaux Sociaux
applications=Applications
orgs=Organisations
delete=Supprimer le Compte
delete=Supprimer le compte
uid=ID d'Utilisateur
public_profile=Profil public
@@ -256,7 +259,7 @@ change_username_prompt=Cette modification affectera la manière dont les liens s
continue=Continuer
cancel=Annuler
enable_custom_avatar=Activer l'Avatar personnalisé
enable_custom_avatar=Activer l'avatar personnalisé
choose_new_avatar=Sélectionner un nouvel avatar
update_avatar=Mettre à jour l'avatar
delete_current_avatar=Supprimer l'avatar actuel
@@ -265,21 +268,21 @@ update_avatar_success=Votre avatar a été mis à jour avec succès.
change_password=Modifier le mot de passe
old_password=Mot de passe actuel
new_password=Nouveau Mot de Passe
new_password=Nouveau mot de passe
retype_new_password=Retapez le nouveau mot de passe
password_incorrect=Mot de passe actuel incorrect.
change_password_success=Mot de passe modifié avec succès. Vous pouvez à présent vous connecter avec le nouveau mot de passe.
password_change_disabled=Les utilisateurs non-locaux n'ont pas le droit de modifier leur mot de passe.
emails=Adresses E-mail
emails=Adresses e-mail
manage_emails=Gérer les adresses e-mail
email_desc=Votre adresse e-mail principale sera utilisée pour les notifications et d'autres opérations.
primary=Principale
primary_email=Définir comme principale
delete_email=Supprimer
email_deletion=Suppression de l'adresse l
email_deletion=Suppression de l'adresse e-mail
email_deletion_desc=Supprimer cette adresse e-mail supprimera les informations associées à votre compte. Voulez-vous continuer ?
email_deletion_success=L'adresse l a été supprimée avec succès !
email_deletion_success=L'adresse e-mail a été supprimée avec succès !
add_new_email=Ajouter une nouvelle adresse e-mail
add_email=Ajouter un e-mail
add_email_confirmation_sent=Une nouvelle confirmation d'adresse e-mail a été envoyé à '%s', veuillez vérifier votre boîte de réception dans un délai de %d heures pour terminer le processus de confirmation.
@@ -330,7 +333,7 @@ delete_account_desc=Ce compte sera supprimé définitivement. Voulez-vous contin
[repo]
owner=Propriétaire
repo_name=Nom du Dépôt
repo_name=Nom du dépôt
repo_name_helper=Idéalement, le nom d'un dépot devrait être court, mémorable et <strong>unique</strong>.
visibility=Visibilité
visiblity_helper=Ce dépôt est <span class="ui red text"> privé</span>
@@ -350,6 +353,8 @@ readme_helper=Sélectionnez un modèle de readme
auto_init=Initialiser ce dépôt avec le modèle et les fichiers sélectionnés
create_repo=Créer un dépôt
default_branch=Branche par défaut
mirror_prune=Purger
mirror_prune_desc=Supprimez toute référence de suivi à distance qui n'existe plus sur le dépôt distant
mirror_interval=Intervalle du miroir (heure)
mirror_address=Adresse du miroir
mirror_address_desc=Veuillez inclure les informations d'identification nécessaires dans l'adresse.
@@ -372,7 +377,7 @@ migrate.invalid_local_path=Chemin local non valide, non existant ou n'étant pas
migrate.failed=Echec de migration: %v
mirror_from=miroir de
forked_from=scindé depuis
forked_from=forké depuis
fork_from_self=Vous ne pouvez pas forker un dépôt que vous possédez déja !
copy_link=Copier
copy_link_success=Copié!
@@ -400,13 +405,14 @@ tags=Tags
issues=Tickets
pulls=Pull Requests
labels=Etiquettes
milestones=Étapes
milestones=Jalons
commits=Commits
releases=Publications
file_raw=Raw
file_history=Historique
file_view_raw=Voir le Raw
file_permalink=Lien permanent
file_too_large=Ce fichier est trop gros pour être afficher
commits.commits=Commits
commits.search=Rechercher des commits
@@ -425,7 +431,7 @@ issues.new.milestone=Étape
issues.new.no_milestone=Pas d'étape
issues.new.clear_milestone=Effacer l'étape
issues.new.open_milestone=Ouvrir l'étape
issues.new.closed_milestone=Étapes fermées
issues.new.closed_milestone=Jalons fermés
issues.new.assignee=Affecté à
issues.new.clear_assignee=Supprimer les assignataires
issues.new.no_assignee=Pas d'assignataire
@@ -437,7 +443,7 @@ issues.open_tab=%d Ouvert
issues.close_tab=%d Fermé
issues.filter_label=Étiquette
issues.filter_label_no_select=Aucun étiquette sélectionnée
issues.filter_milestone=Étape
issues.filter_milestone=Jalon
issues.filter_milestone_no_select=Aucun jalon sélectionné
issues.filter_assignee=Assigné
issues.filter_assginee_no_select=Pas d'assignataire selectionné
@@ -460,7 +466,8 @@ issues.next=Page Suivante
issues.open_title=Ouvert
issues.closed_title=Fermé
issues.num_comments=%d commentaires
issues.commented_at=`commenté à <a id="%[1]s" href="#%[1]s"> %[2]s</a>`
issues.commented_at=`a commenté <a href="#%s"> %s</a>`
issues.delete_comment_confirm=Êtes-vous certain de vouloir supprimer ce commentaire?
issues.no_content=Il n'existe pas encore de contenu.
issues.close_issue=Fermer
issues.close_comment_issue=Commenter et fermer
@@ -561,6 +568,10 @@ wiki.last_updated=Dernière mise à jour: %s
settings=Paramètres
settings.options=Options
settings.collaboration=Collaboration
settings.collaboration.admin=Administrateur
settings.collaboration.write=Écrire
settings.collaboration.read=Lire
settings.collaboration.undefined=Indéfini
settings.hooks=Webhooks
settings.githooks=Git Hooks
settings.basic_settings=Paramètres de base
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Les visiteurs seront redirigés vers cette URL l
settings.issues_desc=Activer le bug-tracker léger intégré
settings.use_external_issue_tracker=Utiliser un bug-tracker externe
settings.tracker_url_format=Format d'URL du bug tracker
settings.tracker_issue_style=Style de nommage des bugs du tracker externe :
settings.tracker_issue_style.numeric=Numérique
settings.tracker_issue_style.alphanumeric=Alphanumérique
settings.tracker_url_format_desc=Vous pouvez utiliser l'espace réservé <code>{user} {repo} {index}</code> pour le nom d'utilisateur, le nom du dépôt et le numéro de bug.
settings.pulls_desc=Activer les pull requests pour accepter les contributions publiques
settings.danger_zone=Zone de danger
@@ -586,7 +600,7 @@ settings.convert_confirm=Confirmer la conversion
settings.convert_succeed=Le dépôt a été converti avec succès en dépôt ordinaire.
settings.transfer=Changer de propriétaire
settings.transfer_desc=Transférer ce dépôt à un autre utilisateur ou une organisation dont vous possédez des droits d'administrateur.
settings.transfer_notices_1=-Vous perdrez l'accès si le nouveau propriétaire est un utilisateur individuel.
settings.transfer_notices_1=- Vous perdrez l'accès si le nouveau propriétaire est un utilisateur individuel.
settings.transfer_notices_2=- Vous conserverez l'accès si le nouveau propriétaire est une organisation et que vous y appartenez.
settings.transfer_form_title=Veuillez recopier le texte suivant afin de confirmer votre opération :
settings.wiki_delete=Effacer les données du Wiki
@@ -597,9 +611,7 @@ settings.delete=Supprimer ce dépôt
settings.delete_desc=Attention, cette action est action irréversible. Soyez sûr de vous.
settings.delete_notices_1=- Cette opération <strong>ne peut pas </strong> être annulée.
settings.delete_notices_2=- Cette opération supprimera définitivement le dépôt, y compris les données Git, les tickets, les commentaires et les accès des collaborateurs.
settings.delete_notices_fork_1=- Si ce dépôt est public, tous les forks vont devenir indépendant après sa suppression.
settings.delete_notices_fork_2=-Si ce dépôt est privé, tous les forks seront supprimés en même temps.
settings.delete_notices_fork_3=-Si vous souhaitez conserver tous les forks après suppression, veuillez tout d'abord modifier la visibilité de ce dépôt en public.
settings.delete_notices_fork_1=-Tous les forks deviendront indépendants après leffacement.
settings.deletion_success=Le dépôt a été supprimé avec succès!
settings.update_settings_success=Options mises à jour avec succès.
settings.transfer_owner=Nouveau propriétaire
@@ -683,6 +695,8 @@ diff.show_unified_view=Vue unifiée
diff.stats_desc=<strong> %d fichiers modifiés</strong> avec <strong>%d ajouts</strong> et <strong>%d suppressions</strong>
diff.bin=BIN
diff.view_file=Voir le fichier
diff.file_suppressed=Fichier diff supprimé car celui-ci est trop grand
diff.too_many_files=Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff
release.releases=Versions
release.new_release=Nouvelle version
@@ -708,15 +722,16 @@ release.cancel=Annuler
release.publish=Publier
release.save_draft=Sauvegarder le Brouillon
release.edit_release=Modifier la version
release.delete_release=Supprimer Cette Version
release.delete_release=Supprimer cette version
release.deletion=Suppression de la Version
release.deletion_desc=Supprimer cette version supprimera le tag Git correspondant. Voulez-vous continuer ?
release.deletion_success=La version à été supprimée avec succès !
release.tag_name_already_exist=Une version avec ce nom de tag existe déjà.
release.tag_name_invalid=Nom de tag invalide.
release.downloads=Téléchargements
[org]
org_name_holder=Nom d'organisation
org_name_holder=Nom de l'organisation
org_full_name_holder=Nom complet de l'organisation
org_name_helper=Idéalement, un nom d'organisation devrait être court et facilement mémorisable.
create_org=Créer une organisation
@@ -726,7 +741,7 @@ invite_someone=Inviter quelqu'un
teams=Équipes
lower_members=Membres
lower_repositories=dépôts
create_new_team=Créer une Nouvelle Équipe
create_new_team=Créer une nouvelle équipe
org_desc=Description
team_name=Nom d'Équipe
team_desc=Description
@@ -831,7 +846,7 @@ dashboard.resync_all_update_hooks_success=Tous les hooks de mises à jour des d
dashboard.reinit_missing_repos=Réinitialiser tous les dépôts qui ont perdu des fichiers Git
dashboard.reinit_missing_repos_success=Tous les enregistrements de dépôts qui ont perdu des fichiers Git ont été réinitialisés avec succès.
dashboard.server_uptime=Durée de Marche Serveur
dashboard.server_uptime=Uptime du serveur
dashboard.current_goroutine=Goroutines actuelles
dashboard.current_memory_usage=Utilisation Mémoire actuelle
dashboard.total_memory_allocated=Mémoire totale allouée
@@ -880,6 +895,7 @@ users.edit_account=Modifier le Compte
users.max_repo_creation=Nombre maximum de dépôts créés
users.max_repo_creation_desc=(Mettre à -1 pour utiliser la limite globale par défaut)
users.is_activated=Ce compte est activé
users.prohibit_login=Ce compte est interdit de se connecter
users.is_admin=Ce compte possède un niveau d'accès administrateur
users.allow_git_hook=Ce compte dispose des autorisations pour créer des crochets de Git
users.allow_import_local=Ce compte dispose des permissions nécessaire à l'import des dépôts locaux
@@ -910,6 +926,7 @@ auths.enabled=Activé
auths.updated=Mis à jour
auths.auth_type=Type d'authentification
auths.auth_name=Nom de l'authentification
auths.security_protocol=Protocole de sécurité
auths.domain=Domaine
auths.host=Hôte
auths.port=Port

53
conf/locale/locale_it-IT.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Nome Utente o E-mail
password_holder=Password
switch_dashboard_context=Cambia Dashboard Context
my_repos=I miei Repository
show_more_repos=Show more repositories...
collaborative_repos=Repository Condivisi
my_orgs=Le mie Organizzazioni
my_mirrors=I miei Mirror
@@ -148,6 +149,8 @@ forget_password=Password dimenticata?
sign_up_now=Bisogno di un account? Iscriviti ora.
confirmation_mail_sent_prompt=Una nuova email di conferma è stata inviata a <b>%s</b>, verifica la tua casella di posta entro le prossime %d ore per completare la registrazione.
active_your_account=Attiva il tuo Account
prohibit_login=Login Prohibited
prohibit_login_desc=Your account is prohibited to login, please contact site admin.
resent_limit_prompt=Siamo spiacenti, si stanno inviando e-mail di attivazione troppo spesso. Si prega di attendere 3 minuti.
has_unconfirmed_mail=Ciao %s, hai un indirizzo di posta elettronica non confermato (<b>%s</b>). Se non hai ricevuto una e-mail di conferma o vuoi riceverla nuovamente, fare clic sul pulsante qui sotto.
resend_mail=Clicca qui per inviare nuovamente l'e-mail di attivazione
@@ -157,6 +160,7 @@ reset_password=Reimposta la tua Password
invalid_code=Siamo spiacenti, il codice di conferma è scaduto o non valido.
reset_password_helper=Clicca qui per reimpostare la password
password_too_short=La lunghezza della password non può essere meno 6 caratteri.
non_local_account=Non-local accounts cannot change passwords through Gogs.
[mail]
activate_account=Per favore attiva il tuo account
@@ -219,8 +223,7 @@ org_still_own_repo=Questa organizzazione ha ancora la proprietà del repository,
target_branch_not_exist=Il ramo (branch) di destinazione non esiste.
[user]
change_avatar=Cambia il tuo avatar su gravatar.com
change_custom_avatar=Cambia il tuo avatar nelle impostazioni
change_avatar=Change your avatar
join_on=Si è unito il
repositories=Repository
activity=Attività pubblica
@@ -350,6 +353,8 @@ readme_helper=Seleziona un template per il readme
auto_init=Inizializzare questo repository con i file e il modello selezionati
create_repo=Crea Repository
default_branch=Ramo (Branch) predefinito
mirror_prune=Prune
mirror_prune_desc=Remove any remote-tracking references that no longer exist on the remote
mirror_interval=Intervallo Mirror (in ore)
mirror_address=Indirizzo del mirror
mirror_address_desc=Si prega di includere nell'indirizzo le credenziali utente necessarie.
@@ -407,6 +412,7 @@ file_raw=Originale
file_history=Cronologia
file_view_raw=Vedi originale
file_permalink=Permalink
file_too_large=This file is too large to be shown
commits.commits=Commits
commits.search=Ricerca una versione
@@ -460,15 +466,16 @@ issues.next=Pagina successiva
issues.open_title=Aperto
issues.closed_title=Chiuso
issues.num_comments=%d commenti
issues.commented_at=`commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`commented <a href="#%s">%s</a>`
issues.delete_comment_confirm=Are you sure you want to delete this comment?
issues.no_content=Non ci sono ancora contenuti.
issues.close_issue=Chiudi
issues.close_comment_issue=Commenta e chiudi
issues.reopen_issue=Riapri
issues.reopen_comment_issue=Commenta e riapri
issues.create_comment=Commento
issues.closed_at=`closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.closed_at=`chiuso <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`riaperto <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.poster=Autore
issues.collaborator=Collaboratori
@@ -500,8 +507,8 @@ pulls.no_results=Nessun risultato trovato.
pulls.nothing_to_compare=Non c'è niente da confrontare perchè i branch base e head uguali.
pulls.has_pull_request=`E' già presente una pull request tra questi due trargets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
pulls.create=Crea Pull Request
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
pulls.title_desc=vorrebbe unire %[1]d commit da <code>%[2]s</code> a <code>%[3]s</code>
pulls.merged_title_desc=ha unito %[1]d commit da <code>%[2]s</code> a <code>%[3]s</code> %[4]s
pulls.tab_conversation=Conversazione
pulls.tab_commits=Commit
pulls.tab_files=File modificati
@@ -561,6 +568,10 @@ wiki.last_updated=Ultimo aggiornamento: %s
settings=Impostazioni
settings.options=Opzioni
settings.collaboration=Collaborazione
settings.collaboration.admin=Admin
settings.collaboration.write=Write
settings.collaboration.read=Read
settings.collaboration.undefined=Undefined
settings.hooks=Webhooks
settings.githooks=Git Hooks
settings.basic_settings=Impostazioni di Base
@@ -574,7 +585,10 @@ settings.external_wiki_url=URL Wiki esterno
settings.external_wiki_url_desc=I visitatori verranno reindirizzati all'URL quando cliccano sulla scheda.
settings.issues_desc=Abilita l'issue tracker builtin leggero
settings.use_external_issue_tracker=Utilizza gestore di problemi esterno
settings.tracker_url_format=External Issue Tracker URL Format
settings.tracker_url_format=Formato URL Gestore Problemi Esterno
settings.tracker_issue_style=External Issue Tracker Naming Style:
settings.tracker_issue_style.numeric=Numeric
settings.tracker_issue_style.alphanumeric=Alphanumeric
settings.tracker_url_format_desc=You can use placeholder <code>{user} {repo} {index}</code> for user name, repository name and issue index.
settings.pulls_desc=Abilita le pull requests per accettare contributi pubblici
settings.danger_zone=Zona Pericolosa
@@ -583,7 +597,7 @@ settings.convert=Converti in Repository Regolare
settings.convert_desc=Puoi convertire questo mirror in un repository regolare. Questa operazione non può essere annullata.
settings.convert_notices_1=- Questa operazione non potrà essere annullata e convertirà questo mirror in un repository regolare.
settings.convert_confirm=Conferma la conversione
settings.convert_succeed=Repository has been converted to regular type successfully.
settings.convert_succeed=Il repository è stato convertito con successo al formato normale.
settings.transfer=Trasferisci proprietà
settings.transfer_desc=Trasferisci questa repository a un altro utente o a un'organizzazione nella quale hai diritti d'amministratore.
settings.transfer_notices_1=- You will lose access if new owner is a individual user.
@@ -591,15 +605,13 @@ settings.transfer_notices_2=- You will conserve access if new owner is an organi
settings.transfer_form_title=Per favore inserisci le informazioni seguenti per confermare l'operazione:
settings.wiki_delete=Elimina i dati della Wiki
settings.wiki_delete_desc=Once you erase wiki data there is no going back. Please be certain.
settings.wiki_delete_notices_1=- This will delete and disable the wiki for %s
settings.wiki_delete_notices_1=Questo eliminerà e disabiliterà la wiki per %s
settings.wiki_deletion_success=I dati della wiki del repository sono stati eliminati con successo.
settings.delete=Elimina questo repository
settings.delete_desc=Una volta che hai cancellato il repository, non puoi tornare indietro. Si prega di fare attenzione.
settings.delete_notices_1=-Questa operazione <strong>NON PUÒ</strong> essere annullata.
settings.delete_notices_2=-Questa operazione eliminerà definitivamente il tutto il contenuto del repository, inclusi i dati di Git, incidenti, commenti e accessi dei collaboratori.
settings.delete_notices_fork_1=-Se questo repository è pubblico, tutti i fork diventeranno indipendenti dopo la sua cancellazione.
settings.delete_notices_fork_2=-Se questo repository è privato, tutti fork verranno rimossi assieme ad esso.
settings.delete_notices_fork_3=- Se vuoi mantenere i forks dopo la cancellazione, per piacere cambia la visibilità di questo repository in pubblica.
settings.delete_notices_fork_1=- All forks will become independent after deletion.
settings.deletion_success=Il repository è stato eliminato con successo!
settings.update_settings_success=Le opzioni repository sono state aggiornate con successo.
settings.transfer_owner=Nuovo Proprietario
@@ -619,7 +631,7 @@ settings.add_webhook=Aggiungi Webhook
settings.hooks_desc=I Webhooks sono molto simili a un basilare evento trigger HTTP POST. Ogni volta che qualcosa si verifica in Gogs, tratteremo la notifica all'host di destinazione specificato. Ulteriori informazioni in questa <a target="_blank" href="%s">Guida ai Webhooks</a>.
settings.webhook_deletion=Elimina Webhook
settings.webhook_deletion_desc=Delete this webhook will remove its information and all delivery history. Do you want to continue?
settings.webhook_deletion_success=Webhook has been deleted successfully!
settings.webhook_deletion_success=Il Webhook è stato eliminato con successo!
settings.webhook.test_delivery=Test di consegna
settings.webhook.test_delivery_desc=Send a fake push event delivery to test your webhook settings
settings.webhook.test_delivery_success=Test webhook has been added to delivery queue. It may take few seconds before it shows up in the delivery history.
@@ -683,6 +695,8 @@ diff.show_unified_view=Visualizzazione unificata
diff.stats_desc=<strong>%d ha cambiato i file</strong> con <strong>%d aggiunte</strong> e <strong>%d eliminazioni</strong>
diff.bin=BIN
diff.view_file=Vedi File
diff.file_suppressed=File diff suppressed because it is too large
diff.too_many_files=Some files were not shown because too many files changed in this diff
release.releases=Rilasci
release.new_release=Nuovo Rilascio
@@ -713,6 +727,7 @@ release.deletion=Eliminazione Release
release.deletion_desc=Eliminando questa release cancellarai anche i tag Git corrispondenti. Vuoi continuare?
release.deletion_success=La release è stata eliminata con successo!
release.tag_name_already_exist=Un rilascio con questo tag esiste già.
release.tag_name_invalid=Tag name is not valid.
release.downloads=Download
[org]
@@ -880,6 +895,7 @@ users.edit_account=Modifica Account
users.max_repo_creation=Limite massimo per la creazione di Repository
users.max_repo_creation_desc=(Inserire -1 per usare il limite globale di default)
users.is_activated=Questo account è attivato
users.prohibit_login=This account is prohibited to login
users.is_admin=Questo account ha permessi di amministratore
users.allow_git_hook=Questo account ha il permesso di creare hooks di Git
users.allow_import_local=Questo account dispone delle autorizzazioni per importare repository locali
@@ -910,6 +926,7 @@ auths.enabled=Attivo
auths.updated=Aggiornato
auths.auth_type=Tipo di autenticazione
auths.auth_name=Nome di autenticazione
auths.security_protocol=Security Protocol
auths.domain=Dominio
auths.host=Host
auths.port=Porta
@@ -1067,7 +1084,7 @@ comment_issue=`ha commentato il problema <a href="%s/issues/%s">%s#%[2]s</a>`
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
transfer_repo=ha trasferito il repository <code>%s</code> a <a href="%s">%s</a>
push_tag=ha pushato il tag <a href="%s/src/%s">%[2]s</a> a <a href="%[1]s">%[3]s</a>
compare_commits=View comparison for these %d commits
compare_commits=Visualizza comparazione tra questi %d commit
[tool]
ago=fa
@@ -1091,8 +1108,8 @@ raw_seconds=secondi
raw_minutes=minuti
[dropzone]
default_message=Drop files here or click to upload.
invalid_input_type=You can't upload files of this type.
file_too_big=File size ({{filesize}} MB) exceeds maximum size ({{maxFilesize}} MB).
default_message=Trascina i file qui o clicca per caricare.
invalid_input_type=Non è possibile caricare file di questo tipo.
file_too_big=La dimensione del file ({{filesize}} MB) supera la dimensione massima ({{maxFilesize}} MB).
remove_file=Rimuovi file

117
conf/locale/locale_ja-JP.ini Executable file → Normal file
View File

@@ -2,7 +2,7 @@ app_desc=Go言語で実装したセルフホストGitサービス
home=ホーム
dashboard=ダッシュボード
explore=クプローラ
explore=エクプローラ
help=ヘルプ
sign_in=サインイン
sign_out=サインアウト
@@ -21,7 +21,7 @@ username=ユーザ名
email=E-mail
password=パスワード
re_type=再入力
captcha=キャプチャ
captcha=CAPTCHA
repository=リポジトリ
organization=組織
@@ -35,8 +35,8 @@ manage_org=組織を管理
admin_panel=管理者パネル
account_settings=アカウント設定
settings=設定
your_profile=あなたのプロファイ
your_settings=あなたの設定
your_profile=プロフィー
your_settings=設定
activities=アクティビティ
pull_requests=プルリクエスト
@@ -72,9 +72,9 @@ repo_path_helper=すべての Git リモート リポジトリはこのディレ
run_user=実行ユーザ
run_user_helper=ユーザーはリポジトリ ルートパスへのアクセス、及びGogs を実行する権限を所有する必要があります。
domain=ドメイン
domain_helper=これはSSHクローンURLに影響す
domain_helper=これはSSHクローンURLに影響します。
ssh_port=SSH ポート
ssh_port_helper=あならのSSHサーバおポート番号、SSH機能を無効するにはここを空白のままにしてください
ssh_port_helper=SSHサーバーを使用する場合はポート番号を入力してください。 空白にした場合は無効化されます
http_port=HTTP ポート
http_port_helper=アプリケーションが待ち受けするポート番号。
app_url=アプリケーションの URL
@@ -83,7 +83,7 @@ log_root_path=ログのパス
log_root_path_helper=ログファイルを書き込むディレクトリ。
optional_title=オプション設定
email_title=E-mailサービス設定
email_title=メールサービス設定
smtp_host=SMTP ホスト
smtp_from=差出人
smtp_from_helper=送信者メールアドレス、RFC 5322。フォーマットはメールアドレスのみ、または"Name" <email@example.com>。
@@ -123,31 +123,34 @@ invalid_log_root_path=ログのルートパスがむこうです: %v
uname_holder=ユーザー名またはEメール
password_holder=パスワード
switch_dashboard_context=ダッシュ ボードのコンテキストを切替
my_repos=のリポジトリ
my_repos=自分のリポジトリ
show_more_repos=リポジトリをさらに表示…
collaborative_repos=共同リポジトリ
my_orgs=の組織
my_mirrors=のミラー
my_orgs=自分の組織
my_mirrors=自分のミラー
view_home=ビュー %s
issues.in_your_repos=あなたのリポジトリ
[explore]
repos=リポジトリ
users=Users
search=Search
users=ユーザ
search=検索
[auth]
create_new_account=新規アカウントを作成
register_hepler_msg=すでにアカウントをお持ちですか?今すぐログイン
social_register_hepler_msg=すでにアカウントをお持ちですか?今すぐバインド
disable_register_prompt=申し訳ありませんが、登録が無効になっています。サイト管理者に問い合わせください。
register_hepler_msg=にアカウントをお持ちですか?今すぐログインしましょう
social_register_hepler_msg=にアカウントをお持ちですか?連携しましょう
disable_register_prompt=申し訳ありませんが、現在登録は受け付けておりません。サイト管理者に問い合わせください。
disable_register_mail=申し訳ありませんが、登録メールの確認機能が無効になっています。
remember_me=ログイン状態を保持する
remember_me=ログインしたままにする
forgot_password=パスワードを忘れた
forget_password=パスワードを忘れ
sign_up_now=アカウントが必要ですか?今すぐサインアップ
forget_password=パスワードを忘れましたか
sign_up_now=アカウントが必要ですか?今すぐ登録しましょう!
confirmation_mail_sent_prompt=新しい確認メールを <b>%s</b> に送りました。登録を完了させるために、%d時間以内にあなたのメールボックスを確認してください。
active_your_account=アカウントをアクティブ
active_your_account=アカウントを有効化
prohibit_login=Login Prohibited
prohibit_login_desc=Your account is prohibited to login, please contact site admin.
resent_limit_prompt=申し訳ありませんが、アクティベーションメールは頻繁に送信しています。3 分お待ちください。
has_unconfirmed_mail=こんにちは %s さん、あなたの電子メール アドレス (<b>%s</b>) は未確認です。もし確認メールをまだ確認できていないか、改めて再送信する場合は、下のボタンをクリックしてください。
resend_mail=アクティベーションメールを再送信するにはここをクリック
@@ -157,6 +160,7 @@ reset_password=パスワードリセット
invalid_code=申し訳ありませんが、確認用コードが期限切れまたは無効です。
reset_password_helper=パスワードをリセットするにはここをクリック
password_too_short=6文字未満のパスワードは設定できません。
non_local_account=Non-local accounts cannot change passwords through Gogs.
[mail]
activate_account=あなたのアカウントを有効にしてください。
@@ -219,8 +223,7 @@ org_still_own_repo=この組織はまだリポジトリの所有しています
target_branch_not_exist=ターゲットブランチが存在しない
[user]
change_avatar=gravatar.com で自分のアバターを変更
change_custom_avatar=設定で自分のアバターを変更
change_avatar=アバターを変更
join_on=参加しました
repositories=リポジトリ
activity=パブリック・アクティビティ
@@ -310,7 +313,7 @@ social_desc=これは関連付けられたソーシャルアカウントのリ
unbind=バインド解除
unbind_success=SNSアカウントがバインドされていない。
manage_access_token=個人のアクセス トークンを管理
manage_access_token=パーソナルアクセス トークンを管理
generate_new_token=新しいトークンを生成
tokens_desc=生成したトークンを利用して Gogs の API にアクセスすることができます。
new_token_desc=今のところ、全てのトークンはあなたのアカウントにフルアクセスできます。
@@ -323,7 +326,7 @@ access_token_deletion_desc=パーソナルアクセストークンを削除す
delete_token_success=パーソナルアクセストークンは正常に削除されました!同時にあなたのアプリケーションを更新することを忘れないでください。
delete_account=アカウントを削除
delete_prompt=この操作はあなたのアカウント完全に削除し、復旧<strong>できない</strong>
delete_prompt=この操作をするとアカウント完全に削除され、<strong>二度と元に戻すことができなくなります</strong>
confirm_delete_account=削除の確認
delete_account_title=アカウントの削除
delete_account_desc=このアカウントは永久に削除しようとしている、継続しますか?
@@ -331,15 +334,15 @@ delete_account_desc=このアカウントは永久に削除しようとしてい
[repo]
owner=オーナー
repo_name=リポジトリ名
repo_name_helper=偉大なリポジトリ名は短い。思い出に残り、そして<strong>一意</strong>
visibility=ビジビリティ
repo_name_helper=短くて分かりやすく<strong>重複しない</strong>リポジトリ名を決めてください
visibility=公開/非公開
visiblity_helper=このリポジトリは<span class="ui red text">プライベート</span>です。
visiblity_helper_forced=サイト管理者は、強制的にすべての新しいリポジトリを<span class="ui red text"> プライベート</span> にしています。
visiblity_fork_helper=(この値の変更はすべてのフォークに適用されます)
clone_helper=クローニングのヘルプが必要ですか?<a target="_blank"href="%s"> ヘルプ</a> を参照してください!
fork_repo=フォークのリポジトリ
clone_helper=クローンに関してお困りであれば<a target="_blank"href="%s"> ヘルプ</a> を参照しましょう。
fork_repo=リポジトリをフォーク
fork_from=フォーク元
fork_visiblity_helper=フォークされたリポジトリ可視状態変更できません
fork_visiblity_helper=フォークされたリポジトリ可視状態変更できません
repo_desc=説明
repo_lang=言語
repo_lang_helper=.gitignoreファイルを選択
@@ -350,6 +353,8 @@ readme_helper=Readme ファイルのテンプレートを選択
auto_init=選択されたファイルおよびテンプレートでリポジトリを初期化
create_repo=リポジトリを作成
default_branch=デフォルトのブランチ
mirror_prune=Prune
mirror_prune_desc=Remove any remote-tracking references that no longer exist on the remote
mirror_interval=ミラー 間隔(時)
mirror_address=ミラー アドレス
mirror_address_desc=Please include necessary user credentials in the address.
@@ -358,8 +363,8 @@ stargazers=Stargazers
forks=フォーク
form.reach_limit_of_creation=The owner has reached maximum creation limit of %d repositories.
form.name_reserved=リポジトリ名 '%s' は予約されています。
form.name_pattern_not_allowed=リポジトリ名のパターン '%s' は許可されていません。
form.name_reserved=リポジトリ名 '%s' は使用されています。
form.name_pattern_not_allowed=リポジトリ名 '%s' は使用できません。
need_auth=認証が必要
migrate_type=マイグレーションの種類
@@ -371,7 +376,7 @@ migrate.permission_denied=ローカル リポジトリをインポートする
migrate.invalid_local_path=ローカルパスが無効です。存在しないかディレクトリではありません。
migrate.failed=移行に失敗しました: %v
mirror_from=mirror of
mirror_from=同期ミラー
forked_from=フォーク元
fork_from_self=すでにあなたの所有しているリポジトリはフォークできません
copy_link=コピー
@@ -403,10 +408,11 @@ labels=ラベル
milestones=マイルストーン
commits=コミット
releases=リリース
file_raw=生データ
file_raw=Raw
file_history=履歴
file_view_raw=データを見る
file_view_raw=Rawデータを見る
file_permalink=パーマリンク
file_too_large=This file is too large to be shown
commits.commits=コミット
commits.search=コミットの検索
@@ -443,8 +449,8 @@ issues.filter_assignee=アサインされた人
issues.filter_assginee_no_select=選択可能な担当者がいない
issues.filter_type=タイプ
issues.filter_type.all_issues=すべての問題
issues.filter_type.assigned_to_you=あなたに割り当てられました。
issues.filter_type.created_by_you=あなたが作成しました。
issues.filter_type.assigned_to_you=担当中のリポジトリ
issues.filter_type.created_by_you=作成したリポジトリ
issues.filter_type.mentioning_you=あなたに伝える
issues.filter_sort=並べ替え
issues.filter_sort.latest=最新
@@ -460,10 +466,11 @@ issues.next=次ページ
issues.open_title=オープン
issues.closed_title=クローズ
issues.num_comments=%d コメント
issues.commented_at=`コメント <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`commented <a href="#%s">%s</a>`
issues.delete_comment_confirm=Are you sure you want to delete this comment?
issues.no_content=まだコンテンツがありません
issues.close_issue=閉じる
issues.close_comment_issue=コメントと閉じる
issues.close_comment_issue=コメントしてクローズ
issues.reopen_issue=Reopen
issues.reopen_comment_issue=コメントと再開
issues.create_comment=コメント 
@@ -471,7 +478,7 @@ issues.closed_at=`closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.poster=ポスター
issues.collaborator=Collaborator
issues.collaborator=コラボレータ
issues.owner=オーナー
issues.sign_up_for_free=無料でサインアップ
issues.sign_in_require_desc=to join this conversation. Already have an account? <a href="%s">Sign in to comment</a>
@@ -561,6 +568,10 @@ wiki.last_updated=最終更新 %s
settings=設定
settings.options=オプション
settings.collaboration=コラボレーション
settings.collaboration.admin=Admin
settings.collaboration.write=Write
settings.collaboration.read=Read
settings.collaboration.undefined=Undefined
settings.hooks=Webhooks
settings.githooks=Git のフック
settings.basic_settings=基本設定
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Visitors will be redirected to URL when they cli
settings.issues_desc=組み込み簡易課題トラッカーを有効
settings.use_external_issue_tracker=外部課題トラッキングシステムを使用
settings.tracker_url_format=外部課題トラッキングツール URLのフォーマット
settings.tracker_issue_style=External Issue Tracker Naming Style:
settings.tracker_issue_style.numeric=Numeric
settings.tracker_issue_style.alphanumeric=Alphanumeric
settings.tracker_url_format_desc=You can use placeholder <code>{user} {repo} {index}</code> for user name, repository name and issue index.
settings.pulls_desc=Enable pull requests to accept public contributions
settings.danger_zone=危険地帯
@@ -597,15 +611,13 @@ settings.delete=このリポジトリを削除
settings.delete_desc=リポジトリを削除すると元に戻せません。確実に確認してください。
settings.delete_notices_1=-この操作は<strong>元に戻せません</strong> 。
settings.delete_notices_2=- This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
settings.delete_notices_fork_1=- If this repository is public, all forks will become independent after deletion.
settings.delete_notices_fork_2=もしプライペートリポジトリの場合、全てのフォークも同時に削除されます。
settings.delete_notices_fork_3=- If you want to keep all forks after deletion, please change visibility of this repository to public first.
settings.delete_notices_fork_1=- All forks will become independent after deletion.
settings.deletion_success=Repository has been deleted successfully!
settings.update_settings_success=リポジトリ オプションが更新されました。
settings.transfer_owner=新しいオーナー
settings.make_transfer=転送
settings.transfer_succeed=リポジトリの所有権は正常に転送されました。
settings.confirm_delete=削除確認
settings.confirm_delete=削除確認
settings.add_collaborator=新しい共同編集者を追加
settings.add_collaborator_success=新しい共同編集者が追加されました。
settings.delete_collaborator=Delete
@@ -683,6 +695,8 @@ diff.show_unified_view=Unified View
diff.stats_desc=共有<strong>%d 個のファイルを変更した</strong>、<strong>%d 個の追加</strong> と <strong>%d 個の削除</strong>を含む
diff.bin=BIN
diff.view_file=ファイルの表示
diff.file_suppressed=File diff suppressed because it is too large
diff.too_many_files=Some files were not shown because too many files changed in this diff
release.releases=リリース
release.new_release=新しいリリース
@@ -713,6 +727,7 @@ release.deletion=リリースの削除
release.deletion_desc=このリリースを削除すると、対応するGitのタグも削除されます。よろしいですか
release.deletion_success=リリースが正常に削除されました。
release.tag_name_already_exist=このタグ名には既にリリースが存在します。
release.tag_name_invalid=Tag name is not valid.
release.downloads=Downloads
[org]
@@ -720,7 +735,7 @@ org_name_holder=組織名
org_full_name_holder=組織のフルネーム
org_name_helper=偉大な組織の名は短く覚えやすいです。
create_org=組織を作成
repo_updated=更新した
repo_updated=最終更新
people=人々
invite_someone=誰かを招待
teams=チーム
@@ -748,8 +763,8 @@ settings.change_orgname_prompt=This change will affect how links relate to the o
settings.update_avatar_success=組織のアバター画像が正常に更新されました。
settings.delete=組織を削除
settings.delete_account=この組織を削除
settings.delete_prompt=操作はこの組織完全に削除し、復旧<strong>できない</strong>
settings.confirm_delete_account=削除確認
settings.delete_prompt=この操作をすると組織完全に削除され、<strong>二度と元に戻すことができなくなります</strong>
settings.confirm_delete_account=削除確認
settings.delete_org_title=組織の削除
settings.delete_org_desc=この組織は完全に削除されます、継続しますか?
settings.hooks_desc=この組織のもとで <strong>すべてのリポジトリ</strong> に対してトリガーされる webhook を追加します。
@@ -880,6 +895,7 @@ users.edit_account=アカウントの編集
users.max_repo_creation=Maximum Repository Creation Limit
users.max_repo_creation_desc=(Set -1 to use global default limit)
users.is_activated=アカウントがアクティブされました
users.prohibit_login=This account is prohibited to login
users.is_admin=このアカウントには管理者の権限を持つ
users.allow_git_hook=このアカウントには Git のフックを作成する権限を持つ
users.allow_import_local=This account has permissions to import local repositories
@@ -907,9 +923,10 @@ auths.new=新しいソースを追加
auths.name=名前
auths.type=タイプ
auths.enabled=Enabled
auths.updated=Updated
auths.updated=更新しました
auths.auth_type=認証タイプ
auths.auth_name=認証名
auths.security_protocol=Security Protocol
auths.domain=ドメイン
auths.host=ホスト
auths.port=ポート
@@ -1016,7 +1033,7 @@ config.cache_conn=キャッシュ接続
config.session_config=セッションの構成
config.session_provider=セッション プロバイダー
config.provider_config=プロバイダーの構成
config.cookie_name=クッキー
config.cookie_name=クッキーの名前
config.enable_set_cookie=クッキーの設定を有効にする
config.gc_interval_time=GC 間隔
config.session_life_time=セッションのライフタイム
@@ -1054,8 +1071,8 @@ notices.op=Op。
notices.delete_success=システム通知が正常に削除されました。
[action]
create_repo=リポジトリ <a href="%s"> %s</a>を作成しました
rename_repo=<code>%[1]s</code> から <a href="%[2]s">[3]s</a> にリポジトリ名を変更した
create_repo=リポジトリ <a href="%s"> %s</a> を作成しました
rename_repo=<code>%[1]s</code> から <a href="%[2]s">[3]s</a> にリポジトリ名を変更しまし
commit_repo=<a href="%[1]s">%[4]s</a>を<a href="%[1]s/src/%[2]s">%[3]s</a>にプッシュしました
create_issue=`問題 <a href="%s/issues/%s">%s#%[2]s</a> を開きました`
close_issue=`closed issue <a href="%s/issues/%s">%s#%[2]s</a>`
@@ -1081,7 +1098,7 @@ now=今
1mon=1 ヶ月 %s
1y=1 年間 %s
seconds=%d 秒 %s
minutes=%d 分の %s
minutes=%d%s
hours=%d 時間 %s
days=%d 日 %s
weeks=%d 週間 %s

29
conf/locale/locale_lv-LV.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Lietotājvārds vai e-pasts
password_holder=Parole
switch_dashboard_context=Mainīt infopaneļa kontekstu
my_repos=Mani repozitoriji
show_more_repos=Parādīt vairāk repozitorojus...
collaborative_repos=Sadarbības repozitoriji
my_orgs=Manas organizācijas
my_mirrors=Mani spoguļi
@@ -148,6 +149,8 @@ forget_password=Aizmirsi paroli?
sign_up_now=Nepieciešams konts? Reģistrējies tagad.
confirmation_mail_sent_prompt=Jauns apstiprināšanas e-pasts ir nosūtīts uz <b>%s</b>, lūdzu, pārbaudies savu e-pasta kontu tuvāko %d stundu laikā, lai pabeigtu reģistrācijas procesu.
active_your_account=Aktivizēt savu kontu
prohibit_login=Aizliegt pieteikšanos
prohibit_login_desc=Ar Jūsu kontu nav atļauts pieteikties, sazinoties ar lapas administratoru.
resent_limit_prompt=Atvainojiet, Jūs sūtījāt aktivizācijas e-pastu pārāk bieži. Lūdzu, gaidiet 3 minūtes.
has_unconfirmed_mail=Sveiki %s, Jums ir neapstiprināta e-pasta adrese (<b>%s</b>). Ja neesat saņēmis apstiprināšanas e-pastu vai Jums ir nepieciešams nosūtīt jaunu, lūdzu, nospiediet pogu, kas atrodas zemāk.
resend_mail=Nospiediet šeit, lai vēlreiz nosūtītu aktivizācijas e-pastu
@@ -157,6 +160,7 @@ reset_password=Atjaunot savu paroli
invalid_code=Atvainojiet, Jūsu apstiprināšanas kodam ir beidzies derīguma termiņš vai arī tas ir nepareizs.
reset_password_helper=Nospiediet šeit, lai atjaunotu paroli
password_too_short=Paroles garums nedrīkst būt mazāks par 6.
non_local_account=Tikai lokālie konti var nomainīt savu paroli Gogs.
[mail]
activate_account=Lūdzu, aktivizējiet savu kontu
@@ -219,8 +223,7 @@ org_still_own_repo=Šī organizācija ir vismaz viena repozitorija īpašnieks,
target_branch_not_exist=Mērķa atzars neeksistē
[user]
change_avatar=Mainīt savu profila attēlu vietnē gravatar.com
change_custom_avatar=Mainīt savu profila attēlu iestatījumos
change_avatar=Mainīt profila attēlu
join_on=Pievienojās
repositories=Repozitoriji
activity=Publiskā aktivitāte
@@ -350,6 +353,8 @@ readme_helper=Izvēlieties faila LasiMani sagatavi
auto_init=Inicializēt šo repozitoriju ar izvēlētajiem failiem un sagatavi
create_repo=Izveidot repozitoriju
default_branch=Noklusējuma atzars
mirror_prune=Izmest
mirror_prune_desc=Izdzēst visas ārējās atsauces, kas ārējā repozitorijā vairs neeksistē
mirror_interval=Spoguļošanas intervāls (stundās)
mirror_address=Spoguļa adrese
mirror_address_desc=Lūdzu iekļaujiet adresē nepieciešamo lietotājvārdu/paroli.
@@ -407,6 +412,7 @@ file_raw=Neapstrādāts
file_history=Vēsture
file_view_raw=Rādīt neapstrādātu
file_permalink=Patstāvīgā saite
file_too_large=Šis fails ir par lielu, lai to parādītu
commits.commits=Revīzijas
commits.search=Meklēt revīzijas
@@ -460,7 +466,8 @@ issues.next=Nākamā
issues.open_title=Atvērta
issues.closed_title=Slēgta
issues.num_comments=%d komentāri
issues.commented_at=`komentēja <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=` komentēja <a href="#%s">%s</a>`
issues.delete_comment_confirm=Vai patiešām vēlaties dzēst šo komentāru?
issues.no_content=Vēl nav satura.
issues.close_issue=Aizvērt
issues.close_comment_issue=Komentēt un aizvērt
@@ -561,6 +568,10 @@ wiki.last_updated=Pēdējo reizi labota %s
settings=Iestatījumi
settings.options=Opcijas
settings.collaboration=Sadarbība
settings.collaboration.admin=Administrators
settings.collaboration.write=Rakstīšanas
settings.collaboration.read=Skatīšanās
settings.collaboration.undefined=Nedefinētas
settings.hooks=Tīmekļa āķi
settings.githooks=Git āķi
settings.basic_settings=Pamatiestatījumi
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Apmeklētāji tiks novirzīti uz adresi, kad vi
settings.issues_desc=Iespējot iebūvētu vieglu problēmu sekotāju
settings.use_external_issue_tracker=Izmantot ārējo problēmu sekotāju
settings.tracker_url_format=Ārējā problēmu sekotāja adreses formāts
settings.tracker_issue_style=Ārējā problēmu reģistra nosaukumu stils:
settings.tracker_issue_style.numeric=Cipari
settings.tracker_issue_style.alphanumeric=Burti un cipari
settings.tracker_url_format_desc=Jūs varat izmantot <code>{user}{repo}{index}</code> lietotājvārdam, repozitorija nosaukumam un problēmas identifikātoram.
settings.pulls_desc=Iespējot izmaiņu pieprasījumus lai saņemtu publiskus ieguldījumus
settings.danger_zone=Bīstamā zona
@@ -597,9 +611,7 @@ settings.delete=Dzēst šo repozitoriju
settings.delete_desc=Dzēšot repozitoriju, tā datus vairs nebūs iespējams atgūt. Pirms dzēšanas pārliecinieites vai patiešām vēlaties to darīt.
settings.delete_notices_1=- Šī darbība ir <strong>NEATGRIEZENISKA</strong>.
settings.delete_notices_2=- Šī darbība neatgriezeniski izdzēsīs visus šī repozitorija datus, tai skaitā Git datus, problēmu ziņojumus, komentārus un definētās piekļuves tiesības.
settings.delete_notices_fork_1=- Ja repozitorijs ir publisks, visi atdalītie repozitoriji kļūs neatkarīgi.
settings.delete_notices_fork_2=- Ja repozitorijs ir privāts, tiks dzēsti arī visi atdalītie repozitoriji.
settings.delete_notices_fork_3=- Ja vēlaties saglabāt atdalīts repozitorijus pēc dzēšanas, sākumā nomainiet repozitorija redzamību uz publisku.
settings.delete_notices_fork_1=- Visi atdalītie repozitoriji kļūs neatkarīgi pēc dzēšanas.
settings.deletion_success=Repozitorijs tika veiksmīgi dzēsts!
settings.update_settings_success=Repozitorija opcijas ir veiksmīgi saglabātas.
settings.transfer_owner=Jaunais īpašnieks
@@ -683,6 +695,8 @@ diff.show_unified_view=Apvienotais skats
diff.stats_desc=<strong>%d mainītis faili</strong> ar <strong>%d papildinājumiem</strong> un <strong>%d dzēšanām</strong>
diff.bin=BIN
diff.view_file=Parādīt failu
diff.file_suppressed=Failā izmaiņas netiks attēlotas, jo tās ir par lielu
diff.too_many_files=Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels
release.releases=Laidieni
release.new_release=Jauns laidiens
@@ -713,6 +727,7 @@ release.deletion=Laidiena dzēšana
release.deletion_desc=Dzēšot šo laidienu tiks dzēsts arī atbilstošs Git tags. Vai vēlaties turpināt?
release.deletion_success=Laidiens tika veiksmīgi dzēsts!
release.tag_name_already_exist=Laidiens ar šādu taga nosaukumu jau eksistē.
release.tag_name_invalid=Nekorekts birkas nosaukams.
release.downloads=Lejupielādes
[org]
@@ -880,6 +895,7 @@ users.edit_account=Labot kontu
users.max_repo_creation=Maksimāls repozitoriju veidošanas limits
users.max_repo_creation_desc=(Uzlikt -1 lai izmantotu globālu limitu pēc noklusējuma)
users.is_activated=Konts ir aktivizēts
users.prohibit_login=Šim kontam ir aizliegts autorizēties
users.is_admin=Šim kontam ir administratora piekļuves tiesības
users.allow_git_hook=Šim kontam ir tiesības pievienot/labot Git āķus
users.allow_import_local=Šim kontam ir tiesības importēt lokālus repozitorijus
@@ -910,6 +926,7 @@ auths.enabled=Iespējota
auths.updated=Atjaunināta
auths.auth_type=Autentifikācijas tips
auths.auth_name=Autentifikācijas nosaukums
auths.security_protocol=Drošības protokols
auths.domain=Domēns
auths.host=Resursdators
auths.port=Ports

141
conf/locale/locale_nl-NL.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Gebruikersnaam of e-mail
password_holder=Wachtwoord
switch_dashboard_context=Wissel voorpaginacontext
my_repos=Mijn repositories
show_more_repos=Show more repositories...
collaborative_repos=Gedeelde repositories
my_orgs=Mijn organisaties
my_mirrors=Mijn mirrors
@@ -148,6 +149,8 @@ forget_password=Wachtwoord vergeten?
sign_up_now=Een account nodig? Meld u nu aan.
confirmation_mail_sent_prompt=Een bevestigingsemail is gestuurd naar <b>%s</b>, Bevestig u aanvraag binnen %d uren om uw registratie te voltooien.
active_your_account=Activeer uw account
prohibit_login=Login Prohibited
prohibit_login_desc=Your account is prohibited to login, please contact site admin.
resent_limit_prompt=Sorry, u heeft te snel na elkaar een aanvraag gedaan voor een activatie mail. Wacht drie minuten voor uw volgende aanvraag.
has_unconfirmed_mail=Beste %s, u heeft een onbevestigd e-mailadres (<b>%s</b>). Als u nog geen bevestiging heeft ontvangen, of u een nieuwe aanvraag wilt doen, klik dan op de onderstaande knop.
resend_mail=Klik hier om uw activatie mail nog een keer te verzenden
@@ -157,6 +160,7 @@ reset_password=Reset uw wachtwoord
invalid_code=Sorry, uw bevestigingscode is verlopen of niet meer geldig.
reset_password_helper=Klik hier om uw wachtwoord opnieuw in te stellen.
password_too_short=De lengte van uw wachtwoord moet minimaal zes karakters zijn.
non_local_account=Non-local accounts cannot change passwords through Gogs.
[mail]
activate_account=Activeer uw account
@@ -219,8 +223,7 @@ org_still_own_repo=De organisatie heeft nog eigendomen op repositories. U moet d
target_branch_not_exist=Doel branch bestaat niet
[user]
change_avatar=Verander uw avatar op Gravatar.com
change_custom_avatar=Wijzig uw avatar in de instellingen
change_avatar=Change your avatar
join_on=Aangemeld op
repositories=repositories
activity=Openbare activiteit
@@ -350,6 +353,8 @@ readme_helper=Selecteer een sjabloon voor het Leesmij-bestand
auto_init=Initialiseer deze repositorie met de geselecteerde bestanden en sjabloon
create_repo=Nieuwe repository
default_branch=Standaard branch
mirror_prune=Prune
mirror_prune_desc=Remove any remote-tracking references that no longer exist on the remote
mirror_interval=Mirror interval(uur)
mirror_address=Kopie-adres
mirror_address_desc=Gelieve noodzakelijke gebruikersgegevens in de adresbalk.
@@ -407,6 +412,7 @@ file_raw=Ruwe
file_history=Geschiedenis
file_view_raw=Weergave ruwe
file_permalink=Permalink
file_too_large=This file is too large to be shown
commits.commits=Commits
commits.search=Zoeken
@@ -460,7 +466,8 @@ issues.next=Volgende
issues.open_title=Open
issues.closed_title=Gesloten
issues.num_comments=%d opmerkingen
issues.commented_at=`gaf commentaar op <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`commented <a href="#%s">%s</a>`
issues.delete_comment_confirm=Are you sure you want to delete this comment?
issues.no_content=Er is nog geen inhoud.
issues.close_issue=Sluit
issues.close_comment_issue=Sluit en geef commentaar
@@ -469,9 +476,9 @@ issues.reopen_comment_issue=Heropen en geef commentaar
issues.create_comment=Reageer
issues.closed_at=`gesloten om <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`heropend om <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at='verwees naar dit probleem vanuit een commit <a id="%[1]s" href="#%[1]s"> %[2]s'</a>
issues.commit_ref_at=`verwees naar dit probleem vanuit een commit <a id="%[1]s" href="#%[1]s"> %[2]s'</a>`
issues.poster=Poster
issues.collaborator=Collaborator
issues.collaborator=Medewerker
issues.owner=Eigenaar
issues.sign_up_for_free=Gratis aanmelden
issues.sign_in_require_desc=om deel te nemen in deze conversatie. Heeft u al een account? <a href="%s">Meld u aan om te reageren</a>
@@ -501,7 +508,7 @@ pulls.nothing_to_compare=Er is niets te vergelijken omdat base en head branches
pulls.has_pull_request=' Er is al een pull-aanvraag tussen deze twee targets: <a href="%[1]s/pulls/%[3]d"> %[2]s #% [3]d</a>'
pulls.create=Pull verzoek aanmaken
pulls.title_desc=wil %[1]d commits van <code>%[2]s</code> samenvoegen met <code>%[3]s</code>
pulls.merged_title_desc=%[1] commits samengevoegd van <code>%[2]s</code> naar <code>%[3]s</code> %[4]s
pulls.merged_title_desc=heeft %[1]d commits samengevoegd van <code>%[2]s</code> naar <code>%[3]s</code> %[4]s
pulls.tab_conversation=Discussie
pulls.tab_commits=Commits
pulls.tab_files=Bestanden gewijzigd
@@ -512,9 +519,9 @@ pulls.data_broken=Omdat informatie over de fork is verwijderd, zijn de gegevens
pulls.is_checking=Controle van conflicten is nog bezig, ververs deze pagina in enkele ogenblikken.
pulls.can_auto_merge_desc=Dit pull-request kan automatisch samengevoegd worden.
pulls.cannot_auto_merge_desc=Dit pull-request kan niet worden gemerged omdat er conflicten zijn.
pulls.cannot_auto_merge_helper=Please merge manually in order to resolve the conflicts.
pulls.cannot_auto_merge_helper=Gelieve beide versies manueel samen te voegen om de conflicten op te lossen.
pulls.merge_pull_request=Samenvoegen van pull verzoek
pulls.open_unmerged_pull_exists=`You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
pulls.open_unmerged_pull_exists=U kan de bewerking 'heropenen' niet uitvoeren omdat er al een pull-aanvraag (#%d) is van dezelfde repository met dezelfde informatie. Voeg deze eerst samen.
milestones.new=Nieuwe mijlpaal
milestones.open_tab=%d geopend
@@ -553,7 +560,7 @@ wiki.last_commit_info=%s heeft deze pagina aangepast %s
wiki.edit_page_button=Bewerken
wiki.new_page_button=Nieuwe pagina
wiki.delete_page_button=Verwijder pagina
wiki.delete_page_notice_1=This will delete the page <code>"%s"</code>. Please be certain.
wiki.delete_page_notice_1=Dit zal pagina <code>"%s"</code> verwijderen. Weet u het zeker?
wiki.page_already_exists=Er bestaat al een wiki-pagina met deze naam.
wiki.pages=Paginas
wiki.last_updated=Laatst bijgewerkt: %s
@@ -561,45 +568,50 @@ wiki.last_updated=Laatst bijgewerkt: %s
settings=Instellingen
settings.options=Opties
settings.collaboration=Samenwerking
settings.collaboration.admin=Admin
settings.collaboration.write=Write
settings.collaboration.read=Read
settings.collaboration.undefined=Undefined
settings.hooks=Webhooks
settings.githooks=Git-hooks
settings.basic_settings=Basis instellingen
settings.site=Officiële site
settings.update_settings=Instellingen bewerken
settings.change_reponame_prompt=This change will affect how links relate to the repository.
settings.change_reponame_prompt=Deze verandering zal gevolgen hebben voor hoe links zich verhouden tot de repository.
settings.advanced_settings=Geavanceerde opties
settings.wiki_desc=Enable wiki to allow people write documents
settings.wiki_desc=Wiki inschakelen, om mensen documenten te laten schrijven
settings.use_external_wiki=Externe wiki gebruiken
settings.external_wiki_url=Externe wiki-URL
settings.external_wiki_url_desc=Bezoekers worden doorgestuurd naar de URL als ze op het tabblad klikken.
settings.issues_desc=Ingebouwde compacte issuetracker inschakelen
settings.use_external_issue_tracker=Externe issuetracker gebruiken
settings.tracker_url_format=URL-formaat externe issuetracker
settings.tracker_url_format_desc=You can use placeholder <code>{user} {repo} {index}</code> for user name, repository name and issue index.
settings.pulls_desc=Enable pull requests to accept public contributions
settings.tracker_issue_style=External Issue Tracker Naming Style:
settings.tracker_issue_style.numeric=Numeric
settings.tracker_issue_style.alphanumeric=Alphanumeric
settings.tracker_url_format_desc=U kan de aanduidingen <code>{user} {repo} {index}</code> gebruiken voor de gebruikersnaam, de naam van de repository en de lijst van open tickets.
settings.pulls_desc=Schakel 'pull request' in om publieke bijdragen te mogelijk te maken
settings.danger_zone=Gevaren zone
settings.new_owner_has_same_repo=De nieuwe eigenaar heeft al een repositorie met deze naam
settings.convert=Converteren naar gewone repository
settings.convert_desc=U kunt deze mirror converteren naar een gewone repository. Dit kan niet ongedaan worden gemaakt.
settings.convert_notices_1=- This operation will convert this repository mirror into a regular repository and cannot be undone.
settings.convert_notices_1=- Deze operatie zet de mirror repository om in een gewone repository en dit kan niet ongedaan gemaakt worden.
settings.convert_confirm=Conversie bevestigen
settings.convert_succeed=Deze repository is geconverteerd naar een normale repository.
settings.transfer=Eigendom overdragen
settings.transfer_desc=Draag deze repo over aan een andere gebruiker of een organisatie waar u beheerders rechten heeft.
settings.transfer_notices_1=- You will lose access if new owner is a individual user.
settings.transfer_notices_2=- You will conserve access if new owner is an organization and if you're one of the owners.
settings.transfer_form_title=Please enter following information to confirm your operation:
settings.wiki_delete=Erase Wiki Data
settings.wiki_delete_desc=Once you erase wiki data there is no going back. Please be certain.
settings.wiki_delete_notices_1=- This will delete and disable the wiki for %s
settings.wiki_deletion_success=Repository wiki data have been erased successfully.
settings.transfer_notices_1=- U verliest toegang als de nieuwe gebruiker een individuele gebruiker is.
settings.transfer_notices_2=- U behoudt toegang indien de nieuwe eigenaar een organisatie is en U één van de eigenaren van de organisatie bent.
settings.transfer_form_title=Voer de volgende informatie in om de bewerking te bevestigen:
settings.wiki_delete=Wiki gegevens verwijderen
settings.wiki_delete_desc=Als U wiki informatie wist gaat deze onherroepelijk verloren. Bent U zeker?
settings.wiki_delete_notices_1=- Deze operatie wist de wiki voor %s en schakelt de wiki uit
settings.wiki_deletion_success=De repository met wiki data is succesvol gewist.
settings.delete=Verwijder deze repositorie
settings.delete_desc=Als u eenmaal een repositorie verwijderd is er geen weg terug. Gelieve zeker te zijn van uw acties.
settings.delete_notices_1=- This operation <strong>CANNOT</strong> be undone.
settings.delete_notices_2=- This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
settings.delete_notices_fork_1=- If this repository is public, all forks will become independent after deletion.
settings.delete_notices_fork_2=- If this repository is private, all forks will be removed at the same time.
settings.delete_notices_fork_3=- If you want to keep all forks after deletion, please change visibility of this repository to public first.
settings.delete_notices_1=- Deze bewerking kan <strong>NIET</strong> ongedaan gemaakt worden.
settings.delete_notices_2=- Deze bewerking verwijdert permanent alle informatie van deze repository met inbegrip van Git gegevens, tickets, opmerkingen en de toegang van medewerkers.
settings.delete_notices_fork_1=- All forks will become independent after deletion.
settings.deletion_success=Repository is succesvol verwijderd!
settings.update_settings_success=Repositorie instellingen zijn succesvol bijgewerkt.
settings.transfer_owner=Nieuwe eigenaar
@@ -609,20 +621,20 @@ settings.confirm_delete=Bevestig verwijdering
settings.add_collaborator=Nieuwe medewerker toevoegen
settings.add_collaborator_success=medewerker is toegevoegd.
settings.delete_collaborator=Verwijderen
settings.collaborator_deletion=Collaborator Deletion
settings.collaborator_deletion_desc=This user will no longer have collaboration access to this repository after deletion. Do you want to continue?
settings.collaborator_deletion=Verwijder Medewerker
settings.collaborator_deletion_desc=Deze gebruiker zal niet langer toegang hebben tot deze repository. Wilt U doorgaan?
settings.remove_collaborator_success=medewerker is verwijderd.
settings.search_user_placeholder=Zoek gebruiker...
settings.org_not_allowed_to_be_collaborator=Organization is not allowed to be added as a collaborator.
settings.org_not_allowed_to_be_collaborator=De organisatie kan niet toegevoegd worden als medewerker.
settings.user_is_org_member=Gebruiker is lid van de organisatie die als een medewerker kan niet worden toegevoegd.
settings.add_webhook=Webhook toevoegen
settings.hooks_desc=Webhooks dat de externe diensten om kennisgevingen te ontvangen wanneer bepaalde gebeurtenissen op Gogs plaatsvinden. Wanneer de opgegeven gebeurtenissen plaatsvinden, sturen we een POST-aanvraag naar elk van de URL's die u opgeeft. Meer informatie vindt u in onze <a target="_blank" href="%s"> Webhooks gids</a>.
settings.webhook_deletion=Webhook verwijderen
settings.webhook_deletion_desc=Delete this webhook will remove its information and all delivery history. Do you want to continue?
settings.webhook_deletion_desc=Verwijderen van deze webhook zal de informatie en alle geschiedenis verwijderen. Wilt u doorgaan?
settings.webhook_deletion_success=Webhook is succesvol verwijderd!
settings.webhook.test_delivery=Test-bezorging
settings.webhook.test_delivery_desc=Send a fake push event delivery to test your webhook settings
settings.webhook.test_delivery_success=Test webhook has been added to delivery queue. It may take few seconds before it shows up in the delivery history.
settings.webhook.test_delivery_desc=Stuur een nep push bericht om de webhook te testen
settings.webhook.test_delivery_success=De test webhook is toegevoegd aan de wachtrij. Het kan enkele seconden duren voor deze in de geschiedenis wordt weergegeven.
settings.webhook.request=Verzoek
settings.webhook.response=Antwoord
settings.webhook.headers=Headers
@@ -662,7 +674,7 @@ settings.slack_domain=Slack domein
settings.slack_channel=Slack kanaal
settings.deploy_keys=Installeer sleutels
settings.add_deploy_key=Toevoegen deploy sleutel
settings.deploy_key_desc=Deploy keys have read-only access. They are not the same as personal account SSH keys.
settings.deploy_key_desc=Sleutels voor uitrol hebben enkel leesrechten. Ze zijn niet dezelfde als de SSH sleutels van persoonlijke accounts.
settings.no_deploy_keys=U hebt nog geen deploy sleutels toegevoegd.
settings.title=Titel
settings.deploy_key_content=Inhoud
@@ -678,11 +690,13 @@ diff.parent=bovenliggende
diff.commit=commit
diff.data_not_available=Diff gegevens niet beschikbaar.
diff.show_diff_stats=Toon Diff Stats
diff.show_split_view=Split View
diff.show_unified_view=Unified View
diff.show_split_view=Zij-aan-zij weergave
diff.show_unified_view=Gecombineerde weergave
diff.stats_desc=<strong>%d gewijzigde bestanden</strong> met <strong>toevoegingen van %d</strong> en <strong>%d verwijderingen</strong>
diff.bin=BIN
diff.view_file=Bestand weergeven
diff.file_suppressed=File diff suppressed because it is too large
diff.too_many_files=Some files were not shown because too many files changed in this diff
release.releases=Releases
release.new_release=Nieuwe release
@@ -692,7 +706,7 @@ release.stable=Stabiel
release.edit=bewerken
release.ahead=<strong>%d</strong> aanpassingen aan %s sinds deze versie
release.source_code=Broncode
release.new_subheader=Publish releases to iterate product.
release.new_subheader=Publiceer releases om te itereren.
release.edit_subheader=Een gedetailleerd changelog helpt gebruikers te begrijpen wat er is verbeterd in deze release.
release.tag_name=Tagnaam
release.target=Doel
@@ -710,9 +724,10 @@ release.save_draft=Concept opslaan
release.edit_release=Release bewerken
release.delete_release=Deze release verwijderen
release.deletion=Release verwijderen
release.deletion_desc=Deleting this release will delete the corresponding Git tag. Do you want to continue?
release.deletion_desc=Als deze release verwijdert, worden de bijbehorende Git tag ook gewist. Wilt u doorgaan?
release.deletion_success=Release is verwijderd!
release.tag_name_already_exist=Versie met deze naam bestaat al.
release.tag_name_invalid=Tag name is not valid.
release.downloads=Downloads
[org]
@@ -744,8 +759,8 @@ settings.website=Website
settings.location=Locatie
settings.update_settings=Instellingen bijwerken
settings.update_setting_success=Organisatie instellingen zijn succesvol bijgewerkt.
settings.change_orgname_prompt=This change will affect how links relate to the organization.
settings.update_avatar_success=Organization avatar setting has been updated successfully.
settings.change_orgname_prompt=Deze verandering beinvloed de verhouding tussen links en de organisatie.
settings.update_avatar_success=Organisatie avatar-instellingen zijn succesvol gewijzigd.
settings.delete=Verwijder organisatie
settings.delete_account=Verwijder deze organisatie
settings.delete_prompt=Deze actie zal de origanisatie permanent verwijderen. U kunt dit <strong>NIET</strong> terug draaien!
@@ -754,7 +769,7 @@ settings.delete_org_title=Verwijderen organsiatie
settings.delete_org_desc=Deze organisatie zal permanent worden verwijderd, wilt u doorgaan?
settings.hooks_desc=Een webhook toevoegen die door <strong>alle repositories</strong> in deze organisatie getriggerd kan worden.
members.membership_visibility=Membership Visibility:
members.membership_visibility=Zichtbaarheid lidmaatschap:
members.public=Openbaar
members.public_helper=maak prive
members.private=Prive
@@ -764,7 +779,7 @@ members.owner=Eigenaar
members.member=Lid
members.remove=Verwijderen
members.leave=Verlaat
members.invite_desc=Add a new member to %s:
members.invite_desc=Voeg nieuw lid toe aan %s:
members.invite_now=Nu uitnodigen
teams.join=Lid worden
@@ -820,16 +835,16 @@ dashboard.delete_inactivate_accounts=Verwijder alle inactieve accounts
dashboard.delete_inactivate_accounts_success=Alle inactivering van rekeningen hebben verwijderd.
dashboard.delete_repo_archives=Verwijderen van alle repositories archieven
dashboard.delete_repo_archives_success=Alle repositories archieven hebben verwijderd.
dashboard.delete_missing_repos=Delete all repository records that lost Git files
dashboard.delete_missing_repos_success=All repository records that lost Git files have been deleted successfully.
dashboard.delete_missing_repos=Verwijder alle repositories zonder Git files
dashboard.delete_missing_repos_success=Alle repositories zonder Git files verwijderd.
dashboard.git_gc_repos=Garbage collectie uitvoeren
dashboard.git_gc_repos_success=Garbage collectie met succes uitgevoerd.
dashboard.resync_all_sshkeys=Herschrijf '.ssh/authorized_keys' (Let op: alle sleutels die niet van Gogs zijn zullen verloren gaan!)
dashboard.resync_all_sshkeys_success=Alle publieke sleutels zijn herschreven.
dashboard.resync_all_update_hooks=Herschrijf alle repositorie-hooks (nodig als de configuratie bestandslocatie is gewijzigd)
dashboard.resync_all_update_hooks_success=Alle repositorie-hooks zijn herschreven.
dashboard.reinit_missing_repos=Reinitialize all repository records that lost Git files
dashboard.reinit_missing_repos_success=All repository records that lost Git files have been reinitialized successfully.
dashboard.reinit_missing_repos=Alle repositories zonder Git files opnieuw initialiseren
dashboard.reinit_missing_repos_success=Alle repositories zonder Git files zijn succesvol opnieuw geinitializeerd.
dashboard.server_uptime=Uptime server
dashboard.current_goroutine=Huidige Goroutines
@@ -874,12 +889,13 @@ users.edit=Bewerken
users.auth_source=Authenticatiebron
users.local=Lokaal
users.auth_login_name=Authenticatie-loginnaam
users.password_helper=Leave it empty to remain unchanged.
users.password_helper=Laat leeg om ongewijzigd te blijven.
users.update_profile_success=Profiel is succesvol bijgewerkt.
users.edit_account=Bewerk account
users.max_repo_creation=Maximum Repository Creation Limit
users.max_repo_creation_desc=(Set -1 to use global default limit)
users.max_repo_creation=Maximum-limiet voor aanmaken van repositories
users.max_repo_creation_desc=(Zet op -1 om de globale limiet te gebruiken)
users.is_activated=Dit account is geactiveerd
users.prohibit_login=This account is prohibited to login
users.is_admin=Dit account heeft beheerdersrechten
users.allow_git_hook=Deze account beschikt over machtigingen voor het maken van Git haken
users.allow_import_local=Dit account mag lokale repositories importeren
@@ -902,7 +918,7 @@ repos.watches=Volgers
repos.stars=Sterren
repos.issues=Kwesties
auths.auth_manage_panel=Authentication Manage Panel
auths.auth_manage_panel=Authenticatie-beheer paneel
auths.new=Nieuwe bron toevoegen
auths.name=Naam
auths.type=Type
@@ -910,20 +926,21 @@ auths.enabled=Ingeschakeld
auths.updated=Bijgewerkt
auths.auth_type=Authenticatietype
auths.auth_name=Authenticatienaam
auths.security_protocol=Security Protocol
auths.domain=Domein
auths.host=Host
auths.port=Poort
auths.bind_dn=Binden DN
auths.bind_password=Bind Password
auths.bind_password_helper=Warning: This password is stored in plain text. Do not use a high privileged account.
auths.bind_password=Bind wachtwoord
auths.bind_password_helper=Opgelet: Dit wachtwoord wordt opgeslagen als leesbare tekst. Gebruik geen account met verhoogde rechten.
auths.user_base=User Search Base
auths.user_dn=User DN
auths.attribute_username=Username attribute
auths.attribute_username_placeholder=Leave empty to use sign-in form field value for user name.
auths.attribute_username=Gebruikersnaam attribuut
auths.attribute_username_placeholder=Laat leeg om het login veld van het formulier te gebruiken als gebruikersnaam.
auths.attribute_name=Voornaam attribuut
auths.attribute_surname=Achternaam attribuut
auths.attribute_mail=E-mail attribuut
auths.attributes_in_bind=Fetch attributes in Bind DN context
auths.attributes_in_bind=Verkrijg attributes van de Bind DN context
auths.filter=Gebruikersfilter
auths.admin_filter=Beheerdersfilter
auths.ms_ad_sa=MS Ad SA
@@ -931,20 +948,20 @@ auths.smtp_auth=SMTP-authenticatietype
auths.smtphost=SMTP host
auths.smtpport=SMTP poort
auths.allowed_domains=Toegelaten domeinen
auths.allowed_domains_helper=Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
auths.allowed_domains_helper=Laat dit leeg om geen enkel domein te beperken. Meerdere domeinen moeten door een komma (',') gescheiden worden.
auths.enable_tls=Activeer TLS-encryptie
auths.skip_tls_verify=TLS-verificatie overslaan
auths.pam_service_name=PAM servicenaam
auths.enable_auto_register=Activeer automatische registratie
auths.tips=Tips
auths.edit=Edit Authentication Setting
auths.edit=Verificatie-instelling bewerken
auths.activated=Deze autorisatiemethode is geactiveerd
auths.new_success=New authentication '%s' has been added successfully.
auths.update_success=Authentication setting has been updated successfully.
auths.new_success=Nieuwe authenticatie '%s' werd toegevoegd.
auths.update_success=Authenticatie instellingen zijn succesvol gewijzigd.
auths.update=Authenticatie-instellingen bijwerken
auths.delete=Deze authenticatiewijze verwijderen
auths.delete_auth_title=Authentication Deletion
auths.delete_auth_desc=This authentication is going to be deleted, do you want to continue?
auths.delete_auth_title=Authenticatie verwijderd
auths.delete_auth_desc=Deze authenticatie zal verwijderd worden, wil je verdergaan?
auths.still_in_used=This authentication is still used by some users, please delete or convert these users to another login type first.
auths.deletion_success=Authentication has been deleted successfully!
@@ -1040,13 +1057,13 @@ monitor.start=Starttijd
monitor.execute_time=Uitvoertijd
notices.system_notice_list=Systeem aankondigingen
notices.view_detail_header=View Notice Detail
notices.view_detail_header=Bekijk bericht details
notices.actions=Acties
notices.select_all=Alles selecteren
notices.deselect_all=Alles deselecteren
notices.inverse_selection=Selectie omkeren
notices.delete_selected=Selectie verwijderen
notices.delete_all=Delete All Notices
notices.delete_all=Verwijder alle berichten
notices.type=Type
notices.type_1=Opslagplaats
notices.desc=Beschrijving

31
conf/locale/locale_pl-PL.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Nazwa użytkownika lub e-mail
password_holder=Hasło
switch_dashboard_context=Przełącz kontekst pulpitu
my_repos=Moje repozytoria
show_more_repos=Pokaż więcej repozytoriów...
collaborative_repos=Wspólne repozytoria
my_orgs=Moje organizacje
my_mirrors=Moje kopie lustrzane
@@ -148,6 +149,8 @@ forget_password=Zapomniałeś hasła?
sign_up_now=Potrzebujesz konta? Zarejestruj się teraz.
confirmation_mail_sent_prompt=Nowa wiadomość e-mail z potwierdzeniem została wysłana do <b>%s</b>, proszę sprawdzić swoją skrzynkę odbiorczą w ciągu najbliższych godzin %d aby dokończyć proces rejestracji.
active_your_account=Aktywuj swoje konto
prohibit_login=Logowanie zabronione
prohibit_login_desc=Nie możesz się zalogować na to konto, skontaktuj się z administratorem strony.
resent_limit_prompt=Niestety, zbyt często wysyłasz e-mail aktywacyjny. Proszę odczekać 3 minuty.
has_unconfirmed_mail=Witaj, %s, masz niepotwierdzony adres e-mail (<b>%s</b>). Jeśli nie otrzymałeś wiadomości e-mail z potwierdzeniem lub potrzebujesz wysłać nową, kliknij na poniższy przycisk.
resend_mail=Kliknij tutaj, aby wysłać e-mail aktywacyjny
@@ -157,6 +160,7 @@ reset_password=Resetowanie hasła
invalid_code=Niestety, Twój kod potwierdzający wygasł lub jest nieprawidłowy.
reset_password_helper=Kliknij tutaj, aby zresetować hasło
password_too_short=Długość hasła nie może być mniejsza niż 6 znaków.
non_local_account=Nie lokalne konta nie mogą zmieniać haseł przez Gogs.
[mail]
activate_account=Prosimy aktywować swoje konto
@@ -219,8 +223,7 @@ org_still_own_repo=Ta organizacja dalej jest właścicielem repozytorium, które
target_branch_not_exist=Gałąź docelowa nie istnieje.
[user]
change_avatar=Zmień swój avatar na gravatar.com
change_custom_avatar=Zmień awatar w ustawieniach
change_avatar=Zmień swój awatar
join_on=Dołączył
repositories=Repozytoria
activity=Publiczna aktywność
@@ -350,6 +353,8 @@ readme_helper=Wybierz szablon readme
auto_init=Zainicjuj to repozytorium używając wybranych plików i szablonu
create_repo=Utwórz repozytorium
default_branch=Domyślna gałąź
mirror_prune=Wyczyść
mirror_prune_desc=Usuń wszystkie śledzone odwołania które nie istnieją w zdalnym repozytorium
mirror_interval=Częstotliwość kopiowania (godziny)
mirror_address=Adres kopii lustrzanej
mirror_address_desc=Proszę podać wymagane poświadczenia użytkownika w adresie.
@@ -407,6 +412,7 @@ file_raw=Czysty
file_history=Historia
file_view_raw=Zobacz czysty
file_permalink=Bezpośredni odnośnik
file_too_large=Ten plik jest zbyt duży, aby go wyświetlić
commits.commits=Commity
commits.search=Przeszukaj commity
@@ -460,7 +466,8 @@ issues.next=Następny
issues.open_title=Otwarty
issues.closed_title=Zamknięty
issues.num_comments=%d komentarzy
issues.commented_at=`komentuje <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`skomentował <a href="#%s">%s</a>`
issues.delete_comment_confirm=Czy na pewno chcesz usunąć ten komentarz?
issues.no_content=Nie ma jeszcze treści.
issues.close_issue=Zamknij
issues.close_comment_issue=Skomentuj i zamknij
@@ -561,6 +568,10 @@ wiki.last_updated=Ostatnia aktualizacja %s
settings=Ustawienia
settings.options=Opcje
settings.collaboration=Współpraca
settings.collaboration.admin=Administrator
settings.collaboration.write=Zapis
settings.collaboration.read=Odczyt
settings.collaboration.undefined=Niezdefiniowany
settings.hooks=Webhooki
settings.githooks=Hooki Git
settings.basic_settings=Ustawienia podstawowe
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Odwiedzający zostaną przekierowani do adresu U
settings.issues_desc=Włącz wbudowany lekki system zgłaszania problemów
settings.use_external_issue_tracker=Użyj zewnętrznego systemu zgłaszania problemów
settings.tracker_url_format=Format dla adresu URL zewnętrznego systemu
settings.tracker_issue_style=Styl nazw zewnętrznego systemu zgłaszania problemów:
settings.tracker_issue_style.numeric=Numeryczny
settings.tracker_issue_style.alphanumeric=Alfanumeryczne
settings.tracker_url_format_desc=Symbole zastępcze <code>{user} {repo} {index}</code> mogą być użyte dla nazwy użytkownika, nazwy repozytorium i numeru problemu.
settings.pulls_desc=Włącz obsługę pull request, aby akceptować publiczny wkład
settings.danger_zone=Strefa niebezpieczeństwa
@@ -597,9 +611,7 @@ settings.delete=Usuń to repozytorium
settings.delete_desc=Po usunięciu repozytorium nie ma odwrotu. Upewnij się, że tego chcesz.
settings.delete_notices_1=- Ta operacja <strong>NIE MOŻE</strong> zostać cofnięta.
settings.delete_notices_2=- Ta operacja trwale usunie wszystko z tego repozytorium, w tym dane Git, problemy, komentarze i dostęp dla współpracowników.
settings.delete_notices_fork_1=- Jeśli to repozytorium jest publiczne, wszystkie forki staną się niezależne.
settings.delete_notices_fork_2=- Jeśli to repozytorium jest prywatne, forki zostaną usunięte wraz z usunięciem tego repozytorium.
settings.delete_notices_fork_3=- Jeśli chcesz zachować wszystkie forki po usunięciu, proszę najpierw uczyń to repozytorium publicznym.
settings.delete_notices_fork_1=Wszystkie forki staną się niezależne po usunięciu.
settings.deletion_success=Repozytorium zostało pomyślnie usunięte!
settings.update_settings_success=Opcje repozytorium zostały pomyślnie zaktualizowane.
settings.transfer_owner=Nowy właściciel
@@ -683,6 +695,8 @@ diff.show_unified_view=Zunifikowany widok
diff.stats_desc=<strong>%d zmienionych plików</strong> z <strong>%d dodań</strong> i <strong>%d usunięć</strong>
diff.bin=BIN
diff.view_file=Wyświetl plik
diff.file_suppressed=Plik diff jest za duży
diff.too_many_files=Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików
release.releases=Wydania
release.new_release=Nowe wydanie
@@ -703,7 +717,7 @@ release.write=Napisz
release.preview=Podgląd
release.loading=Ładowanie...
release.prerelease_desc=To jest wersja wstępna
release.prerelease_helper=Chcemy zwrócić uwagę, że ta wersja jest oznaczona jako eksperymentalna.
release.prerelease_helper=Oznacz to wydanie jako wersję wstępną.
release.cancel=Anuluj
release.publish=Publikuj wersję
release.save_draft=Zapisz szkic
@@ -713,6 +727,7 @@ release.deletion=Usuwanie wydania
release.deletion_desc=Usunięcie tego wydania spowoduje usunięcie odpowiednich tagów Git. Czy chcesz kontynuować?
release.deletion_success=Wydanie zostało pomyślnie usunięte!
release.tag_name_already_exist=Wersja o tej nazwie tagu już istnieje.
release.tag_name_invalid=Nazwa tagu jest niepoprawna.
release.downloads=Pliki do pobrania
[org]
@@ -880,6 +895,7 @@ users.edit_account=Edytuj konto
users.max_repo_creation=Maksymalna liczba repozytoriów
users.max_repo_creation_desc=(Ustaw -1, aby użyć globalnego limitu)
users.is_activated=To konto jest aktywne
users.prohibit_login=Nie możesz się zalogować na to konto
users.is_admin=To konto ma uprawnienia administratora
users.allow_git_hook=To konto posiada uprawnienia do tworzenia hooków Git
users.allow_import_local=To konto ma uprawnienia do importu lokalnych repozytoriów
@@ -910,6 +926,7 @@ auths.enabled=Włączono
auths.updated=Zaktualizowano
auths.auth_type=Typ uwierzytelniania
auths.auth_name=Nazwa uwierzytelniania
auths.security_protocol=Protokół zabezpieczeń
auths.domain=Domena
auths.host=Host
auths.port=Port

31
conf/locale/locale_pt-BR.ini Executable file → Normal file
View File

@@ -11,7 +11,7 @@ register=Registrar
website=Site
version=Versão
page=Página
template=Modelo
template=Template
language=Idioma
create_new=Criar...
user_profile_and_more=Perfil do usuário e configurações
@@ -124,6 +124,7 @@ uname_holder=Nome de Usuário ou E-mail
password_holder=Senha
switch_dashboard_context=Trocar Contexto do Painel de Controle
my_repos=Meus Repositórios
show_more_repos=Show more repositories...
collaborative_repos=Repositórios Colaborativos
my_orgs=Minhas Organizações
my_mirrors=Meus Espelhos
@@ -148,6 +149,8 @@ forget_password=Esqueceu a senha?
sign_up_now=Precisa de uma conta? Cadastre-se agora.
confirmation_mail_sent_prompt=Um novo e-mail de confirmação foi enviado para <b>%s</b>, por favor, verifique sua caixa de entrada nas próximas %d horas para completar seu registro.
active_your_account=Ativar Sua Conta
prohibit_login=Login proibido
prohibit_login_desc=Sua conta foi proibida de efetuar login, por favor contate o administrador do site.
resent_limit_prompt=Desculpe, você está enviando um e-mail de ativação com muita frequência. Por favor, aguarde 3 minutos.
has_unconfirmed_mail=Oi %s, você possui um endereço de e-mail não confirmado (<b>%s</b>). Se você não recebeu um e-mail de confirmação ou precisa reenviar um novo, clique no botão abaixo.
resend_mail=Clique aqui para reenviar seu e-mail de ativação
@@ -157,6 +160,7 @@ reset_password=Redefinir Sua Senha
invalid_code=Desculpe, seu código de confirmação expirou ou não é válido.
reset_password_helper=Clique aqui para redefinir sua senha
password_too_short=O comprimento da senha não pode ser menor que 6.
non_local_account=Não é possível mudar a senha de contas não-locais pelo Gogs.
[mail]
activate_account=Por favor, ative sua conta
@@ -219,8 +223,7 @@ org_still_own_repo=Esta organização ainda tem a propriedade do repositório, v
target_branch_not_exist=O branch de destino não existe.
[user]
change_avatar=Altere o seu avatar em gravatar.com
change_custom_avatar=Altere seu avatar nas configurações
change_avatar=Mude seu avatar
join_on=Inscreveu-se em
repositories=Repositórios
activity=Atividade Pública
@@ -350,6 +353,8 @@ readme_helper=Selecione um modelo de leia-me
auto_init=Inicializar este repositório com os arquivos selecionados e modelo
create_repo=Criar Repositório
default_branch=Branch padrão
mirror_prune=Varrer
mirror_prune_desc=Remova quaisquer referências que não existem mais no remote
mirror_interval=Intervalo de Espelho (hora)
mirror_address=Endereço do espelho
mirror_address_desc=Por favor, inclua as credenciais do usuário necessários no endereço.
@@ -407,6 +412,7 @@ file_raw=Cru
file_history=Histórico
file_view_raw=Ver cru
file_permalink=Link permanente
file_too_large=Este arquivo é muito grande para ser exibido
commits.commits=Commits
commits.search=Pesquisar commits
@@ -460,7 +466,8 @@ issues.next=Próxima página
issues.open_title=aberto
issues.closed_title=fechado
issues.num_comments=%d comentários
issues.commented_at=`comentado <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`commented <a href="#%s">%s</a>`
issues.delete_comment_confirm=Are you sure you want to delete this comment?
issues.no_content=Nenhum conteúdo textual.
issues.close_issue=Fechar
issues.close_comment_issue=Comentar e fechar
@@ -561,6 +568,10 @@ wiki.last_updated=Última atualização %s
settings=Configurações
settings.options=Opções
settings.collaboration=Colaboração
settings.collaboration.admin=Administrador
settings.collaboration.write=Escrita
settings.collaboration.read=Leitura
settings.collaboration.undefined=Indefinido
settings.hooks=Webhooks
settings.githooks=Hooks do Git
settings.basic_settings=Configurações Básicas
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Os visitantes serão redirecionados para a URL q
settings.issues_desc=Habilitar gerenciamento de "problemas" nativo
settings.use_external_issue_tracker=Usar issue tracker externo
settings.tracker_url_format=Formato de URL do issue tracker externo
settings.tracker_issue_style=Estilo de nome de issue tracker externo:
settings.tracker_issue_style.numeric=Numeric
settings.tracker_issue_style.alphanumeric=Alphanumeric
settings.tracker_url_format_desc=Você pode usar o espaço reservado <code>{user} {repo} {index}</code> para o nome do usuário, índice de nome e a questão do repositório.
settings.pulls_desc=Habilitar pull requests para aceitar contribuições públicas
settings.danger_zone=Zona de Perigo
@@ -597,9 +611,7 @@ settings.delete=Deletar Este Repositório
settings.delete_desc=Uma vez que você deleta um repositório, não tem volta. Por favor, tenha certeza.
settings.delete_notices_1=-Esta operação <strong>NÃO PODERÁ</strong> ser desfeita.
settings.delete_notices_2=- Esta operação irá apagar permanentemente o tudo deste repositório, incluindo os dados do Git, problemas, comentários e acessos dos colaboradores.
settings.delete_notices_fork_1=- Se este repositório é público, todos os forks se tornarão independentes após a deleção.
settings.delete_notices_fork_2=- Se este repositório é privado, todos os forks serão removidos imediatamente.
settings.delete_notices_fork_3=- Se você deseja manter todos os forks, por favor muda a visibilidade do repositório para pública primeiro.
settings.delete_notices_fork_1=-Todos os forks se tornarão independentes após a exclusão.
settings.deletion_success=Repositório excluído com sucesso!
settings.update_settings_success=As opções do repositório foram atualizadas com sucesso.
settings.transfer_owner=Novo Dono
@@ -683,6 +695,8 @@ diff.show_unified_view=Visão unificada
diff.stats_desc=<strong> %d arquivos alterados</strong> com <strong>%d adições</strong> e <strong>%d exclusões</strong>
diff.bin=BIN
diff.view_file=Ver Arquivo
diff.file_suppressed=Diferenças do arquivo suprimidas por serem muito extensas
diff.too_many_files=Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff
release.releases=Versões
release.new_release=Nova Versão
@@ -713,6 +727,7 @@ release.deletion=Deleção de versão
release.deletion_desc=Deletar esta versão vai deletar a tag do git correspondente. Você deseja continuar?
release.deletion_success=A versão foi deletada com sucesso!
release.tag_name_already_exist=Já existiu versão com esse nome de tag.
release.tag_name_invalid=Tag name is not valid.
release.downloads=Downloads
[org]
@@ -880,6 +895,7 @@ users.edit_account=Editar Conta
users.max_repo_creation=Limite máximo de criação de repositórios
users.max_repo_creation_desc=(Use "-1" para utilizar o limite padrão)
users.is_activated=Esta conta está ativada
users.prohibit_login=Esta conta está proibida de efetuar login
users.is_admin=Esta conta tem permissões de administrador
users.allow_git_hook=Esta conta tem permissões para criar hooks do Git
users.allow_import_local=Esta conta tem permissões para importar repositórios locais
@@ -910,6 +926,7 @@ auths.enabled=Habilitado
auths.updated=Atualizado
auths.auth_type=Tipo de autenticação
auths.auth_name=Nome da autenticação
auths.security_protocol=Protocolo de segurança
auths.domain=Domínio
auths.host=Host
auths.port=Porta

31
conf/locale/locale_ru-RU.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=Имя пользователь или E-mail
password_holder=Пароль
switch_dashboard_context=Переключить контекст панели управления
my_repos=Мои репозитории
show_more_repos=Show more repositories...
collaborative_repos=Совместные репозитории
my_orgs=Мои организации
my_mirrors=Мои зеркала
@@ -148,6 +149,8 @@ forget_password=Забыли пароль?
sign_up_now=Нужен аккаунт? Зарегистрируйтесь.
confirmation_mail_sent_prompt=Новое письмо для подтверждения было направлено на <b>%s</b>, пожалуйста, проверьте ваш почтовый ящик в течение %d часов для завершения регистрации.
active_your_account=Активируйте свой аккаунт
prohibit_login=Login Prohibited
prohibit_login_desc=Your account is prohibited to login, please contact site admin.
resent_limit_prompt=Извините, вы уже запросили активацию по электронной почте недавно. Пожалуйста, подождите 3 минуты, а затем повторите попытку.
has_unconfirmed_mail=Здравствуйте, %s! У вас есть неподтвержденный адрес электронной почты (<b>%s</b>). Если вам не приходило письмо с подтверждением или нужно выслать новое письмо, нажмите на кнопку ниже.
resend_mail=Нажмите здесь, чтобы переотправить активационное письмо
@@ -157,6 +160,7 @@ reset_password=Сброс пароля
invalid_code=Извините, ваш код подтверждения истек или не является допустимым.
reset_password_helper=Нажмите здесь, чтобы сбросить свой пароль
password_too_short=Длина пароля не менее 6 символов.
non_local_account=Нелокальные аккаунты не могут изменить пароль через Gogs.
[mail]
activate_account=Пожалуйста активируйте свой аккаунт
@@ -219,8 +223,7 @@ org_still_own_repo=Данная организация все еще являе
target_branch_not_exist=Целевая ветка не существует
[user]
change_avatar=Измените ваш аватар на gravatar.com
change_custom_avatar=Измените ваш аватар в настройках
change_avatar=Изменить аватар
join_on=Присоединился
repositories=Репозитории
activity=Активность
@@ -350,6 +353,8 @@ readme_helper=Выберите шаблон для файла readme
auto_init=Инициализировать этот репозиторий выбранными файлами и шаблоном
create_repo=Создать репозиторий
default_branch=Ветка по умолчанию
mirror_prune=Очистить
mirror_prune_desc=Remove any remote-tracking references that no longer exist on the remote
mirror_interval=Интервал зеркалирования (час)
mirror_address=Адрес зеркала
mirror_address_desc=Укажите необходимые учетные данные в адрес.
@@ -407,6 +412,7 @@ file_raw=Исходник
file_history=История
file_view_raw=Посмотреть исходник
file_permalink=Постоянная ссылка
file_too_large=This file is too large to be shown
commits.commits=Коммиты
commits.search=Поиск коммитов
@@ -460,7 +466,8 @@ issues.next=Следующая страница
issues.open_title=Открыто
issues.closed_title=Закрыто
issues.num_comments=комментариев: %d
issues.commented_at=` прокомментировал <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commented_at=`commented <a href="#%s">%s</a>`
issues.delete_comment_confirm=Are you sure you want to delete this comment?
issues.no_content=Пока нет содержимого.
issues.close_issue=Закрыть
issues.close_comment_issue=Прокомментировать и закрыть
@@ -561,6 +568,10 @@ wiki.last_updated=Последнее обновление %s
settings=Настройки
settings.options=Опции
settings.collaboration=Сотрудничество
settings.collaboration.admin=Администратор
settings.collaboration.write=Запись
settings.collaboration.read=Просмотр
settings.collaboration.undefined=Не определено
settings.hooks=Автоматическое обновление
settings.githooks=Git хуки
settings.basic_settings=Основные параметры
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=Посетители будут перенапр
settings.issues_desc=Включить встроенную, легковесную систему отслеживания ошибок
settings.use_external_issue_tracker=Использовать внешнюю систему отслеживания ошибок
settings.tracker_url_format=Внешний формат ссылки системы отслеживания ошибок.
settings.tracker_issue_style=Стиль Именования Внешней Системы Учета Задач:
settings.tracker_issue_style.numeric=Numeric
settings.tracker_issue_style.alphanumeric=Alphanumeric
settings.tracker_url_format_desc=Вы можете использовать шаблон <code>{user} {repo} {index}</code> для имени пользователя, репозитория и номера задачи.
settings.pulls_desc=Включить публичные запросы на слияние
settings.danger_zone=Опасная зона
@@ -597,9 +611,7 @@ settings.delete=Удалить этот репозиторий
settings.delete_desc=Будьте внимательны! Как только вы удалите репозиторий — пути назад не будет.
settings.delete_notices_1=- Эта операция <strong>НЕ МОЖЕТ</strong> быть отменена.
settings.delete_notices_2=- Эта операция навсегда удалит всё из этого репозитория, включая данные Git, связанные с ним задачи, комментарии и права доступа для сотрудников.
settings.delete_notices_fork_1=- Если данный репозиторий является публичным, все склонированные репозитории останутся независимыми, после его удаления.
settings.delete_notices_fork_2=- Если данный репозиторий является приватным, все его ответвления будут удалены вместе с ним.
settings.delete_notices_fork_3=- Если вы хотите сохранить все ответвления после удаления репозитория, то сначала сделайте его публичным.
settings.delete_notices_fork_1=- Все отвлетвления станут независимыми после удаления.
settings.deletion_success=Репозиторий был успешно удалён!
settings.update_settings_success=Настройка репозитория обновлена успешно.
settings.transfer_owner=Новый владелец
@@ -676,13 +688,15 @@ settings.deploy_key_deletion_success=Ключ развертывания усп
diff.browse_source=Просмотр исходного кода
diff.parent=Родитель
diff.commit=Сommit
diff.data_not_available=Данные Diff не доступны.
diff.data_not_available=Данные Diff недоступны.
diff.show_diff_stats=Показать статистику Diff
diff.show_split_view=Разделённый вид
diff.show_unified_view=Единый вид
diff.stats_desc=<strong> %d измененных файлов</strong> с <strong>%d добавлено</strong> и <strong>%d удалено</strong>
diff.bin=BIN
diff.view_file=Просмотреть файл
diff.file_suppressed=File diff suppressed because it is too large
diff.too_many_files=Некоторые файлы не были показать из-за большого количества измененных файлов
release.releases=Релизы
release.new_release=Новый релиз
@@ -713,6 +727,7 @@ release.deletion=Удаление релиза
release.deletion_desc=Удаление этого релиза удалит соответствующую Git метку. Вы хотите продолжить?
release.deletion_success=Релиз был успешно удален!
release.tag_name_already_exist=Релиз с этим именем метки уже существует.
release.tag_name_invalid=Tag name is not valid.
release.downloads=Загрузки
[org]
@@ -880,6 +895,7 @@ users.edit_account=Изменение учетной записи
users.max_repo_creation=Ограничение максимального количества создаваемых репозиториев
users.max_repo_creation_desc=(Установить -1 для использования стандартного глобального значения предела)
users.is_activated=Эта учетная запись активирована
users.prohibit_login=This account is prohibited to login
users.is_admin=У этой учетной записи есть права администратора
users.allow_git_hook=Пользователь имеет право создать Git перехватчик
users.allow_import_local=Пользователь имеет право импортировать локальные репозитории
@@ -910,6 +926,7 @@ auths.enabled=Включено
auths.updated=Обновлено
auths.auth_type=Тип аутентификации
auths.auth_name=Имя аутентификации
auths.security_protocol=Протокол безопасности
auths.domain=Домен
auths.host=Хост
auths.port=Порт

1115
conf/locale/locale_tr-TR.ini Normal file

File diff suppressed because it is too large Load Diff

29
conf/locale/locale_zh-CN.ini Executable file → Normal file
View File

@@ -124,6 +124,7 @@ uname_holder=用户名或邮箱
password_holder=密码
switch_dashboard_context=切换控制面板用户
my_repos=我的仓库
show_more_repos=显示更多仓库...
collaborative_repos=参与协作的仓库
my_orgs=我的组织
my_mirrors=我的镜像
@@ -148,6 +149,8 @@ forget_password=忘记密码?
sign_up_now=还没帐户?马上注册。
confirmation_mail_sent_prompt=一封新的确认邮件已经被发送至 <b>%s</b>,请检查您的收件箱并在 %d 小时内完成确认注册操作。
active_your_account=激活您的帐户
prohibit_login=禁止登录
prohibit_login_desc=您的帐户被禁止登录,请联系网站管理员。
resent_limit_prompt=对不起,您请求发送激活邮件过于频繁,请等待 3 分钟后再试!
has_unconfirmed_mail=%s 您好,系统检测到您有一封发送至 <b>%s</b> 但未被确认的邮件。如果您未收到激活邮件,或需要重新发送,请单击下方的按钮。
resend_mail=单击此处重新发送确认邮件
@@ -157,6 +160,7 @@ reset_password=重置密码
invalid_code=对不起,您的确认代码已过期或已失效。
reset_password_helper=单击此处重置密码
password_too_short=密码长度不能少于 6 位!
non_local_account=非本地类型的帐户无法通过 Gogs 修改密码。
[mail]
activate_account=请激活您的帐户
@@ -219,8 +223,7 @@ org_still_own_repo=该组织仍然是某些仓库的拥有者,您必须先转
target_branch_not_exist=目标分支不存在。
[user]
change_avatar=到 gravatar.com 上修改您的头像
change_custom_avatar=到个人设置中修改头像
change_avatar=修改头像
join_on=加入于
repositories=仓库列表
activity=公开活动
@@ -350,6 +353,8 @@ readme_helper=请选择自述文档模板
auto_init=使用选定的文件和模板初始化仓库
create_repo=创建仓库
default_branch=默认分支
mirror_prune=修剪
mirror_prune_desc=当远程追踪的引用被删除时本地也同步删除
mirror_interval=镜像同步周期(小时)
mirror_address=镜像地址
mirror_address_desc=请在镜像地址中写入必要的用户凭据信息。
@@ -407,6 +412,7 @@ file_raw=原始文件
file_history=文件历史
file_view_raw=查看原始文件
file_permalink=永久链接
file_too_large=文件过大导致无法显示
commits.commits=次代码提交
commits.search=搜索提交历史
@@ -460,7 +466,8 @@ issues.next=下一页
issues.open_title=开启中
issues.closed_title=已关闭
issues.num_comments=%d 条评论
issues.commented_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 评论`
issues.commented_at=`评论于 <a href="#%s">%s</a>`
issues.delete_comment_confirm=您确定要删除该条评论吗?
issues.no_content=这个人很懒,什么都没留下。
issues.close_issue=关闭
issues.close_comment_issue=评论并关闭
@@ -561,6 +568,10 @@ wiki.last_updated=最后更新于 %s
settings=仓库设置
settings.options=基本设置
settings.collaboration=管理协作者
settings.collaboration.admin=管理权限
settings.collaboration.write=可写权限
settings.collaboration.read=可读权限
settings.collaboration.undefined=未定义
settings.hooks=管理 Web 钩子
settings.githooks=管理 Git 钩子
settings.basic_settings=基本设置
@@ -575,6 +586,9 @@ settings.external_wiki_url_desc=当访问者单击分页标签时,将会被重
settings.issues_desc=启用内置的轻量级工单管理系统
settings.use_external_issue_tracker=使用外部的工单管理系统
settings.tracker_url_format=外部工单管理系统的 URL 格式
settings.tracker_issue_style=外部工单管理系统命名风格:
settings.tracker_issue_style.numeric=纯数字形式
settings.tracker_issue_style.alphanumeric=英文字母数字组合形式
settings.tracker_url_format_desc=您可以使用 <code>{user} {repo} {index}</code> 分别作为用户名、仓库名和工单索引的占位符。
settings.pulls_desc=启用合并请求以接受社区贡献
settings.danger_zone=危险操作区
@@ -597,9 +611,7 @@ settings.delete=删除本仓库
settings.delete_desc=删除仓库操作不可逆转,请三思而后行。
settings.delete_notices_1=- 此操作 <strong>不可以</strong> 被回滚。
settings.delete_notices_2=- 此操作将永久删除该仓库,包括 Git 数据、 工单、 评论和协作者的操作权限。
settings.delete_notices_fork_1=- 如果该仓库为公开的,则在删除仓库后所有的派生仓库都将转换独立的仓库。
settings.delete_notices_fork_2=- 如果该仓库为私有,则会同时删除所有的派生仓库。
settings.delete_notices_fork_3=- 如果您想要保留派生仓库,请先将可见性修改为公开的后再进行删除操作。
settings.delete_notices_fork_1=- 删除完成后所有的派生仓库都将转换独立的仓库。
settings.deletion_success=仓库删除成功!
settings.update_settings_success=仓库设置更新成功!
settings.transfer_owner=新拥有者
@@ -683,6 +695,8 @@ diff.show_unified_view=合并视图
diff.stats_desc=共有 <strong> %d 个文件被更改</strong>,包括 <strong>%d 次插入</strong> 和 <strong>%d 次删除</strong>
diff.bin=二进制
diff.view_file=查看文件
diff.file_suppressed=文件差异内容过多而无法显示
diff.too_many_files=部分文件因为文件数量过多而无法显示
release.releases=版本发布
release.new_release=发布新版
@@ -713,6 +727,7 @@ release.deletion=删除版本发布操作
release.deletion_desc=删除该版本发布将会移除相应的 Git 标签。是否继续?
release.deletion_success=版本发布删除成功!
release.tag_name_already_exist=已经存在使用相同标签进行发布的版本。
release.tag_name_invalid=标签名称不是有效的名称。
release.downloads=下载附件
[org]
@@ -880,6 +895,7 @@ users.edit_account=编辑用户信息
users.max_repo_creation=最大允许创建仓库数量
users.max_repo_creation_desc=(设置为 -1 表示使用全局默认值)
users.is_activated=该用户已被激活
users.prohibit_login=该帐户被禁止登录
users.is_admin=该用户具有管理员权限
users.allow_git_hook=该用户具有创建 Git 钩子的权限
users.allow_import_local=该用户具有导入本地仓库的权限
@@ -910,6 +926,7 @@ auths.enabled=已启用
auths.updated=最后更新时间
auths.auth_type=认证类型
auths.auth_name=认证名称
auths.security_protocol=安全协议
auths.domain=域名
auths.host=主机地址
auths.port=主机端口

91
conf/locale/locale_zh-HK.ini Executable file → Normal file
View File

@@ -38,7 +38,7 @@ settings=設定
your_profile=個人資料
your_settings=用戶設定
activities=Activities
activities=活動
pull_requests=合併請求
issues=問題
@@ -58,7 +58,7 @@ db_name=資料庫名稱
db_helper=如果您使用 MySQL請使用 INNODB 引擎以及 utf8_general_ci 字符集。
ssl_mode=SSL 模式
path=數據庫文件路徑
sqlite_helper=The file path of SQLite3 or TiDB database. <br>Please use absolute path when you start as service.
sqlite_helper=SQLite3 TiDB 資料庫檔案路徑。<br>作為以服務執行時,請使用絕對路徑。
err_empty_db_path=SQLite3 或 TiDB 的數據庫路徑不能為空。
err_invalid_tidb_name=TiDB 數據庫名稱不允許包含字符 "." 或 "-" 。
no_admin_and_disable_registration=您不能夠在未創建管理員用戶的情況下禁止註冊。
@@ -79,8 +79,8 @@ http_port=HTTP 端口號
http_port_helper=應用監聽的端口號
app_url=應用程式網址
app_url_helper=該設置影響 HTTP/HTTPS 複製地址和一些郵箱中的連結。
log_root_path=Log Path
log_root_path_helper=Directory to write log files to.
log_root_path=日誌路徑
log_root_path_helper=寫入日誌檔目錄
optional_title=可選設置
email_title=電子郵件服務設定
@@ -117,13 +117,14 @@ run_user_not_match=執行系統用戶非當前用戶:%s -> %s
save_config_failed=應用配置保存失敗:%v
invalid_admin_setting=管理員帳戶設置不正確:%v
install_success=您好!我們很高興您選擇使用 Gogs祝您使用愉快代碼從此無 BUG
invalid_log_root_path=Log root path is invalid: %v
invalid_log_root_path=日誌根目錄無效: %v
[home]
uname_holder=用戶名或郵箱
password_holder=密碼
switch_dashboard_context=切換控制面版用戶
my_repos=我的倉庫
show_more_repos=Show more repositories...
collaborative_repos=參與協作的倉庫
my_orgs=我的組織
my_mirrors=我的鏡像
@@ -133,8 +134,8 @@ issues.in_your_repos=屬於該用戶倉庫的
[explore]
repos=探索倉庫
users=Users
search=Search
users=用戶
search=搜索
[auth]
create_new_account=創建帳戶
@@ -148,6 +149,8 @@ forget_password=忘記密碼?
sign_up_now=還沒帳戶?馬上註冊。
confirmation_mail_sent_prompt=一封新的確認郵件已經被發送至 <b>%s</b>,請檢查您的收件箱並在 %d 小時內完成確認註冊操作。
active_your_account=激活您的帳戶
prohibit_login=Login Prohibited
prohibit_login_desc=Your account is prohibited to login, please contact site admin.
resent_limit_prompt=對不起,您請求發送激活郵件過於頻繁,請等待 3 分鐘後再試!
has_unconfirmed_mail=%s 您好,您有一封發送至( <b>%s</b>) 但未被確認的郵件。如果您未收到激活郵件,或需要重新發送,請單擊下方的按鈕。
resend_mail=單擊此處重新發送確認郵件
@@ -157,6 +160,7 @@ reset_password=重置密碼
invalid_code=對不起,您的確認代碼已過期或已失效。
reset_password_helper=單擊此處重置密碼
password_too_short=密碼長度不能少於 6 位!
non_local_account=Non-local accounts cannot change passwords through Gogs.
[mail]
activate_account=請激活您的帳戶
@@ -219,8 +223,7 @@ org_still_own_repo=該組織仍然是某些倉庫的擁有者,您必須先轉
target_branch_not_exist=目標分支不存在
[user]
change_avatar=到 gravatar.com 上修改您的頭像
change_custom_avatar=到個人設置中修改頭像
change_avatar=Change your avatar
join_on=加入於
repositories=倉庫列表
activity=公開活動
@@ -259,7 +262,7 @@ cancel=取消操作
enable_custom_avatar=啟動自定義頭像
choose_new_avatar=選擇新的頭像
update_avatar=更新頭像設置
delete_current_avatar=Delete Current Avatar
delete_current_avatar=刪除當前頭像
uploaded_avatar_not_a_image=上傳的文件不是一張圖片!
update_avatar_success=您的頭像設置更新成功!
@@ -350,6 +353,8 @@ readme_helper=請選擇readme模板
auto_init=使用選定的文件和模板初始化倉庫
create_repo=創建倉庫
default_branch=默認分支
mirror_prune=Prune
mirror_prune_desc=Remove any remote-tracking references that no longer exist on the remote
mirror_interval=鏡像同步周期(小時)
mirror_address=鏡像地址
mirror_address_desc=請在位址中包括必要的使用者憑據。
@@ -382,7 +387,7 @@ unwatch=取消關注
watch=關註
unstar=取消讚好
star=讚好
fork=派生
fork=複刻
no_desc=暫無描述
quick_guide=快速幫助
@@ -407,6 +412,7 @@ file_raw=原始文件
file_history=文件歷史
file_view_raw=查看原始文件
file_permalink=永久連結
file_too_large=This file is too large to be shown
commits.commits=次代碼提交
commits.search=搜索提交歷史
@@ -460,7 +466,8 @@ issues.next=下一頁
issues.open_title=開啟中
issues.closed_title=已關閉
issues.num_comments=%d 條評論
issues.commented_at=`於 <a id="%[1]s" href="#%[1]s">%[2]s</a> 評論`
issues.commented_at=`commented <a href="#%s">%s</a>`
issues.delete_comment_confirm=Are you sure you want to delete this comment?
issues.no_content=尚未有任何內容
issues.close_issue=關閉
issues.close_comment_issue=關閉及評論
@@ -471,7 +478,7 @@ issues.closed_at=`於 <a id="%[1]s" href="#%[1]s">%[2]s</a> 關閉`
issues.reopened_at=`於 <a id="%[1]s" href="#%[1]s">%[2]s</a> 重新開啟`
issues.commit_ref_at=`在代碼提交 <a id="%[1]s" href="#%[1]s">%[2]s</a> 中引用了該問題`
issues.poster=發佈者
issues.collaborator=Collaborator
issues.collaborator=協同者
issues.owner=所有者
issues.sign_up_for_free=免費註冊
issues.sign_in_require_desc=及加入到對話當中。如果您已經註冊,可以直接 <a href="%s">登錄及評論</a>
@@ -488,7 +495,7 @@ issues.label_modify=修改標籤
issues.label_deletion=刪除標籤
issues.label_deletion_desc=刪除該標籤將會移除所有問題中相關的訊息。是否繼續?
issues.label_deletion_success=標籤刪除成功!
issues.num_participants=%d Participants
issues.num_participants=%d 參與者
pulls.new=創建合併請求
pulls.compare_changes=對比文件變化
@@ -552,8 +559,8 @@ wiki.save_page=保存頁面
wiki.last_commit_info=%s 於 %s 修改了此頁面
wiki.edit_page_button=修改
wiki.new_page_button=新的頁面
wiki.delete_page_button=Delete Page
wiki.delete_page_notice_1=This will delete the page <code>"%s"</code>. Please be certain.
wiki.delete_page_button=刪除頁面
wiki.delete_page_notice_1=這將刪除頁面 <code>"%s"</code>。請三思而後行。
wiki.page_already_exists=相同名稱的 Wiki 頁面已經存在。
wiki.pages=所有頁面
wiki.last_updated=最後更新於 %s
@@ -561,6 +568,10 @@ wiki.last_updated=最後更新於 %s
settings=倉庫設置
settings.options=基本設置
settings.collaboration=管理協作者
settings.collaboration.admin=Admin
settings.collaboration.write=Write
settings.collaboration.read=Read
settings.collaboration.undefined=Undefined
settings.hooks=管理 Web 鉤子
settings.githooks=管理 Git 鉤子
settings.basic_settings=基本設置
@@ -575,12 +586,15 @@ settings.external_wiki_url_desc=當分頁上按一下,訪客將會重新導到
settings.issues_desc=啟用內置的輕量級問題管理系統
settings.use_external_issue_tracker=使用外部的問題管理系統
settings.tracker_url_format=外部問題管理系統的 URL 格式
settings.tracker_issue_style=External Issue Tracker Naming Style:
settings.tracker_issue_style.numeric=Numeric
settings.tracker_issue_style.alphanumeric=Alphanumeric
settings.tracker_url_format_desc=您可以使用 <code>{user} {repo} {index}</code> 分別作為用戶名、倉庫名和問題索引的占位符。
settings.pulls_desc=啟用合併請求以接受社區貢獻
settings.danger_zone=危險操作區
settings.new_owner_has_same_repo=新的倉庫擁有者已經存在同名倉庫!
settings.convert=Convert To Regular Repository
settings.convert_desc=You can convert this mirror to a regular repository. This cannot be reversed.
settings.convert=轉換為正規倉庫
settings.convert_desc=您可以將此鏡像轉成正規倉庫。此動做不可逆。
settings.convert_notices_1=- This operation will convert this repository mirror into a regular repository and cannot be undone.
settings.convert_confirm=Confirm Conversion
settings.convert_succeed=Repository has been converted to regular type successfully.
@@ -589,17 +603,15 @@ settings.transfer_desc=您可以將倉庫轉移至您擁有管理員權限的帳
settings.transfer_notices_1=- 如果您將倉庫轉移給個人用戶,您將會丟失操作權限。
settings.transfer_notices_2=- 如果您將倉庫轉移給您是所有者的組織,您的操作權限將被保留。
settings.transfer_form_title=請輸入以下信息以確認您的操作:
settings.wiki_delete=Erase Wiki Data
settings.wiki_delete=刪除 Wiki 資料
settings.wiki_delete_desc=Once you erase wiki data there is no going back. Please be certain.
settings.wiki_delete_notices_1=- This will delete and disable the wiki for %s
settings.wiki_delete_notices_1=- 將刪除和停用 %s 的 wiki
settings.wiki_deletion_success=Repository wiki data have been erased successfully.
settings.delete=刪除本倉庫
settings.delete_desc=刪除倉庫操作不可逆轉,請三思而後行。
settings.delete_notices_1=- 此操作 <strong>不可以</strong> 被回滾。
settings.delete_notices_2=- 此操作將永久刪除該倉庫,包括 Git 數據、 問題、 評論和協作者的操作權限。
settings.delete_notices_fork_1=- 如果該倉庫為公開的,則在刪除倉庫後所有的派生倉庫都將轉換成獨立的倉庫。
settings.delete_notices_fork_2=- 如果該倉庫為私有,則會同時刪除所有的派生倉庫。
settings.delete_notices_fork_3=- 如果您想要保留派生倉庫,請先將可見性修改為公開的後再進行刪除操作。
settings.delete_notices_fork_1=- All forks will become independent after deletion.
settings.deletion_success=倉庫刪除成功!
settings.update_settings_success=倉庫設置更新成功!
settings.transfer_owner=新擁有者
@@ -608,7 +620,7 @@ settings.transfer_succeed=倉庫所有權轉移成功!
settings.confirm_delete=確認刪除倉庫
settings.add_collaborator=增加新的協作者
settings.add_collaborator_success=成功添加新的協作者!
settings.delete_collaborator=Delete
settings.delete_collaborator=刪除
settings.collaborator_deletion=Collaborator Deletion
settings.collaborator_deletion_desc=This user will no longer have collaboration access to this repository after deletion. Do you want to continue?
settings.remove_collaborator_success=被操作的協作者已經被收回權限!
@@ -683,6 +695,8 @@ diff.show_unified_view=統一視圖
diff.stats_desc=共有 <strong> %d 個文件被更改</strong>,包括 <strong>%d 次插入</strong> 和 <strong>%d 次删除</strong>
diff.bin=二進制
diff.view_file=查看文件
diff.file_suppressed=File diff suppressed because it is too large
diff.too_many_files=Some files were not shown because too many files changed in this diff
release.releases=版本發佈
release.new_release=發佈新版本
@@ -713,6 +727,7 @@ release.deletion=刪除版本發布操作
release.deletion_desc=刪除該版本發布將會移除相應的 Git 標籤。是否繼續?
release.deletion_success=版本發布刪除成功!
release.tag_name_already_exist=已經存在使用相同標籤的發佈版本。
release.tag_name_invalid=Tag name is not valid.
release.downloads=下載附件
[org]
@@ -880,6 +895,7 @@ users.edit_account=編輯用戶信息
users.max_repo_creation=最大儲存庫新增限制
users.max_repo_creation_desc=(設定 -1 使用全域預設限制)
users.is_activated=該用戶已被激活
users.prohibit_login=This account is prohibited to login
users.is_admin=該用戶具有管理員權限
users.allow_git_hook=該帳戶具有創建 Git 鉤子的權限
users.allow_import_local=該用戶具有導入本地倉庫的權限
@@ -910,6 +926,7 @@ auths.enabled=已啟用
auths.updated=最後更新時間
auths.auth_type=認證類型
auths.auth_name=認證名稱
auths.security_protocol=Security Protocol
auths.domain=域名
auths.host=主機地址
auths.port=主機端口
@@ -963,17 +980,17 @@ config.log_file_root_path=日志文件根目錄
config.script_type=腳本類型
config.reverse_auth_user=反向代理認證
config.ssh_config=SSH Configuration
config.ssh_enabled=Enabled
config.ssh_start_builtin_server=Start Builtin Server
config.ssh_config=SSH 配置
config.ssh_enabled=已啟用
config.ssh_start_builtin_server=啟動內建伺服器
config.ssh_domain=Domain
config.ssh_port=Port
config.ssh_listen_port=Listen Port
config.ssh_root_path=Root Path
config.ssh_key_test_path=Key Test Path
config.ssh_keygen_path=Keygen ('ssh-keygen') Path
config.ssh_minimum_key_size_check=Minimum Key Size Check
config.ssh_minimum_key_sizes=Minimum Key Sizes
config.ssh_port=
config.ssh_listen_port=監聽埠
config.ssh_root_path=根路徑
config.ssh_key_test_path=金鑰測試路徑
config.ssh_keygen_path=金鑰產生 (' ssh-keygen ') 路徑
config.ssh_minimum_key_size_check=金鑰最小大小檢查
config.ssh_minimum_key_sizes=金鑰最小大小
config.db_config=數據庫配置
config.db_type=數據庫類型
@@ -1004,9 +1021,9 @@ config.mailer_disable_helo=禁用 HELO 操作
config.mailer_name=發送者名稱
config.mailer_host=郵件主機地址
config.mailer_user=發送者帳號
config.send_test_mail=Send Test Email
config.test_mail_failed=Fail to send test email to '%s': %v
config.test_mail_sent=Test email has been sent to '%s'.
config.send_test_mail=發送測試郵件
config.test_mail_failed=無法向 '%s' 發送測試郵件: %v
config.test_mail_sent=測試電子郵件已發送到 '%s'
config.oauth_config=社交帳號配置
config.oauth_enabled=啟用服務
config.cache_config=Cache 配置

1115
conf/locale/locale_zh-TW.ini Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# Docker for Gogs
Visit [Docker Hub](https://hub.docker.com/r/gogs/gogs/) see all available tags.
Visit [Docker Hub](https://hub.docker.com/r/gogs/) see all available images and tags.
## Usage
@@ -43,9 +43,11 @@ If you're more comfortable with mounting data to a data container, the commands
```
# Create data container
docker run --name=gogs-data --entrypoint /bin/true gogs/gogs
# Use `docker run` for the first time.
docker run --name=gogs --volumes-from gogs-data -p 10022:22 -p 10080:3000 gogs/gogs
```
#### Using Docker 1.9 Volume command
```
@@ -71,9 +73,27 @@ Most of settings are obvious and easy to understand, but there are some settings
Full documentation of application settings can be found [here](http://gogs.io/docs/advanced/configuration_cheat_sheet.html).
### Crond
### Container options
Please set environment variable `RUN_CROND` to be `true` or `1` in order to start `crond` inside the container.
This container have some options available via environment variables, these options are opt-in features that can help the administration of this container:
- **SOCAT_LINK**:
- <u>Possible value:</u>
`true`, `false`, `1`, `0`
- <u>Default:</u>
`true`
- <u>Action:</u>
Bind linked docker container to localhost socket using socat.
Any exported port from a linked container will be binded to the matching port on localhost.
- <u>Disclaimer:</u>
As this option rely on the environment variable created by docker when a container is linked, this option should be deactivated in managed environment such as Rancher or Kubernetes (set to `0` or `false`)
- **RUN_CROND**:
- <u>Possible value:</u>
`true`, `false`, `1`, `0`
- <u>Default:</u>
`false`
- <u>Action:</u>
Request crond to be run inside the container. Its default configuration will periodically run all scripts from `/etc/periodic/${period}` but custom crontabs can be added to `/var/spool/cron/crontabs/`.
## Upgrade

View File

@@ -5,19 +5,28 @@ set -e
# Set temp environment vars
export GOPATH=/tmp/go
export PATH=${PATH}:${GOPATH}/bin
export GO15VENDOREXPERIMENT=1
# Install build deps
apk --no-cache --no-progress add --virtual build-deps linux-pam-dev go gcc musl-dev
apk --no-cache --no-progress add --virtual build-deps build-base linux-pam-dev go
# Init go environment to build Gogs
# Install glide
git clone -b 0.10.2 https://github.com/Masterminds/glide ${GOPATH}/src/github.com/Masterminds/glide
cd ${GOPATH}/src/github.com/Masterminds/glide
make build
go install
# Build Gogs
mkdir -p ${GOPATH}/src/github.com/gogits/
ln -s /app/gogs/ ${GOPATH}/src/github.com/gogits/gogs
cd ${GOPATH}/src/github.com/gogits/gogs
go get -v -tags "sqlite cert pam"
go build -tags "sqlite cert pam"
glide install
make build TAGS="sqlite cert pam"
# Cleanup GOPATH
rm -r $GOPATH
# Cleanup GOPATH & vendoring dir
rm -r $GOPATH /app/gogs/vendor
# Remove build deps
apk --no-progress del build-deps

64
glide.lock generated
View File

@@ -1,14 +1,14 @@
hash: f2fa73b9a379e1fa12f2b48fb0b9942a545b4518a2d71cbd956ee81093347773
updated: 2016-03-19T14:44:26.835671547-04:00
hash: 1d5fcf2a90f7621ecbc0b1abed548e11d13bda3fea49b4326c829a523268e5cf
updated: 2016-06-12T17:35:14.27036884+08:00
imports:
- name: github.com/bradfitz/gomemcache
version: fb1f79c6b65acda83063cbc69f6bba1522558bfc
subpackages:
- memcache
- name: github.com/codegangsta/cli
version: aca5b047ed14d17224157c3434ea93bf6cdaadee
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
- name: github.com/go-macaron/binding
version: a68f34212fe257219981e43adfe4c96ab48f42cd
version: 9440f336b443056c90d7d448a0a55ad8c7599880
- name: github.com/go-macaron/cache
version: 56173531277692bc2925924d51fda1cd0a6b8178
subpackages:
@@ -21,7 +21,7 @@ imports:
- name: github.com/go-macaron/gzip
version: cad1c6580a07c56f5f6bc52d66002a05985c5854
- name: github.com/go-macaron/i18n
version: d2d3329f13b52314f3292c4cecb601fad13f02c8
version: ef57533c3b0fc2d8581deda14937e52f11a203ab
- name: github.com/go-macaron/inject
version: c5ab7bf3a307593cd44cb272d1a5beea473dd072
- name: github.com/go-macaron/session
@@ -31,25 +31,27 @@ imports:
- name: github.com/go-macaron/toolbox
version: 82b511550b0aefc36b3a28062ad3a52e812bee38
- name: github.com/go-sql-driver/mysql
version: 66312f7fe2678aa0f5ec770f96702f4c4ec5aa8e
version: 0b58b37b664c21f3010e836f1b931e1d0b0b0685
- name: github.com/go-xorm/core
version: 502158401cde814951eae62f064d9e5ff39e13ce
version: bc1b7f81f0e369289078424064634a5ee7d21051
- name: github.com/go-xorm/xorm
version: 769f6b3ae663248e8f1b1d8fecbe1eb26ac77ac7
version: b8b1711cde4d9c68da95cd0ff8de0de0cb491df3
- name: github.com/gogits/chardet
version: 2404f777256163ea3eadb273dada5dcb037993c0
- name: github.com/gogits/cron
version: 3abc0f88f2062336bcc41b43a4febbd847a390ce
version: 96040e4fab17baa0391ce571694e4ccb5fce5dac
- name: github.com/gogits/git-module
version: 76e8cce6c7ef3ba1cf75752261c721ebf14cd129
version: 53bcb7352ff838610c537c9b589ca79bca92c661
- name: github.com/gogits/go-gogs-client
version: 788ec59749df076b98e208909b44fdef02779deb
version: d725743594dfcd8eea25024f8456c9c103dadb1a
- name: github.com/issue9/identicon
version: f8c0d2ce04db79c663b1da33d3a9f62be753ee88
version: d36b54562f4cf70c83653e13dc95c220c79ef521
- name: github.com/jaytaylor/html2text
version: 52d9b785554a1918cb09909b89a1509a98b853fd
- name: github.com/kardianos/minwinsvc
version: cad6b2b879b0970e4245a20ebf1a81a756e2bb70
- name: github.com/klauspost/compress
version: 006acde2c5d283d2f8b8aa03d8f0cd2891c680cf
version: 14eb9c4951195779ecfbec34431a976de7335b0a
subpackages:
- gzip
- flate
@@ -58,23 +60,23 @@ imports:
- name: github.com/klauspost/crc32
version: 19b0b332c9e4516a6370a0456e6182c3b5036720
- name: github.com/lib/pq
version: 165a3529e799da61ab10faed1fabff3662d6193f
version: 4dd446efc17690bc53e154025146f73203b18309
subpackages:
- oid
- name: github.com/mattn/go-sqlite3
version: 76e335f60bbcee20755df9864f0153af1a80ad2d
version: e118d4451349065b8e7ce0f0af32e033995363f8
- name: github.com/mcuadros/go-version
version: d52711f8d6bea8dc01efafdb68ad95a4e2606630
- name: github.com/microcosm-cc/bluemonday
version: 4ac6f27528d0a3f2a59e0b0a6f6b3ff0bb89fe20
version: 9dc199233bf72cc1aad9b61f73daf2f0075b9ee4
- name: github.com/msteinert/pam
version: 02ccfbfaf0cc627aa3aec8ef7ed5cfeec5b43f63
- name: github.com/nfnt/resize
version: 4d93a29130b1b6aba503e2aa8b50f516213ea80e
version: 891127d8d1b52734debe1b3c3d7e747502b6c366
- name: github.com/russross/blackfriday
version: b43df972fb5fdf3af8d2e90f38a69d374fe26dd0
version: 93622da34e54fb6529bfb7c57e710f37a8d9cbd8
- name: github.com/satori/go.uuid
version: e673fdd4dea8a7334adbbe7f57b7e4b00bdc5502
version: 0aa62d5ddceb50dbcb909d790b5345affd3669b6
- name: github.com/sergi/go-diff
version: ec7fdbb58eb3e300c8595ad5ac74a5aa50019cc7
subpackages:
@@ -88,32 +90,36 @@ imports:
- name: github.com/Unknwon/com
version: 28b053d5a2923b87ce8c5a08f3af779894a72758
- name: github.com/Unknwon/i18n
version: 3b48b6662051bed72d36efa3c1e897bdf96b2e37
version: 39d6f2727e0698b1021ceb6a77c1801aa92e7d5d
- name: github.com/Unknwon/paginater
version: 7748a72e01415173a27d79866b984328e7b0c12b
- name: golang.org/x/crypto
version: c197bcf24cde29d3f73c7b4ac6fd41f4384e8af6
version: bc89c496413265e715159bdc8478ee9a92fdc265
subpackages:
- ssh
- curve25519
- ed25519
- ed25519/internal/edwards25519
- name: golang.org/x/net
version: 35b06af0720201bc2f326773a80767387544f8c4
version: 57bfaa875b96fb91b4766077f34470528d4b03e9
subpackages:
- html
- html/charset
- html/atom
- name: golang.org/x/sys
version: 9d4e42a20653790449273b3c85e67d6d8bae6e2e
version: a646d33e2ee3172a661fc09bca23bb4889a41bc8
subpackages:
- windows/svc
- windows
- name: golang.org/x/text
version: 1b466db55e0ba5d56ef5315c728216b42f796491
version: 2910a502d2bf9e43193af9d68ca516529614eed3
subpackages:
- transform
- language
- encoding
- encoding/charmap
- encoding/htmlindex
- internal/tag
- encoding/internal/identifier
- encoding/internal
- encoding/japanese
@@ -121,10 +127,8 @@ imports:
- encoding/simplifiedchinese
- encoding/traditionalchinese
- encoding/unicode
- language
- internal/utf8internal
- runes
- internal/tag
- name: gopkg.in/alexcesaro/quotedprintable.v3
version: 2caba252f4dc53eaf6b553000885530023f54623
- name: gopkg.in/asn1-ber.v1
@@ -132,13 +136,13 @@ imports:
- name: gopkg.in/bufio.v1
version: 567b2bfa514e796916c4747494d6ff5132a1dfce
- name: gopkg.in/gomail.v2
version: 060a5f4e98dbf37408cf0c745681e4001d877827
version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1
- name: gopkg.in/ini.v1
version: 776aa739ce9373377cd16f526cdf06cb4c89b40f
version: cf53f9204df4fbdd7ec4164b57fa6184ba168292
- name: gopkg.in/ldap.v2
version: 07a7330929b9ee80495c88a4439657d89c7dbd87
version: 537128fee7cca108d8ce74e4309fdfcdd9c7f496
- name: gopkg.in/macaron.v1
version: 94a5ef7105036242f79e5e07a8eb8651d06c8533
version: 2133042f8d1022b8253e4e23f7940467941409ce
- name: gopkg.in/redis.v2
version: e6179049628164864e6e84e973cfb56335748dea
devImports: []

View File

@@ -50,6 +50,7 @@ import:
- package: golang.org/x/text
subpackages:
- transform
- language
- package: gopkg.in/gomail.v2
- package: gopkg.in/ini.v1
- package: gopkg.in/ldap.v2

View File

@@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting"
)
const APP_VER = "0.9.13.0318"
const APP_VER = "0.9.60.0803"
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())

View File

@@ -20,6 +20,33 @@ const (
ACCESS_MODE_OWNER // 4
)
func (mode AccessMode) String() string {
switch mode {
case ACCESS_MODE_READ:
return "read"
case ACCESS_MODE_WRITE:
return "write"
case ACCESS_MODE_ADMIN:
return "admin"
case ACCESS_MODE_OWNER:
return "owner"
default:
return "none"
}
}
// ParseAccessMode returns corresponding access mode to given permission string.
func ParseAccessMode(permission string) AccessMode {
switch permission {
case "write":
return ACCESS_MODE_WRITE
case "admin":
return ACCESS_MODE_ADMIN
default:
return ACCESS_MODE_READ
}
}
// Access represents the highest access level of a user to the repository. The only access type
// that is not in this table is the real owner of a repository. In case of an organization
// repository, the members of the owners team are in this table.
@@ -40,11 +67,11 @@ func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
return mode, nil
}
if u.Id == repo.OwnerID {
if u.ID == repo.OwnerID {
return ACCESS_MODE_OWNER, nil
}
a := &Access{UserID: u.Id, RepoID: repo.ID}
a := &Access{UserID: u.ID, RepoID: repo.ID}
if has, err := e.Get(a); !has || err != nil {
return mode, err
}
@@ -70,7 +97,7 @@ func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
accesses := make([]*Access, 0, 10)
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
if err := x.Find(&accesses, &Access{UserID: u.ID}); err != nil {
return nil, err
}
@@ -86,7 +113,7 @@ func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
}
if err = repo.GetOwner(); err != nil {
return nil, err
} else if repo.OwnerID == u.Id {
} else if repo.OwnerID == u.ID {
continue
}
repos[repo] = access.Mode
@@ -94,23 +121,17 @@ func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
return repos, nil
}
// GetAccessibleRepositories finds all repositories where a user has access but does not own.
func (u *User) GetAccessibleRepositories() ([]*Repository, error) {
accesses := make([]*Access, 0, 10)
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
return nil, err
// GetAccessibleRepositories finds repositories which the user has access but does not own.
// If limit is smaller than 1 means returns all found results.
func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) {
sess := x.Where("owner_id !=? ", user.ID).Desc("updated_unix")
if limit > 0 {
sess.Limit(limit)
repos = make([]*Repository, 0, limit)
} else {
repos = make([]*Repository, 0, 10)
}
if len(accesses) == 0 {
return []*Repository{}, nil
}
repoIDs := make([]int64, 0, len(accesses))
for _, access := range accesses {
repoIDs = append(repoIDs, access.RepoID)
}
repos := make([]*Repository, 0, len(repoIDs))
return repos, x.Where("owner_id != ?", u.Id).In("id", repoIDs).Desc("updated_unix").Find(&repos)
return repos, sess.Join("INNER", "access", "access.user_id = ? AND access.repo_id = repository.id", user.ID).Find(&repos)
}
func maxAccessMode(modes ...AccessMode) AccessMode {
@@ -200,7 +221,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err
return fmt.Errorf("getMembers '%d': %v", t.ID, err)
}
for _, m := range t.Members {
accessMap[m.Id] = maxAccessMode(accessMap[m.Id], t.Authorize)
accessMap[m.ID] = maxAccessMode(accessMap[m.ID], t.Authorize)
}
}

View File

@@ -89,7 +89,7 @@ type Action struct {
}
func (a *Action) BeforeInsert() {
a.CreatedUnix = time.Now().UTC().Unix()
a.CreatedUnix = time.Now().Unix()
}
func (a *Action) AfterSet(colName string, _ xorm.Cell) {
@@ -184,7 +184,7 @@ func (a *Action) GetIssueContent() string {
func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
if err = notifyWatchers(e, &Action{
ActUserID: u.Id,
ActUserID: u.ID,
ActUserName: u.Name,
ActEmail: u.Email,
OpType: ACTION_CREATE_REPO,
@@ -193,7 +193,7 @@ func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
}); err != nil {
return fmt.Errorf("notify watchers '%d/%d': %v", u.Id, repo.ID, err)
return fmt.Errorf("notify watchers '%d/%d': %v", u.ID, repo.ID, err)
}
log.Trace("action.newRepoAction: %s/%s", u.Name, repo.Name)
@@ -207,7 +207,7 @@ func NewRepoAction(u *User, repo *Repository) (err error) {
func renameRepoAction(e Engine, actUser *User, oldRepoName string, repo *Repository) (err error) {
if err = notifyWatchers(e, &Action{
ActUserID: actUser.Id,
ActUserID: actUser.ID,
ActUserName: actUser.Name,
ActEmail: actUser.Email,
OpType: ACTION_RENAME_REPO,
@@ -470,8 +470,8 @@ func CommitRepoAction(
}
}
if len(commit.Commits) > setting.FeedMaxCommitNum {
commit.Commits = commit.Commits[:setting.FeedMaxCommitNum]
if len(commit.Commits) > setting.UI.FeedMaxCommitNum {
commit.Commits = commit.Commits[:setting.UI.FeedMaxCommitNum]
}
bs, err := json.Marshal(commit)
@@ -482,14 +482,14 @@ func CommitRepoAction(
refName := git.RefEndName(refFullName)
if err = NotifyWatchers(&Action{
ActUserID: u.Id,
ActUserID: u.ID,
ActUserName: userName,
ActEmail: actEmail,
OpType: opType,
Content: string(bs),
RepoID: repo.ID,
RepoUserName: repoUserName,
RepoName: repoName,
RepoName: repo.Name,
RefName: refName,
IsPrivate: repo.IsPrivate,
}); err != nil {
@@ -506,7 +506,7 @@ func CommitRepoAction(
}
payloadSender := &api.PayloadUser{
UserName: pusher.Name,
ID: pusher.Id,
ID: pusher.ID,
AvatarUrl: pusher.AvatarLink(),
}
@@ -517,7 +517,7 @@ func CommitRepoAction(
Before: oldCommitID,
After: newCommitID,
CompareUrl: setting.AppUrl + commit.CompareUrl,
Commits: commit.ToApiPayloadCommits(repo.FullRepoLink()),
Commits: commit.ToApiPayloadCommits(repo.FullLink()),
Repo: payloadRepo,
Pusher: &api.PayloadAuthor{
Name: pusher_name,
@@ -553,7 +553,7 @@ func CommitRepoAction(
func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
if err = notifyWatchers(e, &Action{
ActUserID: actUser.Id,
ActUserID: actUser.ID,
ActUserName: actUser.Name,
ActEmail: actUser.Email,
OpType: ACTION_TRANSFER_REPO,
@@ -561,14 +561,14 @@ func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repos
RepoUserName: newOwner.Name,
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
Content: path.Join(oldOwner.LowerName, repo.LowerName),
Content: path.Join(oldOwner.Name, repo.Name),
}); err != nil {
return fmt.Errorf("notify watchers '%d/%d': %v", actUser.Id, repo.ID, err)
return fmt.Errorf("notify watchers '%d/%d': %v", actUser.ID, repo.ID, err)
}
// Remove watch for organization.
if repo.Owner.IsOrganization() {
if err = watchRepo(e, repo.Owner.Id, repo.ID, false); err != nil {
if err = watchRepo(e, repo.Owner.ID, repo.ID, false); err != nil {
return fmt.Errorf("watch repository: %v", err)
}
}
@@ -584,7 +584,7 @@ func TransferRepoAction(actUser, oldOwner, newOwner *User, repo *Repository) err
func mergePullRequestAction(e Engine, actUser *User, repo *Repository, pull *Issue) error {
return notifyWatchers(e, &Action{
ActUserID: actUser.Id,
ActUserID: actUser.ID,
ActUserName: actUser.Name,
ActEmail: actUser.Email,
OpType: ACTION_MERGE_PULL_REQUEST,
@@ -602,21 +602,22 @@ func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error
}
// GetFeeds returns action list of given user in given context.
// userID is the user who's requesting, ctxUserID is the user/org that is requested.
// userID can be -1, if isProfile is true or in order to skip the permission check.
func GetFeeds(ctxUserID, userID, offset int64, isProfile bool) ([]*Action, error) {
// actorID is the user who's requesting, ctxUserID is the user/org that is requested.
// actorID can be -1 when isProfile is true or to skip the permission check.
func GetFeeds(ctxUser *User, actorID, offset int64, isProfile bool) ([]*Action, error) {
actions := make([]*Action, 0, 20)
sess := x.Limit(20, int(offset)).Desc("id").Where("user_id=?", ctxUserID)
sess := x.Limit(20, int(offset)).Desc("id").Where("user_id = ?", ctxUser.ID)
if isProfile {
sess.And("is_private=?", false).And("act_user_id=?", ctxUserID)
} else if ctxUserID != -1 {
ctxUser := &User{Id: ctxUserID}
if err := ctxUser.GetUserRepositories(userID); err != nil {
return nil, err
sess.And("is_private = ?", false).And("act_user_id = ?", ctxUser.ID)
} else if actorID != -1 && ctxUser.IsOrganization() {
// FIXME: only need to get IDs here, not all fields of repository.
repos, _, err := ctxUser.GetUserRepositories(actorID, 1, ctxUser.NumRepos)
if err != nil {
return nil, fmt.Errorf("GetUserRepositories: %v", err)
}
var repoIDs []int64
for _, repo := range ctxUser.Repos {
for _, repo := range repos {
repoIDs = append(repoIDs, repo.ID)
}

View File

@@ -35,7 +35,7 @@ type Notice struct {
}
func (n *Notice) BeforeInsert() {
n.CreatedUnix = time.Now().UTC().Unix()
n.CreatedUnix = time.Now().Unix()
}
func (n *Notice) AfterSet(colName string, _ xorm.Cell) {
@@ -52,6 +52,11 @@ func (n *Notice) TrStr() string {
// CreateNotice creates new system notice.
func CreateNotice(tp NoticeType, desc string) error {
// prevent panic if database connection is not available at this point
if x == nil {
return fmt.Errorf("Could not save notice due database connection not being available: %d %s", tp, desc)
}
n := &Notice{
Type: tp,
Description: desc,
@@ -69,7 +74,12 @@ func CreateRepositoryNotice(desc string) error {
// creates a system notice when error occurs.
func RemoveAllWithNotice(title, path string) {
var err error
// workaround for Go not being able to remove read-only files/folders: https://github.com/golang/go/issues/9606
// this bug should be fixed on Go 1.7, so the workaround should be removed when Gogs don't support Go 1.6 anymore:
// https://github.com/golang/go/commit/2ffb3e5d905b5622204d199128dec06cefd57790
if setting.IsWindows {
// converting "/" to "\" in path on Windows
path = strings.Replace(path, "/", "\\", -1)
err = exec.Command("cmd", "/C", "rmdir", "/S", "/Q", path).Run()
} else {
err = os.RemoveAll(path)

View File

@@ -280,6 +280,18 @@ func (err ErrAccessTokenNotExist) Error() string {
return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
}
type ErrAccessTokenEmpty struct {
}
func IsErrAccessTokenEmpty(err error) bool {
_, ok := err.(ErrAccessTokenEmpty)
return ok
}
func (err ErrAccessTokenEmpty) Error() string {
return fmt.Sprintf("access token is empty")
}
// ________ .__ __ .__
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
@@ -375,7 +387,7 @@ func IsErrReleaseAlreadyExist(err error) bool {
}
func (err ErrReleaseAlreadyExist) Error() string {
return fmt.Sprintf("Release tag already exist [tag_name: %s]", err.TagName)
return fmt.Sprintf("release tag already exist [tag_name: %s]", err.TagName)
}
type ErrReleaseNotExist struct {
@@ -389,7 +401,20 @@ func IsErrReleaseNotExist(err error) bool {
}
func (err ErrReleaseNotExist) Error() string {
return fmt.Sprintf("Release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
return fmt.Sprintf("release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
}
type ErrInvalidTagName struct {
TagName string
}
func IsErrInvalidTagName(err error) bool {
_, ok := err.(ErrInvalidTagName)
return ok
}
func (err ErrInvalidTagName) Error() string {
return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName)
}
// __________ .__
@@ -409,7 +434,7 @@ func IsErrBranchNotExist(err error) bool {
}
func (err ErrBranchNotExist) Error() string {
return fmt.Sprintf("Branch does not exist [name: %s]", err.Name)
return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
}
// __ __ ___. .__ __
@@ -508,7 +533,8 @@ func (err ErrCommentNotExist) Error() string {
// \/ \/ \/ \/
type ErrLabelNotExist struct {
ID int64
LabelID int64
RepoID int64
}
func IsErrLabelNotExist(err error) bool {
@@ -517,7 +543,7 @@ func IsErrLabelNotExist(err error) bool {
}
func (err ErrLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [id: %d]", err.ID)
return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID)
}
// _____ .__.__ __

View File

@@ -48,10 +48,10 @@ const (
)
type DiffLine struct {
LeftIdx int
RightIdx int
Type DiffLineType
Content string
LeftIdx int
RightIdx int
Type DiffLineType
Content string
}
func (d *DiffLine) GetType() int {
@@ -160,7 +160,9 @@ type DiffFile struct {
IsDeleted bool
IsBin bool
IsRenamed bool
IsSubmodule bool
Sections []*DiffSection
IsIncomplete bool
}
func (diffFile *DiffFile) GetType() int {
@@ -174,6 +176,7 @@ func (diffFile *DiffFile) GetHighlightClass() string {
type Diff struct {
TotalAddition, TotalDeletion int
Files []*DiffFile
IsIncomplete bool
}
func (diff *Diff) NumFiles() int {
@@ -182,7 +185,8 @@ func (diff *Diff) NumFiles() int {
const DIFF_HEAD = "diff --git "
func ParsePatch(maxlines int, reader io.Reader) (*Diff, error) {
// TODO: move this function to gogits/git-module
func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*Diff, error) {
var (
diff = &Diff{Files: make([]*DiffFile, 0)}
@@ -193,15 +197,12 @@ func ParsePatch(maxlines int, reader io.Reader) (*Diff, error) {
leftLine, rightLine int
lineCount int
curFileLinesCount int
)
input := bufio.NewReader(reader)
isEOF := false
for {
if isEOF {
break
}
for !isEOF {
line, err := input.ReadString('\n')
if err != nil {
if err == io.EOF {
@@ -216,20 +217,16 @@ func ParsePatch(maxlines int, reader io.Reader) (*Diff, error) {
line = line[:len(line)-1]
}
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
continue
} else if len(line) == 0 {
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 {
continue
}
curFileLinesCount++
lineCount++
// Diff data too large, we only show the first about maxlines lines
if lineCount >= maxlines {
log.Warn("Diff data too large")
io.Copy(ioutil.Discard, reader)
diff.Files = nil
return diff, nil
if curFileLinesCount >= maxLines || len(line) >= maxLineCharacteres {
curFile.IsIncomplete = true
}
switch {
@@ -304,8 +301,14 @@ func ParsePatch(maxlines int, reader io.Reader) (*Diff, error) {
Sections: make([]*DiffSection, 0, 10),
}
diff.Files = append(diff.Files, curFile)
if len(diff.Files) >= maxFiles {
diff.IsIncomplete = true
io.Copy(ioutil.Discard, reader)
break
}
curFileLinesCount = 0
// Check file diff type.
// Check file diff type and is submodule.
for {
line, err := input.ReadString('\n')
if err != nil {
@@ -332,6 +335,9 @@ func ParsePatch(maxlines int, reader io.Reader) (*Diff, error) {
curFile.Name = b
}
if curFile.Type > 0 {
if strings.HasSuffix(line, " 160000\n") {
curFile.IsSubmodule = true
}
break
}
}
@@ -366,13 +372,13 @@ func ParsePatch(maxlines int, reader io.Reader) (*Diff, error) {
return diff, nil
}
func GetDiffRange(repoPath, beforeCommitID string, afterCommitID string, maxlines int) (*Diff, error) {
repo, err := git.OpenRepository(repoPath)
func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
return nil, err
}
commit, err := repo.GetCommit(afterCommitID)
commit, err := gitRepo.GetCommit(afterCommitID)
if err != nil {
return nil, err
}
@@ -405,7 +411,7 @@ func GetDiffRange(repoPath, beforeCommitID string, afterCommitID string, maxline
pid := process.Add(fmt.Sprintf("GetDiffRange (%s)", repoPath), cmd)
defer process.Remove(pid)
diff, err := ParsePatch(maxlines, stdout)
diff, err := ParsePatch(maxLines, maxLineCharacteres, maxFiles, stdout)
if err != nil {
return nil, fmt.Errorf("ParsePatch: %v", err)
}
@@ -417,6 +423,59 @@ func GetDiffRange(repoPath, beforeCommitID string, afterCommitID string, maxline
return diff, nil
}
func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {
return GetDiffRange(repoPath, "", commitId, maxlines)
type RawDiffType string
const (
RAW_DIFF_NORMAL RawDiffType = "diff"
RAW_DIFF_PATCH RawDiffType = "patch"
)
// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
// TODO: move this function to gogits/git-module
func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
repo, err := git.OpenRepository(repoPath)
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
}
commit, err := repo.GetCommit(commitID)
if err != nil {
return fmt.Errorf("GetCommit: %v", err)
}
var cmd *exec.Cmd
switch diffType {
case RAW_DIFF_NORMAL:
if commit.ParentCount() == 0 {
cmd = exec.Command("git", "show", commitID)
} else {
c, _ := commit.Parent(0)
cmd = exec.Command("git", "diff", "-M", c.ID.String(), commitID)
}
case RAW_DIFF_PATCH:
if commit.ParentCount() == 0 {
cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", "--root", commitID)
} else {
c, _ := commit.Parent(0)
query := fmt.Sprintf("%s...%s", commitID, c.ID.String())
cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", query)
}
default:
return fmt.Errorf("invalid diffType: %s", diffType)
}
stderr := new(bytes.Buffer)
cmd.Dir = repoPath
cmd.Stdout = writer
cmd.Stderr = stderr
if err = cmd.Run(); err != nil {
return fmt.Errorf("Run: %v - %s", err, stderr)
}
return nil
}
func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacteres, maxFiles)
}

View File

@@ -5,7 +5,6 @@
package models
import (
"bytes"
"errors"
"fmt"
"io"
@@ -33,8 +32,8 @@ var (
// Issue represents an issue or pull request of repository.
type Issue struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Index int64 // Index in one repository.
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
Name string
Repo *Repository `xorm:"-"`
PosterID int64
@@ -65,13 +64,26 @@ type Issue struct {
}
func (i *Issue) BeforeInsert() {
i.CreatedUnix = time.Now().UTC().Unix()
i.CreatedUnix = time.Now().Unix()
i.UpdatedUnix = i.CreatedUnix
}
func (i *Issue) BeforeUpdate() {
i.UpdatedUnix = time.Now().UTC().Unix()
i.DeadlineUnix = i.Deadline.UTC().Unix()
i.UpdatedUnix = time.Now().Unix()
i.DeadlineUnix = i.Deadline.Unix()
}
func (issue *Issue) loadAttributes(e Engine) (err error) {
issue.Repo, err = getRepositoryByID(e, issue.RepoID)
if err != nil {
return fmt.Errorf("getRepositoryByID: %v", err)
}
return nil
}
func (issue *Issue) LoadAttributes() error {
return issue.loadAttributes(x)
}
func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
@@ -147,6 +159,10 @@ func (i *Issue) State() string {
return "open"
}
func (issue *Issue) FullLink() string {
return fmt.Sprintf("%s/issues/%d", issue.Repo.FullLink(), issue.Index)
}
// IsPoster returns true if given user by ID is the poster.
func (i *Issue) IsPoster(uid int64) bool {
return i.PosterID == uid
@@ -165,7 +181,7 @@ func (i *Issue) addLabel(e *xorm.Session, label *Label) error {
return newIssueLabel(e, i, label)
}
// AddLabel adds new label to issue by given ID.
// AddLabel adds a new label to the issue.
func (i *Issue) AddLabel(label *Label) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
@@ -180,52 +196,76 @@ func (i *Issue) AddLabel(label *Label) (err error) {
return sess.Commit()
}
func (i *Issue) getLabels(e Engine) (err error) {
if len(i.Labels) > 0 {
func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error {
return newIssueLabels(e, issue, labels)
}
// AddLabels adds a list of new labels to the issue.
func (issue *Issue) AddLabels(labels []*Label) error {
return NewIssueLabels(issue, labels)
}
func (issue *Issue) getLabels(e Engine) (err error) {
if len(issue.Labels) > 0 {
return nil
}
i.Labels, err = getLabelsByIssueID(e, i.ID)
issue.Labels, err = getLabelsByIssueID(e, issue.ID)
if err != nil {
return fmt.Errorf("getLabelsByIssueID: %v", err)
}
return nil
}
func (i *Issue) removeLabel(e *xorm.Session, label *Label) error {
return deleteIssueLabel(e, i, label)
func (issue *Issue) removeLabel(e *xorm.Session, label *Label) error {
return deleteIssueLabel(e, issue, label)
}
// RemoveLabel removes a label from issue by given ID.
func (i *Issue) RemoveLabel(label *Label) (err error) {
func (issue *Issue) RemoveLabel(label *Label) (err error) {
return DeleteIssueLabel(issue, label)
}
func (issue *Issue) clearLabels(e *xorm.Session) (err error) {
if err = issue.getLabels(e); err != nil {
return fmt.Errorf("getLabels: %v", err)
}
for i := range issue.Labels {
if err = issue.removeLabel(e, issue.Labels[i]); err != nil {
return fmt.Errorf("removeLabel: %v", err)
}
}
return nil
}
func (issue *Issue) ClearLabels() (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = i.removeLabel(sess, label); err != nil {
if err = issue.clearLabels(sess); err != nil {
return err
}
return sess.Commit()
}
func (i *Issue) ClearLabels() (err error) {
// ReplaceLabels removes all current labels and add new labels to the issue.
func (issue *Issue) ReplaceLabels(labels []*Label) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = i.getLabels(sess); err != nil {
return err
}
for idx := range i.Labels {
if err = i.removeLabel(sess, i.Labels[idx]); err != nil {
return err
}
if err = issue.clearLabels(sess); err != nil {
return fmt.Errorf("clearLabels: %v", err)
} else if err = issue.addLabels(sess, labels); err != nil {
return fmt.Errorf("addLabels: %v", err)
}
return sess.Commit()
@@ -320,7 +360,7 @@ func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64,
if issue.AssigneeID > 0 {
// Silently drop invalid assignee
valid, err := hasAccess(e, &User{Id: issue.AssigneeID}, repo, ACCESS_MODE_WRITE)
valid, err := hasAccess(e, &User{ID: issue.AssigneeID}, repo, ACCESS_MODE_WRITE)
if err != nil {
return fmt.Errorf("hasAccess: %v", err)
} else if !valid {
@@ -391,7 +431,7 @@ func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64,
}
}
return nil
return issue.loadAttributes(e)
}
// NewIssue creates new issue with labels for repository.
@@ -412,7 +452,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
// Notify watchers.
act := &Action{
ActUserID: issue.Poster.Id,
ActUserID: issue.Poster.ID,
ActUserName: issue.Poster.Name,
ActEmail: issue.Poster.Email,
OpType: ACTION_CREATE_ISSUE,
@@ -423,7 +463,9 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
IsPrivate: repo.IsPrivate,
}
if err = NotifyWatchers(act); err != nil {
log.Error(4, "notifyWatchers: %v", err)
log.Error(4, "NotifyWatchers: %v", err)
} else if err = issue.MailParticipants(); err != nil {
log.Error(4, "MailParticipants: %v", err)
}
return nil
@@ -452,8 +494,7 @@ func GetIssueByRef(ref string) (*Issue, error) {
return nil, err
}
issue.Repo = repo
return issue, nil
return issue, issue.LoadAttributes()
}
// GetIssueByIndex returns issue by given index in repository.
@@ -468,7 +509,7 @@ func GetIssueByIndex(repoID, index int64) (*Issue, error) {
} else if !has {
return nil, ErrIssueNotExist{0, repoID, index}
}
return issue, nil
return issue, issue.LoadAttributes()
}
// GetIssueByID returns an issue by given ID.
@@ -480,7 +521,7 @@ func GetIssueByID(id int64) (*Issue, error) {
} else if !has {
return nil, ErrIssueNotExist{id, 0, 0}
}
return issue, nil
return issue, issue.LoadAttributes()
}
type IssuesOptions struct {
@@ -504,7 +545,7 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
opts.Page = 1
}
sess := x.Limit(setting.IssuePagingNum, (opts.Page-1)*setting.IssuePagingNum)
sess := x.Limit(setting.UI.IssuePagingNum, (opts.Page-1)*setting.UI.IssuePagingNum)
if opts.RepoID > 0 {
sess.Where("issue.repo_id=?", opts.RepoID).And("issue.is_closed=?", opts.IsClosed)
@@ -513,7 +554,7 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
if len(opts.RepoIDs) == 0 {
return make([]*Issue, 0), nil
}
sess.Where("issue.repo_id IN ("+strings.Join(base.Int64sToStrings(opts.RepoIDs), ",")+")").And("issue.is_closed=?", opts.IsClosed)
sess.In("issue.repo_id", base.Int64sToStrings(opts.RepoIDs)).And("issue.is_closed=?", opts.IsClosed)
} else {
sess.Where("issue.is_closed=?", opts.IsClosed)
}
@@ -532,46 +573,37 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
switch opts.SortType {
case "oldest":
sess.Asc("created_unix")
sess.Asc("issue.created_unix")
case "recentupdate":
sess.Desc("updated_unix")
sess.Desc("issue.updated_unix")
case "leastupdate":
sess.Asc("updated_unix")
sess.Asc("issue.updated_unix")
case "mostcomment":
sess.Desc("num_comments")
sess.Desc("issue.num_comments")
case "leastcomment":
sess.Asc("num_comments")
sess.Asc("issue.num_comments")
case "priority":
sess.Desc("priority")
sess.Desc("issue.priority")
default:
sess.Desc("created_unix")
sess.Desc("issue.created_unix")
}
labelIDs := base.StringsToInt64s(strings.Split(opts.Labels, ","))
if len(labelIDs) > 0 {
validJoin := false
queryStr := "issue.id=issue_label.issue_id"
for _, id := range labelIDs {
if id == 0 {
continue
}
validJoin = true
queryStr += " AND issue_label.label_id=" + com.ToStr(id)
}
if validJoin {
sess.Join("INNER", "issue_label", queryStr)
if len(opts.Labels) > 0 && opts.Labels != "0" {
labelIDs := base.StringsToInt64s(strings.Split(opts.Labels, ","))
if len(labelIDs) > 0 {
sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id").In("issue_label.label_id", labelIDs)
}
}
if opts.IsMention {
queryStr := "issue.id=issue_user.issue_id AND issue_user.is_mentioned=1"
sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id").And("issue_user.is_mentioned = ?", true)
if opts.UserID > 0 {
queryStr += " AND issue_user.uid=" + com.ToStr(opts.UserID)
sess.And("issue_user.uid = ?", opts.UserID)
}
sess.Join("INNER", "issue_user", queryStr)
}
issues := make([]*Issue, 0, setting.IssuePagingNum)
issues := make([]*Issue, 0, setting.UI.IssuePagingNum)
return issues, sess.Find(&issues)
}
@@ -624,7 +656,7 @@ func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error {
isNeedAddPoster := true
for _, u := range users {
iu.ID = 0
iu.UID = u.Id
iu.UID = u.ID
iu.IsPoster = iu.UID == issue.PosterID
if isNeedAddPoster && iu.IsPoster {
isNeedAddPoster = false
@@ -684,18 +716,8 @@ func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*Issue
return []*IssueUser{}, nil
}
buf := bytes.NewBufferString("")
for _, rid := range rids {
buf.WriteString("repo_id=")
buf.WriteString(com.ToStr(rid))
buf.WriteString(" OR ")
}
cond := strings.TrimSuffix(buf.String(), " OR ")
ius := make([]*IssueUser, 0, 10)
sess := x.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
if len(cond) > 0 {
sess.And(cond)
}
sess := x.Limit(20, (page-1)*20).Where("is_closed=?", isClosed).In("repo_id", rids)
err := sess.Find(&ius)
return ius, err
}
@@ -720,42 +742,44 @@ func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int
return ius, err
}
func UpdateMentions(userNames []string, issueId int64) error {
for i := range userNames {
userNames[i] = strings.ToLower(userNames[i])
}
users := make([]*User, 0, len(userNames))
if err := x.Where("lower_name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("lower_name ASC").Find(&users); err != nil {
return err
// UpdateIssueMentions extracts mentioned people from content and
// updates issue-user relations for them.
func UpdateIssueMentions(issueID int64, mentions []string) error {
if len(mentions) == 0 {
return nil
}
ids := make([]int64, 0, len(userNames))
for i := range mentions {
mentions[i] = strings.ToLower(mentions[i])
}
users := make([]*User, 0, len(mentions))
if err := x.In("lower_name", mentions).Asc("lower_name").Find(&users); err != nil {
return fmt.Errorf("find mentioned users: %v", err)
}
ids := make([]int64, 0, len(mentions))
for _, user := range users {
ids = append(ids, user.Id)
if !user.IsOrganization() {
ids = append(ids, user.ID)
if !user.IsOrganization() || user.NumMembers == 0 {
continue
}
if user.NumMembers == 0 {
continue
}
tempIds := make([]int64, 0, user.NumMembers)
orgUsers, err := GetOrgUsersByOrgId(user.Id)
memberIDs := make([]int64, 0, user.NumMembers)
orgUsers, err := GetOrgUsersByOrgID(user.ID)
if err != nil {
return err
return fmt.Errorf("GetOrgUsersByOrgID [%d]: %v", user.ID, err)
}
for _, orgUser := range orgUsers {
tempIds = append(tempIds, orgUser.ID)
memberIDs = append(memberIDs, orgUser.ID)
}
ids = append(ids, tempIds...)
ids = append(ids, memberIDs...)
}
if err := UpdateIssueUsersByMentions(ids, issueId); err != nil {
return err
if err := UpdateIssueUsersByMentions(issueID, ids); err != nil {
return fmt.Errorf("UpdateIssueUsersByMentions: %v", err)
}
return nil
@@ -791,7 +815,7 @@ func parseCountResult(results []map[string][]byte) int64 {
type IssueStatsOptions struct {
RepoID int64
UserID int64
LabelID int64
Labels string
MilestoneID int64
AssigneeID int64
FilterMode int
@@ -802,41 +826,60 @@ type IssueStatsOptions struct {
func GetIssueStats(opts *IssueStatsOptions) *IssueStats {
stats := &IssueStats{}
queryStr := "SELECT COUNT(*) FROM `issue` "
if opts.LabelID > 0 {
queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com.ToStr(opts.LabelID)
}
countSession := func(opts *IssueStatsOptions) *xorm.Session {
sess := x.Where("issue.repo_id = ?", opts.RepoID).And("is_pull = ?", opts.IsPull)
baseCond := " WHERE issue.repo_id=" + com.ToStr(opts.RepoID) + " AND issue.is_closed=?"
if opts.MilestoneID > 0 {
baseCond += " AND issue.milestone_id=" + com.ToStr(opts.MilestoneID)
if len(opts.Labels) > 0 && opts.Labels != "0" {
labelIDs := base.StringsToInt64s(strings.Split(opts.Labels, ","))
if len(labelIDs) > 0 {
sess.Join("INNER", "issue_label", "issue.id = issue_id").In("label_id", labelIDs)
}
}
if opts.MilestoneID > 0 {
sess.And("issue.milestone_id = ?", opts.MilestoneID)
}
if opts.AssigneeID > 0 {
sess.And("assignee_id = ?", opts.AssigneeID)
}
return sess
}
if opts.AssigneeID > 0 {
baseCond += " AND assignee_id=" + com.ToStr(opts.AssigneeID)
}
baseCond += " AND issue.is_pull=?"
switch opts.FilterMode {
case FM_ALL, FM_ASSIGN:
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull)
stats.OpenCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull)
stats.ClosedCount = parseCountResult(results)
stats.OpenCount, _ = countSession(opts).
And("is_closed = ?", false).
Count(&Issue{})
stats.ClosedCount, _ = countSession(opts).
And("is_closed = ?", true).
Count(&Issue{})
case FM_CREATE:
baseCond += " AND poster_id=?"
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID)
stats.OpenCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID)
stats.ClosedCount = parseCountResult(results)
stats.OpenCount, _ = countSession(opts).
And("poster_id = ?", opts.UserID).
And("is_closed = ?", false).
Count(&Issue{})
stats.ClosedCount, _ = countSession(opts).
And("poster_id = ?", opts.UserID).
And("is_closed = ?", true).
Count(&Issue{})
case FM_MENTION:
queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID, true)
stats.OpenCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID, true)
stats.ClosedCount = parseCountResult(results)
stats.OpenCount, _ = countSession(opts).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
And("issue_user.uid = ?", opts.UserID).
And("issue_user.is_mentioned = ?", true).
And("issue.is_closed = ?", false).
Count(&Issue{})
stats.ClosedCount, _ = countSession(opts).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
And("issue_user.uid = ?", opts.UserID).
And("issue_user.is_mentioned = ?", true).
And("issue.is_closed = ?", true).
Count(&Issue{})
}
return stats
}
@@ -845,64 +888,70 @@ func GetIssueStats(opts *IssueStatsOptions) *IssueStats {
func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPull bool) *IssueStats {
stats := &IssueStats{}
queryStr := "SELECT COUNT(*) FROM `issue` "
baseCond := " WHERE issue.is_closed=?"
if repoID > 0 || len(repoIDs) == 0 {
baseCond += " AND issue.repo_id=" + com.ToStr(repoID)
} else {
baseCond += " AND issue.repo_id IN (" + strings.Join(base.Int64sToStrings(repoIDs), ",") + ")"
countSession := func(isClosed, isPull bool, repoID int64, repoIDs []int64) *xorm.Session {
sess := x.Where("issue.is_closed = ?", isClosed).And("issue.is_pull = ?", isPull)
if repoID > 0 || len(repoIDs) == 0 {
sess.And("repo_id = ?", repoID)
} else {
sess.In("repo_id", repoIDs)
}
return sess
}
if isPull {
baseCond += " AND issue.is_pull=1"
} else {
baseCond += " AND issue.is_pull=0"
}
stats.AssignCount, _ = countSession(false, isPull, repoID, repoIDs).
And("assignee_id = ?", uid).
Count(&Issue{})
results, _ := x.Query(queryStr+baseCond+" AND assignee_id=?", false, uid)
stats.AssignCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond+" AND poster_id=?", false, uid)
stats.CreateCount = parseCountResult(results)
stats.CreateCount, _ = countSession(false, isPull, repoID, repoIDs).
And("assignee_id = ?", uid).
Count(&Issue{})
openCountSession := countSession(false, isPull, repoID, repoIDs)
closedCountSession := countSession(true, isPull, repoID, repoIDs)
switch filterMode {
case FM_ASSIGN:
baseCond += " AND assignee_id=" + com.ToStr(uid)
openCountSession.And("assignee_id = ?", uid)
closedCountSession.And("assignee_id = ?", uid)
case FM_CREATE:
baseCond += " AND poster_id=" + com.ToStr(uid)
openCountSession.And("poster_id = ?", uid)
closedCountSession.And("poster_id = ?", uid)
}
results, _ = x.Query(queryStr+baseCond, false)
stats.OpenCount = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, true)
stats.ClosedCount = parseCountResult(results)
stats.OpenCount, _ = openCountSession.Count(&Issue{})
stats.ClosedCount, _ = closedCountSession.Count(&Issue{})
return stats
}
// GetRepoIssueStats returns number of open and closed repository issues by given filter mode.
func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen int64, numClosed int64) {
queryStr := "SELECT COUNT(*) FROM `issue` "
baseCond := " WHERE issue.repo_id=? AND issue.is_closed=?"
countSession := func(isClosed, isPull bool, repoID int64) *xorm.Session {
sess := x.Where("issue.repo_id = ?", isClosed).
And("is_pull = ?", isPull).
And("repo_id = ?", repoID)
if isPull {
baseCond += " AND issue.is_pull=1"
} else {
baseCond += " AND issue.is_pull=0"
return sess
}
openCountSession := countSession(false, isPull, repoID)
closedCountSession := countSession(true, isPull, repoID)
switch filterMode {
case FM_ASSIGN:
baseCond += " AND assignee_id=" + com.ToStr(uid)
openCountSession.And("assignee_id = ?", uid)
closedCountSession.And("assignee_id = ?", uid)
case FM_CREATE:
baseCond += " AND poster_id=" + com.ToStr(uid)
openCountSession.And("poster_id = ?", uid)
closedCountSession.And("poster_id = ?", uid)
}
results, _ := x.Query(queryStr+baseCond, repoID, false)
numOpen = parseCountResult(results)
results, _ = x.Query(queryStr+baseCond, repoID, true)
numClosed = parseCountResult(results)
return numOpen, numClosed
openResult, _ := openCountSession.Count(&Issue{})
closedResult, _ := closedCountSession.Count(&Issue{})
return openResult, closedResult
}
func updateIssue(e Engine, issue *Issue) error {
@@ -968,9 +1017,12 @@ func UpdateIssueUserByRead(uid, issueID int64) error {
}
// UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
func UpdateIssueUsersByMentions(issueID int64, uids []int64) error {
for _, uid := range uids {
iu := &IssueUser{UID: uid, IssueID: iid}
iu := &IssueUser{
UID: uid,
IssueID: issueID,
}
has, err := x.Get(iu)
if err != nil {
return err
@@ -1018,7 +1070,7 @@ type Milestone struct {
}
func (m *Milestone) BeforeInsert() {
m.DeadlineUnix = m.Deadline.UTC().Unix()
m.DeadlineUnix = m.Deadline.Unix()
}
func (m *Milestone) BeforeUpdate() {
@@ -1028,8 +1080,8 @@ func (m *Milestone) BeforeUpdate() {
m.Completeness = 0
}
m.DeadlineUnix = m.Deadline.UTC().Unix()
m.ClosedDateUnix = m.ClosedDate.UTC().Unix()
m.DeadlineUnix = m.Deadline.Unix()
m.ClosedDateUnix = m.ClosedDate.Unix()
}
func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
@@ -1115,10 +1167,10 @@ func GetAllRepoMilestones(repoID int64) ([]*Milestone, error) {
// GetMilestones returns a list of milestones of given repository and status.
func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) {
miles := make([]*Milestone, 0, setting.IssuePagingNum)
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
sess := x.Where("repo_id=? AND is_closed=?", repoID, isClosed)
if page > 0 {
sess = sess.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
}
return miles, sess.Find(&miles)
}
@@ -1333,7 +1385,7 @@ type Attachment struct {
}
func (a *Attachment) BeforeInsert() {
a.CreatedUnix = time.Now().UTC().Unix()
a.CreatedUnix = time.Now().Unix()
}
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) {

View File

@@ -13,6 +13,7 @@ import (
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/markdown"
)
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
@@ -68,7 +69,7 @@ type Comment struct {
}
func (c *Comment) BeforeInsert() {
c.CreatedUnix = time.Now().UTC().Unix()
c.CreatedUnix = time.Now().Unix()
}
func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
@@ -113,10 +114,34 @@ func (c *Comment) EventTag() string {
return "event-" + com.ToStr(c.ID)
}
// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
func (cmt *Comment) MailParticipants(opType ActionType, issue *Issue) (err error) {
mentions := markdown.FindAllMentions(cmt.Content)
if err = UpdateIssueMentions(cmt.IssueID, mentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", cmt.IssueID, err)
}
switch opType {
case ACTION_COMMENT_ISSUE:
issue.Content = cmt.Content
case ACTION_CLOSE_ISSUE:
issue.Content = fmt.Sprintf("Closed #%d", issue.Index)
case ACTION_REOPEN_ISSUE:
issue.Content = fmt.Sprintf("Reopened #%d", issue.Index)
}
if err = mailIssueCommentToParticipants(issue, cmt.Poster, mentions); err != nil {
log.Error(4, "mailIssueCommentToParticipants: %v", err)
}
return nil
}
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) {
comment := &Comment{
Type: opts.Type,
PosterID: opts.Doer.Id,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
@@ -130,7 +155,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
// Compose comment action, could be plain comment, close or reopen issue/pull request.
// This object will be used to notify watchers in the end of function.
act := &Action{
ActUserID: opts.Doer.Id,
ActUserID: opts.Doer.ID,
ActUserName: opts.Doer.Name,
ActEmail: opts.Doer.Email,
Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]),
@@ -157,7 +182,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
if IsErrAttachmentNotExist(err) {
continue
}
return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err)
return nil, fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
}
attachments = append(attachments, attach)
}
@@ -167,7 +192,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
attachments[i].CommentID = comment.ID
// No assign value could be 0, so ignore AllCols().
if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
return nil, fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
}
}
@@ -200,13 +225,15 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
if err != nil {
return nil, err
}
}
// Notify watchers for whatever action comes in, ignore if no action type
// Notify watchers for whatever action comes in, ignore if no action type.
if act.OpType > 0 {
if err = notifyWatchers(e, act); err != nil {
return nil, fmt.Errorf("notifyWatchers: %v", err)
log.Error(4, "notifyWatchers: %v", err)
}
comment.MailParticipants(act.OpType, opts.Issue)
}
return comment, nil
@@ -318,3 +345,29 @@ func UpdateComment(c *Comment) error {
_, err := x.Id(c.ID).AllCols().Update(c)
return err
}
// DeleteCommentByID deletes a comment by given ID.
func DeleteCommentByID(id int64) error {
comment, err := GetCommentByID(id)
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Id(comment.ID).Delete(new(Comment)); err != nil {
return err
}
if comment.Type == COMMENT_TYPE_COMMENT {
if _, err = sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
return err
}
}
return sess.Commit()
}

View File

@@ -11,6 +11,8 @@ import (
"strings"
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/base"
)
// Label represents a label of repository for issues.
@@ -56,48 +58,65 @@ func NewLabel(l *Label) error {
return err
}
func getLabelByID(e Engine, id int64) (*Label, error) {
if id <= 0 {
return nil, ErrLabelNotExist{id}
// getLabelInRepoByID returns a label by ID in given repository.
// If pass repoID as 0, then ORM will ignore limitation of repository
// and can return arbitrary label with any valid ID.
func getLabelInRepoByID(e Engine, repoID, labelID int64) (*Label, error) {
if labelID <= 0 {
return nil, ErrLabelNotExist{labelID, repoID}
}
l := &Label{ID: id}
l := &Label{
ID: labelID,
RepoID: repoID,
}
has, err := x.Get(l)
if err != nil {
return nil, err
} else if !has {
return nil, ErrLabelNotExist{l.ID}
return nil, ErrLabelNotExist{l.ID, l.RepoID}
}
return l, nil
}
// GetLabelByID returns a label by given ID.
func GetLabelByID(id int64) (*Label, error) {
return getLabelByID(x, id)
return getLabelInRepoByID(x, 0, id)
}
// GetLabelInRepoByID returns a label by ID in given repository.
func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) {
return getLabelInRepoByID(x, repoID, labelID)
}
// GetLabelsInRepoByIDs returns a list of labels by IDs in given repository,
// it silently ignores label IDs that are not belong to the repository.
func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) {
labels := make([]*Label, 0, len(labelIDs))
return labels, x.Where("repo_id = ?", repoID).In("id", base.Int64sToStrings(labelIDs)).Find(&labels)
}
// GetLabelsByRepoID returns all labels that belong to given repository by ID.
func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
labels := make([]*Label, 0, 10)
return labels, x.Where("repo_id=?", repoID).Find(&labels)
return labels, x.Where("repo_id = ?", repoID).Find(&labels)
}
func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
issueLabels, err := getIssueLabels(e, issueID)
if err != nil {
return nil, fmt.Errorf("getIssueLabels: %v", err)
} else if len(issueLabels) == 0 {
return []*Label{}, nil
}
var label *Label
labels := make([]*Label, 0, len(issueLabels))
for idx := range issueLabels {
label, err = getLabelByID(e, issueLabels[idx].LabelID)
if err != nil && !IsErrLabelNotExist(err) {
return nil, fmt.Errorf("getLabelByID: %v", err)
}
labels = append(labels, label)
labelIDs := make([]int64, len(issueLabels))
for i := range issueLabels {
labelIDs[i] = issueLabels[i].LabelID
}
return labels, nil
labels := make([]*Label, 0, len(labelIDs))
return labels, e.Where("id > 0").In("id", base.Int64sToStrings(labelIDs)).Find(&labels)
}
// GetLabelsByIssueID returns all labels that belong to given issue by ID.
@@ -117,7 +136,7 @@ func UpdateLabel(l *Label) error {
// DeleteLabel delete a label of given repository.
func DeleteLabel(repoID, labelID int64) error {
l, err := GetLabelByID(labelID)
_, err := GetLabelInRepoByID(repoID, labelID)
if err != nil {
if IsErrLabelNotExist(err) {
return nil
@@ -131,11 +150,12 @@ func DeleteLabel(repoID, labelID int64) error {
return err
}
if _, err = x.Where("label_id=?", labelID).Delete(new(IssueLabel)); err != nil {
if _, err = sess.Id(labelID).Delete(new(Label)); err != nil {
return err
} else if _, err = sess.Delete(l); err != nil {
} else if _, err = sess.Where("label_id = ?", labelID).Delete(new(IssueLabel)); err != nil {
return err
}
return sess.Commit()
}
@@ -154,7 +174,7 @@ type IssueLabel struct {
}
func hasIssueLabel(e Engine, issueID, labelID int64) bool {
has, _ := e.Where("issue_id=? AND label_id=?", issueID, labelID).Get(new(IssueLabel))
has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel))
return has
}
@@ -180,6 +200,10 @@ func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
// NewIssueLabel creates a new issue-label relation.
func NewIssueLabel(issue *Issue, label *Label) (err error) {
if HasIssueLabel(issue.ID, label.ID) {
return nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
@@ -193,6 +217,35 @@ func NewIssueLabel(issue *Issue, label *Label) (err error) {
return sess.Commit()
}
func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) {
for i := range labels {
if hasIssueLabel(e, issue.ID, labels[i].ID) {
continue
}
if err = newIssueLabel(e, issue, labels[i]); err != nil {
return fmt.Errorf("newIssueLabel: %v", err)
}
}
return nil
}
// NewIssueLabels creates a list of issue-label relations.
func NewIssueLabels(issue *Issue, labels []*Label) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = newIssueLabels(sess, issue, labels); err != nil {
return err
}
return sess.Commit()
}
func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
issueLabels := make([]*IssueLabel, 0, 10)
return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)

81
models/issue_mail.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright 2016 The Gogs 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 models
import (
"fmt"
"github.com/Unknwon/com"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/markdown"
"github.com/gogits/gogs/modules/setting"
)
func (issue *Issue) MailSubject() string {
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Name, issue.Index)
}
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error {
if !setting.Service.EnableNotifyMail {
return nil
}
// Mail wahtcers.
watchers, err := GetWatchers(issue.RepoID)
if err != nil {
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err)
}
tos := make([]string, 0, len(watchers)) // List of email addresses.
names := make([]string, 0, len(watchers))
for i := range watchers {
if watchers[i].UserID == doer.ID {
continue
}
to, err := GetUserByID(watchers[i].UserID)
if err != nil {
return fmt.Errorf("GetUserByID [%d]: %v", watchers[i].UserID, err)
}
if to.IsOrganization() {
continue
}
tos = append(tos, to.Email)
names = append(names, to.Name)
}
SendIssueCommentMail(issue, doer, tos)
// Mail mentioned people and exclude watchers.
names = append(names, doer.Name)
tos = make([]string, 0, len(mentions)) // list of user names.
for i := range mentions {
if com.IsSliceContainsStr(names, mentions[i]) {
continue
}
tos = append(tos, mentions[i])
}
SendIssueMentionMail(issue, doer, GetUserEmailsByNames(tos))
return nil
}
// MailParticipants sends new issue thread created emails to repository watchers
// and mentioned people.
func (issue *Issue) MailParticipants() (err error) {
mentions := markdown.FindAllMentions(issue.Content)
if err = UpdateIssueMentions(issue.ID, mentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
}
if err = mailIssueCommentToParticipants(issue, issue.Poster, mentions); err != nil {
log.Error(4, "mailIssueCommentToParticipants: %v", err)
}
return nil
}

View File

@@ -15,6 +15,7 @@ import (
"time"
"github.com/Unknwon/com"
"github.com/go-macaron/binding"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
@@ -23,6 +24,11 @@ import (
"github.com/gogits/gogs/modules/log"
)
var (
ErrAuthenticationAlreadyExist = errors.New("Authentication already exist")
ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
)
type LoginType int
// Note: new type must be added at the end of list to maintain compatibility.
@@ -35,11 +41,6 @@ const (
LOGIN_DLDAP // 5
)
var (
ErrAuthenticationAlreadyExist = errors.New("Authentication already exist")
ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
)
var LoginNames = map[LoginType]string{
LOGIN_LDAP: "LDAP (via BindDN)",
LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind
@@ -47,6 +48,12 @@ var LoginNames = map[LoginType]string{
LOGIN_PAM: "PAM",
}
var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
ldap.SECURITY_PROTOCOL_UNENCRYPTED: "Unencrypted",
ldap.SECURITY_PROTOCOL_LDAPS: "LDAPS",
ldap.SECURITY_PROTOCOL_START_TLS: "StartTLS",
}
// Ensure structs implemented interface.
var (
_ core.Conversion = &LDAPConfig{}
@@ -66,6 +73,10 @@ func (cfg *LDAPConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}
func (cfg *LDAPConfig) SecurityProtocolName() string {
return SecurityProtocolNames[cfg.SecurityProtocol]
}
type SMTPConfig struct {
Auth string
Host string
@@ -109,12 +120,12 @@ type LoginSource struct {
}
func (s *LoginSource) BeforeInsert() {
s.CreatedUnix = time.Now().UTC().Unix()
s.CreatedUnix = time.Now().Unix()
s.UpdatedUnix = s.CreatedUnix
}
func (s *LoginSource) BeforeUpdate() {
s.UpdatedUnix = time.Now().UTC().Unix()
s.UpdatedUnix = time.Now().Unix()
}
// Cell2Int64 converts a xorm.Cell type to int64,
@@ -173,10 +184,16 @@ func (source *LoginSource) IsPAM() bool {
return source.Type == LOGIN_PAM
}
func (source *LoginSource) HasTLS() bool {
return ((source.IsLDAP() || source.IsDLDAP()) &&
source.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) ||
source.IsSMTP()
}
func (source *LoginSource) UseTLS() bool {
switch source.Type {
case LOGIN_LDAP, LOGIN_DLDAP:
return source.LDAP().UseSSL
return source.LDAP().SecurityProtocol != ldap.SECURITY_PROTOCOL_UNENCRYPTED
case LOGIN_SMTP:
return source.SMTP().TLS
}
@@ -264,7 +281,7 @@ func DeleteSource(source *LoginSource) error {
func LoginUserLDAPSource(u *User, loginName, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
cfg := source.Cfg.(*LDAPConfig)
directBind := (source.Type == LOGIN_DLDAP)
name, fn, sn, mail, admin, logged := cfg.SearchEntry(loginName, passwd, directBind)
username, fn, sn, mail, isAdmin, logged := cfg.SearchEntry(loginName, passwd, directBind)
if !logged {
// User not in LDAP, do nothing
return nil, ErrUserNotExist{0, loginName}
@@ -275,37 +292,42 @@ func LoginUserLDAPSource(u *User, loginName, passwd string, source *LoginSource,
}
// Fallback.
if len(name) == 0 {
name = loginName
if len(username) == 0 {
username = loginName
}
// Validate username make sure it satisfies requirement.
if binding.AlphaDashDotPattern.MatchString(username) {
return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username)
}
if len(mail) == 0 {
mail = fmt.Sprintf("%s@localhost", name)
mail = fmt.Sprintf("%s@localhost", username)
}
u = &User{
LowerName: strings.ToLower(name),
Name: name,
FullName: composeFullName(fn, sn, name),
LowerName: strings.ToLower(username),
Name: username,
FullName: composeFullName(fn, sn, username),
LoginType: source.Type,
LoginSource: source.ID,
LoginName: loginName,
Email: mail,
IsAdmin: admin,
IsAdmin: isAdmin,
IsActive: true,
}
return u, CreateUser(u)
}
func composeFullName(firstName, surename, userName string) string {
func composeFullName(firstname, surname, username string) string {
switch {
case len(firstName) == 0 && len(surename) == 0:
return userName
case len(firstName) == 0:
return surename
case len(surename) == 0:
return firstName
case len(firstname) == 0 && len(surname) == 0:
return username
case len(firstname) == 0:
return surname
case len(surname) == 0:
return firstname
default:
return firstName + " " + surename
return firstname + " " + surname
}
}
@@ -512,7 +534,7 @@ func UserSignIn(uname, passwd string) (*User, error) {
return u, nil
}
return nil, ErrUserNotExist{u.Id, u.Name}
return nil, ErrUserNotExist{u.ID, u.Name}
default:
var source LoginSource
@@ -541,5 +563,5 @@ func UserSignIn(uname, passwd string) (*User, error) {
log.Warn("Failed to login '%s' via '%s': %v", uname, source.Name, err)
}
return nil, ErrUserNotExist{u.Id, u.Name}
return nil, ErrUserNotExist{u.ID, u.Name}
}

183
models/mail.go Normal file
View File

@@ -0,0 +1,183 @@
// Copyright 2016 The Gogs 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 models
import (
"fmt"
"html/template"
"path"
"gopkg.in/gomail.v2"
"gopkg.in/macaron.v1"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/mailer"
"github.com/gogits/gogs/modules/markdown"
"github.com/gogits/gogs/modules/setting"
)
const (
MAIL_AUTH_ACTIVATE base.TplName = "auth/activate"
MAIL_AUTH_ACTIVATE_EMAIL base.TplName = "auth/activate_email"
MAIL_AUTH_RESET_PASSWORD base.TplName = "auth/reset_passwd"
MAIL_AUTH_REGISTER_NOTIFY base.TplName = "auth/register_notify"
MAIL_ISSUE_COMMENT base.TplName = "issue/comment"
MAIL_ISSUE_MENTION base.TplName = "issue/mention"
MAIL_NOTIFY_COLLABORATOR base.TplName = "notify/collaborator"
)
type MailRender interface {
HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error)
}
var mailRender MailRender
func InitMailRender(dir, appendDir string, funcMap []template.FuncMap) {
opt := &macaron.RenderOptions{
Directory: dir,
AppendDirectories: []string{appendDir},
Funcs: funcMap,
Extensions: []string{".tmpl", ".html"},
}
ts := macaron.NewTemplateSet()
ts.Set(macaron.DEFAULT_TPL_SET_NAME, opt)
mailRender = &macaron.TplRender{
TemplateSet: ts,
Opt: opt,
}
}
func SendTestMail(email string) error {
return gomail.Send(&mailer.Sender{}, mailer.NewMessage([]string{email}, "Gogs Test Email!", "Gogs Test Email!").Message)
}
func SendUserMail(c *macaron.Context, u *User, tpl base.TplName, code, subject, info string) {
data := map[string]interface{}{
"Username": u.DisplayName(),
"ActiveCodeLives": setting.Service.ActiveCodeLives / 60,
"ResetPwdCodeLives": setting.Service.ResetPwdCodeLives / 60,
"Code": code,
}
body, err := mailRender.HTMLString(string(tpl), data)
if err != nil {
log.Error(3, "HTMLString: %v", err)
return
}
msg := mailer.NewMessage([]string{u.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info)
mailer.SendAsync(msg)
}
func SendActivateAccountMail(c *macaron.Context, u *User) {
SendUserMail(c, u, MAIL_AUTH_ACTIVATE, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account")
}
func SendResetPasswordMail(c *macaron.Context, u *User) {
SendUserMail(c, u, MAIL_AUTH_RESET_PASSWORD, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "reset password")
}
// SendActivateAccountMail sends confirmation email.
func SendActivateEmailMail(c *macaron.Context, u *User, email *EmailAddress) {
data := map[string]interface{}{
"Username": u.DisplayName(),
"ActiveCodeLives": setting.Service.ActiveCodeLives / 60,
"Code": u.GenerateEmailActivateCode(email.Email),
"Email": email.Email,
}
body, err := mailRender.HTMLString(string(MAIL_AUTH_ACTIVATE_EMAIL), data)
if err != nil {
log.Error(3, "HTMLString: %v", err)
return
}
msg := mailer.NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), body)
msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID)
mailer.SendAsync(msg)
}
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
func SendRegisterNotifyMail(c *macaron.Context, u *User) {
data := map[string]interface{}{
"Username": u.DisplayName(),
}
body, err := mailRender.HTMLString(string(MAIL_AUTH_REGISTER_NOTIFY), data)
if err != nil {
log.Error(3, "HTMLString: %v", err)
return
}
msg := mailer.NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), body)
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID)
mailer.SendAsync(msg)
}
// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(u, doer *User, repo *Repository) {
repoName := path.Join(repo.Owner.Name, repo.Name)
subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName)
data := map[string]interface{}{
"Subject": subject,
"RepoName": repoName,
"Link": repo.FullLink(),
}
body, err := mailRender.HTMLString(string(MAIL_NOTIFY_COLLABORATOR), data)
if err != nil {
log.Error(3, "HTMLString: %v", err)
return
}
msg := mailer.NewMessage([]string{u.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID)
mailer.SendAsync(msg)
}
func composeTplData(subject, body, link string) map[string]interface{} {
data := make(map[string]interface{}, 10)
data["Subject"] = subject
data["Body"] = body
data["Link"] = link
return data
}
func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []string, info string) *mailer.Message {
subject := issue.MailSubject()
body := string(markdown.RenderSpecialLink([]byte(issue.Content), issue.Repo.FullLink(), issue.Repo.ComposeMetas()))
data := composeTplData(subject, body, issue.FullLink())
data["Doer"] = doer
content, err := mailRender.HTMLString(string(tplName), data)
if err != nil {
log.Error(3, "HTMLString (%s): %v", tplName, err)
}
msg := mailer.NewMessage(tos, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
return msg
}
// SendIssueCommentMail composes and sends issue comment emails to target receivers.
func SendIssueCommentMail(issue *Issue, doer *User, tos []string) {
if len(tos) == 0 {
return
}
mailer.SendAsync(composeIssueMessage(issue, doer, MAIL_ISSUE_COMMENT, tos, "issue comment"))
}
// SendIssueMentionMail composes and sends issue mention emails to target receivers.
func SendIssueMentionMail(issue *Issue, doer *User, tos []string) {
if len(tos) == 0 {
return
}
mailer.SendAsync(composeIssueMessage(issue, doer, MAIL_ISSUE_MENTION, tos, "issue mention"))
}

View File

@@ -51,7 +51,7 @@ func (m *migration) Migrate(x *xorm.Engine) error {
// The version table. Should have only one row with id==1
type Version struct {
Id int64
ID int64 `xorm:"pk autoincr"`
Version int64
}
@@ -59,14 +59,15 @@ type Version struct {
// If you want to "retire" a migration, remove it from the top of the list and
// update _MIN_VER_DB accordingly
var migrations = []Migration{
NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
NewMigration("generate rands and salt for organizations", generateOrgRandsAndSalt), // V10 -> V11:v0.8.5
NewMigration("convert date to unix timestamp", convertDateToUnix), // V11 -> V12:v0.9.2
NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
NewMigration("generate rands and salt for organizations", generateOrgRandsAndSalt), // V10 -> V11:v0.8.5
NewMigration("convert date to unix timestamp", convertDateToUnix), // V11 -> V12:v0.9.2
NewMigration("convert LDAP UseSSL option to SecurityProtocol", ldapUseSSLToSecurityProtocol), // V12 -> V13:v0.9.37
}
// Migrate database to current version
@@ -75,7 +76,7 @@ func Migrate(x *xorm.Engine) error {
return fmt.Errorf("sync: %v", err)
}
currentVersion := &Version{Id: 1}
currentVersion := &Version{ID: 1}
has, err := x.Get(currentVersion)
if err != nil {
return fmt.Errorf("get: %v", err)
@@ -580,6 +581,7 @@ type TWebhook struct {
func (t *TWebhook) TableName() string { return "webhook" }
func convertDateToUnix(x *xorm.Engine) (err error) {
log.Info("This migration could take up to minutes, please be patient.")
type Bean struct {
ID int64 `xorm:"pk autoincr"`
Created time.Time
@@ -644,17 +646,17 @@ func convertDateToUnix(x *xorm.Engine) (err error) {
if bean.Deadline.IsZero() {
continue
}
fieldSQL += com.ToStr(bean.Deadline.UTC().Unix())
fieldSQL += com.ToStr(bean.Deadline.Unix())
case "created":
fieldSQL += com.ToStr(bean.Created.UTC().Unix())
fieldSQL += com.ToStr(bean.Created.Unix())
case "updated":
fieldSQL += com.ToStr(bean.Updated.UTC().Unix())
fieldSQL += com.ToStr(bean.Updated.Unix())
case "closed_date":
fieldSQL += com.ToStr(bean.ClosedDate.UTC().Unix())
fieldSQL += com.ToStr(bean.ClosedDate.Unix())
case "merged":
fieldSQL += com.ToStr(bean.Merged.UTC().Unix())
fieldSQL += com.ToStr(bean.Merged.Unix())
case "next_update":
fieldSQL += com.ToStr(bean.NextUpdate.UTC().Unix())
fieldSQL += com.ToStr(bean.NextUpdate.Unix())
}
valSQLs = append(valSQLs, fieldSQL)

52
models/migrations/v13.go Normal file
View File

@@ -0,0 +1,52 @@
// Copyright 2016 The Gogs 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 migrations
import (
"encoding/json"
"fmt"
"strings"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
)
func ldapUseSSLToSecurityProtocol(x *xorm.Engine) error {
results, err := x.Query("SELECT `id`,`cfg` FROM `login_source` WHERE `type` = 2 OR `type` = 5")
if err != nil {
if strings.Contains(err.Error(), "no such column") {
return nil
}
return fmt.Errorf("select LDAP login sources: %v", err)
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
for _, result := range results {
cfg := map[string]interface{}{}
if err = json.Unmarshal(result["cfg"], &cfg); err != nil {
return fmt.Errorf("decode JSON config: %v", err)
}
if com.ToStr(cfg["UseSSL"]) == "true" {
cfg["SecurityProtocol"] = 1 // LDAPS
}
delete(cfg, "UseSSL")
data, err := json.Marshal(&cfg)
if err != nil {
return fmt.Errorf("encode JSON config: %v", err)
}
if _, err = sess.Exec("UPDATE `login_source` SET `cfg`=? WHERE `id`=?",
string(data), com.StrTo(result["id"]).MustInt64()); err != nil {
return fmt.Errorf("update config column: %v", err)
}
}
return sess.Commit()
}

View File

@@ -27,9 +27,10 @@ type Engine interface {
Exec(string, ...interface{}) (sql.Result, error)
Find(interface{}, ...interface{}) error
Get(interface{}) (bool, error)
Id(interface{}) *xorm.Session
Insert(...interface{}) (int64, error)
InsertOne(interface{}) (int64, error)
Id(interface{}) *xorm.Session
Iterate(interface{}, xorm.IterFunc) error
Sql(string, ...interface{}) *xorm.Session
Where(string, ...interface{}) *xorm.Session
}
@@ -97,14 +98,18 @@ func LoadConfigs() {
func getEngine() (*xorm.Engine, error) {
cnnstr := ""
var Param string = "?"
if strings.Contains(DbCfg.Name, Param) {
Param = "&"
}
switch DbCfg.Type {
case "mysql":
if DbCfg.Host[0] == '/' { // looks like a unix socket
cnnstr = fmt.Sprintf("%s:%s@unix(%s)/%s?charset=utf8&parseTime=true",
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name)
cnnstr = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8&parseTime=true",
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
} else {
cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true",
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name)
cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s%scharset=utf8&parseTime=true",
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
}
case "postgres":
var host, port = "127.0.0.1", "5432"
@@ -115,8 +120,8 @@ func getEngine() (*xorm.Engine, error) {
if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 {
port = fields[1]
}
cnnstr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, DbCfg.SSLMode)
cnnstr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, Param, DbCfg.SSLMode)
case "sqlite3":
if !EnableSQLite3 {
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
@@ -202,7 +207,7 @@ func GetStatistic() (stats Statistic) {
stats.Counter.User = CountUsers()
stats.Counter.Org = CountOrganizations()
stats.Counter.PublicKey, _ = x.Count(new(PublicKey))
stats.Counter.Repo = CountRepositories()
stats.Counter.Repo = CountRepositories(true)
stats.Counter.Watch, _ = x.Count(new(Watch))
stats.Counter.Star, _ = x.Count(new(Star))
stats.Counter.Action, _ = x.Count(new(Action))

File diff suppressed because it is too large Load Diff

618
models/org_team.go Normal file
View File

@@ -0,0 +1,618 @@
// Copyright 2016 The Gogs 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 models
import (
"errors"
"fmt"
"strings"
)
const OWNER_TEAM = "Owners"
// Team represents a organization team.
type Team struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
LowerName string
Name string
Description string
Authorize AccessMode
Repos []*Repository `xorm:"-"`
Members []*User `xorm:"-"`
NumRepos int
NumMembers int
}
// IsOwnerTeam returns true if team is owner team.
func (t *Team) IsOwnerTeam() bool {
return t.Name == OWNER_TEAM
}
// IsTeamMember returns true if given user is a member of team.
func (t *Team) IsMember(uid int64) bool {
return IsTeamMember(t.OrgID, t.ID, uid)
}
func (t *Team) getRepositories(e Engine) (err error) {
teamRepos := make([]*TeamRepo, 0, t.NumRepos)
if err = x.Where("team_id=?", t.ID).Find(&teamRepos); err != nil {
return fmt.Errorf("get team-repos: %v", err)
}
t.Repos = make([]*Repository, 0, len(teamRepos))
for i := range teamRepos {
repo, err := getRepositoryByID(e, teamRepos[i].RepoID)
if err != nil {
return fmt.Errorf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err)
}
t.Repos = append(t.Repos, repo)
}
return nil
}
// GetRepositories returns all repositories in team of organization.
func (t *Team) GetRepositories() error {
return t.getRepositories(x)
}
func (t *Team) getMembers(e Engine) (err error) {
t.Members, err = getTeamMembers(e, t.ID)
return err
}
// GetMembers returns all members in team of organization.
func (t *Team) GetMembers() (err error) {
return t.getMembers(x)
}
// AddMember adds new membership of the team to the organization,
// the user will have membership to the organization automatically when needed.
func (t *Team) AddMember(uid int64) error {
return AddTeamMember(t.OrgID, t.ID, uid)
}
// RemoveMember removes member from team of organization.
func (t *Team) RemoveMember(uid int64) error {
return RemoveTeamMember(t.OrgID, t.ID, uid)
}
func (t *Team) hasRepository(e Engine, repoID int64) bool {
return hasTeamRepo(e, t.OrgID, t.ID, repoID)
}
// HasRepository returns true if given repository belong to team.
func (t *Team) HasRepository(repoID int64) bool {
return t.hasRepository(x, repoID)
}
func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil {
return err
}
t.NumRepos++
if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
return fmt.Errorf("update team: %v", err)
}
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
return fmt.Errorf("recalculateAccesses: %v", err)
}
if err = t.getMembers(e); err != nil {
return fmt.Errorf("getMembers: %v", err)
}
for _, u := range t.Members {
if err = watchRepo(e, u.ID, repo.ID, true); err != nil {
return fmt.Errorf("watchRepo: %v", err)
}
}
return nil
}
// AddRepository adds new repository to team of organization.
func (t *Team) AddRepository(repo *Repository) (err error) {
if repo.OwnerID != t.OrgID {
return errors.New("Repository does not belong to organization")
} else if t.HasRepository(repo.ID) {
return nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = t.addRepository(sess, repo); err != nil {
return err
}
return sess.Commit()
}
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
return err
}
t.NumRepos--
if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
return err
}
// Don't need to recalculate when delete a repository from organization.
if recalculate {
if err = repo.recalculateTeamAccesses(e, t.ID); err != nil {
return err
}
}
if err = t.getMembers(e); err != nil {
return fmt.Errorf("get team members: %v", err)
}
for _, u := range t.Members {
has, err := hasAccess(e, u, repo, ACCESS_MODE_READ)
if err != nil {
return err
} else if has {
continue
}
if err = watchRepo(e, u.ID, repo.ID, false); err != nil {
return err
}
}
return nil
}
// RemoveRepository removes repository from team of organization.
func (t *Team) RemoveRepository(repoID int64) error {
if !t.HasRepository(repoID) {
return nil
}
repo, err := GetRepositoryByID(repoID)
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = t.removeRepository(sess, repo, true); err != nil {
return err
}
return sess.Commit()
}
// NewTeam creates a record of new team.
// It's caller's responsibility to assign organization ID.
func NewTeam(t *Team) error {
if len(t.Name) == 0 {
return errors.New("empty team name")
}
has, err := x.Id(t.OrgID).Get(new(User))
if err != nil {
return err
} else if !has {
return ErrOrgNotExist
}
t.LowerName = strings.ToLower(t.Name)
has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(new(Team))
if err != nil {
return err
} else if has {
return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(t); err != nil {
sess.Rollback()
return err
}
// Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
}
func getTeam(e Engine, orgId int64, name string) (*Team, error) {
t := &Team{
OrgID: orgId,
LowerName: strings.ToLower(name),
}
has, err := e.Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrTeamNotExist
}
return t, nil
}
// GetTeam returns team by given team name and organization.
func GetTeam(orgId int64, name string) (*Team, error) {
return getTeam(x, orgId, name)
}
func getTeamByID(e Engine, teamId int64) (*Team, error) {
t := new(Team)
has, err := e.Id(teamId).Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrTeamNotExist
}
return t, nil
}
// GetTeamByID returns team by given ID.
func GetTeamByID(teamId int64) (*Team, error) {
return getTeamByID(x, teamId)
}
// UpdateTeam updates information of team.
func UpdateTeam(t *Team, authChanged bool) (err error) {
if len(t.Name) == 0 {
return errors.New("empty team name")
}
if len(t.Description) > 255 {
t.Description = t.Description[:255]
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
t.LowerName = strings.ToLower(t.Name)
has, err := x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).And("id!=?", t.ID).Get(new(Team))
if err != nil {
return err
} else if has {
return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
}
if _, err = sess.Id(t.ID).AllCols().Update(t); err != nil {
return fmt.Errorf("update: %v", err)
}
// Update access for team members if needed.
if authChanged {
if err = t.getRepositories(sess); err != nil {
return fmt.Errorf("getRepositories:%v", err)
}
for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
return fmt.Errorf("recalculateTeamAccesses: %v", err)
}
}
}
return sess.Commit()
}
// DeleteTeam deletes given team.
// It's caller's responsibility to assign organization ID.
func DeleteTeam(t *Team) error {
if err := t.GetRepositories(); err != nil {
return err
}
// Get organization.
org, err := GetUserByID(t.OrgID)
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
// Delete all accesses.
for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(sess, t.ID); err != nil {
return err
}
}
// Delete team-user.
if _, err = sess.Where("org_id=?", org.ID).Where("team_id=?", t.ID).Delete(new(TeamUser)); err != nil {
return err
}
// Delete team.
if _, err = sess.Id(t.ID).Delete(new(Team)); err != nil {
return err
}
// Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
return err
}
return sess.Commit()
}
// ___________ ____ ___
// \__ ___/___ _____ _____ | | \______ ___________
// | |_/ __ \\__ \ / \| | / ___// __ \_ __ \
// | |\ ___/ / __ \| Y Y \ | /\___ \\ ___/| | \/
// |____| \___ >____ /__|_| /______//____ >\___ >__|
// \/ \/ \/ \/ \/
// TeamUser represents an team-user relation.
type TeamUser struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
TeamID int64 `xorm:"UNIQUE(s)"`
Uid int64 `xorm:"UNIQUE(s)"`
}
func isTeamMember(e Engine, orgID, teamID, uid int64) bool {
has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("uid=?", uid).Get(new(TeamUser))
return has
}
// IsTeamMember returns true if given user is a member of team.
func IsTeamMember(orgID, teamID, uid int64) bool {
return isTeamMember(x, orgID, teamID, uid)
}
func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) {
teamUsers := make([]*TeamUser, 0, 10)
if err = e.Where("team_id=?", teamID).Find(&teamUsers); err != nil {
return nil, fmt.Errorf("get team-users: %v", err)
}
members := make([]*User, 0, len(teamUsers))
for i := range teamUsers {
member := new(User)
if _, err = e.Id(teamUsers[i].Uid).Get(member); err != nil {
return nil, fmt.Errorf("get user '%d': %v", teamUsers[i].Uid, err)
}
members = append(members, member)
}
return members, nil
}
// GetTeamMembers returns all members in given team of organization.
func GetTeamMembers(teamID int64) ([]*User, error) {
return getTeamMembers(x, teamID)
}
func getUserTeams(e Engine, orgId, uid int64) ([]*Team, error) {
tus := make([]*TeamUser, 0, 5)
if err := e.Where("uid=?", uid).And("org_id=?", orgId).Find(&tus); err != nil {
return nil, err
}
ts := make([]*Team, len(tus))
for i, tu := range tus {
t := new(Team)
has, err := e.Id(tu.TeamID).Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrTeamNotExist
}
ts[i] = t
}
return ts, nil
}
// GetUserTeams returns all teams that user belongs to in given organization.
func GetUserTeams(orgId, uid int64) ([]*Team, error) {
return getUserTeams(x, orgId, uid)
}
// AddTeamMember adds new membership of given team to given organization,
// the user will have membership to given organization automatically when needed.
func AddTeamMember(orgID, teamID, uid int64) error {
if IsTeamMember(orgID, teamID, uid) {
return nil
}
if err := AddOrgUser(orgID, uid); err != nil {
return err
}
// Get team and its repositories.
t, err := GetTeamByID(teamID)
if err != nil {
return err
}
t.NumMembers++
if err = t.GetRepositories(); err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
tu := &TeamUser{
Uid: uid,
OrgID: orgID,
TeamID: teamID,
}
if _, err = sess.Insert(tu); err != nil {
return err
} else if _, err = sess.Id(t.ID).Update(t); err != nil {
return err
}
// Give access to team repositories.
for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
return err
}
}
// We make sure it exists before.
ou := new(OrgUser)
if _, err = sess.Where("uid = ?", uid).And("org_id = ?", orgID).Get(ou); err != nil {
return err
}
ou.NumTeams++
if t.IsOwnerTeam() {
ou.IsOwner = true
}
if _, err = sess.Id(ou.ID).AllCols().Update(ou); err != nil {
return err
}
return sess.Commit()
}
func removeTeamMember(e Engine, orgID, teamID, uid int64) error {
if !isTeamMember(e, orgID, teamID, uid) {
return nil
}
// Get team and its repositories.
t, err := getTeamByID(e, teamID)
if err != nil {
return err
}
// Check if the user to delete is the last member in owner team.
if t.IsOwnerTeam() && t.NumMembers == 1 {
return ErrLastOrgOwner{UID: uid}
}
t.NumMembers--
if err = t.getRepositories(e); err != nil {
return err
}
// Get organization.
org, err := getUserByID(e, orgID)
if err != nil {
return err
}
tu := &TeamUser{
Uid: uid,
OrgID: orgID,
TeamID: teamID,
}
if _, err := e.Delete(tu); err != nil {
return err
} else if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
return err
}
// Delete access to team repositories.
for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
return err
}
}
// This must exist.
ou := new(OrgUser)
_, err = e.Where("uid = ?", uid).And("org_id = ?", org.ID).Get(ou)
if err != nil {
return err
}
ou.NumTeams--
if t.IsOwnerTeam() {
ou.IsOwner = false
}
if _, err = e.Id(ou.ID).AllCols().Update(ou); err != nil {
return err
}
return nil
}
// RemoveTeamMember removes member from given team of given organization.
func RemoveTeamMember(orgID, teamID, uid int64) error {
sess := x.NewSession()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return err
}
if err := removeTeamMember(sess, orgID, teamID, uid); err != nil {
return err
}
return sess.Commit()
}
// ___________ __________
// \__ ___/___ _____ _____\______ \ ____ ______ ____
// | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \
// | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> )
// |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/
// \/ \/ \/ \/ \/|__|
// TeamRepo represents an team-repository relation.
type TeamRepo struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
TeamID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
}
func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool {
has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("repo_id=?", repoID).Get(new(TeamRepo))
return has
}
// HasTeamRepo returns true if given repository belongs to team.
func HasTeamRepo(orgID, teamID, repoID int64) bool {
return hasTeamRepo(x, orgID, teamID, repoID)
}
func addTeamRepo(e Engine, orgID, teamID, repoID int64) error {
_, err := e.InsertOne(&TeamRepo{
OrgID: orgID,
TeamID: teamID,
RepoID: repoID,
})
return err
}
// AddTeamRepo adds new repository relation to team.
func AddTeamRepo(orgID, teamID, repoID int64) error {
return addTeamRepo(x, orgID, teamID, repoID)
}
func removeTeamRepo(e Engine, teamID, repoID int64) error {
_, err := e.Delete(&TeamRepo{
TeamID: teamID,
RepoID: repoID,
})
return err
}
// RemoveTeamRepo deletes repository relation to team.
func RemoveTeamRepo(teamID, repoID int64) error {
return removeTeamRepo(x, teamID, repoID)
}

View File

@@ -65,7 +65,7 @@ type PullRequest struct {
}
func (pr *PullRequest) BeforeUpdate() {
pr.MergedUnix = pr.Merged.UTC().Unix()
pr.MergedUnix = pr.Merged.Unix()
}
// Note: don't try to get Pull because will end up recursive querying.
@@ -163,7 +163,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
pr.HasMerged = true
pr.Merged = time.Now()
pr.MergerID = doer.Id
pr.MergerID = doer.ID
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
return fmt.Errorf("update pull request: %v", err)
}
@@ -236,7 +236,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
Before: pr.MergeBase,
After: pr.MergedCommitID,
CompareUrl: setting.AppUrl + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
Commits: ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.FullRepoLink()),
Commits: ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.FullLink()),
Repo: pr.BaseRepo.ComposePayload(),
Pusher: &api.PayloadAuthor{
Name: pr.HeadRepo.MustOwner().DisplayName(),
@@ -245,7 +245,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
},
Sender: &api.PayloadUser{
UserName: doer.Name,
ID: doer.Id,
ID: doer.ID,
AvatarUrl: setting.AppUrl + doer.RelAvatarLink(),
},
}
@@ -332,7 +332,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
// Notify watchers.
act := &Action{
ActUserID: pull.Poster.Id,
ActUserID: pull.Poster.ID,
ActUserName: pull.Poster.Name,
ActEmail: pull.Poster.Email,
OpType: ACTION_CREATE_PULL_REQUEST,
@@ -342,9 +342,6 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
}
if err = notifyWatchers(sess, act); err != nil {
return err
}
pr.Index = pull.Index
if err = repo.SavePatch(pr.Index, patch); err != nil {
@@ -364,7 +361,17 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
return fmt.Errorf("insert pull repo: %v", err)
}
return sess.Commit()
if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
if err = NotifyWatchers(act); err != nil {
log.Error(4, "NotifyWatchers: %v", err)
} else if err = pull.MailParticipants(); err != nil {
log.Error(4, "MailParticipants: %v", err)
}
return nil
}
// GetUnmergedPullRequest returnss a pull request that is open and has not been merged

View File

@@ -39,7 +39,7 @@ type Release struct {
}
func (r *Release) BeforeInsert() {
r.CreatedUnix = time.Now().UTC().Unix()
r.CreatedUnix = time.Now().Unix()
}
func (r *Release) AfterSet(colName string, _ xorm.Cell) {
@@ -67,7 +67,12 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
return fmt.Errorf("GetBranchCommit: %v", err)
}
// Trim '--' prefix to prevent command line argument vulnerability.
rel.TagName = strings.TrimPrefix(rel.TagName, "--")
if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
if strings.Contains(err.Error(), "is not a valid tag name") {
return ErrInvalidTagName{rel.TagName}
}
return err
}
} else {
@@ -76,6 +81,7 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
return fmt.Errorf("GetTagCommit: %v", err)
}
rel.Sha1 = commit.ID.String()
rel.NumCommits, err = commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %v", err)

View File

@@ -19,7 +19,6 @@ import (
"strings"
"sync"
"time"
"unicode/utf8"
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
@@ -129,8 +128,7 @@ func NewRepoContext() {
log.Fatal(4, "Fail to execute 'git config --global core.quotepath false': %s", stderr)
}
// Clean up temporary data.
os.RemoveAll(filepath.Join(setting.AppDataPath, "tmp"))
RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp"))
}
// Repository represents a git repository.
@@ -171,6 +169,7 @@ type Repository struct {
EnableIssues bool `xorm:"NOT NULL DEFAULT true"`
EnableExternalTracker bool
ExternalTrackerFormat string
ExternalTrackerStyle string
ExternalMetas map[string]string `xorm:"-"`
EnablePulls bool `xorm:"NOT NULL DEFAULT true"`
@@ -185,12 +184,12 @@ type Repository struct {
}
func (repo *Repository) BeforeInsert() {
repo.CreatedUnix = time.Now().UTC().Unix()
repo.CreatedUnix = time.Now().Unix()
repo.UpdatedUnix = repo.CreatedUnix
}
func (repo *Repository) BeforeUpdate() {
repo.UpdatedUnix = time.Now().UTC().Unix()
repo.UpdatedUnix = time.Now().Unix()
}
func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
@@ -206,6 +205,10 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
case "num_closed_milestones":
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
case "external_tracker_style":
if len(repo.ExternalTrackerStyle) == 0 {
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC
}
case "created_unix":
repo.Created = time.Unix(repo.CreatedUnix, 0).Local()
case "updated_unix":
@@ -255,6 +258,13 @@ func (repo *Repository) ComposeMetas() map[string]string {
"user": repo.MustOwner().Name,
"repo": repo.Name,
}
switch repo.ExternalTrackerStyle {
case markdown.ISSUE_NAME_STYLE_ALPHANUMERIC:
repo.ExternalMetas["style"] = markdown.ISSUE_NAME_STYLE_ALPHANUMERIC
default:
repo.ExternalMetas["style"] = markdown.ISSUE_NAME_STYLE_NUMERIC
}
}
return repo.ExternalMetas
}
@@ -335,11 +345,11 @@ func (repo *Repository) GitConfigPath() string {
return filepath.Join(repo.RepoPath(), "config")
}
func (repo *Repository) RepoLink() string {
func (repo *Repository) Link() string {
return setting.AppSubUrl + "/" + repo.MustOwner().Name + "/" + repo.Name
}
func (repo *Repository) RepoRelLink() string {
func (repo *Repository) RelLink() string {
return "/" + repo.MustOwner().Name + "/" + repo.Name
}
@@ -347,7 +357,7 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID)
}
func (repo *Repository) FullRepoLink() string {
func (repo *Repository) FullLink() string {
return setting.AppUrl + repo.MustOwner().Name + "/" + repo.Name
}
@@ -448,7 +458,7 @@ func (repo *Repository) ComposePayload() *api.PayloadRepo {
return &api.PayloadRepo{
ID: repo.ID,
Name: repo.Name,
URL: repo.FullRepoLink(),
URL: repo.FullLink(),
SSHURL: cl.SSH,
CloneURL: cl.HTTPS,
Description: repo.Description,
@@ -466,7 +476,7 @@ func (repo *Repository) ComposePayload() *api.PayloadRepo {
func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
has, err := e.Get(&Repository{
OwnerID: u.Id,
OwnerID: u.ID,
LowerName: strings.ToLower(repoName),
})
return has && com.IsDir(RepoPath(u.Name, repoName)), err
@@ -506,40 +516,13 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
return repo.cloneLink(false)
}
var (
reservedNames = []string{"debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new"}
reservedPatterns = []string{"*.git", "*.keys", "*.wiki"}
)
// IsUsableName checks if name is reserved or pattern of name is not allowed.
func IsUsableName(name string) error {
name = strings.TrimSpace(strings.ToLower(name))
if utf8.RuneCountInString(name) == 0 {
return ErrNameEmpty
}
for i := range reservedNames {
if name == reservedNames[i] {
return ErrNameReserved{name}
}
}
for _, pat := range reservedPatterns {
if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) ||
(pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) {
return ErrNamePatternNotAllowed{pat}
}
}
return nil
}
// Mirror represents a mirror information of repository.
type Mirror struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64
Repo *Repository `xorm:"-"`
Interval int // Hour.
ID int64 `xorm:"pk autoincr"`
RepoID int64
Repo *Repository `xorm:"-"`
Interval int // Hour.
EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
Updated time.Time `xorm:"-"`
UpdatedUnix int64
@@ -550,12 +533,12 @@ type Mirror struct {
}
func (m *Mirror) BeforeInsert() {
m.NextUpdateUnix = m.NextUpdate.UTC().Unix()
m.NextUpdateUnix = m.NextUpdate.Unix()
}
func (m *Mirror) BeforeUpdate() {
m.UpdatedUnix = time.Now().UTC().Unix()
m.NextUpdateUnix = m.NextUpdate.UTC().Unix()
m.UpdatedUnix = time.Now().Unix()
m.NextUpdateUnix = m.NextUpdate.Unix()
}
func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
@@ -645,7 +628,7 @@ func GetMirror(repoId int64) (*Mirror, error) {
}
func updateMirror(e Engine, m *Mirror) error {
_, err := e.Id(m.ID).Update(m)
_, err := e.Id(m.ID).AllCols().Update(m)
return err
}
@@ -735,9 +718,10 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
if opts.IsMirror {
if _, err = x.InsertOne(&Mirror{
RepoID: repo.ID,
Interval: 24,
NextUpdate: time.Now().Add(24 * time.Hour),
RepoID: repo.ID,
Interval: 24,
EnablePrune: true,
NextUpdate: time.Now().Add(24 * time.Hour),
}); err != nil {
return repo, fmt.Errorf("InsertOne: %v", err)
}
@@ -927,8 +911,17 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C
return nil
}
var (
reservedRepoNames = []string{".", ".."}
reservedRepoPatterns = []string{"*.git", "*.wiki"}
)
func IsUsableRepoName(name string) error {
return isUsableName(reservedRepoNames, reservedRepoPatterns, name)
}
func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
if err = IsUsableName(repo.Name); err != nil {
if err = IsUsableRepoName(repo.Name); err != nil {
return err
}
@@ -965,7 +958,7 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
}
}
if err = watchRepo(e, u.Id, repo.ID, true); err != nil {
if err = watchRepo(e, u.ID, repo.ID, true); err != nil {
return fmt.Errorf("watchRepo: %v", err)
} else if err = newRepoAction(e, u, repo); err != nil {
return fmt.Errorf("newRepoAction: %v", err)
@@ -981,7 +974,7 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error
}
repo := &Repository{
OwnerID: u.Id,
OwnerID: u.ID,
Owner: u,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
@@ -1025,11 +1018,14 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error
return repo, sess.Commit()
}
func countRepositories(showPrivate bool) int64 {
sess := x.NewSession()
func countRepositories(userID int64, private bool) int64 {
sess := x.Where("id > 0")
if !showPrivate {
sess.Where("is_private=?", false)
if userID > 0 {
sess.And("owner_id = ?", userID)
}
if !private {
sess.And("is_private=?", false)
}
count, err := sess.Count(new(Repository))
@@ -1040,13 +1036,17 @@ func countRepositories(showPrivate bool) int64 {
}
// CountRepositories returns number of repositories.
func CountRepositories() int64 {
return countRepositories(true)
// Argument private only takes effect when it is false,
// set it true to count all repositories.
func CountRepositories(private bool) int64 {
return countRepositories(-1, private)
}
// CountPublicRepositories returns number of public repositories.
func CountPublicRepositories() int64 {
return countRepositories(false)
// CountUserRepositories returns number of repositories user owns.
// Argument private only takes effect when it is false,
// set it true to count all repositories.
func CountUserRepositories(userID int64, private bool) int64 {
return countRepositories(userID, private)
}
func Repositories(page, pageSize int) (_ []*Repository, err error) {
@@ -1100,7 +1100,7 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
// Note: we have to set value here to make sure recalculate accesses is based on
// new owner.
repo.OwnerID = newOwner.Id
repo.OwnerID = newOwner.ID
repo.Owner = newOwner
// Update repository.
@@ -1117,10 +1117,10 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
// Dummy object.
collaboration := &Collaboration{RepoID: repo.ID}
for _, c := range collaborators {
collaboration.UserID = c.Id
if c.Id == newOwner.Id || newOwner.IsOrgMember(c.Id) {
collaboration.UserID = c.ID
if c.ID == newOwner.ID || newOwner.IsOrgMember(c.ID) {
if _, err = sess.Delete(collaboration); err != nil {
return fmt.Errorf("remove collaborator '%d': %v", c.Id, err)
return fmt.Errorf("remove collaborator '%d': %v", c.ID, err)
}
}
}
@@ -1161,19 +1161,20 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
// Update repository count.
if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.Id); err != nil {
if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil {
return fmt.Errorf("increase new owner repository count: %v", err)
} else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.Id); err != nil {
} else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID); err != nil {
return fmt.Errorf("decrease old owner repository count: %v", err)
}
if err = watchRepo(sess, newOwner.Id, repo.ID, true); err != nil {
if err = watchRepo(sess, newOwner.ID, repo.ID, true); err != nil {
return fmt.Errorf("watchRepo: %v", err)
} else if err = transferRepoAction(sess, u, owner, newOwner, repo); err != nil {
return fmt.Errorf("transferRepoAction: %v", err)
}
// Rename remote repository to new path and delete local copy.
os.MkdirAll(UserPath(newOwner.Name), os.ModePerm)
if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
return fmt.Errorf("rename repository directory: %v", err)
}
@@ -1195,7 +1196,7 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) {
oldRepoName = strings.ToLower(oldRepoName)
newRepoName = strings.ToLower(newRepoName)
if err = IsUsableName(newRepoName); err != nil {
if err = IsUsableRepoName(newRepoName); err != nil {
return err
}
@@ -1206,7 +1207,7 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error)
return ErrRepoAlreadyExist{u.Name, newRepoName}
}
repo, err := GetRepositoryByName(u.Id, oldRepoName)
repo, err := GetRepositoryByName(u.ID, oldRepoName)
if err != nil {
return fmt.Errorf("GetRepositoryByName: %v", err)
}
@@ -1398,20 +1399,8 @@ func DeleteRepository(uid, repoID int64) error {
}
if repo.NumForks > 0 {
if repo.IsPrivate {
forkRepos, err := GetRepositoriesByForkID(repo.ID)
if err != nil {
return fmt.Errorf("getRepositoriesByForkID: %v", err)
}
for i := range forkRepos {
if err = DeleteRepository(forkRepos[i].OwnerID, forkRepos[i].ID); err != nil {
log.Error(4, "DeleteRepository [%d]: %v", forkRepos[i].ID, err)
}
}
} else {
if _, err = x.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
log.Error(4, "reset 'fork_id' and 'is_fork': %v", err)
}
if _, err = x.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
log.Error(4, "reset 'fork_id' and 'is_fork': %v", err)
}
}
@@ -1432,7 +1421,7 @@ func GetRepositoryByRef(ref string) (*Repository, error) {
return nil, err
}
return GetRepositoryByName(user.Id, repoName)
return GetRepositoryByName(user.ID, repoName)
}
// GetRepositoryByName returns the repository by given name under user if exists.
@@ -1466,16 +1455,26 @@ func GetRepositoryByID(id int64) (*Repository, error) {
return getRepositoryByID(x, id)
}
// GetRepositories returns a list of repositories of given user.
func GetRepositories(uid int64, private bool) ([]*Repository, error) {
repos := make([]*Repository, 0, 10)
sess := x.Desc("updated_unix")
// GetUserRepositories returns a list of repositories of given user.
func GetUserRepositories(userID int64, private bool, page, pageSize int) ([]*Repository, error) {
sess := x.Where("owner_id = ?", userID).Desc("updated_unix")
if !private {
sess.Where("is_private=?", false)
sess.And("is_private=?", false)
}
return repos, sess.Find(&repos, &Repository{OwnerID: uid})
if page <= 0 {
page = 1
}
sess.Limit(pageSize, (page-1)*pageSize)
repos := make([]*Repository, 0, pageSize)
return repos, sess.Find(&repos)
}
// GetUserRepositories returns a list of mirror repositories of given user.
func GetUserMirrorRepositories(userID int64) ([]*Repository, error) {
repos := make([]*Repository, 0, 10)
return repos, x.Where("owner_id = ?", userID).And("is_mirror = ?", true).Find(&repos)
}
// GetRecentUpdatedRepositories returns the list of repositories that are recently updated.
@@ -1485,7 +1484,7 @@ func GetRecentUpdatedRepositories(page, pageSize int) (repos []*Repository, err
}
func getRepositoryCount(e Engine, u *User) (int64, error) {
return x.Count(&Repository{OwnerID: u.Id})
return x.Count(&Repository{OwnerID: u.ID})
}
// GetRepositoryCount returns the total number of repositories of user.
@@ -1510,9 +1509,6 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos []*Repository, _ int
}
opts.Keyword = strings.ToLower(opts.Keyword)
if opts.PageSize <= 0 || opts.PageSize > setting.ExplorePagingNum {
opts.PageSize = setting.ExplorePagingNum
}
if opts.Page <= 0 {
opts.Page = 1
}
@@ -1672,22 +1668,24 @@ func MirrorUpdate() {
log.Trace("Doing: MirrorUpdate")
mirrors := make([]*Mirror, 0, 10)
if err := x.Iterate(new(Mirror), func(idx int, bean interface{}) error {
if err := x.Where("next_update_unix<=?", time.Now().Unix()).Iterate(new(Mirror), func(idx int, bean interface{}) error {
m := bean.(*Mirror)
if m.NextUpdate.After(time.Now()) {
return nil
}
if m.Repo == nil {
log.Error(4, "Disconnected mirror repository found: %d", m.ID)
return nil
}
repoPath := m.Repo.RepoPath()
gitArgs := []string{"remote", "update"}
if m.EnablePrune {
gitArgs = append(gitArgs, "--prune")
}
if _, stderr, err := process.ExecDir(
time.Duration(setting.Git.Timeout.Mirror)*time.Second,
repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
"git", "remote", "update", "--prune"); err != nil {
"git", gitArgs...); err != nil {
desc := fmt.Sprintf("Fail to update mirror repository(%s): %s", repoPath, stderr)
log.Error(4, desc)
if err = CreateRepositoryNotice(desc); err != nil {
@@ -1819,9 +1817,26 @@ func CheckRepoStats() {
repoStatsCheck(checkers[i])
}
// FIXME: use checker when v0.9, stop supporting old fork repo format.
// ***** START: Repository.NumClosedIssues *****
desc := "repository count 'num_closed_issues'"
results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false)
if err != nil {
log.Error(4, "Select %s: %v", desc, err)
} else {
for _, result := range results {
id := com.StrTo(result["id"]).MustInt64()
log.Trace("Updating %s: %d", desc, id)
_, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id)
if err != nil {
log.Error(4, "Update %s[%d]: %v", desc, id, err)
}
}
}
// ***** END: Repository.NumClosedIssues *****
// FIXME: use checker when stop supporting old fork repo format.
// ***** START: Repository.NumForks *****
results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
if err != nil {
log.Error(4, "Select repository count 'num_forks': %v", err)
} else {
@@ -1851,6 +1866,74 @@ func CheckRepoStats() {
// ***** END: Repository.NumForks *****
}
type RepositoryList []*Repository
func (repos RepositoryList) loadAttributes(e Engine) error {
if len(repos) == 0 {
return nil
}
// Load owners.
set := make(map[int64]*User)
for i := range repos {
set[repos[i].OwnerID] = nil
}
userIDs := make([]int64, 0, len(set))
for userID := range set {
userIDs = append(userIDs, userID)
}
users := make([]*User, 0, len(userIDs))
if err := e.Where("id > 0").In("id", userIDs).Find(&users); err != nil {
return fmt.Errorf("find users: %v", err)
}
for i := range users {
set[users[i].ID] = users[i]
}
for i := range repos {
repos[i].Owner = set[repos[i].OwnerID]
}
return nil
}
func (repos RepositoryList) LoadAttributes() error {
return repos.loadAttributes(x)
}
type MirrorRepositoryList []*Repository
func (repos MirrorRepositoryList) loadAttributes(e Engine) error {
if len(repos) == 0 {
return nil
}
// Load mirrors.
repoIDs := make([]int64, 0, len(repos))
for i := range repos {
if !repos[i].IsMirror {
continue
}
repoIDs = append(repoIDs, repos[i].ID)
}
mirrors := make([]*Mirror, 0, len(repoIDs))
if err := e.Where("id > 0").In("repo_id", repoIDs).Find(&mirrors); err != nil {
return fmt.Errorf("find mirrors: %v", err)
}
set := make(map[int64]*Mirror)
for i := range mirrors {
set[mirrors[i].RepoID] = mirrors[i]
}
for i := range repos {
repos[i].Mirror = set[repos[i].ID]
}
return nil
}
func (repos MirrorRepositoryList) LoadAttributes() error {
return repos.loadAttributes(x)
}
// __ __ __ .__
// / \ / \_____ _/ |_ ____ | |__
// \ \/\/ /\__ \\ __\/ ___\| | \
@@ -1865,40 +1948,40 @@ type Watch struct {
RepoID int64 `xorm:"UNIQUE(watch)"`
}
func isWatching(e Engine, uid, repoId int64) bool {
has, _ := e.Get(&Watch{0, uid, repoId})
func isWatching(e Engine, userID, repoID int64) bool {
has, _ := e.Get(&Watch{0, userID, repoID})
return has
}
// IsWatching checks if user has watched given repository.
func IsWatching(uid, repoId int64) bool {
return isWatching(x, uid, repoId)
func IsWatching(userID, repoID int64) bool {
return isWatching(x, userID, repoID)
}
func watchRepo(e Engine, uid, repoId int64, watch bool) (err error) {
func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) {
if watch {
if isWatching(e, uid, repoId) {
if isWatching(e, userID, repoID) {
return nil
}
if _, err = e.Insert(&Watch{RepoID: repoId, UserID: uid}); err != nil {
if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil {
return err
}
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId)
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID)
} else {
if !isWatching(e, uid, repoId) {
if !isWatching(e, userID, repoID) {
return nil
}
if _, err = e.Delete(&Watch{0, uid, repoId}); err != nil {
if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil {
return err
}
_, err = e.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", repoId)
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID)
}
return err
}
// Watch or unwatch repository.
func WatchRepo(uid, repoId int64, watch bool) (err error) {
return watchRepo(x, uid, repoId, watch)
func WatchRepo(userID, repoID int64, watch bool) (err error) {
return watchRepo(x, userID, repoID, watch)
}
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
@@ -1969,34 +2052,34 @@ type Star struct {
}
// Star or unstar repository.
func StarRepo(uid, repoId int64, star bool) (err error) {
func StarRepo(userID, repoID int64, star bool) (err error) {
if star {
if IsStaring(uid, repoId) {
if IsStaring(userID, repoID) {
return nil
}
if _, err = x.Insert(&Star{UID: uid, RepoID: repoId}); err != nil {
if _, err = x.Insert(&Star{UID: userID, RepoID: repoID}); err != nil {
return err
} else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoId); err != nil {
} else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoID); err != nil {
return err
}
_, err = x.Exec("UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", uid)
_, err = x.Exec("UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", userID)
} else {
if !IsStaring(uid, repoId) {
if !IsStaring(userID, repoID) {
return nil
}
if _, err = x.Delete(&Star{0, uid, repoId}); err != nil {
if _, err = x.Delete(&Star{0, userID, repoID}); err != nil {
return err
} else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoId); err != nil {
} else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil {
return err
}
_, err = x.Exec("UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", uid)
_, err = x.Exec("UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", userID)
}
return err
}
// IsStaring checks if user has starred given repository.
func IsStaring(uid, repoId int64) bool {
has, _ := x.Get(&Star{0, uid, repoId})
func IsStaring(userID, repoID int64) bool {
has, _ := x.Get(&Star{0, userID, repoID})
return has
}
@@ -2027,7 +2110,7 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
repo := &Repository{
OwnerID: u.Id,
OwnerID: u.ID,
Owner: u,
Name: name,
LowerName: strings.ToLower(name),

View File

@@ -16,23 +16,24 @@ type Collaboration struct {
Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"`
}
func (c *Collaboration) ModeName() string {
func (c *Collaboration) ModeI18nKey() string {
switch c.Mode {
case ACCESS_MODE_READ:
return "Read"
return "repo.settings.collaboration.read"
case ACCESS_MODE_WRITE:
return "Write"
return "repo.settings.collaboration.write"
case ACCESS_MODE_ADMIN:
return "Admin"
return "repo.settings.collaboration.admin"
default:
return "repo.settings.collaboration.undefined"
}
return "Undefined"
}
// AddCollaborator adds new collaboration relation between an individual and a repository.
func (repo *Repository) AddCollaborator(u *User) error {
collaboration := &Collaboration{
RepoID: repo.ID,
UserID: u.Id,
UserID: u.ID,
}
has, err := x.Get(collaboration)

62
models/repo_test.go Normal file
View File

@@ -0,0 +1,62 @@
package models_test
import (
. "github.com/gogits/gogs/models"
. "github.com/smartystreets/goconvey/convey"
"testing"
"github.com/gogits/gogs/modules/markdown"
)
func TestRepo(t *testing.T) {
Convey("The metas map", t, func() {
var repo = new(Repository)
repo.Name = "testrepo"
repo.Owner = new(User)
repo.Owner.Name = "testuser"
repo.ExternalTrackerFormat = "https://someurl.com/{user}/{repo}/{issue}"
Convey("When no external tracker is configured", func() {
Convey("It should be nil", func() {
repo.EnableExternalTracker = false
So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil))
})
Convey("It should be nil even if other settings are present", func() {
repo.EnableExternalTracker = false
repo.ExternalTrackerFormat = "http://someurl.com/{user}/{repo}/{issue}"
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC
So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil))
})
})
Convey("When an external issue tracker is configured", func() {
repo.EnableExternalTracker = true
Convey("It should default to numeric issue style", func() {
metas := repo.ComposeMetas()
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_NUMERIC)
})
Convey("It should pass through numeric issue style setting", func() {
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC
metas := repo.ComposeMetas()
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_NUMERIC)
})
Convey("It should pass through alphanumeric issue style setting", func() {
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_ALPHANUMERIC
metas := repo.ComposeMetas()
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_ALPHANUMERIC)
})
Convey("It should contain the user name", func() {
metas := repo.ComposeMetas()
So(metas["user"], ShouldEqual, "testuser")
})
Convey("It should contain the repo name", func() {
metas := repo.ComposeMetas()
So(metas["repo"], ShouldEqual, "testrepo")
})
Convey("It should contain the URL format", func() {
metas := repo.ComposeMetas()
So(metas["format"], ShouldEqual, "https://someurl.com/{user}/{repo}/{issue}")
})
})
})
}

View File

@@ -5,12 +5,10 @@
package models
import (
"bufio"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"os"
@@ -24,17 +22,17 @@ import (
"github.com/go-xorm/xorm"
"golang.org/x/crypto/ssh"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process"
"github.com/gogits/gogs/modules/setting"
)
const (
// "### autogenerated by gitgos, DO NOT EDIT\n"
_TPL_PUBLICK_KEY = `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
)
var sshOpLocker = sync.Mutex{}
var sshOpLocker sync.Mutex
type KeyType int
@@ -43,7 +41,7 @@ const (
KEY_TYPE_DEPLOY
)
// PublicKey represents a SSH or deploy key.
// PublicKey represents a user or deploy SSH public key.
type PublicKey struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
@@ -62,11 +60,11 @@ type PublicKey struct {
}
func (k *PublicKey) BeforeInsert() {
k.CreatedUnix = time.Now().UTC().Unix()
k.CreatedUnix = time.Now().Unix()
}
func (k *PublicKey) BeforeUpdate() {
k.UpdatedUnix = time.Now().UTC().Unix()
k.UpdatedUnix = time.Now().Unix()
}
func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) {
@@ -80,45 +78,44 @@ func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) {
}
}
// OmitEmail returns content of public key but without e-mail address.
// OmitEmail returns content of public key without email address.
func (k *PublicKey) OmitEmail() string {
return strings.Join(strings.Split(k.Content, " ")[:2], " ")
}
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
func (key *PublicKey) GetAuthorizedString() string {
// AuthorizedString returns formatted public key string for authorized_keys file.
func (key *PublicKey) AuthorizedString() string {
return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, key.ID, setting.CustomConf, key.Content)
}
func extractTypeFromBase64Key(key string) (string, error) {
b, err := base64.StdEncoding.DecodeString(key)
if err != nil || len(b) < 4 {
return "", fmt.Errorf("Invalid key format: %v", err)
return "", fmt.Errorf("invalid key format: %v", err)
}
keyLength := int(binary.BigEndian.Uint32(b))
if len(b) < 4+keyLength {
return "", fmt.Errorf("Invalid key format: not enough length")
return "", fmt.Errorf("invalid key format: not enough length %d", keyLength)
}
return string(b[4 : 4+keyLength]), nil
}
// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253)
// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253).
func parseKeyString(content string) (string, error) {
// Transform all legal line endings to a single "\n"
s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1)
lines := strings.Split(s, "\n")
// Transform all legal line endings to a single "\n".
content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
lines := strings.Split(content, "\n")
var keyType, keyContent, keyComment string
if len(lines) == 1 {
// Parse openssh format
// Parse OpenSSH format.
parts := strings.SplitN(lines[0], " ", 3)
switch len(parts) {
case 0:
return "", errors.New("Empty key")
return "", errors.New("empty key")
case 1:
keyContent = parts[0]
case 2:
@@ -130,17 +127,15 @@ func parseKeyString(content string) (string, error) {
keyComment = parts[2]
}
// If keyType is not given, extract it from content. If given, validate it
// If keyType is not given, extract it from content. If given, validate it.
t, err := extractTypeFromBase64Key(keyContent)
if err != nil {
return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
}
if len(keyType) == 0 {
if t, err := extractTypeFromBase64Key(keyContent); err == nil {
keyType = t
} else {
return "", err
}
} else {
if t, err := extractTypeFromBase64Key(keyContent); err != nil || keyType != t {
return "", err
}
keyType = t
} else if keyType != t {
return "", fmt.Errorf("key type and content does not match: %s - %s", keyType, t)
}
} else {
// Parse SSH2 file format.
@@ -158,11 +153,11 @@ func parseKeyString(content string) (string, error) {
}
}
if t, err := extractTypeFromBase64Key(keyContent); err == nil {
keyType = t
} else {
return "", err
t, err := extractTypeFromBase64Key(keyContent)
if err != nil {
return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
}
keyType = t
}
return keyType + " " + keyContent + " " + keyComment, nil
}
@@ -177,7 +172,7 @@ func writeTmpKeyFile(content string) (string, error) {
defer tmpFile.Close()
if _, err = tmpFile.WriteString(content); err != nil {
return "", fmt.Errorf("tmpFile.WriteString: %v", err)
return "", fmt.Errorf("WriteString: %v", err)
}
return tmpFile.Name(), nil
}
@@ -197,7 +192,7 @@ func SSHKeyGenParsePublicKey(key string) (string, int, error) {
stdout, stderr, err := process.Exec("SSHKeyGenParsePublicKey", setting.SSH.KeygenPath, "-lf", tmpName)
if err != nil {
return "", 0, fmt.Errorf("Fail to parse public key: %s - %s", err, stderr)
return "", 0, fmt.Errorf("fail to parse public key: %s - %s", err, stderr)
}
if strings.Contains(stdout, "is not a public key file") {
return "", 0, ErrKeyUnableVerify{stdout}
@@ -205,7 +200,7 @@ func SSHKeyGenParsePublicKey(key string) (string, int, error) {
fields := strings.Split(stdout, " ")
if len(fields) < 4 {
return "", 0, fmt.Errorf("Invalid public key line: %s", stdout)
return "", 0, fmt.Errorf("invalid public key line: %s", stdout)
}
keyType := strings.Trim(fields[len(fields)-1], "()\r\n")
@@ -230,7 +225,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
if strings.Contains(err.Error(), "ssh: unknown key algorithm") {
return "", 0, ErrKeyUnableVerify{err.Error()}
}
return "", 0, fmt.Errorf("ssh.ParsePublicKey: %v", err)
return "", 0, fmt.Errorf("ParsePublicKey: %v", err)
}
// The ssh library can parse the key, so next we find out what key exactly we have.
@@ -262,15 +257,14 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
return "ecdsa", 384, nil
case ssh.KeyAlgoECDSA521:
return "ecdsa", 521, nil
case "ssh-ed25519": // TODO replace with ssh constant when available
case "ssh-ed25519": // TODO: replace with ssh constant when available
return "ed25519", 256, nil
}
return "", 0, fmt.Errorf("Unsupported key length detection for type: %s", pkey.Type())
return "", 0, fmt.Errorf("unsupported key length detection for type: %s", pkey.Type())
}
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
//
// The function returns the actual public key line on success.
// It returns the actual public key line on success.
func CheckPublicKeyString(content string) (_ string, err error) {
if setting.SSH.Disabled {
return "", errors.New("SSH is disabled")
@@ -290,16 +284,19 @@ func CheckPublicKeyString(content string) (_ string, err error) {
content = strings.TrimSpace(content)
var (
fnName string
keyType string
length int
)
if setting.SSH.StartBuiltinServer {
fnName = "SSHNativeParsePublicKey"
keyType, length, err = SSHNativeParsePublicKey(content)
} else {
fnName = "SSHKeyGenParsePublicKey"
keyType, length, err = SSHKeyGenParsePublicKey(content)
}
if err != nil {
return "", fmt.Errorf("ParsePublicKey: %v", err)
return "", fmt.Errorf("%s: %v", fnName, err)
}
log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length)
@@ -309,13 +306,13 @@ func CheckPublicKeyString(content string) (_ string, err error) {
if minLen, found := setting.SSH.MinimumKeySizes[keyType]; found && length >= minLen {
return content, nil
} else if found && length < minLen {
return "", fmt.Errorf("Key length is not enough: got %d, needs %d", length, minLen)
return "", fmt.Errorf("key length is not enough: got %d, needs %d", length, minLen)
}
return "", fmt.Errorf("Key type is not allowed: %s", keyType)
return "", fmt.Errorf("key type is not allowed: %s", keyType)
}
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
func saveAuthorizedKeyFile(keys ...*PublicKey) error {
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
@@ -326,13 +323,13 @@ func saveAuthorizedKeyFile(keys ...*PublicKey) error {
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return err
}
// FIXME: following command does not support in Windows.
// Note: chmod command does not support in Windows.
if !setting.IsWindows {
fi, err := f.Stat()
if err != nil {
return err
}
// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
if fi.Mode().Perm() > 0600 {
log.Error(4, "authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
@@ -343,7 +340,7 @@ func saveAuthorizedKeyFile(keys ...*PublicKey) error {
}
for _, key := range keys {
if _, err = f.WriteString(key.GetAuthorizedString()); err != nil {
if _, err = f.WriteString(key.AuthorizedString()); err != nil {
return err
}
}
@@ -390,7 +387,7 @@ func addKey(e Engine, key *PublicKey) (err error) {
if setting.SSH.StartBuiltinServer {
return nil
}
return saveAuthorizedKeyFile(key)
return appendAuthorizedKeysToFile(key)
}
// AddPublicKey adds new public key to database and authorized_keys file.
@@ -401,7 +398,7 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
}
// Key name of same user cannot be duplicated.
has, err := x.Where("owner_id=? AND name=?", ownerID, name).Get(new(PublicKey))
has, err := x.Where("owner_id = ? AND name = ?", ownerID, name).Get(new(PublicKey))
if err != nil {
return nil, err
} else if has {
@@ -456,62 +453,7 @@ func SearchPublicKeyByContent(content string) (*PublicKey, error) {
// ListPublicKeys returns a list of public keys belongs to given user.
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5)
return keys, x.Where("owner_id=?", uid).Find(&keys)
}
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
fr, err := os.Open(p)
if err != nil {
return err
}
defer fr.Close()
fw, err := os.OpenFile(tmpP, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return err
}
defer fw.Close()
isFound := false
keyword := fmt.Sprintf("key-%d", key.ID)
buf := bufio.NewReader(fr)
for {
line, errRead := buf.ReadString('\n')
line = strings.TrimSpace(line)
if errRead != nil {
if errRead != io.EOF {
return errRead
}
// Reached end of file, if nothing to read then break,
// otherwise handle the last line.
if len(line) == 0 {
break
}
}
// Found the line and copy rest of file.
if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) {
isFound = true
continue
}
// Still finding the line, copy the line that currently read.
if _, err = fw.WriteString(line + "\n"); err != nil {
return err
}
if errRead == io.EOF {
break
}
}
if !isFound {
log.Warn("SSH key %d not found in authorized_keys file for deletion", key.ID)
}
return nil
return keys, x.Where("owner_id = ?", uid).Find(&keys)
}
// UpdatePublicKey updates given public key.
@@ -520,35 +462,14 @@ func UpdatePublicKey(key *PublicKey) error {
return err
}
func deletePublicKey(e *xorm.Session, keyID int64) error {
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
key := &PublicKey{ID: keyID}
has, err := e.Get(key)
if err != nil {
return err
} else if !has {
// deletePublicKeys does the actual key deletion but does not update authorized_keys file.
func deletePublicKeys(e *xorm.Session, keyIDs ...int64) error {
if len(keyIDs) == 0 {
return nil
}
if _, err = e.Id(key.ID).Delete(new(PublicKey)); err != nil {
return err
}
// Don't need to rewrite this file if builtin SSH server is enabled.
if setting.SSH.StartBuiltinServer {
return nil
}
fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
tmpPath := fpath + ".tmp"
if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
return err
} else if err = os.Remove(fpath); err != nil {
return err
}
return os.Rename(tmpPath, fpath)
_, err := e.In("id", strings.Join(base.Int64sToStrings(keyIDs), ",")).Delete(new(PublicKey))
return err
}
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
@@ -562,8 +483,8 @@ func DeletePublicKey(doer *User, id int64) (err error) {
}
// Check if user has access to delete this key.
if !doer.IsAdmin && doer.Id != key.OwnerID {
return ErrKeyAccessDenied{doer.Id, key.ID, "public"}
if !doer.IsAdmin && doer.ID != key.OwnerID {
return ErrKeyAccessDenied{doer.ID, key.ID, "public"}
}
sess := x.NewSession()
@@ -572,14 +493,20 @@ func DeletePublicKey(doer *User, id int64) (err error) {
return err
}
if err = deletePublicKey(sess, id); err != nil {
if err = deletePublicKeys(sess, id); err != nil {
return err
}
return sess.Commit()
if err = sess.Commit(); err != nil {
return err
}
return RewriteAllPublicKeys()
}
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
// outsite any session scope independently.
func RewriteAllPublicKeys() error {
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
@@ -593,7 +520,7 @@ func RewriteAllPublicKeys() error {
defer os.Remove(tmpPath)
err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
_, err = f.WriteString((bean.(*PublicKey)).GetAuthorizedString())
_, err = f.WriteString((bean.(*PublicKey)).AuthorizedString())
return err
})
f.Close()
@@ -638,11 +565,11 @@ type DeployKey struct {
}
func (k *DeployKey) BeforeInsert() {
k.CreatedUnix = time.Now().UTC().Unix()
k.CreatedUnix = time.Now().Unix()
}
func (k *DeployKey) BeforeUpdate() {
k.UpdatedUnix = time.Now().UTC().Unix()
k.UpdatedUnix = time.Now().Unix()
}
func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
@@ -668,14 +595,14 @@ func (k *DeployKey) GetContent() error {
func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
// Note: We want error detail, not just true or false here.
has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
has, err := e.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey))
if err != nil {
return err
} else if has {
return ErrDeployKeyAlreadyExist{keyID, repoID}
}
has, err = e.Where("repo_id=? AND name=?", repoID, name).Get(new(DeployKey))
has, err = e.Where("repo_id = ? AND name = ?", repoID, name).Get(new(DeployKey))
if err != nil {
return err
} else if has {
@@ -703,7 +630,7 @@ func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string
// HasDeployKey returns true if public key is a deploy key of given repository.
func HasDeployKey(keyID, repoID int64) bool {
has, _ := x.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
has, _ := x.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey))
return has
}
@@ -797,7 +724,7 @@ func DeleteDeployKey(doer *User, id int64) error {
if err != nil {
return fmt.Errorf("HasAccess: %v", err)
} else if !yes {
return ErrKeyAccessDenied{doer.Id, key.ID, "deploy"}
return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"}
}
}
@@ -808,15 +735,15 @@ func DeleteDeployKey(doer *User, id int64) error {
}
if _, err = sess.Id(key.ID).Delete(new(DeployKey)); err != nil {
return fmt.Errorf("delete deploy key[%d]: %v", key.ID, err)
return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err)
}
// Check if this is the last reference to same key content.
has, err := sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
has, err := sess.Where("key_id = ?", key.KeyID).Get(new(DeployKey))
if err != nil {
return err
} else if !has {
if err = deletePublicKey(sess, key.KeyID); err != nil {
if err = deletePublicKeys(sess, key.KeyID); err != nil {
return err
}
}
@@ -827,5 +754,5 @@ func DeleteDeployKey(doer *User, id int64) error {
// ListDeployKeys returns all deploy keys by given repository ID.
func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
keys := make([]*DeployKey, 0, 5)
return keys, x.Where("repo_id=?", repoID).Find(&keys)
return keys, x.Where("repo_id = ?", repoID).Find(&keys)
}

View File

@@ -29,11 +29,11 @@ type AccessToken struct {
}
func (t *AccessToken) BeforeInsert() {
t.CreatedUnix = time.Now().UTC().Unix()
t.CreatedUnix = time.Now().Unix()
}
func (t *AccessToken) BeforeUpdate() {
t.UpdatedUnix = time.Now().UTC().Unix()
t.UpdatedUnix = time.Now().Unix()
}
func (t *AccessToken) AfterSet(colName string, _ xorm.Cell) {
@@ -56,6 +56,9 @@ func NewAccessToken(t *AccessToken) error {
// GetAccessTokenBySHA returns access token by given sha1.
func GetAccessTokenBySHA(sha string) (*AccessToken, error) {
if sha == "" {
return nil, ErrAccessTokenEmpty{}
}
t := &AccessToken{Sha1: sha}
has, err := x.Get(t)
if err != nil {

View File

@@ -108,7 +108,7 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
return fmt.Errorf("GetUserByName: %v", err)
}
repo, err := GetRepositoryByName(repoUser.Id, opts.RepoName)
repo, err := GetRepositoryByName(repoUser.ID, opts.RepoName)
if err != nil {
return fmt.Errorf("GetRepositoryByName: %v", err)
}
@@ -133,7 +133,7 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
}
commit := &PushCommits{}
if err = CommitRepoAction(opts.PusherID, repoUser.Id, opts.PusherName, actEmail,
if err = CommitRepoAction(opts.PusherID, repoUser.ID, opts.PusherName, actEmail,
repo.ID, opts.RepoUserName, opts.RepoName, opts.RefName, commit, opts.OldCommitID, opts.NewCommitID); err != nil {
return fmt.Errorf("CommitRepoAction (tag): %v", err)
}
@@ -159,7 +159,7 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
}
}
if err = CommitRepoAction(opts.PusherID, repoUser.Id, opts.PusherName, repoUser.Email,
if err = CommitRepoAction(opts.PusherID, repoUser.ID, opts.PusherName, repoUser.Email,
repo.ID, opts.RepoUserName, opts.RepoName, opts.RefName, ListToPushCommits(l),
opts.OldCommitID, opts.NewCommitID); err != nil {
return fmt.Errorf("CommitRepoAction (branch): %v", err)

View File

@@ -12,13 +12,13 @@ import (
"errors"
"fmt"
"image"
"image/jpeg"
_ "image/jpeg"
"image/png"
"os"
"path/filepath"
"strings"
"time"
"unicode/utf8"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
@@ -52,7 +52,7 @@ var (
// User represents the object of individual and member of organization.
type User struct {
Id int64
ID int64 `xorm:"pk autoincr"`
LowerName string `xorm:"UNIQUE NOT NULL"`
Name string `xorm:"UNIQUE NOT NULL"`
FullName string
@@ -82,10 +82,11 @@ type User struct {
MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"`
// Permissions
IsActive bool
IsActive bool // Activate primary email
IsAdmin bool
AllowGitHook bool
AllowImportLocal bool // Allow migrate repository by local path
ProhibitLogin bool
// Avatar
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
@@ -107,7 +108,7 @@ type User struct {
}
func (u *User) BeforeInsert() {
u.CreatedUnix = time.Now().UTC().Unix()
u.CreatedUnix = time.Now().Unix()
u.UpdatedUnix = u.CreatedUnix
}
@@ -115,7 +116,7 @@ func (u *User) BeforeUpdate() {
if u.MaxRepoCreation < -1 {
u.MaxRepoCreation = -1
}
u.UpdatedUnix = time.Now().UTC().Unix()
u.UpdatedUnix = time.Now().Unix()
}
func (u *User) AfterSet(colName string, _ xorm.Cell) {
@@ -136,7 +137,7 @@ func (u *User) IsLocal() bool {
// HasForkedRepo checks if user has already forked a repository with given ID.
func (u *User) HasForkedRepo(repoID int64) bool {
_, has := HasForkedRepo(u.Id, repoID)
_, has := HasForkedRepo(u.ID, repoID)
return has
}
@@ -167,16 +168,6 @@ func (u *User) CanImportLocal() bool {
return u.IsAdmin || u.AllowImportLocal
}
// EmailAdresses is the list of all email addresses of a user. Can contain the
// primary email address, but is not obligatory
type EmailAddress struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX NOT NULL"`
Email string `xorm:"UNIQUE NOT NULL"`
IsActivated bool
IsPrimary bool `xorm:"-"`
}
// DashboardLink returns the user dashboard page link.
func (u *User) DashboardLink() string {
if u.IsOrganization() {
@@ -193,7 +184,7 @@ func (u *User) HomeLink() string {
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
func (u *User) GenerateEmailActivateCode(email string) string {
code := base.CreateTimeLimitCode(
com.ToStr(u.Id)+email+u.LowerName+u.Passwd+u.Rands,
com.ToStr(u.ID)+email+u.LowerName+u.Passwd+u.Rands,
setting.Service.ActiveCodeLives, nil)
// Add tail hex username
@@ -208,7 +199,7 @@ func (u *User) GenerateActivateCode() string {
// CustomAvatarPath returns user custom avatar file path.
func (u *User) CustomAvatarPath() string {
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.ID))
}
// GenerateRandomAvatar generates a random avatar for user.
@@ -231,17 +222,17 @@ func (u *User) GenerateRandomAvatar() error {
}
defer fw.Close()
if err = jpeg.Encode(fw, img, nil); err != nil {
if err = png.Encode(fw, img); err != nil {
return fmt.Errorf("Encode: %v", err)
}
log.Info("New random avatar created: %d", u.Id)
log.Info("New random avatar created: %d", u.ID)
return nil
}
func (u *User) RelAvatarLink() string {
defaultImgUrl := "/img/avatar_default.jpg"
if u.Id == -1 {
defaultImgUrl := "/img/avatar_default.png"
if u.ID == -1 {
return defaultImgUrl
}
@@ -250,7 +241,7 @@ func (u *User) RelAvatarLink() string {
if !com.IsExist(u.CustomAvatarPath()) {
return defaultImgUrl
}
return "/avatars/" + com.ToStr(u.Id)
return "/avatars/" + com.ToStr(u.ID)
case setting.DisableGravatar, setting.OfflineMode:
if !com.IsExist(u.CustomAvatarPath()) {
if err := u.GenerateRandomAvatar(); err != nil {
@@ -258,16 +249,16 @@ func (u *User) RelAvatarLink() string {
}
}
return "/avatars/" + com.ToStr(u.Id)
return "/avatars/" + com.ToStr(u.ID)
}
return setting.GravatarSource + u.Avatar
}
// AvatarLink returns user gravatar link.
// AvatarLink returns user avatar link.
func (u *User) AvatarLink() string {
link := u.RelAvatarLink()
if link[0] == '/' && link[1] != '/' {
return setting.AppSubUrl + link
return strings.TrimSuffix(setting.AppUrl, "/") + link
}
return link
}
@@ -275,7 +266,7 @@ func (u *User) AvatarLink() string {
// User.GetFollwoers returns range of user's followers.
func (u *User) GetFollowers(page int) ([]*User, error) {
users := make([]*User, 0, ItemsPerPage)
sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.follow_id=?", u.Id)
sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.follow_id=?", u.ID)
if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`)
} else {
@@ -285,13 +276,13 @@ func (u *User) GetFollowers(page int) ([]*User, error) {
}
func (u *User) IsFollowing(followID int64) bool {
return IsFollowing(u.Id, followID)
return IsFollowing(u.ID, followID)
}
// GetFollowing returns range of user's following.
func (u *User) GetFollowing(page int) ([]*User, error) {
users := make([]*User, 0, ItemsPerPage)
sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.user_id=?", u.Id)
sess := x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).Where("follow.user_id=?", u.ID)
if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`)
} else {
@@ -330,7 +321,7 @@ func (u *User) UploadAvatar(data []byte) error {
return fmt.Errorf("Decode: %v", err)
}
m := resize.Resize(290, 290, img, resize.NearestNeighbor)
m := resize.Resize(avatar.AVATAR_SIZE, avatar.AVATAR_SIZE, img, resize.NearestNeighbor)
sess := x.NewSession()
defer sessionRelease(sess)
@@ -359,7 +350,7 @@ func (u *User) UploadAvatar(data []byte) error {
// DeleteAvatar deletes the user's custom avatar.
func (u *User) DeleteAvatar() error {
log.Trace("DeleteAvatar[%d]: %s", u.Id, u.CustomAvatarPath())
log.Trace("DeleteAvatar[%d]: %s", u.ID, u.CustomAvatarPath())
os.Remove(u.CustomAvatarPath())
u.UseCustomAvatar = false
@@ -394,16 +385,16 @@ func (u *User) IsOrganization() bool {
// IsUserOrgOwner returns true if user is in the owner team of given organization.
func (u *User) IsUserOrgOwner(orgId int64) bool {
return IsOrganizationOwner(orgId, u.Id)
return IsOrganizationOwner(orgId, u.ID)
}
// IsPublicMember returns true if user public his/her membership in give organization.
func (u *User) IsPublicMember(orgId int64) bool {
return IsPublicMembership(orgId, u.Id)
return IsPublicMembership(orgId, u.ID)
}
func (u *User) getOrganizationCount(e Engine) (int64, error) {
return e.Where("uid=?", u.Id).Count(new(OrgUser))
return e.Where("uid=?", u.ID).Count(new(OrgUser))
}
// GetOrganizationCount returns count of membership of organization of user.
@@ -411,21 +402,26 @@ func (u *User) GetOrganizationCount() (int64, error) {
return u.getOrganizationCount(x)
}
// GetRepositories returns all repositories that user owns, including private repositories.
func (u *User) GetRepositories() (err error) {
u.Repos, err = GetRepositories(u.Id, true)
// GetRepositories returns repositories that user owns, including private repositories.
func (u *User) GetRepositories(page, pageSize int) (err error) {
u.Repos, err = GetUserRepositories(u.ID, true, page, pageSize)
return err
}
// GetRepositories returns mirror repositories that user owns, including private repositories.
func (u *User) GetMirrorRepositories() ([]*Repository, error) {
return GetUserMirrorRepositories(u.ID)
}
// GetOwnedOrganizations returns all organizations that user owns.
func (u *User) GetOwnedOrganizations() (err error) {
u.OwnedOrgs, err = GetOwnedOrgsByUserID(u.Id)
u.OwnedOrgs, err = GetOwnedOrgsByUserID(u.ID)
return err
}
// GetOrganizations returns all organizations that user belongs to.
func (u *User) GetOrganizations(all bool) error {
ous, err := GetOrgUsersByUserID(u.Id, all)
ous, err := GetOrgUsersByUserID(u.ID, all)
if err != nil {
return err
}
@@ -464,19 +460,6 @@ func IsUserExist(uid int64, name string) (bool, error) {
return x.Where("id!=?", uid).Get(&User{LowerName: strings.ToLower(name)})
}
// IsEmailUsed returns true if the e-mail has been used.
func IsEmailUsed(email string) (bool, error) {
if len(email) == 0 {
return false, nil
}
email = strings.ToLower(email)
if has, err := x.Get(&EmailAddress{Email: email}); has || err != nil {
return has, err
}
return x.Get(&User{Email: email})
}
// GetUserSalt returns a ramdom user salt token.
func GetUserSalt() string {
return base.GetRandomString(10)
@@ -485,15 +468,49 @@ func GetUserSalt() string {
// NewFakeUser creates and returns a fake user for someone has deleted his/her account.
func NewFakeUser() *User {
return &User{
Id: -1,
ID: -1,
Name: "Someone",
LowerName: "someone",
}
}
var (
reversedUsernames = []string{"debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."}
reversedUserPatterns = []string{"*.keys"}
)
// isUsableName checks if name is reserved or pattern of name is not allowed
// based on given reversed names and patterns.
// Names are exact match, patterns can be prefix or suffix match with placeholder '*'.
func isUsableName(names, patterns []string, name string) error {
name = strings.TrimSpace(strings.ToLower(name))
if utf8.RuneCountInString(name) == 0 {
return ErrNameEmpty
}
for i := range names {
if name == names[i] {
return ErrNameReserved{name}
}
}
for _, pat := range patterns {
if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) ||
(pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) {
return ErrNamePatternNotAllowed{pat}
}
}
return nil
}
func IsUsableUsername(name string) error {
return isUsableName(reversedUsernames, reversedUserPatterns, name)
}
// CreateUser creates record of a new user.
func CreateUser(u *User) (err error) {
if err = IsUsableName(u.Name); err != nil {
if err = IsUsableUsername(u.Name); err != nil {
return err
}
@@ -521,16 +538,14 @@ func CreateUser(u *User) (err error) {
u.MaxRepoCreation = -1
sess := x.NewSession()
defer sess.Close()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(u); err != nil {
sess.Rollback()
return err
} else if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil {
sess.Rollback()
return err
}
@@ -578,7 +593,7 @@ func VerifyUserActiveCode(code string) (user *User) {
if user = getVerifyUser(code); user != nil {
// time limit code
prefix := code[:base.TimeLimitCodeLength]
data := com.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
data := com.ToStr(user.ID) + user.Email + user.LowerName + user.Passwd + user.Rands
if base.VerifyTimeLimitCode(data, minutes, prefix) {
return user
@@ -594,7 +609,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
if user := getVerifyUser(code); user != nil {
// time limit code
prefix := code[:base.TimeLimitCodeLength]
data := com.ToStr(user.Id) + email + user.LowerName + user.Passwd + user.Rands
data := com.ToStr(user.ID) + email + user.LowerName + user.Passwd + user.Rands
if base.VerifyTimeLimitCode(data, minutes, prefix) {
emailAddress := &EmailAddress{Email: email}
@@ -608,7 +623,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
// ChangeUserName changes all corresponding setting from old user name to new one.
func ChangeUserName(u *User, newUserName string) (err error) {
if err = IsUsableName(newUserName); err != nil {
if err = IsUsableUsername(newUserName); err != nil {
return err
}
@@ -624,7 +639,7 @@ func ChangeUserName(u *User, newUserName string) (err error) {
}
// Delete all local copies of repository wiki that user owns.
if err = x.Where("owner_id=?", u.Id).Iterate(new(Repository), func(idx int, bean interface{}) error {
if err = x.Where("owner_id=?", u.ID).Iterate(new(Repository), func(idx int, bean interface{}) error {
repo := bean.(*Repository)
RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath())
return nil
@@ -639,7 +654,7 @@ func updateUser(e Engine, u *User) error {
// Organization does not need email
if !u.IsOrganization() {
u.Email = strings.ToLower(u.Email)
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
has, err := e.Where("id!=?", u.ID).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
if err != nil {
return err
} else if has {
@@ -658,7 +673,7 @@ func updateUser(e Engine, u *User) error {
u.Description = base.TruncateString(u.Description, 255)
u.FullName = markdown.Sanitizer.Sanitize(u.FullName)
_, err := e.Id(u.Id).AllCols().Update(u)
_, err := e.Id(u.ID).AllCols().Update(u)
return err
}
@@ -687,7 +702,7 @@ func deleteUser(e *xorm.Session, u *User) error {
if err != nil {
return fmt.Errorf("GetRepositoryCount: %v", err)
} else if count > 0 {
return ErrUserOwnRepos{UID: u.Id}
return ErrUserOwnRepos{UID: u.ID}
}
// Check membership of organization.
@@ -695,12 +710,12 @@ func deleteUser(e *xorm.Session, u *User) error {
if err != nil {
return fmt.Errorf("GetOrganizationCount: %v", err)
} else if count > 0 {
return ErrUserHasOrgs{UID: u.Id}
return ErrUserHasOrgs{UID: u.ID}
}
// ***** START: Watch *****
watches := make([]*Watch, 0, 10)
if err = e.Find(&watches, &Watch{UserID: u.Id}); err != nil {
if err = e.Find(&watches, &Watch{UserID: u.ID}); err != nil {
return fmt.Errorf("get all watches: %v", err)
}
for i := range watches {
@@ -712,7 +727,7 @@ func deleteUser(e *xorm.Session, u *User) error {
// ***** START: Star *****
stars := make([]*Star, 0, 10)
if err = e.Find(&stars, &Star{UID: u.Id}); err != nil {
if err = e.Find(&stars, &Star{UID: u.ID}); err != nil {
return fmt.Errorf("get all stars: %v", err)
}
for i := range stars {
@@ -724,7 +739,7 @@ func deleteUser(e *xorm.Session, u *User) error {
// ***** START: Follow *****
followers := make([]*Follow, 0, 10)
if err = e.Find(&followers, &Follow{UserID: u.Id}); err != nil {
if err = e.Find(&followers, &Follow{UserID: u.ID}); err != nil {
return fmt.Errorf("get all followers: %v", err)
}
for i := range followers {
@@ -735,37 +750,40 @@ func deleteUser(e *xorm.Session, u *User) error {
// ***** END: Follow *****
if err = deleteBeans(e,
&AccessToken{UID: u.Id},
&Collaboration{UserID: u.Id},
&Access{UserID: u.Id},
&Watch{UserID: u.Id},
&Star{UID: u.Id},
&Follow{FollowID: u.Id},
&Action{UserID: u.Id},
&IssueUser{UID: u.Id},
&EmailAddress{UID: u.Id},
&AccessToken{UID: u.ID},
&Collaboration{UserID: u.ID},
&Access{UserID: u.ID},
&Watch{UserID: u.ID},
&Star{UID: u.ID},
&Follow{FollowID: u.ID},
&Action{UserID: u.ID},
&IssueUser{UID: u.ID},
&EmailAddress{UID: u.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
// ***** START: PublicKey *****
keys := make([]*PublicKey, 0, 10)
if err = e.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
if err = e.Find(&keys, &PublicKey{OwnerID: u.ID}); err != nil {
return fmt.Errorf("get all public keys: %v", err)
}
for _, key := range keys {
if err = deletePublicKey(e, key.ID); err != nil {
return fmt.Errorf("deletePublicKey: %v", err)
}
keyIDs := make([]int64, len(keys))
for i := range keys {
keyIDs[i] = keys[i].ID
}
if err = deletePublicKeys(e, keyIDs...); err != nil {
return fmt.Errorf("deletePublicKeys: %v", err)
}
// ***** END: PublicKey *****
// Clear assignee.
if _, err = e.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.Id); err != nil {
if _, err = e.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.ID); err != nil {
return fmt.Errorf("clear assignee: %v", err)
}
if _, err = e.Id(u.Id).Delete(new(User)); err != nil {
if _, err = e.Id(u.ID).Delete(new(User)); err != nil {
return fmt.Errorf("Delete: %v", err)
}
@@ -773,7 +791,6 @@ func deleteUser(e *xorm.Session, u *User) error {
// Note: There are something just cannot be roll back,
// so just keep error logs of those operations.
RewriteAllPublicKeys()
os.RemoveAll(UserPath(u.Name))
os.Remove(u.CustomAvatarPath())
@@ -794,15 +811,20 @@ func DeleteUser(u *User) (err error) {
return err
}
return sess.Commit()
if err = sess.Commit(); err != nil {
return err
}
return RewriteAllPublicKeys()
}
// DeleteInactivateUsers deletes all inactivate users and email addresses.
func DeleteInactivateUsers() (err error) {
users := make([]*User, 0, 10)
if err = x.Where("is_active=?", false).Find(&users); err != nil {
if err = x.Where("is_active = ?", false).Find(&users); err != nil {
return fmt.Errorf("get all inactive users: %v", err)
}
// FIXME: should only update authorized_keys file once after all deletions.
for _, u := range users {
if err = DeleteUser(u); err != nil {
// Ignore users that were set inactive by admin.
@@ -813,7 +835,7 @@ func DeleteInactivateUsers() (err error) {
}
}
_, err = x.Where("is_activated=?", false).Delete(new(EmailAddress))
_, err = x.Where("is_activated = ?", false).Delete(new(EmailAddress))
return err
}
@@ -851,7 +873,7 @@ func GetUserByID(id int64) (*User, error) {
// GetAssigneeByID returns the user with write access of repository by given ID.
func GetAssigneeByID(repo *Repository, userID int64) (*User, error) {
has, err := HasAccess(&User{Id: userID}, repo, ACCESS_MODE_WRITE)
has, err := HasAccess(&User{ID: userID}, repo, ACCESS_MODE_WRITE)
if err != nil {
return nil, err
} else if !has {
@@ -888,160 +910,19 @@ func GetUserEmailsByNames(names []string) []string {
return mails
}
// GetUserIdsByNames returns a slice of ids corresponds to names.
func GetUserIdsByNames(names []string) []int64 {
// GetUserIDsByNames returns a slice of ids corresponds to names.
func GetUserIDsByNames(names []string) []int64 {
ids := make([]int64, 0, len(names))
for _, name := range names {
u, err := GetUserByName(name)
if err != nil {
continue
}
ids = append(ids, u.Id)
ids = append(ids, u.ID)
}
return ids
}
// GetEmailAddresses returns all e-mail addresses belongs to given user.
func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
emails := make([]*EmailAddress, 0, 5)
err := x.Where("uid=?", uid).Find(&emails)
if err != nil {
return nil, err
}
u, err := GetUserByID(uid)
if err != nil {
return nil, err
}
isPrimaryFound := false
for _, email := range emails {
if email.Email == u.Email {
isPrimaryFound = true
email.IsPrimary = true
} else {
email.IsPrimary = false
}
}
// We alway want the primary email address displayed, even if it's not in
// the emailaddress table (yet)
if !isPrimaryFound {
emails = append(emails, &EmailAddress{
Email: u.Email,
IsActivated: true,
IsPrimary: true,
})
}
return emails, nil
}
func AddEmailAddress(email *EmailAddress) error {
email.Email = strings.ToLower(strings.TrimSpace(email.Email))
used, err := IsEmailUsed(email.Email)
if err != nil {
return err
} else if used {
return ErrEmailAlreadyUsed{email.Email}
}
_, err = x.Insert(email)
return err
}
func AddEmailAddresses(emails []*EmailAddress) error {
if len(emails) == 0 {
return nil
}
// Check if any of them has been used
for i := range emails {
emails[i].Email = strings.ToLower(strings.TrimSpace(emails[i].Email))
used, err := IsEmailUsed(emails[i].Email)
if err != nil {
return err
} else if used {
return ErrEmailAlreadyUsed{emails[i].Email}
}
}
if _, err := x.Insert(emails); err != nil {
return fmt.Errorf("Insert: %v", err)
}
return nil
}
func (email *EmailAddress) Activate() error {
email.IsActivated = true
if _, err := x.Id(email.ID).AllCols().Update(email); err != nil {
return err
}
if user, err := GetUserByID(email.UID); err != nil {
return err
} else {
user.Rands = GetUserSalt()
return UpdateUser(user)
}
}
func DeleteEmailAddress(email *EmailAddress) (err error) {
if email.ID > 0 {
_, err = x.Id(email.ID).Delete(new(EmailAddress))
} else {
_, err = x.Where("email=?", email.Email).Delete(new(EmailAddress))
}
return err
}
func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
for i := range emails {
if err = DeleteEmailAddress(emails[i]); err != nil {
return err
}
}
return nil
}
func MakeEmailPrimary(email *EmailAddress) error {
has, err := x.Get(email)
if err != nil {
return err
} else if !has {
return ErrEmailNotExist
}
if !email.IsActivated {
return ErrEmailNotActivated
}
user := &User{Id: email.UID}
has, err = x.Get(user)
if err != nil {
return err
} else if !has {
return ErrUserNotExist{email.UID, ""}
}
// Make sure the former primary email doesn't disappear
former_primary_email := &EmailAddress{Email: user.Email}
has, err = x.Get(former_primary_email)
if err != nil {
return err
} else if !has {
former_primary_email.UID = user.Id
former_primary_email.IsActivated = user.IsActive
x.Insert(former_primary_email)
}
user.Email = email.Email
_, err = x.Id(user.Id).AllCols().Update(user)
return err
}
// UserCommit represents a commit with validation of user.
type UserCommit struct {
User *User
@@ -1119,7 +1000,7 @@ type SearchUserOptions struct {
Type UserType
OrderBy string
Page int
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
}
// SearchUserByName takes keyword and part of user name to search,
@@ -1130,8 +1011,8 @@ func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error)
}
opts.Keyword = strings.ToLower(opts.Keyword)
if opts.PageSize <= 0 || opts.PageSize > setting.ExplorePagingNum {
opts.PageSize = setting.ExplorePagingNum
if opts.PageSize <= 0 || opts.PageSize > setting.UI.ExplorePagingNum {
opts.PageSize = setting.UI.ExplorePagingNum
}
if opts.Page <= 0 {
opts.Page = 1

198
models/user_mail.go Normal file
View File

@@ -0,0 +1,198 @@
// Copyright 2016 The Gogs 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 models
import (
"fmt"
"strings"
)
// EmailAdresses is the list of all email addresses of a user. Can contain the
// primary email address, but is not obligatory.
type EmailAddress struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX NOT NULL"`
Email string `xorm:"UNIQUE NOT NULL"`
IsActivated bool
IsPrimary bool `xorm:"-"`
}
// GetEmailAddresses returns all email addresses belongs to given user.
func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
emails := make([]*EmailAddress, 0, 5)
if err := x.Where("uid=?", uid).Find(&emails); err != nil {
return nil, err
}
u, err := GetUserByID(uid)
if err != nil {
return nil, err
}
isPrimaryFound := false
for _, email := range emails {
if email.Email == u.Email {
isPrimaryFound = true
email.IsPrimary = true
} else {
email.IsPrimary = false
}
}
// We alway want the primary email address displayed, even if it's not in
// the emailaddress table (yet).
if !isPrimaryFound {
emails = append(emails, &EmailAddress{
Email: u.Email,
IsActivated: true,
IsPrimary: true,
})
}
return emails, nil
}
func isEmailUsed(e Engine, email string) (bool, error) {
if len(email) == 0 {
return true, nil
}
return e.Get(&EmailAddress{Email: email})
}
// IsEmailUsed returns true if the email has been used.
func IsEmailUsed(email string) (bool, error) {
return isEmailUsed(x, email)
}
func addEmailAddress(e Engine, email *EmailAddress) error {
email.Email = strings.ToLower(strings.TrimSpace(email.Email))
used, err := isEmailUsed(e, email.Email)
if err != nil {
return err
} else if used {
return ErrEmailAlreadyUsed{email.Email}
}
_, err = e.Insert(email)
return err
}
func AddEmailAddress(email *EmailAddress) error {
return addEmailAddress(x, email)
}
func AddEmailAddresses(emails []*EmailAddress) error {
if len(emails) == 0 {
return nil
}
// Check if any of them has been used
for i := range emails {
emails[i].Email = strings.ToLower(strings.TrimSpace(emails[i].Email))
used, err := IsEmailUsed(emails[i].Email)
if err != nil {
return err
} else if used {
return ErrEmailAlreadyUsed{emails[i].Email}
}
}
if _, err := x.Insert(emails); err != nil {
return fmt.Errorf("Insert: %v", err)
}
return nil
}
func (email *EmailAddress) Activate() error {
user, err := GetUserByID(email.UID)
if err != nil {
return err
}
user.Rands = GetUserSalt()
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
email.IsActivated = true
if _, err := sess.Id(email.ID).AllCols().Update(email); err != nil {
return err
} else if err = updateUser(sess, user); err != nil {
return err
}
return sess.Commit()
}
func DeleteEmailAddress(email *EmailAddress) (err error) {
if email.ID > 0 {
_, err = x.Id(email.ID).Delete(new(EmailAddress))
} else {
_, err = x.Where("email=?", email.Email).Delete(new(EmailAddress))
}
return err
}
func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
for i := range emails {
if err = DeleteEmailAddress(emails[i]); err != nil {
return err
}
}
return nil
}
func MakeEmailPrimary(email *EmailAddress) error {
has, err := x.Get(email)
if err != nil {
return err
} else if !has {
return ErrEmailNotExist
}
if !email.IsActivated {
return ErrEmailNotActivated
}
user := &User{ID: email.UID}
has, err = x.Get(user)
if err != nil {
return err
} else if !has {
return ErrUserNotExist{email.UID, ""}
}
// Make sure the former primary email doesn't disappear.
formerPrimaryEmail := &EmailAddress{Email: user.Email}
has, err = x.Get(formerPrimaryEmail)
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if !has {
formerPrimaryEmail.UID = user.ID
formerPrimaryEmail.IsActivated = user.IsActive
if _, err = sess.Insert(formerPrimaryEmail); err != nil {
return err
}
}
user.Email = email.Email
if _, err = sess.Id(user.ID).AllCols().Update(user); err != nil {
return err
}
return sess.Commit()
}

View File

@@ -102,12 +102,12 @@ type Webhook struct {
}
func (w *Webhook) BeforeInsert() {
w.CreatedUnix = time.Now().UTC().Unix()
w.CreatedUnix = time.Now().Unix()
w.UpdatedUnix = w.CreatedUnix
}
func (w *Webhook) BeforeUpdate() {
w.UpdatedUnix = time.Now().UTC().Unix()
w.UpdatedUnix = time.Now().Unix()
}
func (w *Webhook) AfterSet(colName string, _ xorm.Cell) {
@@ -174,16 +174,32 @@ func CreateWebhook(w *Webhook) error {
return err
}
// GetWebhookByID returns webhook by given ID.
func GetWebhookByID(id int64) (*Webhook, error) {
w := new(Webhook)
has, err := x.Id(id).Get(w)
// getWebhook uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields.
func getWebhook(bean *Webhook) (*Webhook, error) {
has, err := x.Get(bean)
if err != nil {
return nil, err
} else if !has {
return nil, ErrWebhookNotExist{id}
return nil, ErrWebhookNotExist{bean.ID}
}
return w, nil
return bean, nil
}
// GetWebhookByRepoID returns webhook of repository by given ID.
func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
return getWebhook(&Webhook{
ID: id,
RepoID: repoID,
})
}
// GetWebhookByOrgID returns webhook of organization by given ID.
func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
return getWebhook(&Webhook{
ID: id,
OrgID: orgID,
})
}
// GetActiveWebhooksByRepoID returns all active webhooks of repository.
@@ -204,25 +220,42 @@ func UpdateWebhook(w *Webhook) error {
return err
}
// DeleteWebhook deletes webhook of repository.
func DeleteWebhook(id int64) (err error) {
// deleteWebhook uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields.
func deleteWebhook(bean *Webhook) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Delete(&Webhook{ID: id}); err != nil {
if _, err = sess.Delete(bean); err != nil {
return err
} else if _, err = sess.Delete(&HookTask{HookID: id}); err != nil {
} else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil {
return err
}
return sess.Commit()
}
// GetWebhooksByOrgId returns all webhooks for an organization.
func GetWebhooksByOrgId(orgID int64) (ws []*Webhook, err error) {
// DeleteWebhookByRepoID deletes webhook of repository by given ID.
func DeleteWebhookByRepoID(repoID, id int64) error {
return deleteWebhook(&Webhook{
ID: id,
RepoID: repoID,
})
}
// DeleteWebhookByOrgID deletes webhook of organization by given ID.
func DeleteWebhookByOrgID(orgID, id int64) error {
return deleteWebhook(&Webhook{
ID: id,
OrgID: orgID,
})
}
// GetWebhooksByOrgID returns all webhooks for an organization.
func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
err = x.Find(&ws, &Webhook{OrgID: orgID})
return ws, err
}
@@ -540,7 +573,7 @@ func (t *HookTask) deliver() {
}
defer func() {
t.Delivered = time.Now().UTC().UnixNano()
t.Delivered = time.Now().UnixNano()
if t.IsSucceed {
log.Trace("Hook delivered: %s", t.UUID)
} else {
@@ -548,7 +581,7 @@ func (t *HookTask) deliver() {
}
// Update webhook last delivery status.
w, err := GetWebhookByID(t.HookID)
w, err := GetWebhookByRepoID(t.RepoID, t.HookID)
if err != nil {
log.Error(5, "GetWebhookByID: %v", err)
return
@@ -584,14 +617,6 @@ func (t *HookTask) deliver() {
return
}
t.ResponseInfo.Body = string(p)
switch t.Type {
case SLACK:
if t.ResponseInfo.Body != "ok" {
log.Error(5, "slack failed with: %s", t.ResponseInfo.Body)
t.IsSucceed = false
}
}
}
// DeliverHooks checks and delivers undelivered hooks.

View File

@@ -69,10 +69,12 @@ func ToWikiPageURL(name string) string {
return url.QueryEscape(strings.Replace(name, " ", "-", -1))
}
// ToWikiPageName formats a URL back to corresponding wiki page name.
// ToWikiPageName formats a URL back to corresponding wiki page name,
// and removes leading characters './' to prevent changing files
// that are not belong to wiki repository.
func ToWikiPageName(urlString string) string {
name, _ := url.QueryUnescape(strings.Replace(urlString, "-", " ", -1))
return name
return strings.Replace(strings.TrimLeft(name, "./"), "/", " ", -1)
}
// WikiCloneLink returns clone URLs of repository wiki.
@@ -149,7 +151,7 @@ func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, mes
return fmt.Errorf("UpdateLocalWiki: %v", err)
}
title = ToWikiPageName(strings.Replace(title, "/", " ", -1))
title = ToWikiPageName(title)
filename := path.Join(localPath, title+".md")
// If not a new file, show perform update not create.
@@ -161,6 +163,13 @@ func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, mes
os.Remove(path.Join(localPath, oldTitle+".md"))
}
// SECURITY: if new file is a symlink to non-exist critical file,
// attack content can be written to the target file (e.g. authorized_keys2)
// as a new page operation.
// So we want to make sure the symlink is removed before write anything.
// The new file we created will be in normal text format.
os.Remove(filename)
if err = ioutil.WriteFile(filename, []byte(content), 0666); err != nil {
return fmt.Errorf("WriteFile: %v", err)
}
@@ -198,7 +207,7 @@ func (repo *Repository) DeleteWikiPage(doer *User, title string) (err error) {
return fmt.Errorf("UpdateLocalWiki: %v", err)
}
title = ToWikiPageName(strings.Replace(title, "/", " ", -1))
title = ToWikiPageName(title)
filename := path.Join(localPath, title+".md")
os.Remove(filename)

View File

@@ -36,6 +36,7 @@ type AdminEditUserForm struct {
Admin bool
AllowGitHook bool
AllowImportLocal bool
ProhibitLogin bool
}
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

View File

@@ -49,7 +49,7 @@ func SignedInID(ctx *macaron.Context, sess session.Store) int64 {
if len(tokenSHA) > 0 {
t, err := models.GetAccessTokenBySHA(tokenSHA)
if err != nil {
if models.IsErrAccessTokenNotExist(err) {
if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) {
log.Error(4, "GetAccessTokenBySHA: %v", err)
}
return 0

View File

@@ -31,6 +31,7 @@ type AuthenticationForm struct {
SMTPHost string
SMTPPort int
AllowedDomains string
SecurityProtocol int `binding:"Range(0,2)"`
TLS bool
SkipVerify bool
PAMServiceName string

View File

@@ -16,12 +16,21 @@ import (
"github.com/gogits/gogs/modules/log"
)
type SecurityProtocol int
// Note: new type must be added at the end of list to maintain compatibility.
const (
SECURITY_PROTOCOL_UNENCRYPTED SecurityProtocol = iota
SECURITY_PROTOCOL_LDAPS
SECURITY_PROTOCOL_START_TLS
)
// Basic LDAP authentication service
type Source struct {
Name string // canonical name (ie. corporate.ad)
Host string // LDAP host
Port int // port number
UseSSL bool // Use SSL
SecurityProtocol SecurityProtocol
SkipVerify bool
BindDN string // DN to bind with
BindPassword string // Bind DN password
@@ -102,9 +111,46 @@ func (ls *Source) findUserDN(l *ldap.Conn, name string) (string, bool) {
return userDN, true
}
func dial(ls *Source) (*ldap.Conn, error) {
log.Trace("Dialing LDAP with security protocol (%v) without verifying: %v", ls.SecurityProtocol, ls.SkipVerify)
tlsCfg := &tls.Config{
ServerName: ls.Host,
InsecureSkipVerify: ls.SkipVerify,
}
if ls.SecurityProtocol == SECURITY_PROTOCOL_LDAPS {
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), tlsCfg)
}
conn, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
if err != nil {
return nil, fmt.Errorf("Dial: %v", err)
}
if ls.SecurityProtocol == SECURITY_PROTOCOL_START_TLS {
if err = conn.StartTLS(tlsCfg); err != nil {
conn.Close()
return nil, fmt.Errorf("StartTLS: %v", err)
}
}
return conn, nil
}
func bindUser(l *ldap.Conn, userDN, passwd string) error {
log.Trace("Binding with userDN: %s", userDN)
err := l.Bind(userDN, passwd)
if err != nil {
log.Debug("LDAP auth. failed for %s, reason: %v", userDN, err)
return err
}
log.Trace("Bound successfully with userDN: %s", userDN)
return err
}
// searchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, string, string, string, bool, bool) {
l, err := ldapDial(ls)
l, err := dial(ls)
if err != nil {
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
ls.Enabled = false
@@ -164,12 +210,12 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
return "", "", "", "", false, false
}
username_attr := sr.Entries[0].GetAttributeValue(ls.AttributeUsername)
name_attr := sr.Entries[0].GetAttributeValue(ls.AttributeName)
sn_attr := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
mail_attr := sr.Entries[0].GetAttributeValue(ls.AttributeMail)
username := sr.Entries[0].GetAttributeValue(ls.AttributeUsername)
firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName)
surname := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail)
admin_attr := false
isAdmin := false
if len(ls.AdminFilter) > 0 {
log.Trace("Checking admin with filter %s and base %s", ls.AdminFilter, userDN)
search = ldap.NewSearchRequest(
@@ -183,7 +229,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
} else if len(sr.Entries) < 1 {
log.Error(4, "LDAP Admin Search failed")
} else {
admin_attr = true
isAdmin = true
}
}
@@ -195,27 +241,5 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
}
}
return username_attr, name_attr, sn_attr, mail_attr, admin_attr, true
}
func bindUser(l *ldap.Conn, userDN, passwd string) error {
log.Trace("Binding with userDN: %s", userDN)
err := l.Bind(userDN, passwd)
if err != nil {
log.Debug("LDAP auth. failed for %s, reason: %v", userDN, err)
return err
}
log.Trace("Bound successfully with userDN: %s", userDN)
return err
}
func ldapDial(ls *Source) (*ldap.Conn, error) {
if ls.UseSSL {
log.Debug("Using TLS for LDAP without verifying: %v", ls.SkipVerify)
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), &tls.Config{
InsecureSkipVerify: ls.SkipVerify,
})
} else {
return ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
}
return username, firstname, surname, mail, isAdmin, true
}

View File

@@ -88,6 +88,7 @@ type RepoSettingForm struct {
Interval int
MirrorAddress string
Private bool
EnablePrune bool
// Advanced settings
EnableWiki bool
@@ -96,6 +97,7 @@ type RepoSettingForm struct {
EnableIssues bool
EnableExternalTracker bool
TrackerURLFormat string
TrackerIssueStyle string
EnablePulls bool
}
@@ -141,7 +143,7 @@ func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) bin
}
type NewSlackHookForm struct {
PayloadURL string `binding:"Required;Url`
PayloadURL string `binding:"Required;Url"`
Channel string `binding:"Required"`
Username string
IconURL string

View File

@@ -14,7 +14,7 @@ import (
"github.com/issue9/identicon"
)
const _RANDOM_AVATAR_SIZE = 200
const AVATAR_SIZE = 290
// RandomImage generates and returns a random avatar image unique to input data
// in custom size (height and width).
@@ -39,5 +39,5 @@ func RandomImageSize(size int, data []byte) (image.Image, error) {
// RandomImage generates and returns a random avatar image unique to input data
// in default size (height and width).
func RandomImage(data []byte) (image.Image, error) {
return RandomImageSize(_RANDOM_AVATAR_SIZE, data)
return RandomImageSize(AVATAR_SIZE, data)
}

View File

@@ -207,7 +207,7 @@ func HashEmail(email string) string {
// AvatarLink returns avatar link by given email.
func AvatarLink(email string) string {
if setting.DisableGravatar || setting.OfflineMode {
return setting.AppSubUrl + "/img/avatar_default.jpg"
return setting.AppSubUrl + "/img/avatar_default.png"
}
return setting.GravatarSource + HashEmail(email)
@@ -522,3 +522,11 @@ func IsImageFile(data []byte) (string, bool) {
}
return contentType, false
}
func IsPDFFile(data []byte) (string, bool) {
contentType := http.DetectContentType(data)
if strings.Index(contentType, "application/pdf") != -1 {
return contentType, true
}
return contentType, false
}

File diff suppressed because one or more lines are too long

View File

@@ -18,6 +18,7 @@ import (
type APIContext struct {
*Context
Org *APIOrganization
}
// Error responses error message to client with given message.
@@ -40,6 +41,7 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
})
}
// SetLinkHeader sets pagination link header by given totol number and page size.
func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
page := paginater.New(total, pageSize, ctx.QueryInt("page"), 0)
links := make([]string, 0, 4)

View File

@@ -0,0 +1,14 @@
// Copyright 2016 The Gogs 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 context
import (
"github.com/gogits/gogs/models"
)
type APIOrganization struct {
Organization *models.User
Team *models.Team
}

View File

@@ -29,7 +29,14 @@ func Toggle(options *ToggleOptions) macaron.Handler {
return
}
// Checking non-logged users landing page.
// Check prohibit login users.
if ctx.IsSigned && ctx.User.ProhibitLogin {
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(200, "user/auth/prohibit_login")
return
}
// Check non-logged users landing page.
if !ctx.IsSigned && ctx.Req.RequestURI == "/" && setting.LandingPageUrl != setting.LANDING_PAGE_HOME {
ctx.Redirect(setting.AppSubUrl + string(setting.LandingPageUrl))
return

View File

@@ -105,6 +105,19 @@ func (ctx *Context) Handle(status int, title string, err error) {
ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status)))
}
// HandleError use error check function to determine if server should
// response as client input error or server internal error.
// It responses with given status code for client error,
// or error context description for logging purpose of server error.
func (ctx *Context) HandleError(title string, errck func(error) bool, err error, status int) {
if errck(err) {
ctx.Error(status, err.Error())
return
}
ctx.Handle(500, title, err)
}
func (ctx *Context) HandleText(status int, title string) {
if (status/100 == 4) || (status/100 == 5) {
log.Error(4, "%s", title)
@@ -156,7 +169,7 @@ func Contexter() macaron.Handler {
ctx.IsSigned = true
ctx.Data["IsSigned"] = ctx.IsSigned
ctx.Data["SignedUser"] = ctx.User
ctx.Data["SignedUserID"] = ctx.User.Id
ctx.Data["SignedUserID"] = ctx.User.ID
ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
} else {

View File

@@ -72,13 +72,13 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
} else if ctx.IsSigned {
ctx.Org.IsOwner = org.IsOwnedBy(ctx.User.Id)
ctx.Org.IsOwner = org.IsOwnedBy(ctx.User.ID)
if ctx.Org.IsOwner {
ctx.Org.IsMember = true
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
} else {
if org.IsOrgMember(ctx.User.Id) {
if org.IsOrgMember(ctx.User.ID) {
ctx.Org.IsMember = true
}
}
@@ -105,7 +105,8 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
return
}
} else {
if err := org.GetUserTeams(ctx.User.Id); err != nil {
org.Teams, err = org.GetUserTeams(ctx.User.ID)
if err != nil {
ctx.Handle(500, "GetUserTeams", err)
return
}

View File

@@ -122,7 +122,7 @@ func RepoAssignment(args ...bool) macaron.Handler {
ctx.Repo.Owner = owner
// Get repository.
repo, err := models.GetRepositoryByName(owner.Id, repoName)
repo, err := models.GetRepositoryByName(owner.ID, repoName)
if err != nil {
if models.IsErrRepoNotExist(err) {
ctx.Handle(404, "GetRepositoryByName", err)
@@ -160,6 +160,7 @@ func RepoAssignment(args ...bool) macaron.Handler {
ctx.Handle(500, "GetMirror", err)
return
}
ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune
ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
ctx.Data["Mirror"] = ctx.Repo.Mirror
}
@@ -173,7 +174,7 @@ func RepoAssignment(args ...bool) macaron.Handler {
return
}
ctx.Repo.GitRepo = gitRepo
ctx.Repo.RepoLink = repo.RepoLink()
ctx.Repo.RepoLink = repo.Link()
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
@@ -197,8 +198,8 @@ func RepoAssignment(args ...bool) macaron.Handler {
ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
if ctx.IsSigned {
ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.Id, repo.ID)
ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.Id, repo.ID)
ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
}
// repo is bare and display enable

View File

@@ -23,10 +23,10 @@ func NewLogger(bufLen int64, mode, config string) {
logger := newLogger(bufLen)
isExist := false
for _, l := range loggers {
for i, l := range loggers {
if l.adapter == mode {
isExist = true
l = logger
loggers[i] = logger
}
}
if !isExist {

View File

@@ -1,190 +0,0 @@
// Copyright 2014 The Gogs 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 mailer
import (
"fmt"
"path"
"strings"
"gopkg.in/gomail.v2"
"gopkg.in/macaron.v1"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/markdown"
"github.com/gogits/gogs/modules/setting"
)
const (
AUTH_ACTIVATE base.TplName = "mail/auth/activate"
AUTH_ACTIVATE_EMAIL base.TplName = "mail/auth/activate_email"
AUTH_REGISTER_NOTIFY base.TplName = "mail/auth/register_notify"
AUTH_RESET_PASSWORD base.TplName = "mail/auth/reset_passwd"
NOTIFY_COLLABORATOR base.TplName = "mail/notify/collaborator"
NOTIFY_MENTION base.TplName = "mail/notify/mention"
)
func ComposeTplData(u *models.User) map[interface{}]interface{} {
data := make(map[interface{}]interface{}, 10)
data["AppName"] = setting.AppName
data["AppVer"] = setting.AppVer
data["AppUrl"] = setting.AppUrl
data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
if u != nil {
data["User"] = u
}
return data
}
func SendUserMail(c *macaron.Context, u *models.User, tpl base.TplName, code, subject, info string) {
data := ComposeTplData(u)
data["Code"] = code
body, err := c.HTMLString(string(tpl), data)
if err != nil {
log.Error(4, "HTMLString: %v", err)
return
}
msg := NewMessage([]string{u.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, %s", u.Id, info)
SendAsync(msg)
}
func SendActivateAccountMail(c *macaron.Context, u *models.User) {
SendUserMail(c, u, AUTH_ACTIVATE, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account")
}
// SendResetPasswordMail sends reset password e-mail.
func SendResetPasswordMail(c *macaron.Context, u *models.User) {
SendUserMail(c, u, AUTH_RESET_PASSWORD, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "reset password")
}
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
func SendRegisterNotifyMail(c *macaron.Context, u *models.User) {
body, err := c.HTMLString(string(AUTH_REGISTER_NOTIFY), ComposeTplData(u))
if err != nil {
log.Error(4, "HTMLString: %v", err)
return
}
msg := NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), body)
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.Id)
SendAsync(msg)
}
// SendActivateAccountMail sends confirmation e-mail.
func SendActivateEmailMail(c *macaron.Context, u *models.User, email *models.EmailAddress) {
data := ComposeTplData(u)
data["Code"] = u.GenerateEmailActivateCode(email.Email)
data["Email"] = email.Email
body, err := c.HTMLString(string(AUTH_ACTIVATE_EMAIL), data)
if err != nil {
log.Error(4, "HTMLString: %v", err)
return
}
msg := NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), body)
msg.Info = fmt.Sprintf("UID: %d, activate email", u.Id)
SendAsync(msg)
}
// SendIssueNotifyMail sends mail notification of all watchers of repository.
func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
ws, err := models.GetWatchers(repo.ID)
if err != nil {
return nil, fmt.Errorf("GetWatchers[%d]: %v", repo.ID, err)
}
tos := make([]string, 0, len(ws))
for i := range ws {
uid := ws[i].UserID
if u.Id == uid {
continue
}
to, err := models.GetUserByID(uid)
if err != nil {
return nil, fmt.Errorf("GetUserByID: %v", err)
}
if to.IsOrganization() {
continue
}
tos = append(tos, to.Email)
}
if len(tos) == 0 {
return tos, nil
}
subject := fmt.Sprintf("[%s] %s (#%d)", repo.Name, issue.Name, issue.Index)
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.",
markdown.RenderSpecialLink([]byte(strings.Replace(issue.Content, "\n", "<br>", -1)), owner.Name+"/"+repo.Name, repo.ComposeMetas()),
setting.AppUrl, owner.Name, repo.Name, issue.Index)
msg := NewMessage(tos, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, issue notify", subject)
SendAsync(msg)
return tos, nil
}
// SendIssueMentionMail sends mail notification for who are mentioned in issue.
func SendIssueMentionMail(r macaron.Render, u, owner *models.User,
repo *models.Repository, issue *models.Issue, tos []string) error {
if len(tos) == 0 {
return nil
}
subject := fmt.Sprintf("[%s] %s (#%d)", repo.Name, issue.Name, issue.Index)
data := ComposeTplData(nil)
data["IssueLink"] = fmt.Sprintf("%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)
data["Subject"] = subject
data["ActUserName"] = u.DisplayName()
data["Content"] = string(markdown.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name, repo.ComposeMetas()))
body, err := r.HTMLString(string(NOTIFY_MENTION), data)
if err != nil {
return fmt.Errorf("HTMLString: %v", err)
}
msg := NewMessage(tos, subject, body)
msg.Info = fmt.Sprintf("Subject: %s, issue mention", subject)
SendAsync(msg)
return nil
}
// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(r macaron.Render, u, doer *models.User, repo *models.Repository) error {
subject := fmt.Sprintf("%s added you to %s/%s", doer.Name, repo.Owner.Name, repo.Name)
data := ComposeTplData(nil)
data["RepoLink"] = path.Join(repo.Owner.Name, repo.Name)
data["Subject"] = subject
body, err := r.HTMLString(string(NOTIFY_COLLABORATOR), data)
if err != nil {
return fmt.Errorf("HTMLString: %v", err)
}
msg := NewMessage([]string{u.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.Id)
SendAsync(msg)
return nil
}
func SendTestMail(email string) error {
return gomail.Send(&Sender{}, NewMessage([]string{email}, "Gogs Test Email!", "Gogs Test Email!").Message)
}

View File

@@ -14,6 +14,7 @@ import (
"strings"
"time"
"github.com/jaytaylor/html2text"
"gopkg.in/gomail.v2"
"github.com/gogits/gogs/modules/log"
@@ -26,14 +27,25 @@ type Message struct {
}
// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, body string) *Message {
func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
log.Trace("NewMessageFrom (htmlBody):\n%s", htmlBody)
msg := gomail.NewMessage()
msg.SetHeader("From", from)
msg.SetHeader("To", to...)
msg.SetHeader("Subject", subject)
msg.SetDateHeader("Date", time.Now())
msg.SetBody("text/plain", body)
msg.AddAlternative("text/html", body)
body, err := html2text.FromString(htmlBody)
if err != nil {
log.Error(4, "html2text.FromString: %v", err)
msg.SetBody("text/html", htmlBody)
} else {
msg.SetBody("text/plain", body)
if setting.MailService.EnableHTMLAlternative {
msg.AddAlternative("text/html", htmlBody)
}
}
return &Message{
Message: msg,
@@ -186,7 +198,7 @@ func processMailQueue() {
case msg := <-mailQueue:
log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
if err := gomail.Send(sender, msg.Message); err != nil {
log.Error(4, "Fail to send e-mails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
log.Error(3, "Fail to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
} else {
log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
}

View File

@@ -22,6 +22,11 @@ import (
"github.com/gogits/gogs/modules/setting"
)
const (
ISSUE_NAME_STYLE_NUMERIC = "numeric"
ISSUE_NAME_STYLE_ALPHANUMERIC = "alphanumeric"
)
var Sanitizer = bluemonday.UGCPolicy()
// BuildSanitizer initializes sanitizer with allowed attributes based on settings.
@@ -70,7 +75,7 @@ func IsReadmeFile(name string) bool {
var (
// MentionPattern matches string that mentions someone, e.g. @Unknwon
MentionPattern = regexp.MustCompile(`(\s|^)@[0-9a-zA-Z_\.]+`)
MentionPattern = regexp.MustCompile(`(\s|^)@[0-9a-zA-Z-_\.]+`)
// CommitPattern matches link to certain commit with or without trailing hash,
// e.g. https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2
@@ -79,13 +84,25 @@ var (
// IssueFullPattern matches link to an issue with or without trailing hash,
// e.g. https://try.gogs.io/gogs/gogs/issues/4#issue-685
IssueFullPattern = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`)
// IssueIndexPattern matches string that references to an issue, e.g. #1287
IssueIndexPattern = regexp.MustCompile(`( |^|\()#[0-9]+\b`)
// IssueNumericPattern matches string that references to a numeric issue, e.g. #1287
IssueNumericPattern = regexp.MustCompile(`( |^|\()#[0-9]+\b`)
// IssueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
IssueAlphanumericPattern = regexp.MustCompile(`( |^|\()[A-Z]{1,10}-[1-9][0-9]*\b`)
// Sha1CurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
Sha1CurrentPattern = regexp.MustCompile(`\b[0-9a-f]{40}\b`)
)
// FindAllMentions matches mention patterns in given content
// and returns a list of found user names without @ prefix.
func FindAllMentions(content string) []string {
mentions := MentionPattern.FindAllString(content, -1)
for i := range mentions {
mentions[i] = strings.TrimSpace(mentions[i])[1:] // Strip @ character
}
return mentions
}
// Renderer is a extended version of underlying render object.
type Renderer struct {
blackfriday.Renderer
@@ -113,28 +130,30 @@ func (r *Renderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
// Since this method could only possibly serve one link at a time,
// we do not need to find all.
m := CommitPattern.Find(link)
if m != nil {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
if bytes.HasPrefix(link, []byte(setting.AppUrl)) {
m := CommitPattern.Find(link)
if m != nil {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
out.WriteString(fmt.Sprintf(` <code><a href="%s">%s</a></code>`, m, base.ShortSha(string(m[i+7:j]))))
return
}
out.WriteString(fmt.Sprintf(` <code><a href="%s">%s</a></code>`, m, base.ShortSha(string(m[i+7:j]))))
return
}
m = IssueFullPattern.Find(link)
if m != nil {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "issues/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
m = IssueFullPattern.Find(link)
if m != nil {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "issues/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
out.WriteString(fmt.Sprintf(`<a href="%s">#%s</a>`, m, base.ShortSha(string(m[i+7:j]))))
return
}
out.WriteString(fmt.Sprintf(` <a href="%s">#%s</a>`, m, base.ShortSha(string(m[i+7:j]))))
return
}
r.Renderer.AutoLink(out, link, kind)
@@ -193,6 +212,9 @@ func (r *Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byt
// cutoutVerbosePrefix cutouts URL prefix including sub-path to
// return a clean unified string of request URL path.
func cutoutVerbosePrefix(prefix string) string {
if len(prefix) == 0 || prefix[0] != '/' {
return prefix
}
count := 0
for i := 0; i < len(prefix); i++ {
if prefix[i] == '/' {
@@ -208,22 +230,30 @@ func cutoutVerbosePrefix(prefix string) string {
// RenderIssueIndexPattern renders issue indexes to corresponding links.
func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
urlPrefix = cutoutVerbosePrefix(urlPrefix)
ms := IssueIndexPattern.FindAll(rawBytes, -1)
pattern := IssueNumericPattern
if metas["style"] == ISSUE_NAME_STYLE_ALPHANUMERIC {
pattern = IssueAlphanumericPattern
}
ms := pattern.FindAll(rawBytes, -1)
for _, m := range ms {
var space string
if m[0] != '#' {
space = string(m[0])
m = m[1:]
if m[0] == ' ' || m[0] == '(' {
m = m[1:] // ignore leading space or opening parentheses
}
var link string
if metas == nil {
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s/issues/%s">%s</a>`,
space, urlPrefix, m[1:], m)), 1)
link = fmt.Sprintf(`<a href="%s/issues/%s">%s</a>`, urlPrefix, m[1:], m)
} else {
// Support for external issue tracker
metas["index"] = string(m[1:])
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s">%s</a>`,
space, com.Expand(metas["format"], metas), m)), 1)
if metas["style"] == ISSUE_NAME_STYLE_ALPHANUMERIC {
metas["index"] = string(m)
} else {
metas["index"] = string(m[1:])
}
link = fmt.Sprintf(`<a href="%s">%s</a>`, com.Expand(metas["format"], metas), m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(link), 1)
}
return rawBytes
}

View File

@@ -0,0 +1,308 @@
package markdown_test
import (
. "github.com/gogits/gogs/modules/markdown"
. "github.com/smartystreets/goconvey/convey"
"testing"
"bytes"
"github.com/gogits/gogs/modules/setting"
"github.com/russross/blackfriday"
)
func TestMarkdown(t *testing.T) {
Convey("Rendering an issue mention", t, func() {
var (
urlPrefix = "/prefix"
metas map[string]string = nil
)
setting.AppSubUrlDepth = 0
Convey("To the internal issue tracker", func() {
Convey("It should not render anything when there are no mentions", func() {
testCases := []string{
"",
"this is a test",
"test 123 123 1234",
"#",
"# # #",
"# 123",
"#abcd",
"##1234",
"test#1234",
"#1234test",
" test #1234test",
}
for i := 0; i < len(testCases); i++ {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i])
}
})
Convey("It should render freestanding mentions", func() {
testCases := []string{
"#1234 test", "<a href=\"/prefix/issues/1234\">#1234</a> test",
"test #1234 issue", "test <a href=\"/prefix/issues/1234\">#1234</a> issue",
"test issue #1234", "test issue <a href=\"/prefix/issues/1234\">#1234</a>",
"#5 test", "<a href=\"/prefix/issues/5\">#5</a> test",
"test #5 issue", "test <a href=\"/prefix/issues/5\">#5</a> issue",
"test issue #5", "test issue <a href=\"/prefix/issues/5\">#5</a>",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
Convey("It should not render issue mention without leading space", func() {
input := []byte("test#54321 issue")
expected := "test#54321 issue"
So(string(RenderIssueIndexPattern(input, urlPrefix, metas)), ShouldEqual, expected)
})
Convey("It should not render issue mention without trailing space", func() {
input := []byte("test #54321issue")
expected := "test #54321issue"
So(string(RenderIssueIndexPattern(input, urlPrefix, metas)), ShouldEqual, expected)
})
Convey("It should render issue mention in parentheses", func() {
testCases := []string{
"(#54321 issue)", "(<a href=\"/prefix/issues/54321\">#54321</a> issue)",
"test (#54321) issue", "test (<a href=\"/prefix/issues/54321\">#54321</a>) issue",
"test (#54321 extra) issue", "test (<a href=\"/prefix/issues/54321\">#54321</a> extra) issue",
"test (#54321 issue)", "test (<a href=\"/prefix/issues/54321\">#54321</a> issue)",
"test (#54321)", "test (<a href=\"/prefix/issues/54321\">#54321</a>)",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
Convey("It should render multiple issue mentions in the same line", func() {
testCases := []string{
"#54321 #1243", "<a href=\"/prefix/issues/54321\">#54321</a> <a href=\"/prefix/issues/1243\">#1243</a>",
"test #54321 #1243", "test <a href=\"/prefix/issues/54321\">#54321</a> <a href=\"/prefix/issues/1243\">#1243</a>",
"(#54321 #1243)", "(<a href=\"/prefix/issues/54321\">#54321</a> <a href=\"/prefix/issues/1243\">#1243</a>)",
"(#54321)(#1243)", "(<a href=\"/prefix/issues/54321\">#54321</a>)(<a href=\"/prefix/issues/1243\">#1243</a>)",
"text #54321 test #1243 issue", "text <a href=\"/prefix/issues/54321\">#54321</a> test <a href=\"/prefix/issues/1243\">#1243</a> issue",
"#1 (#4321) test", "<a href=\"/prefix/issues/1\">#1</a> (<a href=\"/prefix/issues/4321\">#4321</a>) test",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
})
Convey("To an external issue tracker with numeric style", func() {
metas = make(map[string]string)
metas["format"] = "https://someurl.com/{user}/{repo}/{index}"
metas["user"] = "someuser"
metas["repo"] = "somerepo"
metas["style"] = ISSUE_NAME_STYLE_NUMERIC
Convey("should not render anything when there are no mentions", func() {
testCases := []string{
"this is a test",
"test 123 123 1234",
"#",
"# # #",
"# 123",
"#abcd",
}
for i := 0; i < len(testCases); i++ {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i])
}
})
Convey("It should render freestanding issue mentions", func() {
testCases := []string{
"#1234 test", "<a href=\"https://someurl.com/someuser/somerepo/1234\">#1234</a> test",
"test #1234 issue", "test <a href=\"https://someurl.com/someuser/somerepo/1234\">#1234</a> issue",
"test issue #1234", "test issue <a href=\"https://someurl.com/someuser/somerepo/1234\">#1234</a>",
"#5 test", "<a href=\"https://someurl.com/someuser/somerepo/5\">#5</a> test",
"test #5 issue", "test <a href=\"https://someurl.com/someuser/somerepo/5\">#5</a> issue",
"test issue #5", "test issue <a href=\"https://someurl.com/someuser/somerepo/5\">#5</a>",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
Convey("It should not render issue mention without leading space", func() {
input := []byte("test#54321 issue")
expected := "test#54321 issue"
So(string(RenderIssueIndexPattern(input, urlPrefix, metas)), ShouldEqual, expected)
})
Convey("It should not render issue mention without trailing space", func() {
input := []byte("test #54321issue")
expected := "test #54321issue"
So(string(RenderIssueIndexPattern(input, urlPrefix, metas)), ShouldEqual, expected)
})
Convey("It should render issue mention in parentheses", func() {
testCases := []string{
"(#54321 issue)", "(<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a> issue)",
"test (#54321) issue", "test (<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a>) issue",
"test (#54321 extra) issue", "test (<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a> extra) issue",
"test (#54321 issue)", "test (<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a> issue)",
"test (#54321)", "test (<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a>)",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
Convey("It should render multiple issue mentions in the same line", func() {
testCases := []string{
"#54321 #1243", "<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a> <a href=\"https://someurl.com/someuser/somerepo/1243\">#1243</a>",
"test #54321 #1243", "test <a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a> <a href=\"https://someurl.com/someuser/somerepo/1243\">#1243</a>",
"(#54321 #1243)", "(<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a> <a href=\"https://someurl.com/someuser/somerepo/1243\">#1243</a>)",
"(#54321)(#1243)", "(<a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a>)(<a href=\"https://someurl.com/someuser/somerepo/1243\">#1243</a>)",
"text #54321 test #1243 issue", "text <a href=\"https://someurl.com/someuser/somerepo/54321\">#54321</a> test <a href=\"https://someurl.com/someuser/somerepo/1243\">#1243</a> issue",
"#1 (#4321) test", "<a href=\"https://someurl.com/someuser/somerepo/1\">#1</a> (<a href=\"https://someurl.com/someuser/somerepo/4321\">#4321</a>) test",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
})
Convey("To an external issue tracker with alphanumeric style", func() {
metas = make(map[string]string)
metas["format"] = "https://someurl.com/{user}/{repo}/?b={index}"
metas["user"] = "someuser"
metas["repo"] = "somerepo"
metas["style"] = ISSUE_NAME_STYLE_ALPHANUMERIC
Convey("It should not render anything when there are no mentions", func() {
testCases := []string{
"",
"this is a test",
"test 123 123 1234",
"#",
"##1234",
"# 123",
"#abcd",
"test #123",
"abc-1234", // issue prefix must be capital
"ABc-1234", // issue prefix must be _all_ capital
"ABCDEFGHIJK-1234", // the limit is 10 characters in the prefix
"ABC1234", // dash is required
"test ABC- test", // number is required
"test -1234 test", // prefix is required
"testABC-123 test", // leading space is required
"test ABC-123test", // trailing space is required
"ABC-0123", // no leading zero
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i])
}
})
Convey("It should render freestanding issue mention", func() {
testCases := []string{
"OTT-1234 test", "<a href=\"https://someurl.com/someuser/somerepo/?b=OTT-1234\">OTT-1234</a> test",
"test T-12 issue", "test <a href=\"https://someurl.com/someuser/somerepo/?b=T-12\">T-12</a> issue",
"test issue ABCDEFGHIJ-1234567890", "test issue <a href=\"https://someurl.com/someuser/somerepo/?b=ABCDEFGHIJ-1234567890\">ABCDEFGHIJ-1234567890</a>",
"A-1 test", "<a href=\"https://someurl.com/someuser/somerepo/?b=A-1\">A-1</a> test",
"test ZED-1 issue", "test <a href=\"https://someurl.com/someuser/somerepo/?b=ZED-1\">ZED-1</a> issue",
"test issue DEED-7154", "test issue <a href=\"https://someurl.com/someuser/somerepo/?b=DEED-7154\">DEED-7154</a>",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
Convey("It should render issue mention in parentheses", func() {
testCases := []string{
"(ABG-124 issue)", "(<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a> issue)",
"test (ABG-124) issue", "test (<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a>) issue",
"test (ABG-124 extra) issue", "test (<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a> extra) issue",
"test (ABG-124 issue)", "test (<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a> issue)",
"test (ABG-124)", "test (<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a>)",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
Convey("It should render multiple issue mentions in the same line", func() {
testCases := []string{
"ABG-124 OTT-4321", "<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a> <a href=\"https://someurl.com/someuser/somerepo/?b=OTT-4321\">OTT-4321</a>",
"test ABG-124 OTT-4321", "test <a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a> <a href=\"https://someurl.com/someuser/somerepo/?b=OTT-4321\">OTT-4321</a>",
"(ABG-124 OTT-4321)", "(<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a> <a href=\"https://someurl.com/someuser/somerepo/?b=OTT-4321\">OTT-4321</a>)",
"(ABG-124)(OTT-4321)", "(<a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a>)(<a href=\"https://someurl.com/someuser/somerepo/?b=OTT-4321\">OTT-4321</a>)",
"text ABG-124 test OTT-4321 issue", "text <a href=\"https://someurl.com/someuser/somerepo/?b=ABG-124\">ABG-124</a> test <a href=\"https://someurl.com/someuser/somerepo/?b=OTT-4321\">OTT-4321</a> issue",
"A-1 (RRE-345) test", "<a href=\"https://someurl.com/someuser/somerepo/?b=A-1\">A-1</a> (<a href=\"https://someurl.com/someuser/somerepo/?b=RRE-345\">RRE-345</a>) test",
}
for i := 0; i < len(testCases); i += 2 {
So(string(RenderIssueIndexPattern([]byte(testCases[i]), urlPrefix, metas)), ShouldEqual, testCases[i+1])
}
})
})
})
Convey("Rendering an issue URL", t, func() {
setting.AppUrl = "http://localhost:3000/"
htmlFlags := 0
htmlFlags |= blackfriday.HTML_SKIP_STYLE
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
renderer := &Renderer{
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
}
buffer := new(bytes.Buffer)
Convey("To the internal issue tracker", func() {
Convey("It should render valid issue URLs", func() {
testCases := []string{
"http://localhost:3000/user/repo/issues/3333", "<a href=\"http://localhost:3000/user/repo/issues/3333\">#3333</a>",
}
for i := 0; i < len(testCases); i += 2 {
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
line, _ := buffer.ReadString(0)
So(line, ShouldEqual, testCases[i+1])
}
})
Convey("It should render but not change non-issue URLs", func() {
testCases := []string{
"http://1111/2222/ssss-issues/3333?param=blah&blahh=333", "<a href=\"http://1111/2222/ssss-issues/3333?param=blah&amp;blahh=333\">http://1111/2222/ssss-issues/3333?param=blah&amp;blahh=333</a>",
"http://test.com/issues/33333", "<a href=\"http://test.com/issues/33333\">http://test.com/issues/33333</a>",
"http://test.com/issues/3", "<a href=\"http://test.com/issues/3\">http://test.com/issues/3</a>",
"http://issues/333", "<a href=\"http://issues/333\">http://issues/333</a>",
"https://issues/333", "<a href=\"https://issues/333\">https://issues/333</a>",
"http://tissues/0", "<a href=\"http://tissues/0\">http://tissues/0</a>",
}
for i := 0; i < len(testCases); i += 2 {
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
line, _ := buffer.ReadString(0)
So(line, ShouldEqual, testCases[i+1])
}
})
})
})
Convey("Rendering a commit URL", t, func() {
setting.AppUrl = "http://localhost:3000/"
htmlFlags := 0
htmlFlags |= blackfriday.HTML_SKIP_STYLE
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
renderer := &Renderer{
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
}
buffer := new(bytes.Buffer)
Convey("To the internal issue tracker", func() {
Convey("It should correctly convert URLs", func() {
testCases := []string{
"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae", " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">d8a994ef24</a></code>",
"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">d8a994ef24</a></code>",
"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", "<a href=\"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2</a>",
"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae", "<a href=\"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae</a>",
}
for i := 0; i < len(testCases); i += 2 {
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
line, _ := buffer.ReadString(0)
So(line, ShouldEqual, testCases[i+1])
}
})
})
})
}

View File

@@ -115,14 +115,23 @@ var (
ScriptType string
// UI settings
ExplorePagingNum int
IssuePagingNum int
FeedMaxCommitNum int
AdminUserPagingNum int
AdminRepoPagingNum int
AdminNoticePagingNum int
AdminOrgPagingNum int
ThemeColorMetaTag string
UI struct {
ExplorePagingNum int
IssuePagingNum int
FeedMaxCommitNum int
ThemeColorMetaTag string
MaxDisplayFileSize int64
Admin struct {
UserPagingNum int
RepoPagingNum int
NoticePagingNum int
OrgPagingNum int
} `ini:"ui.admin"`
User struct {
RepoPagingNum int
} `ini:"ui.user"`
}
// Markdown sttings
Markdown struct {
@@ -159,18 +168,6 @@ var (
SessionConfig session.Options
CSRFCookieName = "_csrf"
// Git settings
Git struct {
MaxGitDiffLines int
GcArgs []string `delim:" "`
Timeout struct {
Migrate int
Mirror int
Clone int
Pull int
} `ini:"git.timeout"`
}
// Cron tasks
Cron struct {
UpdateMirror struct {
@@ -192,6 +189,25 @@ var (
} `ini:"cron.check_repo_stats"`
}
// Git settings
Git struct {
MaxGitDiffLines int
MaxGitDiffLineCharacters int
MaxGitDiffFiles int
GcArgs []string `delim:" "`
Timeout struct {
Migrate int
Mirror int
Clone int
Pull int
} `ini:"git.timeout"`
}
// API settings
API struct {
MaxResponseItems int
}
// I18n settings
Langs, Names []string
dateLangs map[string]string
@@ -331,7 +347,7 @@ func NewContext() {
Domain = sec.Key("DOMAIN").MustString("localhost")
HttpAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
HttpPort = sec.Key("HTTP_PORT").MustString("3000")
LocalURL = sec.Key("LOCAL_ROOT_URL").MustString("http://localhost:" + HttpPort + "/")
LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(string(Protocol) + "://localhost:" + HttpPort + "/")
OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir)
@@ -361,12 +377,6 @@ func NewContext() {
} else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil {
log.Fatal(4, "Fail to create '%s': %v", SSH.KeyTestPath, err)
}
if !filepath.IsAbs(SSH.KeygenPath) {
if _, err := exec.LookPath(SSH.KeygenPath); err != nil {
log.Fatal(4, "Fail to test '%s' command: %v (forgotten install?)", SSH.KeygenPath, err)
}
}
}
SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool()
@@ -435,19 +445,6 @@ func NewContext() {
log.Fatal(4, "Fail to map Repository settings: %v", err)
}
// UI settings.
sec = Cfg.Section("ui")
ExplorePagingNum = sec.Key("EXPLORE_PAGING_NUM").MustInt(20)
IssuePagingNum = sec.Key("ISSUE_PAGING_NUM").MustInt(10)
FeedMaxCommitNum = sec.Key("FEED_MAX_COMMIT_NUM").MustInt(5)
sec = Cfg.Section("ui.admin")
AdminUserPagingNum = sec.Key("USER_PAGING_NUM").MustInt(50)
AdminRepoPagingNum = sec.Key("REPO_PAGING_NUM").MustInt(50)
AdminNoticePagingNum = sec.Key("NOTICE_PAGING_NUM").MustInt(50)
AdminOrgPagingNum = sec.Key("ORG_PAGING_NUM").MustInt(50)
ThemeColorMetaTag = sec.Key("THEME_COLOR_META_TAG").MustString("#ff5343")
sec = Cfg.Section("picture")
AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars"))
forcePathSeparator(AvatarUploadPath)
@@ -467,12 +464,16 @@ func NewContext() {
DisableGravatar = true
}
if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
if err = Cfg.Section("ui").MapTo(&UI); err != nil {
log.Fatal(4, "Fail to map UI settings: %v", err)
} else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
log.Fatal(4, "Fail to map Markdown settings: %v", err)
} else if err = Cfg.Section("git").MapTo(&Git); err != nil {
log.Fatal(4, "Fail to map Git settings: %v", err)
} else if err = Cfg.Section("cron").MapTo(&Cron); err != nil {
log.Fatal(4, "Fail to map Cron settings: %v", err)
} else if err = Cfg.Section("git").MapTo(&Git); err != nil {
log.Fatal(4, "Fail to map Git settings: %v", err)
} else if err = Cfg.Section("api").MapTo(&API); err != nil {
log.Fatal(4, "Fail to map API settings: %v", err)
}
Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
@@ -618,16 +619,17 @@ func newSessionService() {
// Mailer represents mail service.
type Mailer struct {
QueueLength int
Name string
Host string
From string
User, Passwd string
DisableHelo bool
HeloHostname string
SkipVerify bool
UseCertificate bool
CertFile, KeyFile string
QueueLength int
Name string
Host string
From string
User, Passwd string
DisableHelo bool
HeloHostname string
SkipVerify bool
UseCertificate bool
CertFile, KeyFile string
EnableHTMLAlternative bool
}
var (
@@ -642,17 +644,18 @@ func newMailService() {
}
MailService = &Mailer{
QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100),
Name: sec.Key("NAME").MustString(AppName),
Host: sec.Key("HOST").String(),
User: sec.Key("USER").String(),
Passwd: sec.Key("PASSWD").String(),
DisableHelo: sec.Key("DISABLE_HELO").MustBool(),
HeloHostname: sec.Key("HELO_HOSTNAME").String(),
SkipVerify: sec.Key("SKIP_VERIFY").MustBool(),
UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(),
CertFile: sec.Key("CERT_FILE").String(),
KeyFile: sec.Key("KEY_FILE").String(),
QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100),
Name: sec.Key("NAME").MustString(AppName),
Host: sec.Key("HOST").String(),
User: sec.Key("USER").String(),
Passwd: sec.Key("PASSWD").String(),
DisableHelo: sec.Key("DISABLE_HELO").MustBool(),
HeloHostname: sec.Key("HELO_HOSTNAME").String(),
SkipVerify: sec.Key("SKIP_VERIFY").MustBool(),
UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(),
CertFile: sec.Key("CERT_FILE").String(),
KeyFile: sec.Key("KEY_FILE").String(),
EnableHTMLAlternative: sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(),
}
MailService.From = sec.Key("FROM").MustString(MailService.User)
log.Info("Mail Service Enabled")

View File

@@ -18,6 +18,7 @@ import (
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/markdown"
"github.com/gogits/gogs/modules/setting"
)
@@ -97,11 +98,11 @@ func NewFuncMap() []template.FuncMap {
"ActionContent2Commits": ActionContent2Commits,
"ToUtf8": ToUtf8,
"EscapePound": func(str string) string {
return strings.Replace(strings.Replace(str, "%", "%25", -1), "#", "%23", -1)
return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20").Replace(str)
},
"RenderCommitMessage": RenderCommitMessage,
"ThemeColorMetaTag": func() string {
return setting.ThemeColorMetaTag
return setting.UI.ThemeColorMetaTag
},
}}
}
@@ -240,7 +241,7 @@ func ActionIcon(opType int) string {
case 7: // New pull request
return "git-pull-request"
case 10: // Comment issue
return "comment"
return "comment-discussion"
case 11: // Merge pull request
return "git-merge"
case 12, 14: // Close issue or pull request
@@ -255,7 +256,7 @@ func ActionIcon(opType int) string {
func ActionContent2Commits(act Actioner) *models.PushCommits {
push := models.NewPushCommits()
if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
return nil
log.Error(4, "json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
}
return push
}

View File

@@ -5,7 +5,7 @@
package user
import (
"os"
"os"
)
func CurrentUsername() string {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

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