Merge branch 'master' into refactor_issues-subscription

This commit is contained in:
6543 2019-11-06 05:44:00 +01:00 committed by GitHub
commit 90c36e37d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 5437 additions and 512 deletions

View file

@ -1,4 +1,4 @@
[简体中文](https://github.com/go-gitea/gitea/blob/master/README_ZH.md) [简体中文](README_ZH.md)
<h1> <img src="https://raw.githubusercontent.com/go-gitea/gitea/master/public/img/gitea-192.png" alt="logo" width="30" height="30"> Gitea - Git with a cup of tea</h1> <h1> <img src="https://raw.githubusercontent.com/go-gitea/gitea/master/public/img/gitea-192.png" alt="logo" width="30" height="30"> Gitea - Git with a cup of tea</h1>
@ -105,9 +105,8 @@ for the full license text.
## Screenshots ## Screenshots
Looking for an overview of the interface? Check it out! Looking for an overview of the interface? Check it out!
| | | | |![Dashboard](https://dl.gitea.io/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.io/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.io/screenshots/global_issues.png)|
|:---:|:---:|:---:| |:---:|:---:|:---:|
|![Dashboard](https://image.ibb.co/dms6DG/1.png)|![Repository](https://image.ibb.co/m6MSLw/2.png)|![Commits History](https://image.ibb.co/cjrSLw/3.png)| |![Branches](https://dl.gitea.io/screenshots/branches.png)|![Web Editor](https://dl.gitea.io/screenshots/web_editor.png)|![Activity](https://dl.gitea.io/screenshots/activity.png)|
|![Branches](https://image.ibb.co/e6vbDG/4.png)|![Issues](https://image.ibb.co/bJTJSb/5.png)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)| |![New Migration](https://dl.gitea.io/screenshots/migration.png)|![Migrating](https://dl.gitea.io/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
|![Releases](https://image.ibb.co/cUzgfw/7.png)|![Activity](https://image.ibb.co/eZgGDG/8.png)|![Wiki](https://image.ibb.co/dYV9YG/9.png)| ![Pull Request Dark](https://dl.gitea.io/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.io/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.io/screenshots/diff_dark.png)|
|![Diff](https://image.ibb.co/ewA9YG/10.png)|![Organization](https://image.ibb.co/ceOwDG/11.png)|![Profile](https://image.ibb.co/c44Q7b/12.png)|

View file

@ -1,14 +1,15 @@
[English](https://github.com/go-gitea/gitea/blob/master/README.md) [English](README.md)
# Gitea - Git with a cup of tea <h1> <img src="https://raw.githubusercontent.com/go-gitea/gitea/master/public/img/gitea-192.png" alt="logo" width="30" height="30"> Gitea - Git with a cup of tea</h1>
[![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea) [![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea)
[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/gitea) [![Join the Discord chat at https://discord.gg/NsatcWJ](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ)
[![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com") [![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
[![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea) [![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea)
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea)
[![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea) [![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea)
[![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest) [![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest)
[![Help Contribute to Open Source](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea)
[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea) [![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea) [![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
@ -45,9 +46,8 @@ Fork -> Patch -> Push -> Pull Request
## 截图 ## 截图
| | | | |![Dashboard](https://dl.gitea.io/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.io/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.io/screenshots/global_issues.png)|
|:---:|:---:|:---:| |:---:|:---:|:---:|
|![Dashboard](https://image.ibb.co/dms6DG/1.png)|![Repository](https://image.ibb.co/m6MSLw/2.png)|![Commits History](https://image.ibb.co/cjrSLw/3.png)| |![Branches](https://dl.gitea.io/screenshots/branches.png)|![Web Editor](https://dl.gitea.io/screenshots/web_editor.png)|![Activity](https://dl.gitea.io/screenshots/activity.png)|
|![Branches](https://image.ibb.co/e6vbDG/4.png)|![Issues](https://image.ibb.co/bJTJSb/5.png)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)| |![New Migration](https://dl.gitea.io/screenshots/migration.png)|![Migrating](https://dl.gitea.io/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
|![Releases](https://image.ibb.co/cUzgfw/7.png)|![Activity](https://image.ibb.co/eZgGDG/8.png)|![Wiki](https://image.ibb.co/dYV9YG/9.png)| ![Pull Request Dark](https://dl.gitea.io/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.io/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.io/screenshots/diff_dark.png)|
|![Diff](https://image.ibb.co/ewA9YG/10.png)|![Organization](https://image.ibb.co/ceOwDG/11.png)|![Profile](https://image.ibb.co/c44Q7b/12.png)|

View file

@ -30,7 +30,8 @@ After=network.target
## PartOf=gitea.service ## PartOf=gitea.service
## ##
## [Socket] ## [Socket]
## ListenStream= ## Service=gitea.service
## ListenStream=<some_port>
## NoDelay=true ## NoDelay=true
## ##
## [Install] ## [Install]
@ -53,7 +54,7 @@ WorkingDirectory=/var/lib/gitea/
# If using unix socket: Tells Systemd to create /run/gitea folder to home gitea.sock # If using unix socket: Tells Systemd to create /run/gitea folder to home gitea.sock
# Manual cration would vanish after reboot. # Manual cration would vanish after reboot.
#RuntimeDirectory=gitea #RuntimeDirectory=gitea
ExecStart=/usr/local/bin/gitea web -c /etc/gitea/app.ini ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
# If you want to bind Gitea to a port below 1024, uncomment # If you want to bind Gitea to a port below 1024, uncomment

View file

@ -18,7 +18,7 @@ params:
description: Git with a cup of tea description: Git with a cup of tea
author: The Gitea Authors author: The Gitea Authors
website: https://docs.gitea.io website: https://docs.gitea.io
version: 1.9.3 version: 1.9.5
menu: menu:
page: page:

View file

@ -42,6 +42,7 @@ Also see [Support Options]({{< relref "doc/help/seek-help.en-us.md" >}})
* [SSH Common Errors](#ssh-common-errors) * [SSH Common Errors](#ssh-common-errors)
* [Missing releases after migration repository with tags](#missing-releases-after-migrating-repository-with-tags) * [Missing releases after migration repository with tags](#missing-releases-after-migrating-repository-with-tags)
* [LFS Issues](#lfs-issues) * [LFS Issues](#lfs-issues)
* [How can I create users before starting Gitea](#how-can-i-create-users-before-starting-gitea)
## Difference between 1.x and 1.x.x downloads ## Difference between 1.x and 1.x.x downloads
@ -272,3 +273,6 @@ Check the value of `LFS_HTTP_AUTH_EXPIRY` in your `app.ini` file.
By default, your LFS token will expire after 20 minutes. If you have a slow connection or a large file (or both), it may not finish uploading within the time limit. By default, your LFS token will expire after 20 minutes. If you have a slow connection or a large file (or both), it may not finish uploading within the time limit.
You may want to set this value to `60m` or `120m`. You may want to set this value to `60m` or `120m`.
## How can I create users before starting Gitea
Gitea provides a sub-command `gitea migrate` to initialize the database, after which you can use the [admin CLI commands]({{< relref "doc/usage/command-line.en-us.md" >}}) to add users like normal.

View file

@ -281,3 +281,10 @@ provided key. You should also set the value
NB: opensshd requires the gitea program to be owned by root and not NB: opensshd requires the gitea program to be owned by root and not
writable by group or others. The program must be specified by an absolute writable by group or others. The program must be specified by an absolute
path. path.
#### migrate
Migrates the database. This command can be used to run other commands before starting the server for the first time.
This command is idempotent.
#### convert
Converts an existing MySQL database from utf8 to utf8mb4.

6
go.mod
View file

@ -69,12 +69,10 @@ require (
github.com/mattn/go-sqlite3 v1.11.0 github.com/mattn/go-sqlite3 v1.11.0
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75
github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect
github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
github.com/niklasfasching/go-org v0.1.7 github.com/niklasfasching/go-org v0.1.8
github.com/oliamb/cutter v0.2.2 github.com/oliamb/cutter v0.2.2
github.com/philhofer/fwd v1.0.0 // indirect github.com/philhofer/fwd v1.0.0 // indirect
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
@ -103,7 +101,7 @@ require (
github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 // indirect github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 // indirect
github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 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-20190927123631-a832865fa7ad
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 golang.org/x/net v0.0.0-20191101175033-0deb6923b6d9
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2

4
go.sum
View file

@ -427,6 +427,8 @@ github.com/niklasfasching/go-org v0.1.6 h1:F521WcqRNl8OJumlgAnekZgERaTA2HpfOYYfV
github.com/niklasfasching/go-org v0.1.6/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU= github.com/niklasfasching/go-org v0.1.6/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
github.com/niklasfasching/go-org v0.1.7 h1:t3V+3XnS/7BhKv/7SlMUa8FvAiq577/a1T3D7mLIRXE= github.com/niklasfasching/go-org v0.1.7 h1:t3V+3XnS/7BhKv/7SlMUa8FvAiq577/a1T3D7mLIRXE=
github.com/niklasfasching/go-org v0.1.7/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU= github.com/niklasfasching/go-org v0.1.7/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
github.com/niklasfasching/go-org v0.1.8 h1:Kjvs6lP+LIILHhc9zIJ4Gu90a/pVY483if2Qmu8v4Fg=
github.com/niklasfasching/go-org v0.1.8/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k= github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k=
github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU= github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU=
@ -658,6 +660,8 @@ golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsi
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191101175033-0deb6923b6d9 h1:DPz9iiH3YoKiKhX/ijjoZvT0VFwK2c6CWYWQ7Zyr8TU=
golang.org/x/net v0.0.0-20191101175033-0deb6923b6d9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=

View file

@ -1293,8 +1293,12 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
log.Warn("Malformed Labels argument: %s", opts.Labels) log.Warn("Malformed Labels argument: %s", opts.Labels)
} else { } else {
for i, labelID := range labelIDs { for i, labelID := range labelIDs {
if labelID > 0 {
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i), sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID)) fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
} else {
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label WHERE label_id = ?)", -labelID)
}
} }
} }
} }

View file

@ -107,7 +107,7 @@ func parseKeyString(content string) (string, error) {
var keyType, keyContent, keyComment string var keyType, keyContent, keyComment string
if content[:len(ssh2keyStart)] == ssh2keyStart { if strings.HasPrefix(content, ssh2keyStart) {
// Parse SSH2 file format. // Parse SSH2 file format.
// Transform all legal line endings to a single "\n". // Transform all legal line endings to a single "\n".

View file

@ -131,6 +131,19 @@ AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf
_, err := CheckPublicKeyString(test.content) _, err := CheckPublicKeyString(test.content)
assert.NoError(t, err) 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) { func Test_calcFingerprint(t *testing.T) {

View file

@ -118,33 +118,6 @@ func (w *Webhook) AfterLoad() {
} }
} }
// GetSlackHook returns slack metadata
func (w *Webhook) GetSlackHook() *SlackMeta {
s := &SlackMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error("webhook.GetSlackHook(%d): %v", w.ID, err)
}
return s
}
// GetDiscordHook returns discord metadata
func (w *Webhook) GetDiscordHook() *DiscordMeta {
s := &DiscordMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error("webhook.GetDiscordHook(%d): %v", w.ID, err)
}
return s
}
// GetTelegramHook returns telegram metadata
func (w *Webhook) GetTelegramHook() *TelegramMeta {
s := &TelegramMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error("webhook.GetTelegramHook(%d): %v", w.ID, err)
}
return s
}
// History returns history of webhook by given conditions. // History returns history of webhook by given conditions.
func (w *Webhook) History(page int) ([]*HookTask, error) { func (w *Webhook) History(page int) ([]*HookTask, error) {
return HookTasks(w.ID, page) return HookTasks(w.ID, page)

View file

@ -24,18 +24,6 @@ func TestIsValidHookContentType(t *testing.T) {
assert.False(t, IsValidHookContentType("invalid")) assert.False(t, IsValidHookContentType("invalid"))
} }
func TestWebhook_GetSlackHook(t *testing.T) {
w := &Webhook{
Meta: `{"channel": "foo", "username": "username", "color": "blue"}`,
}
slackHook := w.GetSlackHook()
assert.Equal(t, *slackHook, SlackMeta{
Channel: "foo",
Username: "username",
Color: "blue",
})
}
func TestWebhook_History(t *testing.T) { func TestWebhook_History(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
webhook := AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook) webhook := AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook)

View file

@ -8,7 +8,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"html" "html"
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
@ -91,7 +90,7 @@ func (r *Renderer) WriteRegularLink(l org.RegularLink) {
description := string(link) description := string(link)
if l.Description != nil { if l.Description != nil {
description = r.nodesAsString(l.Description...) description = r.WriteNodesAsString(l.Description...)
} }
switch l.Kind() { switch l.Kind() {
case "image": case "image":
@ -102,21 +101,3 @@ func (r *Renderer) WriteRegularLink(l org.RegularLink) {
r.WriteString(fmt.Sprintf(`<a href="%s" title="%s">%s</a>`, link, description, description)) r.WriteString(fmt.Sprintf(`<a href="%s" title="%s">%s</a>`, link, description, description))
} }
} }
func (r *Renderer) emptyClone() *Renderer {
wcopy := *(r.HTMLWriter)
wcopy.Builder = strings.Builder{}
rcopy := *r
rcopy.HTMLWriter = &wcopy
wcopy.ExtendingWriter = &rcopy
return &rcopy
}
func (r *Renderer) nodesAsString(nodes ...org.Node) string {
tmp := r.emptyClone()
org.WriteNodes(tmp, nodes...)
return tmp.String()
}

View file

@ -252,6 +252,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
} }
// download attachment // download attachment
err = func() error {
resp, err := http.Get(asset.URL) resp, err := http.Get(asset.URL)
if err != nil { if err != nil {
return err return err
@ -269,10 +270,12 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
} }
defer fw.Close() defer fw.Close()
if _, err := io.Copy(fw, resp.Body); err != nil { _, err = io.Copy(fw, resp.Body)
return err
}()
if err != nil {
return err return err
} }
rel.Attachments = append(rel.Attachments, &attach) rel.Attachments = append(rel.Attachments, &attach)
} }
@ -468,21 +471,24 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
} }
// download patch file // download patch file
err := func() error {
resp, err := http.Get(pr.PatchURL) resp, err := http.Get(pr.PatchURL)
if err != nil { if err != nil {
return nil, err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
pullDir := filepath.Join(g.repo.RepoPath(), "pulls") pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
if err = os.MkdirAll(pullDir, os.ModePerm); err != nil { if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
return nil, err return err
} }
f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number))) f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
if err != nil { if err != nil {
return nil, err return err
} }
defer f.Close() defer f.Close()
_, err = io.Copy(f, resp.Body) _, err = io.Copy(f, resp.Body)
return err
}()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -496,8 +502,8 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer p.Close()
_, err = p.WriteString(pr.Head.SHA) _, err = p.WriteString(pr.Head.SHA)
p.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -531,8 +537,8 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer b.Close()
_, err = b.WriteString(pr.Head.SHA) _, err = b.WriteString(pr.Head.SHA)
b.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -0,0 +1,77 @@
// 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 action
import (
"fmt"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification/base"
)
type actionNotifier struct {
base.NullNotifier
}
var (
_ base.Notifier = &actionNotifier{}
)
// NewNotifier create a new webhookNotifier notifier
func NewNotifier() base.Notifier {
return &actionNotifier{}
}
func (a *actionNotifier) NotifyNewIssue(issue *models.Issue) {
if err := issue.LoadPoster(); err != nil {
log.Error("issue.LoadPoster: %v", err)
return
}
if err := issue.LoadRepo(); err != nil {
log.Error("issue.LoadRepo: %v", err)
return
}
repo := issue.Repo
if err := models.NotifyWatchers(&models.Action{
ActUserID: issue.Poster.ID,
ActUser: issue.Poster,
OpType: models.ActionCreateIssue,
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
RepoID: repo.ID,
Repo: repo,
IsPrivate: repo.IsPrivate,
}); err != nil {
log.Error("NotifyWatchers: %v", err)
}
}
func (a *actionNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
if err := pull.LoadIssue(); err != nil {
log.Error("pull.LoadIssue: %v", err)
return
}
if err := pull.Issue.LoadRepo(); err != nil {
log.Error("pull.Issue.LoadRepo: %v", err)
return
}
if err := pull.Issue.LoadPoster(); err != nil {
log.Error("pull.Issue.LoadPoster: %v", err)
return
}
if err := models.NotifyWatchers(&models.Action{
ActUserID: pull.Issue.Poster.ID,
ActUser: pull.Issue.Poster,
OpType: models.ActionCreatePullRequest,
Content: fmt.Sprintf("%d|%s", pull.Issue.Index, pull.Issue.Title),
RepoID: pull.Issue.Repo.ID,
Repo: pull.Issue.Repo,
IsPrivate: pull.Issue.Repo.IsPrivate,
}); err != nil {
log.Error("NotifyWatchers: %v", err)
}
}

View file

@ -30,6 +30,7 @@ type Notifier interface {
NotifyNewPullRequest(*models.PullRequest) NotifyNewPullRequest(*models.PullRequest)
NotifyMergePullRequest(*models.PullRequest, *models.User, *git.Repository) NotifyMergePullRequest(*models.PullRequest, *models.User, *git.Repository)
NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest)
NotifyPullRequestReview(*models.PullRequest, *models.Review, *models.Comment) NotifyPullRequestReview(*models.PullRequest, *models.Review, *models.Comment)
NotifyCreateIssueComment(*models.User, *models.Repository, NotifyCreateIssueComment(*models.User, *models.Repository,
@ -40,4 +41,6 @@ type Notifier interface {
NotifyNewRelease(rel *models.Release) NotifyNewRelease(rel *models.Release)
NotifyUpdateRelease(doer *models.User, rel *models.Release) NotifyUpdateRelease(doer *models.User, rel *models.Release)
NotifyDeleteRelease(doer *models.User, rel *models.Release) NotifyDeleteRelease(doer *models.User, rel *models.Release)
NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits)
} }

View file

@ -46,6 +46,10 @@ func (*NullNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.R
func (*NullNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *models.User, baseRepo *git.Repository) { func (*NullNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *models.User, baseRepo *git.Repository) {
} }
// NotifyPullRequestSynchronized places a place holder function
func (*NullNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) {
}
// NotifyUpdateComment places a place holder function // NotifyUpdateComment places a place holder function
func (*NullNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) { func (*NullNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) {
} }
@ -106,3 +110,7 @@ func (*NullNotifier) NotifyCreateRepository(doer *models.User, u *models.User, r
// NotifyMigrateRepository places a place holder function // NotifyMigrateRepository places a place holder function
func (*NullNotifier) NotifyMigrateRepository(doer *models.User, u *models.User, repo *models.Repository) { func (*NullNotifier) NotifyMigrateRepository(doer *models.User, u *models.User, repo *models.Repository) {
} }
// NotifyPushCommits notifies commits pushed to notifiers
func (*NullNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
}

View file

@ -7,6 +7,7 @@ package notification
import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/notification/action"
"code.gitea.io/gitea/modules/notification/base" "code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/notification/indexer" "code.gitea.io/gitea/modules/notification/indexer"
"code.gitea.io/gitea/modules/notification/mail" "code.gitea.io/gitea/modules/notification/mail"
@ -33,6 +34,7 @@ func NewContext() {
} }
RegisterNotifier(indexer.NewNotifier()) RegisterNotifier(indexer.NewNotifier())
RegisterNotifier(webhook.NewNotifier()) RegisterNotifier(webhook.NewNotifier())
RegisterNotifier(action.NewNotifier())
} }
// NotifyCreateIssueComment notifies issue comment related message to notifiers // NotifyCreateIssueComment notifies issue comment related message to notifiers
@ -71,6 +73,13 @@ func NotifyNewPullRequest(pr *models.PullRequest) {
} }
} }
// NotifyPullRequestSynchronized notifies Synchronized pull request
func NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) {
for _, notifier := range notifiers {
notifier.NotifyPullRequestSynchronized(doer, pr)
}
}
// NotifyPullRequestReview notifies new pull request review // NotifyPullRequestReview notifies new pull request review
func NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) { func NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
for _, notifier := range notifiers { for _, notifier := range notifiers {
@ -183,3 +192,10 @@ func NotifyMigrateRepository(doer *models.User, u *models.User, repo *models.Rep
notifier.NotifyMigrateRepository(doer, u, repo) notifier.NotifyMigrateRepository(doer, u, repo)
} }
} }
// NotifyPushCommits notifies commits pushed to notifiers
func NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
for _, notifier := range notifiers {
notifier.NotifyPushCommits(pusher, repo, refName, oldCommitID, newCommitID, commits)
}
}

View file

@ -8,7 +8,9 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification/base" "code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/webhook"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
) )
@ -250,6 +252,15 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode
} }
func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) { func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
if err := issue.LoadRepo(); err != nil {
log.Error("issue.LoadRepo: %v", err)
return
}
if err := issue.LoadPoster(); err != nil {
log.Error("issue.LoadPoster: %v", err)
return
}
mode, _ := models.AccessLevel(issue.Poster, issue.Repo) mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
Action: api.HookIssueOpened, Action: api.HookIssueOpened,
@ -262,6 +273,32 @@ func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
} }
} }
func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
if err := pull.LoadIssue(); err != nil {
log.Error("pull.LoadIssue: %v", err)
return
}
if err := pull.Issue.LoadRepo(); err != nil {
log.Error("pull.Issue.LoadRepo: %v", err)
return
}
if err := pull.Issue.LoadPoster(); err != nil {
log.Error("pull.Issue.LoadPoster: %v", err)
return
}
mode, _ := models.AccessLevel(pull.Issue.Poster, pull.Issue.Repo)
if err := webhook.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueOpened,
Index: pull.Issue.Index,
PullRequest: pull.APIFormat(),
Repository: pull.Issue.Repo.APIFormat(mode),
Sender: pull.Issue.Poster.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
}
}
func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string) { func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string) {
mode, _ := models.AccessLevel(issue.Poster, issue.Repo) mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
var err error var err error
@ -461,3 +498,87 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m
log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
} }
} }
func (m *webhookNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
apiPusher := pusher.APIFormat()
apiCommits, err := commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL())
if err != nil {
log.Error("commits.ToAPIPayloadCommits failed: %v", err)
return
}
if err := webhook_module.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{
Ref: refName,
Before: oldCommitID,
After: newCommitID,
CompareURL: setting.AppURL + commits.CompareURL,
Commits: apiCommits,
Repo: repo.APIFormat(models.AccessModeOwner),
Pusher: apiPusher,
Sender: apiPusher,
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
}
}
func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
var reviewHookType models.HookEventType
switch review.Type {
case models.ReviewTypeApprove:
reviewHookType = models.HookEventPullRequestApproved
case models.ReviewTypeComment:
reviewHookType = models.HookEventPullRequestComment
case models.ReviewTypeReject:
reviewHookType = models.HookEventPullRequestRejected
default:
// unsupported review webhook type here
log.Error("Unsupported review webhook type")
return
}
if err := pr.LoadIssue(); err != nil {
log.Error("pr.LoadIssue: %v", err)
return
}
mode, err := models.AccessLevel(review.Issue.Poster, review.Issue.Repo)
if err != nil {
log.Error("models.AccessLevel: %v", err)
return
}
if err := webhook.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: review.Issue.Index,
PullRequest: pr.APIFormat(),
Repository: review.Issue.Repo.APIFormat(mode),
Sender: review.Reviewer.APIFormat(),
Review: &api.ReviewPayload{
Type: string(reviewHookType),
Content: review.Content,
},
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
}
}
func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) {
if err := pr.LoadIssue(); err != nil {
log.Error("pr.LoadIssue: %v", err)
return
}
if err := pr.Issue.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
return
}
if err := webhook.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: pr.Issue.Index,
PullRequest: pr.Issue.PullRequest.APIFormat(),
Repository: pr.Issue.Repo.APIFormat(models.AccessModeNone),
Sender: doer.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
}
}

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/webhook" "code.gitea.io/gitea/modules/webhook"
@ -190,22 +191,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
} }
if isHookEventPush { if isHookEventPush {
commits, err := opts.Commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL()) notification.NotifyPushCommits(pusher, repo, opts.RefFullName, opts.OldCommitID, opts.NewCommitID, opts.Commits)
if err != nil {
return err
}
if err = webhook.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{
Ref: opts.RefFullName,
Before: opts.OldCommitID,
After: opts.NewCommitID,
CompareURL: setting.AppURL + opts.Commits.CompareURL,
Commits: commits,
Repo: apiRepo,
Pusher: apiPusher,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks: %v", err)
}
} }
return nil return nil

