mirror of
https://github.com/go-gitea/gitea
synced 2026-02-06 03:40:55 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa1d9ef6cb | ||
|
|
20a75f86a1 | ||
|
|
c1c5e00d20 | ||
|
|
3e279dfb0b | ||
|
|
e9346fc4a9 | ||
|
|
b62e13a001 | ||
|
|
470b195da1 | ||
|
|
09178300b0 | ||
|
|
23aae3274a | ||
|
|
a98bf03204 | ||
|
|
65aef7b35f | ||
|
|
65ef634d5c | ||
|
|
869fd17b88 | ||
|
|
d624e91c0c | ||
|
|
62a3c847cd | ||
|
|
3a02f0896e | ||
|
|
408db95dc1 | ||
|
|
6305f07fdc | ||
|
|
ff9d99f63d | ||
|
|
37572551d7 | ||
|
|
0ee823be0b | ||
|
|
062ea40a79 | ||
|
|
7a25441abe | ||
|
|
dc71d00393 | ||
|
|
0bb56a413d | ||
|
|
2806a312e1 | ||
|
|
8a51c48eb6 | ||
|
|
0fa538e552 | ||
|
|
69e4b6910b | ||
|
|
0e9dcc9500 | ||
|
|
87f02d90cf | ||
|
|
21cd7ab812 | ||
|
|
981216c9fe | ||
|
|
cfbfb73c56 | ||
|
|
4a548a0332 | ||
|
|
8bf2ee1e02 | ||
|
|
a687980412 | ||
|
|
1f85815a3b | ||
|
|
ee5e5a5093 | ||
|
|
03ba12aabf | ||
|
|
24ed1b5feb | ||
|
|
8282697734 | ||
|
|
ec48618d40 | ||
|
|
f0dd07129a | ||
|
|
6d3b8141df | ||
|
|
13c4c7a132 | ||
|
|
6015d30dd6 | ||
|
|
b1cfb0d7a2 | ||
|
|
48a423a8a8 | ||
|
|
cc8a7c9345 |
@@ -4,6 +4,60 @@ 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.5](https://github.com/go-gitea/gitea/releases/tag/v1.12.5) - 2020-10-01
|
||||
|
||||
* BUGFIXES
|
||||
* Allow U2F with default settings for gitea in subpath (#12990) (#13001)
|
||||
* Prevent empty div when editing comment (#12404) (#12991)
|
||||
* On mirror update also update address in DB (#12964) (#12967)
|
||||
* Allow extended config on cron settings (#12939) (#12943)
|
||||
* Open transaction when adding Avatar email-hash pairs to the DB (#12577) (#12940)
|
||||
* Fix internal server error from ListUserOrgs API (#12910) (#12915)
|
||||
* Update only the repository columns that need updating (#12900) (#12912)
|
||||
* Fix panic when adding long comment (#12892) (#12894)
|
||||
* Add size limit for content of comment on action ui (#12881) (#12890)
|
||||
* Convert User expose ID each time (#12855) (#12883)
|
||||
* Support slashes in release tags (#12864) (#12882)
|
||||
* Add missing information to CreateRepo API endpoint (#12848) (#12867)
|
||||
* On Migration respect old DefaultBranch (#12843) (#12858)
|
||||
* Fix notifications page links (#12838) (#12853)
|
||||
* Stop cloning unnecessarily on PR update (#12839) (#12852)
|
||||
* Escape more things that are passed through str2html (#12622) (#12850)
|
||||
* Remove double escape on labels addition in comments (#12809) (#12810)
|
||||
* Fix "only mail on mention" bug (#12775) (#12789)
|
||||
* Fix yet another bug with diff file names (#12771) (#12776)
|
||||
* RepoInit Respect AlternateDefaultBranch (#12746) (#12751)
|
||||
* Fix Avatar Resize (resize algo NearestNeighbor -> Bilinear) (#12745) (#12750)
|
||||
* ENHANCEMENTS
|
||||
* gitea dump: include version & Check InstallLock (#12760) (#12762)
|
||||
|
||||
## [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
|
||||
|
||||
@@ -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)
|
||||
@@ -531,7 +532,7 @@ release-windows: | $(DIST_DIRS)
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
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
|
||||
|
||||
@@ -66,6 +66,10 @@ func fatal(format string, args ...interface{}) {
|
||||
|
||||
func runDump(ctx *cli.Context) error {
|
||||
setting.NewContext()
|
||||
if !setting.InstallLock {
|
||||
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
|
||||
return fmt.Errorf("gitea is not initialized")
|
||||
}
|
||||
setting.NewServices() // cannot access session settings otherwise
|
||||
|
||||
err := models.SetEngine()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -75,7 +75,7 @@ require (
|
||||
github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/niklasfasching/go-org v0.1.9
|
||||
github.com/oliamb/cutter v0.2.2
|
||||
github.com/olivere/elastic/v7 v7.0.9
|
||||
|
||||
@@ -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=
|
||||
@@ -485,8 +483,8 @@ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOl
|
||||
github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc h1:z1PgdCCmYYVL0BoJTUgmAq1p7ca8fzYIPsNyfsN3xAU=
|
||||
github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/niklasfasching/go-org v0.1.9 h1:Toz8WMIt+qJb52uYEk1YD/muLuOOmRt1CfkV+bKVMkI=
|
||||
@@ -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=
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestAPIPullReview(t *testing.T) {
|
||||
assert.EqualValues(t, 10, reviews[5].ID)
|
||||
assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State)
|
||||
assert.EqualValues(t, 1, reviews[5].CodeCommentsCount)
|
||||
assert.EqualValues(t, 0, reviews[5].Reviewer.ID) // ghost user
|
||||
assert.EqualValues(t, -1, reviews[5].Reviewer.ID) // ghost user
|
||||
assert.EqualValues(t, false, reviews[5].Stale)
|
||||
assert.EqualValues(t, true, reviews[5].Official)
|
||||
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -45,8 +48,14 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) {
|
||||
var results SearchResults
|
||||
DecodeJSON(t, resp, &results)
|
||||
assert.NotEmpty(t, results.Data)
|
||||
var modelUser *models.User
|
||||
for _, user := range results.Data {
|
||||
assert.Contains(t, user.UserName, query)
|
||||
assert.Empty(t, user.Email)
|
||||
modelUser = models.AssertExistsAndLoadBean(t, &models.User{ID: user.ID}).(*models.User)
|
||||
if modelUser.KeepEmailPrivate {
|
||||
assert.EqualValues(t, fmt.Sprintf("%s@%s", modelUser.LowerName, setting.Service.NoReplyAddress), user.Email)
|
||||
} else {
|
||||
assert.EqualValues(t, modelUser.Email, user.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ func restoreOldDB(t *testing.T, version string) bool {
|
||||
|
||||
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name))
|
||||
assert.NoError(t, err)
|
||||
db.Close()
|
||||
|
||||
db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?multiStatements=true",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name))
|
||||
@@ -182,6 +183,8 @@ func restoreOldDB(t *testing.T, version string) bool {
|
||||
if !assert.NoError(t, err) {
|
||||
return false
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
|
||||
if !assert.NoError(t, err) || !assert.NotEmpty(t, schrows) {
|
||||
return false
|
||||
|
||||
+12
-1
@@ -41,7 +41,18 @@ func AvatarLink(email string) string {
|
||||
Email: lowerEmail,
|
||||
Hash: sum,
|
||||
}
|
||||
_, _ = x.Insert(emailHash)
|
||||
// OK we're going to open a session just because I think that that might hide away any problems with postgres reporting errors
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
// we don't care about any DB problem just return the lowerEmail
|
||||
return lowerEmail, nil
|
||||
}
|
||||
_, _ = sess.Insert(emailHash)
|
||||
if err := sess.Commit(); err != nil {
|
||||
// Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time
|
||||
return lowerEmail, nil
|
||||
}
|
||||
return lowerEmail, nil
|
||||
})
|
||||
return setting.AppSubURL + "/avatar/" + url.PathEscape(sum)
|
||||
|
||||
+3
-2
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+34
-15
@@ -11,7 +11,6 @@ import (
|
||||
)
|
||||
|
||||
func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
|
||||
|
||||
type ProtectedBranch struct {
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
@@ -23,29 +22,26 @@ func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
|
||||
Official bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Sync2(new(ProtectedBranch)); err != nil {
|
||||
if err := x.Sync2(new(ProtectedBranch)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sess.Sync2(new(Review)); err != nil {
|
||||
if err := x.Sync2(new(Review)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil {
|
||||
if _, err := x.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil {
|
||||
if _, err := x.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil {
|
||||
if _, err := x.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pageSize int64 = 20
|
||||
qresult, err := sess.QueryInterface("SELECT max(id) as max_id FROM issue")
|
||||
qresult, err := x.QueryInterface("SELECT max(id) as max_id FROM issue")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -57,10 +53,19 @@ func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
|
||||
}
|
||||
totalPages := totalIssues / pageSize
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find latest review of each user in each pull request, and set official field if appropriate
|
||||
reviews := []*models.Review{}
|
||||
|
||||
var page int64
|
||||
var count int
|
||||
for page = 0; page <= totalPages; page++ {
|
||||
reviews := []*models.Review{}
|
||||
if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id > ? AND issue_id <= ? AND type in (?, ?) GROUP BY issue_id, reviewer_id)",
|
||||
page*pageSize, (page+1)*pageSize, models.ReviewTypeApprove, models.ReviewTypeReject).
|
||||
Find(&reviews); err != nil {
|
||||
@@ -68,23 +73,37 @@ func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
|
||||
}
|
||||
|
||||
for _, review := range reviews {
|
||||
if err := review.LoadAttributes(); err != nil {
|
||||
if err := review.LoadAttributesX(sess); err != nil {
|
||||
// Error might occur if user or issue doesn't exist, ignore it.
|
||||
continue
|
||||
}
|
||||
official, err := models.IsOfficialReviewer(review.Issue, review.Reviewer)
|
||||
official, err := models.IsOfficialReviewerX(sess, review.Issue, review.Reviewer)
|
||||
if err != nil {
|
||||
// Branch might not be proteced or other error, ignore it.
|
||||
continue
|
||||
}
|
||||
review.Official = official
|
||||
|
||||
count++
|
||||
|
||||
if _, err := sess.ID(review.ID).Cols("official").Update(review); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if count == 100 {
|
||||
if err := sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
count = 0
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
if count > 0 {
|
||||
return sess.Commit()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -264,6 +264,17 @@ func DumpDatabase(filePath string, dbType string) error {
|
||||
}
|
||||
tbs = append(tbs, t)
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64
|
||||
}
|
||||
t, err := x.TableInfo(Version{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tbs = append(tbs, t)
|
||||
|
||||
if len(dbType) > 0 {
|
||||
return x.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType))
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ func TestDumpDatabase(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "dump")
|
||||
assert.NoError(t, err)
|
||||
|
||||
type Version struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64
|
||||
}
|
||||
assert.NoError(t, x.Sync2(Version{}))
|
||||
|
||||
for _, dbName := range setting.SupportedDatabases {
|
||||
dbType := setting.GetDBTypeByName(dbName)
|
||||
assert.NoError(t, DumpDatabase(filepath.Join(dir, dbType+".sql"), dbType))
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
+9
-2
@@ -110,7 +110,8 @@ func (r *Review) LoadReviewer() error {
|
||||
return r.loadReviewer(x)
|
||||
}
|
||||
|
||||
func (r *Review) loadAttributes(e Engine) (err error) {
|
||||
// LoadAttributesX loads all attributes except CodeComments with an Engine parameter
|
||||
func (r *Review) LoadAttributesX(e Engine) (err error) {
|
||||
if err = r.loadIssue(e); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -125,7 +126,7 @@ func (r *Review) loadAttributes(e Engine) (err error) {
|
||||
|
||||
// LoadAttributes loads all attributes except CodeComments
|
||||
func (r *Review) LoadAttributes() error {
|
||||
return r.loadAttributes(x)
|
||||
return r.LoadAttributesX(x)
|
||||
}
|
||||
|
||||
func getReviewByID(e Engine, id int64) (*Review, error) {
|
||||
@@ -203,6 +204,12 @@ func IsOfficialReviewer(issue *Issue, reviewer *User) (bool, error) {
|
||||
return isOfficialReviewer(x, issue, reviewer)
|
||||
}
|
||||
|
||||
// IsOfficialReviewerX check if reviewer can make official reviews in issue (counts towards required approvals)
|
||||
// with an Engine parameter
|
||||
func IsOfficialReviewerX(e Engine, issue *Issue, reviewer *User) (bool, error) {
|
||||
return isOfficialReviewer(x, issue, reviewer)
|
||||
}
|
||||
|
||||
func isOfficialReviewer(e Engine, issue *Issue, reviewer *User) (bool, error) {
|
||||
pr, err := getPullRequestByIssueID(e, issue.ID)
|
||||
if err != nil {
|
||||
|
||||
+15
-5
@@ -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)
|
||||
@@ -1418,11 +1418,21 @@ func getUserEmailsByNames(e Engine, names []string) []string {
|
||||
}
|
||||
|
||||
// GetMaileableUsersByIDs gets users from ids, but only if they can receive mails
|
||||
func GetMaileableUsersByIDs(ids []int64) ([]*User, error) {
|
||||
func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ous := make([]*User, 0, len(ids))
|
||||
|
||||
if isMention {
|
||||
return ous, x.In("id", ids).
|
||||
Where("`type` = ?", UserTypeIndividual).
|
||||
And("`prohibit_login` = ?", false).
|
||||
And("`is_active` = ?", true).
|
||||
And("`email_notifications_preference` IN ( ?, ?)", EmailNotificationsEnabled, EmailNotificationsOnMention).
|
||||
Find(&ous)
|
||||
}
|
||||
|
||||
return ous, x.In("id", ids).
|
||||
Where("`type` = ?", UserTypeIndividual).
|
||||
And("`prohibit_login` = ?", false).
|
||||
|
||||
@@ -389,3 +389,20 @@ func TestGetUserIDsByNames(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, []int64(nil), IDs)
|
||||
}
|
||||
|
||||
func TestGetMaileableUsersByIDs(t *testing.T) {
|
||||
results, err := GetMaileableUsersByIDs([]int64{1, 4}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(results))
|
||||
if len(results) > 1 {
|
||||
assert.Equal(t, results[0].ID, 1)
|
||||
}
|
||||
|
||||
results, err = GetMaileableUsersByIDs([]int64{1, 4}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(results))
|
||||
if len(results) > 2 {
|
||||
assert.Equal(t, results[0].ID, 1)
|
||||
assert.Equal(t, results[1].ID, 4)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -89,6 +89,6 @@ func Prepare(data []byte) (*image.Image, error) {
|
||||
}
|
||||
}
|
||||
|
||||
img = resize.Resize(AvatarSize, AvatarSize, img, resize.NearestNeighbor)
|
||||
img = resize.Resize(AvatarSize, AvatarSize, img, resize.Bilinear)
|
||||
return &img, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -67,8 +68,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
|
||||
@@ -331,9 +336,11 @@ func ToTeam(team *models.Team) *api.Team {
|
||||
// signed shall only be set if requester is logged in. authed shall only be set if user is site admin or user himself
|
||||
func ToUser(user *models.User, signed, authed bool) *api.User {
|
||||
result := &api.User{
|
||||
ID: user.ID,
|
||||
UserName: user.Name,
|
||||
AvatarURL: user.AvatarLink(),
|
||||
FullName: markup.Sanitize(user.FullName),
|
||||
Email: user.GetEmail(),
|
||||
AvatarURL: user.AvatarLink(),
|
||||
Created: user.CreatedUnix.AsTime(),
|
||||
}
|
||||
// hide primary email if API caller is anonymous or user keep email private
|
||||
@@ -342,7 +349,6 @@ func ToUser(user *models.User, signed, authed bool) *api.User {
|
||||
}
|
||||
// only site admin will get these information and possibly user himself
|
||||
if authed {
|
||||
result.ID = user.ID
|
||||
result.IsAdmin = user.IsAdmin
|
||||
result.LastLogin = user.LastLoginUnix.AsTime()
|
||||
result.Language = user.Language
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ package base
|
||||
|
||||
// Repository defines a standard repository information
|
||||
type Repository struct {
|
||||
Name string
|
||||
Owner string
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
Description string
|
||||
AuthUsername string
|
||||
AuthPassword string
|
||||
CloneURL string
|
||||
OriginalURL string
|
||||
Name string
|
||||
Owner string
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
Description string
|
||||
AuthUsername string
|
||||
AuthPassword string
|
||||
CloneURL string
|
||||
OriginalURL string
|
||||
DefaultBranch string
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ package migrations
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/go-github/v24/github"
|
||||
"github.com/google/go-github/v32/github"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -119,6 +119,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.DefaultBranch = repo.DefaultBranch
|
||||
|
||||
r, err = repository.MigrateRepositoryGitData(g.doer, owner, r, structs.MigrateRepoOption{
|
||||
RepoName: g.repoName,
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -154,14 +154,20 @@ func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) {
|
||||
}
|
||||
g.rate = &resp.Rate
|
||||
|
||||
defaultBranch := ""
|
||||
if gr.DefaultBranch != nil {
|
||||
defaultBranch = *gr.DefaultBranch
|
||||
}
|
||||
|
||||
// convert github repo to stand Repo
|
||||
return &base.Repository{
|
||||
Owner: g.repoOwner,
|
||||
Name: gr.GetName(),
|
||||
IsPrivate: *gr.Private,
|
||||
Description: gr.GetDescription(),
|
||||
OriginalURL: gr.GetHTMLURL(),
|
||||
CloneURL: gr.GetCloneURL(),
|
||||
Owner: g.repoOwner,
|
||||
Name: gr.GetName(),
|
||||
IsPrivate: *gr.Private,
|
||||
Description: gr.GetDescription(),
|
||||
OriginalURL: gr.GetHTMLURL(),
|
||||
CloneURL: gr.GetCloneURL(),
|
||||
DefaultBranch: defaultBranch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -364,7 +370,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 +431,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,
|
||||
},
|
||||
|
||||
@@ -71,11 +71,12 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, &base.Repository{
|
||||
Name: "test_repo",
|
||||
Owner: "go-gitea",
|
||||
Description: "Test repository for testing migration from github to gitea",
|
||||
CloneURL: "https://github.com/go-gitea/test_repo.git",
|
||||
OriginalURL: "https://github.com/go-gitea/test_repo",
|
||||
Name: "test_repo",
|
||||
Owner: "go-gitea",
|
||||
Description: "Test repository for testing migration from github to gitea",
|
||||
CloneURL: "https://github.com/go-gitea/test_repo.git",
|
||||
OriginalURL: "https://github.com/go-gitea/test_repo",
|
||||
DefaultBranch: "master",
|
||||
}, repo)
|
||||
|
||||
topics, err := downloader.GetTopics()
|
||||
|
||||
@@ -157,12 +157,13 @@ func (g *GitlabDownloader) GetRepoInfo() (*base.Repository, error) {
|
||||
|
||||
// convert gitlab repo to stand Repo
|
||||
return &base.Repository{
|
||||
Owner: owner,
|
||||
Name: gr.Name,
|
||||
IsPrivate: private,
|
||||
Description: gr.Description,
|
||||
OriginalURL: gr.WebURL,
|
||||
CloneURL: gr.HTTPURLToRepo,
|
||||
Owner: owner,
|
||||
Name: gr.Name,
|
||||
IsPrivate: private,
|
||||
Description: gr.Description,
|
||||
OriginalURL: gr.WebURL,
|
||||
CloneURL: gr.HTTPURLToRepo,
|
||||
DefaultBranch: gr.DefaultBranch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,12 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
// Repo Owner is blank in Gitlab Group repos
|
||||
assert.EqualValues(t, &base.Repository{
|
||||
Name: "test_repo",
|
||||
Owner: "",
|
||||
Description: "Test repository for testing migration from gitlab to gitea",
|
||||
CloneURL: "https://gitlab.com/gitea/test_repo.git",
|
||||
OriginalURL: "https://gitlab.com/gitea/test_repo",
|
||||
Name: "test_repo",
|
||||
Owner: "",
|
||||
Description: "Test repository for testing migration from gitlab to gitea",
|
||||
CloneURL: "https://gitlab.com/gitea/test_repo.git",
|
||||
OriginalURL: "https://gitlab.com/gitea/test_repo",
|
||||
DefaultBranch: "master",
|
||||
}, repo)
|
||||
|
||||
topics, err := downloader.GetTopics()
|
||||
|
||||
@@ -92,13 +92,22 @@ func (a *actionNotifier) NotifyCreateIssueComment(doer *models.User, repo *model
|
||||
act := &models.Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUser: doer,
|
||||
Content: fmt.Sprintf("%d|%s", issue.Index, comment.Content),
|
||||
RepoID: issue.Repo.ID,
|
||||
Repo: issue.Repo,
|
||||
Comment: comment,
|
||||
CommentID: comment.ID,
|
||||
IsPrivate: issue.Repo.IsPrivate,
|
||||
}
|
||||
|
||||
content := ""
|
||||
|
||||
if len(comment.Content) > 200 {
|
||||
content = comment.Content[:strings.LastIndex(comment.Content[0:200], " ")] + "…"
|
||||
} else {
|
||||
content = comment.Content
|
||||
}
|
||||
act.Content = fmt.Sprintf("%d|%s", issue.Index, content)
|
||||
|
||||
if issue.IsPull {
|
||||
act.OpType = models.ActionCommentPull
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,7 @@ package public
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
@@ -159,7 +160,7 @@ func (opts *Options) handle(ctx *macaron.Context, log *log.Logger, opt *Options)
|
||||
// Add an Expires header to the static content
|
||||
if opt.ExpiresAfter > 0 {
|
||||
ctx.Resp.Header().Set("Expires", time.Now().Add(opt.ExpiresAfter).UTC().Format(http.TimeFormat))
|
||||
tag := GenerateETag(string(fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
tag := GenerateETag(fmt.Sprint(fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
ctx.Resp.Header().Set("ETag", tag)
|
||||
if ctx.Req.Header.Get("If-None-Match") == tag {
|
||||
ctx.Resp.WriteHeader(304)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
@@ -176,7 +177,7 @@ func CommitRepoAction(optsList ...*CommitRepoActionOptions) error {
|
||||
var err error
|
||||
if repo != nil {
|
||||
// Change repository empty status and update last updated time.
|
||||
if err := models.UpdateRepository(repo, false); err != nil {
|
||||
if err := models.UpdateRepositoryUpdatedTime(repo.ID, time.Now()); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -204,6 +205,10 @@ func CommitRepoAction(optsList ...*CommitRepoActionOptions) error {
|
||||
}
|
||||
gitRepo.Close()
|
||||
}
|
||||
// Update the is empty and default_branch columns
|
||||
if err := models.UpdateRepositoryCols(repo, "default_branch", "is_empty"); err != nil {
|
||||
return fmt.Errorf("UpdateRepositoryCols: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
opType := models.ActionCommitRepo
|
||||
@@ -274,7 +279,7 @@ func CommitRepoAction(optsList ...*CommitRepoActionOptions) error {
|
||||
|
||||
if repo != nil {
|
||||
// Change repository empty status and update last updated time.
|
||||
if err := models.UpdateRepository(repo, false); err != nil {
|
||||
if err := models.UpdateRepositoryUpdatedTime(repo.ID, time.Now()); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.DefaultBranch) == 0 {
|
||||
opts.DefaultBranch = setting.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
repo := &models.Repository{
|
||||
OwnerID: u.ID,
|
||||
Owner: u,
|
||||
|
||||
+14
-10
@@ -102,18 +102,22 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt
|
||||
return repo, fmt.Errorf("git.IsEmpty: %v", err)
|
||||
}
|
||||
|
||||
if !opts.Releases && !repo.IsEmpty {
|
||||
// Try to get HEAD branch and set it as default branch.
|
||||
headBranch, err := gitRepo.GetHEADBranch()
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("GetHEADBranch: %v", err)
|
||||
}
|
||||
if headBranch != nil {
|
||||
repo.DefaultBranch = headBranch.Name
|
||||
if !repo.IsEmpty {
|
||||
if len(repo.DefaultBranch) == 0 {
|
||||
// Try to get HEAD branch and set it as default branch.
|
||||
headBranch, err := gitRepo.GetHEADBranch()
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("GetHEADBranch: %v", err)
|
||||
}
|
||||
if headBranch != nil {
|
||||
repo.DefaultBranch = headBranch.Name
|
||||
}
|
||||
}
|
||||
|
||||
if err = SyncReleasesWithTags(repo, gitRepo); err != nil {
|
||||
log.Error("Failed to synchronize tags to releases for repository: %v", err)
|
||||
if !opts.Releases {
|
||||
if err = SyncReleasesWithTags(repo, gitRepo); err != nil {
|
||||
log.Error("Failed to synchronize tags to releases for repository: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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":
|
||||
|
||||
@@ -23,7 +23,7 @@ type Cache struct {
|
||||
var (
|
||||
// CacheService the global cache
|
||||
CacheService = struct {
|
||||
Cache
|
||||
Cache `ini:"cache"`
|
||||
|
||||
LastCommit struct {
|
||||
Enabled bool
|
||||
|
||||
+20
-2
@@ -4,8 +4,26 @@
|
||||
|
||||
package setting
|
||||
|
||||
import "reflect"
|
||||
|
||||
// GetCronSettings maps the cron subsection to the provided config
|
||||
func GetCronSettings(name string, config interface{}) (interface{}, error) {
|
||||
err := Cfg.Section("cron." + name).MapTo(config)
|
||||
return config, err
|
||||
if err := Cfg.Section("cron." + name).MapTo(config); err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
typ := reflect.TypeOf(config).Elem()
|
||||
val := reflect.ValueOf(config).Elem()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
tpField := typ.Field(i)
|
||||
if tpField.Type.Kind() == reflect.Struct && tpField.Anonymous {
|
||||
if err := Cfg.Section("cron." + name).MapTo(field.Addr().Interface()); err != nil {
|
||||
return config, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
ini "gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func Test_GetCronSettings(t *testing.T) {
|
||||
|
||||
type BaseStruct struct {
|
||||
Base bool
|
||||
Second string
|
||||
}
|
||||
|
||||
type Extended struct {
|
||||
BaseStruct
|
||||
Extend bool
|
||||
}
|
||||
|
||||
iniStr := `
|
||||
[cron.test]
|
||||
Base = true
|
||||
Second = white rabbit
|
||||
Extend = true
|
||||
`
|
||||
Cfg, _ = ini.Load([]byte(iniStr))
|
||||
|
||||
extended := &Extended{
|
||||
BaseStruct: BaseStruct{
|
||||
Second: "queen of hearts",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := GetCronSettings("test", extended)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, extended.Base)
|
||||
assert.EqualValues(t, extended.Second, "white rabbit")
|
||||
assert.True(t, extended.Extend)
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
@@ -1029,8 +1032,8 @@ func NewContext() {
|
||||
newMarkup()
|
||||
|
||||
sec = Cfg.Section("U2F")
|
||||
U2F.TrustedFacets, _ = shellquote.Split(sec.Key("TRUSTED_FACETS").MustString(strings.TrimRight(AppURL, "/")))
|
||||
U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimRight(AppURL, "/"))
|
||||
U2F.TrustedFacets, _ = shellquote.Split(sec.Key("TRUSTED_FACETS").MustString(strings.TrimSuffix(AppURL, AppSubURL+"/")))
|
||||
U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
|
||||
|
||||
zip.Verbose = false
|
||||
|
||||
|
||||
@@ -619,7 +619,7 @@ func ActionContent2Commits(act Actioner) *repository.PushCommits {
|
||||
// DiffTypeToStr returns diff type name
|
||||
func DiffTypeToStr(diffType int) string {
|
||||
diffTypes := map[int]string{
|
||||
1: "add", 2: "modify", 3: "del", 4: "rename",
|
||||
1: "add", 2: "modify", 3: "del", 4: "rename", 5: "copy",
|
||||
}
|
||||
return diffTypes[diffType]
|
||||
}
|
||||
|
||||
Generated
+5
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -84,7 +84,7 @@ func ListUserOrgs(ctx *context.APIContext) {
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
listUserOrgs(ctx, u, ctx.User.IsAdmin)
|
||||
listUserOrgs(ctx, u, ctx.User != nil && (ctx.User.IsAdmin || ctx.User.ID == u.ID))
|
||||
}
|
||||
|
||||
// GetAll return list of all public organizations
|
||||
|
||||
@@ -256,6 +256,12 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR
|
||||
return
|
||||
}
|
||||
|
||||
// reload repo from db to get a real state after creation
|
||||
repo, err = models.GetRepositoryByID(repo.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeOwner))
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
@@ -1954,7 +1956,7 @@ func updateAttachments(item interface{}, files []string) error {
|
||||
case *models.Comment:
|
||||
attachments = content.Attachments
|
||||
default:
|
||||
return fmt.Errorf("Unknow Type")
|
||||
return fmt.Errorf("Unknown Type: %T", content)
|
||||
}
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
if util.IsStringInSlice(attachments[i].UUID, files) {
|
||||
@@ -1972,7 +1974,7 @@ func updateAttachments(item interface{}, files []string) error {
|
||||
case *models.Comment:
|
||||
err = content.UpdateAttachments(files)
|
||||
default:
|
||||
return fmt.Errorf("Unknow Type")
|
||||
return fmt.Errorf("Unknown Type: %T", content)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1984,7 +1986,7 @@ func updateAttachments(item interface{}, files []string) error {
|
||||
case *models.Comment:
|
||||
content.Attachments, err = models.GetAttachmentsByCommentID(content.ID)
|
||||
default:
|
||||
return fmt.Errorf("Unknow Type")
|
||||
return fmt.Errorf("Unknown Type: %T", content)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func SingleRelease(ctx *context.Context) {
|
||||
writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases)
|
||||
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived
|
||||
|
||||
release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag"))
|
||||
release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("*"))
|
||||
if err != nil {
|
||||
if models.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound("GetRelease", err)
|
||||
|
||||
@@ -185,8 +185,8 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
|
||||
|
||||
address = u.String()
|
||||
|
||||
if err := mirror_service.SaveAddress(ctx.Repo.Mirror, address); err != nil {
|
||||
ctx.ServerError("SaveAddress", err)
|
||||
if err := mirror_service.UpdateAddress(ctx.Repo.Mirror, address); err != nil {
|
||||
ctx.ServerError("UpdateAddress", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -814,9 +814,9 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Group("/releases", func() {
|
||||
m.Get("/", repo.Releases)
|
||||
m.Get("/tag/:tag", repo.SingleRelease)
|
||||
m.Get("/tag/*", repo.SingleRelease)
|
||||
m.Get("/latest", repo.LatestRelease)
|
||||
}, repo.MustBeNotEmpty, context.RepoRef())
|
||||
}, repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag))
|
||||
m.Group("/releases", func() {
|
||||
m.Get("/new", repo.NewRelease)
|
||||
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
||||
|
||||
@@ -174,6 +174,7 @@ func NotificationStatusPost(c *context.Context) {
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["Link"] = fmt.Sprintf("%snotifications", setting.AppURL)
|
||||
|
||||
c.HTML(http.StatusOK, tplNotificationDiv)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package user
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -271,8 +272,8 @@ func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
|
||||
ctx.Data["Application"] = app
|
||||
ctx.Data["RedirectURI"] = form.RedirectURI
|
||||
ctx.Data["State"] = form.State
|
||||
ctx.Data["ApplicationUserLink"] = "<a href=\"" + setting.AppURL + app.User.LowerName + "\">@" + app.User.Name + "</a>"
|
||||
ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + form.RedirectURI + "</strong>"
|
||||
ctx.Data["ApplicationUserLink"] = "<a href=\"" + html.EscapeString(setting.AppURL) + html.EscapeString(url.PathEscape(app.User.LowerName)) + "\">@" + html.EscapeString(app.User.Name) + "</a>"
|
||||
ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + html.EscapeString(form.RedirectURI) + "</strong>"
|
||||
// TODO document SESSION <=> FORM
|
||||
err = ctx.Session.Set("client_id", app.ClientID)
|
||||
if err != nil {
|
||||
|
||||
+105
-59
@@ -18,7 +18,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
@@ -54,6 +53,7 @@ const (
|
||||
DiffFileChange
|
||||
DiffFileDel
|
||||
DiffFileRename
|
||||
DiffFileCopy
|
||||
)
|
||||
|
||||
// DiffLineExpandDirection represents the DiffLineSection expand direction
|
||||
@@ -448,7 +448,46 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
|
||||
}
|
||||
line := linebuf.String()
|
||||
|
||||
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 {
|
||||
if strings.HasPrefix(line, "--- ") {
|
||||
if line[4] == '"' {
|
||||
fmt.Sscanf(line[4:], "%q", &curFile.OldName)
|
||||
} else {
|
||||
curFile.OldName = line[4:]
|
||||
if strings.Contains(curFile.OldName, " ") {
|
||||
// Git adds a terminal \t if there is a space in the name
|
||||
curFile.OldName = curFile.OldName[:len(curFile.OldName)-1]
|
||||
}
|
||||
}
|
||||
if curFile.OldName[0:2] == "a/" {
|
||||
curFile.OldName = curFile.OldName[2:]
|
||||
}
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "+++ ") {
|
||||
if line[4] == '"' {
|
||||
fmt.Sscanf(line[4:], "%q", &curFile.Name)
|
||||
} else {
|
||||
curFile.Name = line[4:]
|
||||
if strings.Contains(curFile.Name, " ") {
|
||||
// Git adds a terminal \t if there is a space in the name
|
||||
curFile.Name = curFile.Name[:len(curFile.Name)-1]
|
||||
}
|
||||
}
|
||||
if curFile.Name[0:2] == "b/" {
|
||||
curFile.Name = curFile.Name[2:]
|
||||
}
|
||||
curFile.IsRenamed = (curFile.Name != curFile.OldName) && !(curFile.IsCreated || curFile.IsDeleted)
|
||||
if curFile.IsDeleted {
|
||||
curFile.Name = curFile.OldName
|
||||
curFile.OldName = ""
|
||||
} else if curFile.IsCreated {
|
||||
curFile.OldName = ""
|
||||
}
|
||||
continue
|
||||
} else if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "+++") || strings.HasPrefix(line, "---") || len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -532,48 +571,10 @@ 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/`)
|
||||
} else {
|
||||
middle = strings.Index(line, " b/")
|
||||
}
|
||||
|
||||
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:]
|
||||
|
||||
}
|
||||
|
||||
curFile = &DiffFile{
|
||||
Name: b,
|
||||
OldName: a,
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DiffFileChange,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
IsRenamed: a != b,
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DiffFileChange,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
}
|
||||
diff.Files = append(diff.Files, curFile)
|
||||
curFileLinesCount = 0
|
||||
@@ -582,6 +583,7 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
|
||||
curFileLFSPrefix = false
|
||||
|
||||
// Check file diff type and is submodule.
|
||||
loop:
|
||||
for {
|
||||
line, err := input.ReadString('\n')
|
||||
if err != nil {
|
||||
@@ -592,23 +594,67 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(line, "new file"):
|
||||
curFile.Type = DiffFileAdd
|
||||
curFile.IsCreated = true
|
||||
case strings.HasPrefix(line, "deleted"):
|
||||
curFile.Type = DiffFileDel
|
||||
curFile.IsDeleted = true
|
||||
case strings.HasPrefix(line, "index"):
|
||||
curFile.Type = DiffFileChange
|
||||
case strings.HasPrefix(line, "similarity index 100%"):
|
||||
curFile.Type = DiffFileRename
|
||||
}
|
||||
if curFile.Type > 0 {
|
||||
if strings.HasSuffix(line, " 160000\n") {
|
||||
curFile.IsSubmodule = true
|
||||
if curFile.Type != DiffFileRename {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "new file"):
|
||||
curFile.Type = DiffFileAdd
|
||||
curFile.IsCreated = true
|
||||
case strings.HasPrefix(line, "deleted"):
|
||||
curFile.Type = DiffFileDel
|
||||
curFile.IsDeleted = true
|
||||
case strings.HasPrefix(line, "index"):
|
||||
curFile.Type = DiffFileChange
|
||||
case strings.HasPrefix(line, "similarity index 100%"):
|
||||
curFile.Type = DiffFileRename
|
||||
}
|
||||
if curFile.Type > 0 && curFile.Type != DiffFileRename {
|
||||
if strings.HasSuffix(line, " 160000\n") {
|
||||
curFile.IsSubmodule = true
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "rename from "):
|
||||
if line[12] == '"' {
|
||||
fmt.Sscanf(line[12:], "%q", &curFile.OldName)
|
||||
} else {
|
||||
curFile.OldName = line[12:]
|
||||
curFile.OldName = curFile.OldName[:len(curFile.OldName)-1]
|
||||
}
|
||||
case strings.HasPrefix(line, "rename to "):
|
||||
if line[10] == '"' {
|
||||
fmt.Sscanf(line[10:], "%q", &curFile.Name)
|
||||
} else {
|
||||
curFile.Name = line[10:]
|
||||
curFile.Name = curFile.Name[:len(curFile.Name)-1]
|
||||
}
|
||||
curFile.IsRenamed = true
|
||||
break loop
|
||||
case strings.HasPrefix(line, "copy from "):
|
||||
if line[10] == '"' {
|
||||
fmt.Sscanf(line[10:], "%q", &curFile.OldName)
|
||||
} else {
|
||||
curFile.OldName = line[10:]
|
||||
curFile.OldName = curFile.OldName[:len(curFile.OldName)-1]
|
||||
}
|
||||
case strings.HasPrefix(line, "copy to "):
|
||||
if line[8] == '"' {
|
||||
fmt.Sscanf(line[8:], "%q", &curFile.Name)
|
||||
} else {
|
||||
curFile.Name = line[8:]
|
||||
curFile.Name = curFile.Name[:len(curFile.Name)-1]
|
||||
}
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileCopy
|
||||
break loop
|
||||
default:
|
||||
if strings.HasSuffix(line, " 160000\n") {
|
||||
curFile.IsSubmodule = true
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package gitdiff
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
@@ -41,7 +42,145 @@ func TestDiffToHTML(t *testing.T) {
|
||||
}, DiffLineDel))
|
||||
}
|
||||
|
||||
func TestParsePatch(t *testing.T) {
|
||||
func TestParsePatch_singlefile(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
gitdiff string
|
||||
wantErr bool
|
||||
addition int
|
||||
deletion int
|
||||
oldFilename string
|
||||
filename string
|
||||
}
|
||||
|
||||
tests := []testcase{
|
||||
{
|
||||
name: "readme.md2readme.md",
|
||||
gitdiff: `diff --git "a/README.md" "b/README.md"
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -1,3 +1,6 @@
|
||||
# gitea-github-migrator
|
||||
+
|
||||
+ Build Status
|
||||
- Latest Release
|
||||
Docker Pulls
|
||||
+ cut off
|
||||
+ cut off
|
||||
`,
|
||||
addition: 4,
|
||||
deletion: 1,
|
||||
filename: "README.md",
|
||||
},
|
||||
{
|
||||
name: "A \\ B",
|
||||
gitdiff: `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`,
|
||||
addition: 4,
|
||||
deletion: 1,
|
||||
filename: "A \\ B",
|
||||
},
|
||||
{
|
||||
name: "really weird filename",
|
||||
gitdiff: `diff --git a/a b/file b/a a/file b/a b/file b/a a/file
|
||||
index d2186f1..f5c8ed2 100644
|
||||
--- a/a b/file b/a a/file
|
||||
+++ b/a b/file b/a a/file
|
||||
@@ -1,3 +1,2 @@
|
||||
Create a weird file.
|
||||
|
||||
-and what does diff do here?
|
||||
\ No newline at end of file`,
|
||||
addition: 0,
|
||||
deletion: 1,
|
||||
filename: "a b/file b/a a/file",
|
||||
oldFilename: "a b/file b/a a/file",
|
||||
},
|
||||
{
|
||||
name: "delete file with blanks",
|
||||
gitdiff: `diff --git a/file with blanks b/file with blanks
|
||||
deleted file mode 100644
|
||||
index 898651a..0000000
|
||||
--- a/file with blanks
|
||||
+++ /dev/null
|
||||
@@ -1,5 +0,0 @@
|
||||
-a blank file
|
||||
-
|
||||
-has a couple o line
|
||||
-
|
||||
-the 5th line is the last
|
||||
`,
|
||||
addition: 0,
|
||||
deletion: 5,
|
||||
filename: "file with blanks",
|
||||
},
|
||||
{
|
||||
name: "rename a—as",
|
||||
gitdiff: `diff --git "a/\360\243\220\265b\342\200\240vs" "b/a\342\200\224as"
|
||||
similarity index 100%
|
||||
rename from "\360\243\220\265b\342\200\240vs"
|
||||
rename to "a\342\200\224as"
|
||||
`,
|
||||
addition: 0,
|
||||
deletion: 0,
|
||||
oldFilename: "𣐵b†vs",
|
||||
filename: "a—as",
|
||||
},
|
||||
{
|
||||
name: "rename with spaces",
|
||||
gitdiff: `diff --git a/a b/file b/a a/file b/a b/a a/file b/b file
|
||||
similarity index 100%
|
||||
rename from a b/file b/a a/file
|
||||
rename to a b/a a/file b/b file
|
||||
`,
|
||||
oldFilename: "a b/file b/a a/file",
|
||||
filename: "a b/a a/file b/b file",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range tests {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
got, err := ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff))
|
||||
if (err != nil) != testcase.wantErr {
|
||||
t.Errorf("ParsePatch() error = %v, wantErr %v", err, testcase.wantErr)
|
||||
return
|
||||
}
|
||||
gotMarshaled, _ := json.MarshalIndent(got, " ", " ")
|
||||
if got.NumFiles() != 1 {
|
||||
t.Errorf("ParsePath() did not receive 1 file:\n%s", string(gotMarshaled))
|
||||
return
|
||||
}
|
||||
if got.TotalAddition != testcase.addition {
|
||||
t.Errorf("ParsePath() does not have correct totalAddition %d, wanted %d", got.TotalAddition, testcase.addition)
|
||||
}
|
||||
if got.TotalDeletion != testcase.deletion {
|
||||
t.Errorf("ParsePath() did not have correct totalDeletion %d, wanted %d", got.TotalDeletion, testcase.deletion)
|
||||
}
|
||||
file := got.Files[0]
|
||||
if file.Addition != testcase.addition {
|
||||
t.Errorf("ParsePath() does not have correct file addition %d, wanted %d", file.Addition, testcase.addition)
|
||||
}
|
||||
if file.Deletion != testcase.deletion {
|
||||
t.Errorf("ParsePath() did not have correct file deletion %d, wanted %d", file.Deletion, testcase.deletion)
|
||||
}
|
||||
if file.OldName != testcase.oldFilename {
|
||||
t.Errorf("ParsePath() did not have correct OldName %s, wanted %s", file.OldName, testcase.oldFilename)
|
||||
}
|
||||
if file.Name != testcase.filename {
|
||||
t.Errorf("ParsePath() did not have correct Name %s, wanted %s", file.Name, testcase.filename)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var diff = `diff --git "a/README.md" "b/README.md"
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -76,6 +215,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
|
||||
|
||||
@@ -118,7 +118,7 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int
|
||||
visited[id] = true
|
||||
}
|
||||
}
|
||||
recipients, err := models.GetMaileableUsersByIDs(unique)
|
||||
recipients, err := models.GetMaileableUsersByIDs(unique, fromMention)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ func AddressNoCredentials(m *models.Mirror) string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// SaveAddress writes new address to Git repository config.
|
||||
func SaveAddress(m *models.Mirror, addr string) error {
|
||||
// UpdateAddress writes new address to Git repository and database
|
||||
func UpdateAddress(m *models.Mirror, addr string) error {
|
||||
repoPath := m.Repo.RepoPath()
|
||||
// Remove old origin
|
||||
_, err := git.NewCommand("remote", "rm", "origin").RunInDir(repoPath)
|
||||
@@ -99,8 +99,12 @@ func SaveAddress(m *models.Mirror, addr string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = git.NewCommand("remote", "add", "origin", "--mirror=fetch", addr).RunInDir(repoPath)
|
||||
return err
|
||||
if _, err = git.NewCommand("remote", "add", "origin", "--mirror=fetch", addr).RunInDir(repoPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Repo.OriginalURL = addr
|
||||
return models.UpdateRepositoryCols(m.Repo, "original_url")
|
||||
}
|
||||
|
||||
// gitShortEmptySha Git short empty SHA
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+19
-60
@@ -9,8 +9,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -177,18 +175,6 @@ func checkForInvalidation(requests models.PullRequestList, repoID int64, doer *m
|
||||
return nil
|
||||
}
|
||||
|
||||
func addHeadRepoTasks(prs []*models.PullRequest) {
|
||||
for _, pr := range prs {
|
||||
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
|
||||
if err := PushToBaseRepo(pr); err != nil {
|
||||
log.Error("PushToBaseRepo: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
AddToTaskQueue(pr)
|
||||
}
|
||||
}
|
||||
|
||||
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
|
||||
// and generate new patch for testing as needed.
|
||||
func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
|
||||
@@ -245,7 +231,15 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy
|
||||
}
|
||||
}
|
||||
|
||||
addHeadRepoTasks(prs)
|
||||
for _, pr := range prs {
|
||||
log.Trace("Updating PR[%d]: composing new test task", pr.ID)
|
||||
if err := PushToBaseRepo(pr); err != nil {
|
||||
log.Error("PushToBaseRepo: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
AddToTaskQueue(pr)
|
||||
}
|
||||
|
||||
log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
|
||||
prs, err = models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
|
||||
@@ -345,54 +339,17 @@ func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID st
|
||||
func PushToBaseRepo(pr *models.PullRequest) (err error) {
|
||||
log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName())
|
||||
|
||||
// Clone base repo.
|
||||
tmpBasePath, err := models.CreateTemporaryPath("pull")
|
||||
if err != nil {
|
||||
log.Error("CreateTemporaryPath: %v", err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err := models.RemoveTemporaryPath(tmpBasePath)
|
||||
if err != nil {
|
||||
log.Error("Error whilst removing temporary path: %s Error: %v", tmpBasePath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := pr.LoadHeadRepo(); err != nil {
|
||||
log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
|
||||
return err
|
||||
}
|
||||
headRepoPath := pr.HeadRepo.RepoPath()
|
||||
|
||||
if err := git.Clone(headRepoPath, tmpBasePath, git.CloneRepoOptions{
|
||||
Bare: true,
|
||||
Shared: true,
|
||||
Branch: pr.HeadBranch,
|
||||
Quiet: true,
|
||||
}); err != nil {
|
||||
log.Error("git clone tmpBasePath: %v", err)
|
||||
return err
|
||||
}
|
||||
gitRepo, err := git.OpenRepository(tmpBasePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
|
||||
if err := pr.LoadBaseRepo(); err != nil {
|
||||
log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
|
||||
return err
|
||||
}
|
||||
if err := gitRepo.AddRemote("base", pr.BaseRepo.RepoPath(), false); err != nil {
|
||||
return fmt.Errorf("tmpGitRepo.AddRemote: %v", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
headFile := pr.GetGitRefName()
|
||||
|
||||
// Remove head in case there is a conflict.
|
||||
file := path.Join(pr.BaseRepo.RepoPath(), headFile)
|
||||
|
||||
_ = os.Remove(file)
|
||||
baseRepoPath := pr.BaseRepo.RepoPath()
|
||||
|
||||
if err = pr.LoadIssue(); err != nil {
|
||||
return fmt.Errorf("unable to load issue %d for pr %d: %v", pr.IssueID, pr.ID, err)
|
||||
@@ -401,24 +358,26 @@ func PushToBaseRepo(pr *models.PullRequest) (err error) {
|
||||
return fmt.Errorf("unable to load poster %d for pr %d: %v", pr.Issue.PosterID, pr.ID, err)
|
||||
}
|
||||
|
||||
if err = git.Push(tmpBasePath, git.PushOptions{
|
||||
Remote: "base",
|
||||
Branch: fmt.Sprintf("%s:%s", pr.HeadBranch, headFile),
|
||||
gitRefName := pr.GetGitRefName()
|
||||
|
||||
if err := git.Push(headRepoPath, git.PushOptions{
|
||||
Remote: baseRepoPath,
|
||||
Branch: pr.HeadBranch + ":" + gitRefName,
|
||||
Force: true,
|
||||
// Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
|
||||
Env: models.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
|
||||
}); err != nil {
|
||||
if git.IsErrPushOutOfDate(err) {
|
||||
// This should not happen as we're using force!
|
||||
log.Error("Unable to push PR head for %s#%d (%-v:%s) due to ErrPushOfDate: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, headFile, err)
|
||||
log.Error("Unable to push PR head for %s#%d (%-v:%s) due to ErrPushOfDate: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err)
|
||||
return err
|
||||
} else if git.IsErrPushRejected(err) {
|
||||
rejectErr := err.(*git.ErrPushRejected)
|
||||
log.Info("Unable to push PR head for %s#%d (%-v:%s) due to rejection:\nStdout: %s\nStderr: %s\nError: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, headFile, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
|
||||
log.Info("Unable to push PR head for %s#%d (%-v:%s) due to rejection:\nStdout: %s\nStderr: %s\nError: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
|
||||
return err
|
||||
}
|
||||
log.Error("Unable to push PR head for %s#%d (%-v:%s) due to Error: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, headFile, err)
|
||||
return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), headFile, err)
|
||||
log.Error("Unable to push PR head for %s#%d (%-v:%s) due to Error: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err)
|
||||
return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}}<{{.Commit.Author.Email}}>{{end}}</a>
|
||||
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a>
|
||||
{{else}}
|
||||
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{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> <{{.Commit.Committer.Email}}></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}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{range .Attachments}}
|
||||
{{- range .Attachments -}}
|
||||
<div class="twelve wide column" style="padding: 6px;">
|
||||
<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}" title='{{$.ctx.i18n.Tr "repo.issues.attachment.open_tab" .Name}}'>
|
||||
{{if FilenameIsImage .Name}}
|
||||
@@ -12,4 +12,4 @@
|
||||
<div class="four wide column" style="padding: 0px;">
|
||||
<span class="ui text grey right">{{.Size | FileSize}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end -}}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<span class="text grey">
|
||||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
||||
{{$link := printf "%s/commit/%s" $.Repository.HTMLURL $.Issue.PullRequest.MergedCommitID}}
|
||||
{{$.i18n.Tr "repo.issues.pull_merged_at" $link (ShortSha $.Issue.PullRequest.MergedCommitID) $.BaseTarget $createdStr | Str2html}}
|
||||
{{$.i18n.Tr "repo.issues.pull_merged_at" $link (ShortSha $.Issue.PullRequest.MergedCommitID) ($.BaseTarget|Escape) $createdStr | Str2html}}
|
||||
</span>
|
||||
</div>
|
||||
{{else if eq .Type 3 5 6}}
|
||||
@@ -163,7 +163,7 @@
|
||||
</a>
|
||||
<span class="text grey">
|
||||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
||||
{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape|RenderEmoji) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape|RenderEmoji) $createdStr | Safe}}{{end}}
|
||||
{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|RenderEmoji) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|RenderEmoji) $createdStr | Safe}}{{end}}
|
||||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -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}}">
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
{{else if .IsPullWorkInProgress}}
|
||||
<div class="item text grey">
|
||||
<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i>
|
||||
{{$.i18n.Tr "repo.pulls.cannot_merge_work_in_progress" .WorkInProgressPrefix | Str2html}}
|
||||
{{$.i18n.Tr "repo.pulls.cannot_merge_work_in_progress" (.WorkInProgressPrefix|Escape) | Str2html}}
|
||||
</div>
|
||||
{{else if .Issue.PullRequest.IsChecking}}
|
||||
<div class="item text yellow">
|
||||
|
||||
@@ -31,18 +31,18 @@
|
||||
{{ $mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix $.Lang }}
|
||||
{{if .Issue.OriginalAuthor }}
|
||||
{{.Issue.OriginalAuthor}}
|
||||
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}}</span>
|
||||
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits (.HeadTarget|Escape) (.BaseTarget|Escape) $mergedStr | Str2html}}</span>
|
||||
{{else}}
|
||||
<a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.GetDisplayName}}</a>
|
||||
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}}</span>
|
||||
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits (.HeadTarget|Escape) (.BaseTarget|Escape) $mergedStr | Str2html}}</span>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if .Issue.OriginalAuthor }}
|
||||
<span id="pull-desc" class="pull-desc">{{.Issue.OriginalAuthor}} {{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span>
|
||||
<span id="pull-desc" class="pull-desc">{{.Issue.OriginalAuthor}} {{$.i18n.Tr "repo.pulls.title_desc" .NumCommits (.HeadTarget|Escape) (.BaseTarget|Escape) | Str2html}}</span>
|
||||
{{else}}
|
||||
<span id="pull-desc" class="pull-desc">
|
||||
<a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a>
|
||||
{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}
|
||||
{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits (.HeadTarget|Escape) (.BaseTarget|Escape) | Str2html}}
|
||||
</span>
|
||||
{{end}}
|
||||
<span id="pull-desc-edit" style="display: none">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "repo.settings.branch_protection" .Branch.BranchName | Str2html}}
|
||||
{{.i18n.Tr "repo.settings.branch_protection" (.Branch.BranchName|Escape) | Str2html}}
|
||||
</h4>
|
||||
<div class="ui attached segment branch-protection">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
{{else if .ResendLimited}}
|
||||
<p class="center">{{.i18n.Tr "auth.resent_limit_prompt"}}</p>
|
||||
{{else}}
|
||||
<p>{{.i18n.Tr "auth.confirmation_mail_sent_prompt" .SignedUser.Email .ActiveCodeLives | Str2html}}</p>
|
||||
<p>{{.i18n.Tr "auth.confirmation_mail_sent_prompt" (.SignedUser.Email|Escape) .ActiveCodeLives | Str2html}}</p>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if .IsSendRegisterMail}}
|
||||
<p>{{.i18n.Tr "auth.confirmation_mail_sent_prompt" .Email .ActiveCodeLives | Str2html}}</p>
|
||||
<p>{{.i18n.Tr "auth.confirmation_mail_sent_prompt" (.Email|Escape) .ActiveCodeLives | Str2html}}</p>
|
||||
{{else if .IsActivateFailed}}
|
||||
<p>{{.i18n.Tr "auth.invalid_code"}}</p>
|
||||
{{else}}
|
||||
<p>{{.i18n.Tr "auth.has_unconfirmed_mail" .SignedUser.Name .SignedUser.Email | Str2html}}</p>
|
||||
<p>{{.i18n.Tr "auth.has_unconfirmed_mail" (.SignedUser.Name|Escape) (.SignedUser.Email|Escape) | Str2html}}</p>
|
||||
<div class="ui divider"></div>
|
||||
<div class="text right">
|
||||
<button class="ui blue button">{{.i18n.Tr "auth.resend_mail"}}</button>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -50,17 +50,17 @@
|
||||
{{$.i18n.Tr "action.reopen_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 16}}
|
||||
{{ $index := index .GetIssueInfos 0}}
|
||||
{{$.i18n.Tr "action.delete_tag" .GetRepoLink .GetBranch .ShortRepoPath | Str2html}}
|
||||
{{$.i18n.Tr "action.delete_tag" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 17}}
|
||||
{{ $index := index .GetIssueInfos 0}}
|
||||
{{$.i18n.Tr "action.delete_branch" .GetRepoLink .GetBranch .ShortRepoPath | Str2html}}
|
||||
{{$.i18n.Tr "action.delete_branch" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 18}}
|
||||
{{ $branchLink := .GetBranch | EscapePound}}
|
||||
{{$.i18n.Tr "action.mirror_sync_push" .GetRepoLink $branchLink .GetBranch .ShortRepoPath | Str2html}}
|
||||
{{$.i18n.Tr "action.mirror_sync_push" .GetRepoLink $branchLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 19}}
|
||||
{{$.i18n.Tr "action.mirror_sync_create" .GetRepoLink .GetBranch .ShortRepoPath | Str2html}}
|
||||
{{$.i18n.Tr "action.mirror_sync_create" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 20}}
|
||||
{{$.i18n.Tr "action.mirror_sync_delete" .GetRepoLink .GetBranch .ShortRepoPath | Str2html}}
|
||||
{{$.i18n.Tr "action.mirror_sync_delete" .GetRepoLink (.GetBranch|Escape) .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 21}}
|
||||
{{ $index := index .GetIssueInfos 0}}
|
||||
{{$.i18n.Tr "action.approve_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}
|
||||
@@ -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}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+8
@@ -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)
|
||||
|
||||
+9
-3
@@ -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{})
|
||||
}
|
||||
|
||||
+5
-3
@@ -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)
|
||||
|
||||
+5
-3
@@ -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)
|
||||
|
||||
-230
@@ -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
|
||||
}
|
||||
-457
@@ -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)
|
||||
}
|
||||
-155
@@ -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)
|
||||
}
|
||||
-160
@@ -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)
|
||||
}
|
||||
-174
@@ -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 team’s 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
|
||||
}
|
||||
Generated
Vendored
+67
-1
@@ -9,22 +9,31 @@
|
||||
# their employer, as appropriate).
|
||||
|
||||
178inaba <masahiro.furudate@gmail.com>
|
||||
2BFL <imqksl@gmail.com>
|
||||
413x <dedifferentiator@gmail.com>
|
||||
Abhinav Gupta <mail@abhinavg.net>
|
||||
adrienzieba <adrien.zieba@appdirect.com>
|
||||
Ahmed Hagy <a.akram93@gmail.com>
|
||||
Aidan Steele <aidan.steele@glassechidna.com.au>
|
||||
Ainsley Chong <ainsley.chong@gmail.com>
|
||||
ajz01 <azdenek@yahoo.com>
|
||||
Akeda Bagus <akeda@x-team.com>
|
||||
Akhil Mohan <akhilerm@gmail.com>
|
||||
Alec Thomas <alec@swapoff.org>
|
||||
Aleks Clark <aleks.clark@gmail.com>
|
||||
Alex Bramley <a.bramley@gmail.com>
|
||||
Alex Orr <Alexorr.CSE@gmail.com>
|
||||
Alexander Harkness <me@bearbin.net>
|
||||
Allen Sun <shlallen1990@gmail.com>
|
||||
Amey Sakhadeo <me@ameyms.com>
|
||||
Anders Janmyr <anders@janmyr.com>
|
||||
Andreas Garnæs <https://github.com/andreas>
|
||||
Andrew Ryabchun <aryabchun@mail.ua>
|
||||
Andy Grunwald <andygrunwald@gmail.com>
|
||||
Andy Hume <andyhume@gmail.com>
|
||||
Andy Lindeman <andy@lindeman.io>
|
||||
angie pinilla <angelinepinilla@gmail.com>
|
||||
anjanashenoy <anjanashenoy1@gmail.com>
|
||||
Anshuman Bhartiya <anshuman.bhartiya@gmail.com>
|
||||
Antoine <antoine.tu@mail.mcgill.ca>
|
||||
Antoine Pelisse <apelisse@gmail.com>
|
||||
@@ -33,6 +42,7 @@ appilon <apilon@hashicorp.com>
|
||||
Aravind <aravindkp@outlook.in>
|
||||
Arda Kuyumcu <kuyumcuarda@gmail.com>
|
||||
Arıl Bozoluk <arilbozoluk@hotmail.com>
|
||||
Austin Burdine <acburdine@gmail.com>
|
||||
Austin Dizzy <dizzy@wow.com>
|
||||
Ben Batha <bhbatha@gmail.com>
|
||||
Benjamen Keroack <benjamen@dollarshaveclub.com>
|
||||
@@ -47,13 +57,18 @@ Brandon Cook <phylake@gmail.com>
|
||||
Brian Egizi <brian@mojotech.com>
|
||||
Bryan Boreham <bryan@weave.works>
|
||||
Cami Diez <diezcami@gmail.com>
|
||||
Carl Johnson <me@carlmjohnson.net>
|
||||
Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||
Carlos Tadeu Panato Junior <ctadeu@gmail.com>
|
||||
chandresh-pancholi <chandreshpancholi007@gmail.com>
|
||||
Charles Fenwick Elliott <Charles@FenwickElliott.io>
|
||||
Charlie Yan <charlieyan08@gmail.com>
|
||||
Chris King <chriskingnet@gmail.com>
|
||||
Chris Raborg <craborg57@gmail.com>
|
||||
Chris Roche <chris@vsco.co>
|
||||
Chris Schaefer <chris@dtzq.com>
|
||||
chrisforrette <chris@chrisforrette.com>
|
||||
Christian Muehlhaeuser <muesli@gmail.com>
|
||||
Christoph Sassenberg <defsprite@gmail.com>
|
||||
Colin Misare <github.com/cmisare>
|
||||
Craig Peterson <cpeterson@stackoverflow.com>
|
||||
@@ -61,10 +76,14 @@ Cristian Maglie <c.maglie@bug.st>
|
||||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
Daniel Leavitt <daniel.leavitt@gmail.com>
|
||||
Daniel Nilsson <daniel.nilsson1989@gmail.com>
|
||||
Daoq <masseto2002@gmail.com>
|
||||
Dave Du Cros <davidducros@gmail.com>
|
||||
Dave Henderson <dhenderson@gmail.com>
|
||||
Dave Protasowski <dprotaso@gmail.com>
|
||||
David Deng <daviddengcn@gmail.com>
|
||||
David Jannotta <djannotta@gmail.com>
|
||||
David Ji <github.com/davidji99>
|
||||
David Lopez Reyes <davidlopezre@gmail.com>
|
||||
Davide Zipeto <dawez1@gmail.com>
|
||||
Dennis Webb <dennis@bluesentryit.com>
|
||||
Dhi Aurrahman <diorahman@rockybars.com>
|
||||
@@ -74,22 +93,26 @@ dmnlk <seikima2demon@gmail.com>
|
||||
Don Petersen <don@donpetersen.net>
|
||||
Doug Turner <doug.turner@gmail.com>
|
||||
Drew Fradette <drew.fradette@gmail.com>
|
||||
Eivind <eivindkn@gmail.com>
|
||||
Eli Uriegas <seemethere101@gmail.com>
|
||||
Elliott Beach <elliott2.71828@gmail.com>
|
||||
Emerson Wood <emersonwood94@gmail.com>
|
||||
eperm <staffordworrell@gmail.com>
|
||||
Erick Fejta <erick@fejta.com>
|
||||
erwinvaneyk <erwinvaneyk@gmail.com>
|
||||
Evan Elias <evanjelias@gmail.com>
|
||||
Fabrice <fabrice.vaillant@student.ecp.fr>
|
||||
Felix Geisendörfer <felix@debuggable.com>
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Florian Forster <ff@octo.it>
|
||||
Francesc Gil <xescugil@gmail.com>
|
||||
Francis <hello@francismakes.com>
|
||||
Francisco Guimarães <francisco.cpg@gmail.com>
|
||||
Fredrik Jönsson <fredrik.jonsson@izettle.com>
|
||||
Garrett Squire <garrettsquire@gmail.com>
|
||||
George Kontridze <george.kontridze@gmail.com>
|
||||
Georgy Buranov <gburanov@gmail.com>
|
||||
Glen Mailer <glenjamin@gmail.com>
|
||||
Gnahz <p@oath.pl>
|
||||
Google Inc.
|
||||
Grachev Mikhail <work@mgrachev.com>
|
||||
@@ -99,13 +122,16 @@ Guz Alexander <kalimatas@gmail.com>
|
||||
Guðmundur Bjarni Ólafsson <gudmundur@github.com>
|
||||
Hanno Hecker <hanno.hecker@zalando.de>
|
||||
Hari haran <hariharan.uno@gmail.com>
|
||||
haya14busa <haya14busa@gmail.com>
|
||||
haya14busa <hayabusa1419@gmail.com>
|
||||
Huy Tr <kingbazoka@gmail.com>
|
||||
huydx <doxuanhuy@gmail.com>
|
||||
i2bskn <i2bskn@gmail.com>
|
||||
Ioannis Georgoulas <igeorgoulas21@gmail.com>
|
||||
Isao Jonas <isao.jonas@gmail.com>
|
||||
isqua <isqua@isqua.ru>
|
||||
Jameel Haffejee <RC1140@republiccommandos.co.za>
|
||||
James Cockbain <james.cockbain@ibm.com>
|
||||
Jan Kosecki <jan.kosecki91@gmail.com>
|
||||
Javier Campanini <jcampanini@palantir.com>
|
||||
Jens Rantil <jens.rantil@gmail.com>
|
||||
@@ -117,6 +143,7 @@ Joan Saum <joan.saum@epitech.eu>
|
||||
Joe Tsai <joetsai@digital-static.net>
|
||||
John Barton <jrbarton@gmail.com>
|
||||
John Engelman <john.r.engelman@gmail.com>
|
||||
Jordan Brockopp <jdbro94@gmail.com>
|
||||
Jordan Sussman <jordansail22@gmail.com>
|
||||
Joshua Bezaleel Abednego <joshua.bezaleel@gmail.com>
|
||||
JP Phillips <jonphill9@gmail.com>
|
||||
@@ -124,11 +151,12 @@ jpbelanger-mtl <jp.belanger@gmail.com>
|
||||
Juan Basso <jrbasso@gmail.com>
|
||||
Julien Garcia Gonzalez <garciagonzalez.julien@gmail.com>
|
||||
Julien Rostand <jrostand@users.noreply.github.com>
|
||||
Junya Kono <junya03dance@gmail.com>
|
||||
Justin Abrahms <justin@abrah.ms>
|
||||
Jusung Lee <e.jusunglee@gmail.com>
|
||||
jzhoucliqr <jzhou@cliqr.com>
|
||||
kadern0 <kaderno@gmail.com>
|
||||
Katrina Owen <kytrinyx@github.com>
|
||||
Kautilya Tripathi < tripathi.kautilya@gmail.com>
|
||||
Kautilya Tripathi <tripathi.kautilya@gmail.com>
|
||||
Keita Urashima <ursm@ursm.jp>
|
||||
Kevin Burke <kev@inburke.com>
|
||||
@@ -137,15 +165,21 @@ Kookheon Kwon <kucuny@gmail.com>
|
||||
Krzysztof Kowalczyk <kkowalczyk@gmail.com>
|
||||
Kshitij Saraogi <KshitijSaraogi@gmail.com>
|
||||
kyokomi <kyoko1220adword@gmail.com>
|
||||
Laurent Verdoïa <verdoialaurent@gmail.com>
|
||||
Liam Galvin <liam@liam-galvin.co.uk>
|
||||
Lovro Mažgon <lovro.mazgon@gmail.com>
|
||||
Lucas Alcantara <lucasalcantaraf@gmail.com>
|
||||
Luke Evers <me@lukevers.com>
|
||||
Luke Kysow <lkysow@gmail.com>
|
||||
Luke Roberts <email@luke-roberts.co.uk>
|
||||
Luke Young <luke@hydrantlabs.org>
|
||||
lynn [they] <lynncyrin@gmail.com>
|
||||
Maksim Zhylinski <uzzable@gmail.com>
|
||||
Mark Tareshawty <tarebyte@github.com>
|
||||
Martin-Louis Bright <mlbright@gmail.com>
|
||||
Martins Sipenko <martins.sipenko@gmail.com>
|
||||
Marwan Sulaiman <marwan.sameer@gmail.com>
|
||||
Masayuki Izumi <m@izum.in>
|
||||
Mat Geist <matgeist@gmail.com>
|
||||
Matt <alpmatthew@gmail.com>
|
||||
Matt Brender <mjbrender@gmail.com>
|
||||
@@ -155,6 +189,7 @@ Maxime Bury <maxime.bury@gmail.com>
|
||||
Michael Spiegel <michael.m.spiegel@gmail.com>
|
||||
Michael Tiller <michael.tiller@gmail.com>
|
||||
Michał Glapa <michal.glapa@gmail.com>
|
||||
Nadav Kaner <nadavkaner1@gmail.com>
|
||||
Nathan VanBenschoten <nvanbenschoten@gmail.com>
|
||||
Navaneeth Suresh <navaneeths1998@gmail.com>
|
||||
Neil O'Toole <neilotoole@apache.org>
|
||||
@@ -163,6 +198,7 @@ Nick Spragg <nick.spragg@bbc.co.uk>
|
||||
Nikhita Raghunath <nikitaraghunath@gmail.com>
|
||||
Noah Zoschke <noah+sso2@convox.com>
|
||||
ns-cweber <cweber@narrativescience.com>
|
||||
Ole Orhagen <ole.orhagen@northern.tech>
|
||||
Oleg Kovalov <iamolegkovalov@gmail.com>
|
||||
Ondřej Kupka <ondra.cap@gmail.com>
|
||||
Palash Nigam <npalash25@gmail.com>
|
||||
@@ -170,29 +206,43 @@ Panagiotis Moustafellos <pmoust@gmail.com>
|
||||
Parham Alvani <parham.alvani@gmail.com>
|
||||
Parker Moore <parkrmoore@gmail.com>
|
||||
parkhyukjun89 <park.hyukjun89@gmail.com>
|
||||
Patrick DeVivo <patrick.devivo@gmail.com>
|
||||
Patrick Marabeas <patrick@marabeas.io>
|
||||
Pavel Shtanko <pavel.shtanko@gmail.com>
|
||||
Pete Wagner <thepwagner@github.com>
|
||||
Petr Shevtsov <petr.shevtsov@gmail.com>
|
||||
Pierre Carrier <pierre@meteor.com>
|
||||
Piotr Zurek <p.zurek@gmail.com>
|
||||
Pratik Mallya <pratik.mallya@gmail.com>
|
||||
Qais Patankar <qaisjp@gmail.com>
|
||||
Quang Le Hong <iamquang95@gmail.com>
|
||||
Quentin Leffray <fiahil@gmail.com>
|
||||
Quinn Slack <qslack@qslack.com>
|
||||
Rackspace US, Inc.
|
||||
Radek Simko <radek.simko@gmail.com>
|
||||
Radliński Ignacy <radlinsk@student.agh.edu.pl>
|
||||
Rajat Jindal <rajatjindal83@gmail.com>
|
||||
Rajendra arora <rajendraarora16@yahoo.com>
|
||||
Ranbir Singh <binkkatal.r@gmail.com>
|
||||
Ravi Shekhar Jethani <rsjethani@gmail.com>
|
||||
RaviTeja Pothana <ravi-teja@live.com>
|
||||
rc1140 <jameel@republiccommandos.co.za>
|
||||
Red Hat, Inc.
|
||||
Reinier Timmer <reinier.timmer@ah.nl>
|
||||
Ricco Førgaard <ricco@fiskeben.dk>
|
||||
Rob Figueiredo <robfig@yext.com>
|
||||
Rohit Upadhyay <urohit011@gmail.com>
|
||||
Ronak Jain <ronakjain@outlook.in>
|
||||
Ruben Vereecken <rubenvereecken@gmail.com>
|
||||
Ryan Leung <rleungx@gmail.com>
|
||||
Ryan Lower <rpjlower@gmail.com>
|
||||
Ryo Nakao <nakabonne@gmail.com>
|
||||
Safwan Olaimat <safwan.olaimat@gmail.com>
|
||||
Sahil Dua <sahildua2305@gmail.com>
|
||||
saisi <saisi@users.noreply.github.com>
|
||||
Sam Minnée <sam@silverstripe.com>
|
||||
Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
|
||||
Sander Knape <s.knape88@gmail.com>
|
||||
Sander van Harmelen <svanharmelen@schubergphilis.com>
|
||||
Sanket Payghan <sanket.payghan8@gmail.com>
|
||||
Sarasa Kisaragi <lingsamuelgrace@gmail.com>
|
||||
@@ -200,30 +250,46 @@ Sean Wang <sean@decrypted.org>
|
||||
Sebastian Mandrean <sebastian.mandrean@gmail.com>
|
||||
Sebastian Mæland Pedersen <sem.pedersen@stud.uis.no>
|
||||
Sergey Romanov <xxsmotur@gmail.com>
|
||||
Sergio Garcia <sergio.garcia@gmail.com>
|
||||
Sevki <s@sevki.org>
|
||||
Shagun Khemka <shagun.khemka60@gmail.com>
|
||||
shakeelrao <shakeelrao79@gmail.com>
|
||||
Shawn Catanzarite <me@shawncatz.com>
|
||||
Shawn Smith <shawnpsmith@gmail.com>
|
||||
Shibasis Patel <patelshibasis@gmail.com>
|
||||
Shrikrishna Singh <krishnasingh.ss30@gmail.com>
|
||||
sona-tar <sona.zip@gmail.com>
|
||||
SoundCloud, Ltd.
|
||||
Sridhar Mocherla <srmocher@microsoft.com>
|
||||
SriVignessh Pss <sriknowledge@gmail.com>
|
||||
Stefan Sedich <stefan.sedich@gmail.com>
|
||||
Stian Eikeland <stian@eikeland.se>
|
||||
Suhaib Mujahid <suhaibmujahid@gmail.com>
|
||||
Szymon Kodrebski <simonkey007@gmail.com>
|
||||
Takayuki Watanabe <takanabe.w@gmail.com>
|
||||
Taketoshi Fujiwara <taketoshi.fujiwara@gmail.com>
|
||||
Tasya Aditya Rukmana <tadityar@gmail.com>
|
||||
Thomas Bruyelle <thomas.bruyelle@gmail.com>
|
||||
Timothée Peignier <timothee.peignier@tryphon.org>
|
||||
tkhandel <tarunkhandelwal.iitr@gmail.com>
|
||||
Trey Tacon <ttacon@gmail.com>
|
||||
ttacon <ttacon@gmail.com>
|
||||
Vaibhav Singh <vaibhav.singh.14cse@bml.edu.in>
|
||||
Varadarajan Aravamudhan <varadaraajan@gmail.com>
|
||||
Victor Castell <victor@victorcastell.com>
|
||||
Victor Vrantchan <vrancean+github@gmail.com>
|
||||
vikkyomkar <vikky.omkar@samsung.com>
|
||||
Vlad Ungureanu <vladu@palantir.com>
|
||||
Wasim Thabraze <wasim@thabraze.me>
|
||||
Weslei Juan Moser Pereira <wesleimsr@gmail.com>
|
||||
Will Maier <wcmaier@gmail.com>
|
||||
Willem D'Haeseleer <dhwillem@gmail.com>
|
||||
William Bailey <mail@williambailey.org.uk>
|
||||
William Cooke <pipeston@gmail.com>
|
||||
xibz <impactbchang@gmail.com>
|
||||
Yann Malet <yann.malet@gmail.com>
|
||||
Yannick Utard <yannickutard@gmail.com>
|
||||
Yicheng Qin <qycqycqycqycqyc@gmail.com>
|
||||
Yosuke Akatsuka <yosuke.akatsuka@access-company.com>
|
||||
Yumikiyo Osanai <yumios.art@gmail.com>
|
||||
Zach Latta <zach@zachlatta.com>
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020 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
|
||||
|
||||
// ActionsService handles communication with the actions related
|
||||
// methods of the GitHub API.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/
|
||||
type ActionsService service
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
// Copyright 2020 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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Artifact reprents a GitHub artifact. Artifacts allow sharing
|
||||
// data between jobs in a workflow and provide storage for data
|
||||
// once a workflow is complete.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/artifacts/
|
||||
type Artifact struct {
|
||||
ID *int64 `json:"id,omitempty"`
|
||||
NodeID *string `json:"node_id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
SizeInBytes *int64 `json:"size_in_bytes,omitempty"`
|
||||
ArchiveDownloadURL *string `json:"archive_download_url,omitempty"`
|
||||
Expired *bool `json:"expired,omitempty"`
|
||||
CreatedAt *Timestamp `json:"created_at,omitempty"`
|
||||
ExpiresAt *Timestamp `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
// ArtifactList represents a list of GitHub artifacts.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/artifacts/
|
||||
type ArtifactList struct {
|
||||
TotalCount *int64 `json:"total_count,omitempty"`
|
||||
Artifacts []*Artifact `json:"artifacts,omitempty"`
|
||||
}
|
||||
|
||||
// ListArtifacts lists all artifacts that belong to a repository.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/artifacts/#list-artifacts-for-a-repository
|
||||
func (s *ActionsService) ListArtifacts(ctx context.Context, owner, repo string, opts *ListOptions) (*ArtifactList, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/artifacts", owner, repo)
|
||||
u, err := addOptions(u, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
artifactList := new(ArtifactList)
|
||||
resp, err := s.client.Do(ctx, req, artifactList)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return artifactList, resp, nil
|
||||
}
|
||||
|
||||
// ListWorkflowRunArtifacts lists all artifacts that belong to a workflow run.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/artifacts/#list-workflow-run-artifacts
|
||||
func (s *ActionsService) ListWorkflowRunArtifacts(ctx context.Context, owner, repo string, runID int64, opts *ListOptions) (*ArtifactList, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/artifacts", owner, repo, runID)
|
||||
u, err := addOptions(u, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
artifactList := new(ArtifactList)
|
||||
resp, err := s.client.Do(ctx, req, artifactList)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return artifactList, resp, nil
|
||||
}
|
||||
|
||||
// GetArtifact gets a specific artifact for a workflow run.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/artifacts/#get-an-artifact
|
||||
func (s *ActionsService) GetArtifact(ctx context.Context, owner, repo string, artifactID int64) (*Artifact, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v", owner, repo, artifactID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
artifact := new(Artifact)
|
||||
resp, err := s.client.Do(ctx, req, artifact)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return artifact, resp, nil
|
||||
}
|
||||
|
||||
// DownloadArtifact gets a redirect URL to download an archive for a repository.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/artifacts/#download-an-artifact
|
||||
func (s *ActionsService) DownloadArtifact(ctx context.Context, owner, repo string, artifactID int64, followRedirects bool) (*url.URL, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v/zip", owner, repo, artifactID)
|
||||
|
||||
resp, err := s.getDownloadArtifactFromURL(ctx, u, followRedirects)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusFound {
|
||||
return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
|
||||
}
|
||||
parsedURL, err := url.Parse(resp.Header.Get("Location"))
|
||||
return parsedURL, newResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s *ActionsService) getDownloadArtifactFromURL(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
// Use http.DefaultTransport if no custom Transport is configured
|
||||
req = withContext(ctx, req)
|
||||
if s.client.client.Transport == nil {
|
||||
resp, err = http.DefaultTransport.RoundTrip(req)
|
||||
} else {
|
||||
resp, err = s.client.client.Transport.RoundTrip(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// If redirect response is returned, follow it
|
||||
if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
|
||||
u = resp.Header.Get("Location")
|
||||
resp, err = s.getDownloadArtifactFromURL(ctx, u, false)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DeleteArtifact deletes a workflow run artifact.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/artifacts/#delete-an-artifact
|
||||
func (s *ActionsService) DeleteArtifact(ctx context.Context, owner, repo string, artifactID int64) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v", owner, repo, artifactID)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
// Copyright 2020 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"
|
||||
)
|
||||
|
||||
// RunnerApplicationDownload represents a binary for the self-hosted runner application that can be downloaded.
|
||||
type RunnerApplicationDownload struct {
|
||||
OS *string `json:"os,omitempty"`
|
||||
Architecture *string `json:"architecture,omitempty"`
|
||||
DownloadURL *string `json:"download_url,omitempty"`
|
||||
Filename *string `json:"filename,omitempty"`
|
||||
}
|
||||
|
||||
// ListRunnerApplicationDownloads lists self-hosted runner application binaries that can be downloaded and run.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#list-runner-applications-for-a-repository
|
||||
func (s *ActionsService) ListRunnerApplicationDownloads(ctx context.Context, owner, repo string) ([]*RunnerApplicationDownload, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/runners/downloads", owner, repo)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var rads []*RunnerApplicationDownload
|
||||
resp, err := s.client.Do(ctx, req, &rads)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return rads, resp, nil
|
||||
}
|
||||
|
||||
// RegistrationToken represents a token that can be used to add a self-hosted runner to a repository.
|
||||
type RegistrationToken struct {
|
||||
Token *string `json:"token,omitempty"`
|
||||
ExpiresAt *Timestamp `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
// CreateRegistrationToken creates a token that can be used to add a self-hosted runner.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#create-a-registration-token-for-a-repository
|
||||
func (s *ActionsService) CreateRegistrationToken(ctx context.Context, owner, repo string) (*RegistrationToken, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/runners/registration-token", owner, repo)
|
||||
|
||||
req, err := s.client.NewRequest("POST", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
registrationToken := new(RegistrationToken)
|
||||
resp, err := s.client.Do(ctx, req, registrationToken)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return registrationToken, resp, nil
|
||||
}
|
||||
|
||||
// Runner represents a self-hosted runner registered with a repository.
|
||||
type Runner struct {
|
||||
ID *int64 `json:"id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
OS *string `json:"os,omitempty"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Runners represents a collection of self-hosted runners for a repository.
|
||||
type Runners struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Runners []*Runner `json:"runners"`
|
||||
}
|
||||
|
||||
// ListRunners lists all the self-hosted runners for a repository.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#list-self-hosted-runners-for-a-repository
|
||||
func (s *ActionsService) ListRunners(ctx context.Context, owner, repo string, opts *ListOptions) (*Runners, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/runners", owner, repo)
|
||||
u, err := addOptions(u, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
runners := &Runners{}
|
||||
resp, err := s.client.Do(ctx, req, &runners)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return runners, resp, nil
|
||||
}
|
||||
|
||||
// GetRunner gets a specific self-hosted runner for a repository using its runner ID.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#get-a-self-hosted-runner-for-a-repository
|
||||
func (s *ActionsService) GetRunner(ctx context.Context, owner, repo string, runnerID int64) (*Runner, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/runners/%v", owner, repo, runnerID)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
runner := new(Runner)
|
||||
resp, err := s.client.Do(ctx, req, runner)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return runner, resp, nil
|
||||
}
|
||||
|
||||
// RemoveToken represents a token that can be used to remove a self-hosted runner from a repository.
|
||||
type RemoveToken struct {
|
||||
Token *string `json:"token,omitempty"`
|
||||
ExpiresAt *Timestamp `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
// CreateRemoveToken creates a token that can be used to remove a self-hosted runner from a repository.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#create-a-remove-token-for-a-repository
|
||||
func (s *ActionsService) CreateRemoveToken(ctx context.Context, owner, repo string) (*RemoveToken, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/runners/remove-token", owner, repo)
|
||||
|
||||
req, err := s.client.NewRequest("POST", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
removeToken := new(RemoveToken)
|
||||
resp, err := s.client.Do(ctx, req, removeToken)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return removeToken, resp, nil
|
||||
}
|
||||
|
||||
// RemoveRunner forces the removal of a self-hosted runner in a repository using the runner id.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#delete-a-self-hosted-runner-from-a-repository
|
||||
func (s *ActionsService) RemoveRunner(ctx context.Context, owner, repo string, runnerID int64) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/runners/%v", owner, repo, runnerID)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// ListOrganizationRunnerApplicationDownloads lists self-hosted runner application binaries that can be downloaded and run.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#list-runner-applications-for-an-organization
|
||||
func (s *ActionsService) ListOrganizationRunnerApplicationDownloads(ctx context.Context, owner string) ([]*RunnerApplicationDownload, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/runners/downloads", owner)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var rads []*RunnerApplicationDownload
|
||||
resp, err := s.client.Do(ctx, req, &rads)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return rads, resp, nil
|
||||
}
|
||||
|
||||
// CreateOrganizationRegistrationToken creates a token that can be used to add a self-hosted runner to an organization.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#create-a-registration-token-for-an-organization
|
||||
func (s *ActionsService) CreateOrganizationRegistrationToken(ctx context.Context, owner string) (*RegistrationToken, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/runners/registration-token", owner)
|
||||
|
||||
req, err := s.client.NewRequest("POST", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
registrationToken := new(RegistrationToken)
|
||||
resp, err := s.client.Do(ctx, req, registrationToken)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return registrationToken, resp, nil
|
||||
}
|
||||
|
||||
// ListOrganizationRunners lists all the self-hosted runners for an organization.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#list-self-hosted-runners-for-an-organization
|
||||
func (s *ActionsService) ListOrganizationRunners(ctx context.Context, owner string, opts *ListOptions) (*Runners, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/runners", owner)
|
||||
u, err := addOptions(u, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
runners := &Runners{}
|
||||
resp, err := s.client.Do(ctx, req, &runners)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return runners, resp, nil
|
||||
}
|
||||
|
||||
// GetOrganizationRunner gets a specific self-hosted runner for an organization using its runner ID.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#get-a-self-hosted-runner-for-an-organization
|
||||
func (s *ActionsService) GetOrganizationRunner(ctx context.Context, owner string, runnerID int64) (*Runner, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/runners/%v", owner, runnerID)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
runner := new(Runner)
|
||||
resp, err := s.client.Do(ctx, req, runner)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return runner, resp, nil
|
||||
}
|
||||
|
||||
// CreateOrganizationRemoveToken creates a token that can be used to remove a self-hosted runner from an organization.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#create-a-remove-token-for-an-organization
|
||||
func (s *ActionsService) CreateOrganizationRemoveToken(ctx context.Context, owner string) (*RemoveToken, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/runners/remove-token", owner)
|
||||
|
||||
req, err := s.client.NewRequest("POST", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
removeToken := new(RemoveToken)
|
||||
resp, err := s.client.Do(ctx, req, removeToken)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return removeToken, resp, nil
|
||||
}
|
||||
|
||||
// RemoveOrganizationRunner forces the removal of a self-hosted runner from an organization using the runner id.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/self-hosted-runners/#delete-a-self-hosted-runner-from-an-organization
|
||||
func (s *ActionsService) RemoveOrganizationRunner(ctx context.Context, owner string, runnerID int64) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/runners/%v", owner, runnerID)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
// Copyright 2020 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"
|
||||
)
|
||||
|
||||
// PublicKey represents the public key that should be used to encrypt secrets.
|
||||
type PublicKey struct {
|
||||
KeyID *string `json:"key_id"`
|
||||
Key *string `json:"key"`
|
||||
}
|
||||
|
||||
// GetRepoPublicKey gets a public key that should be used for secret encryption.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-a-repository-public-key
|
||||
func (s *ActionsService) GetRepoPublicKey(ctx context.Context, owner, repo string) (*PublicKey, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/secrets/public-key", owner, repo)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pubKey := new(PublicKey)
|
||||
resp, err := s.client.Do(ctx, req, pubKey)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return pubKey, resp, nil
|
||||
}
|
||||
|
||||
// GetOrgPublicKey gets a public key that should be used for secret encryption.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-an-organization-public-key
|
||||
func (s *ActionsService) GetOrgPublicKey(ctx context.Context, org string) (*PublicKey, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/public-key", org)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pubKey := new(PublicKey)
|
||||
resp, err := s.client.Do(ctx, req, pubKey)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return pubKey, resp, nil
|
||||
}
|
||||
|
||||
// Secret represents a repository action secret.
|
||||
type Secret struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt Timestamp `json:"created_at"`
|
||||
UpdatedAt Timestamp `json:"updated_at"`
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
SelectedRepositoriesURL string `json:"selected_repositories_url,omitempty"`
|
||||
}
|
||||
|
||||
// Secrets represents one item from the ListSecrets response.
|
||||
type Secrets struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Secrets []*Secret `json:"secrets"`
|
||||
}
|
||||
|
||||
// ListRepoSecrets lists all secrets available in a repository
|
||||
// without revealing their encrypted values.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-repository-secrets
|
||||
func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string, opts *ListOptions) (*Secrets, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/secrets", owner, repo)
|
||||
u, err := addOptions(u, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
secrets := new(Secrets)
|
||||
resp, err := s.client.Do(ctx, req, &secrets)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return secrets, resp, nil
|
||||
}
|
||||
|
||||
// GetRepoSecret gets a single repository secret without revealing its encrypted value.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-a-repository-secret
|
||||
func (s *ActionsService) GetRepoSecret(ctx context.Context, owner, repo, name string) (*Secret, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, name)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
secret := new(Secret)
|
||||
resp, err := s.client.Do(ctx, req, secret)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return secret, resp, nil
|
||||
}
|
||||
|
||||
// SelectedRepoIDs are the repository IDs that have access to the secret.
|
||||
type SelectedRepoIDs []int64
|
||||
|
||||
// EncryptedSecret represents a secret that is encrypted using a public key.
|
||||
//
|
||||
// The value of EncryptedValue must be your secret, encrypted with
|
||||
// LibSodium (see documentation here: https://libsodium.gitbook.io/doc/bindings_for_other_languages)
|
||||
// using the public key retrieved using the GetPublicKey method.
|
||||
type EncryptedSecret struct {
|
||||
Name string `json:"-"`
|
||||
KeyID string `json:"key_id"`
|
||||
EncryptedValue string `json:"encrypted_value"`
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
SelectedRepositoryIDs SelectedRepoIDs `json:"selected_repository_ids,omitempty"`
|
||||
}
|
||||
|
||||
// CreateOrUpdateRepoSecret creates or updates a repository secret with an encrypted value.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#create-or-update-a-repository-secret
|
||||
func (s *ActionsService) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *EncryptedSecret) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, eSecret.Name)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", u, eSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// DeleteRepoSecret deletes a secret in a repository using the secret name.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#delete-a-repository-secret
|
||||
func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, name)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// ListOrgSecrets lists all secrets available in an organization
|
||||
// without revealing their encrypted values.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-organization-secrets
|
||||
func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string, opts *ListOptions) (*Secrets, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets", org)
|
||||
u, err := addOptions(u, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
secrets := new(Secrets)
|
||||
resp, err := s.client.Do(ctx, req, &secrets)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return secrets, resp, nil
|
||||
}
|
||||
|
||||
// GetOrgSecret gets a single organization secret without revealing its encrypted value.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#get-an-organization-secret
|
||||
func (s *ActionsService) GetOrgSecret(ctx context.Context, org, name string) (*Secret, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, name)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
secret := new(Secret)
|
||||
resp, err := s.client.Do(ctx, req, secret)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return secret, resp, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateOrgSecret creates or updates an organization secret with an encrypted value.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#create-or-update-an-organization-secret
|
||||
func (s *ActionsService) CreateOrUpdateOrgSecret(ctx context.Context, org string, eSecret *EncryptedSecret) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, eSecret.Name)
|
||||
|
||||
req, err := s.client.NewRequest("PUT", u, eSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// SelectedReposList represents the list of repositories selected for an organization secret.
|
||||
type SelectedReposList struct {
|
||||
TotalCount *int `json:"total_count,omitempty"`
|
||||
Repositories []*Repository `json:"repositories,omitempty"`
|
||||
}
|
||||
|
||||
// ListSelectedReposForOrgSecret lists all repositories that have access to a secret.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#list-selected-repositories-for-an-organization-secret
|
||||
func (s *ActionsService) ListSelectedReposForOrgSecret(ctx context.Context, org, name string) (*SelectedReposList, *Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", org, name)
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
result := new(SelectedReposList)
|
||||
resp, err := s.client.Do(ctx, req, result)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return result, resp, nil
|
||||
}
|
||||
|
||||
// SetSelectedReposForOrgSecret sets the repositories that have access to a secret.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#set-selected-repositories-for-an-organization-secret
|
||||
func (s *ActionsService) SetSelectedReposForOrgSecret(ctx context.Context, org, name string, ids SelectedRepoIDs) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", org, name)
|
||||
|
||||
type repoIDs struct {
|
||||
SelectedIDs SelectedRepoIDs `json:"selected_repository_ids,omitempty"`
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("PUT", u, repoIDs{SelectedIDs: ids})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// AddSelectedRepoToOrgSecret adds a repository to an organization secret.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#add-selected-repository-to-an-organization-secret
|
||||
func (s *ActionsService) AddSelectedRepoToOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", org, name, *repo.ID)
|
||||
req, err := s.client.NewRequest("PUT", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// RemoveSelectedRepoFromOrgSecret removes a repository from an organization secret.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#remove-selected-repository-from-an-organization-secret
|
||||
func (s *ActionsService) RemoveSelectedRepoFromOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", org, name, *repo.ID)
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// DeleteOrgSecret deletes a secret in an organization using the secret name.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/secrets/#delete-an-organization-secret
|
||||
func (s *ActionsService) DeleteOrgSecret(ctx context.Context, org, name string) (*Response, error) {
|
||||
u := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, name)
|
||||
|
||||
req, err := s.client.NewRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.client.Do(ctx, req, nil)
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// Copyright 2020 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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// TaskStep represents a single task step from a sequence of tasks of a job.
|
||||
type TaskStep struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
Conclusion *string `json:"conclusion,omitempty"`
|
||||
Number *int64 `json:"number,omitempty"`
|
||||
StartedAt *Timestamp `json:"started_at,omitempty"`
|
||||
CompletedAt *Timestamp `json:"completed_at,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowJob represents a repository action workflow job.
|
||||
type WorkflowJob struct {
|
||||
ID *int64 `json:"id,omitempty"`
|
||||
RunID *int64 `json:"run_id,omitempty"`
|
||||
RunURL *string `json:"run_url,omitempty"`
|
||||
NodeID *string `json:"node_id,omitempty"`
|
||||
HeadSHA *string `json:"head_sha,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
HTMLURL *string `json:"html_url,omitempty"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
Conclusion *string `json:"conclusion,omitempty"`
|
||||
StartedAt *Timestamp `json:"started_at,omitempty"`
|
||||
CompletedAt *Timestamp `json:"completed_at,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Steps []*TaskStep `json:"steps,omitempty"`
|
||||
CheckRunURL *string `json:"check_run_url,omitempty"`
|
||||
}
|
||||
|
||||
// Jobs represents a slice of repository action workflow job.
|
||||
type Jobs struct {
|
||||
TotalCount *int `json:"total_count,omitempty"`
|
||||
Jobs []*WorkflowJob `json:"jobs,omitempty"`
|
||||
}
|
||||
|
||||
// ListWorkflowJobsOptions specifies optional parameters to ListWorkflowJobs.
|
||||
type ListWorkflowJobsOptions struct {
|
||||
// Filter specifies how jobs should be filtered by their completed_at timestamp.
|
||||
// Possible values are:
|
||||
// latest - Returns jobs from the most recent execution of the workflow run
|
||||
// all - Returns all jobs for a workflow run, including from old executions of the workflow run
|
||||
//
|
||||
// Default value is "latest".
|
||||
Filter string `url:"filter,omitempty"`
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListWorkflowJobs lists all jobs for a workflow run.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/workflow-jobs/#list-jobs-for-a-workflow-run
|
||||
func (s *ActionsService) ListWorkflowJobs(ctx context.Context, owner, repo string, runID int64, opts *ListWorkflowJobsOptions) (*Jobs, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%s/%s/actions/runs/%v/jobs", owner, repo, runID)
|
||||
u, err := addOptions(u, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
jobs := new(Jobs)
|
||||
resp, err := s.client.Do(ctx, req, &jobs)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return jobs, resp, nil
|
||||
}
|
||||
|
||||
// GetWorkflowJobByID gets a specific job in a workflow run by ID.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/workflow-jobs/#get-a-job-for-a-workflow-run
|
||||
func (s *ActionsService) GetWorkflowJobByID(ctx context.Context, owner, repo string, jobID int64) (*WorkflowJob, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/jobs/%v", owner, repo, jobID)
|
||||
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
job := new(WorkflowJob)
|
||||
resp, err := s.client.Do(ctx, req, job)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return job, resp, nil
|
||||
}
|
||||
|
||||
// GetWorkflowJobLogs gets a redirect URL to download a plain text file of logs for a workflow job.
|
||||
//
|
||||
// GitHub API docs: https://developer.github.com/v3/actions/workflow-jobs/#download-job-logs-for-a-workflow-run
|
||||
func (s *ActionsService) GetWorkflowJobLogs(ctx context.Context, owner, repo string, jobID int64, followRedirects bool) (*url.URL, *Response, error) {
|
||||
u := fmt.Sprintf("repos/%v/%v/actions/jobs/%v/logs", owner, repo, jobID)
|
||||
|
||||
resp, err := s.getWorkflowLogsFromURL(ctx, u, followRedirects)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusFound {
|
||||
return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
|
||||
}
|
||||
parsedURL, err := url.Parse(resp.Header.Get("Location"))
|
||||
return parsedURL, newResponse(resp), err
|
||||
}
|
||||
|
||||
func (s *ActionsService) getWorkflowLogsFromURL(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
|
||||
req, err := s.client.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
// Use http.DefaultTransport if no custom Transport is configured
|
||||
req = withContext(ctx, req)
|
||||
if s.client.client.Transport == nil {
|
||||
resp, err = http.DefaultTransport.RoundTrip(req)
|
||||
} else {
|
||||
resp, err = s.client.client.Transport.RoundTrip(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// If redirect response is returned, follow it
|
||||
if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
|
||||
u = resp.Header.Get("Location")
|
||||
resp, err = s.getWorkflowLogsFromURL(ctx, u, false)
|
||||
}
|
||||
return resp, err
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user