diff --git a/.changelog.yml b/.changelog.yml index 3bcb309046..3d61e8ef48 100644 --- a/.changelog.yml +++ b/.changelog.yml @@ -1,44 +1,44 @@ repo: go-gitea/gitea groups: - - + - name: BREAKING labels: - kind/breaking - - + - name: FEATURE labels: - kind/feature + - + name: SECURITY + labels: + - kind/security - name: BUGFIXES labels: - kind/bug - - + - name: ENHANCEMENT labels: - kind/enhancement - kind/refactor - kind/ui - - name: SECURITY - labels: - - kind/security - - name: TESTING labels: - kind/testing - - + - name: TRANSLATION labels: - kind/translation - - + - name: BUILD labels: - kind/build - kind/lint - - + - name: DOCS labels: - kind/docs - - + - name: MISC - default: true \ No newline at end of file + default: true diff --git a/.drone.yml b/.drone.yml index 1cddc80d40..aa9b9bfd25 100644 --- a/.drone.yml +++ b/.drone.yml @@ -30,6 +30,7 @@ services: image: postgres:9.5 environment: POSTGRES_DB: test + POSTGRES_PASSWORD: postgres - name: mssql pull: default @@ -388,7 +389,7 @@ steps: - name: static pull: always - image: techknowlogick/xgo:latest + image: techknowlogick/xgo:go-1.13.x commands: - export PATH=$PATH:$GOPATH/bin - make generate @@ -490,7 +491,7 @@ steps: - name: static pull: always - image: techknowlogick/xgo:latest + image: techknowlogick/xgo:go-1.13.x commands: - export PATH=$PATH:$GOPATH/bin - make generate diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a88eb1035..6de71515b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,103 @@ 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.10.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.10.0-rc1) - 2019-10-14 +## [1.10.6](https://github.com/go-gitea/gitea/releases/tag/v1.10.6) - 2020-03-10 + +This is a re-tag version of v1.10.5 and also explicitly built with Go 1.13. + +WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should **not** be used. + +## [1.10.5](https://github.com/go-gitea/gitea/releases/tag/v1.10.5) - 2020-03-06 + +* BUGFIXES + * Fix release attachments being deleted while upgrading (#10572) (#10574) + +## [1.10.4](https://github.com/go-gitea/gitea/releases/tag/v1.10.4) - 2020-02-16 + +* FEATURE + * Prevent empty LDAP search from deactivating all users (#9879) (#9890) +* BUGFIXES + * Fix reply on code review (#10261) (#10227) + * Fix branch page pull request title and link error (#10092) (#10098) + * Fix milestone API state parameter unhandled (#10049) (#10053) + * Fix wiki raw view on sub path (#10002) (#10041) + * Fix RocketChat Webhook (#9908) (#9921) (#9925) + * Fix bug about wrong dependencies permissions check and other wrong permissions check (#9884) (Partial backport #9842) + * Ensure that 2fa is checked on reset-password (#9857) (#9877) + +## [1.10.3](https://github.com/go-gitea/gitea/releases/tag/v1.10.3) - 2020-01-17 +* SECURITY + * Hide credentials when submitting migration (#9102) (#9704) + * Never allow an empty password to validate (#9682) (#9684) + * Prevent redirect to Host (#9678) (#9680) + * Hide public repos owned by private orgs (#9609) (#9616) +* BUGFIXES + * Allow assignee on Pull Creation when Issue Unit is deactivated (#9836) (#9838) + * Fix download file wrong content-type (#9825) (#9835) + * Fix wrong identify poster on a migrated pull request when submit review (#9827) (#9831) + * Fix dump non-exist log directory (#9818) (#9820) + * Fix compare (#9808) (#9815) + * Fix missing msteam webhook on organization (#9781) (#9795) + * Fix add team on collaborator page when same name as organization (#9783) + * Fix cache problem on dashboard (#9358) (#9703) + * Send tag create and push webhook when release created on UI (#8671) (#9702) + * Branches not at ref commit ID should not be listed as Merged (#9614) (#9639) + +## [1.10.2](https://github.com/go-gitea/gitea/releases/tag/v1.10.2) - 2020-01-02 +* BUGFIXES + * Allow only specific Columns to be updated on Issue via API (#9539) (#9580) + * Add ErrReactionAlreadyExist error (#9550) (#9564) + * Fix bug when migrate from API (#8631) (#9563) + * Use default avatar for ghost user (#9536) (#9537) + * Fix repository issues pagination bug when there are more than one label filter (#9512) (#9528) + * Fix deleted branch not removed when push the branch again (#9516) (#9524) + * Fix missing repository status when migrating repository via API (#9511) + * Trigger webhook when deleting a branch after merging a PR (#9510) + * Fix paging on /repos/{owner}/{repo}/git/trees/{sha} API endpoint (#9482) + * Fix NewCommitStatus (#9434) (#9435) + * Use OriginalURL instead of CloneAddr in migration logging (#9418) (#9420) + * Fix Slack webhook payload title generation to work with Mattermost (#9404) + * DefaultBranch needs to be prefixed by BranchPrefix (#9356) (#9359) + * Fix issue indexer not triggered when migrating a repository (#9333) + * Fix bug that release attachment files not deleted when deleting repository (#9322) (#9329) + * Fix migration releases (#9319) (#9326) (#9328) + * Fix File Edit: Author/Committer interchanged (#9297) (#9300) + +## [1.10.1](https://github.com/go-gitea/gitea/releases/tag/v1.10.1) - 2019-12-05 +* BUGFIXES + * Fix max length check and limit in multiple repo forms (#9148) (#9204) + * Properly fix displaying virtual session provider in admin panel (#9137) (#9203) + * Upgrade levelqueue to 0.1.0 (#9192) (#9199) + * Fix panic when diff (#9187) (#9193) + * Smtp logger configuration sendTos should be an array (#9154) (#9157) + * Always Show Password Field on Link Account Sign-in Page (#9150) + * Create PR on Current Repository by Default (#8670) (#9141) + * Fix race on indexer (#9136) (#9139) + * Fix reCAPTCHA URL (#9119) + * Hide migrated credentials (#9098) + * Update golang.org/x/crypto vendor to use acme v2 (#9056) (#9085) + * Fix password checks on admin create/edit user (#9076) (#9081) + * Fix add search as a reserved username (#9063) (#9065) + * Fix permission checks for close/reopen from commit (#8875) (#9033) + * Ensure Written is set in GZIP ProxyResponseWriter (#9018) (#9025) + * Fix broken link to branch from issue list (#9003) (#9021) + * Fix wrong system notice when repository is empty (#9020) + * Shadow password correctly for session config (#8984) (#9002) + +## [1.10.0](https://github.com/go-gitea/gitea/releases/tag/v1.10.0) - 2019-11-13 * BREAKING + * Fix deadline on update issue or PR via API (#8698) + * Hide some user information via API if user doesn't have enough permission (#8655) (#8657) * Remove legacy handling of drone token (#8191) * Change repo search to use exact match for topic search. (#7941) * Add pagination for admin api get orgs and fix only list public orgs bug (#7742) * Implement the ability to change the ssh port to match what is in the gitea config (#7286) +* SECURITY + * Fix issue with user.fullname (#8903) + * Ignore mentions for users with no access (#8395) + * Be more strict with git arguments (#7715) + * Extract the username and password from the mirror url (#7651) + * reserve .well-known username (#7637) * FEATURE * Org/Members: display 2FA members states + optimize sql requests (#7621) * SetDefaultBranch on pushing to empty repository (#7610) @@ -19,6 +110,41 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Add option to initialize repository with labels (#6061) * Add additional password hash algorithms (#6023) * BUGFIXES + * Allow to merge if file path contains " or \ (#8629) (#8771) + * On windows set core.longpaths true (#8776) (#8786) + * Fix 500 when edit hook (#8782) (#8789) + * Fix Checkbox at RepoSettings Protected Branch (#8799) (#8801) + * Fix SSH2 conditional in key parsing code (#8806) (#8810) + * Fix commit expand button to not go to commit link (#8745) (#8825) + * Fix new user form for non-local users (#8826) (#8828) + * Fix to close opened io resources as soon as not needed (#8839) (#8846) + * Fix edit content button on migrated issue content (#8877) (#8884) + * Fix require external registration password (#8885) (#8890) + * Fix password complexity check on registration (#8887) (#8888) + * Update Github Migration Tests (#8896) (#8938) (#8945) + * Enable punctuations ending mentions (#8889) (#8894) + * Add Close() method to gogitRepository (#8901) (#8956) + * Hotfix for review actions and notifications (#8965) + * Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool params (#8528) (#8618) + * Fix milestone close timestamp (#8728) (#8730) + * Fix 500 when getting user as unauthenticated user (#8653) (#8663) + * Fix 'New Issue Missing Milestone Comment' (#8678) (#8681) + * Use AppSubUrl for more redirections (#8647) (#8651) + * Add SubURL to redirect path (#8632) (#8634) + * Fix template error on account page (#8562) (#8622) + * Allow externalID to be UUID (#8551) (#8624) + * Prevent removal of non-empty emoji panel following selection of duplicate (#8609) (#8623) + * Update heatmap fixtures to restore tests (#8615) (#8616) + * Ensure that diff stats can scroll independently of the diff (#8581) (#8621) + * Webhook: set Content-Type for application/x-www-form-urlencoded (#8600) + * Fix #8582 by handling empty repos (#8587) (#8594) + * Fix bug on pull requests when transfer head repository (#8564) (#8569) + * Add missed close in ServeBlobLFS (#8527) (#8542) + * Ensure that GitRepo is set on Empty repositories (#8539) (#8541) + * Fix migrate mirror 500 bug (#8526) (#8530) + * Fix password complexity regex for special characters (#8524) + * Prevent .code-view from overriding font on icon fonts (#8614) (#8627) + * Allow more than 255 characters for tokens in external_login_user table (#8554) * Fix errors in create org UI regarding team access permission (#8506) * Fix bug on FindExternalUsersByProvider (#8504) * Create .ssh dir as necessary (#8486) @@ -218,10 +344,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Support setting cookie domain (#6288) * Move migrating repository from frontend to backend (#6200) * Delete releases attachments if release is deleted (#6068) -* SECURITY - * Ignore mentions for users with no access (#8395) - * Be more strict with git arguments (#7715) - * reserve .well-known username (#7637) * TRANSLATION * Latvian translation for home page (#8468) * Add home template italian translation (#8352) @@ -250,7 +372,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix global search result CSS, misc CSS tweaks (#7789) * Tweak label border CSS (#7739) * Fix create menu item widths (#7708) - * Extract the username and password from the mirror url (#7651) * [Branch View] Delete duplicate protection symbol (#7624) * [Branch View] Delete Table Header (#7622) * [Branch View] icons to buttons (#7602) @@ -263,6 +384,38 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * wiki - editor - add buttons 'inline code', 'empty checkbox', 'checked checkbox' (#7243) * Fix Statuses API only shows first 10 statuses: Add paging and extend API GetCommitStatuses (#7141) +## [1.9.6](https://github.com/go-gitea/gitea/releases/tag/v1.9.6) - 2019-11-13 +* BUGFIXES + * Allow to merge if file path contains " or \ (#8629) (#8772) + * Fix 500 when edit hook (#8782) (#8790) + * Fix issue with user.fullname (#8904) + * Update Github Migration Test (#8897) (#8946) + * Add Close() method to gogitRepository (#8901) (#8958) + +## [1.9.5](https://github.com/go-gitea/gitea/releases/tag/v1.9.5) - 2019-10-30 +* BREAKING + * Hide some user information via API if user doesn't have enough permission (#8655) (#8658) +* BUGFIXES + * Fix milestone close timestamp (#8728) (#8731) + * Fix deadline on update issue or PR via API (#8699) + * Fix 'New Issue Missing Milestone Comment' (#8678) (#8682) + * Fix 500 when getting user as unauthenticated user (#8653) (#8662) + * Use AppSubUrl for more redirections (#8647) (#8652) + * Add SubURL to redirect path (#8632) (#8634) (#8640) + * Fix #8582 by handling empty repos (#8587) (#8593) + * Fix bug on pull requests when transfer head repository (#8571) + * Add missed close in ServeBlobLFS (#8527) (#8543) + * Return false if provided branch name is empty for IsBranchExist (#8485) (#8492) + * Create .ssh dir as necessary (#8369) (#8486) (#8489) + * Restore functionality for early gits (#7775) (#8476) + * Add check for empty set when dropping indexes during migration (#8475) + * Ensure Request Body Readers are closed in LFS server (#8454) (#8459) + * Ensure that LFS files are relative to the LFS content path (#8455) (#8458) +* SECURITY + * Ignore mentions for users with no access (#8395) (#8484) +* TESTING + * Update heatmap fixtures to restore tests (#8615) (#8617) + ## [1.9.4](https://github.com/go-gitea/gitea/releases/tag/v1.9.4) - 2019-10-08 * BUGFIXES * Highlight issue references (#8101) (#8404) diff --git a/cmd/admin.go b/cmd/admin.go index 4346159feb..32ec665ff6 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -375,17 +375,20 @@ func runRepoSyncReleases(c *cli.Context) error { if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil { log.Warn(" SyncReleasesWithTags: %v", err) + gitRepo.Close() continue } count, err = getReleaseCount(repo.ID) if err != nil { log.Warn(" GetReleaseCountByRepoID: %v", err) + gitRepo.Close() continue } log.Trace(" repo %s releases synchronized to tags: from %d to %d", repo.FullName(), oldnum, count) + gitRepo.Close() } } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index cce3aa894f..e869686cbd 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -61,6 +61,10 @@ var ( Name: "admin-filter", Usage: "An LDAP filter specifying if a user should be given administrator privileges.", }, + cli.BoolFlag{ + Name: "allow-deactivate-all", + Usage: "Allow empty search results to deactivate all users.", + }, cli.StringFlag{ Name: "username-attribute", Usage: "The attribute of the user’s LDAP record containing the user name.", @@ -231,6 +235,9 @@ func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error { if c.IsSet("admin-filter") { config.Source.AdminFilter = c.String("admin-filter") } + if c.IsSet("allow-deactivate-all") { + config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all") + } return nil } diff --git a/cmd/dump.go b/cmd/dump.go index 1bf6901769..2de8278a29 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -145,8 +145,10 @@ func runDump(ctx *cli.Context) error { } } - if err := z.AddDir("log", setting.LogRootPath); err != nil { - log.Fatalf("Failed to include log: %v", err) + if com.IsExist(setting.LogRootPath) { + if err := z.AddDir("log", setting.LogRootPath); err != nil { + log.Fatalf("Failed to include log: %v", err) + } } if err = z.Close(); err != nil { diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 79d9960052..e2f88ca305 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -277,10 +277,12 @@ LOG_SQL = true DB_RETRIES = 10 ; Backoff time per DB retry (time.Duration) DB_RETRY_BACKOFF = 3s -; Max idle database connections on connnection pool, default is 0 -MAX_IDLE_CONNS = 0 -; Database connection max life time, default is 3s +; Max idle database connections on connnection pool, default is 2 +MAX_IDLE_CONNS = 2 +; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning) CONN_MAX_LIFETIME = 3s +; Database maximum number of open connections, default is 0 meaning no maximum +MAX_OPEN_CONNS = 0 [indexer] ; Issue indexer type, currently support: bleve or db, default is bleve @@ -333,7 +335,8 @@ IMPORT_LOCAL_PATHS = false ; Set to true to prevent all users (including admin) from creating custom git hooks DISABLE_GIT_HOOKS = false ;Comma separated list of character classes required to pass minimum complexity. -;If left empty or no valid values are specified, the default values (`lower,upper,digit,spec`) will be used. +;If left empty or no valid values are specified, the default values ("lower,upper,digit,spec") will be used. +;Use "off" to disable checking. PASSWORD_COMPLEXITY = lower,upper,digit,spec ; Password Hash algorithm, either "pbkdf2", "argon2", "scrypt" or "bcrypt" PASSWORD_HASH_ALGO = pbkdf2 @@ -822,6 +825,6 @@ TOKEN = QUEUE_TYPE = channel ; Task queue length, available only when `QUEUE_TYPE` is `channel`. QUEUE_LENGTH = 1000 -; Task queue connction string, available only when `QUEUE_TYPE` is `redis`. +; Task queue connection string, available only when `QUEUE_TYPE` is `redis`. ; If there is a password of redis, use `addrs=127.0.0.1:6379 password=123 db=0`. -QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" \ No newline at end of file +QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 100bb229ee..bdf87d29fc 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -167,8 +167,12 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `LOG_SQL`: **true**: Log the executed SQL. - `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed. - `DB_RETRY_BACKOFF`: **3s**: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occured. -- `MAX_IDLE_CONNS` **0**: Max idle database connections on connnection pool, default is 0 -- `CONN_MAX_LIFETIME` **3s**: Database connection max lifetime +- `MAX_OPEN_CONNS` **0**: Database maximum open connections - default is 0, meaning there is no limit. +- `MAX_IDLE_CONNS` **2**: Max idle database connections on connnection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`. +- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071). + +Please see #8540 & #8273 for further discussion of the appropriate values for `MAX_OPEN_CONNS`, `MAX_IDLE_CONNS` & `CONN_MAX_LIFETIME` and their +relation to port exhaustion. ## Indexer (`indexer`) @@ -212,7 +216,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - lower - use one or more lower latin characters - upper - use one or more upper latin characters - digit - use one or more digits - - spec - use one or more special characters as ``][!"#$%&'()*+,./:;<=>?@\^_{|}~`-`` and space symbol. + - spec - use one or more special characters as ``!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~`` + - off - do not check password complexity ## OpenID (`openid`) diff --git a/docs/content/doc/advanced/migrations.en-us.md b/docs/content/doc/advanced/migrations.en-us.md index 2511f7af89..0d9d8b49a7 100644 --- a/docs/content/doc/advanced/migrations.en-us.md +++ b/docs/content/doc/advanced/migrations.en-us.md @@ -68,6 +68,7 @@ type Uploader interface { CreateComment(issueNumber int64, comment *Comment) error CreatePullRequest(pr *PullRequest) error Rollback() error + Close() } -``` \ No newline at end of file +``` diff --git a/go.mod b/go.mod index 203b6ce4c1..84c229d0be 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( cloud.google.com/go v0.45.0 // indirect + gitea.com/lunny/levelqueue v0.1.0 gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae @@ -67,7 +68,6 @@ require ( github.com/lafriks/xormstore v1.3.1 github.com/lib/pq v1.2.0 github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 - github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e github.com/mailru/easyjson v0.7.0 // indirect github.com/markbates/goth v1.56.0 github.com/mattn/go-isatty v0.0.7 @@ -104,7 +104,7 @@ require ( github.com/urfave/cli v1.20.0 github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 // indirect github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 - golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad + golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba golang.org/x/net v0.0.0-20190909003024-a7b16738d86b golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b diff --git a/go.sum b/go.sum index 7306966800..67cb20ff80 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ cloud.google.com/go v0.45.0 h1:bALuGBSgE+BD4rxsopAYlqjcwqcQtye6pWG4bC3N/k0= cloud.google.com/go v0.45.0/go.mod h1:452BcPOeI9AZfbvDw0Tbo7D32wA+WX9WME8AZwMEDZU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +gitea.com/lunny/levelqueue v0.1.0 h1:7wMk0VH6mvKN6vZEZCy9nUDgRmdPLgeNrm1NkW8EHNk= +gitea.com/lunny/levelqueue v0.1.0/go.mod h1:G7hVb908t0Bl0uk7zGSg14fyzNtxgtD9Shf04wkMK7s= gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b h1:vXt85uYV17KURaUlhU7v4GbCShkqRZDSfo0TkC0YCjQ= gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b/go.mod h1:Cxadig6POWpPYYSfg23E7jo35Yf0yvsdC1lifoKWmPo= gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 h1:mMsMEg90c5KXQgRWsH8D6GHXfZIW1RAe5S9VYIb12lM= @@ -391,8 +393,6 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ= -github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e h1:GSprKUrG9wNgwQgROvjPGXmcZrg4OLslOuZGB0uJjx8= -github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e/go.mod h1:rQZVENnBOiVakCs97XvclbwJRTAv77CRFWcYVNDkVf8= github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEgQ07DbcRc5xgNObtbTp76fk= github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ= github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af h1:UaWHNBdukWrSG3DRvHFR/hyfg681fceqQDYVTBncKfQ= @@ -627,6 +627,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49N golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= diff --git a/integrations/api_issue_milestone_test.go b/integrations/api_issue_milestone_test.go new file mode 100644 index 0000000000..a02ab40bab --- /dev/null +++ b/integrations/api_issue_milestone_test.go @@ -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 integrations + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIIssuesMilestone(t *testing.T) { + prepareTestEnv(t) + + milestone := models.AssertExistsAndLoadBean(t, &models.Milestone{ID: 1}).(*models.Milestone) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: milestone.RepoID}).(*models.Repository) + owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + assert.Equal(t, int64(1), int64(milestone.NumIssues)) + assert.Equal(t, structs.StateOpen, milestone.State()) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + // update values of issue + milestoneState := "closed" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d?token=%s", owner.Name, repo.Name, milestone.ID, token) + req := NewRequestWithJSON(t, "PATCH", urlStr, structs.EditMilestoneOption{ + State: &milestoneState, + }) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiMilestone structs.Milestone + DecodeJSON(t, resp, &apiMilestone) + assert.EqualValues(t, "closed", apiMilestone.State) + + req = NewRequest(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiMilestone2 structs.Milestone + DecodeJSON(t, resp, &apiMilestone2) + assert.EqualValues(t, "closed", apiMilestone2.State) +} diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index 24535057e2..15510fcfc1 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "testing" + "time" "code.gitea.io/gitea/models" api "code.gitea.io/gitea/modules/structs" @@ -62,3 +63,61 @@ func TestAPICreateIssue(t *testing.T) { Title: title, }) } + +func TestAPIEditIssue(t *testing.T) { + prepareTestEnv(t) + + issueBefore := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 9}).(*models.Issue) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository) + owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + assert.NoError(t, issueBefore.LoadAttributes()) + assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issueBefore.State()) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + // update values of issue + issueState := "closed" + removeDeadline := time.Unix(0, 0) + milestone := int64(4) + body := "new content!" + title := "new title from api set" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repo.Name, issueBefore.Index, token) + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{ + State: &issueState, + Deadline: &removeDeadline, + Milestone: &milestone, + Body: &body, + Title: title, + + // ToDo change more + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var apiIssue api.Issue + DecodeJSON(t, resp, &apiIssue) + + issueAfter := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 9}).(*models.Issue) + + // check deleted user + assert.Equal(t, int64(500), issueAfter.PosterID) + assert.NoError(t, issueAfter.LoadAttributes()) + assert.Equal(t, int64(-1), issueAfter.PosterID) + assert.Equal(t, int64(-1), issueBefore.PosterID) + assert.Equal(t, int64(-1), apiIssue.Poster.ID) + + // API response + assert.Equal(t, api.StateClosed, apiIssue.State) + assert.Equal(t, milestone, apiIssue.Milestone.ID) + assert.Equal(t, body, apiIssue.Body) + assert.True(t, apiIssue.Deadline == nil) + assert.Equal(t, title, apiIssue.Title) + + // in database + assert.Equal(t, api.StateClosed, issueAfter.State()) + assert.Equal(t, milestone, issueAfter.MilestoneID) + assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix)) + assert.Equal(t, body, issueAfter.Content) + assert.Equal(t, title, issueAfter.Title) +} diff --git a/integrations/api_releases_test.go b/integrations/api_releases_test.go index 897f863eb3..8025f1de5d 100644 --- a/integrations/api_releases_test.go +++ b/integrations/api_releases_test.go @@ -51,6 +51,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { gitRepo, err := git.OpenRepository(repo.RepoPath()) assert.NoError(t, err) + defer gitRepo.Close() err = gitRepo.CreateTag("v0.0.1", "master") assert.NoError(t, err) @@ -112,6 +113,7 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { gitRepo, err := git.OpenRepository(repo.RepoPath()) assert.NoError(t, err) + defer gitRepo.Close() err = gitRepo.CreateTag("v0.0.1", "master") assert.NoError(t, err) diff --git a/integrations/api_repo_file_create_test.go b/integrations/api_repo_file_create_test.go index 42898bf259..62929e9098 100644 --- a/integrations/api_repo_file_create_test.go +++ b/integrations/api_repo_file_create_test.go @@ -30,12 +30,12 @@ func getCreateFileOptions() api.CreateFileOptions { NewBranchName: "master", Message: "Making this new file new/file.txt", Author: api.Identity{ - Name: "John Doe", - Email: "johndoe@example.com", + Name: "Anne Doe", + Email: "annedoe@example.com", }, Committer: api.Identity{ - Name: "Jane Doe", - Email: "janedoe@example.com", + Name: "John Doe", + Email: "johndoe@example.com", }, }, Content: contentEncoded, @@ -77,8 +77,8 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, Author: &api.CommitUser{ Identity: api.Identity{ - Name: "Jane Doe", - Email: "janedoe@example.com", + Name: "Anne Doe", + Email: "annedoe@example.com", }, }, Committer: &api.CommitUser{ @@ -139,6 +139,7 @@ func TestAPICreateFile(t *testing.T) { assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + gitRepo.Close() } // Test creating a file in a new branch diff --git a/integrations/api_repo_file_update_test.go b/integrations/api_repo_file_update_test.go index 366eb5e918..b9ab8eb3a6 100644 --- a/integrations/api_repo_file_update_test.go +++ b/integrations/api_repo_file_update_test.go @@ -35,8 +35,8 @@ func getUpdateFileOptions() *api.UpdateFileOptions { Email: "johndoe@example.com", }, Committer: api.Identity{ - Name: "Jane Doe", - Email: "janedoe@example.com", + Name: "Anne Doe", + Email: "annedoe@example.com", }, }, SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", @@ -80,14 +80,14 @@ func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileRespon HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, Author: &api.CommitUser{ Identity: api.Identity{ - Name: "Jane Doe", - Email: "janedoe@example.com", + Name: "John Doe", + Email: "johndoe@example.com", }, }, Committer: &api.CommitUser{ Identity: api.Identity{ - Name: "John Doe", - Email: "johndoe@example.com", + Name: "Anne Doe", + Email: "annedoe@example.com", }, }, Message: "My update of README.md\n", @@ -143,6 +143,7 @@ func TestAPIUpdateFile(t *testing.T) { assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + gitRepo.Close() } // Test updating a file in a new branch diff --git a/integrations/api_repo_get_contents_list_test.go b/integrations/api_repo_get_contents_list_test.go index f74ceb514a..4605ccf4d9 100644 --- a/integrations/api_repo_get_contents_list_test.go +++ b/integrations/api_repo_get_contents_list_test.go @@ -74,6 +74,8 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch) // Get the commit ID of the default branch gitRepo, _ := git.OpenRepository(repo1.RepoPath()) + defer gitRepo.Close() + commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) // Make a new tag in repo1 newTag := "test_tag" diff --git a/integrations/api_repo_get_contents_test.go b/integrations/api_repo_get_contents_test.go index f6a43bc5c6..77a827ec61 100644 --- a/integrations/api_repo_get_contents_test.go +++ b/integrations/api_repo_get_contents_test.go @@ -75,6 +75,8 @@ func testAPIGetContents(t *testing.T, u *url.URL) { repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch) // Get the commit ID of the default branch gitRepo, _ := git.OpenRepository(repo1.RepoPath()) + defer gitRepo.Close() + commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) // Make a new tag in repo1 newTag := "test_tag" diff --git a/integrations/api_repo_git_tags_test.go b/integrations/api_repo_git_tags_test.go index ae519249e0..d6ff08990a 100644 --- a/integrations/api_repo_git_tags_test.go +++ b/integrations/api_repo_git_tags_test.go @@ -29,6 +29,8 @@ func TestAPIGitTags(t *testing.T) { git.NewCommand("config", "user.email", user.Email).RunInDir(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath()) + defer gitRepo.Close() + commit, _ := gitRepo.GetBranchCommit("master") lTagName := "lightweightTag" gitRepo.CreateTag(lTagName, commit.ID.String()) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 60fe4a3649..a2683d4af4 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -334,7 +334,7 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict) respJSON := map[string]string{} DecodeJSON(t, resp, &respJSON) - assert.Equal(t, respJSON["message"], "The repository with the same name already exists.") + assert.Equal(t, "The repository with the same name already exists.", respJSON["message"]) }) } diff --git a/integrations/api_team_user_test.go b/integrations/api_team_user_test.go index 70d52c1360..4df4dac016 100644 --- a/integrations/api_team_user_test.go +++ b/integrations/api_team_user_test.go @@ -29,7 +29,6 @@ func TestAPITeamUser(t *testing.T) { var user2 *api.User DecodeJSON(t, resp, &user2) user2.Created = user2.Created.In(time.Local) - user2.LastLogin = user2.LastLogin.In(time.Local) user := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) assert.Equal(t, convert.ToUser(user, true, false), user2) diff --git a/integrations/api_user_heatmap_test.go b/integrations/api_user_heatmap_test.go index 5245bb0a26..2e2636ce94 100644 --- a/integrations/api_user_heatmap_test.go +++ b/integrations/api_user_heatmap_test.go @@ -26,7 +26,7 @@ func TestUserHeatmap(t *testing.T) { var heatmap []*models.UserHeatmapData DecodeJSON(t, resp, &heatmap) var dummyheatmap []*models.UserHeatmapData - dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1540080000, Contributions: 1}) + dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1571616000, Contributions: 1}) assert.Equal(t, dummyheatmap, heatmap) } diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba b/integrations/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba new file mode 100644 index 0000000000..d3c45d51ea Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 b/integrations/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 new file mode 100644 index 0000000000..bf97d00fd8 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b b/integrations/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b new file mode 100644 index 0000000000..7678d6754d Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head new file mode 100644 index 0000000000..98593d6537 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head @@ -0,0 +1 @@ +4a357436d925b5c974181ff12a994538ddc5a269 diff --git a/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 new file mode 100644 index 0000000000..bf4ae859f6 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e new file mode 100644 index 0000000000..bcb0e0075c Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 new file mode 100644 index 0000000000..6dcfc96676 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.wiki.git/refs/heads/master b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/refs/heads/master index 86de6490a2..1b1d96a1f0 100644 --- a/integrations/gitea-repositories-meta/user2/repo1.wiki.git/refs/heads/master +++ b/integrations/gitea-repositories-meta/user2/repo1.wiki.git/refs/heads/master @@ -1 +1 @@ -0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3 +423313fbd38093bb10d0c8387db9105409c6f196 diff --git a/integrations/repofiles_delete_test.go b/integrations/repofiles_delete_test.go index f4cb4510be..10742bf0b9 100644 --- a/integrations/repofiles_delete_test.go +++ b/integrations/repofiles_delete_test.go @@ -73,6 +73,7 @@ func testDeleteRepoFile(t *testing.T, u *url.URL) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() repo := ctx.Repo.Repository doer := ctx.User opts := getDeleteRepoFileOptions(repo) @@ -111,6 +112,8 @@ func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository doer := ctx.User opts := getDeleteRepoFileOptions(repo) @@ -139,6 +142,8 @@ func TestDeleteRepoFileErrors(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository doer := ctx.User diff --git a/integrations/repofiles_update_test.go b/integrations/repofiles_update_test.go index a4ce16d847..5b88bb8198 100644 --- a/integrations/repofiles_update_test.go +++ b/integrations/repofiles_update_test.go @@ -191,6 +191,8 @@ func TestCreateOrUpdateRepoFileForCreate(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository doer := ctx.User opts := getCreateRepoFileOptions(repo) @@ -201,6 +203,8 @@ func TestCreateOrUpdateRepoFileForCreate(t *testing.T) { // asserts assert.Nil(t, err) gitRepo, _ := git.OpenRepository(repo.RepoPath()) + defer gitRepo.Close() + commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID) assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) @@ -220,6 +224,8 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository doer := ctx.User opts := getUpdateRepoFileOptions(repo) @@ -230,6 +236,8 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) { // asserts assert.Nil(t, err) gitRepo, _ := git.OpenRepository(repo.RepoPath()) + defer gitRepo.Close() + commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath) assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) @@ -249,6 +257,8 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository doer := ctx.User opts := getUpdateRepoFileOptions(repo) @@ -261,6 +271,8 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { // asserts assert.Nil(t, err) gitRepo, _ := git.OpenRepository(repo.RepoPath()) + defer gitRepo.Close() + commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath) // assert that the old file no longer exists in the last commit of the branch @@ -288,6 +300,8 @@ func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository doer := ctx.User opts := getUpdateRepoFileOptions(repo) @@ -300,6 +314,8 @@ func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) { // asserts assert.Nil(t, err) gitRepo, _ := git.OpenRepository(repo.RepoPath()) + defer gitRepo.Close() + commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch) expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath) assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) @@ -315,6 +331,8 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository doer := ctx.User diff --git a/integrations/signup_test.go b/integrations/signup_test.go index 325c906326..e122efa39c 100644 --- a/integrations/signup_test.go +++ b/integrations/signup_test.go @@ -19,8 +19,8 @@ func TestSignup(t *testing.T) { req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ "user_name": "exampleUser", "email": "exampleUser@example.com", - "password": "examplePassword", - "retype": "examplePassword", + "password": "examplePassword!1", + "retype": "examplePassword!1", }) MakeRequest(t, req, http.StatusFound) diff --git a/integrations/user_test.go b/integrations/user_test.go index 0a6fdd19d5..452ea2ddf0 100644 --- a/integrations/user_test.go +++ b/integrations/user_test.go @@ -90,6 +90,7 @@ func TestRenameReservedUsername(t *testing.T) { "repo", "template", "user", + "search", } session := loginUser(t, "user2") diff --git a/models/action.go b/models/action.go index 2d2999f880..626ae1913d 100644 --- a/models/action.go +++ b/models/action.go @@ -533,31 +533,44 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra } refMarked[key] = true - // only create comments for issues if user has permission for it - if perm.IsAdmin() || perm.IsOwner() || perm.CanWrite(UnitTypeIssues) { - message := fmt.Sprintf(`%s`, repo.Link(), c.Sha1, html.EscapeString(c.Message)) - if err = CreateRefComment(doer, refRepo, refIssue, message, c.Sha1); err != nil { - return err - } + // FIXME: this kind of condition is all over the code, it should be consolidated in a single place + canclose := perm.IsAdmin() || perm.IsOwner() || perm.CanWrite(UnitTypeIssues) || refIssue.PosterID == doer.ID + cancomment := canclose || perm.CanRead(UnitTypeIssues) + + // Don't proceed if the user can't comment + if !cancomment { + continue } - // Process closing/reopening keywords + message := fmt.Sprintf(`%s`, repo.Link(), c.Sha1, html.EscapeString(c.Message)) + if err = CreateRefComment(doer, refRepo, refIssue, message, c.Sha1); err != nil { + return err + } + + // Only issues can be closed/reopened this way, and user needs the correct permissions + if refIssue.IsPull || !canclose { + continue + } + + // Only process closing/reopening keywords if ref.Action != references.XRefActionCloses && ref.Action != references.XRefActionReopens { continue } - // Change issue status only if the commit has been pushed to the default branch. - // and if the repo is configured to allow only that - // FIXME: we should be using Issue.ref if set instead of repo.DefaultBranch - if repo.DefaultBranch != branchName && !repo.CloseIssuesViaCommitInAnyBranch { - continue + if !repo.CloseIssuesViaCommitInAnyBranch { + // If the issue was specified to be in a particular branch, don't allow commits in other branches to close it + if refIssue.Ref != "" { + if branchName != refIssue.Ref { + continue + } + // Otherwise, only process commits to the default branch + } else if branchName != repo.DefaultBranch { + continue + } } - // only close issues in another repo if user has push access - if perm.IsAdmin() || perm.IsOwner() || perm.CanWrite(UnitTypeCode) { - if err := changeIssueStatus(refRepo, refIssue, doer, ref.Action == references.XRefActionCloses); err != nil { - return err - } + if err := changeIssueStatus(refRepo, refIssue, doer, ref.Action == references.XRefActionCloses); err != nil { + return err } } } diff --git a/models/action_test.go b/models/action_test.go index df41556850..25922b2247 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -219,7 +219,7 @@ func TestUpdateIssuesCommit(t *testing.T) { PosterID: user.ID, IssueID: 1, } - issueBean := &Issue{RepoID: repo.ID, Index: 2} + issueBean := &Issue{RepoID: repo.ID, Index: 4} AssertNotExistsBean(t, commentBean) AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1") @@ -273,7 +273,7 @@ func TestUpdateIssuesCommit_Colon(t *testing.T) { repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) repo.Owner = user - issueBean := &Issue{RepoID: repo.ID, Index: 2} + issueBean := &Issue{RepoID: repo.ID, Index: 4} AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1") assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch)) diff --git a/models/branches.go b/models/branches.go index fa8beb866c..3d55cbacd8 100644 --- a/models/branches.go +++ b/models/branches.go @@ -480,6 +480,12 @@ func (deletedBranch *DeletedBranch) LoadUser() { deletedBranch.DeletedBy = user } +// RemoveDeletedBranch removes all deleted branches +func RemoveDeletedBranch(repoID int64, branch string) error { + _, err := x.Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch)) + return err +} + // RemoveOldDeletedBranches removes old deleted branches func RemoveOldDeletedBranches() { log.Trace("Doing: DeletedBranchesCleanup") diff --git a/models/error.go b/models/error.go index 995617e83b..be23b47b87 100644 --- a/models/error.go +++ b/models/error.go @@ -1104,6 +1104,21 @@ func (err ErrNewIssueInsert) Error() string { return err.OriginalError.Error() } +// ErrReactionAlreadyExist is used when a existing reaction was try to created +type ErrReactionAlreadyExist struct { + Reaction string +} + +// IsErrReactionAlreadyExist checks if an error is a ErrReactionAlreadyExist. +func IsErrReactionAlreadyExist(err error) bool { + _, ok := err.(ErrReactionAlreadyExist) + return ok +} + +func (err ErrReactionAlreadyExist) Error() string { + return fmt.Sprintf("reaction '%s' already exists", err.Reaction) +} + // __________ .__ .__ __________ __ // \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_ // | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ diff --git a/models/external_login_user.go b/models/external_login_user.go index 59c3732184..265d855ccf 100644 --- a/models/external_login_user.go +++ b/models/external_login_user.go @@ -28,9 +28,9 @@ type ExternalLoginUser struct { Description string AvatarURL string Location string - AccessToken string - AccessTokenSecret string - RefreshToken string + AccessToken string `xorm:"TEXT"` + AccessTokenSecret string `xorm:"TEXT"` + RefreshToken string `xorm:"TEXT"` ExpiresAt time.Time } @@ -168,7 +168,7 @@ func FindExternalUsersByProvider(opts FindExternalUserOptions) ([]ExternalLoginU } // UpdateMigrationsByType updates all migrated repositories' posterid from gitServiceType to replace originalAuthorID to posterID -func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID, userID int64) error { +func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, userID int64) error { if err := UpdateIssuesMigrationsByType(tp, externalUserID, userID); err != nil { return err } diff --git a/models/fixtures/action.yml b/models/fixtures/action.yml index 34a1a8b2be..e8a6d531f2 100644 --- a/models/fixtures/action.yml +++ b/models/fixtures/action.yml @@ -5,7 +5,7 @@ act_user_id: 2 repo_id: 2 is_private: true - created_unix: 1540139562 + created_unix: 1571686356 - id: 2 diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index 585242a14d..e196cdd577 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -96,4 +96,17 @@ is_closed: false is_pull: true created_unix: 946684820 - updated_unix: 978307180 \ No newline at end of file + updated_unix: 978307180 + +- + id: 9 + repo_id: 42 + index: 1 + poster_id: 500 + name: issue from deleted account + content: content from deleted account + is_closed: false + is_pull: false + created_unix: 946684830 + updated_unix: 999307200 + deadline_unix: 1019307200 diff --git a/models/fixtures/milestone.yml b/models/fixtures/milestone.yml index 15f422fc3b..a9ecb4ee6a 100644 --- a/models/fixtures/milestone.yml +++ b/models/fixtures/milestone.yml @@ -21,3 +21,11 @@ content: content3 is_closed: true num_issues: 0 + +- + id: 4 + repo_id: 42 + name: milestone of repo42 + content: content random + is_closed: false + num_issues: 0 diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml index baaaf6bb8a..505584ea18 100644 --- a/models/fixtures/pull_request.yml +++ b/models/fixtures/pull_request.yml @@ -6,7 +6,6 @@ index: 2 head_repo_id: 1 base_repo_id: 1 - head_user_name: user1 head_branch: branch1 base_branch: master merge_base: 1234567890abcdef @@ -21,7 +20,6 @@ index: 3 head_repo_id: 1 base_repo_id: 1 - head_user_name: user1 head_branch: branch2 base_branch: master merge_base: fedcba9876543210 @@ -35,7 +33,6 @@ index: 1 head_repo_id: 11 base_repo_id: 10 - head_user_name: user13 head_branch: branch2 base_branch: master merge_base: 0abcb056019adb83 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index cf7d24c6cd..a084339cc2 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -547,7 +547,8 @@ is_private: false num_stars: 0 num_forks: 0 - num_issues: 0 + num_issues: 1 + num_milestones: 1 is_mirror: false - diff --git a/models/graph_test.go b/models/graph_test.go index 5c78e3877b..4e104592a3 100644 --- a/models/graph_test.go +++ b/models/graph_test.go @@ -17,6 +17,7 @@ func BenchmarkGetCommitGraph(b *testing.B) { if err != nil { b.Error("Could not open repository") } + defer currentRepo.Close() for i := 0; i < b.N; i++ { graph, err := GetCommitGraph(currentRepo) diff --git a/models/issue.go b/models/issue.go index fc675a3ffb..a0353f0be1 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// 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. @@ -238,6 +239,16 @@ func (issue *Issue) loadReactions(e Engine) (err error) { return nil } +func (issue *Issue) loadMilestone(e Engine) (err error) { + if issue.Milestone == nil && issue.MilestoneID > 0 { + issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) + if err != nil && !IsErrMilestoneNotExist(err) { + return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err) + } + } + return nil +} + func (issue *Issue) loadAttributes(e Engine) (err error) { if err = issue.loadRepo(e); err != nil { return @@ -251,11 +262,8 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { return } - if issue.Milestone == nil && issue.MilestoneID > 0 { - issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) - if err != nil && !IsErrMilestoneNotExist(err) { - return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err) - } + if err = issue.loadMilestone(e); err != nil { + return } if err = issue.loadAssignees(e); err != nil { @@ -295,6 +303,11 @@ func (issue *Issue) LoadAttributes() error { return issue.loadAttributes(x) } +// LoadMilestone load milestone of this issue. +func (issue *Issue) LoadMilestone() error { + return issue.loadMilestone(x) +} + // GetIsRead load the `IsRead` field of the issue func (issue *Issue) GetIsRead(userID int64) error { issueUser := &IssueUser{IssueID: issue.ID, UID: userID} @@ -416,7 +429,7 @@ func (issue *Issue) HashTag() string { // IsPoster returns true if given user by ID is the poster. func (issue *Issue) IsPoster(uid int64) bool { - return issue.PosterID == uid + return issue.OriginalAuthorID == 0 && issue.PosterID == uid } func (issue *Issue) hasLabel(e Engine, labelID int64) bool { @@ -1072,6 +1085,10 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { if _, err = e.Exec("UPDATE `milestone` SET num_issues=num_issues+1 WHERE id=?", opts.Issue.MilestoneID); err != nil { return err } + + if _, err = createMilestoneComment(e, doer, opts.Repo, opts.Issue, 0, opts.Issue.MilestoneID); err != nil { + return err + } } // Insert the assignees @@ -1726,22 +1743,17 @@ func SearchIssueIDsByKeyword(kw string, repoID int64, limit, start int) (int64, return total, ids, nil } -func updateIssue(e Engine, issue *Issue) error { - _, err := e.ID(issue.ID).AllCols().Update(issue) - if err != nil { - return err - } - return nil -} - -// UpdateIssue updates all fields of given issue. -func UpdateIssue(issue *Issue) error { +// UpdateIssueByAPI updates all allowed fields of given issue. +func UpdateIssueByAPI(issue *Issue) error { sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err } - if err := updateIssue(sess, issue); err != nil { + if _, err := sess.ID(issue.ID).Cols( + "name", "is_closed", "content", "milestone_id", "priority", + "deadline_unix", "updated_unix", "closed_unix", "is_locked"). + Update(issue); err != nil { return err } if err := issue.neuterCrossReferences(sess); err != nil { @@ -1950,7 +1962,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti } // UpdateIssuesMigrationsByType updates all migrated repositories' issues from gitServiceType to replace originalAuthorID to posterID -func UpdateIssuesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID, posterID int64) error { +func UpdateIssuesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { _, err := x.Table("issue"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). diff --git a/models/issue_comment.go b/models/issue_comment.go index 3a090c3b19..f3921b80c5 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -538,6 +538,10 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen switch opts.Type { case CommentTypeCode: if comment.ReviewID != 0 { + // Hotfix for 1.10.0 as the Review object has not yet been committed in the other session + if opts.Review != nil { + comment.Review = opts.Review + } if comment.Review == nil { if err := comment.loadReview(e); err != nil { return err @@ -596,6 +600,12 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen if err = opts.Issue.updateClosedNum(e); err != nil { return err } + case CommentTypeReview: + // Hotfix for 1.10.0; make sure a dashboard entry is created + if opts.Content == "" { + return nil + } + act.OpType = ActionCommentIssue } // update the issue's updated_unix column if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil { @@ -751,11 +761,12 @@ func createIssueDependencyComment(e *xorm.Session, doer *User, issue *Issue, dep // CreateCommentOptions defines options for creating comment type CreateCommentOptions struct { - Type CommentType - Doer *User - Repo *Repository - Issue *Issue - Label *Label + Type CommentType + Doer *User + Repo *Repository + Issue *Issue + Label *Label + Review *Review DependentIssueID int64 OldMilestoneID int64 @@ -1025,7 +1036,7 @@ func FetchCodeComments(issue *Issue, currentUser *User) (CodeComments, error) { } // UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id -func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID, posterID int64) error { +func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { _, err := x.Table("comment"). Where(builder.In("issue_id", builder.Select("issue.id"). diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 1587e5e341..447d0b7f46 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -253,12 +253,33 @@ func updateMilestone(e Engine, m *Milestone) error { } // UpdateMilestone updates information of given milestone. -func UpdateMilestone(m *Milestone) error { - if err := updateMilestone(x, m); err != nil { +func UpdateMilestone(m *Milestone, oldIsClosed bool) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { return err } - return updateMilestoneCompleteness(x, m.ID) + if m.IsClosed && !oldIsClosed { + m.ClosedDateUnix = timeutil.TimeStampNow() + } + + if err := updateMilestone(sess, m); err != nil { + return err + } + + if err := updateMilestoneCompleteness(sess, m.ID); err != nil { + return err + } + + // if IsClosed changed, update milestone numbers of repository + if oldIsClosed != m.IsClosed { + if err := updateRepoMilestoneNum(sess, m.RepoID); err != nil { + return err + } + } + + return sess.Commit() } func updateMilestoneCompleteness(e Engine, milestoneID int64) error { @@ -306,7 +327,11 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { } m.IsClosed = isClosed - if _, err := sess.ID(m.ID).Cols("is_closed").Update(m); err != nil { + if isClosed { + m.ClosedDateUnix = timeutil.TimeStampNow() + } + + if _, err := sess.ID(m.ID).Cols("is_closed", "closed_date_unix").Update(m); err != nil { return err } diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 6f8548ec67..42445dafeb 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -160,8 +160,9 @@ func TestUpdateMilestone(t *testing.T) { milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) milestone.Name = "newMilestoneName" milestone.Content = "newMilestoneContent" - assert.NoError(t, UpdateMilestone(milestone)) - AssertExistsAndLoadBean(t, milestone) + assert.NoError(t, UpdateMilestone(milestone, milestone.IsClosed)) + milestone = AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) + assert.EqualValues(t, "newMilestoneName", milestone.Name) CheckConsistencyFor(t, &Milestone{}) } diff --git a/models/issue_reaction.go b/models/issue_reaction.go index ab644b4b3e..a0dc728510 100644 --- a/models/issue_reaction.go +++ b/models/issue_reaction.go @@ -30,6 +30,8 @@ type Reaction struct { type FindReactionsOptions struct { IssueID int64 CommentID int64 + UserID int64 + Reaction string } func (opts *FindReactionsOptions) toConds() builder.Cond { @@ -40,6 +42,13 @@ func (opts *FindReactionsOptions) toConds() builder.Cond { if opts.CommentID > 0 { cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID}) } + if opts.UserID > 0 { + cond = cond.And(builder.Eq{"reaction.user_id": opts.UserID}) + } + if opts.Reaction != "" { + cond = cond.And(builder.Eq{"reaction.type": opts.Reaction}) + } + return cond } @@ -57,9 +66,25 @@ func createReaction(e *xorm.Session, opts *ReactionOptions) (*Reaction, error) { UserID: opts.Doer.ID, IssueID: opts.Issue.ID, } + findOpts := FindReactionsOptions{ + IssueID: opts.Issue.ID, + CommentID: -1, // reaction to issue only + Reaction: opts.Type, + UserID: opts.Doer.ID, + } if opts.Comment != nil { reaction.CommentID = opts.Comment.ID + findOpts.CommentID = opts.Comment.ID } + + existingR, err := findReactions(e, findOpts) + if err != nil { + return nil, err + } + if len(existingR) > 0 { + return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type} + } + if _, err := e.Insert(reaction); err != nil { return nil, err } @@ -76,19 +101,19 @@ type ReactionOptions struct { } // CreateReaction creates reaction for issue or comment. -func CreateReaction(opts *ReactionOptions) (reaction *Reaction, err error) { +func CreateReaction(opts *ReactionOptions) (*Reaction, error) { sess := x.NewSession() defer sess.Close() - if err = sess.Begin(); err != nil { + if err := sess.Begin(); err != nil { return nil, err } - reaction, err = createReaction(sess, opts) + reaction, err := createReaction(sess, opts) if err != nil { - return nil, err + return reaction, err } - if err = sess.Commit(); err != nil { + if err := sess.Commit(); err != nil { return nil, err } return reaction, nil diff --git a/models/issue_reaction_test.go b/models/issue_reaction_test.go index bbd8cf29fe..a2f1c025f2 100644 --- a/models/issue_reaction_test.go +++ b/models/issue_reaction_test.go @@ -50,9 +50,10 @@ func TestIssueAddDuplicateReaction(t *testing.T) { Type: "heart", }) assert.Error(t, err) - assert.Nil(t, reaction) + assert.Equal(t, ErrReactionAlreadyExist{Reaction: "heart"}, err) - AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}) + existingR := AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}).(*Reaction) + assert.Equal(t, existingR.ID, reaction.ID) } func TestIssueDeleteReaction(t *testing.T) { @@ -129,7 +130,6 @@ func TestIssueCommentDeleteReaction(t *testing.T) { user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) - ghost := NewGhostUser() issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) @@ -139,14 +139,13 @@ func TestIssueCommentDeleteReaction(t *testing.T) { addReaction(t, user2, issue1, comment1, "heart") addReaction(t, user3, issue1, comment1, "heart") addReaction(t, user4, issue1, comment1, "+1") - addReaction(t, ghost, issue1, comment1, "heart") err := comment1.LoadReactions() assert.NoError(t, err) - assert.Len(t, comment1.Reactions, 5) + assert.Len(t, comment1.Reactions, 4) reactions := comment1.Reactions.GroupByType() - assert.Len(t, reactions["heart"], 4) + assert.Len(t, reactions["heart"], 3) assert.Len(t, reactions["+1"], 1) } @@ -160,7 +159,7 @@ func TestIssueCommentReactionCount(t *testing.T) { comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) addReaction(t, user1, issue1, comment1, "heart") - DeleteCommentReaction(user1, issue1, comment1, "heart") + assert.NoError(t, DeleteCommentReaction(user1, issue1, comment1, "heart")) AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID}) } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 60a416c6e9..96f7a77589 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -256,6 +256,10 @@ var migrations = []Migration{ NewMigration("add task table and status column for repository table", addTaskTable), // v100 -> v101 NewMigration("update migration repositories' service type", updateMigrationServiceTypes), + // v101 -> v102 + NewMigration("change length of some external login users columns", changeSomeColumnsLengthOfExternalLoginUser), + // v102 -> v103 + NewMigration("update migration repositories' service type", dropColumnHeadUserNameOnPullRequest), } // Migrate database to current version diff --git a/models/migrations/v101.go b/models/migrations/v101.go new file mode 100644 index 0000000000..dfe33f3d5b --- /dev/null +++ b/models/migrations/v101.go @@ -0,0 +1,19 @@ +// Copyright 2019 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 migrations + +import ( + "github.com/go-xorm/xorm" +) + +func changeSomeColumnsLengthOfExternalLoginUser(x *xorm.Engine) error { + type ExternalLoginUser struct { + AccessToken string `xorm:"TEXT"` + AccessTokenSecret string `xorm:"TEXT"` + RefreshToken string `xorm:"TEXT"` + } + + return x.Sync2(new(ExternalLoginUser)) +} diff --git a/models/migrations/v102.go b/models/migrations/v102.go new file mode 100644 index 0000000000..6589ecdb67 --- /dev/null +++ b/models/migrations/v102.go @@ -0,0 +1,15 @@ +// Copyright 2019 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 migrations + +import ( + "github.com/go-xorm/xorm" +) + +func dropColumnHeadUserNameOnPullRequest(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + return dropTableColumns(sess, "pull_request", "head_user_name") +} diff --git a/models/migrations/v39.go b/models/migrations/v39.go index 1312cb3313..992e06bcaa 100644 --- a/models/migrations/v39.go +++ b/models/migrations/v39.go @@ -47,6 +47,7 @@ func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error { if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil { log.Warn("SyncReleasesWithTags: %v", err) } + gitRepo.Close() } if len(repos) < pageSize { break diff --git a/models/migrations/v82.go b/models/migrations/v82.go index eb73f18343..83da703c8a 100644 --- a/models/migrations/v82.go +++ b/models/migrations/v82.go @@ -91,6 +91,7 @@ func fixReleaseSha1OnReleaseTable(x *xorm.Engine) error { if err != nil { return err } + defer gitRepo.Close() gitRepoCache[release.RepoID] = gitRepo } diff --git a/models/migrations/v96.go b/models/migrations/v96.go index 34f67534c2..3269d14aad 100644 --- a/models/migrations/v96.go +++ b/models/migrations/v96.go @@ -26,23 +26,38 @@ func deleteOrphanedAttachments(x *xorm.Engine) error { sess := x.NewSession() defer sess.Close() - err := sess.BufferSize(setting.Database.IterateBufferSize). - Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").Cols("uuid"). - Iterate(new(Attachment), - func(idx int, bean interface{}) error { - attachment := bean.(*Attachment) - - if err := os.RemoveAll(models.AttachmentLocalPath(attachment.UUID)); err != nil { - return err - } - - _, err := sess.ID(attachment.ID).NoAutoCondition().Delete(attachment) - return err - }) - - if err != nil { - return err + var limit = setting.Database.IterateBufferSize + if limit <= 0 { + limit = 50 } - return sess.Commit() + for { + attachements := make([]Attachment, 0, limit) + if err := sess.Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))"). + Cols("id, uuid").Limit(limit). + Asc("id"). + Find(&attachements); err != nil { + return err + } + if len(attachements) == 0 { + return nil + } + + var ids = make([]int64, 0, limit) + for _, attachment := range attachements { + ids = append(ids, attachment.ID) + } + if _, err := sess.In("id", ids).Delete(new(Attachment)); err != nil { + return err + } + + for _, attachment := range attachements { + if err := os.RemoveAll(models.AttachmentLocalPath(attachment.UUID)); err != nil { + return err + } + } + if len(attachements) < limit { + return nil + } + } } diff --git a/models/models.go b/models/models.go index ea550cb839..b963dec17e 100644 --- a/models/models.go +++ b/models/models.go @@ -157,11 +157,9 @@ func SetEngine() (err error) { // so use log file to instead print to stdout. x.SetLogger(NewXORMLogger(setting.Database.LogSQL)) x.ShowSQL(setting.Database.LogSQL) - if setting.Database.UseMySQL { - x.SetMaxIdleConns(setting.Database.MaxIdleConns) - x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) - } - + x.SetMaxOpenConns(setting.Database.MaxOpenConns) + x.SetMaxIdleConns(setting.Database.MaxIdleConns) + x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) return nil } diff --git a/models/pull.go b/models/pull.go index ff1f7b773b..b81f146371 100644 --- a/models/pull.go +++ b/models/pull.go @@ -66,7 +66,6 @@ type PullRequest struct { HeadRepo *Repository `xorm:"-"` BaseRepoID int64 `xorm:"INDEX"` BaseRepo *Repository `xorm:"-"` - HeadUserName string HeadBranch string BaseBranch string ProtectedBranch *ProtectedBranch `xorm:"-"` @@ -79,6 +78,15 @@ type PullRequest struct { MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"` } +// MustHeadUserName returns the HeadRepo's username if failed return blank +func (pr *PullRequest) MustHeadUserName() string { + if err := pr.LoadHeadRepo(); err != nil { + log.Error("LoadHeadRepo: %v", err) + return "" + } + return pr.HeadRepo.MustOwnerName() +} + // Note: don't try to get Issue because will end up recursive querying. func (pr *PullRequest) loadAttributes(e Engine) (err error) { if pr.HasMerged && pr.Merger == nil { @@ -102,6 +110,10 @@ func (pr *PullRequest) LoadAttributes() error { // LoadBaseRepo loads pull request base repository from database func (pr *PullRequest) LoadBaseRepo() error { if pr.BaseRepo == nil { + if pr.HeadRepoID == pr.BaseRepoID && pr.HeadRepo != nil { + pr.BaseRepo = pr.HeadRepo + return nil + } var repo Repository if has, err := x.ID(pr.BaseRepoID).Get(&repo); err != nil { return err @@ -113,6 +125,24 @@ func (pr *PullRequest) LoadBaseRepo() error { return nil } +// LoadHeadRepo loads pull request head repository from database +func (pr *PullRequest) LoadHeadRepo() error { + if pr.HeadRepo == nil { + if pr.HeadRepoID == pr.BaseRepoID && pr.BaseRepo != nil { + pr.HeadRepo = pr.BaseRepo + return nil + } + var repo Repository + if has, err := x.ID(pr.HeadRepoID).Get(&repo); err != nil { + return err + } else if !has { + return ErrRepoNotExist{ID: pr.HeadRepoID} + } + pr.HeadRepo = &repo + } + return nil +} + // LoadIssue loads issue information from database func (pr *PullRequest) LoadIssue() (err error) { return pr.loadIssue(x) @@ -152,7 +182,7 @@ func (pr *PullRequest) GetDefaultMergeMessage() string { return "" } } - return fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch) + return fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.MustHeadUserName(), pr.HeadRepo.Name, pr.BaseBranch) } // GetDefaultSquashMessage returns default message used when squash and merging pull request @@ -352,6 +382,7 @@ func (pr *PullRequest) GetLastCommitStatus() (status *CommitStatus, err error) { if err != nil { return nil, err } + defer headGitRepo.Close() lastCommitID, err := headGitRepo.GetBranchCommitID(pr.HeadBranch) if err != nil { @@ -541,6 +572,7 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) { if err != nil { return nil, fmt.Errorf("OpenRepository: %v", err) } + defer gitRepo.Close() commit, err := gitRepo.GetCommit(mergeCommit[:40]) if err != nil { @@ -925,6 +957,7 @@ func (pr *PullRequest) UpdatePatch() (err error) { if err != nil { return fmt.Errorf("OpenRepository: %v", err) } + defer headGitRepo.Close() // Add a temporary remote. tmpRemote := com.ToStr(time.Now().UnixNano()) @@ -966,6 +999,7 @@ func (pr *PullRequest) PushToBaseRepo() (err error) { if err != nil { return fmt.Errorf("OpenRepository: %v", err) } + defer headGitRepo.Close() tmpRemoteName := fmt.Sprintf("tmp-pull-%d", pr.ID) if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), false); err != nil { @@ -1155,22 +1189,11 @@ func checkForInvalidation(requests PullRequestList, repoID int64, doer *User, br if err != nil { log.Error("PullRequestList.InvalidateCodeComments: %v", err) } + gitRepo.Close() }() return nil } -// ChangeUsernameInPullRequests changes the name of head_user_name -func ChangeUsernameInPullRequests(oldUserName, newUserName string) error { - pr := PullRequest{ - HeadUserName: strings.ToLower(newUserName), - } - _, err := x. - Cols("head_user_name"). - Where("head_user_name = ?", strings.ToLower(oldUserName)). - Update(pr) - return err -} - // checkAndUpdateStatus checks if pull request is possible to leaving checking status, // and set to be either conflict or mergeable. func (pr *PullRequest) checkAndUpdateStatus() { diff --git a/models/pull_test.go b/models/pull_test.go index df051d51bc..8e2436b1a2 100644 --- a/models/pull_test.go +++ b/models/pull_test.go @@ -232,20 +232,6 @@ func TestPullRequestList_LoadAttributes(t *testing.T) { // TODO TestAddTestPullRequestTask -func TestChangeUsernameInPullRequests(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - const newUsername = "newusername" - assert.NoError(t, ChangeUsernameInPullRequests("user1", newUsername)) - - prs := make([]*PullRequest, 0, 10) - assert.NoError(t, x.Where("head_user_name = ?", newUsername).Find(&prs)) - assert.Len(t, prs, 2) - for _, pr := range prs { - assert.Equal(t, newUsername, pr.HeadUserName) - } - CheckConsistencyFor(t, &PullRequest{}) -} - func TestPullRequest_IsWorkInProgress(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/models/release.go b/models/release.go index 03685e0a44..f43d81d822 100644 --- a/models/release.go +++ b/models/release.go @@ -369,7 +369,7 @@ func SyncReleasesWithTags(repo *Repository, gitRepo *git.Repository) error { } // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID -func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID, posterID int64) error { +func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { _, err := x.Table("release"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). diff --git a/models/repo.go b/models/repo.go index aa2cf06f32..dc0b42ee31 100644 --- a/models/repo.go +++ b/models/repo.go @@ -998,6 +998,7 @@ func MigrateRepositoryGitData(doer, u *User, repo *Repository, opts api.MigrateR if err != nil { return repo, fmt.Errorf("OpenRepository: %v", err) } + defer gitRepo.Close() repo.IsEmpty, err = gitRepo.IsEmpty() if err != nil { @@ -1430,6 +1431,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err IsFsckEnabled: !opts.IsMirror, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, Status: opts.Status, + IsEmpty: !opts.AutoInit, } sess := x.NewSession() @@ -1779,6 +1781,12 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { return sess.Commit() } +// UpdateRepositoryStatus updates a repository's status +func UpdateRepositoryStatus(repoID int64, status RepositoryStatus) error { + _, err := x.Exec("UPDATE repository SET status = ? WHERE id = ?", status, repoID) + return err +} + // UpdateRepositoryUpdatedTime updates a repository's updated time func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { _, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) @@ -1858,6 +1866,17 @@ func DeleteRepository(doer *User, uid, repoID int64) error { } } + attachments := make([]*Attachment, 0, 20) + if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id"). + Where("`release`.repo_id = ?", repoID). + Find(&attachments); err != nil { + return err + } + releaseAttachments := make([]string, 0, len(attachments)) + for i := 0; i < len(attachments); i++ { + releaseAttachments = append(releaseAttachments, attachments[i].LocalPath()) + } + if err = deleteBeans(sess, &Access{RepoID: repo.ID}, &Action{RepoID: repo.ID}, @@ -1908,13 +1927,13 @@ func DeleteRepository(doer *User, uid, repoID int64) error { return err } - attachmentPaths := make([]string, 0, 20) - attachments := make([]*Attachment, 0, len(attachmentPaths)) + attachments = attachments[:0] if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id"). Where("issue.repo_id = ?", repoID). Find(&attachments); err != nil { return err } + attachmentPaths := make([]string, 0, len(attachments)) for j := range attachments { attachmentPaths = append(attachmentPaths, attachments[j].LocalPath()) } @@ -1951,11 +1970,6 @@ func DeleteRepository(doer *User, uid, repoID int64) error { return err } - // Remove attachment files. - for i := range attachmentPaths { - removeAllWithNotice(sess, "Delete attachment", attachmentPaths[i]) - } - // Remove LFS objects var lfsObjects []*LFSMetaObject if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { @@ -1995,6 +2009,8 @@ func DeleteRepository(doer *User, uid, repoID int64) error { return fmt.Errorf("Commit: %v", err) } + sess.Close() + if org.IsOrganization() { if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{ Action: api.HookRepoDeleted, @@ -2007,6 +2023,19 @@ func DeleteRepository(doer *User, uid, repoID int64) error { go HookQueue.Add(repo.ID) } + // We should always delete the files after the database transaction succeed. If + // we delete the file but the database rollback, the repository will be borken. + + // Remove issue attachment files. + for i := range attachmentPaths { + removeAllWithNotice(x, "Delete issue attachment", attachmentPaths[i]) + } + + // Remove release attachment files. + for i := range releaseAttachments { + removeAllWithNotice(x, "Delete release attachment", releaseAttachments[i]) + } + if len(repo.Avatar) > 0 { avatarPath := repo.CustomAvatarPath() if com.IsExist(avatarPath) { @@ -2782,3 +2811,9 @@ func (repo *Repository) GetOriginalURLHostname() string { return u.Host } + +// UpdateRepositoryCols updates repository's columns +func UpdateRepositoryCols(repo *Repository, cols ...string) error { + _, err := x.ID(repo.ID).Cols(cols...).Update(repo) + return err +} diff --git a/models/repo_activity.go b/models/repo_activity.go index 04612ae1ef..c95af85495 100644 --- a/models/repo_activity.go +++ b/models/repo_activity.go @@ -64,6 +64,8 @@ func GetActivityStats(repo *Repository, timeFrom time.Time, releases, issues, pr if err != nil { return nil, fmt.Errorf("OpenRepository: %v", err) } + defer gitRepo.Close() + code, err := gitRepo.GetCodeActivityStats(timeFrom, repo.DefaultBranch) if err != nil { return nil, fmt.Errorf("FillFromGit: %v", err) @@ -79,6 +81,8 @@ func GetActivityStatsTopAuthors(repo *Repository, timeFrom time.Time, count int) if err != nil { return nil, fmt.Errorf("OpenRepository: %v", err) } + defer gitRepo.Close() + code, err := gitRepo.GetCodeActivityStats(timeFrom, "") if err != nil { return nil, fmt.Errorf("FillFromGit: %v", err) diff --git a/models/repo_branch.go b/models/repo_branch.go index dee6ef3d7e..c513231836 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -23,6 +23,7 @@ func (repo *Repository) GetBranch(branch string) (*git.Branch, error) { if err != nil { return nil, err } + defer gitRepo.Close() return gitRepo.GetBranch(branch) } @@ -38,6 +39,7 @@ func (repo *Repository) CheckBranchName(name string) error { if err != nil { return err } + defer gitRepo.Close() branches, err := repo.GetBranches() if err != nil { @@ -94,6 +96,7 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st log.Error("Unable to open temporary repository: %s (%v)", basePath, err) return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } + defer gitRepo.Close() if err = gitRepo.CreateBranch(branchName, oldBranchName); err != nil { log.Error("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err) @@ -140,6 +143,7 @@ func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName log.Error("Unable to open temporary repository: %s (%v)", basePath, err) return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } + defer gitRepo.Close() if err = gitRepo.CreateBranch(branchName, commit); err != nil { log.Error("Unable to create branch: %s from %s. (%v)", branchName, commit, err) diff --git a/models/repo_indexer.go b/models/repo_indexer.go index b842a1c87f..673a0743a4 100644 --- a/models/repo_indexer.go +++ b/models/repo_indexer.go @@ -58,7 +58,7 @@ func (repo *Repository) updateIndexerStatus(sha string) error { } type repoIndexerOperation struct { - repo *Repository + repoID int64 deleted bool watchers []chan<- error } @@ -122,7 +122,7 @@ func populateRepoIndexer(maxRepoID int64) { } for _, repo := range repos { repoIndexerOperationQueue <- repoIndexerOperation{ - repo: repo, + repoID: repo.ID, deleted: false, } maxRepoID = repo.ID - 1 @@ -131,7 +131,12 @@ func populateRepoIndexer(maxRepoID int64) { log.Info("Done populating the repo indexer with existing repositories") } -func updateRepoIndexer(repo *Repository) error { +func updateRepoIndexer(repoID int64) error { + repo, err := getRepositoryByID(x, repoID) + if err != nil { + return err + } + sha, err := getDefaultBranchSha(repo) if err != nil { return err @@ -172,7 +177,7 @@ type fileUpdate struct { } func getDefaultBranchSha(repo *Repository) (string, error) { - stdout, err := git.NewCommand("show-ref", "-s", repo.DefaultBranch).RunInDir(repo.RepoPath()) + stdout, err := git.NewCommand("show-ref", "-s", git.BranchPrefix+repo.DefaultBranch).RunInDir(repo.RepoPath()) if err != nil { return "", err } @@ -339,11 +344,11 @@ func processRepoIndexerOperationQueue() { op := <-repoIndexerOperationQueue var err error if op.deleted { - if err = indexer.DeleteRepoFromIndexer(op.repo.ID); err != nil { + if err = indexer.DeleteRepoFromIndexer(op.repoID); err != nil { log.Error("DeleteRepoFromIndexer: %v", err) } } else { - if err = updateRepoIndexer(op.repo); err != nil { + if err = updateRepoIndexer(op.repoID); err != nil { log.Error("updateRepoIndexer: %v", err) } } @@ -355,12 +360,12 @@ func processRepoIndexerOperationQueue() { // DeleteRepoFromIndexer remove all of a repository's entries from the indexer func DeleteRepoFromIndexer(repo *Repository, watchers ...chan<- error) { - addOperationToQueue(repoIndexerOperation{repo: repo, deleted: true, watchers: watchers}) + addOperationToQueue(repoIndexerOperation{repoID: repo.ID, deleted: true, watchers: watchers}) } // UpdateRepoIndexer update a repository's entries in the indexer func UpdateRepoIndexer(repo *Repository, watchers ...chan<- error) { - addOperationToQueue(repoIndexerOperation{repo: repo, deleted: false, watchers: watchers}) + addOperationToQueue(repoIndexerOperation{repoID: repo.ID, deleted: false, watchers: watchers}) } func addOperationToQueue(op repoIndexerOperation) { diff --git a/models/repo_list.go b/models/repo_list.go index 692d4d002f..77efb090af 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -120,7 +120,8 @@ type SearchRepoOptions struct { StarredByID int64 Page int IsProfile bool - AllPublic bool // Include also all public repositories + AllPublic bool // Include also all public repositories of users and public organisations + AllLimited bool // Include also all public repositories of limited organisations PageSize int // Can be smaller than or equal to setting.ExplorePagingNum // None -> include collaborative AND non-collaborative // True -> include just collaborative @@ -240,7 +241,11 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { } if opts.AllPublic { - accessCond = accessCond.Or(builder.Eq{"is_private": false}) + accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})))) + } + + if opts.AllLimited { + accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})))) } cond = cond.And(accessCond) diff --git a/models/repo_list_test.go b/models/repo_list_test.go index e3a7acd4a4..836996cacb 100644 --- a/models/repo_list_test.go +++ b/models/repo_list_test.go @@ -177,8 +177,8 @@ func TestSearchRepository(t *testing.T) { opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true}, count: 22}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, - count: 28}, + opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true}, + count: 27}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, count: 15}, diff --git a/models/repo_tag.go b/models/repo_tag.go deleted file mode 100644 index 3864b7a12a..0000000000 --- a/models/repo_tag.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2019 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 models - -import ( - "code.gitea.io/gitea/modules/git" -) - -// GetTagsByPath returns repo tags by its path -func GetTagsByPath(path string) ([]*git.Tag, error) { - gitRepo, err := git.OpenRepository(path) - if err != nil { - return nil, err - } - - return gitRepo.GetTagInfos() -} - -// GetTags return repo's tags -func (repo *Repository) GetTags() ([]*git.Tag, error) { - return GetTagsByPath(repo.RepoPath()) -} diff --git a/models/review.go b/models/review.go index 454d16ee88..5c1449c18a 100644 --- a/models/review.go +++ b/models/review.go @@ -135,6 +135,7 @@ func (r *Review) publish(e *xorm.Engine) error { Repo: review.Issue.Repo, Type: comm.Type, Content: comm.Content, + Review: r, }, comm); err != nil { log.Warn("sendCreateCommentAction: %v", err) } diff --git a/models/ssh_key.go b/models/ssh_key.go index d1132bf0c6..edc6d45cd8 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -107,7 +107,7 @@ func parseKeyString(content string) (string, error) { var keyType, keyContent, keyComment string - if content[:len(ssh2keyStart)] == ssh2keyStart { + if strings.HasPrefix(content, ssh2keyStart) { // Parse SSH2 file format. // Transform all legal line endings to a single "\n". diff --git a/models/ssh_key_test.go b/models/ssh_key_test.go index 4bb612a671..95cd4eeb1a 100644 --- a/models/ssh_key_test.go +++ b/models/ssh_key_test.go @@ -131,6 +131,19 @@ AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf _, err := CheckPublicKeyString(test.content) assert.NoError(t, err) } + + for _, invalidKeys := range []struct { + content string + }{ + {"test"}, + {"---- NOT A REAL KEY ----"}, + {"bad\nkey"}, + {"\t\t:)\t\r\n"}, + {"\r\ntest \r\ngitea\r\n\r\n"}, + } { + _, err := CheckPublicKeyString(invalidKeys.content) + assert.Error(t, err) + } } func Test_calcFingerprint(t *testing.T) { diff --git a/models/task.go b/models/task.go index cb878d387c..763644e039 100644 --- a/models/task.go +++ b/models/task.go @@ -196,7 +196,7 @@ func CreateMigrateTask(doer, u *User, opts base.MigrateOptions) (*Task, error) { repo, err := CreateRepository(doer, u, CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, - OriginalURL: opts.CloneAddr, + OriginalURL: opts.OriginalURL, IsPrivate: opts.Private, IsMirror: opts.Mirror, Status: RepositoryBeingMigrated, diff --git a/models/user.go b/models/user.go index 030e23c383..64b832a7b6 100644 --- a/models/user.go +++ b/models/user.go @@ -502,7 +502,7 @@ func (u *User) ValidatePassword(passwd string) bool { // IsPasswordSet checks if the password is set or left empty func (u *User) IsPasswordSet() bool { - return len(u.Passwd) > 0 + return !u.ValidatePassword("") } // UploadAvatar saves custom avatar for user. @@ -822,6 +822,7 @@ var ( ".", "..", ".well-known", + "search", } reservedUserPatterns = []string{"*.keys", "*.gpg"} ) @@ -994,10 +995,6 @@ func ChangeUserName(u *User, newUserName string) (err error) { return ErrUserAlreadyExist{newUserName} } - if err = ChangeUsernameInPullRequests(u.Name, newUserName); err != nil { - return fmt.Errorf("ChangeUsernameInPullRequests: %v", err) - } - // Do not fail if directory does not exist if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { return fmt.Errorf("Rename user directory: %v", err) @@ -1718,6 +1715,15 @@ func SyncExternalUsers() { continue } + if len(sr) == 0 { + if !s.LDAP().AllowDeactivateAll { + log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users") + continue + } else { + log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings") + } + } + for _, su := range sr { if len(su.Username) == 0 { continue diff --git a/models/user_heatmap_test.go b/models/user_heatmap_test.go index a71202d857..f882b35247 100644 --- a/models/user_heatmap_test.go +++ b/models/user_heatmap_test.go @@ -17,7 +17,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { CountResult int JSONResult string }{ - {2, 1, `[{"timestamp":1540080000,"contributions":1}]`}, + {2, 1, `[{"timestamp":1571616000,"contributions":1}]`}, {3, 0, `[]`}, } // Prepare @@ -41,7 +41,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { // Get the heatmap and compare heatmap, err := GetUserHeatmapDataByUser(user) assert.NoError(t, err) - assert.Equal(t, len(actions), len(heatmap)) + assert.Equal(t, len(actions), len(heatmap), "invalid action count: did the test data became too old?") assert.Equal(t, tc.CountResult, len(heatmap)) //Test JSON rendering diff --git a/models/webhook.go b/models/webhook.go index f657c18792..6f2162c799 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -833,6 +833,8 @@ func (t *HookTask) deliver() error { return err } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } case http.MethodGet: u, err := url.Parse(t.URL) diff --git a/models/webhook_slack.go b/models/webhook_slack.go index 9c179bb24a..dcc1047599 100644 --- a/models/webhook_slack.go +++ b/models/webhook_slack.go @@ -36,10 +36,11 @@ type SlackPayload struct { // SlackAttachment contains the slack message type SlackAttachment struct { - Fallback string `json:"fallback"` - Color string `json:"color"` - Title string `json:"title"` - Text string `json:"text"` + Fallback string `json:"fallback"` + Color string `json:"color"` + Title string `json:"title"` + TitleLink string `json:"title_link"` + Text string `json:"text"` } // SetSecret sets the slack secret @@ -133,70 +134,78 @@ func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, e func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index), - fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)) - var text, title, attachmentText string + title := SlackTextFormatter(fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)) + titleLink := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index) + repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) + var text, attachmentText string + switch p.Action { case api.HookIssueOpened: - text = fmt.Sprintf("[%s] Issue submitted by %s", p.Repository.FullName, senderLink) - title = titleLink + text = fmt.Sprintf("[%s] Issue opened by %s", repoLink, senderLink) attachmentText = SlackTextFormatter(p.Issue.Body) case api.HookIssueClosed: - text = fmt.Sprintf("[%s] Issue closed: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue closed: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueReOpened: - text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue re-opened: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueEdited: - text = fmt.Sprintf("[%s] Issue edited: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue edited: [%s](%s) by %s", repoLink, title, titleLink, senderLink) attachmentText = SlackTextFormatter(p.Issue.Body) case api.HookIssueAssigned: - text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName, + text = fmt.Sprintf("[%s] Issue assigned to %s: [%s](%s) by %s", repoLink, SlackLinkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName), - titleLink, senderLink) + title, titleLink, senderLink) case api.HookIssueUnassigned: - text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue unassigned: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueLabelUpdated: - text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue labels updated: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueLabelCleared: - text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue labels cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueSynchronized: - text = fmt.Sprintf("[%s] Issue synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue synchronized: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueMilestoned: - text = fmt.Sprintf("[%s] Issue milestoned: #%s %s", p.Repository.FullName, titleLink, senderLink) + mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID) + text = fmt.Sprintf("[%s] Issue milestoned to [%s](%s): [%s](%s) by %s", repoLink, + p.Issue.Milestone.Title, mileStoneLink, title, titleLink, senderLink) case api.HookIssueDemilestoned: - text = fmt.Sprintf("[%s] Issue milestone cleared: #%s %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue milestone cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink) } - return &SlackPayload{ + pl := &SlackPayload{ Channel: slack.Channel, Text: text, Username: slack.Username, IconURL: slack.IconURL, - Attachments: []SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, - }}, - }, nil + } + if attachmentText != "" { + pl.Attachments = []SlackAttachment{{ + Color: slack.Color, + Title: title, + TitleLink: titleLink, + Text: attachmentText, + }} + } + + return pl, nil } func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)), - fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) - var text, title, attachmentText string + title := SlackTextFormatter(fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) + repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) + var text, titleLink, attachmentText string + switch p.Action { case api.HookIssueCommentCreated: - text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink) - title = titleLink + text = fmt.Sprintf("[%s] New comment created by %s", repoLink, senderLink) + titleLink = fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) attachmentText = SlackTextFormatter(p.Comment.Body) case api.HookIssueCommentEdited: - text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink) - title = titleLink + text = fmt.Sprintf("[%s] Comment edited by %s", repoLink, senderLink) + titleLink = fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) attachmentText = SlackTextFormatter(p.Comment.Body) case api.HookIssueCommentDeleted: - text = fmt.Sprintf("[%s] Comment deleted by %s", p.Repository.FullName, senderLink) - title = SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index), - fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) + text = fmt.Sprintf("[%s] Comment deleted by %s", repoLink, senderLink) + titleLink = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index) attachmentText = SlackTextFormatter(p.Comment.Body) } @@ -206,9 +215,10 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) ( Username: slack.Username, IconURL: slack.IconURL, Attachments: []SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, + Color: slack.Color, + Title: title, + TitleLink: titleLink, + Text: attachmentText, }}, }, nil } @@ -273,73 +283,85 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e Username: slack.Username, IconURL: slack.IconURL, Attachments: []SlackAttachment{{ - Color: slack.Color, - Text: attachmentText, + Color: slack.Color, + Title: p.Repo.HTMLURL, + TitleLink: p.Repo.HTMLURL, + Text: attachmentText, }}, }, nil } func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index), - fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)) - var text, title, attachmentText string + title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) + titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) + repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) + var text, attachmentText string + switch p.Action { case api.HookIssueOpened: - text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink) - title = titleLink + text = fmt.Sprintf("[%s] Pull request opened by %s", repoLink, senderLink) attachmentText = SlackTextFormatter(p.PullRequest.Body) case api.HookIssueClosed: if p.PullRequest.HasMerged { - text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request merged: [%s](%s) by %s", repoLink, title, titleLink, senderLink) } else { - text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request closed: [%s](%s) by %s", repoLink, title, titleLink, senderLink) } case api.HookIssueReOpened: - text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request re-opened: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueEdited: - text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request edited: [%s](%s) by %s", repoLink, title, titleLink, senderLink) attachmentText = SlackTextFormatter(p.PullRequest.Body) case api.HookIssueAssigned: list := make([]string, len(p.PullRequest.Assignees)) for i, user := range p.PullRequest.Assignees { list[i] = SlackLinkFormatter(setting.AppURL+user.UserName, user.UserName) } - text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName, + text = fmt.Sprintf("[%s] Pull request assigned to %s: [%s](%s) by %s", repoLink, strings.Join(list, ", "), - titleLink, senderLink) + title, titleLink, senderLink) case api.HookIssueUnassigned: - text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request unassigned: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueLabelUpdated: - text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request labels updated: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueLabelCleared: - text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request labels cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueSynchronized: - text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request synchronized: [%s](%s) by %s", repoLink, title, titleLink, senderLink) case api.HookIssueMilestoned: - text = fmt.Sprintf("[%s] Pull request milestoned: #%s %s", p.Repository.FullName, titleLink, senderLink) + mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID) + text = fmt.Sprintf("[%s] Pull request milestoned to [%s](%s): [%s](%s) %s", repoLink, + p.PullRequest.Milestone.Title, mileStoneLink, title, titleLink, senderLink) case api.HookIssueDemilestoned: - text = fmt.Sprintf("[%s] Pull request milestone cleared: #%s %s", p.Repository.FullName, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request milestone cleared: [%s](%s) %s", repoLink, title, titleLink, senderLink) } - return &SlackPayload{ + pl := &SlackPayload{ Channel: slack.Channel, Text: text, Username: slack.Username, IconURL: slack.IconURL, - Attachments: []SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, - }}, - }, nil + } + if attachmentText != "" { + pl.Attachments = []SlackAttachment{{ + Color: slack.Color, + Title: title, + TitleLink: titleLink, + Text: attachmentText, + }} + } + + return pl, nil } func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event HookEventType) (*SlackPayload, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index), - fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)) - var text, title, attachmentText string + title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) + titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) + repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) + var text string + switch p.Action { case api.HookIssueSynchronized: action, err := parseHookPullRequestEventType(event) @@ -347,7 +369,7 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM return nil, err } - text = fmt.Sprintf("[%s] Pull request review %s : %s by %s", p.Repository.FullName, action, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink) } return &SlackPayload{ @@ -355,21 +377,16 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM Text: text, Username: slack.Username, IconURL: slack.IconURL, - Attachments: []SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, - }}, }, nil } func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - var text, title, attachmentText string + var text string + switch p.Action { case api.HookRepoCreated: text = fmt.Sprintf("[%s] Repository created by %s", p.Repository.FullName, senderLink) - title = p.Repository.HTMLURL case api.HookRepoDeleted: text = fmt.Sprintf("[%s] Repository deleted by %s", p.Repository.FullName, senderLink) } @@ -379,11 +396,6 @@ func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*Sla Text: text, Username: slack.Username, IconURL: slack.IconURL, - Attachments: []SlackAttachment{{ - Color: slack.Color, - Title: title, - Text: attachmentText, - }}, }, nil } diff --git a/models/wiki.go b/models/wiki.go index 0460e0f079..8a2f739dad 100644 --- a/models/wiki.go +++ b/models/wiki.go @@ -140,6 +140,7 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con log.Error("Unable to open temporary repository: %s (%v)", basePath, err) return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } + defer gitRepo.Close() if hasMasterBranch { if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { @@ -276,6 +277,7 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error) log.Error("Unable to open temporary repository: %s (%v)", basePath, err) return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } + defer gitRepo.Close() if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) diff --git a/models/wiki_test.go b/models/wiki_test.go index 991a3d95b9..37c0a86635 100644 --- a/models/wiki_test.go +++ b/models/wiki_test.go @@ -161,6 +161,7 @@ func TestRepository_AddWikiPage(t *testing.T) { // Now need to show that the page has been added: gitRepo, err := git.OpenRepository(repo.WikiPath()) assert.NoError(t, err) + defer gitRepo.Close() masterTree, err := gitRepo.GetTree("master") assert.NoError(t, err) wikiPath := WikiNameToFilename(wikiName) @@ -214,6 +215,7 @@ func TestRepository_EditWikiPage(t *testing.T) { _, err := masterTree.GetTreeEntryByPath("Home.md") assert.Error(t, err) } + gitRepo.Close() } } @@ -226,6 +228,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) { // Now need to show that the page has been added: gitRepo, err := git.OpenRepository(repo.WikiPath()) assert.NoError(t, err) + defer gitRepo.Close() masterTree, err := gitRepo.GetTree("master") assert.NoError(t, err) wikiPath := WikiNameToFilename("Home") diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index 358472a385..6a1cebca85 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -30,6 +30,7 @@ type AuthenticationForm struct { SearchPageSize int Filter string AdminFilter string + AllowDeactivateAll bool IsActive bool IsSyncEnabled bool SMTPAuth string diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index ed83a77e12..7f0d2c93f3 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -47,6 +47,7 @@ type Source struct { Filter string // Query filter to validate entry AdminFilter string // Query filter to check if user is admin Enabled bool // if this source is disabled + AllowDeactivateAll bool // Allow an empty search response to deactivate all users from this source } // SearchResult : user data diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 8d10fc1570..a9985fdcbc 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -499,9 +499,9 @@ func (f SubmitReviewForm) HasEmptyContent() bool { // NewReleaseForm form for creating release type NewReleaseForm struct { - TagName string `binding:"Required;GitRefName"` - Target string `form:"tag_target" binding:"Required"` - Title string `binding:"Required"` + TagName string `binding:"Required;GitRefName;MaxSize(255)"` + Target string `form:"tag_target" binding:"Required;MaxSize(255)"` + Title string `binding:"Required;MaxSize(255)"` Content string Draft string Prerelease bool @@ -515,7 +515,7 @@ func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) bin // EditReleaseForm form for changing release type EditReleaseForm struct { - Title string `form:"title" binding:"Required"` + Title string `form:"title" binding:"Required;MaxSize(255)"` Content string `form:"content"` Draft string `form:"draft"` Prerelease bool `form:"prerelease"` diff --git a/modules/context/api.go b/modules/context/api.go index 024ae487f1..c1de37dd21 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -186,7 +186,16 @@ func ReferencesGitRepo(allowEmpty bool) macaron.Handler { return } ctx.Repo.GitRepo = gitRepo + // We opened it, we should close it + defer func() { + // If it's been set to nil then assume someone else has closed it. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + }() } + + ctx.Next() } } diff --git a/modules/context/context.go b/modules/context/context.go index ef6c19ed12..1db9be1121 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// 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. @@ -122,7 +123,7 @@ func (ctx *Context) RedirectToFirst(location ...string) { } u, err := url.Parse(loc) - if err != nil || (u.Scheme != "" && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) { + if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) { continue } diff --git a/modules/context/org.go b/modules/context/org.go index 4867474334..10791c9d01 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -63,7 +63,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { // Force redirection when username is actually a user. if !org.IsOrganization() { - ctx.Redirect("/" + org.Name) + ctx.Redirect(setting.AppSubURL + "/" + org.Name) return } diff --git a/modules/context/repo.go b/modules/context/repo.go index f4af19a0e8..59b82b6b07 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -91,12 +91,12 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? isAssigned, _ := models.IsUserAssignedToIssue(issue, user) return r.Repository.IsTimetrackerEnabled() && (!r.Repository.AllowOnlyContributorsToTrackTime() || - r.Permission.CanWrite(models.UnitTypeIssues) || issue.IsPoster(user.ID) || isAssigned) + r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned) } // CanCreateIssueDependencies returns whether or not a user can create dependencies. -func (r *Repository) CanCreateIssueDependencies(user *models.User) bool { - return r.Permission.CanWrite(models.UnitTypeIssues) && r.Repository.IsDependenciesEnabled() +func (r *Repository) CanCreateIssueDependencies(user *models.User, isPull bool) bool { + return r.Repository.IsDependenciesEnabled() && r.Permission.CanWriteIssuesOrPulls(isPull) } // GetCommitsCount returns cached commit count for current view @@ -236,7 +236,7 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) { if ctx.Req.URL.RawQuery != "" { redirectPath += "?" + ctx.Req.URL.RawQuery } - ctx.Redirect(redirectPath) + ctx.Redirect(path.Join(setting.AppSubURL, redirectPath)) } func repoAssignment(ctx *Context, repo *models.Repository) { @@ -414,8 +414,8 @@ func RepoAssignment() macaron.Handler { } } - // repo is empty and display enable - if ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBeingCreated() { + // Disable everything when the repo is being created + if ctx.Repo.Repository.IsBeingCreated() { ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch return } @@ -427,6 +427,21 @@ func RepoAssignment() macaron.Handler { } ctx.Repo.GitRepo = gitRepo + // We opened it, we should close it + defer func() { + // If it's been set to nil then assume someone else has closed it. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + }() + + // Stop at this point when the repo is empty. + if ctx.Repo.Repository.IsEmpty { + ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch + ctx.Next() + return + } + tags, err := ctx.Repo.GitRepo.GetTags() if err != nil { ctx.ServerError("GetTags", err) @@ -482,6 +497,7 @@ func RepoAssignment() macaron.Handler { ctx.Data["GoDocDirectory"] = prefix + "{/dir}" ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}" } + ctx.Next() } } @@ -587,6 +603,13 @@ func RepoRefByType(refType RepoRefType) macaron.Handler { ctx.ServerError("RepoRef Invalid repo "+repoPath, err) return } + // We opened it, we should close it + defer func() { + // If it's been set to nil then assume someone else has closed it. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + }() } // Get default branch. @@ -675,6 +698,8 @@ func RepoRefByType(refType RepoRefType) macaron.Handler { return } ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount + + ctx.Next() } } diff --git a/modules/git/blame.go b/modules/git/blame.go index 548236b657..4f4343fe96 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -87,10 +87,11 @@ func (r *BlameReader) Close() error { // CreateBlameReader creates reader for given repository, commit and file func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) { - _, err := OpenRepository(repoPath) + gitRepo, err := OpenRepository(repoPath) if err != nil { return nil, err } + gitRepo.Close() return createBlameReader(repoPath, GitExecutable, "blame", commitID, "--porcelain", "--", file) } diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 66c046ecc8..9043de5955 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -37,6 +37,8 @@ THE SOFTWARE. ` repo, err := OpenRepository("../../.git") assert.NoError(t, err) + defer repo.Close() + testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") assert.NoError(t, err) @@ -55,6 +57,8 @@ func Benchmark_Blob_Data(b *testing.B) { if err != nil { b.Fatal(err) } + defer repo.Close() + testBlob, err := repo.GetBlob("a8d4b49dd073a4a38a7e58385eeff7cc52568697") if err != nil { b.Fatal(err) diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go index 71637d188a..ac7bc43c4e 100644 --- a/modules/git/commit_info_test.go +++ b/modules/git/commit_info_test.go @@ -77,6 +77,8 @@ func TestEntries_GetCommitsInfo(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() + testGetCommitsInfo(t, bareRepo1) clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestEntries_GetCommitsInfo") @@ -84,6 +86,8 @@ func TestEntries_GetCommitsInfo(t *testing.T) { defer os.RemoveAll(clonedPath) clonedRepo1, err := OpenRepository(clonedPath) assert.NoError(t, err) + defer clonedRepo1.Close() + testGetCommitsInfo(t, clonedRepo1) } @@ -101,13 +105,16 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) { for _, benchmark := range benchmarks { var commit *Commit var entries Entries + var repo *Repository if repoPath, err := cloneRepo(benchmark.url, benchmarkReposDir, benchmark.name); err != nil { b.Fatal(err) - } else if repo, err := OpenRepository(repoPath); err != nil { + } else if repo, err = OpenRepository(repoPath); err != nil { b.Fatal(err) } else if commit, err = repo.GetBranchCommit("master"); err != nil { + repo.Close() b.Fatal(err) } else if entries, err = commit.Tree.ListEntries(); err != nil { + repo.Close() b.Fatal(err) } entries.Sort() @@ -120,5 +127,6 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) { } } }) + repo.Close() } } diff --git a/modules/git/git.go b/modules/git/git.go index 964760dfda..df50eac72a 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -8,6 +8,7 @@ package git import ( "fmt" "os/exec" + "runtime" "strings" "time" @@ -133,6 +134,13 @@ func Init() error { return fmt.Errorf("Failed to execute 'git config --global gc.writeCommitGraph true': %s", stderr) } } + + if runtime.GOOS == "windows" { + if _, stderr, err := process.GetManager().Exec("git.Init(git config --global core.longpaths true)", + GitExecutable, "config", "--global", "core.longpaths", "true"); err != nil { + return fmt.Errorf("Failed to execute 'git config --global core.longpaths true': %s", stderr) + } + } return nil } diff --git a/modules/git/hook.go b/modules/git/hook.go index e966591668..245c6b92ce 100644 --- a/modules/git/hook.go +++ b/modules/git/hook.go @@ -90,6 +90,11 @@ func (h *Hook) Update() error { h.IsActive = false return nil } + d := filepath.Dir(h.path) + if err := os.MkdirAll(d, os.ModePerm); err != nil { + return err + } + err := ioutil.WriteFile(h.path, []byte(strings.Replace(h.Content, "\r", "", -1)), os.ModePerm) if err != nil { return err diff --git a/modules/git/notes_test.go b/modules/git/notes_test.go index bf010b9a71..b7939e6913 100644 --- a/modules/git/notes_test.go +++ b/modules/git/notes_test.go @@ -15,6 +15,7 @@ func TestGetNotes(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() note := Note{} err = GetNote(bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e) @@ -27,6 +28,7 @@ func TestGetNestedNotes(t *testing.T) { repoPath := filepath.Join(testReposDir, "repo3_notes") repo, err := OpenRepository(repoPath) assert.NoError(t, err) + defer repo.Close() note := Note{} err = GetNote(repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", ¬e) diff --git a/modules/git/repo.go b/modules/git/repo.go index 1a9112132f..f3453442c9 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -17,6 +17,7 @@ import ( "strings" "time" + gitealog "code.gitea.io/gitea/modules/log" "github.com/unknwon/com" "gopkg.in/src-d/go-billy.v4/osfs" gogit "gopkg.in/src-d/go-git.v4" @@ -107,6 +108,21 @@ func OpenRepository(repoPath string) (*Repository, error) { }, nil } +// Close this repository, in particular close the underlying gogitStorage if this is not nil +func (repo *Repository) Close() { + if repo == nil || repo.gogitStorage == nil { + return + } + if err := repo.gogitStorage.Close(); err != nil { + gitealog.Error("Error closing storage: %v", err) + } +} + +// GoGitRepo gets the go-git repo representation +func (repo *Repository) GoGitRepo() *gogit.Repository { + return repo.gogitRepo +} + // IsEmpty Check if repository is empty. func (repo *Repository) IsEmpty() (bool, error) { var errbuf strings.Builder diff --git a/modules/git/repo_blob_test.go b/modules/git/repo_blob_test.go index 128a227829..52a124db2a 100644 --- a/modules/git/repo_blob_test.go +++ b/modules/git/repo_blob_test.go @@ -17,6 +17,7 @@ func TestRepository_GetBlob_Found(t *testing.T) { repoPath := filepath.Join(testReposDir, "repo1_bare") r, err := OpenRepository(repoPath) assert.NoError(t, err) + defer r.Close() testCases := []struct { OID string @@ -44,6 +45,7 @@ func TestRepository_GetBlob_NotExist(t *testing.T) { repoPath := filepath.Join(testReposDir, "repo1_bare") r, err := OpenRepository(repoPath) assert.NoError(t, err) + defer r.Close() testCase := "0000000000000000000000000000000000000000" testError := ErrNotExist{testCase, ""} @@ -57,6 +59,7 @@ func TestRepository_GetBlob_NoId(t *testing.T) { repoPath := filepath.Join(testReposDir, "repo1_bare") r, err := OpenRepository(repoPath) assert.NoError(t, err) + defer r.Close() testCase := "" testError := fmt.Errorf("Length must be 40: %s", testCase) diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index a2bf9ac973..e79bab76a6 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -108,6 +108,7 @@ func GetBranchesByPath(path string) ([]*Branch, error) { if err != nil { return nil, err } + defer gitRepo.Close() brs, err := gitRepo.GetBranches() if err != nil { diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index 08736d702e..33d31aef68 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -15,6 +15,7 @@ func TestRepository_GetBranches(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() branches, err := bareRepo1.GetBranches() @@ -29,6 +30,7 @@ func BenchmarkRepository_GetBranches(b *testing.B) { if err != nil { b.Fatal(err) } + defer bareRepo1.Close() for i := 0; i < b.N; i++ { _, err := bareRepo1.GetBranches() diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index 6d8ee6453f..87dd6763b3 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -15,6 +15,7 @@ func TestRepository_GetCommitBranches(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() // these test case are specific to the repo1_bare test repo testCases := []struct { @@ -41,6 +42,7 @@ func TestGetTagCommitWithSignature(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a") assert.NoError(t, err) @@ -54,6 +56,7 @@ func TestGetCommitWithBadCommitID(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() commit, err := bareRepo1.GetCommit("bad_branch") assert.Nil(t, commit) diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go index e194788773..def67fa87b 100644 --- a/modules/git/repo_compare_test.go +++ b/modules/git/repo_compare_test.go @@ -20,6 +20,7 @@ func TestGetFormatPatch(t *testing.T) { defer os.RemoveAll(clonedPath) repo, err := OpenRepository(clonedPath) assert.NoError(t, err) + defer repo.Close() rd, err := repo.GetFormatPatch("8d92fc95^", "8d92fc95") assert.NoError(t, err) patchb, err := ioutil.ReadAll(rd) diff --git a/modules/git/repo_ref_test.go b/modules/git/repo_ref_test.go index d32b34994c..303c496c1d 100644 --- a/modules/git/repo_ref_test.go +++ b/modules/git/repo_ref_test.go @@ -15,6 +15,7 @@ func TestRepository_GetRefs(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() refs, err := bareRepo1.GetRefs() @@ -38,6 +39,7 @@ func TestRepository_GetRefsFiltered(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() refs, err := bareRepo1.GetRefsFiltered(TagPrefix) diff --git a/modules/git/repo_stats_test.go b/modules/git/repo_stats_test.go index 6fbcb7ac13..bc1f6a5662 100644 --- a/modules/git/repo_stats_test.go +++ b/modules/git/repo_stats_test.go @@ -16,6 +16,7 @@ func TestRepository_GetCodeActivityStats(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() timeFrom, err := time.Parse(time.RFC3339, "2016-01-01T00:00:00+00:00") assert.NoError(t, err) diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index ab9742afc5..90f2b37358 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -16,6 +16,7 @@ func TestRepository_GetTags(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) assert.NoError(t, err) + defer bareRepo1.Close() tags, err := bareRepo1.GetTagInfos() assert.NoError(t, err) @@ -34,6 +35,7 @@ func TestRepository_GetTag(t *testing.T) { bareRepo1, err := OpenRepository(clonedPath) assert.NoError(t, err) + defer bareRepo1.Close() lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1" lTagName := "lightweightTag" @@ -83,6 +85,7 @@ func TestRepository_GetAnnotatedTag(t *testing.T) { bareRepo1, err := OpenRepository(clonedPath) assert.NoError(t, err) + defer bareRepo1.Close() lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1" lTagName := "lightweightTag" diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index 15f5e3781c..0b6986764c 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -30,6 +30,7 @@ func TestRepoIsEmpty(t *testing.T) { emptyRepo2Path := filepath.Join(testReposDir, "repo2_empty") repo, err := OpenRepository(emptyRepo2Path) assert.NoError(t, err) + defer repo.Close() isEmpty, err := repo.IsEmpty() assert.NoError(t, err) assert.True(t, isEmpty) diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go index c65a691ecf..e872900370 100644 --- a/modules/git/tree_entry_test.go +++ b/modules/git/tree_entry_test.go @@ -56,6 +56,7 @@ func TestEntriesCustomSort(t *testing.T) { func TestFollowLink(t *testing.T) { r, err := OpenRepository("tests/repos/repo1_bare") assert.NoError(t, err) + defer r.Close() commit, err := r.GetCommit("37991dec2c8e592043f47155ce4808d4580f9123") assert.NoError(t, err) diff --git a/modules/gzip/gzip.go b/modules/gzip/gzip.go index d139b09e1c..9573d167ab 100644 --- a/modules/gzip/gzip.go +++ b/modules/gzip/gzip.go @@ -123,7 +123,7 @@ func Middleware(options ...Options) macaron.Handler { // OK we should proxy the response writer // We are still not necessarily going to compress... proxyWriter := &ProxyResponseWriter{ - ResponseWriter: ctx.Resp, + internal: ctx.Resp, } defer proxyWriter.Close() @@ -137,19 +137,52 @@ func Middleware(options ...Options) macaron.Handler { } ctx.Next() + ctx.Resp = proxyWriter.internal } } // ProxyResponseWriter is a wrapped macaron ResponseWriter that may compress its contents type ProxyResponseWriter struct { - writer io.WriteCloser - macaron.ResponseWriter - stopped bool + writer io.WriteCloser + internal macaron.ResponseWriter + stopped bool code int buf []byte } +// Header returns the header map +func (proxy *ProxyResponseWriter) Header() http.Header { + return proxy.internal.Header() +} + +// Status returns the status code of the response or 0 if the response has not been written. +func (proxy *ProxyResponseWriter) Status() int { + if proxy.code != 0 { + return proxy.code + } + return proxy.internal.Status() +} + +// Written returns whether or not the ResponseWriter has been written. +func (proxy *ProxyResponseWriter) Written() bool { + if proxy.code != 0 { + return true + } + return proxy.internal.Written() +} + +// Size returns the size of the response body. +func (proxy *ProxyResponseWriter) Size() int { + return proxy.internal.Size() +} + +// Before allows for a function to be called before the ResponseWriter has been written to. This is +// useful for setting headers or any other operations that must happen before a response has been written. +func (proxy *ProxyResponseWriter) Before(before macaron.BeforeFunc) { + proxy.internal.Before(before) +} + // Write appends data to the proxied gzip writer. func (proxy *ProxyResponseWriter) Write(b []byte) (int, error) { // if writer is initialized, use the writer @@ -210,7 +243,7 @@ func (proxy *ProxyResponseWriter) startGzip() error { // Write the header to gzip response. if proxy.code != 0 { - proxy.ResponseWriter.WriteHeader(proxy.code) + proxy.internal.WriteHeader(proxy.code) // Ensure that no other WriteHeader's happen proxy.code = 0 } @@ -220,7 +253,7 @@ func (proxy *ProxyResponseWriter) startGzip() error { // write the gzip header even if nothing was ever written. if len(proxy.buf) > 0 { // Initialize the GZIP response. - proxy.writer = writerPool.Get(proxy.ResponseWriter) + proxy.writer = writerPool.Get(proxy.internal) return proxy.writeBuf() } @@ -229,11 +262,11 @@ func (proxy *ProxyResponseWriter) startGzip() error { func (proxy *ProxyResponseWriter) startPlain() error { if proxy.code != 0 { - proxy.ResponseWriter.WriteHeader(proxy.code) + proxy.internal.WriteHeader(proxy.code) proxy.code = 0 } proxy.stopped = true - proxy.writer = noopCloser{proxy.ResponseWriter} + proxy.writer = noopCloser{proxy.internal} return proxy.writeBuf() } @@ -295,13 +328,13 @@ func (proxy *ProxyResponseWriter) Flush() { gw.Flush() } - proxy.ResponseWriter.Flush() + proxy.internal.Flush() } // Hijack implements http.Hijacker. If the underlying ResponseWriter is a // Hijacker, its Hijack method is returned. Otherwise an error is returned. func (proxy *ProxyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - hijacker, ok := proxy.ResponseWriter.(http.Hijacker) + hijacker, ok := proxy.internal.(http.Hijacker) if !ok { return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") } diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index df8bfd6305..be2d626454 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -138,26 +138,31 @@ func populateIssueIndexer() { } for _, repo := range repos { - is, err := models.Issues(&models.IssuesOptions{ - RepoIDs: []int64{repo.ID}, - IsClosed: util.OptionalBoolNone, - IsPull: util.OptionalBoolNone, - }) - if err != nil { - log.Error("Issues: %v", err) - continue - } - if err = models.IssueList(is).LoadDiscussComments(); err != nil { - log.Error("LoadComments: %v", err) - continue - } - for _, issue := range is { - UpdateIssueIndexer(issue) - } + UpdateRepoIndexer(repo) } } } +// UpdateRepoIndexer add/update all issues of the repositories +func UpdateRepoIndexer(repo *models.Repository) { + is, err := models.Issues(&models.IssuesOptions{ + RepoIDs: []int64{repo.ID}, + IsClosed: util.OptionalBoolNone, + IsPull: util.OptionalBoolNone, + }) + if err != nil { + log.Error("Issues: %v", err) + return + } + if err = models.IssueList(is).LoadDiscussComments(); err != nil { + log.Error("LoadComments: %v", err) + return + } + for _, issue := range is { + UpdateIssueIndexer(issue) + } +} + // UpdateIssueIndexer add/update an issue to the issue indexer func UpdateIssueIndexer(issue *models.Issue) { var comments []string diff --git a/modules/indexer/issues/queue_disk.go b/modules/indexer/issues/queue_disk.go index b127f8d269..d6187f2acb 100644 --- a/modules/indexer/issues/queue_disk.go +++ b/modules/indexer/issues/queue_disk.go @@ -10,7 +10,7 @@ import ( "code.gitea.io/gitea/modules/log" - "github.com/lunny/levelqueue" + "gitea.com/lunny/levelqueue" ) var ( diff --git a/modules/migrations/base/uploader.go b/modules/migrations/base/uploader.go index a3a9c9fac6..257c7a2909 100644 --- a/modules/migrations/base/uploader.go +++ b/modules/migrations/base/uploader.go @@ -12,9 +12,11 @@ type Uploader interface { CreateTopics(topic ...string) error CreateMilestones(milestones ...*Milestone) error CreateReleases(releases ...*Release) error + SyncTags() error CreateLabels(labels ...*Label) error CreateIssues(issues ...*Issue) error CreateComments(comments ...*Comment) error CreatePullRequests(prs ...*PullRequest) error Rollback() error + Close() } diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go index 2452a7a883..18f4da7584 100644 --- a/modules/migrations/gitea.go +++ b/modules/migrations/gitea.go @@ -131,6 +131,13 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate return err } +// Close closes this uploader +func (g *GiteaLocalUploader) Close() { + if g.gitRepo != nil { + g.gitRepo.Close() + } +} + // CreateTopics creates topics func (g *GiteaLocalUploader) CreateTopics(topics ...string) error { return models.SaveTopics(g.repo.ID, topics...) @@ -252,37 +259,41 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { } // download attachment - resp, err := http.Get(asset.URL) + err = func() error { + resp, err := http.Get(asset.URL) + if err != nil { + return err + } + defer resp.Body.Close() + + localPath := attach.LocalPath() + if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { + return fmt.Errorf("MkdirAll: %v", err) + } + + fw, err := os.Create(localPath) + if err != nil { + return fmt.Errorf("Create: %v", err) + } + defer fw.Close() + + _, err = io.Copy(fw, resp.Body) + return err + }() if err != nil { return err } - defer resp.Body.Close() - - localPath := attach.LocalPath() - if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { - return fmt.Errorf("MkdirAll: %v", err) - } - - fw, err := os.Create(localPath) - if err != nil { - return fmt.Errorf("Create: %v", err) - } - defer fw.Close() - - if _, err := io.Copy(fw, resp.Body); err != nil { - return err - } - rel.Attachments = append(rel.Attachments, &attach) } rels = append(rels, &rel) } - if err := models.InsertReleases(rels...); err != nil { - return err - } - // sync tags to releases in database + return models.InsertReleases(rels...) +} + +// SyncTags syncs releases with tags in the database +func (g *GiteaLocalUploader) SyncTags() error { return models.SyncReleasesWithTags(g.repo, g.gitRepo) } @@ -468,21 +479,24 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR } // download patch file - resp, err := http.Get(pr.PatchURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - pullDir := filepath.Join(g.repo.RepoPath(), "pulls") - if err = os.MkdirAll(pullDir, os.ModePerm); err != nil { - return nil, err - } - f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number))) - if err != nil { - return nil, err - } - defer f.Close() - _, err = io.Copy(f, resp.Body) + err := func() error { + resp, err := http.Get(pr.PatchURL) + if err != nil { + return err + } + defer resp.Body.Close() + pullDir := filepath.Join(g.repo.RepoPath(), "pulls") + if err = os.MkdirAll(pullDir, os.ModePerm); err != nil { + return err + } + f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number))) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + return err + }() if err != nil { return nil, err } @@ -496,8 +510,8 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR if err != nil { return nil, err } - defer p.Close() _, err = p.WriteString(pr.Head.SHA) + p.Close() if err != nil { return nil, err } @@ -531,8 +545,8 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR if err != nil { return nil, err } - defer b.Close() _, err = b.WriteString(pr.Head.SHA) + b.Close() if err != nil { return nil, err } @@ -579,14 +593,13 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR } var pullRequest = models.PullRequest{ - HeadRepoID: g.repo.ID, - HeadBranch: head, - HeadUserName: g.repoOwner, - BaseRepoID: g.repo.ID, - BaseBranch: pr.Base.Ref, - MergeBase: pr.Base.SHA, - Index: pr.Number, - HasMerged: pr.Merged, + HeadRepoID: g.repo.ID, + HeadBranch: head, + BaseRepoID: g.repo.ID, + BaseBranch: pr.Base.Ref, + MergeBase: pr.Base.SHA, + Index: pr.Number, + HasMerged: pr.Merged, Issue: &issue, } diff --git a/modules/migrations/github_test.go b/modules/migrations/github_test.go index 2a0a4edf32..d2973b7b6c 100644 --- a/modules/migrations/github_test.go +++ b/modules/migrations/github_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/assert" ) -func assertMilestoneEqual(t *testing.T, title, dueOn, created, updated, closed, state string, ms *base.Milestone) { +func assertMilestoneEqual(t *testing.T, description, title, dueOn, created, updated, closed, state string, ms *base.Milestone) { var tmPtr *time.Time if dueOn != "" { tm, err := time.Parse("2006-01-02 15:04:05 -0700 MST", dueOn) @@ -43,32 +43,34 @@ func assertMilestoneEqual(t *testing.T, title, dueOn, created, updated, closed, } assert.EqualValues(t, &base.Milestone{ - Title: title, - Deadline: tmPtr, - State: state, - Created: createdTM, - Updated: updatedTM, - Closed: closedTM, + Description: description, + Title: title, + Deadline: tmPtr, + State: state, + Created: createdTM, + Updated: updatedTM, + Closed: closedTM, }, ms) } -func assertLabelEqual(t *testing.T, name, color string, label *base.Label) { +func assertLabelEqual(t *testing.T, name, color, description string, label *base.Label) { assert.EqualValues(t, &base.Label{ - Name: name, - Color: color, + Name: name, + Color: color, + Description: description, }, label) } func TestGitHubDownloadRepo(t *testing.T) { - downloader := NewGithubDownloaderV3("", "", "go-gitea", "gitea") + downloader := NewGithubDownloaderV3("", "", "go-gitea", "test_repo") repo, err := downloader.GetRepoInfo() assert.NoError(t, err) assert.EqualValues(t, &base.Repository{ - Name: "gitea", + Name: "test_repo", Owner: "go-gitea", - Description: "Git with a cup of tea, painless self-hosted git service", - CloneURL: "https://github.com/go-gitea/gitea.git", - OriginalURL: "https://github.com/go-gitea/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", }, repo) topics, err := downloader.GetTopics() @@ -77,83 +79,46 @@ func TestGitHubDownloadRepo(t *testing.T) { milestones, err := downloader.GetMilestones() assert.NoError(t, err) - // before this tool release, we have 39 milestones on github.com/go-gitea/gitea - assert.True(t, len(milestones) >= 39) + assert.True(t, len(milestones) >= 2) for _, milestone := range milestones { switch milestone.Title { case "1.0.0": - assertMilestoneEqual(t, "1.0.0", "2016-12-23 08:00:00 +0000 UTC", - "2016-11-02 18:06:55 +0000 UTC", - "2016-12-29 10:26:00 +0000 UTC", - "2016-12-24 00:40:56 +0000 UTC", + assertMilestoneEqual(t, "Milestone 1.0.0", "1.0.0", "2019-11-11 08:00:00 +0000 UTC", + "2019-11-12 19:37:08 +0000 UTC", + "2019-11-12 21:56:17 +0000 UTC", + "2019-11-12 19:45:49 +0000 UTC", "closed", milestone) case "1.1.0": - assertMilestoneEqual(t, "1.1.0", "2017-02-24 08:00:00 +0000 UTC", - "2016-11-03 08:40:10 +0000 UTC", - "2017-06-15 05:04:36 +0000 UTC", - "2017-03-09 21:22:21 +0000 UTC", - "closed", milestone) - case "1.2.0": - assertMilestoneEqual(t, "1.2.0", "2017-04-24 07:00:00 +0000 UTC", - "2016-11-03 08:40:15 +0000 UTC", - "2017-12-10 02:43:29 +0000 UTC", - "2017-10-12 08:24:28 +0000 UTC", - "closed", milestone) - case "1.3.0": - assertMilestoneEqual(t, "1.3.0", "2017-11-29 08:00:00 +0000 UTC", - "2017-03-03 08:08:59 +0000 UTC", - "2017-12-04 07:48:44 +0000 UTC", - "2017-11-29 18:39:00 +0000 UTC", - "closed", milestone) - case "1.4.0": - assertMilestoneEqual(t, "1.4.0", "2018-01-25 08:00:00 +0000 UTC", - "2017-08-23 11:02:37 +0000 UTC", - "2018-03-25 20:01:56 +0000 UTC", - "2018-03-25 20:01:56 +0000 UTC", - "closed", milestone) - case "1.5.0": - assertMilestoneEqual(t, "1.5.0", "2018-06-15 07:00:00 +0000 UTC", - "2017-12-30 04:21:56 +0000 UTC", - "2018-09-05 16:34:22 +0000 UTC", - "2018-08-11 08:45:01 +0000 UTC", - "closed", milestone) - case "1.6.0": - assertMilestoneEqual(t, "1.6.0", "2018-09-25 07:00:00 +0000 UTC", - "2018-05-11 05:37:01 +0000 UTC", - "2019-01-27 19:21:22 +0000 UTC", - "2018-11-23 13:23:16 +0000 UTC", - "closed", milestone) - case "1.7.0": - assertMilestoneEqual(t, "1.7.0", "2018-12-25 08:00:00 +0000 UTC", - "2018-08-28 14:20:14 +0000 UTC", - "2019-01-27 11:30:24 +0000 UTC", - "2019-01-23 08:58:23 +0000 UTC", + assertMilestoneEqual(t, "Milestone 1.1.0", "1.1.0", "2019-11-12 08:00:00 +0000 UTC", + "2019-11-12 19:37:25 +0000 UTC", + "2019-11-12 21:39:27 +0000 UTC", + "2019-11-12 19:45:46 +0000 UTC", "closed", milestone) } } labels, err := downloader.GetLabels() assert.NoError(t, err) - assert.True(t, len(labels) >= 48) + assert.True(t, len(labels) >= 8) for _, l := range labels { switch l.Name { - case "backport/v1.7": - assertLabelEqual(t, "backport/v1.7", "fbca04", l) - case "backport/v1.8": - assertLabelEqual(t, "backport/v1.8", "fbca04", l) - case "kind/api": - assertLabelEqual(t, "kind/api", "5319e7", l) - case "kind/breaking": - assertLabelEqual(t, "kind/breaking", "fbca04", l) - case "kind/bug": - assertLabelEqual(t, "kind/bug", "ee0701", l) - case "kind/docs": - assertLabelEqual(t, "kind/docs", "c2e0c6", l) - case "kind/enhancement": - assertLabelEqual(t, "kind/enhancement", "84b6eb", l) - case "kind/feature": - assertLabelEqual(t, "kind/feature", "006b75", l) + case "bug": + assertLabelEqual(t, "bug", "d73a4a", "Something isn't working", l) + case "documentation": + assertLabelEqual(t, "documentation", "0075ca", "Improvements or additions to documentation", l) + case "duplicate": + assertLabelEqual(t, "duplicate", "cfd3d7", "This issue or pull request already exists", l) + case "enhancement": + assertLabelEqual(t, "enhancement", "a2eeef", "New feature or request", l) + case "good first issue": + assertLabelEqual(t, "good first issue", "7057ff", "Good for newcomers", l) + case "help wanted": + assertLabelEqual(t, "help wanted", "008672", "Extra attention is needed", l) + case "invalid": + assertLabelEqual(t, "invalid", "e4e669", "This doesn't seem right", l) + case "question": + assertLabelEqual(t, "question", "d876e3", "Further information is requested", l) } } @@ -163,48 +128,50 @@ func TestGitHubDownloadRepo(t *testing.T) { { TagName: "v0.9.99", TargetCommitish: "master", - Name: "fork", - Body: "Forked source from Gogs into Gitea\n", - Created: time.Date(2016, 10, 17, 02, 17, 59, 0, time.UTC), - Published: time.Date(2016, 11, 17, 15, 37, 0, 0, time.UTC), - PublisherID: 4726179, - PublisherName: "bkcsoft", + Name: "First Release", + Body: "A test release", + Created: time.Date(2019, 11, 9, 16, 49, 21, 0, time.UTC), + Published: time.Date(2019, 11, 12, 20, 12, 10, 0, time.UTC), + PublisherID: 1669571, + PublisherName: "mrsdizzie", }, }, releases[len(releases)-1:]) // downloader.GetIssues() - issues, isEnd, err := downloader.GetIssues(1, 8) + issues, isEnd, err := downloader.GetIssues(1, 2) assert.NoError(t, err) - assert.EqualValues(t, 3, len(issues)) + assert.EqualValues(t, 2, len(issues)) assert.False(t, isEnd) var ( - closed1 = time.Date(2018, 10, 23, 02, 57, 43, 0, time.UTC) - closed7 = time.Date(2019, 7, 8, 8, 20, 23, 0, time.UTC) + closed1 = time.Date(2019, 11, 12, 20, 22, 22, 0, time.UTC) + closed2 = time.Date(2019, 11, 12, 21, 1, 31, 0, time.UTC) ) assert.EqualValues(t, []*base.Issue{ { - Number: 6, - Title: "Contribution system: History heatmap for user", - Content: "Hi guys,\r\n\r\nI think that is a possible feature, a history heatmap similar to github or gitlab.\r\nActually exists a plugin called Calendar HeatMap. I used this on mine project to heat application log and worked fine here.\r\nThen, is only a idea, what you think? :)\r\n\r\nhttp://cal-heatmap.com/\r\nhttps://github.com/wa0x6e/cal-heatmap\r\n\r\nReference: https://github.com/gogits/gogs/issues/1640", - Milestone: "1.7.0", - PosterID: 1520407, - PosterName: "joubertredrat", + Number: 1, + Title: "Please add an animated gif icon to the merge button", + Content: "I just want the merge button to hurt my eyes a little. \xF0\x9F\x98\x9D ", + Milestone: "1.0.0", + PosterID: 18600385, + PosterName: "guillep2k", State: "closed", - Created: time.Date(2016, 11, 02, 18, 51, 55, 0, time.UTC), + Created: time.Date(2019, 11, 9, 17, 0, 29, 0, time.UTC), Labels: []*base.Label{ { - Name: "kind/feature", - Color: "006b75", + Name: "bug", + Color: "d73a4a", + Description: "Something isn't working", }, { - Name: "kind/ui", - Color: "fef2c0", + Name: "good first issue", + Color: "7057ff", + Description: "Good for newcomers", }, }, Reactions: &base.Reactions{ - TotalCount: 0, - PlusOne: 0, + TotalCount: 1, + PlusOne: 1, MinusOne: 0, Laugh: 0, Confused: 0, @@ -214,84 +181,48 @@ func TestGitHubDownloadRepo(t *testing.T) { Closed: &closed1, }, { - Number: 7, - Title: "display page revisions on wiki", - Content: "Hi guys,\r\n\r\nWiki on Gogs is very fine, I liked a lot, but I think that is good idea to be possible see other revisions from page as a page history.\r\n\r\nWhat you think?\r\n\r\nReference: https://github.com/gogits/gogs/issues/2991", - Milestone: "1.10.0", - PosterID: 1520407, - PosterName: "joubertredrat", + Number: 2, + Title: "Test issue", + Content: "This is test issue 2, do not touch!", + Milestone: "1.1.0", + PosterID: 1669571, + PosterName: "mrsdizzie", State: "closed", - Created: time.Date(2016, 11, 02, 18, 57, 32, 0, time.UTC), + Created: time.Date(2019, 11, 12, 21, 0, 6, 0, time.UTC), Labels: []*base.Label{ { - Name: "kind/feature", - Color: "006b75", - }, - { - Name: "reviewed/confirmed", - Color: "8d9b12", - Description: "Issue has been reviewed and confirmed to be present or accepted to be implemented", + Name: "duplicate", + Color: "cfd3d7", + Description: "This issue or pull request already exists", }, }, Reactions: &base.Reactions{ TotalCount: 6, - PlusOne: 5, - MinusOne: 0, - Laugh: 0, + PlusOne: 1, + MinusOne: 1, + Laugh: 1, Confused: 1, - Heart: 0, - Hooray: 0, - }, - Closed: &closed7, - }, - { - Number: 8, - Title: "audit logs", - Content: "Hi,\r\n\r\nI think that is good idea to have user operation log to admin see what the user is doing at Gogs. Similar to example below\r\n\r\n| user | operation | information |\r\n| --- | --- | --- |\r\n| joubertredrat | repo.create | Create repo MyProjectData |\r\n| joubertredrat | user.settings | Edit settings |\r\n| tboerger | repo.fork | Create Fork from MyProjectData to ForkMyProjectData |\r\n| bkcsoft | repo.remove | Remove repo MySource |\r\n| tboerger | admin.auth | Edit auth LDAP org-connection |\r\n\r\nThis resource can be used on user page too, as user activity, set that log row is public (repo._) or private (user._, admin.*) and display only public activity.\r\n\r\nWhat you think?\r\n\r\n[Chat summary from March 14, 2017](https://github.com/go-gitea/gitea/issues/8#issuecomment-286463807)\r\n\r\nReferences:\r\nhttps://github.com/gogits/gogs/issues/3016", - Milestone: "1.x.x", - PosterID: 1520407, - PosterName: "joubertredrat", - State: "open", - Created: time.Date(2016, 11, 02, 18, 59, 20, 0, time.UTC), - Labels: []*base.Label{ - { - Name: "kind/feature", - Color: "006b75", - }, - { - Name: "kind/proposal", - Color: "5319e7", - }, - }, - Reactions: &base.Reactions{ - TotalCount: 9, - PlusOne: 8, - MinusOne: 0, - Laugh: 0, - Confused: 0, Heart: 1, - Hooray: 0, + Hooray: 1, }, + Closed: &closed2, }, }, issues) // downloader.GetComments() - comments, err := downloader.GetComments(6) + comments, err := downloader.GetComments(2) assert.NoError(t, err) - assert.EqualValues(t, 35, len(comments)) + assert.EqualValues(t, 2, len(comments)) assert.EqualValues(t, []*base.Comment{ { - IssueIndex: 6, - PosterID: 4726179, - PosterName: "bkcsoft", - Created: time.Date(2016, 11, 02, 18, 59, 48, 0, time.UTC), - Content: `I would prefer a solution that is in the backend, unless it's required to have it update without reloading. Unfortunately I can't seem to find anything that does that :unamused: - -Also this would _require_ caching, since it will fetch huge amounts of data from disk... -`, + IssueIndex: 2, + PosterID: 1669571, + PosterName: "mrsdizzie", + Created: time.Date(2019, 11, 12, 21, 0, 13, 0, time.UTC), + Content: "This is a comment", Reactions: &base.Reactions{ - TotalCount: 2, - PlusOne: 2, + TotalCount: 1, + PlusOne: 1, MinusOne: 0, Laugh: 0, Confused: 0, @@ -300,14 +231,11 @@ Also this would _require_ caching, since it will fetch huge amounts of data from }, }, { - IssueIndex: 6, - PosterID: 1520407, - PosterName: "joubertredrat", - Created: time.Date(2016, 11, 02, 19, 16, 56, 0, time.UTC), - Content: `Yes, this plugin build on front-end, with backend I don't know too, but we can consider make component for this. - -In my case I use ajax to get data, but build on frontend anyway -`, + IssueIndex: 2, + PosterID: 1669571, + PosterName: "mrsdizzie", + Created: time.Date(2019, 11, 12, 22, 7, 14, 0, time.UTC), + Content: "A second comment", Reactions: &base.Reactions{ TotalCount: 0, PlusOne: 0, @@ -318,154 +246,85 @@ In my case I use ajax to get data, but build on frontend anyway Hooray: 0, }, }, - { - IssueIndex: 6, - PosterID: 1799009, - PosterName: "xinity", - Created: time.Date(2016, 11, 03, 13, 04, 56, 0, time.UTC), - Content: `following @bkcsoft retention strategy in cache is a must if we don't want gitea to waste ressources. -something like in the latest 15days could be enough don't you think ? -`, - Reactions: &base.Reactions{ - TotalCount: 2, - PlusOne: 2, - MinusOne: 0, - Laugh: 0, - Confused: 0, - Heart: 0, - Hooray: 0, - }, - }, - }, comments[:3]) + }, comments[:2]) // downloader.GetPullRequests() - prs, err := downloader.GetPullRequests(1, 3) + prs, err := downloader.GetPullRequests(1, 2) assert.NoError(t, err) - assert.EqualValues(t, 3, len(prs)) + assert.EqualValues(t, 2, len(prs)) - closed1 = time.Date(2016, 11, 02, 18, 22, 21, 0, time.UTC) - var ( - closed2 = time.Date(2016, 11, 03, 8, 06, 27, 0, time.UTC) - closed3 = time.Date(2016, 11, 02, 18, 22, 31, 0, time.UTC) - ) + closed1 = time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC) + var merged1 = time.Date(2019, 11, 12, 21, 39, 27, 0, time.UTC) - var ( - merged1 = time.Date(2016, 11, 02, 18, 22, 21, 0, time.UTC) - merged2 = time.Date(2016, 11, 03, 8, 06, 27, 0, time.UTC) - merged3 = time.Date(2016, 11, 02, 18, 22, 31, 0, time.UTC) - ) assert.EqualValues(t, []*base.PullRequest{ { - Number: 1, - Title: "Rename import paths: \"github.com/gogits/gogs\" -> \"github.com/go-gitea/gitea\"", - Content: "", - Milestone: "1.0.0", - PosterID: 7011819, - PosterName: "andreynering", + Number: 3, + Title: "Update README.md", + Content: "add warning to readme", + Milestone: "1.1.0", + PosterID: 1669571, + PosterName: "mrsdizzie", State: "closed", - Created: time.Date(2016, 11, 02, 17, 01, 19, 0, time.UTC), + Created: time.Date(2019, 11, 12, 21, 21, 43, 0, time.UTC), Labels: []*base.Label{ { - Name: "kind/enhancement", - Color: "84b6eb", - }, - { - Name: "lgtm/done", - Color: "0e8a16", + Name: "documentation", + Color: "0075ca", + Description: "Improvements or additions to documentation", }, }, - PatchURL: "https://github.com/go-gitea/gitea/pull/1.patch", + PatchURL: "https://github.com/go-gitea/test_repo/pull/3.patch", Head: base.PullRequestBranch{ - Ref: "import-paths", - SHA: "1b0ec3208db8501acba44a137c009a5a126ebaa9", - OwnerName: "andreynering", + Ref: "master", + CloneURL: "https://github.com/mrsdizzie/test_repo.git", + SHA: "076160cf0b039f13e5eff19619932d181269414b", + RepoName: "test_repo", + + OwnerName: "mrsdizzie", }, Base: base.PullRequestBranch{ Ref: "master", - SHA: "6bcff7828f117af8d51285ce3acba01a7e40a867", + SHA: "72866af952e98d02a73003501836074b286a78f6", OwnerName: "go-gitea", - RepoName: "gitea", + RepoName: "test_repo", }, Closed: &closed1, Merged: true, MergedTime: &merged1, - MergeCommitSHA: "142d35e8d2baec230ddb565d1265940d59141fab", + MergeCommitSHA: "f32b0a9dfd09a60f616f29158f772cedd89942d2", }, { - Number: 2, - Title: "Fix sender of issue notifications", - Content: "It is the FROM field in mailer configuration that needs be used,\r\nnot the USER field, which is for authentication.\r\n\r\nMigrated from https://github.com/gogits/gogs/pull/3616\r\n", + Number: 4, + Title: "Test branch", + Content: "do not merge this PR", Milestone: "1.0.0", - PosterID: 289678, - PosterName: "strk", - State: "closed", - Created: time.Date(2016, 11, 02, 17, 24, 19, 0, time.UTC), + PosterID: 1669571, + PosterName: "mrsdizzie", + State: "open", + Created: time.Date(2019, 11, 12, 21, 54, 18, 0, time.UTC), Labels: []*base.Label{ { - Name: "kind/bug", - Color: "ee0701", - }, - { - Name: "lgtm/done", - Color: "0e8a16", + Name: "bug", + Color: "d73a4a", + Description: "Something isn't working", }, }, - PatchURL: "https://github.com/go-gitea/gitea/pull/2.patch", + PatchURL: "https://github.com/go-gitea/test_repo/pull/4.patch", Head: base.PullRequestBranch{ - Ref: "proper-from-on-issue-mail", - SHA: "af03d00780a6ee70c58e135c6679542cde4f8d50", - RepoName: "gogs", - OwnerName: "strk", - CloneURL: "https://github.com/strk/gogs.git", + Ref: "test-branch", + SHA: "2be9101c543658591222acbee3eb799edfc3853d", + RepoName: "test_repo", + OwnerName: "mrsdizzie", + CloneURL: "https://github.com/mrsdizzie/test_repo.git", }, Base: base.PullRequestBranch{ - Ref: "develop", - SHA: "5c5424301443ffa3659737d12de48ab1dfe39a00", + Ref: "master", + SHA: "f32b0a9dfd09a60f616f29158f772cedd89942d2", OwnerName: "go-gitea", - RepoName: "gitea", + RepoName: "test_repo", }, - Closed: &closed2, - Merged: true, - MergedTime: &merged2, - MergeCommitSHA: "d8de2beb5b92d02a0597ba7c7803839380666653", - }, - { - Number: 3, - Title: "Use proper url for libravatar dep", - Content: "Fetch go-libravatar from its official source, rather than from an unmaintained fork\r\n", - Milestone: "1.0.0", - PosterID: 289678, - PosterName: "strk", - State: "closed", - Created: time.Date(2016, 11, 02, 17, 34, 31, 0, time.UTC), - Labels: []*base.Label{ - { - Name: "kind/enhancement", - Color: "84b6eb", - }, - { - Name: "lgtm/done", - Color: "0e8a16", - }, - }, - PatchURL: "https://github.com/go-gitea/gitea/pull/3.patch", - Head: base.PullRequestBranch{ - Ref: "libravatar-proper-url", - SHA: "d59a48a2550abd4129b96d38473941b895a4859b", - RepoName: "gogs", - OwnerName: "strk", - CloneURL: "https://github.com/strk/gogs.git", - }, - Base: base.PullRequestBranch{ - Ref: "develop", - SHA: "6bcff7828f117af8d51285ce3acba01a7e40a867", - OwnerName: "go-gitea", - RepoName: "gitea", - }, - Closed: &closed3, - Merged: true, - MergedTime: &merged3, - MergeCommitSHA: "5c5424301443ffa3659737d12de48ab1dfe39a00", + Merged: false, + MergeCommitSHA: "565d1208f5fffdc1c5ae1a2436491eb9a5e4ebae", }, }, prs) } diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go index bbc1dc2d56..2348defd19 100644 --- a/modules/migrations/migrate.go +++ b/modules/migrations/migrate.go @@ -57,7 +57,7 @@ func MigrateRepository(doer *models.User, ownerName string, opts base.MigrateOpt opts.PullRequests = false opts.GitServiceType = structs.PlainGitService downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr) - log.Trace("Will migrate from git: %s", opts.CloneAddr) + log.Trace("Will migrate from git: %s", opts.OriginalURL) } else if opts.GitServiceType == structs.NotMigrated { opts.GitServiceType = theFactory.GitServiceType() } @@ -68,7 +68,7 @@ func MigrateRepository(doer *models.User, ownerName string, opts base.MigrateOpt log.Error("rollback failed: %v", err1) } - if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.CloneAddr, err)); err2 != nil { + if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil { log.Error("create respotiry notice failed: ", err2) } return nil, err @@ -94,6 +94,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts if err := uploader.CreateRepo(repo, opts); err != nil { return err } + defer uploader.Close() log.Trace("migrating topics") topics, err := downloader.GetTopics() @@ -164,6 +165,11 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts } releases = releases[relBatchSize:] } + + // Once all releases (if any) are inserted, sync any remaining non-release tags + if err := uploader.SyncTags(); err != nil { + return err + } } var commentBatchSize = uploader.MaxBatchInsertSize("comment") diff --git a/modules/migrations/update.go b/modules/migrations/update.go index df626ddd95..d1465b2baf 100644 --- a/modules/migrations/update.go +++ b/modules/migrations/update.go @@ -5,8 +5,6 @@ package migrations import ( - "strconv" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" @@ -40,11 +38,7 @@ func updateMigrationPosterIDByGitService(tp structs.GitServiceType) error { } for _, user := range users { - externalUserID, err := strconv.ParseInt(user.ExternalID, 10, 64) - if err != nil { - log.Warn("Parse externalUser %#v 's userID failed: %v", user, err) - continue - } + externalUserID := user.ExternalID if err := models.UpdateMigrationsByType(tp, externalUserID, user.UserID); err != nil { log.Error("UpdateMigrationsByType type %s external user id %v to local user id %v failed: %v", tp.Name(), user.ExternalID, user.UserID, err) } diff --git a/modules/notification/indexer/indexer.go b/modules/notification/indexer/indexer.go index 66614b2c20..606024f397 100644 --- a/modules/notification/indexer/indexer.go +++ b/modules/notification/indexer/indexer.go @@ -107,3 +107,8 @@ func (r *indexerNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod func (r *indexerNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) { issue_indexer.UpdateIssueIndexer(issue) } + +func (r *indexerNotifier) NotifyMigrateRepository(doer *models.User, u *models.User, repo *models.Repository) { + issue_indexer.UpdateRepoIndexer(repo) + models.UpdateRepoIndexer(repo) +} diff --git a/modules/password/password.go b/modules/password/password.go index 54131b9641..92986977ec 100644 --- a/modules/password/password.go +++ b/modules/password/password.go @@ -7,45 +7,60 @@ package password import ( "crypto/rand" "math/big" - "regexp" + "strings" "sync" "code.gitea.io/gitea/modules/setting" ) -var matchComplexities = map[string]regexp.Regexp{} -var matchComplexityOnce sync.Once -var validChars string -var validComplexities = map[string]string{ - "lower": "abcdefghijklmnopqrstuvwxyz", - "upper": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "digit": "0123456789", - "spec": `][ !"#$%&'()*+,./:;<=>?@\^_{|}~` + "`-", -} +var ( + matchComplexityOnce sync.Once + validChars string + requiredChars []string + + charComplexities = map[string]string{ + "lower": `abcdefghijklmnopqrstuvwxyz`, + "upper": `ABCDEFGHIJKLMNOPQRSTUVWXYZ`, + "digit": `0123456789`, + "spec": ` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`", + } +) // NewComplexity for preparation func NewComplexity() { matchComplexityOnce.Do(func() { - if len(setting.PasswordComplexity) > 0 { - for key, val := range setting.PasswordComplexity { - matchComplexity := regexp.MustCompile(val) - matchComplexities[key] = *matchComplexity - validChars += validComplexities[key] - } - } else { - for _, val := range validComplexities { - validChars += val - } - } + setupComplexity(setting.PasswordComplexity) }) } -// IsComplexEnough return True if password is Complexity +func setupComplexity(values []string) { + if len(values) != 1 || values[0] != "off" { + for _, val := range values { + if chars, ok := charComplexities[val]; ok { + validChars += chars + requiredChars = append(requiredChars, chars) + } + } + if len(requiredChars) == 0 { + // No valid character classes found; use all classes as default + for _, chars := range charComplexities { + validChars += chars + requiredChars = append(requiredChars, chars) + } + } + } + if validChars == "" { + // No complexities to check; provide a sensible default for password generation + validChars = charComplexities["lower"] + charComplexities["upper"] + charComplexities["digit"] + } +} + +// IsComplexEnough return True if password meets complexity settings func IsComplexEnough(pwd string) bool { - if len(setting.PasswordComplexity) > 0 { - NewComplexity() - for _, val := range matchComplexities { - if !val.MatchString(pwd) { + NewComplexity() + if len(validChars) > 0 { + for _, req := range requiredChars { + if !strings.ContainsAny(req, pwd) { return false } } diff --git a/modules/password/password_test.go b/modules/password/password_test.go new file mode 100644 index 0000000000..d46a6d1571 --- /dev/null +++ b/modules/password/password_test.go @@ -0,0 +1,75 @@ +// Copyright 2019 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 password + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestComplexity_IsComplexEnough(t *testing.T) { + matchComplexityOnce.Do(func() {}) + + testlist := []struct { + complexity []string + truevalues []string + falsevalues []string + }{ + {[]string{"lower"}, []string{"abc", "abc!"}, []string{"ABC", "123", "=!$", ""}}, + {[]string{"upper"}, []string{"ABC"}, []string{"abc", "123", "=!$", "abc!", ""}}, + {[]string{"digit"}, []string{"123"}, []string{"abc", "ABC", "=!$", "abc!", ""}}, + {[]string{"spec"}, []string{"=!$", "abc!"}, []string{"abc", "ABC", "123", ""}}, + {[]string{"off"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}, nil}, + {[]string{"lower", "spec"}, []string{"abc!"}, []string{"abc", "ABC", "123", "=!$", "abcABC123", ""}}, + {[]string{"lower", "upper", "digit"}, []string{"abcABC123"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}}, + } + + for _, test := range testlist { + testComplextity(test.complexity) + for _, val := range test.truevalues { + assert.True(t, IsComplexEnough(val)) + } + for _, val := range test.falsevalues { + assert.False(t, IsComplexEnough(val)) + } + } + + // Remove settings for other tests + testComplextity([]string{"off"}) +} + +func TestComplexity_Generate(t *testing.T) { + matchComplexityOnce.Do(func() {}) + + const maxCount = 50 + const pwdLen = 50 + + test := func(t *testing.T, modes []string) { + testComplextity(modes) + for i := 0; i < maxCount; i++ { + pwd, err := Generate(pwdLen) + assert.NoError(t, err) + assert.Equal(t, pwdLen, len(pwd)) + assert.True(t, IsComplexEnough(pwd), "Failed complexities with modes %+v for generated: %s", modes, pwd) + } + } + + test(t, []string{"lower"}) + test(t, []string{"upper"}) + test(t, []string{"lower", "upper", "spec"}) + test(t, []string{"off"}) + test(t, []string{""}) + + // Remove settings for other tests + testComplextity([]string{"off"}) +} + +func testComplextity(values []string) { + // Cleanup previous values + validChars = "" + requiredChars = make([]string, 0, len(values)) + setupComplexity(values) +} diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go index 2d7bb6a5a6..a9718f2fdd 100644 --- a/modules/recaptcha/recaptcha.go +++ b/modules/recaptcha/recaptcha.go @@ -24,7 +24,7 @@ type Response struct { ErrorCodes []string `json:"error-codes"` } -const apiURL = "/api/siteverify" +const apiURL = "api/siteverify" // Verify calls Google Recaptcha API to verify token func Verify(response string) (bool, error) { diff --git a/modules/references/references.go b/modules/references/references.go index 9c74d0d081..2e36eecec5 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -26,7 +26,7 @@ var ( // TODO: fix invalid linking issue // mentionPattern matches all mentions in the form of "@user" - mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_\.]+)(?:\s|$|\)|\])`) + mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`) // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) // issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234 diff --git a/modules/references/references_test.go b/modules/references/references_test.go index f8153ffe36..23403b42ba 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -204,14 +204,32 @@ func TestFindAllIssueReferences(t *testing.T) { } func TestRegExp_mentionPattern(t *testing.T) { - trueTestCases := []string{ - "@Unknwon", - "@ANT_123", - "@xxx-DiN0-z-A..uru..s-xxx", - " @lol ", - " @Te-st", - "(@gitea)", - "[@gitea]", + trueTestCases := []struct { + pat string + exp string + }{ + {"@Unknwon", "@Unknwon"}, + {"@ANT_123", "@ANT_123"}, + {"@xxx-DiN0-z-A..uru..s-xxx", "@xxx-DiN0-z-A..uru..s-xxx"}, + {" @lol ", "@lol"}, + {" @Te-st", "@Te-st"}, + {"(@gitea)", "@gitea"}, + {"[@gitea]", "@gitea"}, + {"@gitea! this", "@gitea"}, + {"@gitea? this", "@gitea"}, + {"@gitea. this", "@gitea"}, + {"@gitea, this", "@gitea"}, + {"@gitea; this", "@gitea"}, + {"@gitea!\nthis", "@gitea"}, + {"\n@gitea?\nthis", "@gitea"}, + {"\t@gitea.\nthis", "@gitea"}, + {"@gitea,\nthis", "@gitea"}, + {"@gitea;\nthis", "@gitea"}, + {"@gitea!", "@gitea"}, + {"@gitea?", "@gitea"}, + {"@gitea.", "@gitea"}, + {"@gitea,", "@gitea"}, + {"@gitea;", "@gitea"}, } falseTestCases := []string{ "@ 0", @@ -219,17 +237,24 @@ func TestRegExp_mentionPattern(t *testing.T) { "@", "", "ABC", + "@.ABC", "/home/gitea/@gitea", "\"@gitea\"", + "@@gitea", + "@gitea!this", + "@gitea?this", + "@gitea,this", + "@gitea;this", } for _, testCase := range trueTestCases { - res := mentionPattern.MatchString(testCase) - assert.True(t, res) + found := mentionPattern.FindStringSubmatch(testCase.pat) + assert.Len(t, found, 2) + assert.Equal(t, testCase.exp, found[1]) } for _, testCase := range falseTestCases { res := mentionPattern.MatchString(testCase) - assert.False(t, res) + assert.False(t, res, "[%s] should be false", testCase) } } diff --git a/modules/repofiles/action.go b/modules/repofiles/action.go index 9467e4fb72..755c015ca9 100644 --- a/modules/repofiles/action.go +++ b/modules/repofiles/action.go @@ -53,9 +53,11 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { } if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { if !git.IsErrUnsupportedVersion(err) { + gitRepo.Close() return err } } + gitRepo.Close() } } @@ -132,8 +134,10 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { shaSum, err = gitRepo.GetBranchCommitID(refName) if err != nil { + gitRepo.Close() log.Error("GetBranchCommitID[%s]: %v", opts.RefFullName, err) } + gitRepo.Close() if err = models.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ Ref: refName, Sha: shaSum, @@ -167,8 +171,10 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { } shaSum, err = gitRepo.GetTagCommitID(refName) if err != nil { + gitRepo.Close() log.Error("GetTagCommitID[%s]: %v", opts.RefFullName, err) } + gitRepo.Close() if err = models.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ Ref: refName, Sha: shaSum, diff --git a/modules/repofiles/blob.go b/modules/repofiles/blob.go index e9d85a0dcf..60a05e280e 100644 --- a/modules/repofiles/blob.go +++ b/modules/repofiles/blob.go @@ -17,6 +17,7 @@ func GetBlobBySHA(repo *models.Repository, sha string) (*api.GitBlobResponse, er if err != nil { return nil, err } + defer gitRepo.Close() gitBlob, err := gitRepo.GetBlob(sha) if err != nil { return nil, err diff --git a/modules/repofiles/blob_test.go b/modules/repofiles/blob_test.go index 1dc183a8af..ddc23aeac3 100644 --- a/modules/repofiles/blob_test.go +++ b/modules/repofiles/blob_test.go @@ -21,6 +21,8 @@ func TestGetBlobBySHA(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d" ctx.SetParams(":id", "1") ctx.SetParams(":sha", sha) diff --git a/modules/repofiles/commit_status.go b/modules/repofiles/commit_status.go index f3dfbf209f..3d93c58d85 100644 --- a/modules/repofiles/commit_status.go +++ b/modules/repofiles/commit_status.go @@ -23,8 +23,10 @@ func CreateCommitStatus(repo *models.Repository, creator *models.User, sha strin return fmt.Errorf("OpenRepository[%s]: %v", repoPath, err) } if _, err := gitRepo.GetCommit(sha); err != nil { + gitRepo.Close() return fmt.Errorf("GetCommit[%s]: %v", sha, err) } + gitRepo.Close() if err := models.NewCommitStatus(models.NewCommitStatusOptions{ Repo: repo, diff --git a/modules/repofiles/content.go b/modules/repofiles/content.go index 9637658e78..aed98c33a8 100644 --- a/modules/repofiles/content.go +++ b/modules/repofiles/content.go @@ -38,6 +38,9 @@ func (ct *ContentType) String() string { // GetContentsOrList gets the meta data of a file's contents (*ContentsResponse) if treePath not a tree // directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag func GetContentsOrList(repo *models.Repository, treePath, ref string) (interface{}, error) { + if repo.IsEmpty { + return make([]interface{}, 0), nil + } if ref == "" { ref = repo.DefaultBranch } @@ -56,6 +59,7 @@ func GetContentsOrList(repo *models.Repository, treePath, ref string) (interface if err != nil { return nil, err } + defer gitRepo.Close() // Get the commit object for the ref commit, err := gitRepo.GetCommit(ref) @@ -114,6 +118,7 @@ func GetContents(repo *models.Repository, treePath, ref string, forList bool) (* if err != nil { return nil, err } + defer gitRepo.Close() // Get the commit object for the ref commit, err := gitRepo.GetCommit(ref) diff --git a/modules/repofiles/content_test.go b/modules/repofiles/content_test.go index ef6c5eafc2..d024cfd549 100644 --- a/modules/repofiles/content_test.go +++ b/modules/repofiles/content_test.go @@ -56,6 +56,8 @@ func TestGetContents(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + treePath := "README.md" ref := ctx.Repo.Repository.DefaultBranch @@ -82,6 +84,8 @@ func TestGetContentsOrListForDir(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + treePath := "" // root dir ref := ctx.Repo.Repository.DefaultBranch @@ -115,6 +119,8 @@ func TestGetContentsOrListForFile(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + treePath := "README.md" ref := ctx.Repo.Repository.DefaultBranch @@ -141,6 +147,8 @@ func TestGetContentsErrors(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository treePath := "README.md" ref := repo.DefaultBranch @@ -170,6 +178,8 @@ func TestGetContentsOrListErrors(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository treePath := "README.md" ref := repo.DefaultBranch @@ -190,3 +200,21 @@ func TestGetContentsOrListErrors(t *testing.T) { assert.Nil(t, fileContentResponse) }) } + +func TestGetContentsOrListOfEmptyRepos(t *testing.T) { + models.PrepareTestEnv(t) + ctx := test.MockContext(t, "user2/repo15") + ctx.SetParams(":id", "15") + test.LoadRepo(t, ctx, 15) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + + t.Run("empty repo", func(t *testing.T) { + contents, err := GetContentsOrList(repo, "", "") + assert.NoError(t, err) + assert.Empty(t, contents) + }) +} diff --git a/modules/repofiles/delete.go b/modules/repofiles/delete.go index 2210faae6b..95b0804025 100644 --- a/modules/repofiles/delete.go +++ b/modules/repofiles/delete.go @@ -69,7 +69,7 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo message := strings.TrimSpace(opts.Message) - author, committer := GetAuthorAndCommitterUsers(opts.Committer, opts.Author, doer) + author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer) t, err := NewTemporaryUploadRepository(repo) if err != nil { diff --git a/modules/repofiles/diff_test.go b/modules/repofiles/diff_test.go index de5ed1d754..db2c7552c4 100644 --- a/modules/repofiles/diff_test.go +++ b/modules/repofiles/diff_test.go @@ -22,6 +22,8 @@ func TestGetDiffPreview(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + branch := ctx.Repo.Repository.DefaultBranch treePath := "README.md" content := "# repo1\n\nDescription for repo1\nthis is a new line" @@ -119,6 +121,8 @@ func TestGetDiffPreviewErrors(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + branch := ctx.Repo.Repository.DefaultBranch treePath := "README.md" content := "# repo1\n\nDescription for repo1\nthis is a new line" diff --git a/modules/repofiles/file.go b/modules/repofiles/file.go index 801f770e02..abd14b1db8 100644 --- a/modules/repofiles/file.go +++ b/modules/repofiles/file.go @@ -80,7 +80,7 @@ func GetFileCommitResponse(repo *models.Repository, commit *git.Commit) (*api.Fi } // GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions -func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (committerUser, authorUser *models.User) { +func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (authorUser, committerUser *models.User) { // Committer and author are optional. If they are not the doer (not same email address) // then we use bogus User objects for them to store their FullName and Email. // If only one of the two are provided, we set both of them to it. diff --git a/modules/repofiles/file_test.go b/modules/repofiles/file_test.go index 7c45139dd9..46b5a6c549 100644 --- a/modules/repofiles/file_test.go +++ b/modules/repofiles/file_test.go @@ -88,10 +88,13 @@ func TestGetFileResponseFromCommit(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository branch := repo.DefaultBranch treePath := "README.md" gitRepo, _ := git.OpenRepository(repo.RepoPath()) + defer gitRepo.Close() commit, _ := gitRepo.GetBranchCommit(branch) expectedFileResponse := getExpectedFileResponse() diff --git a/modules/repofiles/temp_repo.go b/modules/repofiles/temp_repo.go index 4a50e64192..ae4d905946 100644 --- a/modules/repofiles/temp_repo.go +++ b/modules/repofiles/temp_repo.go @@ -44,6 +44,7 @@ func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepo // Close the repository cleaning up all files func (t *TemporaryUploadRepository) Close() { + defer t.gitRepo.Close() if err := models.RemoveTemporaryPath(t.basePath); err != nil { log.Error("Failed to remove temporary path %s: %v", t.basePath, err) } diff --git a/modules/repofiles/tree.go b/modules/repofiles/tree.go index 318a5d152d..b3edea341f 100644 --- a/modules/repofiles/tree.go +++ b/modules/repofiles/tree.go @@ -19,6 +19,7 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs if err != nil { return nil, err } + defer gitRepo.Close() gitTree, err := gitRepo.GetTree(sha) if err != nil || gitTree == nil { return nil, models.ErrSHANotFound{ @@ -78,11 +79,11 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs for e := rangeStart; e < rangeEnd; e++ { i := e - rangeStart - tree.Entries[e].Path = entries[e].Name() - tree.Entries[e].Mode = fmt.Sprintf("%06o", entries[e].Mode()) - tree.Entries[e].Type = entries[e].Type() - tree.Entries[e].Size = entries[e].Size() - tree.Entries[e].SHA = entries[e].ID.String() + tree.Entries[i].Path = entries[e].Name() + tree.Entries[i].Mode = fmt.Sprintf("%06o", entries[e].Mode()) + tree.Entries[i].Type = entries[e].Type() + tree.Entries[i].Size = entries[e].Size() + tree.Entries[i].SHA = entries[e].ID.String() if entries[e].IsDir() { copy(treeURL[copyPos:], entries[e].ID.String()) diff --git a/modules/repofiles/tree_test.go b/modules/repofiles/tree_test.go index ecff8b9071..e1bb812ec1 100644 --- a/modules/repofiles/tree_test.go +++ b/modules/repofiles/tree_test.go @@ -21,6 +21,8 @@ func TestGetTreeBySHA(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + sha := ctx.Repo.Repository.DefaultBranch page := 1 perPage := 10 diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go index 1a1fe6c389..62e5a49437 100644 --- a/modules/repofiles/update.go +++ b/modules/repofiles/update.go @@ -167,7 +167,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up message := strings.TrimSpace(opts.Message) - author, committer := GetAuthorAndCommitterUsers(opts.Committer, opts.Author, doer) + author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer) t, err := NewTemporaryUploadRepository(repo) if err != nil { @@ -429,6 +429,7 @@ func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOp if err != nil { return fmt.Errorf("OpenRepository: %v", err) } + defer gitRepo.Close() if err = repo.UpdateSize(); err != nil { log.Error("Failed to update size for repository: %v", err) @@ -452,10 +453,15 @@ func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOp } } } else if !isDelRef { + branchName := opts.RefFullName[len(git.BranchPrefix):] + if err = models.RemoveDeletedBranch(repo.ID, branchName); err != nil { + log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branchName, err) + } + // If is branch reference // Clear cache for branch commit count - cache.Remove(repo.GetCommitsCountCacheKey(opts.RefFullName[len(git.BranchPrefix):], true)) + cache.Remove(repo.GetCommitsCountCacheKey(branchName, true)) newCommit, err := gitRepo.GetCommit(opts.NewCommitID) if err != nil { diff --git a/modules/setting/database.go b/modules/setting/database.go index 2cac4824df..8c49ba3c5a 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -42,12 +42,11 @@ var ( DBConnectRetries int DBConnectBackoff time.Duration MaxIdleConns int + MaxOpenConns int ConnMaxLifetime time.Duration IterateBufferSize int }{ - Timeout: 500, - MaxIdleConns: 0, - ConnMaxLifetime: 3 * time.Second, + Timeout: 500, } ) @@ -80,8 +79,13 @@ func InitDBConfig() { Database.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"}) Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db")) Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500) - Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(0) - Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(3 * time.Second) + Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(2) + if Database.UseMySQL { + Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(3 * time.Second) + } else { + Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(0) + } + Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0) Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50) Database.LogSQL = sec.Key("LOG_SQL").MustBool(true) diff --git a/modules/setting/log.go b/modules/setting/log.go index 5e2d2d769d..2cc95f11b2 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -128,7 +128,11 @@ func generateLogConfig(sec *ini.Section, name string, defaults defaultLogOptions logConfig["username"] = sec.Key("USER").MustString("example@example.com") logConfig["password"] = sec.Key("PASSWD").MustString("******") logConfig["host"] = sec.Key("HOST").MustString("127.0.0.1:25") - logConfig["sendTos"] = sec.Key("RECEIVERS").MustString("[]") + sendTos := strings.Split(sec.Key("RECEIVERS").MustString(""), ",") + for i, address := range sendTos { + sendTos[i] = strings.TrimSpace(address) + } + logConfig["sendTos"] = sendTos logConfig["subject"] = sec.Key("SUBJECT").MustString("Diagnostic message from Gitea") } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 278ed4b107..663d2fb7b6 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -146,7 +146,7 @@ var ( MinPasswordLength int ImportLocalPaths bool DisableGitHooks bool - PasswordComplexity map[string]string + PasswordComplexity []string PasswordHashAlgo string // UI settings @@ -775,26 +775,14 @@ func NewContext() { InternalToken = loadInternalToken(sec) - var dictPC = map[string]string{ - "lower": "[a-z]+", - "upper": "[A-Z]+", - "digit": "[0-9]+", - "spec": `][ !"#$%&'()*+,./:;<=>?@\\^_{|}~` + "`-", - } - PasswordComplexity = make(map[string]string) cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") - for _, y := range cfgdata { - ts := strings.TrimSpace(y) - for a := range dictPC { - if strings.ToLower(ts) == a { - PasswordComplexity[ts] = dictPC[ts] - break - } + PasswordComplexity = make([]string, 0, len(cfgdata)) + for _, name := range cfgdata { + name := strings.ToLower(strings.Trim(name, `"`)) + if name != "" { + PasswordComplexity = append(PasswordComplexity, name) } } - if len(PasswordComplexity) == 0 { - PasswordComplexity = dictPC - } sec = Cfg.Section("attachment") AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) diff --git a/modules/task/migrate.go b/modules/task/migrate.go index 5d15a506d7..247403d7be 100644 --- a/modules/task/migrate.go +++ b/modules/task/migrate.go @@ -97,8 +97,6 @@ func runMigrateTask(t *models.Task) (err error) { opts.MigrateToRepoID = t.RepoID repo, err := migrations.MigrateRepository(t.Doer, t.Owner.Name, *opts) if err == nil { - notification.NotifyMigrateRepository(t.Doer, t.Owner, repo) - log.Trace("Repository migrated [%d]: %s/%s", repo.ID, t.Owner.Name, repo.Name) return nil } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 2c53e05fca..7404d046fe 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -19,6 +19,7 @@ import ( "runtime" "strings" "time" + "unicode" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" @@ -238,6 +239,14 @@ func NewFuncMap() []template.FuncMap { "MirrorFullAddress": mirror_service.AddressNoCredentials, "MirrorUserName": mirror_service.Username, "MirrorPassword": mirror_service.Password, + "contain": func(s []int64, id int64) bool { + for i := 0; i < len(s); i++ { + if s[i] == id { + return true + } + } + return false + }, }} } @@ -331,34 +340,46 @@ func RenderCommitMessageLink(msg, urlPrefix, urlDefault string, metas map[string // RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to // the provided default url, handling for special links without email to links. func RenderCommitMessageLinkSubject(msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { - cleanMsg := template.HTMLEscapeString(msg) - // we can safely assume that it will not return any error, since there - // shouldn't be any special HTML. - fullMessage, err := markup.RenderCommitMessageSubject([]byte(cleanMsg), urlPrefix, urlDefault, metas) - if err != nil { - log.Error("RenderCommitMessageSubject: %v", err) - return "" + msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace) + lineEnd := strings.IndexByte(msgLine, '\n') + if lineEnd > 0 { + msgLine = msgLine[:lineEnd] } - msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n") - if len(msgLines) == 0 { + msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace) + if len(msgLine) == 0 { return template.HTML("") } - return template.HTML(msgLines[0]) + + // we can safely assume that it will not return any error, since there + // shouldn't be any special HTML. + renderedMessage, err := markup.RenderCommitMessageSubject([]byte(template.HTMLEscapeString(msgLine)), urlPrefix, urlDefault, metas) + if err != nil { + log.Error("RenderCommitMessageSubject: %v", err) + return template.HTML("") + } + return template.HTML(renderedMessage) } // RenderCommitBody extracts the body of a commit message without its title. func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML { - cleanMsg := template.HTMLEscapeString(msg) - fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, "", metas) + msgLine := strings.TrimRightFunc(msg, unicode.IsSpace) + lineEnd := strings.IndexByte(msgLine, '\n') + if lineEnd > 0 { + msgLine = msgLine[lineEnd+1:] + } else { + return template.HTML("") + } + msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace) + if len(msgLine) == 0 { + return template.HTML("") + } + + renderedMessage, err := markup.RenderCommitMessage([]byte(template.HTMLEscapeString(msgLine)), urlPrefix, "", metas) if err != nil { log.Error("RenderCommitMessage: %v", err) return "" } - body := strings.Split(strings.TrimSpace(string(fullMessage)), "\n") - if len(body) == 0 { - return template.HTML("") - } - return template.HTML(strings.Join(body[1:], "\n")) + return template.HTML(renderedMessage) } // RenderNote renders the contents of a git-notes file as a commit message. diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index 92df1c5762..cf9c5fbc54 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -55,6 +55,7 @@ func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) { func LoadRepoCommit(t *testing.T, ctx *context.Context) { gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath()) assert.NoError(t, err) + defer gitRepo.Close() branch, err := gitRepo.GetHEADBranch() assert.NoError(t, err) ctx.Repo.Commit, err = gitRepo.GetBranchCommit(branch.Name) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4a92b08030..d7c9930215 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -315,7 +315,7 @@ team_no_units_error = Allow access to at least one repository section. email_been_used = The email address is already used. openid_been_used = The OpenID address '%s' is already used. username_password_incorrect = Username or password is incorrect. -password_complexity = Password does not pass complexity requirements. +password_complexity = Password does not pass complexity requirements. enterred_invalid_repo_name = The repository name you entered is incorrect. enterred_invalid_owner_name = The new owner name is not valid. enterred_invalid_password = The password you entered is incorrect. @@ -1700,6 +1700,7 @@ auths.attribute_surname = Surname Attribute auths.attribute_mail = Email Attribute auths.attribute_ssh_public_key = Public SSH Key Attribute auths.attributes_in_bind = Fetch Attributes in Bind DN Context +auths.allow_deactivate_all = Allow an empty search result to deactivate all users auths.use_paged_search = Use Paged Search auths.search_page_size = Page Size auths.filter = User Filter diff --git a/public/css/index.css b/public/css/index.css index fda26f4e08..c11b16c828 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -239,7 +239,7 @@ footer .ui.left,footer .ui.right{line-height:40px} .lines-commit .ui.avatar.image{height:18px;width:18px} .lines-code .bottom-line,.lines-commit .bottom-line,.lines-num .bottom-line{border-bottom:1px solid #eaecef} .code-view{overflow:auto;overflow-x:auto;overflow-y:hidden} -.code-view *{font-size:12px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;line-height:20px} +.code-view :not(.fa):not(.octicon):not(.icon){font-size:12px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;line-height:20px} .code-view table{width:100%} .code-view .active{background:#fff866} .markdown:not(code){overflow:hidden;font-size:16px;line-height:1.6!important;word-wrap:break-word} @@ -659,8 +659,6 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository #commits-table td.sha .sha.label.isSigned.isVerified:hover,.repository #repo-files-table .sha.label.isSigned.isVerified:hover{background:rgba(33,186,69,.3)!important} .repository .diff-detail-box{padding:7px 0;background:#fff;line-height:30px} .repository .diff-detail-box>div:after{clear:both;content:"";display:block} -.repository .diff-detail-box ol{clear:both;padding-left:0;margin-top:5px;margin-bottom:28px} -.repository .diff-detail-box ol li{list-style:none;padding-bottom:4px;margin-bottom:4px;border-bottom:1px dashed #ddd;padding-left:6px} .repository .diff-detail-box span.status{display:inline-block;width:12px;height:12px;margin-right:8px;vertical-align:middle} .repository .diff-detail-box span.status.modify{background-color:#f0db88} .repository .diff-detail-box span.status.add{background-color:#b4e2b4} @@ -697,6 +695,11 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository .diff-file-box.file-content{clear:right} .repository .diff-file-box.file-content img{max-width:100%;padding:5px 5px 0 5px} .repository .diff-file-box.file-content img.emoji{padding:0} +.repository .diff-stats{clear:both;margin-bottom:5px;max-height:400px;overflow:auto;padding-left:0} +.repository .diff-stats li{list-style:none;padding-bottom:4px;margin-bottom:4px;border-bottom:1px dashed #ddd;padding-left:6px} +.repository .diff-stats .diff-counter{margin-right:15px} +.repository .diff-stats .diff-counter .del{color:red} +.repository .diff-stats .diff-counter .add{color:green} .repository .repo-search-result{padding-top:10px;padding-bottom:10px} .repository .repo-search-result .lines-num a{color:inherit} .repository.quickstart .guide .item{padding:1em} diff --git a/public/js/index.js b/public/js/index.js index 3b15ad8f18..46ff00d01c 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,6 +1,6 @@ /* globals wipPrefixes, issuesTribute, emojiTribute */ /* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ -/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ +/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, submitReply, cancelCodeComment, onOAuthLoginClick */ 'use strict'; function htmlEncode(text) { @@ -306,7 +306,7 @@ function initReactionSelector(parent) { if (resp && (resp.html || resp.empty)) { const content = $(vm).closest('.content'); let react = content.find('.segment.reactions'); - if (react.length > 0) { + if (!resp.empty && react.length > 0) { react.remove(); } if (!resp.empty) { @@ -2873,7 +2873,8 @@ function initFilterBranchTagDropdown(selector) { }); } -$(".commit-button").click(function() { +$(".commit-button").click(function(e) { + e.preventDefault(); $(this).parent().find('.commit-body').toggle(); }); @@ -3133,10 +3134,11 @@ function deleteDependencyModal(id, type) { function initIssueList() { const repolink = $('#repolink').val(); + const tp = $('#type').val(); $('#new-dependency-drop-list') .dropdown({ apiSettings: { - url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}', + url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}&type='+tp, onResponse: function(response) { const filteredResponse = {'success': true, 'results': []}; const currIssueId = $('#new-dependency-drop-list').data('issue-id'); @@ -3169,6 +3171,14 @@ function cancelCodeComment(btn) { form.closest('.comment-code-cloud').remove() } } + +function submitReply(btn) { + const form = $(btn).closest('form'); + if (form.length > 0 && form.hasClass('comment-form')) { + form.submit(); + } +} + function onOAuthLoginClick() { const oauthLoader = $('#oauth2-login-loader'); const oauthNav = $('#oauth2-login-navigator'); diff --git a/public/less/_base.less b/public/less/_base.less index e295be368d..a916e08bd3 100644 --- a/public/less/_base.less +++ b/public/less/_base.less @@ -1047,7 +1047,7 @@ footer { overflow-x: auto; overflow-y: hidden; - * { + *:not(.fa):not(.octicon):not(.icon) { font-size: 12px; font-family: @monospaced-fonts, monospace; line-height: 20px; diff --git a/public/less/_repository.less b/public/less/_repository.less index 5f6a7fbd97..3384a9e106 100644 --- a/public/less/_repository.less +++ b/public/less/_repository.less @@ -1237,21 +1237,6 @@ display: block; } - ol { - clear: both; - padding-left: 0; - margin-top: 5px; - margin-bottom: 28px; - - li { - list-style: none; - padding-bottom: 4px; - margin-bottom: 4px; - border-bottom: 1px dashed #dddddd; - padding-left: 6px; - } - } - span.status { display: inline-block; width: 12px; @@ -1466,6 +1451,34 @@ } } + .diff-stats { + + clear: both; + margin-bottom: 5px; + max-height: 400px; + overflow: auto; + padding-left: 0; + + li { + list-style: none; + padding-bottom: 4px; + margin-bottom: 4px; + border-bottom: 1px dashed #dddddd; + padding-left: 6px; + } + + .diff-counter { + margin-right: 15px; + + .del { + color: red; + } + .add { + color: green; + } + } + } + .repo-search-result { padding-top: 10px; padding-bottom: 10px; diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 538d01f9a4..45bdbfe7f2 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -6,6 +6,7 @@ package admin import ( + "encoding/json" "fmt" "net/url" "os" @@ -25,6 +26,7 @@ import ( "code.gitea.io/gitea/services/mailer" "gitea.com/macaron/macaron" + "gitea.com/macaron/session" "github.com/unknwon/com" ) @@ -207,7 +209,7 @@ func SendTestMail(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/admin/config") } -func shadownPasswordKV(cfgItem, splitter string) string { +func shadowPasswordKV(cfgItem, splitter string) string { fields := strings.Split(cfgItem, splitter) for i := 0; i < len(fields); i++ { if strings.HasPrefix(fields[i], "password=") { @@ -218,10 +220,10 @@ func shadownPasswordKV(cfgItem, splitter string) string { return strings.Join(fields, splitter) } -func shadownURL(provider, cfgItem string) string { +func shadowURL(provider, cfgItem string) string { u, err := url.Parse(cfgItem) if err != nil { - log.Error("shodowPassword %v failed: %v", provider, err) + log.Error("Shadowing Password for %v failed: %v", provider, err) return cfgItem } if u.User != nil { @@ -239,7 +241,7 @@ func shadownURL(provider, cfgItem string) string { func shadowPassword(provider, cfgItem string) string { switch provider { case "redis": - return shadownPasswordKV(cfgItem, ",") + return shadowPasswordKV(cfgItem, ",") case "mysql": //root:@tcp(localhost:3306)/macaron?charset=utf8 atIdx := strings.Index(cfgItem, "@") @@ -253,15 +255,15 @@ func shadowPassword(provider, cfgItem string) string { case "postgres": // user=jiahuachen dbname=macaron port=5432 sslmode=disable if !strings.HasPrefix(cfgItem, "postgres://") { - return shadownPasswordKV(cfgItem, " ") + return shadowPasswordKV(cfgItem, " ") } - + fallthrough + case "couchbase": + return shadowURL(provider, cfgItem) // postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full - // Notice: use shadwonURL + // Notice: use shadowURL } - - // "couchbase" - return shadownURL(provider, cfgItem) + return cfgItem } // Config show admin config page @@ -306,8 +308,14 @@ func Config(ctx *context.Context) { ctx.Data["CacheItemTTL"] = setting.CacheService.TTL sessionCfg := setting.SessionConfig + if sessionCfg.Provider == "VirtualSession" { + var realSession session.Options + if err := json.Unmarshal([]byte(sessionCfg.ProviderConfig), &realSession); err != nil { + log.Error("Unable to unmarshall session config for virtualed provider config: %s\nError: %v", sessionCfg.ProviderConfig, err) + } + sessionCfg = realSession + } sessionCfg.ProviderConfig = shadowPassword(sessionCfg.Provider, sessionCfg.ProviderConfig) - ctx.Data["SessionConfig"] = sessionCfg ctx.Data["DisableGravatar"] = setting.DisableGravatar diff --git a/routers/admin/auths.go b/routers/admin/auths.go index 8e0c27e226..922f35d2ee 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -115,6 +115,7 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig { SearchPageSize: pageSize, Filter: form.Filter, AdminFilter: form.AdminFilter, + AllowDeactivateAll: form.AllowDeactivateAll, Enabled: true, }, } diff --git a/routers/admin/users.go b/routers/admin/users.go index fdc4e0e371..881baa6c16 100644 --- a/routers/admin/users.go +++ b/routers/admin/users.go @@ -79,12 +79,11 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) { } u := &models.User{ - Name: form.UserName, - Email: form.Email, - Passwd: form.Password, - IsActive: true, - LoginType: models.LoginPlain, - MustChangePassword: form.MustChangePassword, + Name: form.UserName, + Email: form.Email, + Passwd: form.Password, + IsActive: true, + LoginType: models.LoginPlain, } if len(form.LoginType) > 0 { @@ -95,9 +94,18 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) { u.LoginName = form.LoginName } } - if !password.IsComplexEnough(form.Password) { - ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form) - return + if u.LoginType == models.LoginNoType || u.LoginType == models.LoginPlain { + if len(form.Password) < setting.MinPasswordLength { + ctx.Data["Err_Password"] = true + ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserNew, &form) + return + } + if !password.IsComplexEnough(form.Password) { + ctx.Data["Err_Password"] = true + ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form) + return + } + u.MustChangePassword = form.MustChangePassword } if err := models.CreateUser(u); err != nil { switch { @@ -201,14 +209,19 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) { if len(form.Password) > 0 { var err error - if u.Salt, err = models.GetUserSalt(); err != nil { - ctx.ServerError("UpdateUser", err) + if len(form.Password) < setting.MinPasswordLength { + ctx.Data["Err_Password"] = true + ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserEdit, &form) return } if !password.IsComplexEnough(form.Password) { ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserEdit, &form) return } + if u.Salt, err = models.GetUserSalt(); err != nil { + ctx.ServerError("UpdateUser", err) + return + } u.HashPassword(form.Password) } diff --git a/routers/admin/users_test.go b/routers/admin/users_test.go index e054524fd1..2b36b45d49 100644 --- a/routers/admin/users_test.go +++ b/routers/admin/users_test.go @@ -34,7 +34,7 @@ func TestNewUserPost_MustChangePassword(t *testing.T) { LoginName: "local", UserName: username, Email: email, - Password: "xxxxxxxx", + Password: "abc123ABC!=$", SendNotify: false, MustChangePassword: true, } @@ -71,7 +71,7 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) { LoginName: "local", UserName: username, Email: email, - Password: "xxxxxxxx", + Password: "abc123ABC!=$", SendNotify: false, MustChangePassword: false, } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 04ff91fbbf..10b827253d 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -638,7 +638,7 @@ func RegisterRoutes(m *macaron.Macaron) { }, reqRepoReader(models.UnitTypeCode)) m.Group("/tags", func() { m.Get("", repo.ListTags) - }, reqRepoReader(models.UnitTypeCode)) + }, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true)) m.Group("/keys", func() { m.Combo("").Get(repo.ListDeployKeys). Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go index e0e7f609c7..724086dc6c 100644 --- a/routers/api/v1/convert/convert.go +++ b/routers/api/v1/convert/convert.go @@ -232,12 +232,9 @@ func ToTeam(team *models.Team) *api.Team { // ToUser convert models.User to api.User 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), - IsAdmin: user.IsAdmin, - LastLogin: user.LastLoginUnix.AsTime(), Created: user.CreatedUnix.AsTime(), } // hide primary email if API caller isn't user itself or an admin @@ -245,8 +242,11 @@ func ToUser(user *models.User, signed, authed bool) *api.User { result.Email = "" } else if user.KeepEmailPrivate && !authed { result.Email = user.GetEmail() - } else { + } else { // only user himself and admin could visit these information + result.ID = user.ID result.Email = user.Email + result.IsAdmin = user.IsAdmin + result.LastLogin = user.LastLoginUnix.AsTime() } return result } diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 0156aaaa05..163a06a95e 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -51,6 +51,7 @@ func GetSingleCommit(ctx *context.APIContext) { ctx.ServerError("OpenRepository", err) return } + defer gitRepo.Close() commit, err := gitRepo.GetCommit(ctx.Params(":sha")) if err != nil { ctx.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err) @@ -113,6 +114,7 @@ func GetAllCommits(ctx *context.APIContext) { ctx.ServerError("OpenRepository", err) return } + defer gitRepo.Close() page := ctx.QueryInt("page") if page <= 0 { diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index ae20e1e96b..f23e7bca9e 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -95,6 +95,7 @@ func GetArchive(ctx *context.APIContext) { return } ctx.Repo.GitRepo = gitRepo + defer gitRepo.Close() repo.Download(ctx.Context) } diff --git a/routers/api/v1/repo/git_ref.go b/routers/api/v1/repo/git_ref.go index d7acc139f0..c2bcbb3603 100644 --- a/routers/api/v1/repo/git_ref.go +++ b/routers/api/v1/repo/git_ref.go @@ -76,6 +76,8 @@ func getGitRefs(ctx *context.APIContext, filter string) ([]*git.Reference, strin if err != nil { return nil, "OpenRepository", err } + defer gitRepo.Close() + if len(filter) > 0 { filter = "refs/" + filter } diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index aab167bc68..256c347588 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -57,6 +57,10 @@ func ListIssues(ctx *context.APIContext) { // in: query // description: search string // type: string + // - name: type + // in: query + // description: filter by type (issues / pulls) if set + // type: string // responses: // "200": // "$ref": "#/responses/IssueList" @@ -91,6 +95,16 @@ func ListIssues(ctx *context.APIContext) { } } + var isPull util.OptionalBool + switch ctx.Query("type") { + case "pulls": + isPull = util.OptionalBoolTrue + case "issues": + isPull = util.OptionalBoolFalse + default: + isPull = util.OptionalBoolNone + } + // Only fetch the issues if we either don't have a keyword or the search returned issues // This would otherwise return all issues if no issues were found by the search. if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { @@ -101,6 +115,7 @@ func ListIssues(ctx *context.APIContext) { IsClosed: isClosed, IssueIDs: issueIDs, LabelIDs: labelIDs, + IsPull: isPull, }) } @@ -292,6 +307,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { return } issue.Repo = ctx.Repo.Repository + canWrite := ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) err = issue.LoadAttributes() if err != nil { @@ -299,7 +315,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { return } - if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) { + if !issue.IsPoster(ctx.User.ID) && !canWrite { ctx.Status(403) return } @@ -312,14 +328,13 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { } // Update the deadline - var deadlineUnix timeutil.TimeStamp - if form.Deadline != nil && !form.Deadline.IsZero() && ctx.Repo.CanWrite(models.UnitTypeIssues) { - deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix()) - } - - if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { - ctx.Error(500, "UpdateIssueDeadline", err) - return + if form.Deadline != nil && canWrite { + deadlineUnix := timeutil.TimeStamp(form.Deadline.Unix()) + if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { + ctx.Error(500, "UpdateIssueDeadline", err) + return + } + issue.DeadlineUnix = deadlineUnix } // Add/delete assignees @@ -330,7 +345,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { // Pass one or more user logins to replace the set of assignees on this Issue. // Send an empty array ([]) to clear all assignees from the Issue. - if ctx.Repo.CanWrite(models.UnitTypeIssues) && (form.Assignees != nil || form.Assignee != nil) { + if canWrite && (form.Assignees != nil || form.Assignee != nil) { oneAssignee := "" if form.Assignee != nil { oneAssignee = *form.Assignee @@ -343,7 +358,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { } } - if ctx.Repo.CanWrite(models.UnitTypeIssues) && form.Milestone != nil && + if canWrite && form.Milestone != nil && issue.MilestoneID != *form.Milestone { oldMilestoneID := issue.MilestoneID issue.MilestoneID = *form.Milestone @@ -353,8 +368,8 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { } } - if err = models.UpdateIssue(issue); err != nil { - ctx.Error(500, "UpdateIssue", err) + if err = models.UpdateIssueByAPI(issue); err != nil { + ctx.InternalServerError(err) return } if form.State != nil { @@ -373,7 +388,11 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { // Refetch from database to assign some automatic values issue, err = models.GetIssueByID(issue.ID) if err != nil { - ctx.Error(500, "GetIssueByID", err) + ctx.InternalServerError(err) + return + } + if err = issue.LoadMilestone(); err != nil { + ctx.InternalServerError(err) return } ctx.JSON(201, issue.APIFormat()) @@ -427,7 +446,7 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) { return } - if !ctx.Repo.CanWrite(models.UnitTypeIssues) { + if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { ctx.Status(403) return } @@ -494,7 +513,7 @@ func StartIssueStopwatch(ctx *context.APIContext) { return } - if !ctx.Repo.CanWrite(models.UnitTypeIssues) { + if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { ctx.Status(403) return } @@ -563,7 +582,7 @@ func StopIssueStopwatch(ctx *context.APIContext) { return } - if !ctx.Repo.CanWrite(models.UnitTypeIssues) { + if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { ctx.Status(403) return } diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 60796031a5..fca07ff213 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -185,7 +185,7 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti return } - if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { + if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin { ctx.Error(403, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked"))) return } diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index 9bc73852ca..e496915163 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -189,7 +189,12 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) { milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix()) } - if err := models.UpdateMilestone(milestone); err != nil { + var oldIsClosed = milestone.IsClosed + if form.State != nil { + milestone.IsClosed = *form.State == string(api.StateClosed) + } + + if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil { ctx.ServerError("UpdateMilestone", err) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 978c8a3f1f..0f1be9a6f6 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -190,10 +190,11 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption ) // Get repo/branch information - headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form) + _, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form) if ctx.Written() { return } + defer headGitRepo.Close() // Check if another PR exists with the same targets existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) @@ -265,15 +266,14 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption DeadlineUnix: deadlineUnix, } pr := &models.PullRequest{ - HeadRepoID: headRepo.ID, - BaseRepoID: repo.ID, - HeadUserName: headUser.Name, - HeadBranch: headBranch, - BaseBranch: baseBranch, - HeadRepo: headRepo, - BaseRepo: repo, - MergeBase: compareInfo.MergeBase, - Type: models.PullRequestGitea, + HeadRepoID: headRepo.ID, + BaseRepoID: repo.ID, + HeadBranch: headBranch, + BaseBranch: baseBranch, + HeadRepo: headRepo, + BaseRepo: repo, + MergeBase: compareInfo.MergeBase, + Type: models.PullRequestGitea, } // Get all assignee IDs @@ -369,14 +369,13 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { } // Update Deadline - var deadlineUnix timeutil.TimeStamp - if form.Deadline != nil && !form.Deadline.IsZero() { - deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix()) - } - - if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { - ctx.Error(500, "UpdateIssueDeadline", err) - return + if form.Deadline != nil { + deadlineUnix := timeutil.TimeStamp(form.Deadline.Unix()) + if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { + ctx.Error(500, "UpdateIssueDeadline", err) + return + } + issue.DeadlineUnix = deadlineUnix } // Add/delete assignees @@ -421,8 +420,8 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { } } - if err = models.UpdateIssue(issue); err != nil { - ctx.Error(500, "UpdateIssue", err) + if err = models.UpdateIssueByAPI(issue); err != nil { + ctx.InternalServerError(err) return } if form.State != nil { @@ -689,6 +688,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) // user should have permission to read baseRepo's codes and pulls, NOT headRepo's permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User) if err != nil { + headGitRepo.Close() ctx.ServerError("GetUserRepoPermission", err) return nil, nil, nil, nil, "", "" } @@ -699,6 +699,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) baseRepo, permBase) } + headGitRepo.Close() ctx.NotFound("Can't read pulls or can't read UnitTypeCode") return nil, nil, nil, nil, "", "" } @@ -706,6 +707,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) // user should have permission to read headrepo's codes permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) if err != nil { + headGitRepo.Close() ctx.ServerError("GetUserRepoPermission", err) return nil, nil, nil, nil, "", "" } @@ -716,18 +718,21 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headRepo, permHead) } + headGitRepo.Close() ctx.NotFound("Can't read headRepo UnitTypeCode") return nil, nil, nil, nil, "", "" } // Check if head branch is valid. if !headGitRepo.IsBranchExist(headBranch) { + headGitRepo.Close() ctx.NotFound() return nil, nil, nil, nil, "", "" } compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) if err != nil { + headGitRepo.Close() ctx.Error(500, "GetCompareInfo", err) return nil, nil, nil, nil, "", "" } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index a4417107ee..0c1db2c226 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -6,6 +6,8 @@ package repo import ( + "bytes" + "errors" "fmt" "net/http" "net/url" @@ -431,15 +433,53 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { opts.Releases = false } - repo, err := migrations.MigrateRepository(ctx.User, ctxUser.Name, opts) - if err == nil { - notification.NotifyCreateRepository(ctx.User, ctxUser, repo) - - log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) - ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin)) + repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + Name: opts.RepoName, + Description: opts.Description, + OriginalURL: form.CloneAddr, + IsPrivate: opts.Private, + IsMirror: opts.Mirror, + Status: models.RepositoryBeingMigrated, + }) + if err != nil { + handleMigrateError(ctx, ctxUser, remoteAddr, err) return } + opts.MigrateToRepoID = repo.ID + + defer func() { + if e := recover(); e != nil { + var buf bytes.Buffer + fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2)) + err = errors.New(buf.String()) + } + + if err == nil { + repo.Status = models.RepositoryReady + if err := models.UpdateRepositoryCols(repo, "status"); err == nil { + notification.NotifyMigrateRepository(ctx.User, ctxUser, repo) + return + } + } + + if repo != nil { + if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { + log.Error("DeleteRepository: %v", errDelete) + } + } + }() + + if _, err = migrations.MigrateRepository(ctx.User, ctxUser.Name, opts); err != nil { + handleMigrateError(ctx, ctxUser, remoteAddr, err) + return + } + + log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) + ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin)) +} + +func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) { switch { case models.IsErrRepoAlreadyExist(err): ctx.Error(409, "", "The repository with the same name already exists.") @@ -448,7 +488,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { case migrations.IsTwoFactorAuthError(err): ctx.Error(422, "", "Remote visit required two factors authentication.") case models.IsErrReachLimitOfRepo(err): - ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", ctxUser.MaxCreationLimit())) + ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit())) case models.IsErrNameReserved(err): ctx.Error(422, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name)) case models.IsErrNamePatternNotAllowed(err): diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index 48edd22910..7d4a2de389 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -41,8 +41,8 @@ func NewCommitStatus(ctx *context.APIContext, form api.CreateStatusOption) { // schema: // "$ref": "#/definitions/CreateStatusOption" // responses: - // "200": - // "$ref": "#/responses/StatusList" + // "201": + // "$ref": "#/responses/Status" sha := ctx.Params("sha") if len(sha) == 0 { ctx.Error(400, "sha not given", nil) diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index a802048285..12bac854ec 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -33,7 +33,7 @@ func ListTags(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/TagList" - tags, err := ctx.Repo.Repository.GetTags() + tags, err := ctx.Repo.GitRepo.GetTagInfos() if err != nil { ctx.Error(500, "GetTags", err) return diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index bb1302077b..0639494c0e 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -104,7 +104,7 @@ func GetInfo(ctx *context.APIContext) { return } - ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User.ID == u.ID || ctx.User.IsAdmin)) + ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User != nil && (ctx.User.ID == u.ID || ctx.User.IsAdmin))) } // GetAuthenticatedUser get current user's information diff --git a/routers/home.go b/routers/home.go index f8fb849c8e..97d5cbf332 100644 --- a/routers/home.go +++ b/routers/home.go @@ -141,6 +141,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { Keyword: keyword, OwnerID: opts.OwnerID, AllPublic: true, + AllLimited: true, TopicOnly: topicOnly, IncludeDescription: setting.UI.SearchRepoDescription, }) @@ -273,7 +274,7 @@ func ExploreOrganizations(ctx *context.Context) { // ExploreCode render explore code page func ExploreCode(ctx *context.Context) { if !setting.Indexer.RepoIndexerEnabled { - ctx.Redirect("/explore", 302) + ctx.Redirect(setting.AppSubURL+"/explore", 302) return } diff --git a/routers/repo/branch.go b/routers/repo/branch.go index 5d78518491..93eb384810 100644 --- a/routers/repo/branch.go +++ b/routers/repo/branch.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/util" + "gopkg.in/src-d/go-git.v4/plumbing" ) const ( @@ -32,6 +33,7 @@ type Branch struct { CommitsAhead int CommitsBehind int LatestPullRequest *models.PullRequest + MergeMovedOn bool } // Branches render repository branch page @@ -168,6 +170,12 @@ func loadBranches(ctx *context.Context) []*Branch { return nil } + repoIDToRepo := map[int64]*models.Repository{} + repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository + + repoIDToGitRepo := map[int64]*git.Repository{} + repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo + branches := make([]*Branch, len(rawBranches)) for i := range rawBranches { commit, err := rawBranches[i].GetCommit() @@ -196,11 +204,46 @@ func loadBranches(ctx *context.Context) []*Branch { ctx.ServerError("GetLatestPullRequestByHeadInfo", err) return nil } + headCommit := commit.ID.String() + + mergeMovedOn := false if pr != nil { + pr.HeadRepo = ctx.Repo.Repository if err := pr.LoadIssue(); err != nil { ctx.ServerError("pr.LoadIssue", err) return nil } + if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { + pr.BaseRepo = repo + } else if err := pr.LoadBaseRepo(); err != nil { + ctx.ServerError("pr.LoadBaseRepo", err) + return nil + } else { + repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo + } + pr.Issue.Repo = pr.BaseRepo + + if pr.HasMerged { + baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] + if !ok { + baseGitRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return nil + } + defer baseGitRepo.Close() + repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo + } + pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil && err != plumbing.ErrReferenceNotFound { + ctx.ServerError("GetBranchCommitID", err) + return nil + } + if err == nil && headCommit != pullCommit { + // the head has moved on from the merge - we shouldn't delete + mergeMovedOn = true + } + } } branches[i] = &Branch{ @@ -210,6 +253,7 @@ func loadBranches(ctx *context.Context) []*Branch { CommitsAhead: divergence.Ahead, CommitsBehind: divergence.Behind, LatestPullRequest: pr, + MergeMovedOn: mergeMovedOn, } } diff --git a/routers/repo/compare.go b/routers/repo/compare.go index f8534f68b7..a5faf5b429 100644 --- a/routers/repo/compare.go +++ b/routers/repo/compare.go @@ -152,6 +152,7 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * ctx.ServerError("OpenRepository", err) return nil, nil, nil, nil, "", "" } + defer headGitRepo.Close() } // user should have permission to read baseRepo's codes and pulls, NOT headRepo's @@ -171,30 +172,32 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * return nil, nil, nil, nil, "", "" } - // user should have permission to read headrepo's codes - permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) - if err != nil { - ctx.ServerError("GetUserRepoPermission", err) - return nil, nil, nil, nil, "", "" - } - if !permHead.CanRead(models.UnitTypeCode) { - if log.IsTrace() { - log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", - ctx.User, - headRepo, - permHead) + if !isSameRepo { + // user should have permission to read headrepo's codes + permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return nil, nil, nil, nil, "", "" + } + if !permHead.CanRead(models.UnitTypeCode) { + if log.IsTrace() { + log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", + ctx.User, + headRepo, + permHead) + } + ctx.NotFound("ParseCompareInfo", nil) + return nil, nil, nil, nil, "", "" } - ctx.NotFound("ParseCompareInfo", nil) - return nil, nil, nil, nil, "", "" } // Check if head branch is valid. - headIsCommit := ctx.Repo.GitRepo.IsCommitExist(headBranch) + headIsCommit := headGitRepo.IsCommitExist(headBranch) headIsBranch := headGitRepo.IsBranchExist(headBranch) headIsTag := headGitRepo.IsTagExist(headBranch) if !headIsCommit && !headIsBranch && !headIsTag { // Check if headBranch is short sha commit hash - if headCommit, _ := ctx.Repo.GitRepo.GetCommit(headBranch); headCommit != nil { + if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil { headBranch = headCommit.ID.String() ctx.Data["HeadBranch"] = headBranch headIsCommit = true @@ -223,7 +226,7 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * return nil, nil, nil, nil, "", "" } - compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) + compareInfo, err := headGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranch, headBranch) if err != nil { ctx.ServerError("GetCompareInfo", err) return nil, nil, nil, nil, "", "" @@ -339,12 +342,42 @@ func PrepareCompareDiff( return false } +// parseBaseRepoInfo parse base repository if current repo is forked. +// The "base" here means the repository where current repo forks from, +// not the repository fetch from current URL. +func parseBaseRepoInfo(ctx *context.Context, repo *models.Repository) error { + if !repo.IsFork { + return nil + } + if err := repo.GetBaseRepo(); err != nil { + return err + } + if err := repo.BaseRepo.GetOwnerName(); err != nil { + return err + } + baseGitRepo, err := git.OpenRepository(models.RepoPath(repo.BaseRepo.OwnerName, repo.BaseRepo.Name)) + if err != nil { + return err + } + ctx.Data["BaseRepoBranches"], err = baseGitRepo.GetBranches() + if err != nil { + return err + } + return nil +} + // CompareDiff show different from one commit to another commit func CompareDiff(ctx *context.Context) { headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx) if ctx.Written() { return } + defer headGitRepo.Close() + var err error + if err = parseBaseRepoInfo(ctx, headRepo); err != nil { + ctx.ServerError("parseBaseRepoInfo", err) + return + } nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch) if ctx.Written() { @@ -374,7 +407,7 @@ func CompareDiff(ctx *context.Context) { if !nothingToCompare { // Setup information for new form. - RetrieveRepoMetas(ctx, ctx.Repo.Repository) + RetrieveRepoMetas(ctx, ctx.Repo.Repository, true) if ctx.Written() { return } @@ -383,6 +416,11 @@ func CompareDiff(ctx *context.Context) { beforeCommitID := ctx.Data["BeforeCommitID"].(string) afterCommitID := ctx.Data["AfterCommitID"].(string) + if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil { + ctx.ServerError("GetAssignees", err) + return + } + ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID) ctx.Data["IsRepoToolbarCommits"] = true diff --git a/routers/repo/download.go b/routers/repo/download.go index 2da8b109ca..7ef0574b1d 100644 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" @@ -33,7 +34,12 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error { name = strings.Replace(name, ",", " ", -1) if base.IsTextFile(buf) || ctx.QueryBool("render") { - ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") + cs, err := charset.DetectEncoding(buf) + if err != nil { + log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err) + cs = "utf-8" + } + ctx.Resp.Header().Set("Content-Type", "text/plain; charset="+strings.ToLower(cs)) } else if base.IsImageFile(buf) || base.IsPDFFile(buf) { ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name)) } else { @@ -84,6 +90,11 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { if err != nil { return err } + defer func() { + if err = lfsDataRc.Close(); err != nil { + log.Error("ServeBlobOrLFS: Close: %v", err) + } + }() return ServeData(ctx, ctx.Repo.TreePath, lfsDataRc) } diff --git a/routers/repo/editor_test.go b/routers/repo/editor_test.go index ca00be74b7..ec7aee1e77 100644 --- a/routers/repo/editor_test.go +++ b/routers/repo/editor_test.go @@ -48,6 +48,8 @@ func TestGetUniquePatchBranchName(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + expectedBranchName := "user2-patch-1" branchName := GetUniquePatchBranchName(ctx) assert.Equal(t, expectedBranchName, branchName) @@ -61,9 +63,12 @@ func TestGetClosestParentWithFiles(t *testing.T) { test.LoadRepoCommit(t, ctx) test.LoadUser(t, ctx, 2) test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository branch := repo.DefaultBranch gitRepo, _ := git.OpenRepository(repo.RepoPath()) + defer gitRepo.Close() commit, _ := gitRepo.GetBranchCommit(branch) expectedTreePath := "" diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 16a049c7aa..8e44b363d9 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -67,7 +67,7 @@ func MustAllowUserComment(ctx *context.Context) { return } - if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { + if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin { ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked")) ctx.Redirect(issue.HTMLURL()) return @@ -261,7 +261,8 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB } ctx.Data["IssueStats"] = issueStats - ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64() + ctx.Data["SelLabelIDs"] = labelIDs + ctx.Data["SelectLabels"] = selectLabels ctx.Data["ViewType"] = viewType ctx.Data["SortType"] = sortType ctx.Data["MilestoneID"] = milestoneID @@ -345,8 +346,8 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos } // RetrieveRepoMetas find all the meta information of a repository -func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label { - if !ctx.Repo.CanWrite(models.UnitTypeIssues) { +func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository, isPull bool) []*models.Label { + if !ctx.Repo.CanWriteIssuesOrPulls(isPull) { return nil } @@ -370,7 +371,7 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models. ctx.Data["Branches"] = brs // Contains true if the user can create issue dependencies - ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User) + ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, isPull) return labels } @@ -440,7 +441,7 @@ func NewIssue(ctx *context.Context) { setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates) renderAttachmentSettings(ctx) - RetrieveRepoMetas(ctx, ctx.Repo.Repository) + RetrieveRepoMetas(ctx, ctx.Repo.Repository, false) if ctx.Written() { return } @@ -455,7 +456,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b err error ) - labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository) + labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository, isPull) if ctx.Written() { return nil, nil, 0 } @@ -775,8 +776,16 @@ func ViewIssue(ctx *context.Context) { } } + if issue.IsPull && !ctx.Repo.CanRead(models.UnitTypeIssues) { + ctx.Data["IssueType"] = "pulls" + } else if !issue.IsPull && !ctx.Repo.CanRead(models.UnitTypePullRequests) { + ctx.Data["IssueType"] = "issues" + } else { + ctx.Data["IssueType"] = "all" + } + // Check if the user can use the dependencies - ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User) + ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) // Render comments and and fetch participants. participants[0] = issue.Poster @@ -930,7 +939,10 @@ func ViewIssue(ctx *context.Context) { ctx.Data["IsBlockedByApprovals"] = pull.ProtectedBranch.RequiredApprovals > 0 && cnt < pull.ProtectedBranch.RequiredApprovals ctx.Data["GrantedApprovals"] = cnt } - ctx.Data["IsPullBranchDeletable"] = canDelete && pull.HeadRepo != nil && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) + ctx.Data["IsPullBranchDeletable"] = canDelete && + pull.HeadRepo != nil && + git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) && + (!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"]) ctx.Data["PullReviewersWithType"], err = models.GetReviewersByPullID(issue.ID) if err != nil { @@ -959,7 +971,6 @@ func ViewIssue(ctx *context.Context) { ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) ctx.Data["IsIssueWriter"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin) - ctx.Data["IsRepoIssuesWriter"] = ctx.IsSigned && (ctx.Repo.CanWrite(models.UnitTypeIssues) || ctx.User.IsAdmin) ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons ctx.HTML(200, tplIssueView) } @@ -1204,7 +1215,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { ctx.Error(403) } - if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { + if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin { ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked")) ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther) return diff --git a/routers/repo/issue_dependency.go b/routers/repo/issue_dependency.go index 730271126d..5a661f54cd 100644 --- a/routers/repo/issue_dependency.go +++ b/routers/repo/issue_dependency.go @@ -14,14 +14,6 @@ import ( // AddDependency adds new dependencies func AddDependency(ctx *context.Context) { - // Check if the Repo is allowed to have dependencies - if !ctx.Repo.CanCreateIssueDependencies(ctx.User) { - ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies") - return - } - - depID := ctx.QueryInt64("newDependency") - issueIndex := ctx.ParamsInt64("index") issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex) if err != nil { @@ -29,6 +21,14 @@ func AddDependency(ctx *context.Context) { return } + // Check if the Repo is allowed to have dependencies + if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) { + ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies") + return + } + + depID := ctx.QueryInt64("newDependency") + // Redirect defer ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther) @@ -68,14 +68,6 @@ func AddDependency(ctx *context.Context) { // RemoveDependency removes the dependency func RemoveDependency(ctx *context.Context) { - // Check if the Repo is allowed to have dependencies - if !ctx.Repo.CanCreateIssueDependencies(ctx.User) { - ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies") - return - } - - depID := ctx.QueryInt64("removeDependencyID") - issueIndex := ctx.ParamsInt64("index") issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex) if err != nil { @@ -83,8 +75,13 @@ func RemoveDependency(ctx *context.Context) { return } - // Redirect - ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther) + // Check if the Repo is allowed to have dependencies + if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) { + ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies") + return + } + + depID := ctx.QueryInt64("removeDependencyID") // Dependency Type depTypeStr := ctx.Req.PostForm.Get("dependencyType") @@ -116,4 +113,7 @@ func RemoveDependency(ctx *context.Context) { ctx.ServerError("RemoveIssueDependency", err) return } + + // Redirect + ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther) } diff --git a/routers/repo/middlewares.go b/routers/repo/middlewares.go index 6ff09cb845..e513189f6b 100644 --- a/routers/repo/middlewares.go +++ b/routers/repo/middlewares.go @@ -10,6 +10,11 @@ import ( // SetEditorconfigIfExists set editor config as render variable func SetEditorconfigIfExists(ctx *context.Context) { + if ctx.Repo.Repository.IsEmpty { + ctx.Data["Editorconfig"] = nil + return + } + ec, err := ctx.Repo.GetEditorconfig() if err != nil && !git.IsErrNotExist(err) { diff --git a/routers/repo/milestone.go b/routers/repo/milestone.go index b4056cc6d1..b5c6f12917 100644 --- a/routers/repo/milestone.go +++ b/routers/repo/milestone.go @@ -192,7 +192,7 @@ func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) { m.Name = form.Title m.Content = form.Content m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix()) - if err = models.UpdateMilestone(m); err != nil { + if err = models.UpdateMilestone(m, m.IsClosed); err != nil { ctx.ServerError("UpdateMilestone", err) return } diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 7af01c46ba..d3c17db4e7 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" + "code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/gitdiff" @@ -272,12 +273,12 @@ func checkPullInfo(ctx *context.Context) *models.Issue { } func setMergeTarget(ctx *context.Context, pull *models.PullRequest) { - if ctx.Repo.Owner.Name == pull.HeadUserName { + if ctx.Repo.Owner.Name == pull.MustHeadUserName() { ctx.Data["HeadTarget"] = pull.HeadBranch } else if pull.HeadRepo == nil { - ctx.Data["HeadTarget"] = pull.HeadUserName + ":" + pull.HeadBranch + ctx.Data["HeadTarget"] = pull.MustHeadUserName() + ":" + pull.HeadBranch } else { - ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch + ctx.Data["HeadTarget"] = pull.MustHeadUserName() + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch } ctx.Data["BaseTarget"] = pull.BaseBranch } @@ -314,73 +315,93 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare repo := ctx.Repo.Repository pull := issue.PullRequest - var err error - if err = pull.GetHeadRepo(); err != nil { + if err := pull.GetHeadRepo(); err != nil { ctx.ServerError("GetHeadRepo", err) return nil } + if err := pull.GetBaseRepo(); err != nil { + ctx.ServerError("GetBaseRepo", err) + return nil + } + setMergeTarget(ctx, pull) - if err = pull.LoadProtectedBranch(); err != nil { + if err := pull.LoadProtectedBranch(); err != nil { ctx.ServerError("GetLatestCommitStatus", err) return nil } ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck - var headGitRepo *git.Repository + baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return nil + } + defer baseGitRepo.Close() var headBranchExist bool + var headBranchSha string // HeadRepo may be missing if pull.HeadRepo != nil { - headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath()) + var err error + + headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath()) if err != nil { ctx.ServerError("OpenRepository", err) return nil } + defer headGitRepo.Close() headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch) if headBranchExist { - sha, err := headGitRepo.GetBranchCommitID(pull.HeadBranch) + headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch) if err != nil { ctx.ServerError("GetBranchCommitID", err) return nil } - - commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0) - if err != nil { - ctx.ServerError("GetLatestCommitStatus", err) - return nil - } - if len(commitStatuses) > 0 { - ctx.Data["LatestCommitStatuses"] = commitStatuses - ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) - } - - if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck { - ctx.Data["is_context_required"] = func(context string) bool { - for _, c := range pull.ProtectedBranch.StatusCheckContexts { - if c == context { - return true - } - } - return false - } - ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts) - } } } - if pull.HeadRepo == nil || !headBranchExist { - ctx.Data["IsPullRequestBroken"] = true - ctx.Data["HeadTarget"] = "deleted" - ctx.Data["NumCommits"] = 0 - ctx.Data["NumFiles"] = 0 + sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName()) + if err != nil { + ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err) return nil } - compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(repo.Owner.Name, repo.Name), - pull.BaseBranch, pull.HeadBranch) + commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0) + if err != nil { + ctx.ServerError("GetLatestCommitStatus", err) + return nil + } + if len(commitStatuses) > 0 { + ctx.Data["LatestCommitStatuses"] = commitStatuses + ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) + } + + if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck { + ctx.Data["is_context_required"] = func(context string) bool { + for _, c := range pull.ProtectedBranch.StatusCheckContexts { + if c == context { + return true + } + } + return false + } + ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts) + } + + ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha + ctx.Data["HeadBranchCommitID"] = headBranchSha + ctx.Data["PullHeadCommitID"] = sha + + if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha { + ctx.Data["IsPullRequestBroken"] = true + ctx.Data["HeadTarget"] = "deleted" + } + + compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), + pull.BaseBranch, pull.GetGitRefName()) if err != nil { if strings.Contains(err.Error(), "fatal: Not a valid object name") { ctx.Data["IsPullRequestBroken"] = true @@ -440,7 +461,7 @@ func ViewPullCommits(ctx *context.Context) { ctx.NotFound("ViewPullCommits", nil) return } - ctx.Data["Username"] = pull.HeadUserName + ctx.Data["Username"] = pull.MustHeadUserName() ctx.Data["Reponame"] = pull.HeadRepo.Name commits = prInfo.Commits } @@ -512,13 +533,14 @@ func ViewPullFiles(ctx *context.Context) { return } - headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name) + headRepoPath := pull.HeadRepo.RepoPath() headGitRepo, err := git.OpenRepository(headRepoPath) if err != nil { ctx.ServerError("OpenRepository", err) return } + defer headGitRepo.Close() headCommitID, err := headGitRepo.GetBranchCommitID(pull.HeadBranch) if err != nil { @@ -531,8 +553,8 @@ func ViewPullFiles(ctx *context.Context) { endCommitID = headCommitID gitRepo = headGitRepo - headTarget = path.Join(pull.HeadUserName, pull.HeadRepo.Name) - ctx.Data["Username"] = pull.HeadUserName + headTarget = path.Join(pull.MustHeadUserName(), pull.HeadRepo.Name) + ctx.Data["Username"] = pull.MustHeadUserName() ctx.Data["Reponame"] = pull.HeadRepo.Name } @@ -704,6 +726,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) if ctx.Written() { return } + defer headGitRepo.Close() labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, true) if ctx.Written() { @@ -754,15 +777,14 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) Content: form.Content, } pullRequest := &models.PullRequest{ - HeadRepoID: headRepo.ID, - BaseRepoID: repo.ID, - HeadUserName: headUser.Name, - HeadBranch: headBranch, - BaseBranch: baseBranch, - HeadRepo: headRepo, - BaseRepo: repo, - MergeBase: prInfo.MergeBase, - Type: models.PullRequestGitea, + HeadRepoID: headRepo.ID, + BaseRepoID: repo.ID, + HeadBranch: headBranch, + BaseBranch: baseBranch, + HeadRepo: headRepo, + BaseRepo: repo, + MergeBase: prInfo.MergeBase, + Type: models.PullRequestGitea, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt // instead of 500. @@ -871,12 +893,14 @@ func CleanUpPullRequest(ctx *context.Context) { ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err) return } + defer gitRepo.Close() gitBaseRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) if err != nil { ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err) return } + defer gitBaseRepo.Close() defer func() { ctx.JSON(200, map[string]interface{}{ @@ -924,6 +948,21 @@ func CleanUpPullRequest(ctx *context.Context) { return } + if err := repofiles.PushUpdate( + pr.HeadRepo, + pr.HeadBranch, + models.PushUpdateOptions{ + RefFullName: git.BranchPrefix + pr.HeadBranch, + OldCommitID: branchCommitID, + NewCommitID: git.EmptySHA, + PusherID: ctx.User.ID, + PusherName: ctx.User.Name, + RepoUserName: pr.HeadRepo.Owner.Name, + RepoName: pr.HeadRepo.Name, + }); err != nil { + log.Error("Update: %v", err) + } + if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil { // Do not fail here as branch has already been deleted log.Error("DeleteBranch: %v", err) @@ -1005,6 +1044,7 @@ func DownloadPullPatch(ctx *context.Context) { ctx.ServerError("OpenRepository", err) return } + defer headGitRepo.Close() patch, err := headGitRepo.GetFormatPatch(pr.MergeBase, pr.HeadBranch) if err != nil { diff --git a/routers/repo/pull_review.go b/routers/repo/pull_review.go index 5eb0dfe9a7..601fb44c2c 100644 --- a/routers/repo/pull_review.go +++ b/routers/repo/pull_review.go @@ -117,9 +117,7 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) { // can not approve/reject your own PR case models.ReviewTypeApprove, models.ReviewTypeReject: - - if issue.Poster.ID == ctx.User.ID { - + if issue.IsPoster(ctx.User.ID) { var translated string if reviewType == models.ReviewTypeApprove { @@ -174,6 +172,12 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) { return } } + + // Hotfix 1.10.0: make sure the review exists before creating the head comment + if err = review.Publish(); err != nil { + ctx.ServerError("Publish", err) + return + } comm, err := models.CreateComment(&models.CreateCommentOptions{ Type: models.CommentTypeReview, Doer: ctx.User, @@ -186,10 +190,6 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) { ctx.ServerError("CreateComment", err) return } - if err = review.Publish(); err != nil { - ctx.ServerError("Publish", err) - return - } pr, err := issue.GetPullRequest() if err != nil { diff --git a/routers/repo/release_test.go b/routers/repo/release_test.go index 524c1c7346..47d1a89b54 100644 --- a/routers/repo/release_test.go +++ b/routers/repo/release_test.go @@ -57,5 +57,6 @@ func TestNewReleasePost(t *testing.T) { Title: testCase.Form.Title, Note: testCase.Form.Content, }, models.Cond("is_draft=?", len(testCase.Form.Draft) > 0)) + ctx.Repo.GitRepo.Close() } } diff --git a/routers/repo/repo.go b/routers/repo/repo.go index bfd0c771b0..dd401a20ad 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -291,6 +291,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { } var opts = migrations.MigrateOptions{ + OriginalURL: form.CloneAddr, CloneAddr: remoteAddr, RepoName: form.RepoName, Description: form.Description, diff --git a/routers/repo/setting.go b/routers/repo/setting.go index c74b273420..80f10cd8a8 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -71,6 +71,11 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { // Check if repository name has been changed. if repo.LowerName != strings.ToLower(newRepoName) { isNameChanged = true + // Close the GitRepo if open + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + ctx.Repo.GitRepo = nil + } if err := models.ChangeRepositoryName(ctx.Repo.Owner, repo.Name, newRepoName); err != nil { ctx.Data["Err_RepoName"] = true switch { @@ -378,6 +383,11 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { } oldOwnerID := ctx.Repo.Owner.ID + // Close the GitRepo if open + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + ctx.Repo.GitRepo = nil + } if err = models.TransferOwnership(ctx.User, newOwner, repo); err != nil { if models.IsErrRepoAlreadyExist(err) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) @@ -590,7 +600,7 @@ func AddTeamPost(ctx *context.Context) { } name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("team"))) - if len(name) == 0 || ctx.Repo.Owner.LowerName == name { + if len(name) == 0 { ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") return } diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index 02fbe4a1dd..bd68473bf4 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -65,27 +65,20 @@ type PageMeta struct { // findEntryForFile finds the tree entry for a target filepath. func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) { - entries, err := commit.ListEntries() - if err != nil { + entry, err := commit.GetTreeEntryByPath(target) + if err != nil && !git.IsErrNotExist(err) { return nil, err } - // The longest name should be checked first - for _, entry := range entries { - if entry.IsRegular() && entry.Name() == target { - return entry, nil - } + if entry != nil { + return entry, nil } + // Then the unescaped, shortest alternative var unescapedTarget string if unescapedTarget, err = url.QueryUnescape(target); err != nil { return nil, err } - for _, entry := range entries { - if entry.IsRegular() && entry.Name() == unescapedTarget { - return entry, nil - } - } - return nil, nil + return commit.GetTreeEntryByPath(unescapedTarget) } func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) { @@ -122,10 +115,9 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { // wikiContentsByName returns the contents of a wiki page, along with a boolean // indicating whether the page exists. Writes to ctx if an error occurs. func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) { - var entry *git.TreeEntry - var err error pageFilename := models.WikiNameToFilename(wikiName) - if entry, err = findEntryForFile(commit, pageFilename); err != nil { + entry, err := findEntryForFile(commit, pageFilename) + if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findEntryForFile", err) return nil, nil, "", false } else if entry == nil { @@ -146,6 +138,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { // Get page list. entries, err := commit.ListEntries() if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } ctx.ServerError("ListEntries", err) return nil, nil } @@ -159,6 +154,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if models.IsErrWikiInvalidFileName(err) { continue } + if wikiRepo != nil { + wikiRepo.Close() + } ctx.ServerError("WikiFilenameToName", err) return nil, nil } else if wikiName == "_Sidebar" || wikiName == "_Footer" { @@ -188,16 +186,25 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") } if entry == nil || ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } return nil, nil } sidebarContent, _, _, _ := wikiContentsByName(ctx, commit, "_Sidebar") if ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } return nil, nil } footerContent, _, _, _ := wikiContentsByName(ctx, commit, "_Footer") if ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } return nil, nil } @@ -218,6 +225,9 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { wikiRepo, commit, err := findWikiRepoCommit(ctx) if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } if !git.IsErrNotExist(err) { ctx.ServerError("GetBranchCommit", err) } @@ -241,6 +251,9 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") } if entry == nil || ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } return nil, nil } @@ -263,6 +276,9 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) // get Commit Count commitsHistory, err := wikiRepo.CommitsByFileAndRangeNoFollow("master", pageFilename, page) if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } ctx.ServerError("CommitsByFileAndRangeNoFollow", err) return nil, nil } @@ -279,13 +295,21 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) } func renderEditPage(ctx *context.Context) { - _, commit, err := findWikiRepoCommit(ctx) + wikiRepo, commit, err := findWikiRepoCommit(ctx) if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } if !git.IsErrNotExist(err) { ctx.ServerError("GetBranchCommit", err) } return } + defer func() { + if wikiRepo != nil { + wikiRepo.Close() + } + }() // get requested pagename pageName := models.NormalizeWikiName(ctx.Params(":page")) @@ -327,8 +351,16 @@ func Wiki(ctx *context.Context) { wikiRepo, entry := renderViewPage(ctx) if ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } return } + defer func() { + if wikiRepo != nil { + wikiRepo.Close() + } + }() if entry == nil { ctx.Data["Title"] = ctx.Tr("repo.wiki") ctx.HTML(200, tplWikiStart) @@ -364,8 +396,16 @@ func WikiRevision(ctx *context.Context) { wikiRepo, entry := renderRevisionPage(ctx) if ctx.Written() { + if wikiRepo != nil { + wikiRepo.Close() + } return } + defer func() { + if wikiRepo != nil { + wikiRepo.Close() + } + }() if entry == nil { ctx.Data["Title"] = ctx.Tr("repo.wiki") ctx.HTML(200, tplWikiStart) @@ -397,11 +437,18 @@ func WikiPages(ctx *context.Context) { wikiRepo, commit, err := findWikiRepoCommit(ctx) if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } return } entries, err := commit.ListEntries() if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } + ctx.ServerError("ListEntries", err) return } @@ -412,6 +459,10 @@ func WikiPages(ctx *context.Context) { } c, err := wikiRepo.GetCommitByPath(entry.Name()) if err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } + ctx.ServerError("GetCommit", err) return } @@ -420,6 +471,10 @@ func WikiPages(ctx *context.Context) { if models.IsErrWikiInvalidFileName(err) { continue } + if wikiRepo != nil { + wikiRepo.Close() + } + ctx.ServerError("WikiFilenameToName", err) return } @@ -431,6 +486,11 @@ func WikiPages(ctx *context.Context) { } ctx.Data["Pages"] = pages + defer func() { + if wikiRepo != nil { + wikiRepo.Close() + } + }() ctx.HTML(200, tplWikiPages) } @@ -449,7 +509,7 @@ func WikiRaw(ctx *context.Context) { if commit != nil { // Try to find a file with that name entry, err = findEntryForFile(commit, providedPath) - if err != nil { + if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findFile", err) return } @@ -462,7 +522,7 @@ func WikiRaw(ctx *context.Context) { wikiPath := models.WikiNameToFilename(providedPath) entry, err = findEntryForFile(commit, wikiPath) - if err != nil { + if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findFile", err) return } diff --git a/routers/repo/wiki_test.go b/routers/repo/wiki_test.go index 4687d24f6b..50a2500a01 100644 --- a/routers/repo/wiki_test.go +++ b/routers/repo/wiki_test.go @@ -23,6 +23,7 @@ const message = "Wiki commit message for unit tests" func wikiEntry(t *testing.T, repo *models.Repository, wikiName string) *git.TreeEntry { wikiRepo, err := git.OpenRepository(repo.WikiPath()) assert.NoError(t, err) + defer wikiRepo.Close() commit, err := wikiRepo.GetBranchCommit("master") assert.NoError(t, err) entries, err := commit.ListEntries() @@ -190,6 +191,7 @@ func TestDeleteWikiPagePost(t *testing.T) { func TestWikiRaw(t *testing.T) { for filepath, filetype := range map[string]string{ "jpeg.jpg": "image/jpeg", + "images/jpeg.jpg": "image/jpeg", "Page With Spaced Name": "text/plain; charset=utf-8", "Page-With-Spaced-Name": "text/plain; charset=utf-8", "Page With Spaced Name.md": "text/plain; charset=utf-8", diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 8dfcdb9c9b..c02b73212f 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -457,7 +457,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost) m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost) - m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) + m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) }) m.Group("/auths", func() { @@ -532,18 +532,12 @@ func RegisterRoutes(m *macaron.Macaron) { reqRepoReleaseReader := context.RequireRepoReader(models.UnitTypeReleases) reqRepoWikiWriter := context.RequireRepoWriter(models.UnitTypeWiki) reqRepoIssueReader := context.RequireRepoReader(models.UnitTypeIssues) + reqRepoIssueWriter := context.RequireRepoWriter(models.UnitTypeIssues) reqRepoPullsWriter := context.RequireRepoWriter(models.UnitTypePullRequests) reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests) reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests) reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests) - reqRepoIssueWriter := func(ctx *context.Context) { - if !ctx.Repo.CanWrite(models.UnitTypeIssues) { - ctx.Error(403) - return - } - } - // ***** START: Organization ***** m.Group("/org", func() { m.Group("", func() { @@ -590,6 +584,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost) m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost) m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost) + m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) m.Get("/:id", repo.WebHooksEdit) m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost) @@ -597,6 +592,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost) m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost) + m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) }) m.Route("/delete", "GET,POST", org.SettingsDelete) diff --git a/routers/user/auth.go b/routers/user/auth.go index 82a508e4dc..b1f926c77f 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -707,7 +707,7 @@ func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Requ // LinkAccount shows the page where the user can decide to login or create a new account func LinkAccount(ctx *context.Context) { - ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration + ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration ctx.Data["Title"] = ctx.Tr("link_account") ctx.Data["LinkAccountMode"] = true ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha @@ -757,7 +757,7 @@ func LinkAccount(ctx *context.Context) { // LinkAccountPostSignIn handle the coupling of external account with another account using signIn func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) { - ctx.Data["DisablePassword"] = setting.Service.AllowOnlyExternalRegistration + ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration ctx.Data["Title"] = ctx.Tr("link_account") ctx.Data["LinkAccountMode"] = true ctx.Data["LinkAccountModeSignIn"] = true @@ -786,6 +786,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) { u, err := models.UserSignIn(signInForm.UserName, signInForm.Password) if err != nil { if models.IsErrUserNotExist(err) { + ctx.Data["user_exists"] = true ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplLinkAccount, &signInForm) } else { ctx.ServerError("UserLinkAccount", err) @@ -840,7 +841,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) { func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterForm) { // TODO Make insecure passwords optional for local accounts also, // once email-based Second-Factor Auth is available - ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration + ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration ctx.Data["Title"] = ctx.Tr("link_account") ctx.Data["LinkAccountMode"] = true ctx.Data["LinkAccountModeRegister"] = true @@ -1070,6 +1071,11 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplSignUp, &form) return } + if !password.IsComplexEnough(form.Password) { + ctx.Data["Err_Password"] = true + ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplSignUp, &form) + return + } u := &models.User{ Name: form.UserName, @@ -1276,7 +1282,7 @@ func ForgotPasswdPost(ctx *context.Context) { ctx.HTML(200, tplForgotPassword) } -func commonResetPassword(ctx *context.Context) *models.User { +func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) { code := ctx.Query("code") ctx.Data["Title"] = ctx.Tr("auth.reset_password") @@ -1288,14 +1294,25 @@ func commonResetPassword(ctx *context.Context) *models.User { if len(code) == 0 { ctx.Flash.Error(ctx.Tr("auth.invalid_code")) - return nil + return nil, nil } // Fail early, don't frustrate the user u := models.VerifyUserActiveCode(code) if u == nil { ctx.Flash.Error(ctx.Tr("auth.invalid_code")) - return nil + return nil, nil + } + + twofa, err := models.GetTwoFactorByUID(u.ID) + if err != nil { + if !models.IsErrTwoFactorNotEnrolled(err) { + ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error()) + return nil, nil + } + } else { + ctx.Data["has_two_factor"] = true + ctx.Data["scratch_code"] = ctx.QueryBool("scratch_code") } // Show the user that they are affecting the account that they intended to @@ -1303,10 +1320,10 @@ func commonResetPassword(ctx *context.Context) *models.User { if nil != ctx.User && u.ID != ctx.User.ID { ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.User.Email, u.Email)) - return nil + return nil, nil } - return u + return u, twofa } // ResetPasswd render the account recovery page @@ -1314,13 +1331,19 @@ func ResetPasswd(ctx *context.Context) { ctx.Data["IsResetForm"] = true commonResetPassword(ctx) + if ctx.Written() { + return + } ctx.HTML(200, tplResetPassword) } // ResetPasswdPost response from account recovery request func ResetPasswdPost(ctx *context.Context) { - u := commonResetPassword(ctx) + u, twofa := commonResetPassword(ctx) + if ctx.Written() { + return + } if u == nil { // Flash error has been set @@ -1342,6 +1365,39 @@ func ResetPasswdPost(ctx *context.Context) { return } + // Handle two-factor + regenerateScratchToken := false + if twofa != nil { + if ctx.QueryBool("scratch_code") { + if !twofa.VerifyScratchToken(ctx.Query("token")) { + ctx.Data["IsResetForm"] = true + ctx.Data["Err_Token"] = true + ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil) + return + } + regenerateScratchToken = true + } else { + passcode := ctx.Query("passcode") + ok, err := twofa.ValidateTOTP(passcode) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error()) + return + } + if !ok || twofa.LastUsedPasscode == passcode { + ctx.Data["IsResetForm"] = true + ctx.Data["Err_Passcode"] = true + ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil) + return + } + + twofa.LastUsedPasscode = passcode + if err = models.UpdateTwoFactor(twofa); err != nil { + ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err) + return + } + } + } + var err error if u.Rands, err = models.GetUserSalt(); err != nil { ctx.ServerError("UpdateUser", err) @@ -1351,7 +1407,6 @@ func ResetPasswdPost(ctx *context.Context) { ctx.ServerError("UpdateUser", err) return } - u.HashPassword(passwd) u.MustChangePassword = false if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil { @@ -1360,9 +1415,27 @@ func ResetPasswdPost(ctx *context.Context) { } log.Trace("User password reset: %s", u.Name) - ctx.Data["IsResetFailed"] = true remember := len(ctx.Query("remember")) != 0 + + if regenerateScratchToken { + // Invalidate the scratch token. + _, err = twofa.GenerateScratchToken() + if err != nil { + ctx.ServerError("UserSignIn", err) + return + } + if err = models.UpdateTwoFactor(twofa); err != nil { + ctx.ServerError("UserSignIn", err) + return + } + + handleSignInFull(ctx, u, remember, false) + ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used")) + ctx.Redirect(setting.AppSubURL + "/user/settings/security") + return + } + handleSignInFull(ctx, u, remember, true) } diff --git a/routers/user/avatar.go b/routers/user/avatar.go index 20597c72ff..045206c50a 100644 --- a/routers/user/avatar.go +++ b/routers/user/avatar.go @@ -6,6 +6,7 @@ package user import ( "strconv" + "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -23,14 +24,19 @@ func Avatar(ctx *context.Context) { log.Debug("Asked avatar for user %v and size %v", userName, size) - user, err := models.GetUserByName(userName) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.ServerError("Requested avatar for invalid user", err) - } else { - ctx.ServerError("Retrieving user by name", err) + var user *models.User + if strings.ToLower(userName) != "ghost" { + user, err = models.GetUserByName(userName) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.ServerError("Requested avatar for invalid user", err) + } else { + ctx.ServerError("Retrieving user by name", err) + } + return } - return + } else { + user = models.NewGhostUser() } ctx.Redirect(user.RealSizedAvatarLink(size)) diff --git a/routers/user/home.go b/routers/user/home.go index 40b3bc3fc1..605db86a08 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -74,7 +74,9 @@ func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) { if act.ActUser != nil { userCache[act.ActUserID] = act.ActUser } + } + for _, act := range actions { repoOwner, ok := userCache[act.Repo.OwnerID] if !ok { repoOwner, err = models.GetUserByID(act.Repo.OwnerID) diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go index c782224216..73799c8bd7 100644 --- a/routers/user/setting/account.go +++ b/routers/user/setting/account.go @@ -28,7 +28,6 @@ func Account(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true ctx.Data["Email"] = ctx.User.Email - ctx.Data["EmailNotificationsPreference"] = ctx.User.EmailNotifications() loadAccountData(ctx) @@ -54,7 +53,7 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) { } else if form.Password != form.Retype { ctx.Flash.Error(ctx.Tr("form.password_not_match")) } else if !password.IsComplexEnough(form.Password) { - ctx.Flash.Error(ctx.Tr("settings.password_complexity")) + ctx.Flash.Error(ctx.Tr("form.password_complexity")) } else { var err error if ctx.User.Salt, err = models.GetUserSalt(); err != nil { @@ -230,4 +229,5 @@ func loadAccountData(ctx *context.Context) { return } ctx.Data["Emails"] = emails + ctx.Data["EmailNotificationsPreference"] = ctx.User.EmailNotifications() } diff --git a/routers/user/setting/account_test.go b/routers/user/setting/account_test.go index 497ee658b0..41783e19d7 100644 --- a/routers/user/setting/account_test.go +++ b/routers/user/setting/account_test.go @@ -19,76 +19,64 @@ import ( func TestChangePassword(t *testing.T) { oldPassword := "password" setting.MinPasswordLength = 6 - setting.PasswordComplexity = map[string]string{ - "lower": "[a-z]+", - "upper": "[A-Z]+", - "digit": "[0-9]+", - "spec": "[-_]+", - } - var pcLUN = map[string]string{ - "lower": "[a-z]+", - "upper": "[A-Z]+", - "digit": "[0-9]+", - } - var pcLU = map[string]string{ - "lower": "[a-z]+", - "upper": "[A-Z]+", - } + var pcALL = []string{"lower", "upper", "digit", "spec"} + var pcLUN = []string{"lower", "upper", "digit"} + var pcLU = []string{"lower", "upper"} for _, req := range []struct { OldPassword string NewPassword string Retype string Message string - PasswordComplexity map[string]string + PasswordComplexity []string }{ { OldPassword: oldPassword, NewPassword: "Qwerty123456-", Retype: "Qwerty123456-", Message: "", - PasswordComplexity: setting.PasswordComplexity, + PasswordComplexity: pcALL, }, { OldPassword: oldPassword, NewPassword: "12345", Retype: "12345", Message: "auth.password_too_short", - PasswordComplexity: setting.PasswordComplexity, + PasswordComplexity: pcALL, }, { OldPassword: "12334", NewPassword: "123456", Retype: "123456", Message: "settings.password_incorrect", - PasswordComplexity: setting.PasswordComplexity, + PasswordComplexity: pcALL, }, { OldPassword: oldPassword, NewPassword: "123456", Retype: "12345", Message: "form.password_not_match", - PasswordComplexity: setting.PasswordComplexity, + PasswordComplexity: pcALL, }, { OldPassword: oldPassword, NewPassword: "Qwerty", Retype: "Qwerty", - Message: "settings.password_complexity", - PasswordComplexity: setting.PasswordComplexity, + Message: "form.password_complexity", + PasswordComplexity: pcALL, }, { OldPassword: oldPassword, NewPassword: "Qwerty", Retype: "Qwerty", - Message: "settings.password_complexity", + Message: "form.password_complexity", PasswordComplexity: pcLUN, }, { OldPassword: oldPassword, NewPassword: "QWERTY", Retype: "QWERTY", - Message: "settings.password_complexity", + Message: "form.password_complexity", PasswordComplexity: pcLU, }, } { diff --git a/services/comments/comments.go b/services/comments/comments.go index e8448e9065..406cd79ce6 100644 --- a/services/comments/comments.go +++ b/services/comments/comments.go @@ -60,6 +60,7 @@ func CreateCodeComment(doer *models.User, repo *models.Repository, issue *models if err != nil { return nil, fmt.Errorf("OpenRepository: %v", err) } + defer gitRepo.Close() // FIXME validate treePath // Get latest commit referencing the commented line diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go index 800546f123..45773fdb12 100644 --- a/services/externalaccount/user.go +++ b/services/externalaccount/user.go @@ -5,7 +5,6 @@ package externalaccount import ( - "strconv" "strings" "code.gitea.io/gitea/models" @@ -45,10 +44,7 @@ func LinkAccountToUser(user *models.User, gothUser goth.User) error { return err } - externalID, err := strconv.ParseInt(externalLoginUser.ExternalID, 10, 64) - if err != nil { - return err - } + externalID := externalLoginUser.ExternalID var tp structs.GitServiceType for _, s := range structs.SupportedFullGitService { diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index c2c5675d9f..b2f5adeeee 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -546,6 +546,15 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D // Get new file. if strings.HasPrefix(line, cmdDiffHead) { + if len(diff.Files) >= maxFiles { + diff.IsIncomplete = true + _, err := io.Copy(ioutil.Discard, reader) + if err != nil { + return nil, fmt.Errorf("Copy: %v", err) + } + break + } + var middle int // Note: In case file name is surrounded by double quotes (it happens only in git-shell). @@ -590,14 +599,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D IsRenamed: a != b, } diff.Files = append(diff.Files, curFile) - if len(diff.Files) >= maxFiles { - diff.IsIncomplete = true - _, err := io.Copy(ioutil.Discard, reader) - if err != nil { - return nil, fmt.Errorf("Copy: %v", err) - } - break - } curFileLinesCount = 0 curFileLFSPrefix = false @@ -678,6 +679,7 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID if err != nil { return nil, err } + defer gitRepo.Close() commit, err := gitRepo.GetCommit(afterCommitID) if err != nil { @@ -750,6 +752,7 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff if err != nil { return fmt.Errorf("OpenRepository: %v", err) } + defer repo.Close() commit, err := repo.GetCommit(endCommit) if err != nil { diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index acf3c6d7bd..ea25b38dff 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -6,11 +6,13 @@ package gitdiff import ( + "fmt" "html/template" "strings" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" dmp "github.com/sergi/go-diff/diffmatchpatch" @@ -195,3 +197,15 @@ func TestDiffLine_GetCommentSide(t *testing.T) { assert.Equal(t, "previous", (&DiffLine{Comments: []*models.Comment{{Line: -3}}}).GetCommentSide()) assert.Equal(t, "proposed", (&DiffLine{Comments: []*models.Comment{{Line: 3}}}).GetCommentSide()) } + +func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) { + git.Debug = true + for _, behavior := range []string{"-w", "--ignore-space-at-eol", "-b", ""} { + diffs, err := GetDiffRangeWithWhitespaceBehavior("./testdata/academic-module", "559c156f8e0178b71cb44355428f24001b08fc68", "bd7063cc7c04689c4d082183d32a604ed27a24f9", + setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles, behavior) + assert.NoError(t, err, fmt.Sprintf("Error when diff with %s", behavior)) + for _, f := range diffs.Files { + assert.True(t, len(f.Sections) > 0, fmt.Sprintf("%s should have sections", f.Name)) + } + } +} diff --git a/services/gitdiff/testdata/academic-module/HEAD b/services/gitdiff/testdata/academic-module/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/services/gitdiff/testdata/academic-module/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/services/gitdiff/testdata/academic-module/config b/services/gitdiff/testdata/academic-module/config new file mode 100644 index 0000000000..1bc26be514 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/services/gitdiff/testdata/academic-module/description b/services/gitdiff/testdata/academic-module/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/services/gitdiff/testdata/academic-module/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/services/gitdiff/testdata/academic-module/hooks/applypatch-msg.sample b/services/gitdiff/testdata/academic-module/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/services/gitdiff/testdata/academic-module/hooks/commit-msg.sample b/services/gitdiff/testdata/academic-module/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/services/gitdiff/testdata/academic-module/hooks/fsmonitor-watchman.sample b/services/gitdiff/testdata/academic-module/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000000..e673bb3980 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/fsmonitor-watchman.sample @@ -0,0 +1,114 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 1) and a time in nanoseconds +# formatted as a string and outputs to stdout all files that have been +# modified since the given time. Paths must be relative to the root of +# the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $time) = @ARGV; + +# Check the hook interface version + +if ($version == 1) { + # convert nanoseconds to seconds + $time = int $time / 1000000000; +} else { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree; +if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $git_work_tree = Win32::GetCwd(); + $git_work_tree =~ tr/\\/\//; +} else { + require Cwd; + $git_work_tree = Cwd::cwd(); +} + +my $retry = 1; + +launch_watchman(); + +sub launch_watchman { + + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $time but were not transient (ie created after + # $time but no longer exist). + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + # + # The category of transient files that we want to ignore will have a + # creation clock (cclock) newer than $time_t value and will also not + # currently exist. + + my $query = <<" END"; + ["query", "$git_work_tree", { + "since": $time, + "fields": ["name"], + "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] + }] + END + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + my $json_pkg; + eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; + } or do { + require JSON::PP; + $json_pkg = "JSON::PP"; + }; + + my $o = $json_pkg->new->utf8->decode($response); + + if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { + print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; + $retry--; + qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + print "/\0"; + eval { launch_watchman() }; + exit 0; + } + + die "Watchman: $o->{error}.\n" . + "Falling back to scanning...\n" if $o->{error}; + + binmode STDOUT, ":utf8"; + local $, = "\0"; + print @{$o->{files}}; +} diff --git a/services/gitdiff/testdata/academic-module/hooks/post-update.sample b/services/gitdiff/testdata/academic-module/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/services/gitdiff/testdata/academic-module/hooks/pre-applypatch.sample b/services/gitdiff/testdata/academic-module/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/services/gitdiff/testdata/academic-module/hooks/pre-commit.sample b/services/gitdiff/testdata/academic-module/hooks/pre-commit.sample new file mode 100755 index 0000000000..6a75641638 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/services/gitdiff/testdata/academic-module/hooks/pre-push.sample b/services/gitdiff/testdata/academic-module/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/services/gitdiff/testdata/academic-module/hooks/pre-rebase.sample b/services/gitdiff/testdata/academic-module/hooks/pre-rebase.sample new file mode 100755 index 0000000000..6cbef5c370 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/services/gitdiff/testdata/academic-module/hooks/pre-receive.sample b/services/gitdiff/testdata/academic-module/hooks/pre-receive.sample new file mode 100755 index 0000000000..a1fd29ec14 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/services/gitdiff/testdata/academic-module/hooks/prepare-commit-msg.sample b/services/gitdiff/testdata/academic-module/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..10fa14c5ab --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/services/gitdiff/testdata/academic-module/hooks/update.sample b/services/gitdiff/testdata/academic-module/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/services/gitdiff/testdata/academic-module/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/services/gitdiff/testdata/academic-module/index b/services/gitdiff/testdata/academic-module/index new file mode 100644 index 0000000000..e712c906e3 Binary files /dev/null and b/services/gitdiff/testdata/academic-module/index differ diff --git a/services/gitdiff/testdata/academic-module/info/exclude b/services/gitdiff/testdata/academic-module/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/services/gitdiff/testdata/academic-module/logs/HEAD b/services/gitdiff/testdata/academic-module/logs/HEAD new file mode 100644 index 0000000000..16b2e1c0f6 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 bd7063cc7c04689c4d082183d32a604ed27a24f9 Lunny Xiao 1574829684 +0800 clone: from https://try.gitea.io/shemgp-aiias/academic-module diff --git a/services/gitdiff/testdata/academic-module/logs/refs/heads/master b/services/gitdiff/testdata/academic-module/logs/refs/heads/master new file mode 100644 index 0000000000..16b2e1c0f6 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 bd7063cc7c04689c4d082183d32a604ed27a24f9 Lunny Xiao 1574829684 +0800 clone: from https://try.gitea.io/shemgp-aiias/academic-module diff --git a/services/gitdiff/testdata/academic-module/logs/refs/remotes/origin/HEAD b/services/gitdiff/testdata/academic-module/logs/refs/remotes/origin/HEAD new file mode 100644 index 0000000000..16b2e1c0f6 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 bd7063cc7c04689c4d082183d32a604ed27a24f9 Lunny Xiao 1574829684 +0800 clone: from https://try.gitea.io/shemgp-aiias/academic-module diff --git a/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.idx b/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.idx new file mode 100644 index 0000000000..4d759aa504 Binary files /dev/null and b/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.idx differ diff --git a/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.pack b/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.pack new file mode 100644 index 0000000000..2dc49cfded Binary files /dev/null and b/services/gitdiff/testdata/academic-module/objects/pack/pack-597efbc3613c7ba790e33b178fd9fc1fe17b4245.pack differ diff --git a/services/gitdiff/testdata/academic-module/packed-refs b/services/gitdiff/testdata/academic-module/packed-refs new file mode 100644 index 0000000000..13b5611650 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +bd7063cc7c04689c4d082183d32a604ed27a24f9 refs/remotes/origin/master diff --git a/services/gitdiff/testdata/academic-module/refs/heads/master b/services/gitdiff/testdata/academic-module/refs/heads/master new file mode 100644 index 0000000000..bd2b56eaf4 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/refs/heads/master @@ -0,0 +1 @@ +bd7063cc7c04689c4d082183d32a604ed27a24f9 diff --git a/services/gitdiff/testdata/academic-module/refs/remotes/origin/HEAD b/services/gitdiff/testdata/academic-module/refs/remotes/origin/HEAD new file mode 100644 index 0000000000..6efe28fff8 --- /dev/null +++ b/services/gitdiff/testdata/academic-module/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 7bfc5fd4da..a5e956f3d3 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -197,8 +197,10 @@ func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) { return nil, false } if err = models.SyncReleasesWithTags(m.Repo, gitRepo); err != nil { + gitRepo.Close() log.Error("Failed to synchronize tags to releases for repository: %v", err) } + gitRepo.Close() if err := m.Repo.UpdateSize(); err != nil { log.Error("Failed to update size for mirror repository: %v", err) @@ -290,97 +292,103 @@ func Update() { func SyncMirrors() { // Start listening on new sync requests. for repoID := range mirrorQueue.Queue() { - log.Trace("SyncMirrors [repo_id: %v]", repoID) - mirrorQueue.Remove(repoID) + syncMirror(repoID) + } +} - m, err := models.GetMirrorByRepoID(com.StrTo(repoID).MustInt64()) +func syncMirror(repoID string) { + log.Trace("SyncMirrors [repo_id: %v]", repoID) + mirrorQueue.Remove(repoID) + + m, err := models.GetMirrorByRepoID(com.StrTo(repoID).MustInt64()) + if err != nil { + log.Error("GetMirrorByRepoID [%s]: %v", repoID, err) + return + + } + + results, ok := runSync(m) + if !ok { + return + } + + m.ScheduleNextUpdate() + if err = models.UpdateMirror(m); err != nil { + log.Error("UpdateMirror [%s]: %v", repoID, err) + return + } + + var gitRepo *git.Repository + if len(results) == 0 { + log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID) + } else { + gitRepo, err = git.OpenRepository(m.Repo.RepoPath()) if err != nil { - log.Error("GetMirrorByRepoID [%s]: %v", repoID, err) + log.Error("OpenRepository [%d]: %v", m.RepoID, err) + return + } + defer gitRepo.Close() + } + + for _, result := range results { + // Discard GitHub pull requests, i.e. refs/pull/* + if strings.HasPrefix(result.refName, "refs/pull/") { continue } - results, ok := runSync(m) - if !ok { + // Create reference + if result.oldCommitID == gitShortEmptySha { + if err = models.MirrorSyncCreateAction(m.Repo, result.refName); err != nil { + log.Error("MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err) + } continue } - m.ScheduleNextUpdate() - if err = models.UpdateMirror(m); err != nil { - log.Error("UpdateMirror [%s]: %v", repoID, err) + // Delete reference + if result.newCommitID == gitShortEmptySha { + if err = models.MirrorSyncDeleteAction(m.Repo, result.refName); err != nil { + log.Error("MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err) + } continue } - var gitRepo *git.Repository - if len(results) == 0 { - log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID) - } else { - gitRepo, err = git.OpenRepository(m.Repo.RepoPath()) - if err != nil { - log.Error("OpenRepository [%d]: %v", m.RepoID, err) - continue - } - } - - for _, result := range results { - // Discard GitHub pull requests, i.e. refs/pull/* - if strings.HasPrefix(result.refName, "refs/pull/") { - continue - } - - // Create reference - if result.oldCommitID == gitShortEmptySha { - if err = models.MirrorSyncCreateAction(m.Repo, result.refName); err != nil { - log.Error("MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err) - } - continue - } - - // Delete reference - if result.newCommitID == gitShortEmptySha { - if err = models.MirrorSyncDeleteAction(m.Repo, result.refName); err != nil { - log.Error("MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err) - } - continue - } - - // Push commits - oldCommitID, err := git.GetFullCommitID(gitRepo.Path, result.oldCommitID) - if err != nil { - log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) - continue - } - newCommitID, err := git.GetFullCommitID(gitRepo.Path, result.newCommitID) - if err != nil { - log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) - continue - } - commits, err := gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID) - if err != nil { - log.Error("CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err) - continue - } - if err = models.MirrorSyncPushAction(m.Repo, models.MirrorSyncPushActionOptions{ - RefName: result.refName, - OldCommitID: oldCommitID, - NewCommitID: newCommitID, - Commits: models.ListToPushCommits(commits), - }); err != nil { - log.Error("MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err) - continue - } - } - - // Get latest commit date and update to current repository updated time - commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath()) + // Push commits + oldCommitID, err := git.GetFullCommitID(gitRepo.Path, result.oldCommitID) if err != nil { - log.Error("GetLatestCommitDate [%d]: %v", m.RepoID, err) + log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) continue } + newCommitID, err := git.GetFullCommitID(gitRepo.Path, result.newCommitID) + if err != nil { + log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) + continue + } + commits, err := gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID) + if err != nil { + log.Error("CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err) + continue + } + if err = models.MirrorSyncPushAction(m.Repo, models.MirrorSyncPushActionOptions{ + RefName: result.refName, + OldCommitID: oldCommitID, + NewCommitID: newCommitID, + Commits: models.ListToPushCommits(commits), + }); err != nil { + log.Error("MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err) + continue + } + } - if err = models.UpdateRepositoryUpdatedTime(m.RepoID, commitDate); err != nil { - log.Error("Update repository 'updated_unix' [%d]: %v", m.RepoID, err) - continue - } + // Get latest commit date and update to current repository updated time + commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath()) + if err != nil { + log.Error("GetLatestCommitDate [%d]: %v", m.RepoID, err) + return + } + + if err = models.UpdateRepositoryUpdatedTime(m.RepoID, commitDate); err != nil { + log.Error("Update repository 'updated_unix' [%d]: %v", m.RepoID, err) + return } } diff --git a/services/mirror/mirror_test.go b/services/mirror/mirror_test.go index 9ad11b7265..37e8a7be57 100644 --- a/services/mirror/mirror_test.go +++ b/services/mirror/mirror_test.go @@ -51,6 +51,7 @@ func TestRelease_MirrorDelete(t *testing.T) { gitRepo, err := git.OpenRepository(repoPath) assert.NoError(t, err) + defer gitRepo.Close() findOptions := models.FindReleasesOptions{IncludeDrafts: true, IncludeTags: true} initCount, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions) diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index 2872db7bd2..ca00cdaad9 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -55,6 +55,7 @@ func IsPullCommitStatusPass(pr *models.PullRequest) (bool, error) { if err != nil { return false, errors.Wrap(err, "OpenRepository") } + defer headGitRepo.Close() if !headGitRepo.IsBranchExist(pr.HeadBranch) { return false, errors.New("Head branch does not exist, can not merge") diff --git a/services/pull/merge.go b/services/pull/merge.go index e83784f31e..db071418da 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strings" "code.gitea.io/gitea/models" @@ -65,7 +66,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor } }() - headRepoPath := models.RepoPath(pr.HeadUserName, pr.HeadRepo.Name) + headRepoPath := pr.HeadRepo.RepoPath() if err := git.InitRepository(tmpBasePath, false); err != nil { return fmt.Errorf("git init: %v", err) @@ -260,14 +261,17 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor } } - headUser, err := models.GetUserByName(pr.HeadUserName) + var headUser *models.User + err = pr.HeadRepo.GetOwner() if err != nil { if !models.IsErrUserNotExist(err) { - log.Error("Can't find user: %s for head repository - %v", pr.HeadUserName, err) + log.Error("Can't find user: %d for head repository - %v", pr.HeadRepo.OwnerID, err) return err } - log.Error("Can't find user: %s for head repository - defaulting to doer: %s - %v", pr.HeadUserName, doer.Name, err) + log.Error("Can't find user: %d for head repository - defaulting to doer: %s - %v", pr.HeadRepo.OwnerID, doer.Name, err) headUser = doer + } else { + headUser = pr.HeadRepo.Owner } env := models.FullPushingEnvironment( @@ -325,16 +329,31 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor return nil } +var escapedSymbols = regexp.MustCompile(`([*[?! \\])`) + func getDiffTree(repoPath, baseBranch, headBranch string) (string, error) { getDiffTreeFromBranch := func(repoPath, baseBranch, headBranch string) (string, error) { var outbuf, errbuf strings.Builder // Compute the diff-tree for sparse-checkout - if err := git.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "--root", baseBranch, headBranch, "--").RunInDirPipeline(repoPath, &outbuf, &errbuf); err != nil { + if err := git.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "-z", "--root", baseBranch, headBranch, "--").RunInDirPipeline(repoPath, &outbuf, &errbuf); err != nil { return "", fmt.Errorf("git diff-tree [%s base:%s head:%s]: %s", repoPath, baseBranch, headBranch, errbuf.String()) } return outbuf.String(), nil } + scanNullTerminatedStrings := func(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexByte(data, '\x00'); i >= 0 { + return i + 1, data[0:i], nil + } + if atEOF { + return len(data), data, nil + } + return 0, nil, nil + } + list, err := getDiffTreeFromBranch(repoPath, baseBranch, headBranch) if err != nil { return "", err @@ -343,8 +362,14 @@ func getDiffTree(repoPath, baseBranch, headBranch string) (string, error) { // Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched. out := bytes.Buffer{} scanner := bufio.NewScanner(strings.NewReader(list)) + scanner.Split(scanNullTerminatedStrings) for scanner.Scan() { - fmt.Fprintf(&out, "/%s\n", scanner.Text()) + filepath := scanner.Text() + // escape '*', '?', '[', spaces and '!' prefix + filepath = escapedSymbols.ReplaceAllString(filepath, `\$1`) + // no necessary to escape the first '#' symbol because the first symbol is '/' + fmt.Fprintf(&out, "/%s\n", filepath) } + return out.String(), nil } diff --git a/services/release/release.go b/services/release/release.go index b4f814a77f..da7359e3f0 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" ) @@ -37,6 +38,54 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error { return err } rel.LowerTagName = strings.ToLower(rel.TagName) + // Prepare Notify + if err := rel.LoadAttributes(); err != nil { + log.Error("LoadAttributes: %v", err) + return err + } + + refName := git.TagPrefix + rel.TagName + apiPusher := rel.Publisher.APIFormat() + apiRepo := rel.Repo.APIFormat(models.AccessModeNone) + apiCommits, err := models.NewPushCommits().ToAPIPayloadCommits(rel.Repo.RepoPath(), rel.Repo.HTMLURL()) + if err != nil { + log.Error("commits.ToAPIPayloadCommits failed: %v", err) + return err + } + + if err := models.PrepareWebhooks(rel.Repo, models.HookEventPush, &api.PushPayload{ + Ref: refName, + Before: git.EmptySHA, + After: commit.ID.String(), + CompareURL: setting.AppURL + models.NewPushCommits().CompareURL, + Commits: apiCommits, + Repo: apiRepo, + Pusher: apiPusher, + Sender: apiPusher, + }); err != nil { + log.Error("PrepareWebhooks: %v", err) + } + + gitRepo, err := git.OpenRepository(rel.Repo.RepoPath()) + if err != nil { + log.Error("OpenRepository[%s]: %v", rel.Repo.RepoPath(), err) + } + shaSum, err := gitRepo.GetTagCommitID(refName) + if err != nil { + gitRepo.Close() + log.Error("GetTagCommitID[%s]: %v", refName, err) + } + gitRepo.Close() + + if err = models.PrepareWebhooks(rel.Repo, models.HookEventCreate, &api.CreatePayload{ + Ref: git.RefEndName(refName), + Sha: shaSum, + RefType: "tag", + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks: %v", err) + } } commit, err := gitRepo.GetTagCommit(rel.TagName) if err != nil { diff --git a/services/release/release_test.go b/services/release/release_test.go index d30dfee286..effab21251 100644 --- a/services/release/release_test.go +++ b/services/release/release_test.go @@ -27,6 +27,7 @@ func TestRelease_Create(t *testing.T) { gitRepo, err := git.OpenRepository(repoPath) assert.NoError(t, err) + defer gitRepo.Close() assert.NoError(t, CreateRelease(gitRepo, &models.Release{ RepoID: repo.ID, diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 6a176a43a8..ec5af3d4f0 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -112,6 +112,12 @@ {{end}} +
+
+ + +
+
{{end}} diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 79eec31dfd..c2793ece9d 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -270,7 +270,7 @@
{{.i18n.Tr "admin.config.session_provider"}}
{{.SessionConfig.Provider}}
{{.i18n.Tr "admin.config.provider_config"}}
-
{{if .SessionConfig.ProviderConfig}}{{.SessionConfig.ProviderConfig  | JsonPrettyPrint}}{{else}}-{{end}}
+
{{if .SessionConfig.ProviderConfig}}{{.SessionConfig.ProviderConfig}}{{else}}-{{end}}
{{.i18n.Tr "admin.config.cookie_name"}}
{{.SessionConfig.CookieName}}
{{.i18n.Tr "admin.config.gc_interval_time"}}
diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl index b9e326e734..06b7d70efe 100644 --- a/templates/admin/user/new.tmpl +++ b/templates/admin/user/new.tmpl @@ -42,7 +42,7 @@ -
+
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 13718620da..2a4b895dc2 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -63,11 +63,13 @@ noMatchTemplate: function () { return null }, menuItemTemplate: function (item) { var user = item.original; - var itemStr = '' + user.name + ''; + var item = $('
') + item.append($('', {'src': user.avatar})) + item.append($('', {'class': 'name'}).text(user.name)) if (user.fullname && user.fullname != '') { - itemStr += '' + user.fullname + ''; + item.append($('', {'class': 'fullname'}).text(user.fullname)) } - return itemStr; + return item.html(); } }); var content = document.getElementById('content'); diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 9c53f4e67a..a110d1ec66 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -73,21 +73,27 @@
{{end}} - + {{if not .LatestPullRequest}} {{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} {{end}} + {{else if and .LatestPullRequest.HasMerged .MergeMovedOn}} + {{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} + + + + {{end}} {{else}} - #{{.LatestPullRequest.Issue.Index}} + {{if ne .LatestPullRequest.BaseRepoID .LatestPullRequest.HeadRepoID}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}} {{if .LatestPullRequest.HasMerged}} - {{$.i18n.Tr "repo.pulls.merged"}} + {{$.i18n.Tr "repo.pulls.merged"}} {{else if .LatestPullRequest.Issue.IsClosed}} - {{$.i18n.Tr "repo.issues.closed_title"}} + {{$.i18n.Tr "repo.issues.closed_title"}} {{else}} - {{$.i18n.Tr "repo.issues.open_title"}} + {{$.i18n.Tr "repo.issues.open_title"}} {{end}} {{end}} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index b5fde36a6f..604b83c8df 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -16,8 +16,8 @@

{{.i18n.Tr "repo.diff.data_not_available"}}

{{else}} -
-
+
+
{{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}}
@@ -32,17 +32,17 @@ {{end}}
-
    +
      {{range .Diff.Files}}
    1. {{if not .IsBin}} - {{.Addition}} + +{{.Addition}}
      - {{.Deletion}} + -{{.Deletion}} {{else}} {{$.i18n.Tr "repo.diff.bin"}} {{end}} @@ -53,188 +53,187 @@
    2. {{end}}
    -
- - {{range $i, $file := .Diff.Files}} - {{if $file.IsIncomplete}} -
-

-
- {{if not $file.IsRenamed}} - + {{.Addition}} - -
-
-
- - {{.Deletion}} + {{range $i, $file := .Diff.Files}} + {{if $file.IsIncomplete}} +
+

+
+ {{if not $file.IsRenamed}} + + {{.Addition}} + +
+
+
+ - {{.Deletion}} + {{end}} +
+ {{$file.Name}} +
{{$.i18n.Tr "repo.diff.file_suppressed"}}
+ {{if not $file.IsSubmodule}} + {{if $file.IsDeleted}} + {{$.i18n.Tr "repo.diff.view_file"}} + {{else}} + {{$.i18n.Tr "repo.diff.view_file"}} + {{end}} {{end}} -

- {{$file.Name}} -
{{$.i18n.Tr "repo.diff.file_suppressed"}}
- {{if not $file.IsSubmodule}} - {{if $file.IsDeleted}} - {{$.i18n.Tr "repo.diff.view_file"}} - {{else}} - {{$.i18n.Tr "repo.diff.view_file"}} +

+
+ {{else}} +
+

+
+ {{if $file.IsBin}} + {{$.i18n.Tr "repo.diff.bin"}} + {{else if not $file.IsRenamed}} + + {{.Addition}} + +
+
+
+ - {{.Deletion}} + {{end}} +
+ {{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}{{if .IsLFSFile}} ({{$.i18n.Tr "repo.stored_lfs"}}){{end}} + {{if not $file.IsSubmodule}} + {{if $file.IsDeleted}} + {{$.i18n.Tr "repo.diff.view_file"}} + {{else}} + {{$.i18n.Tr "repo.diff.view_file"}} + {{end}} {{end}} - {{end}} -

-
- {{else}} -
-

-
- {{if $file.IsBin}} - {{$.i18n.Tr "repo.diff.bin"}} - {{else if not $file.IsRenamed}} - + {{.Addition}} - -
-
-
- - {{.Deletion}} - {{end}} -
- {{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}{{if .IsLFSFile}} ({{$.i18n.Tr "repo.stored_lfs"}}){{end}} - {{if not $file.IsSubmodule}} - {{if $file.IsDeleted}} - {{$.i18n.Tr "repo.diff.view_file"}} - {{else}} - {{$.i18n.Tr "repo.diff.view_file"}} - {{end}} - {{end}} -

-
- {{if ne $file.Type 4}} - {{$isImage := false}} - {{if $file.IsDeleted}} - {{$isImage = (call $.IsImageFileInBase $file.Name)}} - {{else}} - {{$isImage = (call $.IsImageFileInHead $file.Name)}} - {{end}} -
- - - {{if $isImage}} - {{template "repo/diff/image_diff" dict "file" . "root" $}} - {{else}} - {{if $.IsSplitStyle}} - {{$highlightClass := $file.GetHighlightClass}} - {{range $j, $section := $file.Sections}} - {{range $k, $line := $section.Lines}} - - - - - - - - - {{if gt (len $line.Comments) 0}} - - - - - - - + +
+ {{if ne $file.Type 4}} + {{$isImage := false}} + {{if $file.IsDeleted}} + {{$isImage = (call $.IsImageFileInBase $file.Name)}} + {{else}} + {{$isImage = (call $.IsImageFileInHead $file.Name)}} + {{end}} +
+
{{if $line.LeftIdx}}{{end}}{{if and $.SignedUserID $line.CanComment $.PageIsPullFiles (not (eq .GetType 2))}}+{{end}}{{if $line.LeftIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}{{if $line.RightIdx}}{{end}}{{if and $.SignedUserID $line.CanComment $.PageIsPullFiles (not (eq .GetType 3))}}+{{end}}{{if $line.RightIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}
- {{if eq $line.GetCommentSide "previous"}} -
-
- - {{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}} - -
- {{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}} -
- {{end}} -
- {{if eq $line.GetCommentSide "proposed"}} -
-
- - {{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}} - -
- {{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}} -
- {{end}} -
+ + {{if $isImage}} + {{template "repo/diff/image_diff" dict "file" . "root" $}} + {{else}} + {{if $.IsSplitStyle}} + {{$highlightClass := $file.GetHighlightClass}} + {{range $j, $section := $file.Sections}} + {{range $k, $line := $section.Lines}} + + + + + + + + {{if gt (len $line.Comments) 0}} + + + + + + + + + {{end}} {{end}} {{end}} + {{else}} + {{template "repo/diff/section_unified" dict "file" . "root" $}} {{end}} - {{else}} - {{template "repo/diff/section_unified" dict "file" . "root" $}} {{end}} - {{end}} - -
{{if $line.LeftIdx}}{{end}}{{if and $.SignedUserID $line.CanComment $.PageIsPullFiles (not (eq .GetType 2))}}+{{end}}{{if $line.LeftIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}{{if $line.RightIdx}}{{end}}{{if and $.SignedUserID $line.CanComment $.PageIsPullFiles (not (eq .GetType 3))}}+{{end}}{{if $line.RightIdx}}{{$section.GetComputedInlineDiffFor $line}}{{end}}
+ {{if eq $line.GetCommentSide "previous"}} +
+
+ + {{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}} + +
+ {{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}} +
+ {{end}} +
+ {{if eq $line.GetCommentSide "proposed"}} +
+
+ + {{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}} + +
+ {{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}} +
+ {{end}} +
-
- {{end}} + + +
+ {{end}} +
+ {{end}} +
+ {{end}} + + {{if .Diff.IsIncomplete}} +
+

+ {{$.i18n.Tr "repo.diff.too_many_files"}} +

{{end}} -
- {{end}} - {{if .Diff.IsIncomplete}} -
-

- {{$.i18n.Tr "repo.diff.too_many_files"}} -

-
- {{end}} + {{if not $.Repository.IsArchived}} +
+ {{template "repo/diff/new_comment" dict "root" .}} +
+
+
+ +
+ +
+
+ {{$.i18n.Tr "loading"}} +
+
+
{{.i18n.Tr "repo.issues.cancel"}}
+
{{.i18n.Tr "repo.issues.save"}}
+
+
+
+ {{end}} - {{if not $.Repository.IsArchived}} -
- {{template "repo/diff/new_comment" dict "root" .}} -
-
-
- -
- -
-
- {{$.i18n.Tr "loading"}} -
-
-
{{.i18n.Tr "repo.issues.cancel"}}
-
{{.i18n.Tr "repo.issues.save"}}
-
-
-
- {{end}} + {{if .IsSplitStyle}} + - {{end}} + + {{end}} +
{{end}} diff --git a/templates/repo/diff/comment_form.tmpl b/templates/repo/diff/comment_form.tmpl index 38fd3fa7fb..5f3bbc4483 100644 --- a/templates/repo/diff/comment_form.tmpl +++ b/templates/repo/diff/comment_form.tmpl @@ -26,7 +26,8 @@ {{$.root.i18n.Tr "repo.diff.comment.markdown_info"}}
{{if $.reply}} - + + {{else}} {{if $.root.CurrentReview}}
{{end}} + {{if .Repository.IsFork}} + {{range .BaseRepoBranches}} +
{{$.PullRequestCtx.BaseRepo.OwnerName}}:{{.}}
+ {{end}} + {{end}}
@@ -54,7 +59,7 @@ {{if .IsNothingToCompare}}
{{.i18n.Tr "repo.pulls.nothing_to_compare"}}
- {{else if .PageIsComparePull}} + {{else if .PageIsComparePull}} {{if .HasPullRequest}}
{{.i18n.Tr "repo.pulls.has_pull_request" $.RepoLink $.RepoRelPath .PullRequest.Index | Safe}} diff --git a/templates/repo/issue/labels.tmpl b/templates/repo/issue/labels.tmpl index 956fc955a0..176304278f 100644 --- a/templates/repo/issue/labels.tmpl +++ b/templates/repo/issue/labels.tmpl @@ -17,16 +17,16 @@
- +
- +
- +
{{template "repo/issue/label_precolors"}} @@ -151,16 +151,16 @@
- +
- +
- +
{{template "repo/issue/label_precolors"}} diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index e64cef2724..5572df671e 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -14,7 +14,7 @@ {{if .PageIsIssueList}} {{.i18n.Tr "repo.issues.new"}} {{else}} - {{.i18n.Tr "repo.pulls.new"}} + {{.i18n.Tr "repo.pulls.new"}} {{end}}
{{else}} @@ -154,7 +154,7 @@ @@ -245,7 +245,7 @@ {{end}} {{if .Ref}} - + {{.Ref}} {{end}} diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl index 889cd2c2f6..f5c6e17331 100644 --- a/templates/repo/issue/milestone_issues.tmpl +++ b/templates/repo/issue/milestone_issues.tmpl @@ -60,7 +60,7 @@
@@ -148,7 +148,7 @@ diff --git a/templates/repo/issue/milestone_new.tmpl b/templates/repo/issue/milestone_new.tmpl index 30a7d7ebbb..ea704c0561 100644 --- a/templates/repo/issue/milestone_new.tmpl +++ b/templates/repo/issue/milestone_new.tmpl @@ -26,7 +26,7 @@
- +
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 99a68bc76e..6777ec7c07 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -13,7 +13,7 @@
- + {{if .PageIsComparePull}} {{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}} {{end}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index acabe34782..555f34679e 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -22,7 +22,7 @@
{{if .Issue.OriginalAuthor }} - {{ .Issue.OriginalAuthor }} {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}} {{if .Repository.OriginalURL}} ({{$.i18n.Tr "repo.migrated_from" .Repository.OriginalURL .Repository.GetOriginalURLHostname | Safe }}){{end}} + {{ .Issue.OriginalAuthor }} {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}} {{if .Repository.OriginalURL}} ({{$.i18n.Tr "repo.migrated_from" .Repository.OriginalURL .Repository.GetOriginalURLHostname | Safe }}){{end}} {{else}} {{.Issue.Poster.GetDisplayName}} {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}} {{end}} @@ -78,7 +78,7 @@ {{ template "repo/issue/view_content/pull". }} {{end}} {{if .IsSigned}} - {{ if and (or .IsRepoAdmin .IsRepoIssuesWriter (or (not .Issue.IsLocked))) (not .Repository.IsArchived) }} + {{ if and (or .IsRepoAdmin .IsIssuesWriter (or (not .Issue.IsLocked))) (not .Repository.IsArchived) }} - {{if .IsPullBranchDeletable}} + {{if and .IsPullBranchDeletable ( not .IsPullRequestBroken )}}
{{$.i18n.Tr "repo.branch.delete" .HeadTarget}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index c42d8aff7f..01809b9fd3 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -426,6 +426,7 @@ +