View file

@ -100,6 +100,7 @@ type EditIssueOption struct {
State *string `json:"state"` State *string `json:"state"`
// swagger:strfmt date-time // swagger:strfmt date-time
Deadline *time.Time `json:"due_date"` Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
} }
// EditDeadlineOption options for creating a deadline // EditDeadlineOption options for creating a deadline

View file

@ -89,4 +89,5 @@ type EditPullRequestOption struct {
State *string `json:"state"` State *string `json:"state"`
// swagger:strfmt date-time // swagger:strfmt date-time
Deadline *time.Time `json:"due_date"` Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
} }

View file

@ -2,13 +2,14 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package webhook
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@ -184,7 +185,7 @@ func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) {
func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) { func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) {
title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title) title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title)
url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
var content string var content string
switch p.Action { switch p.Action {
case api.HookIssueCommentCreated: case api.HookIssueCommentCreated:
@ -286,7 +287,7 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload,
}, nil }, nil
} }
func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event HookEventType) (*DingtalkPayload, error) { func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*DingtalkPayload, error) {
var text, title string var text, title string
switch p.Action { switch p.Action {
case api.HookIssueSynchronized: case api.HookIssueSynchronized:
@ -392,29 +393,29 @@ func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error)
} }
// GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload // GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload
func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) { func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string) (*DingtalkPayload, error) {
s := new(DingtalkPayload) s := new(DingtalkPayload)
switch event { switch event {
case HookEventCreate: case models.HookEventCreate:
return getDingtalkCreatePayload(p.(*api.CreatePayload)) return getDingtalkCreatePayload(p.(*api.CreatePayload))
case HookEventDelete: case models.HookEventDelete:
return getDingtalkDeletePayload(p.(*api.DeletePayload)) return getDingtalkDeletePayload(p.(*api.DeletePayload))
case HookEventFork: case models.HookEventFork:
return getDingtalkForkPayload(p.(*api.ForkPayload)) return getDingtalkForkPayload(p.(*api.ForkPayload))
case HookEventIssues: case models.HookEventIssues:
return getDingtalkIssuesPayload(p.(*api.IssuePayload)) return getDingtalkIssuesPayload(p.(*api.IssuePayload))
case HookEventIssueComment: case models.HookEventIssueComment:
return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload)) return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload))
case HookEventPush: case models.HookEventPush:
return getDingtalkPushPayload(p.(*api.PushPayload)) return getDingtalkPushPayload(p.(*api.PushPayload))
case HookEventPullRequest: case models.HookEventPullRequest:
return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
case HookEventPullRequestApproved, HookEventPullRequestRejected, HookEventPullRequestComment: case models.HookEventPullRequestApproved, models.HookEventPullRequestRejected, models.HookEventPullRequestComment:
return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case HookEventRepository: case models.HookEventRepository:
return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload))
case HookEventRelease: case models.HookEventRelease:
return getDingtalkReleasePayload(p.(*api.ReleasePayload)) return getDingtalkReleasePayload(p.(*api.ReleasePayload))
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package webhook
import ( import (
"encoding/json" "encoding/json"
@ -11,7 +11,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@ -63,6 +65,15 @@ type (
} }
) )
// GetDiscordHook returns discord metadata
func GetDiscordHook(w *models.Webhook) *DiscordMeta {
s := &DiscordMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error("webhook.GetDiscordHook(%d): %v", w.ID, err)
}
return s
}
func color(clr string) int { func color(clr string) int {
if clr != "" { if clr != "" {
clr = strings.TrimLeft(clr, "#") clr = strings.TrimLeft(clr, "#")
@ -288,7 +299,7 @@ func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPa
func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) { func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) {
title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title) title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title)
url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
content := "" content := ""
var color int var color int
switch p.Action { switch p.Action {
@ -421,7 +432,7 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta)
}, nil }, nil
} }
func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event HookEventType) (*DiscordPayload, error) { func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event models.HookEventType) (*DiscordPayload, error) {
var text, title string var text, title string
var color int var color int
switch p.Action { switch p.Action {
@ -435,11 +446,11 @@ func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *Disco
text = p.Review.Content text = p.Review.Content
switch event { switch event {
case HookEventPullRequestApproved: case models.HookEventPullRequestApproved:
color = greenColor color = greenColor
case HookEventPullRequestRejected: case models.HookEventPullRequestRejected:
color = redColor color = redColor
case HookEventPullRequestComment: case models.HookEventPullRequestComment:
color = greyColor color = greyColor
default: default:
color = yellowColor color = yellowColor
@ -534,7 +545,7 @@ func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*Discor
} }
// GetDiscordPayload converts a discord webhook into a DiscordPayload // GetDiscordPayload converts a discord webhook into a DiscordPayload
func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) { func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string) (*DiscordPayload, error) {
s := new(DiscordPayload) s := new(DiscordPayload)
discord := &DiscordMeta{} discord := &DiscordMeta{}
@ -543,40 +554,40 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
} }
switch event { switch event {
case HookEventCreate: case models.HookEventCreate:
return getDiscordCreatePayload(p.(*api.CreatePayload), discord) return getDiscordCreatePayload(p.(*api.CreatePayload), discord)
case HookEventDelete: case models.HookEventDelete:
return getDiscordDeletePayload(p.(*api.DeletePayload), discord) return getDiscordDeletePayload(p.(*api.DeletePayload), discord)
case HookEventFork: case models.HookEventFork:
return getDiscordForkPayload(p.(*api.ForkPayload), discord) return getDiscordForkPayload(p.(*api.ForkPayload), discord)
case HookEventIssues: case models.HookEventIssues:
return getDiscordIssuesPayload(p.(*api.IssuePayload), discord) return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
case HookEventIssueComment: case models.HookEventIssueComment:
return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord) return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord)
case HookEventPush: case models.HookEventPush:
return getDiscordPushPayload(p.(*api.PushPayload), discord) return getDiscordPushPayload(p.(*api.PushPayload), discord)
case HookEventPullRequest: case models.HookEventPullRequest:
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment: case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event) return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event)
case HookEventRepository: case models.HookEventRepository:
return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
case HookEventRelease: case models.HookEventRelease:
return getDiscordReleasePayload(p.(*api.ReleasePayload), discord) return getDiscordReleasePayload(p.(*api.ReleasePayload), discord)
} }
return s, nil return s, nil
} }
func parseHookPullRequestEventType(event HookEventType) (string, error) { func parseHookPullRequestEventType(event models.HookEventType) (string, error) {
switch event { switch event {
case HookEventPullRequestApproved: case models.HookEventPullRequestApproved:
return "approved", nil return "approved", nil
case HookEventPullRequestRejected: case models.HookEventPullRequestRejected:
return "rejected", nil return "rejected", nil
case HookEventPullRequestComment: case models.HookEventPullRequestComment:
return "comment", nil return "comment", nil
default: default:

View file

@ -2,13 +2,14 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package webhook
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@ -357,7 +358,7 @@ func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) {
func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, error) { func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, error) {
title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title) title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title)
url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
content := "" content := ""
var color int var color int
switch p.Action { switch p.Action {
@ -530,7 +531,7 @@ func getMSTeamsPullRequestPayload(p *api.PullRequestPayload) (*MSTeamsPayload, e
}, nil }, nil
} }
func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event HookEventType) (*MSTeamsPayload, error) { func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*MSTeamsPayload, error) {
var text, title string var text, title string
var color int var color int
switch p.Action { switch p.Action {
@ -544,11 +545,11 @@ func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event HookE
text = p.Review.Content text = p.Review.Content
switch event { switch event {
case HookEventPullRequestApproved: case models.HookEventPullRequestApproved:
color = greenColor color = greenColor
case HookEventPullRequestRejected: case models.HookEventPullRequestRejected:
color = redColor color = redColor
case HookEventPullRequestComment: case models.HookEventPullRequestComment:
color = greyColor color = greyColor
default: default:
color = yellowColor color = yellowColor
@ -699,29 +700,29 @@ func getMSTeamsReleasePayload(p *api.ReleasePayload) (*MSTeamsPayload, error) {
} }
// GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload // GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload
func GetMSTeamsPayload(p api.Payloader, event HookEventType, meta string) (*MSTeamsPayload, error) { func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string) (*MSTeamsPayload, error) {
s := new(MSTeamsPayload) s := new(MSTeamsPayload)
switch event { switch event {
case HookEventCreate: case models.HookEventCreate:
return getMSTeamsCreatePayload(p.(*api.CreatePayload)) return getMSTeamsCreatePayload(p.(*api.CreatePayload))
case HookEventDelete: case models.HookEventDelete:
return getMSTeamsDeletePayload(p.(*api.DeletePayload)) return getMSTeamsDeletePayload(p.(*api.DeletePayload))
case HookEventFork: case models.HookEventFork:
return getMSTeamsForkPayload(p.(*api.ForkPayload)) return getMSTeamsForkPayload(p.(*api.ForkPayload))
case HookEventIssues: case models.HookEventIssues:
return getMSTeamsIssuesPayload(p.(*api.IssuePayload)) return getMSTeamsIssuesPayload(p.(*api.IssuePayload))
case HookEventIssueComment: case models.HookEventIssueComment:
return getMSTeamsIssueCommentPayload(p.(*api.IssueCommentPayload)) return getMSTeamsIssueCommentPayload(p.(*api.IssueCommentPayload))
case HookEventPush: case models.HookEventPush:
return getMSTeamsPushPayload(p.(*api.PushPayload)) return getMSTeamsPushPayload(p.(*api.PushPayload))
case HookEventPullRequest: case models.HookEventPullRequest:
return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload)) return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload))
case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment: case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
return getMSTeamsPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) return getMSTeamsPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
case HookEventRepository: case models.HookEventRepository:
return getMSTeamsRepositoryPayload(p.(*api.RepositoryPayload)) return getMSTeamsRepositoryPayload(p.(*api.RepositoryPayload))
case HookEventRelease: case models.HookEventRelease:
return getMSTeamsReleasePayload(p.(*api.ReleasePayload)) return getMSTeamsReleasePayload(p.(*api.ReleasePayload))
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package webhook
import ( import (
"encoding/json" "encoding/json"
@ -10,7 +10,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@ -23,6 +25,15 @@ type SlackMeta struct {
Color string `json:"color"` Color string `json:"color"`
} }
// GetSlackHook returns slack metadata
func GetSlackHook(w *models.Webhook) *SlackMeta {
s := &SlackMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error("webhook.GetSlackHook(%d): %v", w.ID, err)
}
return s
}
// SlackPayload contains the information about the slack channel // SlackPayload contains the information about the slack channel
type SlackPayload struct { type SlackPayload struct {
Channel string `json:"channel"` Channel string `json:"channel"`
@ -181,7 +192,7 @@ func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload
func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) { func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) 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)), titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID)),
fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title)) fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
var text, title, attachmentText string var text, title, attachmentText string
switch p.Action { switch p.Action {
@ -335,7 +346,7 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
}, nil }, nil
} }
func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event HookEventType) (*SlackPayload, error) { func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event models.HookEventType) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index), titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)) fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
@ -388,7 +399,7 @@ func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*Sla
} }
// GetSlackPayload converts a slack webhook into a SlackPayload // GetSlackPayload converts a slack webhook into a SlackPayload
func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackPayload, error) { func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (*SlackPayload, error) {
s := new(SlackPayload) s := new(SlackPayload)
slack := &SlackMeta{} slack := &SlackMeta{}
@ -397,25 +408,25 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP
} }
switch event { switch event {
case HookEventCreate: case models.HookEventCreate:
return getSlackCreatePayload(p.(*api.CreatePayload), slack) return getSlackCreatePayload(p.(*api.CreatePayload), slack)
case HookEventDelete: case models.HookEventDelete:
return getSlackDeletePayload(p.(*api.DeletePayload), slack) return getSlackDeletePayload(p.(*api.DeletePayload), slack)
case HookEventFork: case models.HookEventFork:
return getSlackForkPayload(p.(*api.ForkPayload), slack) return getSlackForkPayload(p.(*api.ForkPayload), slack)
case HookEventIssues: case models.HookEventIssues:
return getSlackIssuesPayload(p.(*api.IssuePayload), slack) return getSlackIssuesPayload(p.(*api.IssuePayload), slack)
case HookEventIssueComment: case models.HookEventIssueComment:
return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack) return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
case HookEventPush: case models.HookEventPush:
return getSlackPushPayload(p.(*api.PushPayload), slack) return getSlackPushPayload(p.(*api.PushPayload), slack)
case HookEventPullRequest: case models.HookEventPullRequest:
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment: case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event) return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event)
case HookEventRepository: case models.HookEventRepository:
return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack)
case HookEventRelease: case models.HookEventRelease:
return getSlackReleasePayload(p.(*api.ReleasePayload), slack) return getSlackReleasePayload(p.(*api.ReleasePayload), slack)
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package webhook
import ( import (
"encoding/json" "encoding/json"
@ -10,7 +10,9 @@ import (
"html" "html"
"strings" "strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@ -30,6 +32,15 @@ type (
} }
) )
// GetTelegramHook returns telegram metadata
func GetTelegramHook(w *models.Webhook) *TelegramMeta {
s := &TelegramMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error("webhook.GetTelegramHook(%d): %v", w.ID, err)
}
return s
}
// SetSecret sets the telegram secret // SetSecret sets the telegram secret
func (p *TelegramPayload) SetSecret(_ string) {} func (p *TelegramPayload) SetSecret(_ string) {}
@ -169,7 +180,7 @@ func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) {
} }
func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayload, error) { func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayload, error) {
url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)) url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
title := fmt.Sprintf(`<a href="%s">#%d %s</a>`, url, p.Issue.Index, html.EscapeString(p.Issue.Title)) title := fmt.Sprintf(`<a href="%s">#%d %s</a>`, url, p.Issue.Index, html.EscapeString(p.Issue.Title))
var text string var text string
switch p.Action { switch p.Action {
@ -214,7 +225,7 @@ func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload,
p.PullRequest.HTMLURL, p.Index, p.PullRequest.Title) p.PullRequest.HTMLURL, p.Index, p.PullRequest.Title)
text = p.PullRequest.Body text = p.PullRequest.Body
case api.HookIssueAssigned: case api.HookIssueAssigned:
list, err := MakeAssigneeList(&Issue{ID: p.PullRequest.ID}) list, err := models.MakeAssigneeList(&models.Issue{ID: p.PullRequest.ID})
if err != nil { if err != nil {
return &TelegramPayload{}, err return &TelegramPayload{}, err
} }
@ -297,27 +308,27 @@ func getTelegramReleasePayload(p *api.ReleasePayload) (*TelegramPayload, error)
} }
// GetTelegramPayload converts a telegram webhook into a TelegramPayload // GetTelegramPayload converts a telegram webhook into a TelegramPayload
func GetTelegramPayload(p api.Payloader, event HookEventType, meta string) (*TelegramPayload, error) { func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string) (*TelegramPayload, error) {
s := new(TelegramPayload) s := new(TelegramPayload)
switch event { switch event {
case HookEventCreate: case models.HookEventCreate:
return getTelegramCreatePayload(p.(*api.CreatePayload)) return getTelegramCreatePayload(p.(*api.CreatePayload))
case HookEventDelete: case models.HookEventDelete:
return getTelegramDeletePayload(p.(*api.DeletePayload)) return getTelegramDeletePayload(p.(*api.DeletePayload))
case HookEventFork: case models.HookEventFork:
return getTelegramForkPayload(p.(*api.ForkPayload)) return getTelegramForkPayload(p.(*api.ForkPayload))
case HookEventIssues: case models.HookEventIssues:
return getTelegramIssuesPayload(p.(*api.IssuePayload)) return getTelegramIssuesPayload(p.(*api.IssuePayload))
case HookEventIssueComment: case models.HookEventIssueComment:
return getTelegramIssueCommentPayload(p.(*api.IssueCommentPayload)) return getTelegramIssueCommentPayload(p.(*api.IssueCommentPayload))
case HookEventPush: case models.HookEventPush:
return getTelegramPushPayload(p.(*api.PushPayload)) return getTelegramPushPayload(p.(*api.PushPayload))
case HookEventPullRequest: case models.HookEventPullRequest:
return getTelegramPullRequestPayload(p.(*api.PullRequestPayload)) return getTelegramPullRequestPayload(p.(*api.PullRequestPayload))
case HookEventRepository: case models.HookEventRepository:
return getTelegramRepositoryPayload(p.(*api.RepositoryPayload)) return getTelegramRepositoryPayload(p.(*api.RepositoryPayload))
case HookEventRelease: case models.HookEventRelease:
return getTelegramReleasePayload(p.(*api.ReleasePayload)) return getTelegramReleasePayload(p.(*api.ReleasePayload))
} }

View file

@ -90,27 +90,27 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo
// Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks. // Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks.
switch w.HookTaskType { switch w.HookTaskType {
case models.SLACK: case models.SLACK:
payloader, err = models.GetSlackPayload(p, event, w.Meta) payloader, err = GetSlackPayload(p, event, w.Meta)
if err != nil { if err != nil {
return fmt.Errorf("GetSlackPayload: %v", err) return fmt.Errorf("GetSlackPayload: %v", err)
} }
case models.DISCORD: case models.DISCORD:
payloader, err = models.GetDiscordPayload(p, event, w.Meta) payloader, err = GetDiscordPayload(p, event, w.Meta)
if err != nil { if err != nil {
return fmt.Errorf("GetDiscordPayload: %v", err) return fmt.Errorf("GetDiscordPayload: %v", err)
} }
case models.DINGTALK: case models.DINGTALK:
payloader, err = models.GetDingtalkPayload(p, event, w.Meta) payloader, err = GetDingtalkPayload(p, event, w.Meta)
if err != nil { if err != nil {
return fmt.Errorf("GetDingtalkPayload: %v", err) return fmt.Errorf("GetDingtalkPayload: %v", err)
} }
case models.TELEGRAM: case models.TELEGRAM:
payloader, err = models.GetTelegramPayload(p, event, w.Meta) payloader, err = GetTelegramPayload(p, event, w.Meta)
if err != nil { if err != nil {
return fmt.Errorf("GetTelegramPayload: %v", err) return fmt.Errorf("GetTelegramPayload: %v", err)
} }
case models.MSTEAMS: case models.MSTEAMS:
payloader, err = models.GetMSTeamsPayload(p, event, w.Meta) payloader, err = GetMSTeamsPayload(p, event, w.Meta)
if err != nil { if err != nil {
return fmt.Errorf("GetMSTeamsPayload: %v", err) return fmt.Errorf("GetMSTeamsPayload: %v", err)
} }

View file

@ -12,6 +12,18 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestWebhook_GetSlackHook(t *testing.T) {
w := &models.Webhook{
Meta: `{"channel": "foo", "username": "username", "color": "blue"}`,
}
slackHook := GetSlackHook(w)
assert.Equal(t, *slackHook, SlackMeta{
Channel: "foo",
Username: "username",
Color: "blue",
})
}
func TestPrepareWebhooks(t *testing.T) { func TestPrepareWebhooks(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase()) assert.NoError(t, models.PrepareTestDatabase())

View file

@ -1090,6 +1090,9 @@ activity.period.daily = 1 day
activity.period.halfweekly = 3 days activity.period.halfweekly = 3 days
activity.period.weekly = 1 week activity.period.weekly = 1 week
activity.period.monthly = 1 month activity.period.monthly = 1 month
activity.period.quarterly = 3 months
activity.period.semiyearly = 6 months
activity.period.yearly = 1 year
activity.overview = Overview activity.overview = Overview
activity.active_prs_count_1 = <strong>%d</strong> Active Pull Request activity.active_prs_count_1 = <strong>%d</strong> Active Pull Request
activity.active_prs_count_n = <strong>%d</strong> Active Pull Requests activity.active_prs_count_n = <strong>%d</strong> Active Pull Requests

View file

@ -741,6 +741,7 @@ editor.no_changes_to_show=表示する変更箇所はありません。
editor.fail_to_update_file=ファイル '%s' を作成または変更できませんでした: %v editor.fail_to_update_file=ファイル '%s' を作成または変更できませんでした: %v
editor.add_subdir=ディレクトリを追加… editor.add_subdir=ディレクトリを追加…
editor.unable_to_upload_files='%s' へファイルをアップロードすることができませんでした: %v editor.unable_to_upload_files='%s' へファイルをアップロードすることができませんでした: %v
editor.upload_file_is_locked=ファイル '%[1]s' は %[2]s がロックしています。
editor.upload_files_to_dir='%s' にファイルをアップロード editor.upload_files_to_dir='%s' にファイルをアップロード
editor.cannot_commit_to_protected_branch=保護されたブランチ '%s' にコミットすることはできません。 editor.cannot_commit_to_protected_branch=保護されたブランチ '%s' にコミットすることはできません。
@ -1088,6 +1089,9 @@ activity.period.daily=1日
activity.period.halfweekly=3日 activity.period.halfweekly=3日
activity.period.weekly=1週間 activity.period.weekly=1週間
activity.period.monthly=1ヶ月 activity.period.monthly=1ヶ月
activity.period.quarterly=3ヶ月
activity.period.semiyearly=6ヶ月
activity.period.yearly=1年
activity.overview=概要 activity.overview=概要
activity.active_prs_count_1=<strong>%d</strong>件のアクティブなプルリクエスト activity.active_prs_count_1=<strong>%d</strong>件のアクティブなプルリクエスト
activity.active_prs_count_n=<strong>%d</strong>件のアクティブなプルリクエスト activity.active_prs_count_n=<strong>%d</strong>件のアクティブなプルリクエスト
@ -1379,8 +1383,20 @@ settings.unarchive.success=リポジトリのアーカイブを解除しまし
settings.unarchive.error=リポジトリのアーカイブ解除でエラーが発生しました。 詳細はログを確認してください。 settings.unarchive.error=リポジトリのアーカイブ解除でエラーが発生しました。 詳細はログを確認してください。
settings.update_avatar_success=リポジトリのアバターを更新しました。 settings.update_avatar_success=リポジトリのアバターを更新しました。
settings.lfs=LFS settings.lfs=LFS
settings.lfs_filelist=このリポジトリに含まれているLFSファイル
settings.lfs_no_lfs_files=このリポジトリにLFSファイルはありません
settings.lfs_findcommits=コミットを検索
settings.lfs_lfs_file_no_commits=このLFSファイルに関するコミットはありません
settings.lfs_delete=LFSファイル(OID %s)の削除 settings.lfs_delete=LFSファイル(OID %s)の削除
settings.lfs_delete_warning=LFSファイルを削除すると、チェックアウトのときに 'object does not exist' エラーが発生するかもしれません。 よろしいですか? settings.lfs_delete_warning=LFSファイルを削除すると、チェックアウトのときに 'object does not exist' エラーが発生するかもしれません。 よろしいですか?
settings.lfs_findpointerfiles=ポインタファイルを検索
settings.lfs_pointers.found=%d件のblobポインタ - 登録済 %d件、未登録 %d件 (実体ファイルなし %d件)
settings.lfs_pointers.sha=Blob SHA
settings.lfs_pointers.oid=OID
settings.lfs_pointers.inRepo=Repo内
settings.lfs_pointers.exists=実体ファイルあり
settings.lfs_pointers.accessible=ユーザーがアクセス可
settings.lfs_pointers.associateAccessible=アクセス可能な%d件のOIDを登録
diff.browse_source=ソースを参照 diff.browse_source=ソースを参照
diff.parent= diff.parent=

View file

@ -1089,6 +1089,9 @@ activity.period.daily=1 dia
activity.period.halfweekly=3 dias activity.period.halfweekly=3 dias
activity.period.weekly=1 semana activity.period.weekly=1 semana
activity.period.monthly=1 mês activity.period.monthly=1 mês
activity.period.quarterly=3 meses
activity.period.semiyearly=6 meses
activity.period.yearly=1 ano
activity.overview=Visão geral activity.overview=Visão geral
activity.active_prs_count_1=<strong>%d</strong> Pull request ativo activity.active_prs_count_1=<strong>%d</strong> Pull request ativo
activity.active_prs_count_n=<strong>%d</strong> Pull requests ativos activity.active_prs_count_n=<strong>%d</strong> Pull requests ativos

View file

@ -701,6 +701,7 @@ editor.no_changes_to_show=Gösterilecek değişiklik yok.
editor.fail_to_update_file=Şu hata ile '%s' dosyasını güncelleme/oluşturma başarısız oldu: %v editor.fail_to_update_file=Şu hata ile '%s' dosyasını güncelleme/oluşturma başarısız oldu: %v
editor.add_subdir=Bir dizin ekle… editor.add_subdir=Bir dizin ekle…
editor.unable_to_upload_files=Şu hata ile dosyalar '%s' 'a yüklenemedi: %v editor.unable_to_upload_files=Şu hata ile dosyalar '%s' 'a yüklenemedi: %v
editor.upload_file_is_locked='%s' dosyası %s tarafından kilitlendi.
editor.upload_files_to_dir=Dosyaları '%s' 'a yükle editor.upload_files_to_dir=Dosyaları '%s' 'a yükle
editor.cannot_commit_to_protected_branch=Korunan '%s' dalına işleme yapılamıyor. editor.cannot_commit_to_protected_branch=Korunan '%s' dalına işleme yapılamıyor.
@ -1046,17 +1047,20 @@ activity.period.daily=1 gün
activity.period.halfweekly=3 gün activity.period.halfweekly=3 gün
activity.period.weekly=1 hafta activity.period.weekly=1 hafta
activity.period.monthly=1 ay activity.period.monthly=1 ay
activity.period.quarterly=3 ay
activity.period.semiyearly=6 ay
activity.period.yearly=1 yıl
activity.overview=Genel Bakış activity.overview=Genel Bakış
activity.active_prs_count_1=<strong>%d</strong> Aktif Çekme İsteği activity.active_prs_count_1=<strong>%d</strong> Aktif Çekme İsteği
activity.active_prs_count_n=<strong>%d</strong> Aktif Çekme İsteği activity.active_prs_count_n=<strong>%d</strong> Aktif Çekme İsteği
activity.merged_prs_count_1=Birleştirilmiş Çekme İsteği activity.merged_prs_count_1=Birleştirilmiş Çekme İsteği
activity.merged_prs_count_n=Birleştirilmiş Çekme İstekleri activity.merged_prs_count_n=Birleştirilmiş Çekme İsteği
activity.opened_prs_count_1=Önerilen Çekme İsteği activity.opened_prs_count_1=Önerilen Çekme İsteği
activity.opened_prs_count_n=Önerilen Çekme İstekleri activity.opened_prs_count_n=Önerilen Çekme İsteği
activity.title.user_1=%d kullanıcı activity.title.user_1=%d kullanıcı
activity.title.user_n=%d kullanıcı activity.title.user_n=%d kullanıcı
activity.title.prs_1=%d Çekme isteği activity.title.prs_1=%d Çekme isteği
activity.title.prs_n=%d Çekme istekleri activity.title.prs_n=%d Çekme isteği
activity.title.prs_merged_by=%s tarafından %s birleştirildi activity.title.prs_merged_by=%s tarafından %s birleştirildi
activity.title.prs_opened_by=%s tarafından %s önerildi activity.title.prs_opened_by=%s tarafından %s önerildi
activity.merged_prs_label=Birleştirilen activity.merged_prs_label=Birleştirilen
@ -1064,20 +1068,21 @@ activity.opened_prs_label=Önerilen
activity.active_issues_count_1=<strong>%d</strong> Aktif Konu activity.active_issues_count_1=<strong>%d</strong> Aktif Konu
activity.active_issues_count_n=<strong>%d</strong> Aktif Konu activity.active_issues_count_n=<strong>%d</strong> Aktif Konu
activity.closed_issues_count_1=Kapalı Konu activity.closed_issues_count_1=Kapalı Konu
activity.closed_issues_count_n=Kapalı Konular activity.closed_issues_count_n=Kapalı Konu
activity.title.issues_1=%d Konu activity.title.issues_1=%d Konu
activity.title.issues_n=%d Konu activity.title.issues_n=%d Konu
activity.title.issues_closed_by=%s %s tarafından kapatıldı activity.title.issues_closed_by=%s %s tarafından kapatıldı
activity.title.issues_created_by=%s %s tarafından oluşturuldu activity.title.issues_created_by=%s %s tarafından oluşturuldu
activity.closed_issue_label=Kapalı activity.closed_issue_label=Kapalı
activity.new_issues_count_1=Yeni Konu activity.new_issues_count_1=Yeni Konu
activity.new_issues_count_n=Yeni Konular activity.new_issues_count_n=Yeni Konu
activity.new_issue_label=ıldı activity.new_issue_label=ıldı
activity.title.unresolved_conv_1=%d Çözümlenmemiş Konuşma activity.title.unresolved_conv_1=%d Çözümlenmemiş Konuşma
activity.title.unresolved_conv_n=%d Çözümlenmemiş Konuşma activity.title.unresolved_conv_n=%d Çözümlenmemiş Konuşma
activity.unresolved_conv_desc=Son zamanlarda değişen bu konu ve değişiklik istekleri henüz çözümlenmedi.
activity.unresolved_conv_label=ık activity.unresolved_conv_label=ık
activity.title.releases_1=%d Serbest bırak activity.title.releases_1=%d Sürüm
activity.title.releases_n=%d Serbest bırakmalar activity.title.releases_n=%d Sürüm
activity.title.releases_published_by=%s tarafından %s yayınlandı activity.title.releases_published_by=%s tarafından %s yayınlandı
activity.published_release_label=Yayınlandı activity.published_release_label=Yayınlandı
activity.no_git_activity=Bu dönemde herhangi bir işleme yapılmamıştır. activity.no_git_activity=Bu dönemde herhangi bir işleme yapılmamıştır.
@ -1095,6 +1100,7 @@ activity.git_stats_file_1=%d dosya
activity.git_stats_file_n=%d dosya activity.git_stats_file_n=%d dosya
activity.git_stats_files_changed_1=değişti activity.git_stats_files_changed_1=değişti
activity.git_stats_files_changed_n=değişti activity.git_stats_files_changed_n=değişti
activity.git_stats_additions=ve oldu
activity.git_stats_addition_1=%d ekleme activity.git_stats_addition_1=%d ekleme
activity.git_stats_addition_n=%d ekleme activity.git_stats_addition_n=%d ekleme
activity.git_stats_and_deletions=ve activity.git_stats_and_deletions=ve

View file

@ -84,7 +84,6 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
Passwd: form.Password, Passwd: form.Password,
IsActive: true, IsActive: true,
LoginType: models.LoginPlain, LoginType: models.LoginPlain,
MustChangePassword: form.MustChangePassword,
} }
if len(form.LoginType) > 0 { if len(form.LoginType) > 0 {
@ -95,10 +94,13 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
u.LoginName = form.LoginName u.LoginName = form.LoginName
} }
} }
if u.LoginType == models.LoginPlain {
if !password.IsComplexEnough(form.Password) { if !password.IsComplexEnough(form.Password) {
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form) ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form)
return return
} }
u.MustChangePassword = form.MustChangePassword
}
if err := models.CreateUser(u); err != nil { if err := models.CreateUser(u); err != nil {
switch { switch {
case models.IsErrUserAlreadyExist(err): case models.IsErrUserAlreadyExist(err):

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/webhook"
"github.com/unknwon/com" "github.com/unknwon/com"
) )
@ -166,7 +167,7 @@ func ToHook(repoLink string, w *models.Webhook) *api.Hook {
"content_type": w.ContentType.Name(), "content_type": w.ContentType.Name(),
} }
if w.HookTaskType == models.SLACK { if w.HookTaskType == models.SLACK {
s := w.GetSlackHook() s := webhook.GetSlackHook(w)
config["channel"] = s.Channel config["channel"] = s.Channel
config["username"] = s.Username config["username"] = s.Username
config["icon_url"] = s.IconURL config["icon_url"] = s.IconURL

View file

@ -458,9 +458,16 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
issue.Content = *form.Body issue.Content = *form.Body
} }
// Update the deadline // Update or remove the deadline, only if set and allowed
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) { if (form.Deadline != nil || form.RemoveDeadline != nil) && ctx.Repo.CanWrite(models.UnitTypeIssues) {
deadlineUnix := timeutil.TimeStamp(form.Deadline.Unix()) var deadlineUnix timeutil.TimeStamp
if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
23, 59, 59, 0, form.Deadline.Location())
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
}
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err) ctx.Error(500, "UpdateIssueDeadline", err)
return return

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
"time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
@ -326,7 +327,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
// swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest // swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest
// --- // ---
// summary: Update a pull request // summary: Update a pull request. If using deadline only the date will be taken into account, and time of day ignored.
// consumes: // consumes:
// - application/json // - application/json
// produces: // produces:
@ -385,9 +386,15 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
issue.Content = form.Body issue.Content = form.Body
} }
// Update Deadline // Update or remove deadline if set
if form.Deadline != nil { if form.Deadline != nil || form.RemoveDeadline != nil {
deadlineUnix := timeutil.TimeStamp(form.Deadline.Unix()) var deadlineUnix timeutil.TimeStamp
if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
23, 59, 59, 0, form.Deadline.Location())
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
}
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil { if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err) ctx.Error(500, "UpdateIssueDeadline", err)
return return

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/routers/api/v1/convert" "code.gitea.io/gitea/routers/api/v1/convert"
"code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/routers/utils"
@ -129,7 +130,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
return nil, false return nil, false
} }
meta, err := json.Marshal(&models.SlackMeta{ meta, err := json.Marshal(&webhook.SlackMeta{
Channel: strings.TrimSpace(channel), Channel: strings.TrimSpace(channel),
Username: form.Config["username"], Username: form.Config["username"],
IconURL: form.Config["icon_url"], IconURL: form.Config["icon_url"],
@ -203,7 +204,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *models.Webho
if w.HookTaskType == models.SLACK { if w.HookTaskType == models.SLACK {
if channel, ok := form.Config["channel"]; ok { if channel, ok := form.Config["channel"]; ok {
meta, err := json.Marshal(&models.SlackMeta{ meta, err := json.Marshal(&webhook.SlackMeta{
Channel: channel, Channel: channel,
Username: form.Config["username"], Username: form.Config["username"],
IconURL: form.Config["icon_url"], IconURL: form.Config["icon_url"],

View file

@ -35,6 +35,12 @@ func Activity(ctx *context.Context) {
timeFrom = timeUntil.Add(-time.Hour * 168) timeFrom = timeUntil.Add(-time.Hour * 168)
case "monthly": case "monthly":
timeFrom = timeUntil.AddDate(0, -1, 0) timeFrom = timeUntil.AddDate(0, -1, 0)
case "quarterly":
timeFrom = timeUntil.AddDate(0, -3, 0)
case "semiyearly":
timeFrom = timeUntil.AddDate(0, -6, 0)
case "yearly":
timeFrom = timeUntil.AddDate(-1, 0, 0)
default: default:
ctx.Data["Period"] = "weekly" ctx.Data["Period"] = "weekly"
timeFrom = timeUntil.Add(-time.Hour * 168) timeFrom = timeUntil.Add(-time.Hour * 168)
@ -70,6 +76,12 @@ func ActivityAuthors(ctx *context.Context) {
timeFrom = timeUntil.Add(-time.Hour * 168) timeFrom = timeUntil.Add(-time.Hour * 168)
case "monthly": case "monthly":
timeFrom = timeUntil.AddDate(0, -1, 0) timeFrom = timeUntil.AddDate(0, -1, 0)
case "quarterly":
timeFrom = timeUntil.AddDate(0, -3, 0)
case "semiyearly":
timeFrom = timeUntil.AddDate(0, -6, 0)
case "yearly":
timeFrom = timeUntil.AddDate(-1, 0, 0)
default: default:
timeFrom = timeUntil.Add(-time.Hour * 168) timeFrom = timeUntil.Add(-time.Hour * 168)
} }

View file

@ -268,7 +268,7 @@ func DiscordHooksNewPost(ctx *context.Context, form auth.NewDiscordHookForm) {
return return
} }
meta, err := json.Marshal(&models.DiscordMeta{ meta, err := json.Marshal(&webhook.DiscordMeta{
Username: form.Username, Username: form.Username,
IconURL: form.IconURL, IconURL: form.IconURL,
}) })
@ -357,7 +357,7 @@ func TelegramHooksNewPost(ctx *context.Context, form auth.NewTelegramHookForm) {
return return
} }
meta, err := json.Marshal(&models.TelegramMeta{ meta, err := json.Marshal(&webhook.TelegramMeta{
BotToken: form.BotToken, BotToken: form.BotToken,
ChatID: form.ChatID, ChatID: form.ChatID,
}) })
@ -452,7 +452,7 @@ func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) {
return return
} }
meta, err := json.Marshal(&models.SlackMeta{ meta, err := json.Marshal(&webhook.SlackMeta{
Channel: strings.TrimSpace(form.Channel), Channel: strings.TrimSpace(form.Channel),
Username: form.Username, Username: form.Username,
IconURL: form.IconURL, IconURL: form.IconURL,
@ -515,11 +515,11 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) {
ctx.Data["HookType"] = w.HookTaskType.Name() ctx.Data["HookType"] = w.HookTaskType.Name()
switch w.HookTaskType { switch w.HookTaskType {
case models.SLACK: case models.SLACK:
ctx.Data["SlackHook"] = w.GetSlackHook() ctx.Data["SlackHook"] = webhook.GetSlackHook(w)
case models.DISCORD: case models.DISCORD:
ctx.Data["DiscordHook"] = w.GetDiscordHook() ctx.Data["DiscordHook"] = webhook.GetDiscordHook(w)
case models.TELEGRAM: case models.TELEGRAM:
ctx.Data["TelegramHook"] = w.GetTelegramHook() ctx.Data["TelegramHook"] = webhook.GetTelegramHook(w)
} }
ctx.Data["History"], err = w.History(1) ctx.Data["History"], err = w.History(1)
@ -646,7 +646,7 @@ func SlackHooksEditPost(ctx *context.Context, form auth.NewSlackHookForm) {
return return
} }
meta, err := json.Marshal(&models.SlackMeta{ meta, err := json.Marshal(&webhook.SlackMeta{
Channel: strings.TrimSpace(form.Channel), Channel: strings.TrimSpace(form.Channel),
Username: form.Username, Username: form.Username,
IconURL: form.IconURL, IconURL: form.IconURL,
@ -690,7 +690,7 @@ func DiscordHooksEditPost(ctx *context.Context, form auth.NewDiscordHookForm) {
return return
} }
meta, err := json.Marshal(&models.DiscordMeta{ meta, err := json.Marshal(&webhook.DiscordMeta{
Username: form.Username, Username: form.Username,
IconURL: form.IconURL, IconURL: form.IconURL,
}) })
@ -763,7 +763,7 @@ func TelegramHooksEditPost(ctx *context.Context, form auth.NewTelegramHookForm)
ctx.HTML(200, orCtx.NewTemplate) ctx.HTML(200, orCtx.NewTemplate)
return return
} }
meta, err := json.Marshal(&models.TelegramMeta{ meta, err := json.Marshal(&webhook.TelegramMeta{
BotToken: form.BotToken, BotToken: form.BotToken,
ChatID: form.ChatID, ChatID: form.ChatID,
}) })

View file

@ -5,10 +5,7 @@
package issue package issue
import ( import (
"fmt"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
) )
@ -24,18 +21,6 @@ func NewIssue(repo *models.Repository, issue *models.Issue, labelIDs []int64, uu
} }
} }
if err := models.NotifyWatchers(&models.Action{
ActUserID: issue.Poster.ID,
ActUser: issue.Poster,
OpType: models.ActionCreateIssue,
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
RepoID: repo.ID,
Repo: repo,
IsPrivate: repo.IsPrivate,
}); err != nil {
log.Error("NotifyWatchers: %v", err)
}
notification.NotifyNewIssue(issue) notification.NotifyNewIssue(issue)
return nil return nil

View file

@ -9,9 +9,8 @@ import (
"fmt" "fmt"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/webhook"
) )
func syncAction(opType models.ActionType, repo *models.Repository, refName string, data []byte) error { func syncAction(opType models.ActionType, repo *models.Repository, refName string, data []byte) error {
@ -45,25 +44,9 @@ func SyncPushAction(repo *models.Repository, opts SyncPushActionOptions) error {
opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum]
} }
apiCommits, err := opts.Commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL())
if err != nil {
return err
}
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
apiPusher := repo.MustOwner().APIFormat()
if err := webhook.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{ notification.NotifyPushCommits(repo.MustOwner(), repo, opts.RefName, opts.OldCommitID, opts.NewCommitID, opts.Commits)
Ref: opts.RefName,
Before: opts.OldCommitID,
After: opts.NewCommitID,
CompareURL: setting.AppURL + opts.Commits.CompareURL,
Commits: apiCommits,
Repo: repo.APIFormat(models.AccessModeOwner),
Pusher: apiPusher,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks: %v", err)
}
data, err := json.Marshal(opts.Commits) data, err := json.Marshal(opts.Commits)
if err != nil { if err != nil {

View file

@ -20,10 +20,9 @@ import (
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/webhook"
"github.com/mcuadros/go-version" "github.com/mcuadros/go-version"
) )
@ -360,16 +359,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
return nil return nil
} }
mode, _ := models.AccessLevel(doer, pr.Issue.Repo) notification.NotifyIssueChangeStatus(doer, pr.Issue, true)
if err = webhook.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueClosed,
Index: pr.Index,
PullRequest: pr.APIFormat(),
Repository: pr.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
}
return nil return nil
} }

View file

@ -10,8 +10,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/webhook"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"
) )
@ -27,30 +26,10 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6
} }
} }
if err := models.NotifyWatchers(&models.Action{
ActUserID: pull.Poster.ID,
ActUser: pull.Poster,
OpType: models.ActionCreatePullRequest,
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
RepoID: repo.ID,
Repo: repo,
IsPrivate: repo.IsPrivate,
}); err != nil {
log.Error("NotifyWatchers: %v", err)
}
pr.Issue = pull pr.Issue = pull
pull.PullRequest = pr pull.PullRequest = pr
mode, _ := models.AccessLevel(pull.Poster, repo)
if err := webhook.PrepareWebhooks(repo, models.HookEventPullRequest, &api.PullRequestPayload{ notification.NotifyNewPullRequest(pr)
Action: api.HookIssueOpened,
Index: pull.Index,
PullRequest: pr.APIFormat(),
Repository: repo.APIFormat(mode),
Sender: pull.Poster.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
}
return nil return nil
} }
@ -109,25 +88,11 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy
if err == nil { if err == nil {
for _, pr := range prs { for _, pr := range prs {
pr.Issue.PullRequest = pr pr.Issue.PullRequest = pr
if err = pr.Issue.LoadAttributes(); err != nil { notification.NotifyPullRequestSynchronized(doer, pr)
log.Error("LoadAttributes: %v", err)
continue
}
if err = webhook.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: pr.Issue.Index,
PullRequest: pr.Issue.PullRequest.APIFormat(),
Repository: pr.Issue.Repo.APIFormat(models.AccessModeNone),
Sender: doer.APIFormat(),
}); err != nil {
log.Error("PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
continue
} }
} }
} }
}
addHeadRepoTasks(prs) addHeadRepoTasks(prs)
log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch) log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)

