Compare commits

..

50 Commits

Author SHA1 Message Date
techknowlogick
8a51c48eb6 Changelog for 1.12.4 release (#12687)
Co-authored-by: zeripath <art27@cantab.net>
2020-09-03 17:00:13 -04:00
6543
0fa538e552 [Backport] Fix comment broken issue ref dependence (#12651) (#12692)
* deleteIssuesByRepoID: delete related CommentTypeRemoveDependency & CommentTypeAddDependency comments too

* Ignore ErrIssueNotExist on comment.LoadDepIssueDetails()

* CI.restart()
2020-09-03 17:23:36 +08:00
6543
69e4b6910b Make default StaticRootPath compile time settable (#12371) (#12652)
Make it possible to compile the default location of StaticRootPath independent from AppWorkPath

Co-authored-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-09-02 19:12:24 -04:00
techknowlogick
0e9dcc9500 When reading expired sessions - expire them (#12686) (#12690)
* When reading expired sessions - expire them

Update to latest macaron/session following merge of
https://gitea.com/macaron/session/pulls/11

Also remove old memory provider as 11 updates the memory provider to
make it unnecessary.

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

* and macaron/session/pulls/12

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

Co-authored-by: zeripath <art27@cantab.net>
2020-09-02 18:51:56 -04:00
6543
87f02d90cf Escape provider name in oauth2 provider redirect (#12650)
Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Andrew Thornton <art27@cantab.net>
2020-08-30 23:55:19 +01:00
John Olheiser
21cd7ab812 Mark Cache with ini tag (#12605) (#12611)
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2020-08-26 11:53:14 -04:00
John Olheiser
981216c9fe Escape Email in forgot_password.tmpl (#12610) (#12612)
Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: zeripath <art27@cantab.net>
2020-08-26 10:12:09 -05:00
techknowlogick
cfbfb73c56 go1.15 on windows (#12589) (#12593)
We don't support go1.15 on 1.12.x branch, however this will allow users who chose to build with go1.15 on windows to be successful
2020-08-24 18:38:47 -04:00
zeripath
4a548a0332 Fix diff path unquoting (#12554) (#12575)
Backport #12554

* Fix diff path unquoting

services/gitdiff/gitdiff.go whereby there it assumed that the path would
always be quoted on both sides

This PR simplifies the code here and uses fmt.Fscanf to parse the
strings as necessary.

Fix #12546

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

* Add testcase as per @mrsdizzie

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-08-23 16:58:09 +03:00
zeripath
8bf2ee1e02 Skip SSPI authentication attempts for /api/internal (#12556) (#12559)
Backport #12556

SSPI fails badly on authentication attempts to /api/internal which
it can never succesfully authenticate.

Fix #11260

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

Co-authored-by: Lauris BH <lauris@nix.lv>
2020-08-22 17:09:14 -04:00
zeripath
a687980412 Default empty merger list to those with write permissions (#12535) (#12560)
Backport #12535

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

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-08-22 23:35:56 +03:00
zeripath
1f85815a3b models: break out of loop (#12386) (#12561)
Backport #12386

Co-authored-by: Lars Lehtonen <lars.lehtonen@gmail.com>
2020-08-22 16:22:07 +01:00
silverwind
ee5e5a5093 Improve HTML escaping helper (#12562)
The previous method did not escape single quotes which under some
circumstances can lead to XSS vulnerabilites and the fact that it
depends on jQuery is also not ideal. Replace it with a lightweight
module.
2020-08-22 13:36:56 +01:00
zeripath
03ba12aabf Prevent NPE on commenting on lines with invalidated comments (#12549) (#12550)
* Prevent NPE on commenting on lines with invalidated comments

Only check for a review if we are replying to a previous review.

Prevent the NPE in #12239 by assuming that a comment without a Review is
non-pending.

Fix #12239

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

* Add hack around to show the broken comments

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-08-21 10:52:20 +03:00
John Olheiser
24ed1b5feb Remove hardcoded ES indexername (#12521) (#12526)
Co-authored-by: Wim <wim@42.be>
2020-08-18 21:42:22 -04:00
zeripath
8282697734 Keys should not verify revoked email addresses (#12486) (#12495)
Backport #12486

Fix #6778

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-08-17 12:06:31 -04:00
techknowlogick
ec48618d40 Fix bug preventing transfer to private organization (#12497) (#12501)
* Fix bug preventing transfer to private organization

The code assessing whether a private organization was visible to a user before
allowing transfer was incorrect due to testing membership the wrong way round

This PR fixes this issue and renames the function performing the test to be
clearer.

Further looking at the API for transfer repository - no testing was
performed to ensure that the acting user could actually see the new
owning organization.

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

* change IsUserPartOfOrg everywhere

Co-authored-by: zeripath <art27@cantab.net>
2020-08-17 09:32:33 +03:00
techknowlogick
f0dd07129a Do not add prefix on http/https submodule links (#12477) (#12479)
Fix #12345

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

Co-authored-by: zeripath <art27@cantab.net>
2020-08-13 11:53:40 -04:00
techknowlogick
6d3b8141df Fix ignored login on compare (#12476) (#12478)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-08-12 16:36:22 -04:00
techknowlogick
13c4c7a132 Match GH with Commit page (#12425) (#12431)
Co-authored-by: zeripath <art27@cantab.net>

Co-authored-by: zeripath <art27@cantab.net>
2020-08-05 15:49:12 +08:00
techknowlogick
6015d30dd6 Fix incorrect error logging in Stats indexer and OAuth2 (#12387) (#12422)
* Fix incorrect logging in oauth2.go

Fix #11945

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

* Handle ErrAlreadyInQueue in stats indexer

Fix #12380

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

* Fixes type in error message of indexer

Add the missing character in the error message.

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Lieven Hollevoet <hollie@lika.be>

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lieven Hollevoet <hollie@lika.be>
2020-08-04 16:27:43 +08:00
6543
b1cfb0d7a2 [Vendor] upgrade google/go-github to v32.1.0 (#12361) (#12390)
* upgrate go-github client to v32.1.0

* migrate
2020-07-31 12:02:23 -04:00
6543
48a423a8a8 Rendoer emoji's of Commit message on feed-page (#12373) 2020-07-29 17:09:47 -04:00
zeripath
cc8a7c9345 Git 2.28 no longer permits diff with ... on unrelated branches (#12370)
Backport #12364

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-07-29 12:42:22 -04:00
6543
77af0a23c4 Changelog v1.12.3 (#12356)
* Changelog v1.12.3

* better description for 12351

* @techknowlogick suggestions
2020-07-28 16:41:36 -04:00
Richard Mahn
87bfe02b5b Backport to v1.12 for #12341 - Release date fix (#12351)
* Backport for Issue #12341 PR #12343 - Release date fix

* Adds sleep for comparing times

* Fixes imports

* Fixes tests
2020-07-28 14:10:50 -04:00
techknowlogick
9bac656b7d Show 404 page when release not found (#12328) (#12332)
Signed-off-by: a1012112796 <1012112796@qq.com>

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

Co-authored-by: 赵智超 <1012112796@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-07-27 00:39:44 +03:00
silverwind
ad68c9ccb2 Backport emoji fixes to 1.12 (#12327)
* Fix emoji detection in certain cases (#12320)

* Fix emoji detection certain cases

Previous tests weren't complicated enough so there were some situations where emojis were't detected properly. Find the earliest occurance in addition to checking for the longest combination.

Fixes #12312

* ok spell bot

Co-authored-by: Lauris BH <lauris@nix.lv>

* Reduce emoji size (#12317)

* Reduce emoji size

Rendering should now pretty much match GitHub with 1.25em. I verified
that emojis don't increase the line height and removed unecessary size
overrides because now all emojis should appear similar in relation to
the font size.

* fix reaction hover

Co-authored-by: mrsdizzie <info@mrsdizzie.com>
Co-authored-by: Lauris BH <lauris@nix.lv>
2020-07-25 12:50:57 -04:00
techknowlogick
8d1cd4d252 Fix double-indirection bug in logging IDs (#12294) (#12308)
This PR fixes a bug in log.NewColoredIDValue() which led to a double
indirection and incorrect IDs being printed out.

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

Co-authored-by: zeripath <art27@cantab.net>
2020-07-24 02:24:22 +03:00
techknowlogick
64eaa2a942 [ui] Link to pr list page on sidebar when view pr (#12256) (#12263)
Fix #12254

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

Co-authored-by: 赵智超 <1012112796@qq.com>
2020-07-16 11:56:09 -04:00
zeripath
489e9162fc Extend Notifications API and return pinned notifications by default (#12164) (#12232)
Backport #12164

This PR extends the notifications API to allow specific notification statuses to be searched for and to allow setting of notifications to statuses other than read.

By default unread and pinned statuses will be returned when querying for notifications - however pinned statuses will not be marked as read.

Close #12152

Signed-off-by: Andrew Thornton art27@cantab.net
2020-07-13 21:52:05 +01:00
Lauris BH
5e62137fe3 Changelog for v1.12.2 (#12214)
* Changelog for v1.12.2

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

* Update CHANGELOG.md

Co-authored-by: mrsdizzie <info@mrsdizzie.com>

Co-authored-by: mrsdizzie <info@mrsdizzie.com>
2020-07-11 19:53:26 +03:00
赵智超
6a081f95c0 Decrease the num_stars when deleting a repo (#11954) (#12188)
* Decrease the  num_stars when deleting a repo

fix #11949

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

* Add migration

* use batch

* Apply suggestions from code review

Co-authored-by: Lauris BH <lauris@nix.lv>

* fix lint

* fix lint

* fix ci

* fix ci2

* add doctor

* duplicate code

* fix migration

* fix some nits

* add start

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

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-07-08 17:52:40 -04:00
techknowlogick
c3c246cffc Fix regression: Gitea commits API again returns commit summaries, not full messages (#12186) (#12187)
Closes #12185

Co-authored-by: Kristian Antonsen <kristian@derfor.dk>
2020-07-08 11:22:07 -04:00
techknowlogick
85be939c2a properly set symbolic-ref HEAD when a repo is created with a non-master default branch (#12135) (#12182)
This fixes an issue I noticed with #10803: when you create a repo with a non-master default branch, gitea doesn't change the remote ref HEAD, so it still points at refs/heads/master. As a result, cloning my repos gives me error messages and doesn't check out the desired default branch, so I need to manually check it out after cloning.

Co-authored-by: xenofem <45297511+xenofem@users.noreply.github.com>
2020-07-08 00:40:22 -04:00
Jürgen Hötzel
a680c911e4 Trim to 255 runes instead of bytes (#12150)
* Trim to 255 runes instead of bytes

Prevents invalid UTF-8 encoding for Description and Website. Refs #7905

* Apply suggestions from code review

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

Co-authored-by: techknowlogick <matti@mdranta.net>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
2020-07-07 19:05:35 -04:00
zeripath
d9c18cbba0 Ensure Subkeys are verified (#12155) (#12168)
Backport #12155

When attempting to verify subkeys the email address verification step
requires checking the emails however, these emails are not stored on
subkeys but instead on the primary key.

This PR will obtain the primaryKey and check against these emails too.

Fix #12128

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

Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-07-06 20:13:18 -04:00
Lauris BH
3daedb3877 Use hash of repo path, ref and entrypath as cache key (#12151) (#12161) 2020-07-06 16:51:45 +08:00
zeripath
2bf987229a Multiple small admin dashboard fixes (#12153) (#12156)
* Prevent (EXTRA string) comments in Task headers
* Redirect tasks started from monitor page back to monitor
* Fix #12107 - redirects from process cancel should use AppSubUrl
* When wrapping queues set the name correctly

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-07-05 21:19:53 +01:00
zeripath
f984a7e6c6 Remove spurious logging (#12139) (#12148)
Backport #12139

Unfortunately #10745 merged a spurious logging message. This PR removes this.

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

Co-authored-by: Lauris BH <lauris@nix.lv>

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-07-05 12:09:13 -04:00
Andreas Shimokawa
c96da610c2 templates/repo/empty.tmpl : fix repo setup instructions (#12147)
Co-authored-by: codeberg <codeberg@codeberg.org>
Co-authored-by: zeripath <art27@cantab.net>
2020-07-05 15:56:10 +03:00
zeripath
e46dbec294 Move EventSource to SharedWorker (#12095) (#12130)
* Move EventSource to SharedWorker (#12095)

Backport #12095

Move EventSource to use a SharedWorker. This prevents issues with HTTP/1.1
open browser connections from preventing gitea from opening multiple tabs.

Also allow setting EVENT_SOURCE_UPDATE_TIME to disable EventSource updating

Fix #11978

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

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

* Bugfix for shared event source

For some reason our eslint configuration is not working correctly
and a bug has become apparent when trying to backport this to 1.12.

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

* Re-fix #12095 again

Unfortunately some of the suggested changes to #12095 introduced
bugs which due to caching behaviour of sharedworkers were not caught
on simple tests.

These are as follows:

* Changing from simple for loop to use includes here:

```js
  register(port) {
    if (!this.clients.includes(port)) return;

    this.clients.push(port);

    port.postMessage({
      type: 'status',
      message: `registered to ${this.url}`,
    });
  }
```

The additional `!` prevents any clients from being added and should
read:

```js
    if (this.clients.includes(port)) return;
```

* Dropping the use of jQuery `$(...)` selection and using DOM
`querySelector` here:

```js
async function receiveUpdateCount(event) {
  try {
    const data = JSON.parse(event.data);

    const notificationCount = document.querySelector('.notification_count');
    if (data.Count > 0) {
      notificationCount.classList.remove('hidden');
    } else {
      notificationCount.classList.add('hidden');
    }

    notificationCount.text() = `${data.Count}`;
    await updateNotificationTable();
  } catch (error) {
    console.error(error, event);
  }
}
```

Requires that `notificationCount.text()` be changed to use `textContent`
instead.

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

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-07-05 01:08:03 +03:00
赵智超
8f64017058 Fix ui bug in wiki commit page (#12089) (#12125)
* Fix ui bug in wiki commit page

TODO: Maybe we should allow wiki to have its own ``.editorconfig`` file.

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

* fix a small nit

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
2020-07-03 00:49:55 -04:00
zeripath
d737eaa63a Set the base url when migrating from Gitlab using access token or username without password (#11852) (#12104)
Backport #11852

When migrating from gitlab, set the baseUrl in NewGitlabDownloader when using an access token or username without password

Fix #11851

Co-authored-by: Gernot Eger <gernot.eger@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-07-02 20:26:47 +08:00
zeripath
058ee52333 Fix gitgraph branch continues after merge (#12044) (#12105)
Backport #12044

* Fix gitgraph branch continues after merge

After fixing the initial problem in #11981 another
problem has come to light...

Fix #11981 (part 2)

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

* Update web_src/js/vendor/gitgraph.js

* Apply suggestions from code review

Co-authored-by: silverwind <me@silverwind.io>
2020-07-02 11:46:06 +01:00
Lauris BH
47b1fc5149 Fix merge section in dark theme (#12086) (#12109)
Backport #12086

* Fix merge section in dark theme

* Fix lint
2020-07-02 10:36:45 +01:00
zeripath
20c2bdf86b Ensure BlameReaders close at end of request (#12102) (#12103)
Backport #12102

this was thought to be due to timeouts, however on closer look this
appears to be due to the Close() function of the BlameReader hanging
with a blocked stdout pipe.

This PR fixes this Close function to:

* Cancel the context of the cmd
* Close the StdoutReader - ensuring that the output pipe is closed

Further it makes the context of the `git blame` command a child of the
request context - ensuring that even if Close() is not called, on
cancellation of the Request the blame is command will also be cancelled.

Fixes #11716
Closes #11727

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-07-01 18:43:25 +03:00
Stefan Bethke
df13fc8818 Disable go module when downloading global binaries (#12030) (#12084)
Prevent `go get` from touching `go.mod` and `go.sum` when executing
global binaries during the build process. Once
https://github.com/golang/go/issues/30515 is fixed, we should is
whatever solution is provided there.

Fixes: https://github.com/go-gitea/gitea/pull/12010

Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-06-28 21:14:22 +03:00
Cornel
445992d929 Fix comments webhook panic backport (#12058)
* Handle HookIssueReviewed action in webhook

* Fix webhook comment handling type cast panic
2020-06-25 21:19:11 -04:00
silverwind
d059156c3a Disable dropzone's timeout (#12024) (#12032)
Dropzone 4.4 introduced a 30s XHR timeout that will kill any upload
still in progress. This disable that timeout again.

Ref: https://www.dropzonejs.com/#config-timeout
Ref: https://github.com/go-gitea/gitea/pull/10645
Ref: https://xhr.spec.whatwg.org/#the-timeout-attribute
Fixes: https://github.com/go-gitea/gitea/issues/12022
Fixes: https://github.com/go-gitea/gitea/issues/11906

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-06-24 10:24:27 -04:00
213 changed files with 10405 additions and 3106 deletions

View File

@@ -25,7 +25,7 @@ globals:
Tribute: false
overrides:
- files: ["web_src/**/*.worker.js", "web_src/js/serviceworker.js"]
- files: ["web_src/**/*worker.js"]
env:
worker: true
rules:

View File

@@ -4,6 +4,64 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.12.4](https://github.com/go-gitea/gitea/releases/tag/v1.12.4) - 2020-09-02
* SECURITY
* Escape provider name in oauth2 provider redirect (#12648) (#12650)
* Escape Email on password reset page (#12610) (#12612)
* When reading expired sessions - expire them (#12686) (#12690)
* ENHANCEMENTS
* StaticRootPath configurable at compile time (#12371) (#12652)
* BUGFIXES
* Fix to show an issue that is related to a deleted issue (#12651) (#12692)
* Expire time acknowledged for cache (#12605) (#12611)
* Fix diff path unquoting (#12554) (#12575)
* Improve HTML escaping helper (#12562)
* models: break out of loop (#12386) (#12561)
* Default empty merger list to those with write permissions (#12535) (#12560)
* Skip SSPI authentication attempts for /api/internal (#12556) (#12559)
* Prevent NPE on commenting on lines with invalidated comments (#12549) (#12550)
* Remove hardcoded ES indexername (#12521) (#12526)
* Fix bug preventing transfer to private organization (#12497) (#12501)
* Keys should not verify revoked email addresses (#12486) (#12495)
* Do not add prefix on http/https submodule links (#12477) (#12479)
* Fix ignored login on compare (#12476) (#12478)
* Fix incorrect error logging in Stats indexer and OAuth2 (#12387) (#12422)
* Upgrade google/go-github to v32.1.0 (#12361) (#12390)
* Render emoji's of Commit message on feed-page (#12373)
* Fix handling of diff on unrelated branches when Git 2.28 used (#12370)
## [1.12.3](https://github.com/go-gitea/gitea/releases/tag/v1.12.3) - 2020-07-28
* BUGFIXES
* Don't change creation date when updating Release (#12343) (#12351)
* Show 404 page when release not found (#12328) (#12332)
* Fix emoji detection in certain cases (#12320) (#12327)
* Reduce emoji size (#12317) (#12327)
* Fix double-indirection bug in logging IDs (#12294) (#12308)
* Link to pull list page on sidebar when view pr (#12256) (#12263)
* Extend Notifications API and return pinned notifications by default (#12164) (#12232)
## [1.12.2](https://github.com/go-gitea/gitea/releases/tag/v1.12.2) - 2020-07-11
* BUGFIXES
* When deleting repository decrese user repository count in cache (#11954) (#12188)
* Return full commit message instead of summary in commits API (#12186) (#12187)
* Properly set HEAD when a repo is created with a default branch that is not named 'master' (#12135) (#12182)
* Ensure GPG Subkeys are verified (#12155) (#12168)
* Fix failing to cache last commit with key being to long (#12151) (#12161)
* Multiple small admin dashboard fixes (#12153) (#12156)
* Remove spurious logging of " Delete all repository archives" at startup (#12139) (#12148)
* Fix repository setup instructions when default branch is not named 'master' (#12122) (#12147)
* Move EventSource to SharedWorker (#12095) (#12130)
* Fix ui bug in wiki commit page (#12089) (#12125)
* Fix gitgraph branch continues after merge (#12044) (#12105)
* Set the base url when migrating from Gitlab using access token or username without password (#11852) (#12104)
* Ensure BlameReaders close at end of request (#12102) (#12103)
* Fix panic when adding review comment (#12058)
* ENHANCEMENTS
* Disable dropzone's timeout for file uploads (#12024) (#12032)
## [1.12.1](https://github.com/go-gitea/gitea/releases/tag/v1.12.1) - 2020-06-21
* BUGFIXES

View File

@@ -40,8 +40,10 @@ endif
ifeq ($(OS), Windows_NT)
GOFLAGS := -v -buildmode=exe
EXECUTABLE ?= gitea.exe
else
GOFLAGS := -v
EXECUTABLE ?= gitea
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
@@ -54,7 +56,6 @@ endif
GOFMT ?= gofmt -s
GOFLAGS := -v
EXTRA_GOFLAGS ?=
MAKE_VERSION := $(shell $(MAKE) -v | head -n 1)
@@ -253,7 +254,7 @@ swagger-validate:
.PHONY: errcheck
errcheck:
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/kisielk/errcheck; \
GO111MODULE=off $(GO) get -u github.com/kisielk/errcheck; \
fi
errcheck $(GO_PACKAGES)
@@ -264,14 +265,14 @@ revive:
.PHONY: misspell-check
misspell-check:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
misspell -error -i unknwon,destory $(GO_SOURCES_OWN)
.PHONY: misspell
misspell:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
GO111MODULE=off $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
misspell -w -i unknwon $(GO_SOURCES_OWN)
@@ -529,9 +530,9 @@ $(DIST_DIRS):
.PHONY: release-windows
release-windows: | $(DIST_DIRS)
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u src.techknowlogick.com/xgo; \
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries
endif
@@ -539,7 +540,7 @@ endif
.PHONY: release-linux
release-linux: | $(DIST_DIRS)
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u src.techknowlogick.com/xgo; \
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
@@ -549,7 +550,7 @@ endif
.PHONY: release-darwin
release-darwin: | $(DIST_DIRS)
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u src.techknowlogick.com/xgo; \
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
ifeq ($(CI),drone)

View File

@@ -120,6 +120,12 @@ var checklist = []check{
isDefault: false,
f: runDoctorPRMergeBase,
},
{
title: "Recalculate Stars number for all user",
name: "recalculate_stars_number",
isDefault: false,
f: runDoctorUserStarNum,
},
// more checks please append here
}
@@ -494,6 +500,10 @@ func runDoctorPRMergeBase(ctx *cli.Context) ([]string, error) {
return results, err
}
func runDoctorUserStarNum(ctx *cli.Context) ([]string, error) {
return nil, models.DoctorUserStarNum()
}
func runDoctorScriptType(ctx *cli.Context) ([]string, error) {
path, err := exec.LookPath(setting.ScriptType)
if err != nil {

View File

@@ -211,7 +211,7 @@ MIN_TIMEOUT = 10s
MAX_TIMEOUT = 60s
TIMEOUT_STEP = 10s
; This setting determines how often the db is queried to get the latest notification counts.
; If the browser client supports EventSource, it will be used in preference to polling notification.
; If the browser client supports EventSource and SharedWorker, a SharedWorker will be used in preference to polling notification. Set to -1 to disable the EventSource
EVENT_SOURCE_UPDATE_TIME = 10s
[markdown]

View File

@@ -148,8 +148,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `MIN_TIMEOUT`: **10s**: These options control how often notification endpoint is polled to update the notification count. On page load the notification count will be checked after `MIN_TIMEOUT`. The timeout will increase to `MAX_TIMEOUT` by `TIMEOUT_STEP` if the notification count is unchanged. Set MIN_TIMEOUT to 0 to turn off.
- `MAX_TIMEOUT`: **60s**.
- `TIMEOUT_STEP`: **10s**.
- `EVENT_SOURCE_UPDATE_TIME`: **10s**: This setting determines how often the database is queried to update notification counts. If the browser client supports `EventSource`, it will be used in preference to polling notification endpoint.
- `EVENT_SOURCE_UPDATE_TIME`: **10s**: This setting determines how often the database is queried to update notification counts. If the browser client supports `EventSource` and `SharedWorker`, a `SharedWorker` will be used in preference to polling notification endpoint. Set to **-1** to disable the `EventSource`.
## Markdown (`markdown`)

View File

@@ -141,7 +141,7 @@ Gitea will search for a number of things from the `CustomPath`. By default this
the `custom/` directory in the current working directory when running Gitea. It will also
look for its configuration file `CustomConf` in `$CustomPath/conf/app.ini`, and will use the
current working directory as the relative base path `AppWorkPath` for a number configurable
values.
values. Finally the static files will be served from `StaticRootPath` which defaults to the `AppWorkPath`.
These values, although useful when developing, may conflict with downstream users preferences.
@@ -152,6 +152,7 @@ using the `LDFLAGS` environment variable for `make`. The appropriate settings ar
* To set the `CustomPath` use `LDFLAGS="-X \"code.gitea.io/gitea/modules/setting.CustomPath=custom-path\""`
* For `CustomConf` you should use `-X \"code.gitea.io/gitea/modules/setting.CustomConf=conf.ini\"`
* For `AppWorkPath` you should use `-X \"code.gitea.io/gitea/modules/setting.AppWorkPath=working-path\"`
* For `StaticRootPath` you should use `-X \"code.gitea.io/gitea/modules/setting.StaticRootPath=static-root-path\"`
Add as many of the strings with their preceding `-X` to the `LDFLAGS` variable and run `make build`
with the appropriate `TAGS` as above.

4
go.mod
View File

@@ -15,7 +15,7 @@ require (
gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a
gitea.com/macaron/macaron v1.4.0
gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d
gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7
github.com/BurntSushi/toml v0.3.1
github.com/PuerkitoBio/goquery v1.5.0
@@ -48,7 +48,7 @@ require (
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
github.com/golang/protobuf v1.4.1 // indirect
github.com/google/go-github/v24 v24.0.1
github.com/google/go-github/v32 v32.1.0
github.com/google/uuid v1.1.1
github.com/gorilla/context v1.1.1
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect

12
go.sum
View File

@@ -37,8 +37,8 @@ gitea.com/macaron/macaron v1.4.0 h1:FY1QDGqyuUzs21K6ChkbYbRUfwL7v2aUrhNEJ0IgsAw=
gitea.com/macaron/macaron v1.4.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705 h1:mvkQGAlON1Z6Y8pqa/+FpYIskk54mazuECUfZK5oTg0=
gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA=
gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d h1:XLww3CvnFZkXVwauN67fniDaIpIqsE+9KVcxlZKlvLU=
gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d/go.mod h1:FanKy3WjWb5iw/iZBPk4ggoQT9FcM6bkBPvmDmsH6tY=
gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e h1:BHoJ/xWNt6FrVsL54JennM9HPIQlnbmRvmaC5DO65pU=
gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e/go.mod h1:FanKy3WjWb5iw/iZBPk4ggoQT9FcM6bkBPvmDmsH6tY=
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 h1:N9QFoeNsUXLhl14mefLzGluqV7w2mGU3u+iZU+jCeWk=
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7/go.mod h1:kgsbFPPS4P+acDYDOPDa3N4IWWOuDJt5/INKRUz7aks=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
@@ -315,10 +315,8 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v24 v24.0.1 h1:KCt1LjMJEey1qvPXxa9SjaWxwTsCWSq6p2Ju57UR4Q4=
github.com/google/go-github/v24 v24.0.1/go.mod h1:CRqaW1Uns1TCkP0wqTpxYyRxRjxwvKU/XSS44u6X74M=
github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -664,7 +662,6 @@ go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -737,7 +734,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@@ -55,7 +55,7 @@ func TestAPINotification(t *testing.T) {
assert.EqualValues(t, false, apiNL[2].Pinned)
// -- GET /repos/{owner}/{repo}/notifications --
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?token=%s", user2.Name, repo1.Name, token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&token=%s", user2.Name, repo1.Name, token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
@@ -92,7 +92,7 @@ func TestAPINotification(t *testing.T) {
assert.True(t, new.New > 0)
// -- mark notifications as read --
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 2)
@@ -101,7 +101,7 @@ func TestAPINotification(t *testing.T) {
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
resp = session.MakeRequest(t, req, http.StatusResetContent)
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1)

View File

@@ -59,7 +59,7 @@ func TestEventSourceManagerRun(t *testing.T) {
var apiNL []api.NotificationThread
// -- mark notifications as read --
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
resp := session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
@@ -69,7 +69,7 @@ func TestEventSourceManagerRun(t *testing.T) {
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
resp = session.MakeRequest(t, req, http.StatusResetContent)
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s&status-types=unread", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1)

View File

@@ -98,9 +98,10 @@ func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool {
}
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
func (protectBranch *ProtectedBranch) IsUserMergeWhitelisted(userID int64) bool {
func (protectBranch *ProtectedBranch) IsUserMergeWhitelisted(userID int64, permissionInRepo Permission) bool {
if !protectBranch.EnableMergeWhitelist {
return true
// Then we need to fall back on whether the user has write permission
return permissionInRepo.CanWrite(UnitTypeCode)
}
if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) {

View File

@@ -286,6 +286,9 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
emails := make([]*EmailAddress, 0, len(e.Identities))
for _, ident := range e.Identities {
if ident.Revocation != nil {
continue
}
email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
for _, e := range userEmails {
if e.Email == email {
@@ -509,6 +512,18 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *Use
return nil
}
for _, key := range keys {
var primaryKeys []*GPGKey
if key.PrimaryKeyID != "" {
primaryKeys, err = GetGPGKeysByKeyID(key.PrimaryKeyID)
if err != nil {
log.Error("GetGPGKeysByKeyID: %v", err)
return &CommitVerification{
CommittingUser: committer,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
}
activated := false
if len(email) != 0 {
for _, e := range key.Emails {
@@ -518,6 +533,20 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *Use
break
}
}
if !activated {
for _, pkey := range primaryKeys {
for _, e := range pkey.Emails {
if e.IsActivated && strings.EqualFold(e.Email, email) {
activated = true
email = e.Email
break
}
}
if activated {
break
}
}
}
} else {
for _, e := range key.Emails {
if e.IsActivated {
@@ -526,7 +555,22 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *Use
break
}
}
if !activated {
for _, pkey := range primaryKeys {
for _, e := range pkey.Emails {
if e.IsActivated {
activated = true
email = e.Email
break
}
}
if activated {
break
}
}
}
}
if !activated {
continue
}
@@ -614,7 +658,6 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
}
defaultReason := NoKeyFound
// First check if the sig has a keyID and if so just look at that

View File

@@ -1953,6 +1953,11 @@ func deleteIssuesByRepoID(sess Engine, repoID int64) (attachmentPaths []string,
return
}
if _, err = sess.In("dependent_issue_id", deleteCond).
Delete(&Comment{}); err != nil {
return
}
var attachments []*Attachment
if err = sess.In("issue_id", deleteCond).
Find(&attachments); err != nil {

View File

@@ -72,7 +72,7 @@ type FindNotificationOptions struct {
UserID int64
RepoID int64
IssueID int64
Status NotificationStatus
Status []NotificationStatus
UpdatedAfterUnix int64
UpdatedBeforeUnix int64
}
@@ -89,8 +89,8 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond {
if opts.IssueID != 0 {
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
}
if opts.Status != 0 {
cond = cond.And(builder.Eq{"notification.status": opts.Status})
if len(opts.Status) > 0 {
cond = cond.And(builder.In("notification.status", opts.Status))
}
if opts.UpdatedAfterUnix != 0 {
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})

View File

@@ -435,7 +435,7 @@ func hasOrgVisible(e Engine, org *User, user *User) bool {
return true
}
if (org.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !org.isUserPartOfOrg(e, user.ID) {
if (org.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !org.hasMemberWithUserID(e, user.ID) {
return false
}
return true

View File

@@ -27,12 +27,13 @@ func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit st
var gitRepo *git.Repository
var err error
Loop:
for _, rule := range rules {
switch rule {
case never:
return false, "", &ErrWontSign{never}
case always:
break
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {

View File

@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"html/template"
"unicode/utf8"
// Needed for jpeg support
_ "image/jpeg"
@@ -1384,11 +1385,11 @@ func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) {
func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) {
repo.LowerName = strings.ToLower(repo.Name)
if len(repo.Description) > 255 {
repo.Description = repo.Description[:255]
if utf8.RuneCountInString(repo.Description) > 255 {
repo.Description = string([]rune(repo.Description)[:255])
}
if len(repo.Website) > 255 {
repo.Website = repo.Website[:255]
if utf8.RuneCountInString(repo.Website) > 255 {
repo.Website = string([]rune(repo.Website)[:255])
}
if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
@@ -1566,6 +1567,10 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
releaseAttachments = append(releaseAttachments, attachments[i].LocalPath())
}
if _, err = sess.Exec("UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil {
return err
}
if err = deleteBeans(sess,
&Access{RepoID: repo.ID},
&Action{RepoID: repo.ID},
@@ -2331,3 +2336,38 @@ func updateRepositoryCols(e Engine, repo *Repository, cols ...string) error {
func UpdateRepositoryCols(repo *Repository, cols ...string) error {
return updateRepositoryCols(x, repo, cols...)
}
// DoctorUserStarNum recalculate Stars number for all user
func DoctorUserStarNum() (err error) {
const batchSize = 100
sess := x.NewSession()
defer sess.Close()
for start := 0; ; start += batchSize {
users := make([]User, 0, batchSize)
if err = sess.Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil {
return
}
if len(users) == 0 {
break
}
if err = sess.Begin(); err != nil {
return
}
for _, user := range users {
if _, err = sess.Exec("UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil {
return
}
}
if err = sess.Commit(); err != nil {
return
}
}
log.Debug("recalculate Stars number for all user finished")
return
}

View File

@@ -187,3 +187,9 @@ func TestDeleteAvatar(t *testing.T) {
assert.Equal(t, "", repo.Avatar)
}
func TestDoctorUserStarNum(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.NoError(t, DoctorUserStarNum())
}

View File

@@ -609,12 +609,12 @@ func (u *User) IsUserOrgOwner(orgID int64) bool {
return isOwner
}
// IsUserPartOfOrg returns true if user with userID is part of the u organisation.
func (u *User) IsUserPartOfOrg(userID int64) bool {
return u.isUserPartOfOrg(x, userID)
// HasMemberWithUserID returns true if user with userID is part of the u organisation.
func (u *User) HasMemberWithUserID(userID int64) bool {
return u.hasMemberWithUserID(x, userID)
}
func (u *User) isUserPartOfOrg(e Engine, userID int64) bool {
func (u *User) hasMemberWithUserID(e Engine, userID int64) bool {
isMember, err := isOrganizationMember(e, u.ID, userID)
if err != nil {
log.Error("IsOrganizationMember: %v", err)

View File

@@ -51,7 +51,8 @@ func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors)
// AdminDashboardForm form for admin dashboard operations
type AdminDashboardForm struct {
Op string `binding:"required"`
Op string `binding:"required"`
From string
}
// Validate validates form fields

View File

@@ -6,6 +6,7 @@ package oauth2
import (
"net/http"
"net/url"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -119,7 +120,7 @@ func RemoveProvider(providerName string) {
// used to create different types of goth providers
func createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL string, customURLMapping *CustomURLMapping) (goth.Provider, error) {
callbackURL := setting.AppURL + "user/oauth2/" + providerName + "/callback"
callbackURL := setting.AppURL + "user/oauth2/" + url.PathEscape(providerName) + "/callback"
var provider goth.Provider
var err error

View File

@@ -93,7 +93,7 @@ func (o *OAuth2) userIDFromToken(ctx *macaron.Context) int64 {
}
t, err := models.GetAccessTokenBySHA(tokenSHA)
if err != nil {
if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) {
if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
log.Error("GetAccessTokenBySHA: %v", err)
}
return 0
@@ -121,7 +121,7 @@ func (o *OAuth2) VerifyAuthData(ctx *macaron.Context, sess session.Store) *model
return nil
}
if !isAPIPath(ctx) && !isAttachmentDownload(ctx) {
if isInternalPath(ctx) || !isAPIPath(ctx) && !isAttachmentDownload(ctx) {
return nil
}

View File

@@ -100,6 +100,11 @@ func isAPIPath(ctx *macaron.Context) bool {
return strings.HasPrefix(ctx.Req.URL.Path, "/api/")
}
// isInternalPath returns true if the specified URL is an internal API path
func isInternalPath(ctx *macaron.Context) bool {
return strings.HasPrefix(ctx.Req.URL.Path, "/api/internal/")
}
// isAttachmentDownload check if request is a file download (GET) with URL to an attachment
func isAttachmentDownload(ctx *macaron.Context) bool {
return strings.HasPrefix(ctx.Req.URL.Path, "/attachments/") && ctx.Req.Method == "GET"

View File

@@ -148,6 +148,8 @@ func (s *SSPI) shouldAuthenticate(ctx *macaron.Context) (shouldAuth bool) {
} else if ctx.Req.FormValue("auth_with_sspi") == "1" {
shouldAuth = true
}
} else if isInternalPath(ctx) {
shouldAuth = false
} else if isAPIPath(ctx) || isAttachmentDownload(ctx) {
shouldAuth = true
}

View File

@@ -5,6 +5,7 @@
package cache
import (
"crypto/sha256"
"fmt"
"code.gitea.io/gitea/modules/git"
@@ -34,9 +35,14 @@ func NewLastCommitCache(repoPath string, gitRepo *git.Repository, ttl int64) *La
}
}
func (c LastCommitCache) getCacheKey(repoPath, ref, entryPath string) string {
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, ref, entryPath)))
return fmt.Sprintf("last_commit:%x", hashBytes)
}
// Get get the last commit information by commit id and entry path
func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
v := c.Cache.Get(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath))
v := c.Cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
if vs, ok := v.(string); ok {
log.Trace("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
if commit, ok := c.commitCache[vs]; ok {
@@ -60,5 +66,5 @@ func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
// Put put the last commit id with commit and entry path
func (c LastCommitCache) Put(ref, entryPath, commitID string) error {
log.Trace("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
return c.Cache.Put(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath), commitID, c.ttl)
return c.Cache.Put(c.getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl)
}

View File

@@ -67,8 +67,12 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models.
}
if user != nil {
permission, err := models.GetUserRepoPermission(repo, user)
if err != nil {
return nil, err
}
branch.UserCanPush = bp.CanUserPush(user.ID)
branch.UserCanMerge = bp.IsUserMergeWhitelisted(user.ID)
branch.UserCanMerge = bp.IsUserMergeWhitelisted(user.ID, permission)
}
return branch, nil

View File

@@ -130,6 +130,8 @@ func ReplaceAliases(s string) string {
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
func FindEmojiSubmatchIndex(s string) []int {
loadMap()
found := make(map[int]int)
keys := make([]int, 0)
//see if there are any emoji in string before looking for position of specific ones
//no performance difference when there is a match but 10x faster when there are not
@@ -137,11 +139,26 @@ func FindEmojiSubmatchIndex(s string) []int {
return nil
}
// get index of first emoji occurrence while also checking for longest combination
for j := range GemojiData {
i := strings.Index(s, GemojiData[j].Emoji)
if i != -1 {
return []int{i, i + len(GemojiData[j].Emoji)}
if _, ok := found[i]; !ok {
if len(keys) == 0 || i < keys[0] {
found[i] = j
keys = []int{i}
}
if i == 0 {
break
}
}
}
}
if len(keys) > 0 {
index := keys[0]
return []int{index, index + len(GemojiData[found[index]].Emoji)}
}
return nil
}

View File

@@ -17,6 +17,9 @@ import (
// Init starts this eventsource
func (m *Manager) Init() {
if setting.UI.Notification.EventSourceUpdateTime <= 0 {
return
}
go graceful.GetManager().RunWithShutdownContext(m.Run)
}

View File

@@ -79,7 +79,9 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
defer process.GetManager().Remove(r.pid)
defer r.cancel()
r.cancel()
_ = r.output.Close()
if err := r.cmd.Wait(); err != nil {
return fmt.Errorf("Wait: %v", err)
@@ -89,19 +91,19 @@ func (r *BlameReader) Close() error {
}
// CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) {
gitRepo, err := OpenRepository(repoPath)
if err != nil {
return nil, err
}
gitRepo.Close()
return createBlameReader(repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
return createBlameReader(ctx, repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file)
}
func createBlameReader(dir string, command ...string) (*BlameReader, error) {
// FIXME: graceful: This should have a timeout
ctx, cancel := context.WithCancel(DefaultContext)
func createBlameReader(ctx context.Context, dir string, command ...string) (*BlameReader, error) {
// Here we use the provided context - this should be tied to the request performing the blame so that it does not hang around.
ctx, cancel := context.WithCancel(ctx)
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Dir = dir
cmd.Stderr = os.Stderr

View File

@@ -5,6 +5,7 @@
package git
import (
"context"
"io/ioutil"
"testing"
@@ -93,8 +94,10 @@ func TestReadingBlameOutput(t *testing.T) {
if _, err = tempFile.WriteString(exampleBlame); err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
blameReader, err := createBlameReader("", "cat", tempFile.Name())
blameReader, err := createBlameReader(ctx, "", "cat", tempFile.Name())
if err != nil {
panic(err)
}

View File

@@ -271,11 +271,12 @@ func AllCommitsCount(repoPath string) (int64, error) {
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}
func commitsCount(repoPath, revision, relpath string) (int64, error) {
func commitsCount(repoPath string, revision, relpath []string) (int64, error) {
cmd := NewCommand("rev-list", "--count")
cmd.AddArguments(revision)
cmd.AddArguments(revision...)
if len(relpath) > 0 {
cmd.AddArguments("--", relpath)
cmd.AddArguments("--")
cmd.AddArguments(relpath...)
}
stdout, err := cmd.RunInDir(repoPath)
@@ -288,7 +289,7 @@ func commitsCount(repoPath, revision, relpath string) (int64, error) {
// CommitsCount returns number of total commits of until given revision.
func CommitsCount(repoPath, revision string) (int64, error) {
return commitsCount(repoPath, revision, "")
return commitsCount(repoPath, []string{revision}, []string{})
}
// CommitsCount returns number of total commits of until current revision.

View File

@@ -293,7 +293,7 @@ func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bo
// FileCommitsCount return the number of files at a revison
func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
return commitsCount(repo.Path, revision, file)
return commitsCount(repo.Path, []string{revision}, []string{file})
}
// CommitsByFileAndRange return the commits according revison file and the page
@@ -319,6 +319,11 @@ func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, pag
// FilesCountBetween return the number of files changed between two commits
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
stdout, err := NewCommand("diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path)
if err != nil && strings.Contains(err.Error(), "no merge base") {
// git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated.
// previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that...
stdout, err = NewCommand("diff", "--name-only", startCommitID, endCommitID).RunInDir(repo.Path)
}
if err != nil {
return 0, err
}
@@ -333,6 +338,11 @@ func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List
stdout, err = NewCommand("rev-list", last.ID.String()).RunInDirBytes(repo.Path)
} else {
stdout, err = NewCommand("rev-list", before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
stdout, err = NewCommand("rev-list", before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path)
}
}
if err != nil {
return nil, err
@@ -348,6 +358,11 @@ func (repo *Repository) CommitsBetweenLimit(last *Commit, before *Commit, limit,
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunInDirBytes(repo.Path)
} else {
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list --max-count n before last so let's try that...
stdout, err = NewCommand("rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path)
}
}
if err != nil {
return nil, err
@@ -373,7 +388,14 @@ func (repo *Repository) CommitsBetweenIDs(last, before string) (*list.List, erro
// CommitsCountBetween return numbers of commits between two commits
func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
return commitsCount(repo.Path, start+"..."+end, "")
count, err := commitsCount(repo.Path, []string{start + "..." + end}, []string{})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
return commitsCount(repo.Path, []string{start, end}, []string{})
}
return count, err
}
// commitsBefore the limit is depth, not total number of returned commits.

View File

@@ -6,6 +6,7 @@
package git
import (
"bytes"
"container/list"
"fmt"
"io"
@@ -66,7 +67,7 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string)
compareInfo := new(CompareInfo)
compareInfo.MergeBase, remoteBranch, err = repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
if err == nil {
// We have a common base
// We have a common base - therefore we know that ... should work
logs, err := NewCommand("log", compareInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
if err != nil {
return nil, err
@@ -85,6 +86,11 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string)
// Count number of changed files.
stdout, err := NewCommand("diff", "--name-only", remoteBranch+"..."+headBranch).RunInDir(repo.Path)
if err != nil && strings.Contains(err.Error(), "no merge base") {
// git >= 2.28 now returns an error if base and head have become unrelated.
// previously it would return the results of git diff --name-only base head so let's try that...
stdout, err = NewCommand("diff", "--name-only", remoteBranch, headBranch).RunInDir(repo.Path)
}
if err != nil {
return nil, err
}
@@ -108,12 +114,24 @@ func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
// GetPatch generates and returns format-patch data between given revisions.
func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
return NewCommand("format-patch", "--binary", "--stdout", base+"..."+head).
RunInDirPipeline(repo.Path, w, nil)
stderr := new(bytes.Buffer)
err := NewCommand("format-patch", "--binary", "--stdout", base+"..."+head).
RunInDirPipeline(repo.Path, w, stderr)
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand("format-patch", "--binary", "--stdout", base, head).
RunInDirPipeline(repo.Path, w, nil)
}
return err
}
// GetDiffFromMergeBase generates and return patch data from merge base to head
func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
return NewCommand("diff", "-p", "--binary", base+"..."+head).
RunInDirPipeline(repo.Path, w, nil)
stderr := new(bytes.Buffer)
err := NewCommand("diff", "-p", "--binary", base+"..."+head).
RunInDirPipeline(repo.Path, w, stderr)
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand("diff", "-p", "--binary", base, head).
RunInDirPipeline(repo.Path, w, nil)
}
return err
}

View File

@@ -97,13 +97,13 @@ func getRefURL(refURL, urlPrefix, repoFullName string) string {
for _, scheme := range supportedSchemes {
if ref.Scheme == scheme {
if urlPrefixHostname == refHostname {
return urlPrefix + path.Clean(path.Join("/", ref.Path))
} else if ref.Scheme == "http" || ref.Scheme == "https" {
if ref.Scheme == "http" || ref.Scheme == "https" {
if len(ref.User.Username()) > 0 {
return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path
}
return ref.Scheme + "://" + ref.Host + ref.Path
} else if urlPrefixHostname == refHostname {
return urlPrefix + path.Clean(path.Join("/", ref.Path))
} else {
return "http://" + refHostname + ref.Path
}

View File

@@ -28,6 +28,7 @@ func TestGetRefURL(t *testing.T) {
{"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "https://try.gitea.io/go-gitea/gitea"},
{"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "https://127.0.0.1:3000/go-gitea/gitea"},
{"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "https://gitea.com:3000/user1/repo1"},
{"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"},
{"https://username:password@github.com/username/repository.git", "/", "username/repository2", "https://username:password@github.com/username/repository"},
{"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", ""},
{"git@localhost:user/repo", "https://localhost/", "user2/repo1", "https://localhost/user/repo"},

View File

@@ -171,7 +171,7 @@ func InitIssueIndexer(syncReindex bool) {
log.Debug("Created Bleve Indexer")
case "elasticsearch":
graceful.GetManager().RunWithShutdownFns(func(_, atTerminate func(context.Context, func())) {
issueIndexer, err := NewElasticSearchIndexer(setting.Indexer.IssueConnStr, "gitea_issues")
issueIndexer, err := NewElasticSearchIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueIndexerName)
if err != nil {
log.Fatal("Unable to initialize Elastic Search Issue Indexer at connection: %s Error: %v", setting.Indexer.IssueConnStr, err)
}

View File

@@ -21,7 +21,7 @@ func handle(data ...queue.Data) {
for _, datum := range data {
opts := datum.(int64)
if err := indexer.Index(opts); err != nil {
log.Error("stats queue idexer.Index(%d) failed: %v", opts, err)
log.Error("stats queue indexer.Index(%d) failed: %v", opts, err)
}
}
}
@@ -39,5 +39,11 @@ func initStatsQueue() error {
// UpdateRepoIndexer update a repository's entries in the indexer
func UpdateRepoIndexer(repo *models.Repository) error {
return statsQueue.Push(repo.ID)
if err := statsQueue.Push(repo.ID); err != nil {
if err != queue.ErrAlreadyInQueue {
return err
}
log.Debug("Repo ID: %d already queued", repo.ID)
}
return nil
}

View File

@@ -355,7 +355,7 @@ func NewColoredValueBytes(value interface{}, colorBytes *[]byte) *ColoredValue {
// The Value will be colored with FgCyan
// If a ColoredValue is provided it is not changed
func NewColoredIDValue(value interface{}) *ColoredValue {
return NewColoredValueBytes(&value, &fgCyanBytes)
return NewColoredValueBytes(value, &fgCyanBytes)
}
// Format will format the provided value and protect against ANSI color spoofing within the value

View File

@@ -266,6 +266,10 @@ func TestRender_emoji(t *testing.T) {
test(
"Some text with 😄😄 2 emoji next to each other",
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
test(
"😎🤪🔐🤑❓",
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="question mark">❓</span></p>`)
// should match nothing
test(
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",

View File

@@ -8,7 +8,7 @@ package migrations
import (
"errors"
"github.com/google/go-github/v24/github"
"github.com/google/go-github/v32/github"
)
var (

View File

@@ -18,7 +18,7 @@ import (
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"github.com/google/go-github/v24/github"
"github.com/google/go-github/v32/github"
"golang.org/x/oauth2"
)
@@ -364,7 +364,7 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
}
var labels = make([]*base.Label, 0, len(issue.Labels))
for _, l := range issue.Labels {
labels = append(labels, convertGithubLabel(&l))
labels = append(labels, convertGithubLabel(l))
}
var email string
@@ -425,8 +425,8 @@ func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, er
asc = "asc"
)
opt := &github.IssueListCommentsOptions{
Sort: created,
Direction: asc,
Sort: &created,
Direction: &asc,
ListOptions: github.ListOptions{
PerPage: 100,
},

View File

@@ -90,7 +90,7 @@ func NewGitlabDownloader(baseURL, repoPath, username, password string) *GitlabDo
var err error
if username != "" {
if password == "" {
gitlabClient, err = gitlab.NewClient(username)
gitlabClient, err = gitlab.NewClient(username, gitlab.WithBaseURL(baseURL))
} else {
gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL))
}

View File

@@ -75,6 +75,7 @@ func CreateQueue(name string, handle HandlerFunc, exemplar interface{}) Queue {
MaxAttempts: q.MaxAttempts,
Config: cfg,
QueueLength: q.Length,
Name: name,
}, exemplar)
}
if err != nil {

View File

@@ -214,6 +214,13 @@ func initRepository(ctx models.DBContext, repoPath string, u *models.User, repo
repo.DefaultBranch = "master"
if len(opts.DefaultBranch) > 0 {
repo.DefaultBranch = opts.DefaultBranch
gitRepo, err := git.OpenRepository(repo.RepoPath())
if err != nil {
return fmt.Errorf("openRepository: %v", err)
}
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %v", err)
}
}
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil {

View File

@@ -1,216 +0,0 @@
// Copyright 2013 Beego Authors
// Copyright 2014 The Macaron Authors
// Copyright 2019 The Gitea Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package session
import (
"container/list"
"fmt"
"sync"
"time"
"gitea.com/macaron/session"
)
// MemStore represents a in-memory session store implementation.
type MemStore struct {
sid string
lock sync.RWMutex
data map[interface{}]interface{}
lastAccess time.Time
}
// NewMemStore creates and returns a memory session store.
func NewMemStore(sid string) *MemStore {
return &MemStore{
sid: sid,
data: make(map[interface{}]interface{}),
lastAccess: time.Now(),
}
}
// Set sets value to given key in session.
func (s *MemStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
s.data[key] = val
return nil
}
// Get gets value by given key in session.
func (s *MemStore) Get(key interface{}) interface{} {
s.lock.RLock()
defer s.lock.RUnlock()
return s.data[key]
}
// Delete deletes a key from session.
func (s *MemStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.data, key)
return nil
}
// ID returns current session ID.
func (s *MemStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (*MemStore) Release() error {
return nil
}
// Flush deletes all session data.
func (s *MemStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}
// MemProvider represents a in-memory session provider implementation.
type MemProvider struct {
lock sync.RWMutex
maxLifetime int64
data map[string]*list.Element
// A priority list whose lastAccess newer gets higher priority.
list *list.List
}
// Init initializes memory session provider.
func (p *MemProvider) Init(maxLifetime int64, _ string) error {
p.lock.Lock()
p.maxLifetime = maxLifetime
p.lock.Unlock()
return nil
}
// update expands time of session store by given ID.
func (p *MemProvider) update(sid string) error {
p.lock.Lock()
defer p.lock.Unlock()
if e, ok := p.data[sid]; ok {
e.Value.(*MemStore).lastAccess = time.Now()
p.list.MoveToFront(e)
return nil
}
return nil
}
// Read returns raw session store by session ID.
func (p *MemProvider) Read(sid string) (_ session.RawStore, err error) {
p.lock.RLock()
e, ok := p.data[sid]
p.lock.RUnlock()
if ok {
if err = p.update(sid); err != nil {
return nil, err
}
return e.Value.(*MemStore), nil
}
// Create a new session.
p.lock.Lock()
defer p.lock.Unlock()
s := NewMemStore(sid)
p.data[sid] = p.list.PushBack(s)
return s, nil
}
// Exist returns true if session with given ID exists.
func (p *MemProvider) Exist(sid string) bool {
p.lock.RLock()
defer p.lock.RUnlock()
_, ok := p.data[sid]
return ok
}
// Destroy deletes a session by session ID.
func (p *MemProvider) Destroy(sid string) error {
p.lock.Lock()
defer p.lock.Unlock()
e, ok := p.data[sid]
if !ok {
return nil
}
p.list.Remove(e)
delete(p.data, sid)
return nil
}
// Regenerate regenerates a session store from old session ID to new one.
func (p *MemProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
if p.Exist(sid) {
return nil, fmt.Errorf("new sid '%s' already exists", sid)
}
s, err := p.Read(oldsid)
if err != nil {
return nil, err
}
if err = p.Destroy(oldsid); err != nil {
return nil, err
}
s.(*MemStore).sid = sid
p.lock.Lock()
defer p.lock.Unlock()
p.data[sid] = p.list.PushBack(s)
return s, nil
}
// Count counts and returns number of sessions.
func (p *MemProvider) Count() int {
return p.list.Len()
}
// GC calls GC to clean expired sessions.
func (p *MemProvider) GC() {
p.lock.RLock()
for {
// No session in the list.
e := p.list.Back()
if e == nil {
break
}
if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
p.lock.RUnlock()
p.lock.Lock()
p.list.Remove(e)
delete(p.data, e.Value.(*MemStore).sid)
p.lock.Unlock()
p.lock.RLock()
} else {
break
}
}
p.lock.RUnlock()
}

View File

@@ -5,7 +5,6 @@
package session
import (
"container/list"
"encoding/json"
"fmt"
"sync"
@@ -37,7 +36,7 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
// This is only slightly more wrong than modules/setting/session.go:23
switch opts.Provider {
case "memory":
o.provider = &MemProvider{list: list.New(), data: make(map[string]*list.Element)}
o.provider = &session.MemProvider{}
case "file":
o.provider = &session.FileProvider{}
case "redis":

View File

@@ -23,7 +23,7 @@ type Cache struct {
var (
// CacheService the global cache
CacheService = struct {
Cache
Cache `ini:"cache"`
LastCommit struct {
Enabled bool

View File

@@ -666,7 +666,10 @@ func NewContext() {
PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80")
OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(AppWorkPath)
if len(StaticRootPath) == 0 {
StaticRootPath = AppWorkPath
}
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(StaticRootPath)
StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour)
AppDataPath = sec.Key("APP_DATA_PATH").MustString(path.Join(AppWorkPath, "data"))
EnableGzip = sec.Key("ENABLE_GZIP").MustBool()

View File

@@ -164,9 +164,16 @@ func NewFuncMap() []template.FuncMap {
mimeType := mime.TypeByExtension(filepath.Ext(filename))
return strings.HasPrefix(mimeType, "image/")
},
"TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string {
"TabSizeClass": func(ec interface{}, filename string) string {
var (
value *editorconfig.Editorconfig
ok bool
)
if ec != nil {
def, err := ec.GetDefinitionForFilename(filename)
if value, ok = ec.(*editorconfig.Editorconfig); !ok || value == nil {
return "tab-size-8"
}
def, err := value.GetDefinitionForFilename(filename)
if err != nil {
log.Error("tab size class: getting definition for filename: %v", err)
return "tab-size-8"
@@ -282,8 +289,8 @@ func NewFuncMap() []template.FuncMap {
return ""
}
},
"NotificationSettings": func() map[string]int {
return map[string]int{
"NotificationSettings": func() map[string]interface{} {
return map[string]interface{}{
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
"TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
"MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),

View File

@@ -264,7 +264,11 @@ func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getDingtalkIssuesPayload(p.(*api.IssuePayload))
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload))
pl, ok := p.(*api.IssueCommentPayload)
if ok {
return getDingtalkIssueCommentPayload(pl)
}
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPush:
return getDingtalkPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,

View File

@@ -408,7 +408,11 @@ func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string)
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord)
pl, ok := p.(*api.IssueCommentPayload)
if ok {
return getDiscordIssueCommentPayload(pl, discord)
}
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
case models.HookEventPush:
return getDiscordPushPayload(p.(*api.PushPayload), discord)
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,

View File

@@ -183,13 +183,17 @@ func GetFeishuPayload(p api.Payloader, event models.HookEventType, meta string)
return getFeishuForkPayload(p.(*api.ForkPayload))
case models.HookEventIssues:
return getFeishuIssuesPayload(p.(*api.IssuePayload))
case models.HookEventIssueComment:
return getFeishuIssueCommentPayload(p.(*api.IssueCommentPayload))
case models.HookEventIssueComment, models.HookEventPullRequestComment:
pl, ok := p.(*api.IssueCommentPayload)
if ok {
return getFeishuIssueCommentPayload(pl)
}
return getFeishuPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPush:
return getFeishuPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest:
return getFeishuPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected, models.HookEventPullRequestComment:
case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected:
return getFeishuPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case models.HookEventRepository:
return getFeishuRepositoryPayload(p.(*api.RepositoryPayload))

View File

@@ -119,6 +119,8 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink)
case api.HookIssueDemilestoned:
text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink)
case api.HookIssueReviewed:
text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink)
}
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))

View File

@@ -230,7 +230,11 @@ func GetMatrixPayload(p api.Payloader, event models.HookEventType, meta string)
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getMatrixIssuesPayload(p.(*api.IssuePayload), matrix)
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getMatrixIssueCommentPayload(p.(*api.IssueCommentPayload), matrix)
pl, ok := p.(*api.IssueCommentPayload)
if ok {
return getMatrixIssueCommentPayload(pl, matrix)
}
return getMatrixPullRequestPayload(p.(*api.PullRequestPayload), matrix)
case models.HookEventPush:
return getMatrixPushPayload(p.(*api.PushPayload), matrix)
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,

View File

@@ -558,7 +558,11 @@ func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string)
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getMSTeamsIssuesPayload(p.(*api.IssuePayload))
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getMSTeamsIssueCommentPayload(p.(*api.IssueCommentPayload))
pl, ok := p.(*api.IssueCommentPayload)
if ok {
return getMSTeamsIssueCommentPayload(pl)
}
return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPush:
return getMSTeamsPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,

View File

@@ -321,7 +321,11 @@ func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getSlackIssuesPayload(p.(*api.IssuePayload), slack)
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
pl, ok := p.(*api.IssueCommentPayload)
if ok {
return getSlackIssueCommentPayload(pl, slack)
}
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
case models.HookEventPush:
return getSlackPushPayload(p.(*api.PushPayload), slack)
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,

View File

@@ -206,7 +206,11 @@ func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string
case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone:
return getTelegramIssuesPayload(p.(*api.IssuePayload))
case models.HookEventIssueComment, models.HookEventPullRequestComment:
return getTelegramIssueCommentPayload(p.(*api.IssueCommentPayload))
pl, ok := p.(*api.IssueCommentPayload)
if ok {
return getTelegramIssueCommentPayload(pl)
}
return getTelegramPullRequestPayload(p.(*api.PullRequestPayload))
case models.HookEventPush:
return getTelegramPushPayload(p.(*api.PushPayload))
case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel,

View File

@@ -1843,12 +1843,12 @@ dashboard.operation_switch = Switch
dashboard.operation_run = Run
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
dashboard.task.started=Started Task: %s
dashboard.task.process=Task: %s
dashboard.task.cancelled=Task: %s cancelled: %[3]s
dashboard.task.error=Error in Task: %s: %[3]s
dashboard.task.finished=Task: %s started by %s has finished
dashboard.task.unknown=Unknown task: %s
dashboard.task.started=Started Task: %[1]s
dashboard.task.process=Task: %[1]s
dashboard.task.cancelled=Task: %[1]s cancelled: %[3]s
dashboard.task.error=Error in Task: %[1]s: %[3]s
dashboard.task.finished=Task: %[1]s started by %[2]s has finished
dashboard.task.unknown=Unknown task: %[1]s
dashboard.cron.started=Started Cron: %[1]s
dashboard.cron.process=Cron: %[1]s
dashboard.cron.cancelled=Cron: %s cancelled: %[3]s

5
package-lock.json generated
View File

@@ -4007,6 +4007,11 @@
"es6-symbol": "^3.1.1"
}
},
"escape-goat": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz",
"integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",

View File

@@ -18,6 +18,7 @@
"cssnano": "4.1.10",
"domino": "2.1.5",
"dropzone": "5.7.0",
"escape-goat": "3.0.0",
"fast-glob": "3.2.2",
"file-loader": "6.0.0",
"fomantic-ui": "2.8.4",

View File

@@ -153,8 +153,11 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) {
ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
}
}
ctx.Redirect(setting.AppSubURL + "/admin")
if form.From == "monitor" {
ctx.Redirect(setting.AppSubURL + "/admin/monitor")
} else {
ctx.Redirect(setting.AppSubURL + "/admin")
}
}
// SendTestMail send test mail to confirm mail service is OK
@@ -331,7 +334,7 @@ func MonitorCancel(ctx *context.Context) {
pid := ctx.ParamsInt64("pid")
process.GetManager().Cancel(pid)
ctx.JSON(200, map[string]interface{}{
"redirect": ctx.Repo.RepoLink + "/admin/monitor",
"redirect": setting.AppSubURL + "/admin/monitor",
})
}

View File

@@ -11,9 +11,37 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/api/v1/utils"
)
func statusStringToNotificationStatus(status string) models.NotificationStatus {
switch strings.ToLower(strings.TrimSpace(status)) {
case "unread":
return models.NotificationStatusUnread
case "read":
return models.NotificationStatusRead
case "pinned":
return models.NotificationStatusPinned
default:
return 0
}
}
func statusStringsToNotificationStatuses(statuses []string, defaultStatuses []string) []models.NotificationStatus {
if len(statuses) == 0 {
statuses = defaultStatuses
}
results := make([]models.NotificationStatus, 0, len(statuses))
for _, status := range statuses {
notificationStatus := statusStringToNotificationStatus(status)
if notificationStatus > 0 {
results = append(results, notificationStatus)
}
}
return results
}
// ListRepoNotifications list users's notification threads on a specific repo
func ListRepoNotifications(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
@@ -39,6 +67,14 @@ func ListRepoNotifications(ctx *context.APIContext) {
// description: If true, show notifications marked as read. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned"
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: since
// in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
@@ -75,9 +111,10 @@ func ListRepoNotifications(ctx *context.APIContext) {
UpdatedBeforeUnix: before,
UpdatedAfterUnix: since,
}
qAll := strings.Trim(ctx.Query("all"), " ")
if qAll != "true" {
opts.Status = models.NotificationStatusUnread
if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
}
nl, err := models.GetNotifications(opts)
if err != nil {
@@ -97,7 +134,7 @@ func ListRepoNotifications(ctx *context.APIContext) {
func ReadRepoNotifications(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
// ---
// summary: Mark notification threads as read on a specific repo
// summary: Mark notification threads as read, pinned or unread on a specific repo
// consumes:
// - application/json
// produces:
@@ -113,6 +150,24 @@ func ReadRepoNotifications(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: all
// in: query
// description: If true, mark all notifications on this repo. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: to-status
// in: query
// description: Status to mark notifications as. Defaults to read.
// type: string
// required: false
// - name: last_read_at
// in: query
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
@@ -135,11 +190,17 @@ func ReadRepoNotifications(ctx *context.APIContext) {
lastRead = tmpLastRead.Unix()
}
}
opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
UpdatedBeforeUnix: lastRead,
Status: models.NotificationStatusUnread,
}
if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
log.Error("%v", opts.Status)
}
nl, err := models.GetNotifications(opts)
if err != nil {
@@ -147,8 +208,13 @@ func ReadRepoNotifications(ctx *context.APIContext) {
return
}
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
if targetStatus == 0 {
targetStatus = models.NotificationStatusRead
}
for _, n := range nl {
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
if err != nil {
ctx.InternalServerError(err)
return

View File

@@ -62,6 +62,12 @@ func ReadThread(ctx *context.APIContext) {
// description: id of notification thread
// type: string
// required: true
// - name: to-status
// in: query
// description: Status to mark notifications as
// type: string
// default: read
// required: false
// responses:
// "205":
// "$ref": "#/responses/empty"
@@ -75,7 +81,12 @@ func ReadThread(ctx *context.APIContext) {
return
}
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
if targetStatus == 0 {
targetStatus = models.NotificationStatusRead
}
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
if err != nil {
ctx.InternalServerError(err)
return

View File

@@ -29,6 +29,14 @@ func ListNotifications(ctx *context.APIContext) {
// description: If true, show notifications marked as read. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned."
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: since
// in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
@@ -64,9 +72,9 @@ func ListNotifications(ctx *context.APIContext) {
UpdatedBeforeUnix: before,
UpdatedAfterUnix: since,
}
qAll := strings.Trim(ctx.Query("all"), " ")
if qAll != "true" {
opts.Status = models.NotificationStatusUnread
if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
}
nl, err := models.GetNotifications(opts)
if err != nil {
@@ -82,11 +90,11 @@ func ListNotifications(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, nl.APIFormat())
}
// ReadNotifications mark notification threads as read
// ReadNotifications mark notification threads as read, unread, or pinned
func ReadNotifications(ctx *context.APIContext) {
// swagger:operation PUT /notifications notification notifyReadList
// ---
// summary: Mark notification threads as read
// summary: Mark notification threads as read, pinned or unread
// consumes:
// - application/json
// produces:
@@ -98,6 +106,24 @@ func ReadNotifications(ctx *context.APIContext) {
// type: string
// format: date-time
// required: false
// - name: all
// in: query
// description: If true, mark all notifications on this repo. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: to-status
// in: query
// description: Status to mark notifications as, Defaults to read.
// type: string
// required: false
// responses:
// "205":
// "$ref": "#/responses/empty"
@@ -117,7 +143,10 @@ func ReadNotifications(ctx *context.APIContext) {
opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
UpdatedBeforeUnix: lastRead,
Status: models.NotificationStatusUnread,
}
if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
}
nl, err := models.GetNotifications(opts)
if err != nil {
@@ -125,8 +154,13 @@ func ReadNotifications(ctx *context.APIContext) {
return
}
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
if targetStatus == 0 {
targetStatus = models.NotificationStatusRead
}
for _, n := range nl {
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
if err != nil {
ctx.InternalServerError(err)
return

View File

@@ -296,7 +296,7 @@ func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Comm
},
Date: commit.Committer.When.Format(time.RFC3339),
},
Message: commit.Summary(),
Message: commit.Message(),
Tree: &api.CommitMeta{
URL: repo.APIURL() + "/git/trees/" + commit.ID.String(),
SHA: commit.ID.String(),

View File

@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
repo_service "code.gitea.io/gitea/services/repository"
)
@@ -53,13 +54,21 @@ func Transfer(ctx *context.APIContext, opts api.TransferRepoOption) {
newOwner, err := models.GetUserByName(opts.NewOwner)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(http.StatusNotFound, "GetUserByName", err)
ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
return
}
ctx.InternalServerError(err)
return
}
if newOwner.Type == models.UserTypeOrganization {
if !ctx.User.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !newOwner.HasMemberWithUserID(ctx.User.ID) {
// The user shouldn't know about this organization
ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
return
}
}
var teams []*models.Team
if opts.TeamIDs != nil {
if !newOwner.IsOrganization() {

View File

@@ -36,7 +36,6 @@ import (
"gitea.com/macaron/i18n"
"gitea.com/macaron/macaron"
unknwoni18n "github.com/unknwon/i18n"
)
func checkRunMode() {
@@ -124,8 +123,6 @@ func GlobalInit(ctx context.Context) {
// Setup i18n
InitLocales()
log.Info("%s", unknwoni18n.Tr("en-US", "admin.dashboard.delete_repo_archives"))
NewServices()
if setting.InstallLock {

View File

@@ -39,6 +39,7 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []
_ = stdoutWriter.Close()
}()
// This is safe as force pushes are already forbidden
err = git.NewCommand("rev-list", oldCommitID+"..."+newCommitID).
RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path,
stdoutWriter, nil, nil,
@@ -70,6 +71,7 @@ func checkFileProtection(oldCommitID, newCommitID string, patterns []glob.Glob,
_ = stdoutWriter.Close()
}()
// This use of ... is safe as force-pushes have already been ruled out.
err = git.NewCommand("diff", "--name-only", oldCommitID+"..."+newCommitID).
RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path,
stdoutWriter, nil, nil,

View File

@@ -141,7 +141,13 @@ func RefBlame(ctx *context.Context) {
ctx.Data["FileSize"] = blob.Size()
ctx.Data["FileName"] = blob.Name()
blameReader, err := git.CreateBlameReader(models.RepoPath(userName, repoName), commitID, fileName)
ctx.Data["NumLines"], err = blob.GetBlobLineCount()
if err != nil {
ctx.NotFound("GetBlobLineCount", err)
return
}
blameReader, err := git.CreateBlameReader(ctx.Req.Context(), models.RepoPath(userName, repoName), commitID, fileName)
if err != nil {
ctx.NotFound("CreateBlameReader", err)
return

View File

@@ -969,8 +969,10 @@ func ViewIssue(ctx *context.Context) {
}
} else if comment.Type == models.CommentTypeRemoveDependency || comment.Type == models.CommentTypeAddDependency {
if err = comment.LoadDepIssueDetails(); err != nil {
ctx.ServerError("LoadDepIssueDetails", err)
return
if !models.IsErrIssueNotExist(err) {
ctx.ServerError("LoadDepIssueDetails", err)
return
}
}
} else if comment.Type == models.CommentTypeCode || comment.Type == models.CommentTypeReview {
comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink,

View File

@@ -134,6 +134,10 @@ func SingleRelease(ctx *context.Context) {
release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag"))
if err != nil {
if models.IsErrReleaseNotExist(err) {
ctx.NotFound("GetRelease", err)
return
}
ctx.ServerError("GetReleasesByRepoID", err)
return
}

View File

@@ -381,7 +381,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
}
if newOwner.Type == models.UserTypeOrganization {
if !ctx.User.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !ctx.User.IsUserPartOfOrg(newOwner.ID) {
if !ctx.User.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !newOwner.HasMemberWithUserID(ctx.User.ID) {
// The user shouldn't know about this organization
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
return

View File

@@ -713,7 +713,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/:id", repo.MilestoneIssuesAndPulls)
}, reqRepoIssuesOrPullsReader, context.RepoRef())
m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
Get(repo.SetDiffViewStyle, repo.CompareDiff).
Get(ignSignIn, repo.SetDiffViewStyle, repo.CompareDiff).
Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
}, context.RepoAssignment(), context.UnitTypes())

View File

@@ -18,7 +18,6 @@ import (
"os"
"os/exec"
"sort"
"strconv"
"strings"
"code.gitea.io/gitea/models"
@@ -532,40 +531,28 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
break
}
var middle int
// Note: In case file name is surrounded by double quotes (it happens only in git-shell).
// e.g. diff --git "a/xxx" "b/xxx"
hasQuote := line[len(cmdDiffHead)] == '"'
if hasQuote {
middle = strings.Index(line, ` "b/`)
var a string
var b string
rd := strings.NewReader(line[len(cmdDiffHead):])
char, _ := rd.ReadByte()
_ = rd.UnreadByte()
if char == '"' {
fmt.Fscanf(rd, "%q ", &a)
} else {
middle = strings.Index(line, " b/")
fmt.Fscanf(rd, "%s ", &a)
}
beg := len(cmdDiffHead)
a := line[beg+2 : middle]
b := line[middle+3:]
if hasQuote {
// Keep the entire string in double quotes for now
a = line[beg:middle]
b = line[middle+1:]
var err error
a, err = strconv.Unquote(a)
if err != nil {
return nil, fmt.Errorf("Unquote: %v", err)
}
b, err = strconv.Unquote(b)
if err != nil {
return nil, fmt.Errorf("Unquote: %v", err)
}
// Now remove the /a /b
a = a[2:]
b = b[2:]
char, _ = rd.ReadByte()
_ = rd.UnreadByte()
if char == '"' {
fmt.Fscanf(rd, "%q", &b)
} else {
fmt.Fscanf(rd, "%s", &b)
}
a = a[2:]
b = b[2:]
curFile = &DiffFile{
Name: b,

View File

@@ -76,6 +76,23 @@ func TestParsePatch(t *testing.T) {
}
println(result)
var diff2a = `diff --git "a/A \\ B" b/A/B
--- "a/A \\ B"
+++ b/A/B
@@ -1,3 +1,6 @@
# gitea-github-migrator
+
+ Build Status
- Latest Release
Docker Pulls
+ cut off
+ cut off`
result, err = ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff2a))
if err != nil {
t.Errorf("ParsePatch failed: %s", err)
}
println(result)
var diff3 = `diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md

View File

@@ -544,7 +544,7 @@ func IsUserAllowedToMerge(pr *models.PullRequest, p models.Permission, user *mod
return false, err
}
if (p.CanWrite(models.UnitTypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && pr.ProtectedBranch.IsUserMergeWhitelisted(user.ID)) {
if (p.CanWrite(models.UnitTypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && pr.ProtectedBranch.IsUserMergeWhitelisted(user.ID, p)) {
return true, nil
}

View File

@@ -29,7 +29,7 @@ func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models
// - Comments that are part of a review
// - Comments that reply to an existing review
if !isReview {
if !isReview && replyReviewID != 0 {
// It's not part of a review; maybe a reply to a review comment or a single comment.
// Check if there are reviews for that line already; if there are, this is a reply
if existsReview, err = models.ReviewExists(issue, treePath, line); err != nil {

View File

@@ -46,6 +46,7 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
rel.Publisher, rel.Repo, git.TagPrefix+rel.TagName,
git.EmptySHA, commit.ID.String(), repository.NewPushCommits())
notification.NotifyCreateRef(rel.Publisher, rel.Repo, "tag", git.TagPrefix+rel.TagName)
rel.CreatedUnix = timeutil.TimeStampNow()
}
commit, err := gitRepo.GetTagCommit(rel.TagName)
if err != nil {
@@ -53,7 +54,6 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
}
rel.Sha1 = commit.ID.String()
rel.CreatedUnix = timeutil.TimeStampNow()
rel.NumCommits, err = commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %v", err)

View File

@@ -7,6 +7,7 @@ package release
import (
"path/filepath"
"testing"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
@@ -101,3 +102,153 @@ func TestRelease_Create(t *testing.T) {
IsTag: true,
}, nil))
}
func TestRelease_Update(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
repoPath := models.RepoPath(user.Name, repo.Name)
gitRepo, err := git.OpenRepository(repoPath)
assert.NoError(t, err)
defer gitRepo.Close()
// Test a changed release
assert.NoError(t, CreateRelease(gitRepo, &models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v1.1.1",
Target: "master",
Title: "v1.1.1 is released",
Note: "v1.1.1 is released",
IsDraft: false,
IsPrerelease: false,
IsTag: false,
}, nil))
release, err := models.GetRelease(repo.ID, "v1.1.1")
assert.NoError(t, err)
releaseCreatedUnix := release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Note = "Changed note"
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
release, err = models.GetReleaseByID(release.ID)
assert.NoError(t, err)
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
// Test a changed draft
assert.NoError(t, CreateRelease(gitRepo, &models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v1.2.1",
Target: "65f1bf2",
Title: "v1.2.1 is draft",
Note: "v1.2.1 is draft",
IsDraft: true,
IsPrerelease: false,
IsTag: false,
}, nil))
release, err = models.GetRelease(repo.ID, "v1.2.1")
assert.NoError(t, err)
releaseCreatedUnix = release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
release, err = models.GetReleaseByID(release.ID)
assert.NoError(t, err)
assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
// Test a changed pre-release
assert.NoError(t, CreateRelease(gitRepo, &models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v1.3.1",
Target: "65f1bf2",
Title: "v1.3.1 is pre-released",
Note: "v1.3.1 is pre-released",
IsDraft: false,
IsPrerelease: true,
IsTag: false,
}, nil))
release, err = models.GetRelease(repo.ID, "v1.3.1")
assert.NoError(t, err)
releaseCreatedUnix = release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
release.Note = "Changed note"
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
release, err = models.GetReleaseByID(release.ID)
assert.NoError(t, err)
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
}
func TestRelease_createTag(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
repoPath := models.RepoPath(user.Name, repo.Name)
gitRepo, err := git.OpenRepository(repoPath)
assert.NoError(t, err)
defer gitRepo.Close()
// Test a changed release
release := &models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v2.1.1",
Target: "master",
Title: "v2.1.1 is released",
Note: "v2.1.1 is released",
IsDraft: false,
IsPrerelease: false,
IsTag: false,
}
assert.NoError(t, createTag(gitRepo, release))
assert.NotEmpty(t, release.CreatedUnix)
releaseCreatedUnix := release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Note = "Changed note"
assert.NoError(t, createTag(gitRepo, release))
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
// Test a changed draft
release = &models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v2.2.1",
Target: "65f1bf2",
Title: "v2.2.1 is draft",
Note: "v2.2.1 is draft",
IsDraft: true,
IsPrerelease: false,
IsTag: false,
}
assert.NoError(t, createTag(gitRepo, release))
releaseCreatedUnix = release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
assert.NoError(t, createTag(gitRepo, release))
assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
// Test a changed pre-release
release = &models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v2.3.1",
Target: "65f1bf2",
Title: "v2.3.1 is pre-released",
Note: "v2.3.1 is pre-released",
IsDraft: false,
IsPrerelease: true,
IsTag: false,
}
assert.NoError(t, createTag(gitRepo, release))
releaseCreatedUnix = release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
release.Note = "Changed note"
assert.NoError(t, createTag(gitRepo, release))
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
}

View File

@@ -8,6 +8,7 @@
</h4>
<div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin">
<input type="hidden" name="from" value="monitor"/>
{{.CsrfTokenHtml}}
<table class="ui very basic striped table">
<thead>

View File

@@ -35,9 +35,9 @@
{{if .Author}}
<img class="ui avatar image" src="{{.Author.RelAvatarLink}}" />
{{if .Author.FullName}}
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong> {{if .IsSigned}}&lt;{{.Commit.Author.Email}}&gt;{{end}}</a>
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a>
{{else}}
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong> {{if .IsSigned}}&lt;{{.Commit.Author.Email}}&gt;{{end}}</a>
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a>
{{end}}
{{else}}
<img class="ui avatar image" src="{{AvatarLink .Commit.Author.Email}}" />
@@ -49,7 +49,7 @@
<span class="text grey">{{svg "octicon-git-commit" 16}}{{.i18n.Tr "repo.diff.committed_by"}}</span>
{{if ne .Verification.CommittingUser.ID 0}}
<img class="ui avatar image" src="{{.Verification.CommittingUser.RelAvatarLink}}" />
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong> &lt;{{.Commit.Committer.Email}}&gt;</a>
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a>
{{else}}
<img class="ui avatar image" src="{{AvatarLink .Commit.Committer.Email}}" />
<strong>{{.Commit.Committer.Name}}</strong>
@@ -94,7 +94,7 @@
<span class="ui text">{{.i18n.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}:</span>
{{end}}
<img class="ui avatar image" src="{{.Verification.SigningUser.RelAvatarLink}}" />
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Verification.SigningUser.Name}}</strong> <{{.Verification.SigningEmail}}></a>
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Verification.SigningUser.Name}}</strong></a>
<span class="pull-right"><span class="ui text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> {{.Verification.SigningKey.KeyID}}</span>
{{else}}
<i class="icons" title="{{.i18n.Tr "gpg.default_key"}}">
@@ -103,7 +103,7 @@
</i>
<span class="ui text">{{.i18n.Tr "repo.commits.signed_by"}}:</span>
<img class="ui avatar image" src="{{AvatarLink .Verification.SigningEmail}}" />
<strong>{{.Verification.SigningUser.Name}}</strong> <{{.Verification.SigningEmail}}>
<strong>{{.Verification.SigningUser.Name}}</strong>
<span class="pull-right"><span class="ui text">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <i class="cogs icon" title="{{.i18n.Tr "gpg.default_key"}}"></i>{{.Verification.SigningKey.KeyID}}</span>
{{end}}
{{else if .Verification.Warning}}

View File

@@ -149,7 +149,10 @@
{{if gt (len $line.Comments) 0}}
{{$resolved := (index $line.Comments 0).IsResolved}}
{{$resolveDoer := (index $line.Comments 0).ResolveDoer}}
{{$isNotPending := (not (eq (index $line.Comments 0).Review.Type 0))}}
{{$isNotPending := false}}
{{if (index $line.Comments 0).Review}}
{{$isNotPending = (not (eq (index $line.Comments 0).Review.Type 0))}}
{{end}}
<tr class="add-code-comment">
<td class="lines-num"></td>
<td class="lines-type-marker"></td>

View File

@@ -25,7 +25,10 @@
{{if gt (len $line.Comments) 0}}
{{$resolved := (index $line.Comments 0).IsResolved}}
{{$resolveDoer := (index $line.Comments 0).ResolveDoer}}
{{$isNotPending := (not (eq (index $line.Comments 0).Review.Type 0))}}
{{$isNotPending := false}}
{{if (index $line.Comments 0).Review}}
{{$isNotPending = (not (eq (index $line.Comments 0).Review.Type 0))}}
{{end}}
<tr>
<td colspan="2" class="lines-num"></td>
<td class="add-comment-left add-comment-right" colspan="2">

View File

@@ -49,7 +49,7 @@
<div class="markdown">
<pre><code>touch README.md
git init
{{if ne .Repository.DefaultBranch "master"}}git branch -m master {{.Repository.DefaultBranch}}{{end}}
{{if ne .Repository.DefaultBranch "master"}}git checkout -b {{.Repository.DefaultBranch}}{{end}}
git add README.md
git commit -m "first commit"
git remote add origin <span class="clone-url">{{if $.DisableSSH}}{{$.CloneLink.HTTPS}}{{else}}{{$.CloneLink.SSH}}{{end}}</span>

View File

@@ -366,6 +366,122 @@
</div>
{{end}}
</div>
{{else if and (eq .Type 21) (eq .ReviewID 0)}}
<div class="timeline-item-group">
<div class="timeline-item event" id="{{.HashTag}}">
{{if .OriginalAuthor }}
{{else}}
<a class="timeline-avatar"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>
<img src="{{.Poster.RelAvatarLink}}">
</a>
{{end}}
<span class="badge grey">{{svg "octicon-comment" 16}}</span>
<span class="text grey">
{{if .OriginalAuthor }}
<span class="text black"><i class="fa {{MigrationIcon $.Repository.GetOriginalURLHostname}}" aria-hidden="true"></i> {{ .OriginalAuthor }}</span><span class="text grey"> {{if $.Repository.OriginalURL}}</span><span class="text migrate">({{$.i18n.Tr "repo.migrated_from" $.Repository.OriginalURL $.Repository.GetOriginalURLHostname | Safe }}){{end}}</span>
{{else}}
<a class="author"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>{{.Poster.GetDisplayName}}</a>
{{end}}
{{$.i18n.Tr "repo.issues.review.comment" $createdStr | Safe}}
</span>
</div>
<div class="timeline-item event">
{{$filename := .TreePath}}
{{$line := .Line}}
<div class="ui segments">
<div class="ui segment">
{{$invalid := .Invalidated}}
{{$resolved := .IsResolved}}
{{$ignore := .LoadResolveDoer}}
{{$resolveDoer := .ResolveDoer}}
{{$isNotPending := true}}
{{if or $invalid $resolved}}
<button id="show-outdated-{{.ID}}" data-comment="{{.ID}}" class="ui compact right labeled button show-outdated">
{{svg "octicon-unfold" 16}}
{{if $invalid }}
{{$.i18n.Tr "repo.issues.review.show_outdated"}}
{{else}}
{{$.i18n.Tr "repo.issues.review.show_resolved"}}
{{end}}
</button>
<button id="hide-outdated-{{.ID}}" data-comment="{{.ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold" 16}}
{{if $invalid}}
{{$.i18n.Tr "repo.issues.review.hide_outdated"}}
{{else}}
{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
{{end}}
</button>
{{end}}
<a href="{{.CodeCommentURL}}" class="file-comment">{{$filename}}</a>
</div>
{{$diff := (CommentMustAsDiff .)}}
{{if $diff}}
{{$file := (index $diff.Files 0)}}
<div id="code-preview-{{.ID}}" class="ui table segment{{if or $invalid $resolved}} hide{{end}}">
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="file-body file-code code-view code-diff code-diff-unified">
<table>
<tbody>
{{template "repo/diff/section_unified" dict "file" $file "root" $}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
<div id="code-comments-{{.ID}}" class="ui segment{{if or $invalid $resolved}} hide{{end}}">
<div class="ui comments">
{{ $createdSubStr:= TimeSinceUnix .CreatedUnix $.Lang }}
<div class="comment" id="{{.HashTag}}">
{{if not .OriginalAuthor }}
<a class="avatar">
<img src="{{.Poster.RelAvatarLink}}">
</a>
{{end}}
<div class="content">
<div class="code-comment-content">
<span class="text grey">
{{if .OriginalAuthor }}
<span class="text black"><i class="fa {{MigrationIcon $.Repository.GetOriginalURLHostname}}" aria-hidden="true"></i> {{ .OriginalAuthor }}</span><span class="text grey"> {{if $.Repository.OriginalURL}}</span><span class="text migrate">({{$.i18n.Tr "repo.migrated_from" $.Repository.OriginalURL $.Repository.GetOriginalURLHostname | Safe }}){{end}}</span>
{{else}}
<a class="author"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>{{.Poster.GetDisplayName}}</a>
{{end}}
{{$.i18n.Tr "repo.issues.commented_at" .HashTag $createdSubStr | Safe}}
<div class="text">
<div class="render-content markdown">
{{if .RenderedContent}}
{{.RenderedContent|Str2html}}
{{else}}
<span class="no-content">{{$.i18n.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
<div class="raw-content hide">{{.Content}}</div>
</div>
</span>
</div>
</div>
</div>
</div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" .ReviewID "root" $ "comment" .}}
{{if and $.CanMarkConversation $isNotPending}}
<button class="ui tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{.ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
{{if $resolved}}
{{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
{{end}}
{{if $resolved}}
<span class="ui grey text"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
{{end}}
</div>
</div>
</div>
</div>
{{else if eq .Type 22}}
<div class="timeline-item-group">
<div class="timeline-item event" id="{{.HashTag}}">

View File

@@ -128,12 +128,12 @@
<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span>
{{range .Labels}}
<div class="item">
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
</div>
{{end}}
{{range .OrgLabels}}
<div class="item">
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
</div>
{{end}}
@@ -238,7 +238,7 @@
<div class="selected">
{{range .Issue.Assignees}}
<div class="item" style="margin-bottom: 10px;">
<a href="{{$.RepoLink}}/issues?assignee={{.ID}}"><img class="ui avatar image" src="{{.RelAvatarLink}}">&nbsp;{{.GetDisplayName}}</a>
<a href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}"><img class="ui avatar image" src="{{.RelAvatarLink}}">&nbsp;{{.GetDisplayName}}</a>
</div>
{{end}}
</div>

View File

@@ -459,6 +459,16 @@
"name": "all",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned.",
"name": "status-types",
"in": "query"
},
{
"type": "string",
"format": "date-time",
@@ -502,7 +512,7 @@
"tags": [
"notification"
],
"summary": "Mark notification threads as read",
"summary": "Mark notification threads as read, pinned or unread",
"operationId": "notifyReadList",
"parameters": [
{
@@ -511,6 +521,28 @@
"description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
"name": "last_read_at",
"in": "query"
},
{
"type": "string",
"description": "If true, mark all notifications on this repo. Default value is false",
"name": "all",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
"name": "status-types",
"in": "query"
},
{
"type": "string",
"description": "Status to mark notifications as, Defaults to read.",
"name": "to-status",
"in": "query"
}
],
"responses": {
@@ -587,6 +619,13 @@
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"default": "read",
"description": "Status to mark notifications as",
"name": "to-status",
"in": "query"
}
],
"responses": {
@@ -6290,6 +6329,16 @@
"name": "all",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned",
"name": "status-types",
"in": "query"
},
{
"type": "string",
"format": "date-time",
@@ -6333,7 +6382,7 @@
"tags": [
"notification"
],
"summary": "Mark notification threads as read on a specific repo",
"summary": "Mark notification threads as read, pinned or unread on a specific repo",
"operationId": "notifyReadRepoList",
"parameters": [
{
@@ -6350,6 +6399,28 @@
"in": "path",
"required": true
},
{
"type": "string",
"description": "If true, mark all notifications on this repo. Default value is false",
"name": "all",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
"name": "status-types",
"in": "query"
},
{
"type": "string",
"description": "Status to mark notifications as. Defaults to read.",
"name": "to-status",
"in": "query"
},
{
"type": "string",
"format": "date-time",

View File

@@ -10,7 +10,7 @@
<div class="ui attached segment">
{{template "base/alert" .}}
{{if .IsResetSent}}
<p>{{.i18n.Tr "auth.reset_password_mail_sent_prompt" .Email .ResetPwdCodeLives | Str2html}}</p>
<p>{{.i18n.Tr "auth.reset_password_mail_sent_prompt" (Escape .Email) .ResetPwdCodeLives | Str2html}}</p>
{{else if .IsResetRequest}}
<div class="required inline field {{if .Err_Email}}error{{end}}">
<label for="email">{{.i18n.Tr "email"}}</label>

View File

@@ -79,7 +79,7 @@
{{ $repoLink := .GetRepoLink}}
{{if $push.Commits}}
{{range $push.Commits}}
<li><img class="img-8" src="{{$push.AvatarLink .AuthorEmail}}"> <a class="commit-id" href="{{$repoLink}}/commit/{{.Sha1}}">{{ShortSha .Sha1}}</a> <span class="text truncate light grey">{{.Message}}</span></li>
<li><img class="img-8" src="{{$push.AvatarLink .AuthorEmail}}"> <a class="commit-id" href="{{$repoLink}}/commit/{{.Sha1}}">{{ShortSha .Sha1}}</a> <span class="text truncate light grey">{{.Message | RenderEmoji}}</span></li>
{{end}}
{{end}}
{{if and (gt $push.Len 1) $push.CompareURL}}<li><a href="{{AppSubUrl}}/{{$push.CompareURL}}">{{$.i18n.Tr "action.compare_commits" $push.Len}} »</a></li>{{end}}

View File

@@ -53,7 +53,7 @@
<li>
<ul class="user-orgs">
{{range .Orgs}}
{{if (or .Visibility.IsPublic (and ($.SignedUser) (or .Visibility.IsLimited (and (.IsUserPartOfOrg $.SignedUserID) .Visibility.IsPrivate) ($.IsAdmin))))}}
{{if (or .Visibility.IsPublic (and ($.SignedUser) (or .Visibility.IsLimited (and (.HasMemberWithUserID $.SignedUserID) .Visibility.IsPrivate) ($.IsAdmin))))}}
<li>
<a href="{{.HomeLink}}"><img class="ui image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a>
</li>

View File

@@ -133,7 +133,15 @@ func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
defer p.lock.RUnlock()
var f *os.File
ok := false
if com.IsFile(filename) {
modTime, err := com.FileMTime(filename)
if err != nil {
return nil, err
}
ok = (modTime + p.maxlifetime) >= time.Now().Unix()
}
if ok {
f, err = os.OpenFile(filename, os.O_RDONLY, 0600)
} else {
f, err = os.Create(filename)

View File

@@ -96,6 +96,8 @@ type MemProvider struct {
// Init initializes memory session provider.
func (p *MemProvider) Init(maxLifetime int64, _ string) error {
p.lock.Lock()
p.list = list.New()
p.data = make(map[string]*list.Element)
p.maxLifetime = maxLifetime
p.lock.Unlock()
return nil
@@ -120,7 +122,8 @@ func (p *MemProvider) Read(sid string) (_ RawStore, err error) {
e, ok := p.data[sid]
p.lock.RUnlock()
if ok {
// Only restore if the session is still alive.
if ok && (e.Value.(*MemStore).lastAccess.Unix()+p.maxLifetime) >= time.Now().Unix() {
if err = p.update(sid); err != nil {
return nil, err
}
@@ -130,7 +133,10 @@ func (p *MemProvider) Read(sid string) (_ RawStore, err error) {
// Create a new session.
p.lock.Lock()
defer p.lock.Unlock()
if ok {
p.list.Remove(e)
delete(p.data, sid)
}
s := NewMemStore(sid)
p.data[sid] = p.list.PushBack(s)
return s, nil
@@ -213,5 +219,5 @@ func (p *MemProvider) GC() {
}
func init() {
Register("memory", &MemProvider{list: list.New(), data: make(map[string]*list.Element)})
Register("memory", &MemProvider{})
}

View File

@@ -120,18 +120,20 @@ func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
// Read returns raw session store by session ID.
func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
now := time.Now().Unix()
var data []byte
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
expiry := now
err := p.c.QueryRow("SELECT data, expiry FROM session WHERE `key`=?", sid).Scan(&data, &expiry)
if err == sql.ErrNoRows {
_, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
sid, "", time.Now().Unix())
sid, "", now)
}
if err != nil {
return nil, err
}
var kv map[interface{}]interface{}
if len(data) == 0 {
if len(data) == 0 || expiry+p.expire <= now {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(data)

View File

@@ -121,18 +121,20 @@ func (p *PostgresProvider) Init(maxlifetime int64, connStr string) (err error) {
// Read returns raw session store by session ID.
func (p *PostgresProvider) Read(sid string) (session.RawStore, error) {
now := time.Now().Unix()
var data []byte
err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
expiry := now
err := p.c.QueryRow("SELECT data, expiry FROM session WHERE key=$1", sid).Scan(&data, &expiry)
if err == sql.ErrNoRows {
_, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
sid, "", time.Now().Unix())
sid, "", now)
}
if err != nil {
return nil, err
}
var kv map[interface{}]interface{}
if len(data) == 0 {
if len(data) == 0 || expiry+p.maxlifetime <= now {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(data)

View File

@@ -1,230 +0,0 @@
// Copyright 2016 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"time"
)
// AppsService provides access to the installation related functions
// in the GitHub API.
//
// GitHub API docs: https://developer.github.com/v3/apps/
type AppsService service
// App represents a GitHub App.
type App struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Owner *User `json:"owner,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
ExternalURL *string `json:"external_url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// InstallationToken represents an installation token.
type InstallationToken struct {
Token *string `json:"token,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
// InstallationPermissions lists the permissions for metadata, contents, issues and single file for an installation.
type InstallationPermissions struct {
Metadata *string `json:"metadata,omitempty"`
Contents *string `json:"contents,omitempty"`
Issues *string `json:"issues,omitempty"`
SingleFile *string `json:"single_file,omitempty"`
}
// Installation represents a GitHub Apps installation.
type Installation struct {
ID *int64 `json:"id,omitempty"`
AppID *int64 `json:"app_id,omitempty"`
TargetID *int64 `json:"target_id,omitempty"`
Account *User `json:"account,omitempty"`
AccessTokensURL *string `json:"access_tokens_url,omitempty"`
RepositoriesURL *string `json:"repositories_url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
TargetType *string `json:"target_type,omitempty"`
SingleFileName *string `json:"single_file_name,omitempty"`
RepositorySelection *string `json:"repository_selection,omitempty"`
Events []string `json:"events,omitempty"`
Permissions *InstallationPermissions `json:"permissions,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
}
func (i Installation) String() string {
return Stringify(i)
}
// Get a single GitHub App. Passing the empty string will get
// the authenticated GitHub App.
//
// Note: appSlug is just the URL-friendly name of your GitHub App.
// You can find this on the settings page for your GitHub App
// (e.g., https://github.com/settings/apps/:app_slug).
//
// GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-github-app
func (s *AppsService) Get(ctx context.Context, appSlug string) (*App, *Response, error) {
var u string
if appSlug != "" {
u = fmt.Sprintf("apps/%v", appSlug)
} else {
u = "app"
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeIntegrationPreview)
app := new(App)
resp, err := s.client.Do(ctx, req, app)
if err != nil {
return nil, resp, err
}
return app, resp, nil
}
// ListInstallations lists the installations that the current GitHub App has.
//
// GitHub API docs: https://developer.github.com/v3/apps/#find-installations
func (s *AppsService) ListInstallations(ctx context.Context, opt *ListOptions) ([]*Installation, *Response, error) {
u, err := addOptions("app/installations", opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeIntegrationPreview)
var i []*Installation
resp, err := s.client.Do(ctx, req, &i)
if err != nil {
return nil, resp, err
}
return i, resp, nil
}
// GetInstallation returns the specified installation.
//
// GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-installation
func (s *AppsService) GetInstallation(ctx context.Context, id int64) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("app/installations/%v", id))
}
// ListUserInstallations lists installations that are accessible to the authenticated user.
//
// GitHub API docs: https://developer.github.com/v3/apps/#list-installations-for-user
func (s *AppsService) ListUserInstallations(ctx context.Context, opt *ListOptions) ([]*Installation, *Response, error) {
u, err := addOptions("user/installations", opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeIntegrationPreview)
var i struct {
Installations []*Installation `json:"installations"`
}
resp, err := s.client.Do(ctx, req, &i)
if err != nil {
return nil, resp, err
}
return i.Installations, resp, nil
}
// CreateInstallationToken creates a new installation token.
//
// GitHub API docs: https://developer.github.com/v3/apps/#create-a-new-installation-token
func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64) (*InstallationToken, *Response, error) {
u := fmt.Sprintf("app/installations/%v/access_tokens", id)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeIntegrationPreview)
t := new(InstallationToken)
resp, err := s.client.Do(ctx, req, t)
if err != nil {
return nil, resp, err
}
return t, resp, nil
}
// FindOrganizationInstallation finds the organization's installation information.
//
// GitHub API docs: https://developer.github.com/v3/apps/#find-organization-installation
func (s *AppsService) FindOrganizationInstallation(ctx context.Context, org string) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("orgs/%v/installation", org))
}
// FindRepositoryInstallation finds the repository's installation information.
//
// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation
func (s *AppsService) FindRepositoryInstallation(ctx context.Context, owner, repo string) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("repos/%v/%v/installation", owner, repo))
}
// FindRepositoryInstallationByID finds the repository's installation information.
//
// Note: FindRepositoryInstallationByID uses the undocumented GitHub API endpoint /repositories/:id/installation.
func (s *AppsService) FindRepositoryInstallationByID(ctx context.Context, id int64) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("repositories/%d/installation", id))
}
// FindUserInstallation finds the user's installation information.
//
// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation
func (s *AppsService) FindUserInstallation(ctx context.Context, user string) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("users/%v/installation", user))
}
func (s *AppsService) getInstallation(ctx context.Context, url string) (*Installation, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeIntegrationPreview)
i := new(Installation)
resp, err := s.client.Do(ctx, req, i)
if err != nil {
return nil, resp, err
}
return i, resp, nil
}

View File

@@ -1,457 +0,0 @@
// Copyright 2018 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"strings"
"time"
)
// TeamsService provides access to the team-related functions
// in the GitHub API.
//
// GitHub API docs: https://developer.github.com/v3/teams/
type TeamsService service
// Team represents a team within a GitHub organization. Teams are used to
// manage access to an organization's repositories.
type Team struct {
ID *int64 `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
URL *string `json:"url,omitempty"`
Slug *string `json:"slug,omitempty"`
// Permission specifies the default permission for repositories owned by the team.
Permission *string `json:"permission,omitempty"`
// Privacy identifies the level of privacy this team should have.
// Possible values are:
// secret - only visible to organization owners and members of this team
// closed - visible to all members of this organization
// Default is "secret".
Privacy *string `json:"privacy,omitempty"`
MembersCount *int `json:"members_count,omitempty"`
ReposCount *int `json:"repos_count,omitempty"`
Organization *Organization `json:"organization,omitempty"`
MembersURL *string `json:"members_url,omitempty"`
RepositoriesURL *string `json:"repositories_url,omitempty"`
Parent *Team `json:"parent,omitempty"`
// LDAPDN is only available in GitHub Enterprise and when the team
// membership is synchronized with LDAP.
LDAPDN *string `json:"ldap_dn,omitempty"`
}
func (t Team) String() string {
return Stringify(t)
}
// Invitation represents a team member's invitation status.
type Invitation struct {
ID *int64 `json:"id,omitempty"`
Login *string `json:"login,omitempty"`
Email *string `json:"email,omitempty"`
// Role can be one of the values - 'direct_member', 'admin', 'billing_manager', 'hiring_manager', or 'reinstate'.
Role *string `json:"role,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
Inviter *User `json:"inviter,omitempty"`
TeamCount *int `json:"team_count,omitempty"`
InvitationTeamURL *string `json:"invitation_team_url,omitempty"`
}
func (i Invitation) String() string {
return Stringify(i)
}
// ListTeams lists all of the teams for an organization.
//
// GitHub API docs: https://developer.github.com/v3/teams/#list-teams
func (s *TeamsService) ListTeams(ctx context.Context, org string, opt *ListOptions) ([]*Team, *Response, error) {
u := fmt.Sprintf("orgs/%v/teams", org)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
var teams []*Team
resp, err := s.client.Do(ctx, req, &teams)
if err != nil {
return nil, resp, err
}
return teams, resp, nil
}
// GetTeam fetches a team by ID.
//
// GitHub API docs: https://developer.github.com/v3/teams/#get-team
func (s *TeamsService) GetTeam(ctx context.Context, team int64) (*Team, *Response, error) {
u := fmt.Sprintf("teams/%v", team)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
t := new(Team)
resp, err := s.client.Do(ctx, req, t)
if err != nil {
return nil, resp, err
}
return t, resp, nil
}
// NewTeam represents a team to be created or modified.
type NewTeam struct {
Name string `json:"name"` // Name of the team. (Required.)
Description *string `json:"description,omitempty"`
Maintainers []string `json:"maintainers,omitempty"`
RepoNames []string `json:"repo_names,omitempty"`
ParentTeamID *int64 `json:"parent_team_id,omitempty"`
// Deprecated: Permission is deprecated when creating or editing a team in an org
// using the new GitHub permission model. It no longer identifies the
// permission a team has on its repos, but only specifies the default
// permission a repo is initially added with. Avoid confusion by
// specifying a permission value when calling AddTeamRepo.
Permission *string `json:"permission,omitempty"`
// Privacy identifies the level of privacy this team should have.
// Possible values are:
// secret - only visible to organization owners and members of this team
// closed - visible to all members of this organization
// Default is "secret".
Privacy *string `json:"privacy,omitempty"`
// LDAPDN may be used in GitHub Enterprise when the team membership
// is synchronized with LDAP.
LDAPDN *string `json:"ldap_dn,omitempty"`
}
func (s NewTeam) String() string {
return Stringify(s)
}
// CreateTeam creates a new team within an organization.
//
// GitHub API docs: https://developer.github.com/v3/teams/#create-team
func (s *TeamsService) CreateTeam(ctx context.Context, org string, team NewTeam) (*Team, *Response, error) {
u := fmt.Sprintf("orgs/%v/teams", org)
req, err := s.client.NewRequest("POST", u, team)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
t := new(Team)
resp, err := s.client.Do(ctx, req, t)
if err != nil {
return nil, resp, err
}
return t, resp, nil
}
// EditTeam edits a team.
//
// GitHub API docs: https://developer.github.com/v3/teams/#edit-team
func (s *TeamsService) EditTeam(ctx context.Context, id int64, team NewTeam) (*Team, *Response, error) {
u := fmt.Sprintf("teams/%v", id)
req, err := s.client.NewRequest("PATCH", u, team)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
t := new(Team)
resp, err := s.client.Do(ctx, req, t)
if err != nil {
return nil, resp, err
}
return t, resp, nil
}
// DeleteTeam deletes a team.
//
// GitHub API docs: https://developer.github.com/v3/teams/#delete-team
func (s *TeamsService) DeleteTeam(ctx context.Context, team int64) (*Response, error) {
u := fmt.Sprintf("teams/%v", team)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
return s.client.Do(ctx, req, nil)
}
// ListChildTeams lists child teams for a team.
//
// GitHub API docs: https://developer.github.com/v3/teams/#list-child-teams
func (s *TeamsService) ListChildTeams(ctx context.Context, teamID int64, opt *ListOptions) ([]*Team, *Response, error) {
u := fmt.Sprintf("teams/%v/teams", teamID)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
var teams []*Team
resp, err := s.client.Do(ctx, req, &teams)
if err != nil {
return nil, resp, err
}
return teams, resp, nil
}
// ListTeamRepos lists the repositories that the specified team has access to.
//
// GitHub API docs: https://developer.github.com/v3/teams/#list-team-repos
func (s *TeamsService) ListTeamRepos(ctx context.Context, team int64, opt *ListOptions) ([]*Repository, *Response, error) {
u := fmt.Sprintf("teams/%v/repos", team)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when topics API fully launches.
headers := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview}
req.Header.Set("Accept", strings.Join(headers, ", "))
var repos []*Repository
resp, err := s.client.Do(ctx, req, &repos)
if err != nil {
return nil, resp, err
}
return repos, resp, nil
}
// IsTeamRepo checks if a team manages the specified repository. If the
// repository is managed by team, a Repository is returned which includes the
// permissions team has for that repo.
//
// GitHub API docs: https://developer.github.com/v3/teams/#check-if-a-team-manages-a-repository
func (s *TeamsService) IsTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Repository, *Response, error) {
u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
headers := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview}
req.Header.Set("Accept", strings.Join(headers, ", "))
repository := new(Repository)
resp, err := s.client.Do(ctx, req, repository)
if err != nil {
return nil, resp, err
}
return repository, resp, nil
}
// TeamAddTeamRepoOptions specifies the optional parameters to the
// TeamsService.AddTeamRepo method.
type TeamAddTeamRepoOptions struct {
// Permission specifies the permission to grant the team on this repository.
// Possible values are:
// pull - team members can pull, but not push to or administer this repository
// push - team members can pull and push, but not administer this repository
// admin - team members can pull, push and administer this repository
//
// If not specified, the team's permission attribute will be used.
Permission string `json:"permission,omitempty"`
}
// AddTeamRepo adds a repository to be managed by the specified team. The
// specified repository must be owned by the organization to which the team
// belongs, or a direct fork of a repository owned by the organization.
//
// GitHub API docs: https://developer.github.com/v3/teams/#add-team-repo
func (s *TeamsService) AddTeamRepo(ctx context.Context, team int64, owner string, repo string, opt *TeamAddTeamRepoOptions) (*Response, error) {
u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
req, err := s.client.NewRequest("PUT", u, opt)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// RemoveTeamRepo removes a repository from being managed by the specified
// team. Note that this does not delete the repository, it just removes it
// from the team.
//
// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repo
func (s *TeamsService) RemoveTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Response, error) {
u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// ListUserTeams lists a user's teams
// GitHub API docs: https://developer.github.com/v3/teams/#list-user-teams
func (s *TeamsService) ListUserTeams(ctx context.Context, opt *ListOptions) ([]*Team, *Response, error) {
u := "user/teams"
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
var teams []*Team
resp, err := s.client.Do(ctx, req, &teams)
if err != nil {
return nil, resp, err
}
return teams, resp, nil
}
// ListTeamProjects lists the organization projects for a team.
//
// GitHub API docs: https://developer.github.com/v3/teams/#list-team-projects
func (s *TeamsService) ListTeamProjects(ctx context.Context, teamID int64) ([]*Project, *Response, error) {
u := fmt.Sprintf("teams/%v/projects", teamID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
acceptHeaders := []string{mediaTypeNestedTeamsPreview, mediaTypeProjectsPreview}
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
var projects []*Project
resp, err := s.client.Do(ctx, req, &projects)
if err != nil {
return nil, resp, err
}
return projects, resp, nil
}
// ReviewTeamProjects checks whether a team has read, write, or admin
// permissions for an organization project.
//
// GitHub API docs: https://developer.github.com/v3/teams/#review-a-team-project
func (s *TeamsService) ReviewTeamProjects(ctx context.Context, teamID, projectID int64) (*Project, *Response, error) {
u := fmt.Sprintf("teams/%v/projects/%v", teamID, projectID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
acceptHeaders := []string{mediaTypeNestedTeamsPreview, mediaTypeProjectsPreview}
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
projects := &Project{}
resp, err := s.client.Do(ctx, req, &projects)
if err != nil {
return nil, resp, err
}
return projects, resp, nil
}
// TeamProjectOptions specifies the optional parameters to the
// TeamsService.AddTeamProject method.
type TeamProjectOptions struct {
// Permission specifies the permission to grant to the team for this project.
// Possible values are:
// "read" - team members can read, but not write to or administer this project.
// "write" - team members can read and write, but not administer this project.
// "admin" - team members can read, write and administer this project.
//
Permission *string `json:"permission,omitempty"`
}
// AddTeamProject adds an organization project to a team. To add a project to a team or
// update the team's permission on a project, the authenticated user must have admin
// permissions for the project.
//
// GitHub API docs: https://developer.github.com/v3/teams/#add-or-update-team-project
func (s *TeamsService) AddTeamProject(ctx context.Context, teamID, projectID int64, opt *TeamProjectOptions) (*Response, error) {
u := fmt.Sprintf("teams/%v/projects/%v", teamID, projectID)
req, err := s.client.NewRequest("PUT", u, opt)
if err != nil {
return nil, err
}
// TODO: remove custom Accept header when this API fully launches.
acceptHeaders := []string{mediaTypeNestedTeamsPreview, mediaTypeProjectsPreview}
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
return s.client.Do(ctx, req, nil)
}
// RemoveTeamProject removes an organization project from a team. An organization owner or
// a team maintainer can remove any project from the team. To remove a project from a team
// as an organization member, the authenticated user must have "read" access to both the team
// and project, or "admin" access to the team or project.
// Note: This endpoint removes the project from the team, but does not delete it.
//
// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-project
func (s *TeamsService) RemoveTeamProject(ctx context.Context, teamID int64, projectID int64) (*Response, error) {
u := fmt.Sprintf("teams/%v/projects/%v", teamID, projectID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
// TODO: remove custom Accept header when this API fully launches.
acceptHeaders := []string{mediaTypeNestedTeamsPreview, mediaTypeProjectsPreview}
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
return s.client.Do(ctx, req, nil)
}

View File

@@ -1,155 +0,0 @@
// Copyright 2018 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// DiscussionComment represents a GitHub dicussion in a team.
type DiscussionComment struct {
Author *User `json:"author,omitempty"`
Body *string `json:"body,omitempty"`
BodyHTML *string `json:"body_html,omitempty"`
BodyVersion *string `json:"body_version,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
LastEditedAt *Timestamp `json:"last_edited_at,omitempty"`
DiscussionURL *string `json:"discussion_url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Number *int `json:"number,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
URL *string `json:"url,omitempty"`
Reactions *Reactions `json:"reactions,omitempty"`
}
func (c DiscussionComment) String() string {
return Stringify(c)
}
// DiscussionCommentListOptions specifies optional parameters to the
// TeamServices.ListComments method.
type DiscussionCommentListOptions struct {
// Sorts the discussion comments by the date they were created.
// Accepted values are asc and desc. Default is desc.
Direction string `url:"direction,omitempty"`
}
// ListComments lists all comments on a team discussion.
// Authenticated user must grant read:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#list-comments
func (s *TeamsService) ListComments(ctx context.Context, teamID int64, discussionNumber int, options *DiscussionCommentListOptions) ([]*DiscussionComment, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v/comments", teamID, discussionNumber)
u, err := addOptions(u, options)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
var comments []*DiscussionComment
resp, err := s.client.Do(ctx, req, &comments)
if err != nil {
return nil, resp, err
}
return comments, resp, nil
}
// GetComment gets a specific comment on a team discussion.
// Authenticated user must grant read:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#get-a-single-comment
func (s *TeamsService) GetComment(ctx context.Context, teamID int64, discussionNumber, commentNumber int) (*DiscussionComment, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v", teamID, discussionNumber, commentNumber)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
discussionComment := &DiscussionComment{}
resp, err := s.client.Do(ctx, req, discussionComment)
if err != nil {
return nil, resp, err
}
return discussionComment, resp, nil
}
// CreateComment creates a new discussion post on a team discussion.
// Authenticated user must grant write:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#create-a-comment
func (s *TeamsService) CreateComment(ctx context.Context, teamID int64, discsusionNumber int, comment DiscussionComment) (*DiscussionComment, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v/comments", teamID, discsusionNumber)
req, err := s.client.NewRequest("POST", u, comment)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
discussionComment := &DiscussionComment{}
resp, err := s.client.Do(ctx, req, discussionComment)
if err != nil {
return nil, resp, err
}
return discussionComment, resp, nil
}
// EditComment edits the body text of a discussion comment.
// Authenticated user must grant write:discussion scope.
// User is allowed to edit body of a comment only.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#edit-a-comment
func (s *TeamsService) EditComment(ctx context.Context, teamID int64, discussionNumber, commentNumber int, comment DiscussionComment) (*DiscussionComment, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v", teamID, discussionNumber, commentNumber)
req, err := s.client.NewRequest("PATCH", u, comment)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
discussionComment := &DiscussionComment{}
resp, err := s.client.Do(ctx, req, discussionComment)
if err != nil {
return nil, resp, err
}
return discussionComment, resp, nil
}
// DeleteComment deletes a comment on a team discussion.
// Authenticated user must grant write:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#delete-a-comment
func (s *TeamsService) DeleteComment(ctx context.Context, teamID int64, discussionNumber, commentNumber int) (*Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v", teamID, discussionNumber, commentNumber)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
return s.client.Do(ctx, req, nil)
}

View File

@@ -1,160 +0,0 @@
// Copyright 2018 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// TeamDiscussion represents a GitHub dicussion in a team.
type TeamDiscussion struct {
Author *User `json:"author,omitempty"`
Body *string `json:"body,omitempty"`
BodyHTML *string `json:"body_html,omitempty"`
BodyVersion *string `json:"body_version,omitempty"`
CommentsCount *int `json:"comments_count,omitempty"`
CommentsURL *string `json:"comments_url,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
LastEditedAt *Timestamp `json:"last_edited_at,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Number *int `json:"number,omitempty"`
Pinned *bool `json:"pinned,omitempty"`
Private *bool `json:"private,omitempty"`
TeamURL *string `json:"team_url,omitempty"`
Title *string `json:"title,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
URL *string `json:"url,omitempty"`
Reactions *Reactions `json:"reactions,omitempty"`
}
func (d TeamDiscussion) String() string {
return Stringify(d)
}
// DiscussionListOptions specifies optional parameters to the
// TeamServices.ListDiscussions method.
type DiscussionListOptions struct {
// Sorts the discussion by the date they were created.
// Accepted values are asc and desc. Default is desc.
Direction string `url:"direction,omitempty"`
}
// ListDiscussions lists all discussions on team's page.
// Authenticated user must grant read:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussions/#list-discussions
func (s *TeamsService) ListDiscussions(ctx context.Context, teamID int64, options *DiscussionListOptions) ([]*TeamDiscussion, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions", teamID)
u, err := addOptions(u, options)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
var teamDiscussions []*TeamDiscussion
resp, err := s.client.Do(ctx, req, &teamDiscussions)
if err != nil {
return nil, resp, err
}
return teamDiscussions, resp, nil
}
// GetDiscussion gets a specific discussion on a team's page.
// Authenticated user must grant read:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussions/#get-a-single-discussion
func (s *TeamsService) GetDiscussion(ctx context.Context, teamID int64, discussionNumber int) (*TeamDiscussion, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v", teamID, discussionNumber)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
teamDiscussion := &TeamDiscussion{}
resp, err := s.client.Do(ctx, req, teamDiscussion)
if err != nil {
return nil, resp, err
}
return teamDiscussion, resp, nil
}
// CreateDiscussion creates a new discussion post on a team's page.
// Authenticated user must grant write:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussions/#create-a-discussion
func (s *TeamsService) CreateDiscussion(ctx context.Context, teamID int64, discussion TeamDiscussion) (*TeamDiscussion, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions", teamID)
req, err := s.client.NewRequest("POST", u, discussion)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
teamDiscussion := &TeamDiscussion{}
resp, err := s.client.Do(ctx, req, teamDiscussion)
if err != nil {
return nil, resp, err
}
return teamDiscussion, resp, nil
}
// EditDiscussion edits the title and body text of a discussion post.
// Authenticated user must grant write:discussion scope.
// User is allowed to change Title and Body of a discussion only.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussions/#edit-a-discussion
func (s *TeamsService) EditDiscussion(ctx context.Context, teamID int64, discussionNumber int, discussion TeamDiscussion) (*TeamDiscussion, *Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v", teamID, discussionNumber)
req, err := s.client.NewRequest("PATCH", u, discussion)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
teamDiscussion := &TeamDiscussion{}
resp, err := s.client.Do(ctx, req, teamDiscussion)
if err != nil {
return nil, resp, err
}
return teamDiscussion, resp, nil
}
// DeleteDiscussion deletes a discussion from team's page.
// Authenticated user must grant write:discussion scope.
//
// GitHub API docs: https://developer.github.com/v3/teams/discussions/#delete-a-discussion
func (s *TeamsService) DeleteDiscussion(ctx context.Context, teamID int64, discussionNumber int) (*Response, error) {
u := fmt.Sprintf("teams/%v/discussions/%v", teamID, discussionNumber)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview)
return s.client.Do(ctx, req, nil)
}

View File

@@ -1,174 +0,0 @@
// Copyright 2018 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// TeamListTeamMembersOptions specifies the optional parameters to the
// TeamsService.ListTeamMembers method.
type TeamListTeamMembersOptions struct {
// Role filters members returned by their role in the team. Possible
// values are "all", "member", "maintainer". Default is "all".
Role string `url:"role,omitempty"`
ListOptions
}
// ListTeamMembers lists all of the users who are members of the specified
// team.
//
// GitHub API docs: https://developer.github.com/v3/teams/members/#list-team-members
func (s *TeamsService) ListTeamMembers(ctx context.Context, team int64, opt *TeamListTeamMembersOptions) ([]*User, *Response, error) {
u := fmt.Sprintf("teams/%v/members", team)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
var members []*User
resp, err := s.client.Do(ctx, req, &members)
if err != nil {
return nil, resp, err
}
return members, resp, nil
}
// IsTeamMember checks if a user is a member of the specified team.
//
// GitHub API docs: https://developer.github.com/v3/teams/members/#get-team-member
//
// Deprecated: This API has been marked as deprecated in the Github API docs,
// TeamsService.GetTeamMembership method should be used instead.
func (s *TeamsService) IsTeamMember(ctx context.Context, team int64, user string) (bool, *Response, error) {
u := fmt.Sprintf("teams/%v/members/%v", team, user)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return false, nil, err
}
resp, err := s.client.Do(ctx, req, nil)
member, err := parseBoolResponse(err)
return member, resp, err
}
// GetTeamMembership returns the membership status for a user in a team.
//
// GitHub API docs: https://developer.github.com/v3/teams/members/#get-team-membership
func (s *TeamsService) GetTeamMembership(ctx context.Context, team int64, user string) (*Membership, *Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
t := new(Membership)
resp, err := s.client.Do(ctx, req, t)
if err != nil {
return nil, resp, err
}
return t, resp, nil
}
// TeamAddTeamMembershipOptions specifies the optional
// parameters to the TeamsService.AddTeamMembership method.
type TeamAddTeamMembershipOptions struct {
// Role specifies the role the user should have in the team. Possible
// values are:
// member - a normal member of the team
// maintainer - a team maintainer. Able to add/remove other team
// members, promote other team members to team
// maintainer, and edit the teams name and description
//
// Default value is "member".
Role string `json:"role,omitempty"`
}
// AddTeamMembership adds or invites a user to a team.
//
// In order to add a membership between a user and a team, the authenticated
// user must have 'admin' permissions to the team or be an owner of the
// organization that the team is associated with.
//
// If the user is already a part of the team's organization (meaning they're on
// at least one other team in the organization), this endpoint will add the
// user to the team.
//
// If the user is completely unaffiliated with the team's organization (meaning
// they're on none of the organization's teams), this endpoint will send an
// invitation to the user via email. This newly-created membership will be in
// the "pending" state until the user accepts the invitation, at which point
// the membership will transition to the "active" state and the user will be
// added as a member of the team.
//
// GitHub API docs: https://developer.github.com/v3/teams/members/#add-or-update-team-membership
func (s *TeamsService) AddTeamMembership(ctx context.Context, team int64, user string, opt *TeamAddTeamMembershipOptions) (*Membership, *Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("PUT", u, opt)
if err != nil {
return nil, nil, err
}
t := new(Membership)
resp, err := s.client.Do(ctx, req, t)
if err != nil {
return nil, resp, err
}
return t, resp, nil
}
// RemoveTeamMembership removes a user from a team.
//
// GitHub API docs: https://developer.github.com/v3/teams/members/#remove-team-membership
func (s *TeamsService) RemoveTeamMembership(ctx context.Context, team int64, user string) (*Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// ListPendingTeamInvitations get pending invitaion list in team.
// Warning: The API may change without advance notice during the preview period.
// Preview features are not supported for production use.
//
// GitHub API docs: https://developer.github.com/v3/teams/members/#list-pending-team-invitations
func (s *TeamsService) ListPendingTeamInvitations(ctx context.Context, team int64, opt *ListOptions) ([]*Invitation, *Response, error) {
u := fmt.Sprintf("teams/%v/invitations", team)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var pendingInvitations []*Invitation
resp, err := s.client.Do(ctx, req, &pendingInvitations)
if err != nil {
return nil, resp, err
}
return pendingInvitations, resp, nil
}

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