mirror of
https://github.com/go-gitea/gitea.git
synced 2024-11-04 08:17:24 -05:00
merge upstream, resolve migration conflicts
This commit is contained in:
commit
3459547fca
@ -13,16 +13,13 @@ menu:
|
||||
identifier: "linux-service"
|
||||
---
|
||||
|
||||
### Run as service in Ubuntu 16.04 LTS
|
||||
### Run Gitea as Linux service
|
||||
|
||||
You can run Gitea as service, using either systemd or supervisor. The steps below tested on Ubuntu 16.04, but those should work on any Linux distributions (with little modification).
|
||||
|
||||
#### Using systemd
|
||||
|
||||
Run the below command in a terminal:
|
||||
```
|
||||
sudo vim /etc/systemd/system/gitea.service
|
||||
```
|
||||
|
||||
Copy the sample [gitea.service](https://github.com/go-gitea/gitea/blob/master/contrib/systemd/gitea.service).
|
||||
Copy the sample [gitea.service](https://github.com/go-gitea/gitea/blob/master/contrib/systemd/gitea.service) to `/etc/systemd/system/gitea.service`, then edit the file with your favorite editor.
|
||||
|
||||
Uncomment any service that needs to be enabled on this host, such as MySQL.
|
||||
|
||||
@ -35,6 +32,10 @@ sudo systemctl enable gitea
|
||||
sudo systemctl start gitea
|
||||
```
|
||||
|
||||
If you have systemd version 220 or later, you can enable and immediately start Gitea at once by:
|
||||
```
|
||||
sudo systemctl enable gitea --now
|
||||
```
|
||||
|
||||
#### Using supervisor
|
||||
|
||||
@ -49,19 +50,20 @@ Create a log dir for the supervisor logs:
|
||||
mkdir /home/git/gitea/log/supervisor
|
||||
```
|
||||
|
||||
Open supervisor config file in a file editor:
|
||||
```
|
||||
sudo vim /etc/supervisor/supervisord.conf
|
||||
```
|
||||
|
||||
Append the configuration from the sample
|
||||
[supervisord config](https://github.com/go-gitea/gitea/blob/master/contrib/supervisor/gitea).
|
||||
[supervisord config](https://github.com/go-gitea/gitea/blob/master/contrib/supervisor/gitea) to `/etc/supervisor/supervisord.conf`.
|
||||
|
||||
Change the user (git) and home (/home/git) settings to match the deployment
|
||||
environment. Change the PORT or remove the -p flag if default port is used.
|
||||
Using your favorite editor, change the user (git) and home
|
||||
(/home/git) settings to match the deployment environment. Change the PORT
|
||||
or remove the -p flag if default port is used.
|
||||
|
||||
Lastly enable and start supervisor at boot:
|
||||
```
|
||||
sudo systemctl enable supervisor
|
||||
sudo systemctl start supervisor
|
||||
```
|
||||
|
||||
If you have systemd version 220 or later, you can enable and immediately start supervisor by:
|
||||
```
|
||||
sudo systemctl enable supervisor --now
|
||||
```
|
||||
|
@ -44,6 +44,18 @@ func TestAPIGetTrackedTimes(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.Name, apiTimes[i].UserName)
|
||||
}
|
||||
|
||||
// test filter
|
||||
since := "2000-01-01T00%3A00%3A02%2B00%3A00" //946684802
|
||||
before := "2000-01-01T00%3A00%3A12%2B00%3A00" //946684812
|
||||
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?since=%s&before=%s&token=%s", user2.Name, issue2.Repo.Name, issue2.Index, since, before, token)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
var filterAPITimes api.TrackedTimeList
|
||||
DecodeJSON(t, resp, &filterAPITimes)
|
||||
assert.Len(t, filterAPITimes, 2)
|
||||
assert.Equal(t, int64(3), filterAPITimes[0].ID)
|
||||
assert.Equal(t, int64(6), filterAPITimes[1].ID)
|
||||
}
|
||||
|
||||
func TestAPIDeleteTrackedTime(t *testing.T) {
|
||||
|
@ -196,7 +196,7 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]*userAcces
|
||||
if ua.Mode < minMode && !ua.User.IsRestricted {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
newAccesses = append(newAccesses, Access{
|
||||
UserID: userID,
|
||||
RepoID: repo.ID,
|
||||
|
@ -32,21 +32,23 @@ type ProtectedBranch struct {
|
||||
BranchName string `xorm:"UNIQUE(s)"`
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||
EnableWhitelist bool
|
||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
|
||||
StatusCheckContexts []string `xorm:"JSON TEXT"`
|
||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
|
||||
StatusCheckContexts []string `xorm:"JSON TEXT"`
|
||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
// IsProtected returns if the branch is protected
|
||||
@ -155,10 +157,13 @@ func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool {
|
||||
|
||||
// GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist.
|
||||
func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) int64 {
|
||||
approvals, err := x.Where("issue_id = ?", pr.IssueID).
|
||||
sess := x.Where("issue_id = ?", pr.IssueID).
|
||||
And("type = ?", ReviewTypeApprove).
|
||||
And("official = ?", true).
|
||||
Count(new(Review))
|
||||
And("official = ?", true)
|
||||
if protectBranch.DismissStaleApprovals {
|
||||
sess = sess.And("stale = ?", false)
|
||||
}
|
||||
approvals, err := sess.Count(new(Review))
|
||||
if err != nil {
|
||||
log.Error("GetGrantedApprovalsCount: %v", err)
|
||||
return 0
|
||||
|
@ -381,6 +381,7 @@ func (issue *Issue) apiFormat(e Engine) *api.Issue {
|
||||
apiIssue := &api.Issue{
|
||||
ID: issue.ID,
|
||||
URL: issue.APIURL(),
|
||||
HTMLURL: issue.HTMLURL(),
|
||||
Index: issue.Index,
|
||||
Poster: issue.Poster.APIFormat(),
|
||||
Title: issue.Title,
|
||||
|
@ -100,10 +100,12 @@ func (tl TrackedTimeList) APIFormat() api.TrackedTimeList {
|
||||
|
||||
// FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
|
||||
type FindTrackedTimesOptions struct {
|
||||
IssueID int64
|
||||
UserID int64
|
||||
RepositoryID int64
|
||||
MilestoneID int64
|
||||
IssueID int64
|
||||
UserID int64
|
||||
RepositoryID int64
|
||||
MilestoneID int64
|
||||
CreatedAfterUnix int64
|
||||
CreatedBeforeUnix int64
|
||||
}
|
||||
|
||||
// ToCond will convert each condition into a xorm-Cond
|
||||
@ -121,6 +123,12 @@ func (opts *FindTrackedTimesOptions) ToCond() builder.Cond {
|
||||
if opts.MilestoneID != 0 {
|
||||
cond = cond.And(builder.Eq{"issue.milestone_id": opts.MilestoneID})
|
||||
}
|
||||
if opts.CreatedAfterUnix != 0 {
|
||||
cond = cond.And(builder.Gte{"tracked_time.created_unix": opts.CreatedAfterUnix})
|
||||
}
|
||||
if opts.CreatedBeforeUnix != 0 {
|
||||
cond = cond.And(builder.Lte{"tracked_time.created_unix": opts.CreatedBeforeUnix})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
@ -291,6 +291,8 @@ var migrations = []Migration{
|
||||
// v117 -> v118
|
||||
NewMigration("Add block on rejected reviews branch protection", addBlockOnRejectedReviews),
|
||||
// v118 -> v119
|
||||
NewMigration("Add commit id and stale to reviews", addReviewCommitAndStale),
|
||||
// v119 -> v120
|
||||
NewMigration("add is_restricted column for users table", addIsRestricted),
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,23 @@
|
||||
|
||||
package migrations
|
||||
|
||||
import "xorm.io/xorm"
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addIsRestricted(x *xorm.Engine) error {
|
||||
// User see models/user.go
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
func addReviewCommitAndStale(x *xorm.Engine) error {
|
||||
type Review struct {
|
||||
CommitID string `xorm:"VARCHAR(40)"`
|
||||
Stale bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync2(new(User))
|
||||
type ProtectedBranch struct {
|
||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
// Old reviews will have commit ID set to "" and not stale
|
||||
if err := x.Sync2(new(Review)); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.Sync2(new(ProtectedBranch))
|
||||
}
|
||||
|
17
models/migrations/v119.go
Normal file
17
models/migrations/v119.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func addIsRestricted(x *xorm.Engine) error {
|
||||
// User see models/user.go
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync2(new(User))
|
||||
}
|
@ -175,7 +175,11 @@ func (pr *PullRequest) GetDefaultMergeMessage() string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.MustHeadUserName(), pr.HeadRepo.Name, pr.BaseBranch)
|
||||
if err := pr.LoadIssue(); err != nil {
|
||||
log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("Merge pull request '%s' (#%d) from %s/%s into %s", pr.Issue.Title, pr.Issue.Index, pr.MustHeadUserName(), pr.HeadBranch, pr.BaseBranch)
|
||||
}
|
||||
|
||||
// GetCommitMessages returns the commit messages between head and merge base (if there is one)
|
||||
|
@ -124,41 +124,43 @@ func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository,
|
||||
return fmt.Errorf("checkGiteaTemplate: %v", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(gt.Path); err != nil {
|
||||
return fmt.Errorf("remove .giteatemplate: %v", err)
|
||||
}
|
||||
if gt != nil {
|
||||
if err := os.Remove(gt.Path); err != nil {
|
||||
return fmt.Errorf("remove .giteatemplate: %v", err)
|
||||
}
|
||||
|
||||
// Avoid walking tree if there are no globs
|
||||
if len(gt.Globs()) > 0 {
|
||||
tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/"
|
||||
if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash)
|
||||
for _, g := range gt.Globs() {
|
||||
if g.Match(base) {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path,
|
||||
[]byte(generateExpansion(string(content), templateRepo, generateRepo)),
|
||||
0644); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
// Avoid walking tree if there are no globs
|
||||
if len(gt.Globs()) > 0 {
|
||||
tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/"
|
||||
if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash)
|
||||
for _, g := range gt.Globs() {
|
||||
if g.Match(base) {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path,
|
||||
[]byte(generateExpansion(string(content), templateRepo, generateRepo)),
|
||||
0644); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,9 @@ type Review struct {
|
||||
IssueID int64 `xorm:"index"`
|
||||
Content string `xorm:"TEXT"`
|
||||
// Official is a review made by an assigned approver (counts towards approval)
|
||||
Official bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Official bool `xorm:"NOT NULL DEFAULT false"`
|
||||
CommitID string `xorm:"VARCHAR(40)"`
|
||||
Stale bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
@ -169,6 +171,8 @@ type CreateReviewOptions struct {
|
||||
Issue *Issue
|
||||
Reviewer *User
|
||||
Official bool
|
||||
CommitID string
|
||||
Stale bool
|
||||
}
|
||||
|
||||
// IsOfficialReviewer check if reviewer can make official reviews in issue (counts towards required approvals)
|
||||
@ -200,6 +204,8 @@ func createReview(e Engine, opts CreateReviewOptions) (*Review, error) {
|
||||
ReviewerID: opts.Reviewer.ID,
|
||||
Content: opts.Content,
|
||||
Official: opts.Official,
|
||||
CommitID: opts.CommitID,
|
||||
Stale: opts.Stale,
|
||||
}
|
||||
if _, err := e.Insert(review); err != nil {
|
||||
return nil, err
|
||||
@ -258,7 +264,7 @@ func IsContentEmptyErr(err error) bool {
|
||||
}
|
||||
|
||||
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
|
||||
func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content string) (*Review, *Comment, error) {
|
||||
func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool) (*Review, *Comment, error) {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
@ -295,6 +301,8 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content strin
|
||||
Reviewer: doer,
|
||||
Content: content,
|
||||
Official: official,
|
||||
CommitID: commitID,
|
||||
Stale: stale,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -322,8 +330,10 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content strin
|
||||
review.Issue = issue
|
||||
review.Content = content
|
||||
review.Type = reviewType
|
||||
review.CommitID = commitID
|
||||
review.Stale = stale
|
||||
|
||||
if _, err := sess.ID(review.ID).Cols("content, type, official").Update(review); err != nil {
|
||||
if _, err := sess.ID(review.ID).Cols("content, type, official, commit_id, stale").Update(review); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
@ -374,3 +384,17 @@ func GetReviewersByIssueID(issueID int64) (reviews []*Review, err error) {
|
||||
|
||||
return reviews, nil
|
||||
}
|
||||
|
||||
// MarkReviewsAsStale marks existing reviews as stale
|
||||
func MarkReviewsAsStale(issueID int64) (err error) {
|
||||
_, err = x.Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkReviewsAsNotStale marks existing reviews as not stale for a giving commit SHA
|
||||
func MarkReviewsAsNotStale(issueID int64, commitID string) (err error) {
|
||||
_, err = x.Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -172,6 +172,7 @@ type ProtectBranchForm struct {
|
||||
ApprovalsWhitelistUsers string
|
||||
ApprovalsWhitelistTeams string
|
||||
BlockOnRejectedReviews bool
|
||||
DismissStaleApprovals bool
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
@ -456,12 +457,13 @@ func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Error
|
||||
|
||||
// CodeCommentForm form for adding code comments for PRs
|
||||
type CodeCommentForm struct {
|
||||
Content string `binding:"Required"`
|
||||
Side string `binding:"Required;In(previous,proposed)"`
|
||||
Line int64
|
||||
TreePath string `form:"path" binding:"Required"`
|
||||
IsReview bool `form:"is_review"`
|
||||
Reply int64 `form:"reply"`
|
||||
Content string `binding:"Required"`
|
||||
Side string `binding:"Required;In(previous,proposed)"`
|
||||
Line int64
|
||||
TreePath string `form:"path" binding:"Required"`
|
||||
IsReview bool `form:"is_review"`
|
||||
Reply int64 `form:"reply"`
|
||||
LatestCommitID string
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
@ -471,8 +473,9 @@ func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
|
||||
|
||||
// SubmitReviewForm for submitting a finished code review
|
||||
type SubmitReviewForm struct {
|
||||
Content string
|
||||
Type string `binding:"Required;In(approve,comment,reject)"`
|
||||
Content string
|
||||
Type string `binding:"Required;In(approve,comment,reject)"`
|
||||
CommitID string
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
|
@ -112,3 +112,9 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
|
||||
return NewCommand("format-patch", "--binary", "--stdout", base+"..."+head).
|
||||
RunInDirPipeline(repo.Path, w, nil)
|
||||
}
|
||||
|
||||
// GetDiffFromMergeBase generates and return patch data from merge base to head
|
||||
func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
|
||||
return NewCommand("diff", "-p", "--binary", base+"..."+head).
|
||||
RunInDirPipeline(repo.Path, w, nil)
|
||||
}
|
||||
|
@ -477,7 +477,7 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions)
|
||||
|
||||
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
|
||||
|
||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true)
|
||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
|
||||
|
||||
if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil {
|
||||
log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)
|
||||
@ -528,7 +528,7 @@ func PushUpdates(repo *models.Repository, optsList []*PushUpdateOptions) error {
|
||||
|
||||
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, opts.Branch, pusher.Name)
|
||||
|
||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true)
|
||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true, opts.OldCommitID, opts.NewCommitID)
|
||||
|
||||
if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil {
|
||||
log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)
|
||||
|
@ -7,6 +7,7 @@ package setting
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -44,7 +45,7 @@ func GetQueueSettings(name string) QueueSettings {
|
||||
q := QueueSettings{}
|
||||
sec := Cfg.Section("queue." + name)
|
||||
// DataDir is not directly inheritable
|
||||
q.DataDir = path.Join(Queue.DataDir, name)
|
||||
q.DataDir = filepath.Join(Queue.DataDir, name)
|
||||
// QueueName is not directly inheritable either
|
||||
q.QueueName = name + Queue.QueueName
|
||||
for _, key := range sec.Keys() {
|
||||
@ -55,8 +56,8 @@ func GetQueueSettings(name string) QueueSettings {
|
||||
q.QueueName = key.MustString(q.QueueName)
|
||||
}
|
||||
}
|
||||
if !path.IsAbs(q.DataDir) {
|
||||
q.DataDir = path.Join(AppDataPath, q.DataDir)
|
||||
if !filepath.IsAbs(q.DataDir) {
|
||||
q.DataDir = filepath.Join(AppDataPath, q.DataDir)
|
||||
}
|
||||
sec.Key("DATADIR").SetValue(q.DataDir)
|
||||
// The rest are...
|
||||
@ -82,8 +83,8 @@ func GetQueueSettings(name string) QueueSettings {
|
||||
func NewQueueService() {
|
||||
sec := Cfg.Section("queue")
|
||||
Queue.DataDir = sec.Key("DATADIR").MustString("queues/")
|
||||
if !path.IsAbs(Queue.DataDir) {
|
||||
Queue.DataDir = path.Join(AppDataPath, Queue.DataDir)
|
||||
if !filepath.IsAbs(Queue.DataDir) {
|
||||
Queue.DataDir = filepath.Join(AppDataPath, Queue.DataDir)
|
||||
}
|
||||
Queue.Length = sec.Key("LENGTH").MustInt(20)
|
||||
Queue.BatchLength = sec.Key("BATCH_LENGTH").MustInt(20)
|
||||
|
@ -38,6 +38,7 @@ type RepositoryMeta struct {
|
||||
type Issue struct {
|
||||
ID int64 `json:"id"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Index int64 `json:"number"`
|
||||
Poster *User `json:"user"`
|
||||
OriginalAuthor string `json:"original_author"`
|
||||
|
@ -142,7 +142,7 @@ func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) {
|
||||
Title: issueTitle,
|
||||
HideAvatar: "0",
|
||||
SingleTitle: "view issue",
|
||||
SingleURL: p.Issue.URL,
|
||||
SingleURL: p.Issue.HTMLURL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPa
|
||||
{
|
||||
Title: text,
|
||||
Description: attachmentText,
|
||||
URL: p.Issue.URL,
|
||||
URL: p.Issue.HTMLURL,
|
||||
Color: color,
|
||||
Author: DiscordEmbedAuthor{
|
||||
Name: p.Sender.UserName,
|
||||
|
@ -299,7 +299,7 @@ func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) {
|
||||
Targets: []MSTeamsActionTarget{
|
||||
{
|
||||
Os: "default",
|
||||
URI: p.Issue.URL,
|
||||
URI: p.Issue.HTMLURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -158,7 +158,7 @@ func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload
|
||||
pl.Attachments = []SlackAttachment{{
|
||||
Color: fmt.Sprintf("%x", color),
|
||||
Title: issueTitle,
|
||||
TitleLink: p.Issue.URL,
|
||||
TitleLink: p.Issue.HTMLURL,
|
||||
Text: attachmentText,
|
||||
}}
|
||||
}
|
||||
|
@ -148,6 +148,25 @@ func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTelegramPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*TelegramPayload, error) {
|
||||
var text, attachmentText string
|
||||
switch p.Action {
|
||||
case api.HookIssueSynchronized:
|
||||
action, err := parseHookPullRequestEventType(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
|
||||
attachmentText = p.Review.Content
|
||||
|
||||
}
|
||||
|
||||
return &TelegramPayload{
|
||||
Message: text + "\n" + attachmentText,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTelegramRepositoryPayload(p *api.RepositoryPayload) (*TelegramPayload, error) {
|
||||
var title string
|
||||
switch p.Action {
|
||||
@ -192,6 +211,8 @@ func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string
|
||||
return getTelegramPushPayload(p.(*api.PushPayload))
|
||||
case models.HookEventPullRequest:
|
||||
return getTelegramPullRequestPayload(p.(*api.PullRequestPayload))
|
||||
case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
|
||||
return getTelegramPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
|
||||
case models.HookEventRepository:
|
||||
return getTelegramRepositoryPayload(p.(*api.RepositoryPayload))
|
||||
case models.HookEventRelease:
|
||||
|
@ -1413,6 +1413,8 @@ settings.protect_approvals_whitelist_enabled = Restrict approvals to whitelisted
|
||||
settings.protect_approvals_whitelist_enabled_desc = Only reviews from whitelisted users or teams will count to the required approvals. Without approval whitelist, reviews from anyone with write access count to the required approvals.
|
||||
settings.protect_approvals_whitelist_users = Whitelisted reviewers:
|
||||
settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews:
|
||||
settings.dismiss_stale_approvals = Dismiss stale approvals
|
||||
settings.dismiss_stale_approvals_desc = When new commits that change the content of the pull request are pushed to the branch, old approvals will be dismissed.
|
||||
settings.add_protected_branch = Enable protection
|
||||
settings.delete_protected_branch = Disable protection
|
||||
settings.update_protect_branch_success = Branch protection for branch '%s' has been updated.
|
||||
|
@ -1596,7 +1596,7 @@ settings.full_name=Pilns vārds, uzvārds
|
||||
settings.website=Mājas lapa
|
||||
settings.location=Atrašanās vieta
|
||||
settings.permission=Tiesības
|
||||
settings.repoadminchangeteam=Repozitorija administrators var pievienot vain noņemt piekļuvi komandām
|
||||
settings.repoadminchangeteam=Repozitorija administrators var pievienot vai noņemt piekļuvi komandām
|
||||
settings.visibility=Redzamība
|
||||
settings.visibility.public=Publiska
|
||||
settings.visibility.limited=Ierobežota (redzama tikai autorizētiem lietotājiem)
|
||||
@ -2025,8 +2025,54 @@ monitor.execute_time=Izpildes laiks
|
||||
monitor.process.cancel=Atcelt procesu
|
||||
monitor.process.cancel_desc=Procesa atcelšana var radīt datu zaudējumus
|
||||
monitor.process.cancel_notices=Atcelt: <strong>%s</strong>?
|
||||
monitor.queues=Rindas
|
||||
monitor.queue=Rinda: %s
|
||||
monitor.queue.name=Nosaukums
|
||||
monitor.queue.type=Veids
|
||||
monitor.queue.exemplar=Eksemplāra veids
|
||||
monitor.queue.numberworkers=Strādņu skaits
|
||||
monitor.queue.maxnumberworkers=Maksimālais strādņu skaits
|
||||
monitor.queue.review=Pārbaudīt konfigurāciju
|
||||
monitor.queue.review_add=Pārbaudīt/Pievienot strādņus
|
||||
monitor.queue.configuration=Sākotnējā konfigurācija
|
||||
monitor.queue.nopool.title=Nav strādņu pūla
|
||||
monitor.queue.nopool.desc=Šī rinda apvieno citas rindas un tai nav strādņu pūla.
|
||||
monitor.queue.wrapped.desc=Apvienojošā rinda apvieno lēni startējošās rindas, uzkrājot sarindotos pieprasījumus kanālā. Tai nav strādņu pūla.
|
||||
monitor.queue.persistable-channel.desc=Patstāvīgas kanāli apvieno divas rindas, kanāla rindu, kurai ir savs strādņu pūls un līmeņu rindu patstāvīgajiem pieprasījumiem no iepriekšejām izslēgšanām. Tai nav strādņu pūla.
|
||||
monitor.queue.pool.timeout=Noildze
|
||||
monitor.queue.pool.addworkers.title=Pievienot strādņus
|
||||
monitor.queue.pool.addworkers.submit=Pievienot
|
||||
monitor.queue.pool.addworkers.desc=Pievienot strādņus šim pūlam ar vai bez noildzes. Ja uzstādīsies noildzi, tad šie strādņi tiks noņemti no pūla, kad noildze būs iestājusies.
|
||||
monitor.queue.pool.addworkers.numberworkers.placeholder=Strādņu skaits
|
||||
monitor.queue.pool.addworkers.timeout.placeholder=Norādiet 0, lai nebūtu noildzes
|
||||
monitor.queue.pool.addworkers.mustnumbergreaterzero=Strādņu skaitam, ko pievienot, ir jābūt lielākam par nulli
|
||||
monitor.queue.pool.addworkers.musttimeoutduration=Noildzei ir jābūt norādītai kā ilgumam, piemēram, 5m vai 0
|
||||
|
||||
monitor.queue.settings.title=Pūla iestatījumi
|
||||
monitor.queue.settings.desc=Pūli var dinamiski augt un paildzinātu atbildi uz strādņu rindas bloķēšanu. Šis izmaiņas ietekmēs pašreizējās strādņu grupas.
|
||||
monitor.queue.settings.timeout=Pagarināt noildzi
|
||||
monitor.queue.settings.timeout.placeholder=Pašalaik %[1]v
|
||||
monitor.queue.settings.timeout.error=Noildzei ir jābūt norādītai kā ilgumam, piemēram, 5m vai 0
|
||||
monitor.queue.settings.numberworkers=Palielināt strādņu skaitu
|
||||
monitor.queue.settings.numberworkers.placeholder=Pašalaik %[1]d
|
||||
monitor.queue.settings.numberworkers.error=Strādņu skaitam ir jābūt lielākam vai vienādam ar nulli
|
||||
monitor.queue.settings.maxnumberworkers=Maksimālais strādņu skaits
|
||||
monitor.queue.settings.maxnumberworkers.placeholder=Pašalaik %[1]d
|
||||
monitor.queue.settings.maxnumberworkers.error=Maksimālajam strādņu skaitam ir jābūt skaitlim
|
||||
monitor.queue.settings.submit=Saglabāt iestatījumus
|
||||
monitor.queue.settings.changed=Iestatījumi saglabāti
|
||||
monitor.queue.settings.blocktimeout=Pašreizējās grupas noildze
|
||||
monitor.queue.settings.blocktimeout.value=%[1]v
|
||||
|
||||
monitor.queue.pool.none=Rindai nav pūla
|
||||
monitor.queue.pool.added=Strādņu grupa pievienota
|
||||
monitor.queue.pool.max_changed=Maksimālais strādņu skaits mainīts
|
||||
monitor.queue.pool.workers.title=Aktīvās strādņu grupas
|
||||
monitor.queue.pool.workers.none=Nav strādņu grupu.
|
||||
monitor.queue.pool.cancel=Izslēgt strādņu grupu
|
||||
monitor.queue.pool.cancelling=Strādņu grupa tiek izslēgta
|
||||
monitor.queue.pool.cancel_notices=Izslēgt šo grupu ar %s strādņiem?
|
||||
monitor.queue.pool.cancel_desc=Atstājot rindu bez nevienas strādņu grupas, var radīt pieprasījumu bloķēšanos.
|
||||
|
||||
notices.system_notice_list=Sistēmas paziņojumi
|
||||
notices.view_detail_header=Skatīt paziņojuma detaļas
|
||||
|
@ -654,7 +654,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Group("/times", func() {
|
||||
m.Combo("").Get(repo.ListTrackedTimesByRepository)
|
||||
m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser)
|
||||
}, mustEnableIssues)
|
||||
}, mustEnableIssues, reqToken())
|
||||
m.Group("/issues", func() {
|
||||
m.Combo("").Get(repo.ListIssues).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
|
||||
@ -688,12 +688,12 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
|
||||
})
|
||||
m.Group("/times", func() {
|
||||
m.Combo("", reqToken()).
|
||||
m.Combo("").
|
||||
Get(repo.ListTrackedTimes).
|
||||
Post(bind(api.AddTimeOption{}), repo.AddTime).
|
||||
Delete(repo.ResetIssueTime)
|
||||
m.Delete("/:id", reqToken(), repo.DeleteTime)
|
||||
})
|
||||
m.Delete("/:id", repo.DeleteTime)
|
||||
}, reqToken())
|
||||
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
||||
m.Group("/stopwatch", func() {
|
||||
m.Post("/start", reqToken(), repo.StartIssueStopwatch)
|
||||
|
@ -5,12 +5,15 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
)
|
||||
|
||||
// ListTrackedTimes list all the tracked times of an issue
|
||||
@ -37,6 +40,16 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: since
|
||||
// in: query
|
||||
// description: Only show times updated after the given time. This is a timestamp in RFC 3339 format
|
||||
// type: string
|
||||
// format: date-time
|
||||
// - name: before
|
||||
// in: query
|
||||
// description: Only show times updated before the given time. This is a timestamp in RFC 3339 format
|
||||
// type: string
|
||||
// format: date-time
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/TrackedTimeList"
|
||||
@ -62,6 +75,11 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
IssueID: issue.ID,
|
||||
}
|
||||
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsUserRepoAdmin() && !ctx.User.IsAdmin {
|
||||
opts.UserID = ctx.User.ID
|
||||
}
|
||||
@ -141,7 +159,7 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) {
|
||||
//allow only RepoAdmin, Admin and User to add time
|
||||
user, err = models.GetUserByName(form.User)
|
||||
if err != nil {
|
||||
ctx.Error(500, "GetUserByName", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,33 +213,33 @@ func ResetIssueTime(ctx *context.APIContext) {
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/error"
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
if models.IsErrIssueNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.Error(500, "GetIssueByIndex", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanUseTimetracker(issue, ctx.User) {
|
||||
if !ctx.Repo.Repository.IsTimetrackerEnabled() {
|
||||
ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
|
||||
ctx.JSON(http.StatusBadRequest, struct{ Message string }{Message: "time tracking disabled"})
|
||||
return
|
||||
}
|
||||
ctx.Status(403)
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
err = models.DeleteIssueUserTimes(issue, ctx.User)
|
||||
if err != nil {
|
||||
if models.IsErrNotExist(err) {
|
||||
ctx.Error(404, "DeleteIssueUserTimes", err)
|
||||
ctx.Error(http.StatusNotFound, "DeleteIssueUserTimes", err)
|
||||
} else {
|
||||
ctx.Error(500, "DeleteIssueUserTimes", err)
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteIssueUserTimes", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -266,52 +284,53 @@ func DeleteTime(ctx *context.APIContext) {
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/error"
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
if models.IsErrIssueNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.Error(500, "GetIssueByIndex", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanUseTimetracker(issue, ctx.User) {
|
||||
if !ctx.Repo.Repository.IsTimetrackerEnabled() {
|
||||
ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
|
||||
ctx.JSON(http.StatusBadRequest, struct{ Message string }{Message: "time tracking disabled"})
|
||||
return
|
||||
}
|
||||
ctx.Status(403)
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
time, err := models.GetTrackedTimeByID(ctx.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
ctx.Error(500, "GetTrackedTimeByID", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetTrackedTimeByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.User.IsAdmin && time.UserID != ctx.User.ID {
|
||||
//Only Admin and User itself can delete their time
|
||||
ctx.Status(403)
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
err = models.DeleteTime(time)
|
||||
if err != nil {
|
||||
ctx.Error(500, "DeleteTime", err)
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteTime", err)
|
||||
return
|
||||
}
|
||||
ctx.Status(204)
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ListTrackedTimesByUser lists all tracked times of the user
|
||||
func ListTrackedTimesByUser(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/times/{user} user userTrackedTimes
|
||||
// swagger:operation GET /repos/{owner}/{repo}/times/{user} repository userTrackedTimes
|
||||
// ---
|
||||
// summary: List a user's tracked times in a repo
|
||||
// deprecated: true
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@ -335,6 +354,8 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/TrackedTimeList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
if !ctx.Repo.Repository.IsTimetrackerEnabled() {
|
||||
ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
|
||||
@ -353,9 +374,23 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{
|
||||
|
||||
if !ctx.IsUserRepoAdmin() && !ctx.User.IsAdmin && ctx.User.ID != user.ID {
|
||||
ctx.Error(http.StatusForbidden, "", fmt.Errorf("query user not allowed not enouth rights"))
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsUserRepoAdmin() && !ctx.User.IsAdmin && ctx.User.ID != user.ID {
|
||||
ctx.Error(http.StatusForbidden, "", fmt.Errorf("query user not allowed not enouth rights"))
|
||||
return
|
||||
}
|
||||
|
||||
opts := models.FindTrackedTimesOptions{
|
||||
UserID: user.ID,
|
||||
RepositoryID: ctx.Repo.Repository.ID})
|
||||
RepositoryID: ctx.Repo.Repository.ID,
|
||||
}
|
||||
|
||||
trackedTimes, err := models.GetTrackedTimes(opts)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err)
|
||||
return
|
||||
@ -385,11 +420,27 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: user
|
||||
// in: query
|
||||
// description: optional filter by user
|
||||
// type: string
|
||||
// - name: since
|
||||
// in: query
|
||||
// description: Only show times updated after the given time. This is a timestamp in RFC 3339 format
|
||||
// type: string
|
||||
// format: date-time
|
||||
// - name: before
|
||||
// in: query
|
||||
// description: Only show times updated before the given time. This is a timestamp in RFC 3339 format
|
||||
// type: string
|
||||
// format: date-time
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/TrackedTimeList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
if !ctx.Repo.Repository.IsTimetrackerEnabled() {
|
||||
ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
|
||||
@ -400,8 +451,30 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
RepositoryID: ctx.Repo.Repository.ID,
|
||||
}
|
||||
|
||||
// Filters
|
||||
qUser := strings.Trim(ctx.Query("user"), " ")
|
||||
if qUser != "" {
|
||||
user, err := models.GetUserByName(qUser)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
||||
return
|
||||
}
|
||||
opts.UserID = user.ID
|
||||
}
|
||||
|
||||
var err error
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsUserRepoAdmin() && !ctx.User.IsAdmin {
|
||||
opts.UserID = ctx.User.ID
|
||||
if opts.UserID == 0 {
|
||||
opts.UserID = ctx.User.ID
|
||||
} else {
|
||||
ctx.Error(http.StatusForbidden, "", fmt.Errorf("query user not allowed not enouth rights"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
trackedTimes, err := models.GetTrackedTimes(opts)
|
||||
@ -423,18 +496,39 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
|
||||
// summary: List the current user's tracked times
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: since
|
||||
// in: query
|
||||
// description: Only show times updated after the given time. This is a timestamp in RFC 3339 format
|
||||
// type: string
|
||||
// format: date-time
|
||||
// - name: before
|
||||
// in: query
|
||||
// description: Only show times updated before the given time. This is a timestamp in RFC 3339 format
|
||||
// type: string
|
||||
// format: date-time
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/TrackedTimeList"
|
||||
|
||||
trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: ctx.User.ID})
|
||||
opts := models.FindTrackedTimesOptions{UserID: ctx.User.ID}
|
||||
|
||||
var err error
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
trackedTimes, err := models.GetTrackedTimes(opts)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetTrackedTimesByUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = trackedTimes.LoadAttributes(); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, trackedTimes.APIFormat())
|
||||
}
|
||||
|
@ -4,7 +4,12 @@
|
||||
|
||||
package utils
|
||||
|
||||
import "code.gitea.io/gitea/modules/context"
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
)
|
||||
|
||||
// UserID user ID of authenticated user, or 0 if not authenticated
|
||||
func UserID(ctx *context.APIContext) int64 {
|
||||
@ -13,3 +18,29 @@ func UserID(ctx *context.APIContext) int64 {
|
||||
}
|
||||
return ctx.User.ID
|
||||
}
|
||||
|
||||
// GetQueryBeforeSince return parsed time (unix format) from URL query's before and since
|
||||
func GetQueryBeforeSince(ctx *context.APIContext) (before, since int64, err error) {
|
||||
qCreatedBefore := strings.Trim(ctx.Query("before"), " ")
|
||||
if qCreatedBefore != "" {
|
||||
createdBefore, err := time.Parse(time.RFC3339, qCreatedBefore)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if !createdBefore.IsZero() {
|
||||
before = createdBefore.Unix()
|
||||
}
|
||||
}
|
||||
|
||||
qCreatedAfter := strings.Trim(ctx.Query("since"), " ")
|
||||
if qCreatedAfter != "" {
|
||||
createdAfter, err := time.Parse(time.RFC3339, qCreatedAfter)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if !createdAfter.IsZero() {
|
||||
since = createdAfter.Unix()
|
||||
}
|
||||
}
|
||||
return before, since, nil
|
||||
}
|
||||
|
@ -841,7 +841,7 @@ func TriggerTask(ctx *context.Context) {
|
||||
|
||||
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
|
||||
|
||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true)
|
||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, "", "")
|
||||
ctx.Status(202)
|
||||
}
|
||||
|
||||
|
@ -37,12 +37,14 @@ func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) {
|
||||
|
||||
comment, err := pull_service.CreateCodeComment(
|
||||
ctx.User,
|
||||
ctx.Repo.GitRepo,
|
||||
issue,
|
||||
signedLine,
|
||||
form.Content,
|
||||
form.TreePath,
|
||||
form.IsReview,
|
||||
form.Reply,
|
||||
form.LatestCommitID,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.ServerError("CreateCodeComment", err)
|
||||
@ -95,7 +97,7 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
|
||||
}
|
||||
}
|
||||
|
||||
_, comm, err := pull_service.SubmitReview(ctx.User, issue, reviewType, form.Content)
|
||||
_, comm, err := pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, issue, reviewType, form.Content, form.CommitID)
|
||||
if err != nil {
|
||||
if models.IsContentEmptyErr(err) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty"))
|
||||
|
@ -245,6 +245,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm)
|
||||
}
|
||||
}
|
||||
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
|
||||
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
|
||||
|
||||
err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
|
||||
UserIDs: whitelistUsers,
|
||||
|
@ -64,7 +64,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
|
||||
}
|
||||
|
||||
defer func() {
|
||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
|
||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||
}()
|
||||
|
||||
// Clone base repo.
|
||||
|
@ -5,10 +5,14 @@
|
||||
package pull
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
@ -16,6 +20,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/notification"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
)
|
||||
|
||||
// NewPullRequest creates new pull request with labels for repository.
|
||||
@ -168,7 +174,7 @@ func addHeadRepoTasks(prs []*models.PullRequest) {
|
||||
|
||||
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
|
||||
// and generate new patch for testing as needed.
|
||||
func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSync bool) {
|
||||
func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
|
||||
log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
|
||||
graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
|
||||
// There is no sensible way to shut this down ":-("
|
||||
@ -191,6 +197,22 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy
|
||||
}
|
||||
if err == nil {
|
||||
for _, pr := range prs {
|
||||
if newCommitID != "" && newCommitID != git.EmptySHA {
|
||||
changed, err := checkIfPRContentChanged(pr, oldCommitID, newCommitID)
|
||||
if err != nil {
|
||||
log.Error("checkIfPRContentChanged: %v", err)
|
||||
}
|
||||
if changed {
|
||||
// Mark old reviews as stale if diff to mergebase has changed
|
||||
if err := models.MarkReviewsAsStale(pr.IssueID); err != nil {
|
||||
log.Error("MarkReviewsAsStale: %v", err)
|
||||
}
|
||||
}
|
||||
if err := models.MarkReviewsAsNotStale(pr.IssueID, newCommitID); err != nil {
|
||||
log.Error("MarkReviewsAsNotStale: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
pr.Issue.PullRequest = pr
|
||||
notification.NotifyPullRequestSynchronized(doer, pr)
|
||||
}
|
||||
@ -211,6 +233,78 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy
|
||||
})
|
||||
}
|
||||
|
||||
// checkIfPRContentChanged checks if diff to target branch has changed by push
|
||||
// A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
|
||||
func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
|
||||
|
||||
if err = pr.GetHeadRepo(); err != nil {
|
||||
return false, fmt.Errorf("GetHeadRepo: %v", err)
|
||||
} else if pr.HeadRepo == nil {
|
||||
// corrupt data assumed changed
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err = pr.GetBaseRepo(); err != nil {
|
||||
return false, fmt.Errorf("GetBaseRepo: %v", err)
|
||||
}
|
||||
|
||||
headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
|
||||
// Add a temporary remote.
|
||||
tmpRemote := "checkIfPRContentChanged-" + com.ToStr(time.Now().UnixNano())
|
||||
if err = headGitRepo.AddRemote(tmpRemote, models.RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name), true); err != nil {
|
||||
return false, fmt.Errorf("AddRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
|
||||
log.Error("checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
|
||||
}
|
||||
}()
|
||||
// To synchronize repo and get a base ref
|
||||
_, base, err := headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("GetMergeBase: %v", err)
|
||||
}
|
||||
|
||||
diffBefore := &bytes.Buffer{}
|
||||
diffAfter := &bytes.Buffer{}
|
||||
if err := headGitRepo.GetDiffFromMergeBase(base, oldCommitID, diffBefore); err != nil {
|
||||
// If old commit not found, assume changed.
|
||||
log.Debug("GetDiffFromMergeBase: %v", err)
|
||||
return true, nil
|
||||
}
|
||||
if err := headGitRepo.GetDiffFromMergeBase(base, newCommitID, diffAfter); err != nil {
|
||||
// New commit should be found
|
||||
return false, fmt.Errorf("GetDiffFromMergeBase: %v", err)
|
||||
}
|
||||
|
||||
diffBeforeLines := bufio.NewScanner(diffBefore)
|
||||
diffAfterLines := bufio.NewScanner(diffAfter)
|
||||
|
||||
for diffBeforeLines.Scan() && diffAfterLines.Scan() {
|
||||
if strings.HasPrefix(diffBeforeLines.Text(), "index") && strings.HasPrefix(diffAfterLines.Text(), "index") {
|
||||
// file hashes can change without the diff changing
|
||||
continue
|
||||
} else if strings.HasPrefix(diffBeforeLines.Text(), "@@") && strings.HasPrefix(diffAfterLines.Text(), "@@") {
|
||||
// the location of the difference may change
|
||||
continue
|
||||
} else if !bytes.Equal(diffBeforeLines.Bytes(), diffAfterLines.Bytes()) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if diffBeforeLines.Scan() || diffAfterLines.Scan() {
|
||||
// Diffs not of equal length
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// PushToBaseRepo pushes commits from branches of head repository to
|
||||
// corresponding branches of base repository.
|
||||
// FIXME: Only push branches that are actually updates?
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// CreateCodeComment creates a comment on the code line
|
||||
func CreateCodeComment(doer *models.User, issue *models.Issue, line int64, content string, treePath string, isReview bool, replyReviewID int64) (*models.Comment, error) {
|
||||
func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models.Issue, line int64, content string, treePath string, isReview bool, replyReviewID int64, latestCommitID string) (*models.Comment, error) {
|
||||
|
||||
var (
|
||||
existsReview bool
|
||||
@ -73,6 +73,7 @@ func CreateCodeComment(doer *models.User, issue *models.Issue, line int64, conte
|
||||
Reviewer: doer,
|
||||
Issue: issue,
|
||||
Official: false,
|
||||
CommitID: latestCommitID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -94,7 +95,7 @@ func CreateCodeComment(doer *models.User, issue *models.Issue, line int64, conte
|
||||
|
||||
if !isReview && !existsReview {
|
||||
// Submit the review we've just created so the comment shows up in the issue view
|
||||
if _, _, err = SubmitReview(doer, issue, models.ReviewTypeComment, ""); err != nil {
|
||||
if _, _, err = SubmitReview(doer, gitRepo, issue, models.ReviewTypeComment, "", latestCommitID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -159,16 +160,36 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models
|
||||
}
|
||||
|
||||
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
|
||||
func SubmitReview(doer *models.User, issue *models.Issue, reviewType models.ReviewType, content string) (*models.Review, *models.Comment, error) {
|
||||
review, comm, err := models.SubmitReview(doer, issue, reviewType, content)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
func SubmitReview(doer *models.User, gitRepo *git.Repository, issue *models.Issue, reviewType models.ReviewType, content, commitID string) (*models.Review, *models.Comment, error) {
|
||||
pr, err := issue.GetPullRequest()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var stale bool
|
||||
if reviewType != models.ReviewTypeApprove && reviewType != models.ReviewTypeReject {
|
||||
stale = false
|
||||
} else {
|
||||
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if headCommitID == commitID {
|
||||
stale = false
|
||||
} else {
|
||||
stale, err = checkIfPRContentChanged(pr, commitID, headCommitID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
review, comm, err := models.SubmitReview(doer, issue, reviewType, content, commitID, stale)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
notification.NotifyPullRequestReview(pr, review, comm)
|
||||
|
||||
return review, comm, nil
|
||||
|
@ -4,6 +4,7 @@
|
||||
{{end}}
|
||||
<form class="ui form {{if $.hidden}}hide comment-form comment-form-reply{{end}}" action="{{$.root.Issue.HTMLURL}}/files/reviews/comments" method="post">
|
||||
{{$.root.CsrfTokenHtml}}
|
||||
<input type="hidden" name="latest_commit_id" value="{{$.root.AfterCommitID}}"/>
|
||||
<input type="hidden" name="side" value="{{if $.Side}}{{$.Side}}{{end}}">
|
||||
<input type="hidden" name="line" value="{{if $.Line}}{{$.Line}}{{end}}">
|
||||
<input type="hidden" name="path" value="{{if $.File}}{{$.File}}{{end}}">
|
||||
|
@ -7,6 +7,7 @@
|
||||
<div class="ui clearing segment">
|
||||
<form class="ui form" action="{{.Link}}/reviews/submit" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="commit_id" value="{{.AfterCommitID}}"/>
|
||||
<i class="ui right floated link icon close"></i>
|
||||
<div class="header">
|
||||
{{$.i18n.Tr "repo.diff.review.header"}}
|
||||
|
@ -13,6 +13,11 @@
|
||||
{{else}}grey{{end}}">
|
||||
<span class="octicon octicon-{{.Type.Icon}}"></span>
|
||||
</span>
|
||||
{{if .Stale}}
|
||||
<span class="type-icon text grey">
|
||||
<i class="octicon icon fa-hourglass-end"></i>
|
||||
</span>
|
||||
{{end}}
|
||||
<a class="ui avatar image" href="{{.Reviewer.HomeLink}}">
|
||||
<img src="{{.Reviewer.RelAvatarLink}}">
|
||||
</a>
|
||||
|
@ -211,6 +211,14 @@
|
||||
<p class="help">{{.i18n.Tr "repo.settings.block_rejected_reviews_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="dismiss_stale_approvals" type="checkbox" {{if .Branch.DismissStaleApprovals}}checked{{end}}>
|
||||
<label for="dismiss_stale_approvals">{{.i18n.Tr "repo.settings.dismiss_stale_approvals"}}</label>
|
||||
<p class="help">{{.i18n.Tr "repo.settings.dismiss_stale_approvals_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
@ -4433,6 +4433,20 @@
|
||||
"name": "index",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show times updated after the given time. This is a timestamp in RFC 3339 format",
|
||||
"name": "since",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show times updated before the given time. This is a timestamp in RFC 3339 format",
|
||||
"name": "before",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -4543,7 +4557,7 @@
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/error"
|
||||
"$ref": "#/responses/forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4601,7 +4615,7 @@
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/error"
|
||||
"$ref": "#/responses/forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6419,6 +6433,26 @@
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "optional filter by user",
|
||||
"name": "user",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show times updated after the given time. This is a timestamp in RFC 3339 format",
|
||||
"name": "since",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show times updated before the given time. This is a timestamp in RFC 3339 format",
|
||||
"name": "before",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -6427,6 +6461,9 @@
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6437,10 +6474,11 @@
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
"repository"
|
||||
],
|
||||
"summary": "List a user's tracked times in a repo",
|
||||
"operationId": "userTrackedTimes",
|
||||
"deprecated": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
@ -6470,6 +6508,9 @@
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7685,6 +7726,22 @@
|
||||
],
|
||||
"summary": "List the current user's tracked times",
|
||||
"operationId": "userCurrentTrackedTimes",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show times updated after the given time. This is a timestamp in RFC 3339 format",
|
||||
"name": "since",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show times updated before the given time. This is a timestamp in RFC 3339 format",
|
||||
"name": "before",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/TrackedTimeList"
|
||||
@ -10248,6 +10305,10 @@
|
||||
"format": "date-time",
|
||||
"x-go-name": "Deadline"
|
||||
},
|
||||
"html_url": {
|
||||
"type": "string",
|
||||
"x-go-name": "HTMLURL"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
|
Loading…
Reference in New Issue
Block a user