View file

@ -7,8 +7,7 @@ package pull
import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/webhook"
) )
// CreateReview creates a new review based on opts // CreateReview creates a new review based on opts
@ -18,7 +17,9 @@ func CreateReview(opts models.CreateReviewOptions) (*models.Review, error) {
return nil, err return nil, err
} }
return review, reviewHook(review) notification.NotifyPullRequestReview(review.Issue.PullRequest, review, nil)
return review, nil
} }
// UpdateReview updates a review // UpdateReview updates a review
@ -28,43 +29,7 @@ func UpdateReview(review *models.Review) error {
return err return err
} }
return reviewHook(review) notification.NotifyPullRequestReview(review.Issue.PullRequest, review, nil)
}
func reviewHook(review *models.Review) error {
var reviewHookType models.HookEventType
switch review.Type {
case models.ReviewTypeApprove:
reviewHookType = models.HookEventPullRequestApproved
case models.ReviewTypeComment:
reviewHookType = models.HookEventPullRequestComment
case models.ReviewTypeReject:
reviewHookType = models.HookEventPullRequestRejected
default:
// unsupported review webhook type here
return nil return nil
}
pr := review.Issue.PullRequest
if err := pr.LoadIssue(); err != nil {
return err
}
mode, err := models.AccessLevel(review.Issue.Poster, review.Issue.Repo)
if err != nil {
return err
}
return webhook.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: review.Issue.Index,
PullRequest: pr.APIFormat(),
Repository: review.Issue.Repo.APIFormat(mode),
Sender: review.Reviewer.APIFormat(),
Review: &api.ReviewPayload{
Type: string(reviewHookType),
Content: review.Content,
},
})
} }

View file

@ -42,7 +42,7 @@
<input id="password" name="password" type="password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}> <input id="password" name="password" type="password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}>
</div> </div>
<div class="inline field"> <div class="inline field local{{if ne .login_type "0-0"}} hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "auth.allow_password_change" }}</strong></label> <label><strong>{{.i18n.Tr "auth.allow_password_change" }}</strong></label>
<input name="must_change_password" type="checkbox" checked> <input name="must_change_password" type="checkbox" checked>

View file

@ -17,6 +17,9 @@
<a class="{{if eq .Period "halfweekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/halfweekly">{{.i18n.Tr "repo.activity.period.halfweekly"}}</a> <a class="{{if eq .Period "halfweekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/halfweekly">{{.i18n.Tr "repo.activity.period.halfweekly"}}</a>
<a class="{{if eq .Period "weekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/weekly">{{.i18n.Tr "repo.activity.period.weekly"}}</a> <a class="{{if eq .Period "weekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/weekly">{{.i18n.Tr "repo.activity.period.weekly"}}</a>
<a class="{{if eq .Period "monthly"}}active {{end}}item" href="{{$.RepoLink}}/activity/monthly">{{.i18n.Tr "repo.activity.period.monthly"}}</a> <a class="{{if eq .Period "monthly"}}active {{end}}item" href="{{$.RepoLink}}/activity/monthly">{{.i18n.Tr "repo.activity.period.monthly"}}</a>
<a class="{{if eq .Period "quarterly"}}active {{end}}item" href="{{$.RepoLink}}/activity/quarterly">{{.i18n.Tr "repo.activity.period.quarterly"}}</a>
<a class="{{if eq .Period "semiyearly"}}active {{end}}item" href="{{$.RepoLink}}/activity/semiyearly">{{.i18n.Tr "repo.activity.period.semiyearly"}}</a>
<a class="{{if eq .Period "yearly"}}active {{end}}item" href="{{$.RepoLink}}/activity/yearly">{{.i18n.Tr "repo.activity.period.yearly"}}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -42,7 +42,6 @@
{{if .Signature}} {{if .Signature}}
<div class="ui detail icon button"> <div class="ui detail icon button">
{{if .Verification.Verified}} {{if .Verification.Verified}}
<i title="{{.Verification.Reason}}" class="lock green icon"></i>
{{if ne .Verification.SigningUser.ID 0}} {{if ne .Verification.SigningUser.ID 0}}
<i title="{{.Verification.Reason}}" class="lock green icon"></i> <i title="{{.Verification.Reason}}" class="lock green icon"></i>
{{else}} {{else}}

View file

@ -3,7 +3,12 @@
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="menu"> <div class="menu">
<a class="item tiny basic toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a> <a class="item tiny basic toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a>
{{if .Issue.Index}}
<a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.patch" download="{{.Issue.Index}}.patch">{{.i18n.Tr "repo.diff.download_patch"}}</a> <a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.patch" download="{{.Issue.Index}}.patch">{{.i18n.Tr "repo.diff.download_patch"}}</a>
<a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.diff" download="{{.Issue.Index}}.diff">{{.i18n.Tr "repo.diff.download_diff"}}</a> <a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.diff" download="{{.Issue.Index}}.diff">{{.i18n.Tr "repo.diff.download_diff"}}</a>
{{else if .Commit.ID.String}}
<a class="item" href="{{$.RepoLink}}/commit/{{.Commit.ID.String}}.patch" download="{{ShortSha .Commit.ID.String}}.patch">{{.i18n.Tr "repo.diff.download_patch"}}</a>
<a class="item" href="{{$.RepoLink}}/commit/{{.Commit.ID.String}}.diff" download="{{ShortSha .Commit.ID.String}}.diff">{{.i18n.Tr "repo.diff.download_diff"}}</a>
{{end}}
</div> </div>
</div> </div>

View file

@ -132,7 +132,7 @@
{{range $.branch_status_check_contexts}} {{range $.branch_status_check_contexts}}
<tr><td> <tr><td>
<span class="ui checkbox"> <span class="ui checkbox">
<input class="enable-whitelist" name="status_check_contexts" value="{{.}}" type="checkbox" {{if $.is_context_require}}{{if call $.is_context_required .}}checked{{end}}{{end}}> <input class="enable-whitelist" name="status_check_contexts" value="{{.}}" type="checkbox" {{if $.is_context_required}}{{if call $.is_context_required .}}checked{{end}}{{end}}>
</span> </span>
{{.}} {{.}}
{{if $.is_context_required}}{{if call $.is_context_required .}}<div class="ui label right">Required</div>{{end}}{{end}} {{if $.is_context_required}}{{if call $.is_context_required .}}<div class="ui label right">Required</div>{{end}}{{end}}

View file

@ -4715,7 +4715,7 @@
"tags": [ "tags": [
"repository" "repository"
], ],
"summary": "Update a pull request", "summary": "Update a pull request. If using deadline only the date will be taken into account, and time of day ignored.",
"operationId": "repoEditPullRequest", "operationId": "repoEditPullRequest",
"parameters": [ "parameters": [
{ {
@ -8532,6 +8532,10 @@
"title": { "title": {
"type": "string", "type": "string",
"x-go-name": "Title" "x-go-name": "Title"
},
"unset_due_date": {
"type": "boolean",
"x-go-name": "RemoveDeadline"
} }
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
@ -8660,6 +8664,10 @@
"title": { "title": {
"type": "string", "type": "string",
"x-go-name": "Title" "x-go-name": "Title"
},
"unset_due_date": {
"type": "boolean",
"x-go-name": "RemoveDeadline"
} }
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"

View file

@ -80,5 +80,5 @@ func trimIndentUpTo(max int) func(string) string {
} }
} }
func (n Example) String() string { return orgWriter.nodesAsString(n) } func (n Example) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Block) String() string { return orgWriter.nodesAsString(n) } func (n Block) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -90,7 +90,7 @@ func New() *Configuration {
} }
// String returns the pretty printed Org mode string for the given nodes (see OrgWriter). // String returns the pretty printed Org mode string for the given nodes (see OrgWriter).
func String(nodes []Node) string { return orgWriter.nodesAsString(nodes...) } func String(nodes []Node) string { return orgWriter.WriteNodesAsString(nodes...) }
// Write is called after with an instance of the Writer interface to export a parsed Document into another format. // Write is called after with an instance of the Writer interface to export a parsed Document into another format.
func (d *Document) Write(w Writer) (out string, err error) { func (d *Document) Write(w Writer) (out string, err error) {

View file

@ -93,5 +93,5 @@ func (d *PropertyDrawer) Get(key string) (string, bool) {
return "", false return "", false
} }
func (n Drawer) String() string { return orgWriter.nodesAsString(n) } func (n Drawer) String() string { return orgWriter.WriteNodesAsString(n) }
func (n PropertyDrawer) String() string { return orgWriter.nodesAsString(n) } func (n PropertyDrawer) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -32,4 +32,4 @@ func (d *Document) parseFootnoteDefinition(i int, parentStop stopFn) (int, Node)
return consumed, definition return consumed, definition
} }
func (n FootnoteDefinition) String() string { return orgWriter.nodesAsString(n) } func (n FootnoteDefinition) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -98,4 +98,4 @@ func (parent *Section) add(current *Section) {
} }
} }
func (n Headline) String() string { return orgWriter.nodesAsString(n) } func (n Headline) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -69,16 +69,13 @@ func NewHTMLWriter() *HTMLWriter {
} }
} }
func (w *HTMLWriter) emptyClone() *HTMLWriter { func (w *HTMLWriter) WriteNodesAsString(nodes ...Node) string {
wcopy := *w original := w.Builder
wcopy.Builder = strings.Builder{} w.Builder = strings.Builder{}
return &wcopy WriteNodes(w, nodes...)
} out := w.String()
w.Builder = original
func (w *HTMLWriter) nodesAsString(nodes ...Node) string { return out
tmp := w.emptyClone()
WriteNodes(tmp, nodes...)
return tmp.String()
} }
func (w *HTMLWriter) WriterWithExtensions() Writer { func (w *HTMLWriter) WriterWithExtensions() Writer {
@ -104,12 +101,14 @@ func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {}
func (w *HTMLWriter) WriteBlock(b Block) { func (w *HTMLWriter) WriteBlock(b Block) {
content := "" content := ""
if isRawTextBlock(b.Name) { if isRawTextBlock(b.Name) {
exportWriter := w.emptyClone() builder, htmlEscape := w.Builder, w.htmlEscape
exportWriter.htmlEscape = false w.Builder, w.htmlEscape = strings.Builder{}, false
WriteNodes(exportWriter, b.Children...) WriteNodes(w, b.Children...)
content = strings.TrimRightFunc(exportWriter.String(), unicode.IsSpace) out := w.String()
w.Builder, w.htmlEscape = builder, htmlEscape
content = strings.TrimRightFunc(out, unicode.IsSpace)
} else { } else {
content = w.nodesAsString(b.Children...) content = w.WriteNodesAsString(b.Children...)
} }
switch name := b.Name; { switch name := b.Name; {
case name == "SRC": case name == "SRC":
@ -194,7 +193,7 @@ func (w *HTMLWriter) writeSection(section *Section) {
// NOTE: To satisfy hugo ExtractTOC() check we cannot use `<li>\n` here. Doesn't really matter, just a note. // NOTE: To satisfy hugo ExtractTOC() check we cannot use `<li>\n` here. Doesn't really matter, just a note.
w.WriteString("<li>") w.WriteString("<li>")
h := section.Headline h := section.Headline
title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.nodesAsString(h.Title...), "") title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.WriteNodesAsString(h.Title...), "")
w.WriteString(fmt.Sprintf("<a href=\"#%s\">%s</a>\n", h.ID(), title)) w.WriteString(fmt.Sprintf("<a href=\"#%s\">%s</a>\n", h.ID(), title))
if len(section.Children) != 0 { if len(section.Children) != 0 {
w.WriteString("<ul>\n") w.WriteString("<ul>\n")
@ -306,7 +305,7 @@ func (w *HTMLWriter) WriteRegularLink(l RegularLink) {
} }
description := url description := url
if l.Description != nil { if l.Description != nil {
description = w.nodesAsString(l.Description...) description = w.WriteNodesAsString(l.Description...)
} }
switch l.Kind() { switch l.Kind() {
case "image": case "image":
@ -384,10 +383,10 @@ func (w *HTMLWriter) WriteHorizontalRule(h HorizontalRule) {
} }
func (w *HTMLWriter) WriteNodeWithMeta(n NodeWithMeta) { func (w *HTMLWriter) WriteNodeWithMeta(n NodeWithMeta) {
out := w.nodesAsString(n.Node) out := w.WriteNodesAsString(n.Node)
if p, ok := n.Node.(Paragraph); ok { if p, ok := n.Node.(Paragraph); ok {
if len(p.Children) == 1 && isImageOrVideoLink(p.Children[0]) { if len(p.Children) == 1 && isImageOrVideoLink(p.Children[0]) {
out = w.nodesAsString(p.Children[0]) out = w.WriteNodesAsString(p.Children[0])
} }
} }
for _, attributes := range n.Meta.HTMLAttributes { for _, attributes := range n.Meta.HTMLAttributes {
@ -399,7 +398,7 @@ func (w *HTMLWriter) WriteNodeWithMeta(n NodeWithMeta) {
if i != 0 { if i != 0 {
caption += " " caption += " "
} }
caption += w.nodesAsString(ns...) caption += w.WriteNodesAsString(ns...)
} }
out = fmt.Sprintf("<figure>\n%s<figcaption>\n%s\n</figcaption>\n</figure>\n", out, caption) out = fmt.Sprintf("<figure>\n%s<figcaption>\n%s\n</figcaption>\n</figure>\n", out, caption)
} }

View file

@ -147,8 +147,8 @@ func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int
switch { switch {
case start+2 >= len(input): case start+2 >= len(input):
case input[start+1] == '\\' && start != 0 && input[start-1] != '\n': case input[start+1] == '\\' && start != 0 && input[start-1] != '\n':
for i := start + 2; unicode.IsSpace(rune(input[i])); i++ { for i := start + 2; i <= len(input)-1 && unicode.IsSpace(rune(input[i])); i++ {
if i >= len(input) || input[i] == '\n' { if input[i] == '\n' {
return i + 1 - start, ExplicitLineBreak{} return i + 1 - start, ExplicitLineBreak{}
} }
} }
@ -346,12 +346,12 @@ func (l RegularLink) Kind() string {
return "regular" return "regular"
} }
func (n Text) String() string { return orgWriter.nodesAsString(n) } func (n Text) String() string { return orgWriter.WriteNodesAsString(n) }
func (n LineBreak) String() string { return orgWriter.nodesAsString(n) } func (n LineBreak) String() string { return orgWriter.WriteNodesAsString(n) }
func (n ExplicitLineBreak) String() string { return orgWriter.nodesAsString(n) } func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) }
func (n StatisticToken) String() string { return orgWriter.nodesAsString(n) } func (n StatisticToken) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Emphasis) String() string { return orgWriter.nodesAsString(n) } func (n Emphasis) String() string { return orgWriter.WriteNodesAsString(n) }
func (n LatexFragment) String() string { return orgWriter.nodesAsString(n) } func (n LatexFragment) String() string { return orgWriter.WriteNodesAsString(n) }
func (n FootnoteLink) String() string { return orgWriter.nodesAsString(n) } func (n FootnoteLink) String() string { return orgWriter.WriteNodesAsString(n) }
func (n RegularLink) String() string { return orgWriter.nodesAsString(n) } func (n RegularLink) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Timestamp) String() string { return orgWriter.nodesAsString(n) } func (n Timestamp) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -177,8 +177,8 @@ func (d *Document) loadSetupFile(k Keyword) (int, Node) {
return 1, k return 1, k
} }
func (n Comment) String() string { return orgWriter.nodesAsString(n) } func (n Comment) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Keyword) String() string { return orgWriter.nodesAsString(n) } func (n Keyword) String() string { return orgWriter.WriteNodesAsString(n) }
func (n NodeWithMeta) String() string { return orgWriter.nodesAsString(n) } func (n NodeWithMeta) String() string { return orgWriter.WriteNodesAsString(n) }
func (n NodeWithName) String() string { return orgWriter.nodesAsString(n) } func (n NodeWithName) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Include) String() string { return orgWriter.nodesAsString(n) } func (n Include) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -109,6 +109,6 @@ func (d *Document) parseListItem(l List, i int, parentStop stopFn) (int, Node) {
return i - start, ListItem{bullet, status, nodes} return i - start, ListItem{bullet, status, nodes}
} }
func (n List) String() string { return orgWriter.nodesAsString(n) } func (n List) String() string { return orgWriter.WriteNodesAsString(n) }
func (n ListItem) String() string { return orgWriter.nodesAsString(n) } func (n ListItem) String() string { return orgWriter.WriteNodesAsString(n) }
func (n DescriptiveListItem) String() string { return orgWriter.nodesAsString(n) } func (n DescriptiveListItem) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -43,39 +43,33 @@ func (w *OrgWriter) WriterWithExtensions() Writer {
func (w *OrgWriter) Before(d *Document) {} func (w *OrgWriter) Before(d *Document) {}
func (w *OrgWriter) After(d *Document) {} func (w *OrgWriter) After(d *Document) {}
func (w *OrgWriter) emptyClone() *OrgWriter { func (w *OrgWriter) WriteNodesAsString(nodes ...Node) string {
wcopy := *w builder := w.Builder
wcopy.Builder = strings.Builder{} w.Builder = strings.Builder{}
return &wcopy WriteNodes(w, nodes...)
} out := w.String()
w.Builder = builder
func (w *OrgWriter) nodesAsString(nodes ...Node) string { return out
tmp := w.emptyClone()
WriteNodes(tmp, nodes...)
return tmp.String()
} }
func (w *OrgWriter) WriteHeadline(h Headline) { func (w *OrgWriter) WriteHeadline(h Headline) {
tmp := w.emptyClone() start := w.Len()
tmp.WriteString(strings.Repeat("*", h.Lvl)) w.WriteString(strings.Repeat("*", h.Lvl))
if h.Status != "" { if h.Status != "" {
tmp.WriteString(" " + h.Status) w.WriteString(" " + h.Status)
} }
if h.Priority != "" { if h.Priority != "" {
tmp.WriteString(" [#" + h.Priority + "]") w.WriteString(" [#" + h.Priority + "]")
} }
tmp.WriteString(" ") w.WriteString(" ")
WriteNodes(tmp, h.Title...) WriteNodes(w, h.Title...)
hString := tmp.String()
if len(h.Tags) != 0 { if len(h.Tags) != 0 {
tString := ":" + strings.Join(h.Tags, ":") + ":" tString := ":" + strings.Join(h.Tags, ":") + ":"
if n := w.TagsColumn - len(tString) - len(hString); n > 0 { if n := w.TagsColumn - len(tString) - (w.Len() - start); n > 0 {
w.WriteString(hString + strings.Repeat(" ", n) + tString) w.WriteString(strings.Repeat(" ", n) + tString)
} else { } else {
w.WriteString(hString + " " + tString) w.WriteString(" " + tString)
} }
} else {
w.WriteString(hString)
} }
w.WriteString("\n") w.WriteString("\n")
if len(h.Children) != 0 { if len(h.Children) != 0 {
@ -123,7 +117,7 @@ func (w *OrgWriter) WritePropertyDrawer(d PropertyDrawer) {
func (w *OrgWriter) WriteFootnoteDefinition(f FootnoteDefinition) { func (w *OrgWriter) WriteFootnoteDefinition(f FootnoteDefinition) {
w.WriteString(fmt.Sprintf("[fn:%s]", f.Name)) w.WriteString(fmt.Sprintf("[fn:%s]", f.Name))
content := w.nodesAsString(f.Children...) content := w.WriteNodesAsString(f.Children...)
if content != "" && !unicode.IsSpace(rune(content[0])) { if content != "" && !unicode.IsSpace(rune(content[0])) {
w.WriteString(" ") w.WriteString(" ")
} }
@ -131,7 +125,7 @@ func (w *OrgWriter) WriteFootnoteDefinition(f FootnoteDefinition) {
} }
func (w *OrgWriter) WriteParagraph(p Paragraph) { func (w *OrgWriter) WriteParagraph(p Paragraph) {
content := w.nodesAsString(p.Children...) content := w.WriteNodesAsString(p.Children...)
if len(content) > 0 && content[0] != '\n' { if len(content) > 0 && content[0] != '\n' {
w.WriteString(w.indent) w.WriteString(w.indent)
} }
@ -141,7 +135,7 @@ func (w *OrgWriter) WriteParagraph(p Paragraph) {
func (w *OrgWriter) WriteExample(e Example) { func (w *OrgWriter) WriteExample(e Example) {
for _, n := range e.Children { for _, n := range e.Children {
w.WriteString(w.indent + ":") w.WriteString(w.indent + ":")
if content := w.nodesAsString(n); content != "" { if content := w.WriteNodesAsString(n); content != "" {
w.WriteString(" " + content) w.WriteString(" " + content)
} }
w.WriteString("\n") w.WriteString("\n")
@ -185,10 +179,11 @@ func (w *OrgWriter) WriteComment(c Comment) {
func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) } func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) }
func (w *OrgWriter) WriteListItem(li ListItem) { func (w *OrgWriter) WriteListItem(li ListItem) {
liWriter := w.emptyClone() originalBuilder, originalIndent := w.Builder, w.indent
liWriter.indent = w.indent + strings.Repeat(" ", len(li.Bullet)+1) w.Builder, w.indent = strings.Builder{}, w.indent+strings.Repeat(" ", len(li.Bullet)+1)
WriteNodes(liWriter, li.Children...) WriteNodes(w, li.Children...)
content := strings.TrimPrefix(liWriter.String(), liWriter.indent) content := strings.TrimPrefix(w.String(), w.indent)
w.Builder, w.indent = originalBuilder, originalIndent
w.WriteString(w.indent + li.Bullet) w.WriteString(w.indent + li.Bullet)
if li.Status != "" { if li.Status != "" {
w.WriteString(fmt.Sprintf(" [%s]", li.Status)) w.WriteString(fmt.Sprintf(" [%s]", li.Status))
@ -207,14 +202,15 @@ func (w *OrgWriter) WriteDescriptiveListItem(di DescriptiveListItem) {
} }
indent := w.indent + strings.Repeat(" ", len(di.Bullet)+1) indent := w.indent + strings.Repeat(" ", len(di.Bullet)+1)
if len(di.Term) != 0 { if len(di.Term) != 0 {
term := w.nodesAsString(di.Term...) term := w.WriteNodesAsString(di.Term...)
w.WriteString(" " + term + " ::") w.WriteString(" " + term + " ::")
indent = indent + strings.Repeat(" ", len(term)+4) indent = indent + strings.Repeat(" ", len(term)+4)
} }
diWriter := w.emptyClone() originalBuilder, originalIndent := w.Builder, w.indent
diWriter.indent = indent w.Builder, w.indent = strings.Builder{}, indent
WriteNodes(diWriter, di.Details...) WriteNodes(w, di.Details...)
details := strings.TrimPrefix(diWriter.String(), diWriter.indent) details := strings.TrimPrefix(w.String(), w.indent)
w.Builder, w.indent = originalBuilder, originalIndent
if len(details) > 0 && details[0] == '\n' { if len(details) > 0 && details[0] == '\n' {
w.WriteString(details) w.WriteString(details)
} else { } else {
@ -239,7 +235,7 @@ func (w *OrgWriter) WriteTable(t Table) {
w.WriteString(`|`) w.WriteString(`|`)
for _, column := range row.Columns { for _, column := range row.Columns {
w.WriteString(` `) w.WriteString(` `)
content := w.nodesAsString(column.Children...) content := w.WriteNodesAsString(column.Children...)
if content == "" { if content == "" {
content = " " content = " "
} }
@ -326,9 +322,6 @@ func (w *OrgWriter) WriteRegularLink(l RegularLink) {
} else if l.Description == nil { } else if l.Description == nil {
w.WriteString(fmt.Sprintf("[[%s]]", l.URL)) w.WriteString(fmt.Sprintf("[[%s]]", l.URL))
} else { } else {
descriptionWriter := w.emptyClone() w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...)))
WriteNodes(descriptionWriter, l.Description...)
description := descriptionWriter.String()
w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, description))
} }
} }

View file

@ -42,5 +42,5 @@ func (d *Document) parseHorizontalRule(i int, parentStop stopFn) (int, Node) {
return 1, HorizontalRule{} return 1, HorizontalRule{}
} }
func (n Paragraph) String() string { return orgWriter.nodesAsString(n) } func (n Paragraph) String() string { return orgWriter.WriteNodesAsString(n) }
func (n HorizontalRule) String() string { return orgWriter.nodesAsString(n) } func (n HorizontalRule) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -127,4 +127,4 @@ func isSpecialRow(rawColumns []string) bool {
return isAlignRow return isAlignRow
} }
func (n Table) String() string { return orgWriter.nodesAsString(n) } func (n Table) String() string { return orgWriter.WriteNodesAsString(n) }

View file

@ -9,6 +9,7 @@ type Writer interface {
String() string // String is called at the very end to retrieve the final output. String() string // String is called at the very end to retrieve the final output.
WriterWithExtensions() Writer WriterWithExtensions() Writer
WriteNodesAsString(...Node) string
WriteKeyword(Keyword) WriteKeyword(Keyword)
WriteInclude(Include) WriteInclude(Include)

View file

@ -1,6 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. // Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
// +build go1.13 // +build go1.13,!go1.14
package idna package idna

4733
vendor/golang.org/x/net/idna/tables12.00.go generated vendored Normal file

File diff suppressed because it is too large Load diff

4
vendor/modules.txt vendored
View file

@ -330,7 +330,7 @@ github.com/mschoch/smat
github.com/msteinert/pam github.com/msteinert/pam
# github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 # github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
github.com/nfnt/resize github.com/nfnt/resize
# github.com/niklasfasching/go-org v0.1.7 # github.com/niklasfasching/go-org v0.1.8
github.com/niklasfasching/go-org/org github.com/niklasfasching/go-org/org
# github.com/oliamb/cutter v0.2.2 # github.com/oliamb/cutter v0.2.2
github.com/oliamb/cutter github.com/oliamb/cutter
@ -464,7 +464,7 @@ golang.org/x/crypto/scrypt
golang.org/x/crypto/ssh golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/agent golang.org/x/crypto/ssh/agent
golang.org/x/crypto/ssh/knownhosts golang.org/x/crypto/ssh/knownhosts
# golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 # golang.org/x/net v0.0.0-20191101175033-0deb6923b6d9
golang.org/x/net/context golang.org/x/net/context
golang.org/x/net/context/ctxhttp golang.org/x/net/context/ctxhttp
golang.org/x/net/html golang.org/x/net/html