mirror of
https://github.com/go-gitea/gitea.git
synced 2024-09-01 14:56:30 +00:00
Merge branch 'main' into pacman-packages
This commit is contained in:
commit
0b7f813049
2
.github/workflows/release-nightly.yml
vendored
2
.github/workflows/release-nightly.yml
vendored
@ -47,7 +47,7 @@ jobs:
|
||||
run: |
|
||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||
echo "Cleaned name is ${REF_NAME}"
|
||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||
- name: configure aws
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Build stage
|
||||
FROM docker.io/library/golang:1.22-alpine3.19 AS build-env
|
||||
FROM docker.io/library/golang:1.22-alpine3.20 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM docker.io/library/alpine:3.19
|
||||
FROM docker.io/library/alpine:3.20
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 22 3000
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Build stage
|
||||
FROM docker.io/library/golang:1.22-alpine3.19 AS build-env
|
||||
FROM docker.io/library/golang:1.22-alpine3.20 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM docker.io/library/alpine:3.19
|
||||
FROM docker.io/library/alpine:3.20
|
||||
LABEL maintainer="maintainers@gitea.io"
|
||||
|
||||
EXPOSE 2222 3000
|
||||
|
2
Makefile
2
Makefile
@ -88,7 +88,7 @@ ifneq ($(GITHUB_REF_TYPE),branch)
|
||||
GITEA_VERSION ?= $(VERSION)
|
||||
else
|
||||
ifneq ($(GITHUB_REF_NAME),)
|
||||
VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))
|
||||
VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))-nightly
|
||||
else
|
||||
VERSION ?= main
|
||||
endif
|
||||
|
@ -2036,6 +2036,17 @@ LEVEL = Info
|
||||
;; or only create new users if UPDATE_EXISTING is set to false
|
||||
;UPDATE_EXISTING = true
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Cleanup expired actions assets
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[cron.cleanup_actions]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;ENABLED = true
|
||||
;RUN_AT_START = true
|
||||
;SCHEDULE = @midnight
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Clean-up deleted branches
|
||||
|
@ -975,12 +975,20 @@ Default templates for project boards:
|
||||
- `SCHEDULE`: **@midnight** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts.
|
||||
- `UPDATE_EXISTING`: **true**: Create new users, update existing user data and disable users that are not in external source anymore (default) or only create new users if UPDATE_EXISTING is set to false.
|
||||
|
||||
## Cron - Cleanup Expired Actions Assets (`cron.cleanup_actions`)
|
||||
#### Cron - Cleanup Expired Actions Assets (`cron.cleanup_actions`)
|
||||
|
||||
- `ENABLED`: **true**: Enable cleanup expired actions assets job.
|
||||
- `RUN_AT_START`: **true**: Run job at start time (if ENABLED).
|
||||
- `SCHEDULE`: **@midnight** : Cron syntax for the job.
|
||||
|
||||
#### Cron - Cleanup Deleted Branches (`cron.deleted_branches_cleanup`)
|
||||
|
||||
- `ENABLED`: **true**: Enable deleted branches cleanup.
|
||||
- `RUN_AT_START`: **true**: Run job at start time (if ENABLED).
|
||||
- `NOTICE_ON_SUCCESS`: **false**: Set to true to log a success message.
|
||||
- `SCHEDULE`: **@midnight**: Cron syntax for scheduling deleted branches cleanup.
|
||||
- `OLDER_THAN`: **24h**: Branches deleted OLDER_THAN ago will be cleaned up.
|
||||
|
||||
### Extended cron tasks (not enabled by default)
|
||||
|
||||
#### Cron - Garbage collect all repositories (`cron.git_gc_repos`)
|
||||
|
@ -108,6 +108,10 @@ See [Creating an annotation for an error](https://docs.github.com/en/actions/usi
|
||||
|
||||
It's ignored by Gitea Actions now.
|
||||
|
||||
### Expressions
|
||||
|
||||
For [expressions](https://docs.github.com/en/actions/learn-github-actions/expressions), only [`always()`](https://docs.github.com/en/actions/learn-github-actions/expressions#always) is supported.
|
||||
|
||||
## Missing UI features
|
||||
|
||||
### Pre and Post steps
|
||||
|
@ -108,6 +108,10 @@ Gitea Actions目前不支持此功能。
|
||||
|
||||
Gitea Actions目前不支持此功能。
|
||||
|
||||
### 表达式
|
||||
|
||||
对于 [表达式](https://docs.github.com/en/actions/learn-github-actions/expressions), 当前仅 [`always()`](https://docs.github.com/en/actions/learn-github-actions/expressions#always) 被支持。
|
||||
|
||||
## 缺失的UI功能
|
||||
|
||||
### 预处理和后处理步骤
|
||||
|
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1715534503,
|
||||
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
37
flake.nix
Normal file
37
flake.nix
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
outputs =
|
||||
{ nixpkgs, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
# generic
|
||||
git
|
||||
git-lfs
|
||||
gnumake
|
||||
gnused
|
||||
gnutar
|
||||
gzip
|
||||
|
||||
# frontend
|
||||
nodejs_20
|
||||
|
||||
# linting
|
||||
python312
|
||||
poetry
|
||||
|
||||
# backend
|
||||
go_1_22
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
@ -45,3 +45,39 @@
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 5
|
||||
repo_id: 10
|
||||
name: 'master'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 6
|
||||
repo_id: 10
|
||||
name: 'outdated-new-branch'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 14
|
||||
repo_id: 11
|
||||
name: 'master'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 13
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
@ -1,27 +1,35 @@
|
||||
-
|
||||
group_id: 1
|
||||
max_index: 5
|
||||
|
||||
-
|
||||
group_id: 2
|
||||
max_index: 2
|
||||
|
||||
-
|
||||
group_id: 3
|
||||
max_index: 2
|
||||
|
||||
-
|
||||
group_id: 10
|
||||
max_index: 1
|
||||
|
||||
-
|
||||
group_id: 32
|
||||
max_index: 2
|
||||
|
||||
-
|
||||
group_id: 48
|
||||
max_index: 1
|
||||
|
||||
-
|
||||
group_id: 42
|
||||
max_index: 1
|
||||
|
||||
-
|
||||
group_id: 50
|
||||
max_index: 1
|
||||
|
||||
-
|
||||
group_id: 51
|
||||
max_index: 1
|
||||
|
@ -117,3 +117,15 @@
|
||||
uid: 40
|
||||
org_id: 41
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 21
|
||||
uid: 12
|
||||
org_id: 25
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 22
|
||||
uid: 2
|
||||
org_id: 35
|
||||
is_public: true
|
||||
|
@ -327,7 +327,7 @@
|
||||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: false
|
||||
is_fork: true
|
||||
fork_id: 10
|
||||
is_template: false
|
||||
template_id: 0
|
||||
|
@ -239,3 +239,25 @@
|
||||
num_members: 2
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: false
|
||||
|
||||
-
|
||||
id: 23
|
||||
org_id: 25
|
||||
lower_name: owners
|
||||
name: Owners
|
||||
authorize: 4 # owner
|
||||
num_repos: 0
|
||||
num_members: 1
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: true
|
||||
|
||||
-
|
||||
id: 24
|
||||
org_id: 35
|
||||
lower_name: team24
|
||||
name: team24
|
||||
authorize: 2 # write
|
||||
num_repos: 0
|
||||
num_members: 1
|
||||
includes_all_repositories: true
|
||||
can_create_org_repo: false
|
||||
|
@ -322,3 +322,21 @@
|
||||
team_id: 22
|
||||
type: 3
|
||||
access_mode: 1
|
||||
|
||||
-
|
||||
id: 55
|
||||
team_id: 18
|
||||
type: 1 # code
|
||||
access_mode: 4
|
||||
|
||||
-
|
||||
id: 56
|
||||
team_id: 23
|
||||
type: 1 # code
|
||||
access_mode: 4
|
||||
|
||||
-
|
||||
id: 57
|
||||
team_id: 24
|
||||
type: 1 # code
|
||||
access_mode: 2
|
||||
|
@ -147,3 +147,15 @@
|
||||
org_id: 41
|
||||
team_id: 22
|
||||
uid: 39
|
||||
|
||||
-
|
||||
id: 26
|
||||
org_id: 25
|
||||
team_id: 23
|
||||
uid: 12
|
||||
|
||||
-
|
||||
id: 27
|
||||
org_id: 35
|
||||
team_id: 24
|
||||
uid: 2
|
||||
|
@ -918,8 +918,8 @@
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 1
|
||||
num_members: 1
|
||||
num_teams: 2
|
||||
num_members: 2
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
@ -1289,8 +1289,8 @@
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 1
|
||||
num_members: 1
|
||||
num_teams: 2
|
||||
num_members: 2
|
||||
visibility: 2
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
|
@ -10,9 +10,11 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -102,8 +104,9 @@ func (err ErrBranchesEqual) Unwrap() error {
|
||||
// for pagination, keyword search and filtering
|
||||
type Branch struct {
|
||||
ID int64
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
|
||||
CommitID string
|
||||
CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line)
|
||||
PusherID int64
|
||||
@ -139,6 +142,14 @@ func (b *Branch) LoadPusher(ctx context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Branch) LoadRepo(ctx context.Context) (err error) {
|
||||
if b.Repo != nil || b.RepoID == 0 {
|
||||
return nil
|
||||
}
|
||||
b.Repo, err = repo_model.GetRepositoryByID(ctx, b.RepoID)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(Branch))
|
||||
db.RegisterModel(new(RenamedBranch))
|
||||
@ -400,24 +411,111 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created
|
||||
// except the indicate branch
|
||||
func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) {
|
||||
branches := make(BranchList, 0, 2)
|
||||
subQuery := builder.Select("head_branch").From("pull_request").
|
||||
InnerJoin("issue", "issue.id = pull_request.issue_id").
|
||||
Where(builder.Eq{
|
||||
"pull_request.head_repo_id": repoID,
|
||||
"issue.is_closed": false,
|
||||
})
|
||||
err := db.GetEngine(ctx).
|
||||
Where("pusher_id=? AND is_deleted=?", userID, false).
|
||||
And("name <> ?", excludeBranchName).
|
||||
And("repo_id = ?", repoID).
|
||||
And("commit_time >= ?", time.Now().Add(-time.Hour*6).Unix()).
|
||||
NotIn("name", subQuery).
|
||||
OrderBy("branch.commit_time DESC").
|
||||
Limit(2).
|
||||
Find(&branches)
|
||||
return branches, err
|
||||
type FindRecentlyPushedNewBranchesOptions struct {
|
||||
Repo *repo_model.Repository
|
||||
BaseRepo *repo_model.Repository
|
||||
CommitAfterUnix int64
|
||||
MaxCount int
|
||||
}
|
||||
|
||||
type RecentlyPushedNewBranch struct {
|
||||
BranchDisplayName string
|
||||
BranchLink string
|
||||
BranchCompareURL string
|
||||
CommitTime timeutil.TimeStamp
|
||||
}
|
||||
|
||||
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 2 hours which has no opened PRs created
|
||||
// if opts.CommitAfterUnix is 0, we will find the branches that were committed to in the last 2 hours
|
||||
// if opts.ListOptions is not set, we will only display top 2 latest branch
|
||||
func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, opts *FindRecentlyPushedNewBranchesOptions) ([]*RecentlyPushedNewBranch, error) {
|
||||
if doer == nil {
|
||||
return []*RecentlyPushedNewBranch{}, nil
|
||||
}
|
||||
|
||||
// find all related repo ids
|
||||
repoOpts := repo_model.SearchRepoOptions{
|
||||
Actor: doer,
|
||||
Private: true,
|
||||
AllPublic: false, // Include also all public repositories of users and public organisations
|
||||
AllLimited: false, // Include also all public repositories of limited organisations
|
||||
Fork: optional.Some(true),
|
||||
ForkFrom: opts.BaseRepo.ID,
|
||||
Archived: optional.Some(false),
|
||||
}
|
||||
repoCond := repo_model.SearchRepositoryCondition(&repoOpts).And(repo_model.AccessibleRepositoryCondition(doer, unit.TypeCode))
|
||||
if opts.Repo.ID == opts.BaseRepo.ID {
|
||||
// should also include the base repo's branches
|
||||
repoCond = repoCond.Or(builder.Eq{"id": opts.BaseRepo.ID})
|
||||
} else {
|
||||
// in fork repo, we only detect the fork repo's branch
|
||||
repoCond = repoCond.And(builder.Eq{"id": opts.Repo.ID})
|
||||
}
|
||||
repoIDs := builder.Select("id").From("repository").Where(repoCond)
|
||||
|
||||
if opts.CommitAfterUnix == 0 {
|
||||
opts.CommitAfterUnix = time.Now().Add(-time.Hour * 2).Unix()
|
||||
}
|
||||
|
||||
baseBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// find all related branches, these branches may already created PRs, we will check later
|
||||
var branches []*Branch
|
||||
if err := db.GetEngine(ctx).
|
||||
Where(builder.And(
|
||||
builder.Eq{
|
||||
"pusher_id": doer.ID,
|
||||
"is_deleted": false,
|
||||
},
|
||||
builder.Gte{"commit_time": opts.CommitAfterUnix},
|
||||
builder.In("repo_id", repoIDs),
|
||||
// newly created branch have no changes, so skip them
|
||||
builder.Neq{"commit_id": baseBranch.CommitID},
|
||||
)).
|
||||
OrderBy(db.SearchOrderByRecentUpdated.String()).
|
||||
Find(&branches); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newBranches := make([]*RecentlyPushedNewBranch, 0, len(branches))
|
||||
if opts.MaxCount == 0 {
|
||||
// by default we display 2 recently pushed new branch
|
||||
opts.MaxCount = 2
|
||||
}
|
||||
for _, branch := range branches {
|
||||
// whether branch have already created PR
|
||||
count, err := db.GetEngine(ctx).Table("pull_request").
|
||||
// we should not only use branch name here, because if there are branches with same name in other repos,
|
||||
// we can not detect them correctly
|
||||
Where(builder.Eq{"head_repo_id": branch.RepoID, "head_branch": branch.Name}).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if no PR, we add to the result
|
||||
if count == 0 {
|
||||
if err := branch.LoadRepo(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
branchDisplayName := branch.Name
|
||||
if branch.Repo.ID != opts.BaseRepo.ID && branch.Repo.ID != opts.Repo.ID {
|
||||
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
||||
}
|
||||
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
||||
BranchDisplayName: branchDisplayName,
|
||||
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||
CommitTime: branch.CommitTime,
|
||||
})
|
||||
}
|
||||
if len(newBranches) == opts.MaxCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return newBranches, nil
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
@ -59,6 +60,24 @@ func (branches BranchList) LoadPusher(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (branches BranchList) LoadRepo(ctx context.Context) error {
|
||||
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
|
||||
return branch.RepoID, branch.RepoID > 0 && branch.Repo == nil
|
||||
})
|
||||
|
||||
reposMap := make(map[int64]*repo_model.Repository, len(ids))
|
||||
if err := db.GetEngine(ctx).In("id", ids).Find(&reposMap); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, branch := range branches {
|
||||
if branch.RepoID <= 0 || branch.Repo != nil {
|
||||
continue
|
||||
}
|
||||
branch.Repo = reposMap[branch.RepoID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindBranchOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
|
@ -430,6 +430,21 @@ func (pr *PullRequest) GetGitHeadBranchRefName() string {
|
||||
return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
|
||||
}
|
||||
|
||||
// GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||
func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int {
|
||||
opts := FindCommentsOptions{
|
||||
Type: CommentTypeReview,
|
||||
IssueID: pr.IssueID,
|
||||
}
|
||||
conds := opts.ToConds()
|
||||
|
||||
count, err := db.GetEngine(ctx).Where(conds).Count(new(Comment))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int(count)
|
||||
}
|
||||
|
||||
// IsChecking returns true if this pull request is still checking conflict.
|
||||
func (pr *PullRequest) IsChecking() bool {
|
||||
return pr.Status == PullRequestStatusChecking
|
||||
|
@ -155,14 +155,14 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
|
||||
if r.CodeComments != nil {
|
||||
return err
|
||||
}
|
||||
if err = r.loadIssue(ctx); err != nil {
|
||||
if err = r.LoadIssue(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Review) loadIssue(ctx context.Context) (err error) {
|
||||
func (r *Review) LoadIssue(ctx context.Context) (err error) {
|
||||
if r.Issue != nil {
|
||||
return err
|
||||
}
|
||||
@ -199,7 +199,7 @@ func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {
|
||||
|
||||
// LoadAttributes loads all attributes except CodeComments
|
||||
func (r *Review) LoadAttributes(ctx context.Context) (err error) {
|
||||
if err = r.loadIssue(ctx); err != nil {
|
||||
if err = r.LoadIssue(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = r.LoadCodeComments(ctx); err != nil {
|
||||
|
@ -81,7 +81,7 @@ func TestUserListIsPublicMember(t *testing.T) {
|
||||
{3, map[int64]bool{2: true, 4: false, 28: true}},
|
||||
{6, map[int64]bool{5: true, 28: true}},
|
||||
{7, map[int64]bool{5: false}},
|
||||
{25, map[int64]bool{24: true}},
|
||||
{25, map[int64]bool{12: true, 24: true}},
|
||||
{22, map[int64]bool{}},
|
||||
}
|
||||
for _, v := range tt {
|
||||
@ -108,8 +108,8 @@ func TestUserListIsUserOrgOwner(t *testing.T) {
|
||||
{3, map[int64]bool{2: true, 4: false, 28: false}},
|
||||
{6, map[int64]bool{5: true, 28: false}},
|
||||
{7, map[int64]bool{5: true}},
|
||||
{25, map[int64]bool{24: false}}, // ErrTeamNotExist
|
||||
{22, map[int64]bool{}}, // No member
|
||||
{25, map[int64]bool{12: true, 24: false}}, // ErrTeamNotExist
|
||||
{22, map[int64]bool{}}, // No member
|
||||
}
|
||||
for _, v := range tt {
|
||||
t.Run(fmt.Sprintf("IsUserOrgOwnerOfOrgId%d", v.orgid), func(t *testing.T) {
|
||||
|
@ -175,6 +175,8 @@ type SearchRepoOptions struct {
|
||||
// True -> include just forks
|
||||
// False -> include just non-forks
|
||||
Fork optional.Option[bool]
|
||||
// If Fork option is True, you can use this option to limit the forks of a special repo by repo id.
|
||||
ForkFrom int64
|
||||
// None -> include templates AND non-templates
|
||||
// True -> include just templates
|
||||
// False -> include just non-templates
|
||||
@ -514,6 +516,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
||||
cond = cond.And(builder.Eq{"is_fork": false})
|
||||
} else {
|
||||
cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()})
|
||||
|
||||
if opts.ForkFrom > 0 && opts.Fork.Value() {
|
||||
cond = cond.And(builder.Eq{"fork_id": opts.ForkFrom})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,9 @@ func validateYaml(template *api.IssueTemplate) error {
|
||||
if err := validateOptions(field, idx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateDropdownDefault(position, field.Attributes); err != nil {
|
||||
return err
|
||||
}
|
||||
case api.IssueFormFieldTypeCheckboxes:
|
||||
if err := validateStringItem(position, field.Attributes, false, "description"); err != nil {
|
||||
return err
|
||||
@ -249,6 +252,28 @@ func validateBoolItem(position errorPosition, m map[string]any, names ...string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDropdownDefault(position errorPosition, attributes map[string]any) error {
|
||||
v, ok := attributes["default"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
defaultValue, ok := v.(int)
|
||||
if !ok {
|
||||
return position.Errorf("'default' should be an int")
|
||||
}
|
||||
|
||||
options, ok := attributes["options"].([]any)
|
||||
if !ok {
|
||||
// should not happen
|
||||
return position.Errorf("'options' is required and should be a array")
|
||||
}
|
||||
if defaultValue < 0 || defaultValue >= len(options) {
|
||||
return position.Errorf("the value of 'default' is out of range")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type errorPosition string
|
||||
|
||||
func (p errorPosition) Errorf(format string, a ...any) error {
|
||||
|
@ -355,6 +355,96 @@ body:
|
||||
`,
|
||||
wantErr: "body[0](checkboxes), option[1]: can not require a hidden checkbox",
|
||||
},
|
||||
{
|
||||
name: "dropdown default is not an integer",
|
||||
content: `
|
||||
name: "test"
|
||||
about: "this is about"
|
||||
body:
|
||||
- type: dropdown
|
||||
id: "1"
|
||||
attributes:
|
||||
label: Label of dropdown
|
||||
description: Description of dropdown
|
||||
multiple: true
|
||||
options:
|
||||
- Option 1 of dropdown
|
||||
- Option 2 of dropdown
|
||||
- Option 3 of dropdown
|
||||
default: "def"
|
||||
validations:
|
||||
required: true
|
||||
`,
|
||||
wantErr: "body[0](dropdown): 'default' should be an int",
|
||||
},
|
||||
{
|
||||
name: "dropdown default is out of range",
|
||||
content: `
|
||||
name: "test"
|
||||
about: "this is about"
|
||||
body:
|
||||
- type: dropdown
|
||||
id: "1"
|
||||
attributes:
|
||||
label: Label of dropdown
|
||||
description: Description of dropdown
|
||||
multiple: true
|
||||
options:
|
||||
- Option 1 of dropdown
|
||||
- Option 2 of dropdown
|
||||
- Option 3 of dropdown
|
||||
default: 3
|
||||
validations:
|
||||
required: true
|
||||
`,
|
||||
wantErr: "body[0](dropdown): the value of 'default' is out of range",
|
||||
},
|
||||
{
|
||||
name: "dropdown without default is valid",
|
||||
content: `
|
||||
name: "test"
|
||||
about: "this is about"
|
||||
body:
|
||||
- type: dropdown
|
||||
id: "1"
|
||||
attributes:
|
||||
label: Label of dropdown
|
||||
description: Description of dropdown
|
||||
multiple: true
|
||||
options:
|
||||
- Option 1 of dropdown
|
||||
- Option 2 of dropdown
|
||||
- Option 3 of dropdown
|
||||
validations:
|
||||
required: true
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "test",
|
||||
About: "this is about",
|
||||
Fields: []*api.IssueFormField{
|
||||
{
|
||||
Type: "dropdown",
|
||||
ID: "1",
|
||||
Attributes: map[string]any{
|
||||
"label": "Label of dropdown",
|
||||
"description": "Description of dropdown",
|
||||
"multiple": true,
|
||||
"options": []any{
|
||||
"Option 1 of dropdown",
|
||||
"Option 2 of dropdown",
|
||||
"Option 3 of dropdown",
|
||||
},
|
||||
},
|
||||
Validations: map[string]any{
|
||||
"required": true,
|
||||
},
|
||||
Visible: []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm, api.IssueFormFieldVisibleContent},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
content: `
|
||||
@ -399,6 +489,7 @@ body:
|
||||
- Option 1 of dropdown
|
||||
- Option 2 of dropdown
|
||||
- Option 3 of dropdown
|
||||
default: 1
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
@ -475,6 +566,7 @@ body:
|
||||
"Option 2 of dropdown",
|
||||
"Option 3 of dropdown",
|
||||
},
|
||||
"default": 1,
|
||||
},
|
||||
Validations: map[string]any{
|
||||
"required": true,
|
||||
|
@ -30,6 +30,7 @@ type PullRequestMeta struct {
|
||||
HasMerged bool `json:"merged"`
|
||||
Merged *time.Time `json:"merged_at"`
|
||||
IsWorkInProgress bool `json:"draft"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
||||
// RepositoryMeta basic repository information
|
||||
|
@ -21,8 +21,14 @@ type PullRequest struct {
|
||||
Assignees []*User `json:"assignees"`
|
||||
RequestedReviewers []*User `json:"requested_reviewers"`
|
||||
State StateType `json:"state"`
|
||||
Draft bool `json:"draft"`
|
||||
IsLocked bool `json:"is_locked"`
|
||||
Comments int `json:"comments"`
|
||||
// number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||
ReviewComments int `json:"review_comments"`
|
||||
Additions int `json:"additions"`
|
||||
Deletions int `json:"deletions"`
|
||||
ChangedFiles int `json:"changed_files"`
|
||||
|
||||
HTMLURL string `json:"html_url"`
|
||||
DiffURL string `json:"diff_url"`
|
||||
|
@ -28,6 +28,8 @@ type User struct {
|
||||
Email string `json:"email"`
|
||||
// URL to the user's avatar
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
// URL to the user's gitea page
|
||||
HTMLURL string `json:"html_url"`
|
||||
// User locale
|
||||
Language string `json:"language"`
|
||||
// Is the user an administrator
|
||||
|
@ -798,16 +798,16 @@ manage_ssh_keys=Gerir chaves SSH
|
||||
manage_ssh_principals=Gerir Protagonistas de Certificados SSH
|
||||
manage_gpg_keys=Gerir chaves GPG
|
||||
add_key=Adicionar chave
|
||||
ssh_desc=Essas chaves públicas SSH estão associadas à sua conta. As chaves privadas correspondentes permitem acesso total aos seus repositórios.
|
||||
ssh_desc=Estas chaves públicas SSH estão associadas à sua conta. As chaves privadas correspondentes permitem acesso total aos seus repositórios.
|
||||
principal_desc=Estes protagonistas de certificados SSH estão associados à sua conta e permitem acesso total aos seus repositórios.
|
||||
gpg_desc=Essas chaves GPG públicas estão associadas à sua conta. Mantenha as suas chaves privadas seguras, uma vez que elas permitem a validação dos cometimentos.
|
||||
gpg_desc=Estas chaves GPG públicas estão associadas à sua conta. Mantenha as suas chaves privadas seguras, uma vez que elas permitem a validação dos cometimentos.
|
||||
ssh_helper=<strong>Precisa de ajuda?</strong> Dê uma vista de olhos no guia do GitHub para <a href="%s">criar as suas próprias chaves SSH</a> ou para resolver <a href="%s">problemas comuns</a> que pode encontrar ao usar o SSH.
|
||||
gpg_helper=<strong>Precisa de ajuda?</strong> Dê uma vista de olhos no guia do GitHub <a href="%s">sobre GPG</a>.
|
||||
add_new_key=Adicionar Chave SSH
|
||||
add_new_gpg_key=Adicionar chave GPG
|
||||
key_content_ssh_placeholder=Começa com 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', ou 'sk-ssh-ed25519@openssh.com'
|
||||
key_content_gpg_placeholder=Começa com '-----BEGIN PGP PUBLIC KEY BLOCK-----'
|
||||
add_new_principal=Adicional Protagonista
|
||||
add_new_principal=Adicionar protagonista
|
||||
ssh_key_been_used=Esta chave SSH já tinha sido adicionada ao servidor.
|
||||
ssh_key_name_used=Já existe uma chave SSH com o mesmo nome na sua conta.
|
||||
ssh_principal_been_used=Este protagonista já tinha sido adicionado ao servidor.
|
||||
@ -1595,7 +1595,7 @@ issues.label_title=Nome do rótulo
|
||||
issues.label_description=Descrição do rótulo
|
||||
issues.label_color=Cor do rótulo
|
||||
issues.label_exclusive=Exclusivo
|
||||
issues.label_archive=Rótulo de arquivo
|
||||
issues.label_archive=Arquivar rótulo
|
||||
issues.label_archived_filter=Mostrar rótulos arquivados
|
||||
issues.label_archive_tooltip=Os rótulos arquivados são, por norma, excluídos das sugestões ao pesquisar por rótulo.
|
||||
issues.label_exclusive_desc=Nomeie o rótulo <code>âmbito/item</code> para torná-lo mutuamente exclusivo com outros rótulos do <code>âmbito/</code>.
|
||||
@ -3348,6 +3348,7 @@ mirror_sync_create=sincronizou a nova referência <a href="%[2]s">%[3]s</a> para
|
||||
mirror_sync_delete=sincronizou e eliminou a referência <code>%[2]s</code> em <a href="%[1]s">%[3]s</a> da réplica
|
||||
approve_pull_request=`aprovou <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
reject_pull_request=`sugeriu modificações para <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
publish_release=`lançou <a href="%[2]s"> "%[4]s" </a> em <a href="%[1]s">%[3]s</a>`
|
||||
review_dismissed=`descartou a revisão de <b>%[4]s</b> para <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
review_dismissed_reason=Motivo:
|
||||
create_branch=criou o ramo <a href="%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a>
|
||||
@ -3414,6 +3415,7 @@ error.unit_not_allowed=Não tem permissão para aceder a esta parte do repositó
|
||||
title=Pacotes
|
||||
desc=Gerir pacotes do repositório.
|
||||
empty=Ainda não há pacotes.
|
||||
no_metadata=Sem metadados.
|
||||
empty.documentation=Para obter mais informação sobre o registo de pacotes, veja <a target="_blank" rel="noopener noreferrer" href="%s">a documentação</a>.
|
||||
empty.repo=Carregou um pacote mas este não é apresentado aqui? Vá às <a href="%[1]s">configurações do pacote</a> e ligue-o a este repositório.
|
||||
registry.documentation=Para mais informação sobre o registo %s, veja <a target="_blank" rel="noopener noreferrer" href="%s">a documentação</a>.
|
||||
|
@ -3415,6 +3415,7 @@ error.unit_not_allowed=您没有权限访问此仓库单元
|
||||
title=软件包
|
||||
desc=管理仓库软件包。
|
||||
empty=还没有软件包。
|
||||
no_metadata=没有元数据。
|
||||
empty.documentation=关于软件包注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="%s"> 文档 </a>。
|
||||
empty.repo=您上传了一个包,但没有显示在这里吗?转到 <a href="%[1]s">包设置</a> 并将其链接到这个仓库中。
|
||||
registry.documentation=关于 %s 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="%s">文档</a>。
|
||||
|
@ -556,15 +556,30 @@ func GrantApplicationOAuth(ctx *context.Context) {
|
||||
ctx.ServerError("GetOAuth2ApplicationByClientID", err)
|
||||
return
|
||||
}
|
||||
grant, err := app.CreateGrant(ctx, ctx.Doer.ID, form.Scope)
|
||||
grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
if grant == nil {
|
||||
grant, err = app.CreateGrant(ctx, ctx.Doer.ID, form.Scope)
|
||||
if err != nil {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
State: form.State,
|
||||
ErrorDescription: "cannot create grant for user",
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
} else if grant.Scope != form.Scope {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
State: form.State,
|
||||
ErrorDescription: "cannot create grant for user",
|
||||
ErrorDescription: "a grant exists with different scope",
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Nonce) > 0 {
|
||||
err := grant.SetNonce(ctx, form.Nonce)
|
||||
if err != nil {
|
||||
|
@ -862,7 +862,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
||||
}
|
||||
|
||||
if pull.HeadRepo != nil {
|
||||
ctx.Data["SourcePath"] = pull.HeadRepo.Link() + "/src/branch/" + util.PathEscapeSegments(pull.HeadBranch)
|
||||
ctx.Data["SourcePath"] = pull.HeadRepo.Link() + "/src/commit/" + endCommitID
|
||||
|
||||
if !pull.HasMerged && ctx.Doer != nil {
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issue_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@ -1027,15 +1028,26 @@ func renderHomeCode(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
showRecentlyPushedNewBranches := true
|
||||
if ctx.Repo.Repository.IsMirror ||
|
||||
!ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypePullRequests) {
|
||||
showRecentlyPushedNewBranches = false
|
||||
opts := &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Repo: ctx.Repo.Repository,
|
||||
BaseRepo: ctx.Repo.Repository,
|
||||
}
|
||||
if showRecentlyPushedNewBranches {
|
||||
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID, ctx.Repo.Repository.DefaultBranch)
|
||||
if ctx.Repo.Repository.IsFork {
|
||||
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
|
||||
}
|
||||
|
||||
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
|
||||
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
|
||||
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
|
||||
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRecentlyPushedBranches", err)
|
||||
ctx.ServerError("FindRecentlyPushedNewBranches", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
)
|
||||
|
||||
@ -30,6 +31,8 @@ var prAutoMergeQueue *queue.WorkerPoolQueue[string]
|
||||
|
||||
// Init runs the task queue to that handles auto merges
|
||||
func Init() error {
|
||||
notify_service.RegisterNotifier(NewNotifier())
|
||||
|
||||
prAutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler)
|
||||
if prAutoMergeQueue == nil {
|
||||
return fmt.Errorf("unable to create pr_auto_merge queue")
|
||||
@ -47,7 +50,7 @@ func handler(items ...string) []string {
|
||||
log.Error("could not parse data from pr_auto_merge queue (%v): %v", s, err)
|
||||
continue
|
||||
}
|
||||
handlePull(id, sha)
|
||||
handlePullRequestAutoMerge(id, sha)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -62,16 +65,6 @@ func addToQueue(pr *issues_model.PullRequest, sha string) {
|
||||
// ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
|
||||
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string) (scheduled bool, err error) {
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
lastCommitStatus, err := pull_service.GetPullRequestCommitStatusState(ctx, pull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we don't need to schedule
|
||||
if lastCommitStatus.IsSuccess() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -95,8 +88,8 @@ func RemoveScheduledAutoMerge(ctx context.Context, doer *user_model.User, pull *
|
||||
})
|
||||
}
|
||||
|
||||
// MergeScheduledPullRequest merges a previously scheduled pull request when all checks succeeded
|
||||
func MergeScheduledPullRequest(ctx context.Context, sha string, repo *repo_model.Repository) error {
|
||||
// StartPRCheckAndAutoMergeBySHA start an automerge check and auto merge task for all pull requests of repository and SHA
|
||||
func StartPRCheckAndAutoMergeBySHA(ctx context.Context, sha string, repo *repo_model.Repository) error {
|
||||
pulls, err := getPullRequestsByHeadSHA(ctx, sha, repo, func(pr *issues_model.PullRequest) bool {
|
||||
return !pr.HasMerged && pr.CanAutoMerge()
|
||||
})
|
||||
@ -111,6 +104,32 @@ func MergeScheduledPullRequest(ctx context.Context, sha string, repo *repo_model
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartPRCheckAndAutoMerge start an automerge check and auto merge task for a pull request
|
||||
func StartPRCheckAndAutoMerge(ctx context.Context, pull *issues_model.PullRequest) {
|
||||
if pull == nil || pull.HasMerged || !pull.CanAutoMerge() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := pull.LoadBaseRepo(ctx); err != nil {
|
||||
log.Error("LoadBaseRepo: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo)
|
||||
if err != nil {
|
||||
log.Error("OpenRepository: %v", err)
|
||||
return
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
|
||||
if err != nil {
|
||||
log.Error("GetRefCommitID: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
addToQueue(pull, commitID)
|
||||
}
|
||||
|
||||
func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.Repository, filter func(*issues_model.PullRequest) bool) (map[int64]*issues_model.PullRequest, error) {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
@ -161,7 +180,8 @@ func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.
|
||||
return pulls, nil
|
||||
}
|
||||
|
||||
func handlePull(pullID int64, sha string) {
|
||||
// handlePullRequestAutoMerge merge the pull request if all checks are successful
|
||||
func handlePullRequestAutoMerge(pullID int64, sha string) {
|
||||
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(),
|
||||
fmt.Sprintf("Handle AutoMerge of PR[%d] with sha[%s]", pullID, sha))
|
||||
defer finished()
|
||||
@ -182,24 +202,50 @@ func handlePull(pullID int64, sha string) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = pr.LoadBaseRepo(ctx); err != nil {
|
||||
log.Error("%-v LoadBaseRepo: %v", pr, err)
|
||||
return
|
||||
}
|
||||
|
||||
// check the sha is the same as pull request head commit id
|
||||
baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
log.Error("OpenRepository: %v", err)
|
||||
return
|
||||
}
|
||||
defer baseGitRepo.Close()
|
||||
|
||||
headCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
if err != nil {
|
||||
log.Error("GetRefCommitID: %v", err)
|
||||
return
|
||||
}
|
||||
if headCommitID != sha {
|
||||
log.Warn("Head commit id of auto merge %-v does not match sha [%s], it may means the head branch has been updated. Just ignore this request because a new request expected in the queue", pr, sha)
|
||||
return
|
||||
}
|
||||
|
||||
// Get all checks for this pr
|
||||
// We get the latest sha commit hash again to handle the case where the check of a previous push
|
||||
// did not succeed or was not finished yet.
|
||||
|
||||
if err = pr.LoadHeadRepo(ctx); err != nil {
|
||||
log.Error("%-v LoadHeadRepo: %v", pr, err)
|
||||
return
|
||||
}
|
||||
|
||||
headGitRepo, err := gitrepo.OpenRepository(ctx, pr.HeadRepo)
|
||||
if err != nil {
|
||||
log.Error("OpenRepository %-v: %v", pr.HeadRepo, err)
|
||||
return
|
||||
var headGitRepo *git.Repository
|
||||
if pr.BaseRepoID == pr.HeadRepoID {
|
||||
headGitRepo = baseGitRepo
|
||||
} else {
|
||||
headGitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
|
||||
if err != nil {
|
||||
log.Error("OpenRepository %-v: %v", pr.HeadRepo, err)
|
||||
return
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
|
||||
headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch)
|
||||
|
||||
if pr.HeadRepo == nil || !headBranchExist {
|
||||
log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch)
|
||||
return
|
||||
@ -238,25 +284,11 @@ func handlePull(pullID int64, sha string) {
|
||||
return
|
||||
}
|
||||
|
||||
var baseGitRepo *git.Repository
|
||||
if pr.BaseRepoID == pr.HeadRepoID {
|
||||
baseGitRepo = headGitRepo
|
||||
} else {
|
||||
if err = pr.LoadBaseRepo(ctx); err != nil {
|
||||
log.Error("%-v LoadBaseRepo: %v", pr, err)
|
||||
return
|
||||
}
|
||||
|
||||
baseGitRepo, err = gitrepo.OpenRepository(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
log.Error("OpenRepository %-v: %v", pr.BaseRepo, err)
|
||||
return
|
||||
}
|
||||
defer baseGitRepo.Close()
|
||||
}
|
||||
|
||||
if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message, true); err != nil {
|
||||
log.Error("pull_service.Merge: %v", err)
|
||||
// FIXME: if merge failed, we should display some error message to the pull request page.
|
||||
// The resolution is add a new column on automerge table named `error_message` to store the error message and displayed
|
||||
// on the pull request page. But this should not be finished in a bug fix PR which will be backport to release branch.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
46
services/automerge/notify.go
Normal file
46
services/automerge/notify.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package automerge
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
type automergeNotifier struct {
|
||||
notify_service.NullNotifier
|
||||
}
|
||||
|
||||
var _ notify_service.Notifier = &automergeNotifier{}
|
||||
|
||||
// NewNotifier create a new automergeNotifier notifier
|
||||
func NewNotifier() notify_service.Notifier {
|
||||
return &automergeNotifier{}
|
||||
}
|
||||
|
||||
func (n *automergeNotifier) PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
|
||||
// as a missing / blocking reviews could have blocked a pending automerge let's recheck
|
||||
if review.Type == issues_model.ReviewTypeApprove {
|
||||
if err := StartPRCheckAndAutoMergeBySHA(ctx, review.CommitID, pr.BaseRepo); err != nil {
|
||||
log.Error("StartPullRequestAutoMergeCheckBySHA: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *automergeNotifier) PullReviewDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
|
||||
if err := review.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return
|
||||
}
|
||||
if err := review.Issue.LoadPullRequest(ctx); err != nil {
|
||||
log.Error("LoadPullRequest: %v", err)
|
||||
return
|
||||
}
|
||||
// as reviews could have blocked a pending automerge let's recheck
|
||||
StartPRCheckAndAutoMerge(ctx, review.Issue.PullRequest)
|
||||
}
|
@ -104,6 +104,8 @@ func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Iss
|
||||
if issue.PullRequest.HasMerged {
|
||||
apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr()
|
||||
}
|
||||
// Add pr's html url
|
||||
apiIssue.PullRequest.HTMLURL = issue.HTMLURL()
|
||||
}
|
||||
}
|
||||
if issue.DeadlineUnix != 0 {
|
||||
|
@ -51,29 +51,31 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
||||
}
|
||||
|
||||
apiPullRequest := &api.PullRequest{
|
||||
ID: pr.ID,
|
||||
URL: pr.Issue.HTMLURL(),
|
||||
Index: pr.Index,
|
||||
Poster: apiIssue.Poster,
|
||||
Title: apiIssue.Title,
|
||||
Body: apiIssue.Body,
|
||||
Labels: apiIssue.Labels,
|
||||
Milestone: apiIssue.Milestone,
|
||||
Assignee: apiIssue.Assignee,
|
||||
Assignees: apiIssue.Assignees,
|
||||
State: apiIssue.State,
|
||||
IsLocked: apiIssue.IsLocked,
|
||||
Comments: apiIssue.Comments,
|
||||
HTMLURL: pr.Issue.HTMLURL(),
|
||||
DiffURL: pr.Issue.DiffURL(),
|
||||
PatchURL: pr.Issue.PatchURL(),
|
||||
HasMerged: pr.HasMerged,
|
||||
MergeBase: pr.MergeBase,
|
||||
Mergeable: pr.Mergeable(ctx),
|
||||
Deadline: apiIssue.Deadline,
|
||||
Created: pr.Issue.CreatedUnix.AsTimePtr(),
|
||||
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
|
||||
PinOrder: apiIssue.PinOrder,
|
||||
ID: pr.ID,
|
||||
URL: pr.Issue.HTMLURL(),
|
||||
Index: pr.Index,
|
||||
Poster: apiIssue.Poster,
|
||||
Title: apiIssue.Title,
|
||||
Body: apiIssue.Body,
|
||||
Labels: apiIssue.Labels,
|
||||
Milestone: apiIssue.Milestone,
|
||||
Assignee: apiIssue.Assignee,
|
||||
Assignees: apiIssue.Assignees,
|
||||
State: apiIssue.State,
|
||||
Draft: pr.IsWorkInProgress(ctx),
|
||||
IsLocked: apiIssue.IsLocked,
|
||||
Comments: apiIssue.Comments,
|
||||
ReviewComments: pr.GetReviewCommentsCount(ctx),
|
||||
HTMLURL: pr.Issue.HTMLURL(),
|
||||
DiffURL: pr.Issue.DiffURL(),
|
||||
PatchURL: pr.Issue.PatchURL(),
|
||||
HasMerged: pr.HasMerged,
|
||||
MergeBase: pr.MergeBase,
|
||||
Mergeable: pr.Mergeable(ctx),
|
||||
Deadline: apiIssue.Deadline,
|
||||
Created: pr.Issue.CreatedUnix.AsTimePtr(),
|
||||
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
|
||||
PinOrder: apiIssue.PinOrder,
|
||||
|
||||
AllowMaintainerEdit: pr.AllowMaintainerEdit,
|
||||
|
||||
@ -168,6 +170,12 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
||||
return nil
|
||||
}
|
||||
|
||||
// Outer scope variables to be used in diff calculation
|
||||
var (
|
||||
startCommitID string
|
||||
endCommitID string
|
||||
)
|
||||
|
||||
if git.IsErrBranchNotExist(err) {
|
||||
headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
@ -176,6 +184,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
||||
}
|
||||
if err == nil {
|
||||
apiPullRequest.Head.Sha = headCommitID
|
||||
endCommitID = headCommitID
|
||||
}
|
||||
} else {
|
||||
commit, err := headBranch.GetCommit()
|
||||
@ -186,8 +195,17 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
||||
if err == nil {
|
||||
apiPullRequest.Head.Ref = pr.HeadBranch
|
||||
apiPullRequest.Head.Sha = commit.ID.String()
|
||||
endCommitID = commit.ID.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate diff
|
||||
startCommitID = pr.MergeBase
|
||||
|
||||
apiPullRequest.ChangedFiles, apiPullRequest.Additions, apiPullRequest.Deletions, err = gitRepo.GetDiffShortStat(startCommitID, endCommitID)
|
||||
if err != nil {
|
||||
log.Error("GetDiffShortStat: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 {
|
||||
|
@ -53,6 +53,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
|
||||
FullName: user.FullName,
|
||||
Email: user.GetPlaceholderEmail(),
|
||||
AvatarURL: user.AvatarLink(ctx),
|
||||
HTMLURL: user.HTMLURL(),
|
||||
Created: user.CreatedUnix.AsTime(),
|
||||
Restricted: user.IsRestricted,
|
||||
Location: user.Location,
|
||||
|
@ -115,7 +115,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
|
||||
}
|
||||
|
||||
if status.State.IsSuccess() {
|
||||
if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil {
|
||||
if err := automerge.StartPRCheckAndAutoMergeBySHA(ctx, sha, repo); err != nil {
|
||||
return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export default {
|
||||
'xl': '12px',
|
||||
'2xl': '16px',
|
||||
'3xl': '24px',
|
||||
'full': 'var(--border-radius-circle)', // 50%
|
||||
'full': 'var(--border-radius-full)',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: 'var(--fonts-regular)',
|
||||
|
@ -2,10 +2,10 @@
|
||||
<div class="ui positive message tw-flex tw-items-center">
|
||||
<div class="tw-flex-1">
|
||||
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}}
|
||||
{{$branchLink := HTMLFormat `<a href="%s/src/branch/%s">%s</a>` $.RepoLink (PathEscapeSegments .Name) .Name}}
|
||||
{{$branchLink := HTMLFormat `<a href="%s">%s</a>` .BranchLink .BranchDisplayName}}
|
||||
{{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $branchLink $timeSince}}
|
||||
</div>
|
||||
<a role="button" class="ui compact green button tw-m-0" href="{{$.Repository.ComposeBranchCompareURL $.Repository.BaseRepo .Name}}">
|
||||
<a role="button" class="ui compact green button tw-m-0" href="{{.BranchCompareURL}}">
|
||||
{{ctx.Locale.Tr "repo.pulls.compare_changes"}}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{{template "repo/issue/fields/header" .}}
|
||||
{{/* FIXME: required validation */}}
|
||||
<div class="ui fluid selection dropdown {{if .item.Attributes.multiple}}multiple clearable{{end}}">
|
||||
<input type="hidden" name="form-field-{{.item.ID}}" value="0">
|
||||
<input type="hidden" name="form-field-{{.item.ID}}" value="{{.item.Attributes.default}}">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
{{if not .item.Validations.required}}
|
||||
{{svg "octicon-x" 14 "remove icon"}}
|
||||
|
34
templates/swagger/v1_json.tmpl
generated
34
templates/swagger/v1_json.tmpl
generated
@ -22975,6 +22975,11 @@
|
||||
"description": "PullRequest represents a pull request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"additions": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "Additions"
|
||||
},
|
||||
"allow_maintainer_edit": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "AllowMaintainerEdit"
|
||||
@ -22996,6 +23001,11 @@
|
||||
"type": "string",
|
||||
"x-go-name": "Body"
|
||||
},
|
||||
"changed_files": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "ChangedFiles"
|
||||
},
|
||||
"closed_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
@ -23011,10 +23021,19 @@
|
||||
"format": "date-time",
|
||||
"x-go-name": "Created"
|
||||
},
|
||||
"deletions": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "Deletions"
|
||||
},
|
||||
"diff_url": {
|
||||
"type": "string",
|
||||
"x-go-name": "DiffURL"
|
||||
},
|
||||
"draft": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "Draft"
|
||||
},
|
||||
"due_date": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
@ -23091,6 +23110,12 @@
|
||||
},
|
||||
"x-go-name": "RequestedReviewers"
|
||||
},
|
||||
"review_comments": {
|
||||
"description": "number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"x-go-name": "ReviewComments"
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/StateType"
|
||||
},
|
||||
@ -23121,6 +23146,10 @@
|
||||
"type": "boolean",
|
||||
"x-go-name": "IsWorkInProgress"
|
||||
},
|
||||
"html_url": {
|
||||
"type": "string",
|
||||
"x-go-name": "HTMLURL"
|
||||
},
|
||||
"merged": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "HasMerged"
|
||||
@ -24414,6 +24443,11 @@
|
||||
"type": "string",
|
||||
"x-go-name": "FullName"
|
||||
},
|
||||
"html_url": {
|
||||
"description": "URL to the user's gitea page",
|
||||
"type": "string",
|
||||
"x-go-name": "HTMLURL"
|
||||
},
|
||||
"id": {
|
||||
"description": "the user's id",
|
||||
"type": "integer",
|
||||
|
@ -29,6 +29,7 @@ func TestUserOrgs(t *testing.T) {
|
||||
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
|
||||
org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"})
|
||||
org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"})
|
||||
|
||||
assert.Equal(t, []*api.Organization{
|
||||
{
|
||||
@ -55,6 +56,18 @@ func TestUserOrgs(t *testing.T) {
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
{
|
||||
ID: 35,
|
||||
Name: org35.Name,
|
||||
UserName: org35.Name,
|
||||
FullName: org35.FullName,
|
||||
Email: org35.Email,
|
||||
AvatarURL: org35.AvatarLink(db.DefaultContext),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "private",
|
||||
},
|
||||
}, orgs)
|
||||
|
||||
// user itself should get it's org's he is a member of
|
||||
@ -102,6 +115,7 @@ func TestMyOrgs(t *testing.T) {
|
||||
DecodeJSON(t, resp, &orgs)
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
|
||||
org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"})
|
||||
org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"})
|
||||
|
||||
assert.Equal(t, []*api.Organization{
|
||||
{
|
||||
@ -128,5 +142,17 @@ func TestMyOrgs(t *testing.T) {
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
{
|
||||
ID: 35,
|
||||
Name: org35.Name,
|
||||
UserName: org35.Name,
|
||||
FullName: org35.FullName,
|
||||
Email: org35.Email,
|
||||
AvatarURL: org35.AvatarLink(db.DefaultContext),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "private",
|
||||
},
|
||||
}, orgs)
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ func TestCompareCodeExpand(t *testing.T) {
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session = loginUser(t, user2.Name)
|
||||
testRepoFork(t, session, user1.Name, repo.Name, user2.Name, "test_blob_excerpt-fork")
|
||||
testRepoFork(t, session, user1.Name, repo.Name, user2.Name, "test_blob_excerpt-fork", "")
|
||||
testCreateBranch(t, session, user2.Name, "test_blob_excerpt-fork", "branch/main", "forked-branch", http.StatusSeeOther)
|
||||
testEditFile(t, session, user2.Name, "test_blob_excerpt-fork", "forked-branch", "README.md", strings.Repeat("a\n", 15)+"CHANGED\n"+strings.Repeat("a\n", 15))
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@ -19,27 +20,31 @@ import (
|
||||
func TestCreateFile(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
// Request editor page
|
||||
req := NewRequest(t, "GET", "/user2/repo1/_new/master/")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
lastCommit := doc.GetInputValueByName("last_commit")
|
||||
assert.NotEmpty(t, lastCommit)
|
||||
|
||||
// Save new file to master branch
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
"last_commit": lastCommit,
|
||||
"tree_path": "test.txt",
|
||||
"content": "Content",
|
||||
"commit_choice": "direct",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
testCreateFile(t, session, "user2", "repo1", "master", "test.txt", "Content")
|
||||
})
|
||||
}
|
||||
|
||||
func testCreateFile(t *testing.T, session *TestSession, user, repo, branch, filePath, content string) *httptest.ResponseRecorder {
|
||||
// Request editor page
|
||||
newURL := fmt.Sprintf("/%s/%s/_new/%s/", user, repo, branch)
|
||||
req := NewRequest(t, "GET", newURL)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
lastCommit := doc.GetInputValueByName("last_commit")
|
||||
assert.NotEmpty(t, lastCommit)
|
||||
|
||||
// Save new file to master branch
|
||||
req = NewRequestWithValues(t, "POST", newURL, map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
"last_commit": lastCommit,
|
||||
"tree_path": filePath,
|
||||
"content": content,
|
||||
"commit_choice": "direct",
|
||||
})
|
||||
return session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func TestCreateFileOnProtectedBranch(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user2")
|
||||
|
@ -6,9 +6,11 @@ package integration
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
@ -24,6 +26,17 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) *httptest.ResponseRecorder {
|
||||
url := fmt.Sprintf("/%s/%s/_new/%s", user, repo, branch)
|
||||
req := NewRequestWithValues(t, "POST", url, map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user/settings"),
|
||||
"commit_choice": "direct",
|
||||
"tree_path": treePath,
|
||||
"content": content,
|
||||
})
|
||||
return session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func TestEmptyRepo(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
subPaths := []string{
|
||||
|
@ -485,6 +485,7 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile
|
||||
assert.True(t, result.Valid())
|
||||
}
|
||||
|
||||
// GetCSRF returns CSRF token from body
|
||||
func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
|
||||
t.Helper()
|
||||
req := NewRequest(t, "GET", urlStr)
|
||||
@ -492,3 +493,11 @@ func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
return doc.GetCSRF()
|
||||
}
|
||||
|
||||
// GetCSRFFrom returns CSRF token from body
|
||||
func GetCSRFFromCookie(t testing.TB, session *TestSession, urlStr string) string {
|
||||
t.Helper()
|
||||
req := NewRequest(t, "GET", urlStr)
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
return session.GetCookie("_csrf").Value
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func TestPullCompare(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther)
|
||||
testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
|
||||
testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title")
|
||||
|
@ -85,7 +85,7 @@ func testPullCreateDirectly(t *testing.T, session *TestSession, baseRepoOwner, b
|
||||
func TestPullCreate(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
|
||||
|
||||
@ -113,7 +113,7 @@ func TestPullCreate(t *testing.T) {
|
||||
func TestPullCreate_TitleEscape(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "<i>XSS PR</i>")
|
||||
|
||||
@ -177,7 +177,7 @@ func TestPullBranchDelete(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther)
|
||||
testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title")
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -19,7 +21,9 @@ import (
|
||||
"code.gitea.io/gitea/models"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@ -30,8 +34,10 @@ import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/services/automerge"
|
||||
"code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -89,7 +95,7 @@ func TestPullMerge(t *testing.T) {
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
|
||||
@ -111,7 +117,7 @@ func TestPullRebase(t *testing.T) {
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
|
||||
@ -133,7 +139,7 @@ func TestPullRebaseMerge(t *testing.T) {
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
|
||||
@ -155,7 +161,7 @@ func TestPullSquash(t *testing.T) {
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n")
|
||||
|
||||
@ -174,7 +180,7 @@ func TestPullSquash(t *testing.T) {
|
||||
func TestPullCleanUpAfterMerge(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited - TestPullCleanUpAfterMerge)\n")
|
||||
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "feature/test", "This is a pull title")
|
||||
@ -209,7 +215,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
|
||||
func TestCantMergeWorkInProgress(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "[wip] This is a pull title")
|
||||
@ -228,7 +234,7 @@ func TestCantMergeWorkInProgress(t *testing.T) {
|
||||
func TestCantMergeConflict(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n")
|
||||
|
||||
@ -274,7 +280,7 @@ func TestCantMergeConflict(t *testing.T) {
|
||||
func TestCantMergeUnrelated(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n")
|
||||
|
||||
// Now we want to create a commit on a branch that is totally unrelated to our current head
|
||||
@ -369,7 +375,7 @@ func TestCantMergeUnrelated(t *testing.T) {
|
||||
func TestFastForwardOnlyMerge(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "update", "README.md", "Hello, World 2\n")
|
||||
|
||||
// Use API to create a pr from update to master
|
||||
@ -410,7 +416,7 @@ func TestFastForwardOnlyMerge(t *testing.T) {
|
||||
func TestCantFastForwardOnlyMergeDiverging(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "diverging", "README.md", "Hello, World diverged\n")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World 2\n")
|
||||
|
||||
@ -533,7 +539,7 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testEditFileToNewBranch(t, session, "user2", "repo1", "master", "base-pr", "README.md", "Hello, World\n(Edited - TestPullRetargetOnCleanup - base PR)\n")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "base-pr", "child-pr", "README.md", "Hello, World\n(Edited - TestPullRetargetOnCleanup - base PR)\n(Edited - TestPullRetargetOnCleanup - child PR)")
|
||||
|
||||
respBasePR := testPullCreate(t, session, "user2", "repo1", true, "master", "base-pr", "Base Pull Request")
|
||||
@ -562,7 +568,7 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) {
|
||||
func TestPullDontRetargetChildOnWrongRepo(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base-pr", "README.md", "Hello, World\n(Edited - TestPullDontRetargetChildOnWrongRepo - base PR)\n")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "base-pr", "child-pr", "README.md", "Hello, World\n(Edited - TestPullDontRetargetChildOnWrongRepo - base PR)\n(Edited - TestPullDontRetargetChildOnWrongRepo - child PR)")
|
||||
|
||||
@ -593,7 +599,7 @@ func TestPullMergeIndexerNotifier(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// create a pull request
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
createPullResp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "Indexer notifier test pull")
|
||||
|
||||
@ -648,3 +654,195 @@ func TestPullMergeIndexerNotifier(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testResetRepo(t *testing.T, repoPath, branch, commitID string) {
|
||||
f, err := os.OpenFile(filepath.Join(repoPath, "refs", "heads", branch), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
assert.NoError(t, err)
|
||||
_, err = f.WriteString(commitID + "\n")
|
||||
assert.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
repo, err := git.OpenRepository(context.Background(), repoPath)
|
||||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
id, err := repo.GetBranchCommitID(branch)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, commitID, id)
|
||||
}
|
||||
|
||||
func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// create a pull request
|
||||
session := loginUser(t, "user1")
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
forkedName := "repo1-1"
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", forkedName, "")
|
||||
defer func() {
|
||||
testDeleteRepository(t, session, "user1", forkedName)
|
||||
}()
|
||||
testEditFile(t, session, "user1", forkedName, "master", "README.md", "Hello, World (Edited)\n")
|
||||
testPullCreate(t, session, "user1", forkedName, false, "master", "master", "Indexer notifier test pull")
|
||||
|
||||
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
|
||||
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: forkedName})
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
|
||||
BaseRepoID: baseRepo.ID,
|
||||
BaseBranch: "master",
|
||||
HeadRepoID: forkedRepo.ID,
|
||||
HeadBranch: "master",
|
||||
})
|
||||
|
||||
// add protected branch for commit status
|
||||
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
|
||||
// Change master branch to protected
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
||||
"_csrf": csrf,
|
||||
"rule_name": "master",
|
||||
"enable_push": "true",
|
||||
"enable_status_check": "true",
|
||||
"status_check_contexts": "gitea/actions",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// first time insert automerge record, return true
|
||||
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, scheduled)
|
||||
|
||||
// second time insert automerge record, return false because it does exist
|
||||
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
||||
assert.Error(t, err)
|
||||
assert.False(t, scheduled)
|
||||
|
||||
// reload pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.False(t, pr.HasMerged)
|
||||
assert.Empty(t, pr.MergedCommitID)
|
||||
|
||||
// update commit status to success, then it should be merged automatically
|
||||
baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
|
||||
assert.NoError(t, err)
|
||||
sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
assert.NoError(t, err)
|
||||
masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
|
||||
assert.NoError(t, err)
|
||||
|
||||
branches, _, err := baseGitRepo.GetBranchNames(0, 100)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{"sub-home-md-img-check", "home-md-img-check", "pr-to-update", "branch2", "DefaultBranch", "develop", "feature/1", "master"}, branches)
|
||||
baseGitRepo.Close()
|
||||
defer func() {
|
||||
testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
|
||||
}()
|
||||
|
||||
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
|
||||
State: api.CommitStatusSuccess,
|
||||
TargetURL: "https://gitea.com",
|
||||
Context: "gitea/actions",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// realod pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.True(t, pr.HasMerged)
|
||||
assert.NotEmpty(t, pr.MergedCommitID)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// create a pull request
|
||||
session := loginUser(t, "user1")
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
forkedName := "repo1-2"
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", forkedName, "")
|
||||
defer func() {
|
||||
testDeleteRepository(t, session, "user1", forkedName)
|
||||
}()
|
||||
testEditFile(t, session, "user1", forkedName, "master", "README.md", "Hello, World (Edited)\n")
|
||||
testPullCreate(t, session, "user1", forkedName, false, "master", "master", "Indexer notifier test pull")
|
||||
|
||||
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
|
||||
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: forkedName})
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
|
||||
BaseRepoID: baseRepo.ID,
|
||||
BaseBranch: "master",
|
||||
HeadRepoID: forkedRepo.ID,
|
||||
HeadBranch: "master",
|
||||
})
|
||||
|
||||
// add protected branch for commit status
|
||||
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
|
||||
// Change master branch to protected
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
||||
"_csrf": csrf,
|
||||
"rule_name": "master",
|
||||
"enable_push": "true",
|
||||
"enable_status_check": "true",
|
||||
"status_check_contexts": "gitea/actions",
|
||||
"required_approvals": "1",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// first time insert automerge record, return true
|
||||
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, scheduled)
|
||||
|
||||
// second time insert automerge record, return false because it does exist
|
||||
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
||||
assert.Error(t, err)
|
||||
assert.False(t, scheduled)
|
||||
|
||||
// reload pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.False(t, pr.HasMerged)
|
||||
assert.Empty(t, pr.MergedCommitID)
|
||||
|
||||
// update commit status to success, then it should be merged automatically
|
||||
baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
|
||||
assert.NoError(t, err)
|
||||
sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
assert.NoError(t, err)
|
||||
masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
|
||||
assert.NoError(t, err)
|
||||
baseGitRepo.Close()
|
||||
defer func() {
|
||||
testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
|
||||
}()
|
||||
|
||||
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
|
||||
State: api.CommitStatusSuccess,
|
||||
TargetURL: "https://gitea.com",
|
||||
Context: "gitea/actions",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// reload pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.False(t, pr.HasMerged)
|
||||
assert.Empty(t, pr.MergedCommitID)
|
||||
|
||||
// approve the PR from non-author
|
||||
approveSession := loginUser(t, "user2")
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/user2/repo1/pulls/%d", pr.Index))
|
||||
resp := approveSession.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
testSubmitReview(t, approveSession, htmlDoc.GetCSRF(), "user2", "repo1", strconv.Itoa(int(pr.Index)), sha, "approve", http.StatusOK)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// realod pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.True(t, pr.HasMerged)
|
||||
assert.NotEmpty(t, pr.MergedCommitID)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||
})
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
|
||||
user2Session := loginUser(t, "user2")
|
||||
|
||||
// Have user1 create a fork of repo1.
|
||||
testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1", "")
|
||||
|
||||
t.Run("Submit approve/reject review on merged PR", func(t *testing.T) {
|
||||
// Create a merged PR (made by user1) in the upstream repo1.
|
||||
@ -202,10 +202,10 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
// Submit an approve review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "approve", http.StatusUnprocessableEntity)
|
||||
|
||||
// Submit a reject review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "reject", http.StatusUnprocessableEntity)
|
||||
})
|
||||
|
||||
t.Run("Submit approve/reject review on closed PR", func(t *testing.T) {
|
||||
@ -222,18 +222,18 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
// Submit an approve review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "approve", http.StatusUnprocessableEntity)
|
||||
|
||||
// Submit a reject review on the PR.
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
|
||||
testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "reject", http.StatusUnprocessableEntity)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder {
|
||||
func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, commitID, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder {
|
||||
options := map[string]string{
|
||||
"_csrf": csrf,
|
||||
"commit_id": "",
|
||||
"commit_id": commitID,
|
||||
"content": "test",
|
||||
"type": reviewType,
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
func TestPullCreate_CommitStatus(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1")
|
||||
|
||||
url := path.Join("user1", "repo1", "compare", "master...status1")
|
||||
@ -122,7 +122,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
|
||||
// so we need to have this meta commit also in develop branch.
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "status1", "status1", "README.md", "# repo1\n\nDescription for repo1")
|
||||
|
||||
@ -147,7 +147,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
|
||||
func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther)
|
||||
url := path.Join("user1", "repo1", "compare", "master...status1")
|
||||
req := NewRequestWithValues(t, "POST", url,
|
||||
|
@ -20,7 +20,7 @@ func TestRepoActivity(t *testing.T) {
|
||||
session := loginUser(t, "user1")
|
||||
|
||||
// Create PRs (1 merged & 2 proposed)
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
||||
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
|
||||
elem := strings.Split(test.RedirectURL(resp), "/")
|
||||
|
@ -4,26 +4,37 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string {
|
||||
var csrf string
|
||||
if expectedStatus == http.StatusNotFound {
|
||||
csrf = GetCSRF(t, session, path.Join(user, repo, "src/branch/master"))
|
||||
// src/branch/branch_name may not container "_csrf" input,
|
||||
// so we need to get it from cookies not from body
|
||||
csrf = GetCSRFFromCookie(t, session, path.Join(user, repo, "src/branch/master"))
|
||||
} else {
|
||||
csrf = GetCSRF(t, session, path.Join(user, repo, "src", oldRefSubURL))
|
||||
csrf = GetCSRFFromCookie(t, session, path.Join(user, repo, "src", oldRefSubURL))
|
||||
}
|
||||
req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefSubURL), map[string]string{
|
||||
"_csrf": csrf,
|
||||
@ -145,3 +156,136 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
}
|
||||
|
||||
func prepareBranch(t *testing.T, session *TestSession, repo *repo_model.Repository) {
|
||||
baseRefSubURL := fmt.Sprintf("branch/%s", repo.DefaultBranch)
|
||||
|
||||
// create branch with no new commit
|
||||
testCreateBranch(t, session, repo.OwnerName, repo.Name, baseRefSubURL, "no-commit", http.StatusSeeOther)
|
||||
|
||||
// create branch with commit
|
||||
testCreateBranch(t, session, repo.OwnerName, repo.Name, baseRefSubURL, "new-commit", http.StatusSeeOther)
|
||||
testAPINewFile(t, session, repo.OwnerName, repo.Name, "new-commit", "new-commit.txt", "new-commit")
|
||||
|
||||
// create deleted branch
|
||||
testCreateBranch(t, session, repo.OwnerName, repo.Name, "branch/new-commit", "deleted-branch", http.StatusSeeOther)
|
||||
testUIDeleteBranch(t, session, repo.OwnerName, repo.Name, "deleted-branch")
|
||||
}
|
||||
|
||||
func testCreatePullToDefaultBranch(t *testing.T, session *TestSession, baseRepo, headRepo *repo_model.Repository, headBranch, title string) string {
|
||||
srcRef := headBranch
|
||||
if baseRepo.ID != headRepo.ID {
|
||||
srcRef = fmt.Sprintf("%s/%s:%s", headRepo.OwnerName, headRepo.Name, headBranch)
|
||||
}
|
||||
resp := testPullCreate(t, session, baseRepo.OwnerName, baseRepo.Name, false, baseRepo.DefaultBranch, srcRef, title)
|
||||
elem := strings.Split(test.RedirectURL(resp), "/")
|
||||
// return pull request ID
|
||||
return elem[4]
|
||||
}
|
||||
|
||||
func prepareRepoPR(t *testing.T, baseSession, headSession *TestSession, baseRepo, headRepo *repo_model.Repository) {
|
||||
// create opening PR
|
||||
testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "opening-pr", http.StatusSeeOther)
|
||||
testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "opening-pr", "opening pr")
|
||||
|
||||
// create closed PR
|
||||
testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "closed-pr", http.StatusSeeOther)
|
||||
prID := testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "closed-pr", "closed pr")
|
||||
testIssueClose(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID)
|
||||
|
||||
// create closed PR with deleted branch
|
||||
testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "closed-pr-deleted", http.StatusSeeOther)
|
||||
prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "closed-pr-deleted", "closed pr with deleted branch")
|
||||
testIssueClose(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID)
|
||||
testUIDeleteBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "closed-pr-deleted")
|
||||
|
||||
// create merged PR
|
||||
testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "merged-pr", http.StatusSeeOther)
|
||||
prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "merged-pr", "merged pr")
|
||||
testAPINewFile(t, headSession, headRepo.OwnerName, headRepo.Name, "merged-pr", fmt.Sprintf("new-commit-%s.txt", headRepo.Name), "new-commit")
|
||||
testPullMerge(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID, repo_model.MergeStyleRebaseMerge, false)
|
||||
|
||||
// create merged PR with deleted branch
|
||||
testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, "branch/new-commit", "merged-pr-deleted", http.StatusSeeOther)
|
||||
prID = testCreatePullToDefaultBranch(t, baseSession, baseRepo, headRepo, "merged-pr-deleted", "merged pr with deleted branch")
|
||||
testAPINewFile(t, headSession, headRepo.OwnerName, headRepo.Name, "merged-pr-deleted", fmt.Sprintf("new-commit-%s-2.txt", headRepo.Name), "new-commit")
|
||||
testPullMerge(t, baseSession, baseRepo.OwnerName, baseRepo.Name, prID, repo_model.MergeStyleRebaseMerge, true)
|
||||
}
|
||||
|
||||
func checkRecentlyPushedNewBranches(t *testing.T, session *TestSession, repoPath string, expected []string) {
|
||||
branches := make([]string, 0, 2)
|
||||
req := NewRequest(t, "GET", repoPath)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
doc.doc.Find(".ui.positive.message div a").Each(func(index int, branch *goquery.Selection) {
|
||||
branches = append(branches, branch.Text())
|
||||
})
|
||||
assert.Equal(t, expected, branches)
|
||||
}
|
||||
|
||||
func TestRecentlyPushedNewBranches(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user1Session := loginUser(t, "user1")
|
||||
user2Session := loginUser(t, "user2")
|
||||
user12Session := loginUser(t, "user12")
|
||||
user13Session := loginUser(t, "user13")
|
||||
|
||||
// prepare branch and PRs in original repo
|
||||
repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
|
||||
prepareBranch(t, user12Session, repo10)
|
||||
prepareRepoPR(t, user12Session, user12Session, repo10, repo10)
|
||||
|
||||
// outdated new branch should not be displayed
|
||||
checkRecentlyPushedNewBranches(t, user12Session, "user12/repo10", []string{"new-commit"})
|
||||
|
||||
// create a fork repo in public org
|
||||
testRepoFork(t, user12Session, repo10.OwnerName, repo10.Name, "org25", "org25_fork_repo10", "new-commit")
|
||||
orgPublicForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 25, Name: "org25_fork_repo10"})
|
||||
prepareRepoPR(t, user12Session, user12Session, repo10, orgPublicForkRepo)
|
||||
|
||||
// user12 is the owner of the repo10 and the organization org25
|
||||
// in repo10, user12 has opening/closed/merged pr and closed/merged pr with deleted branch
|
||||
checkRecentlyPushedNewBranches(t, user12Session, "user12/repo10", []string{"org25/org25_fork_repo10:new-commit", "new-commit"})
|
||||
|
||||
userForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
|
||||
testCtx := NewAPITestContext(t, repo10.OwnerName, repo10.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
t.Run("AddUser13AsCollaborator", doAPIAddCollaborator(testCtx, "user13", perm.AccessModeWrite))
|
||||
prepareBranch(t, user13Session, userForkRepo)
|
||||
prepareRepoPR(t, user13Session, user13Session, repo10, userForkRepo)
|
||||
|
||||
// create branch with same name in different repo by user13
|
||||
testCreateBranch(t, user13Session, repo10.OwnerName, repo10.Name, "branch/new-commit", "same-name-branch", http.StatusSeeOther)
|
||||
testCreateBranch(t, user13Session, userForkRepo.OwnerName, userForkRepo.Name, "branch/new-commit", "same-name-branch", http.StatusSeeOther)
|
||||
testCreatePullToDefaultBranch(t, user13Session, repo10, userForkRepo, "same-name-branch", "same name branch pr")
|
||||
|
||||
// user13 pushed 2 branches with the same name in repo10 and repo11
|
||||
// and repo11's branch has a pr, but repo10's branch doesn't
|
||||
// in this case, we should get repo10's branch but not repo11's branch
|
||||
checkRecentlyPushedNewBranches(t, user13Session, "user12/repo10", []string{"same-name-branch", "user13/repo11:new-commit"})
|
||||
|
||||
// create a fork repo in private org
|
||||
testRepoFork(t, user1Session, repo10.OwnerName, repo10.Name, "private_org35", "org35_fork_repo10", "new-commit")
|
||||
orgPrivateForkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 35, Name: "org35_fork_repo10"})
|
||||
prepareRepoPR(t, user1Session, user1Session, repo10, orgPrivateForkRepo)
|
||||
|
||||
// user1 is the owner of private_org35 and no write permission to repo10
|
||||
// so user1 can only see the branch in org35_fork_repo10
|
||||
checkRecentlyPushedNewBranches(t, user1Session, "user12/repo10", []string{"private_org35/org35_fork_repo10:new-commit"})
|
||||
|
||||
// user2 push a branch in private_org35
|
||||
testCreateBranch(t, user2Session, orgPrivateForkRepo.OwnerName, orgPrivateForkRepo.Name, "branch/new-commit", "user-read-permission", http.StatusSeeOther)
|
||||
// convert write permission to read permission for code unit
|
||||
token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteOrganization)
|
||||
req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", 24), &api.EditTeamOption{
|
||||
Name: "team24",
|
||||
UnitsMap: map[string]string{"repo.code": "read"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
teamUnit := unittest.AssertExistsAndLoadBean(t, &org_model.TeamUnit{TeamID: 24, Type: unit.TypeCode})
|
||||
assert.Equal(t, perm.AccessModeRead, teamUnit.AccessMode)
|
||||
// user2 can see the branch as it is created by user2
|
||||
checkRecentlyPushedNewBranches(t, user2Session, "user12/repo10", []string{"private_org35/org35_fork_repo10:user-read-permission"})
|
||||
})
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName string) *httptest.ResponseRecorder {
|
||||
func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName, forkBranch string) *httptest.ResponseRecorder {
|
||||
forkOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: forkOwnerName})
|
||||
|
||||
// Step0: check the existence of the to-fork repo
|
||||
@ -41,9 +41,10 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO
|
||||
_, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", forkOwner.ID)).Attr("data-value")
|
||||
assert.True(t, exists, fmt.Sprintf("Fork owner '%s' is not present in select box", forkOwnerName))
|
||||
req = NewRequestWithValues(t, "POST", link, map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"uid": fmt.Sprintf("%d", forkOwner.ID),
|
||||
"repo_name": forkRepoName,
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"uid": fmt.Sprintf("%d", forkOwner.ID),
|
||||
"repo_name": forkRepoName,
|
||||
"fork_single_branch": forkBranch,
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
@ -57,13 +58,13 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO
|
||||
func TestRepoFork(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
}
|
||||
|
||||
func TestRepoForkToOrg(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user2")
|
||||
testRepoFork(t, session, "user2", "repo1", "org3", "repo1")
|
||||
testRepoFork(t, session, "user2", "repo1", "org3", "repo1", "")
|
||||
|
||||
// Check that no more forking is allowed as user2 owns repository
|
||||
// and org3 organization that owner user2 is also now has forked this repository
|
||||
|
@ -18,7 +18,7 @@
|
||||
/* other variables */
|
||||
--border-radius: 4px;
|
||||
--border-radius-medium: 6px;
|
||||
--border-radius-circle: 50%;
|
||||
--border-radius-full: 99999px; /* TODO: use calc(infinity * 1px) */
|
||||
--opacity-disabled: 0.55;
|
||||
--height-loading: 16rem;
|
||||
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
|
||||
@ -1166,7 +1166,7 @@ overflow-menu .ui.label {
|
||||
|
||||
.color-icon {
|
||||
display: inline-block;
|
||||
border-radius: var(--border-radius-circle);
|
||||
border-radius: var(--border-radius-full);
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
border-color: var(--color-secondary) var(--color-secondary) var(--color-secondary-dark-8) var(--color-secondary-dark-8);
|
||||
border-radius: var(--border-radius-circle);
|
||||
border-radius: var(--border-radius-full);
|
||||
}
|
||||
|
||||
.is-loading.loading-icon-2px::after {
|
||||
|
@ -790,7 +790,7 @@ td .commit-summary {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
background-color: var(--color-timeline);
|
||||
border-radius: var(--border-radius-circle);
|
||||
border-radius: var(--border-radius-full);
|
||||
display: flex;
|
||||
float: left;
|
||||
margin-left: -33px;
|
||||
|
Loading…
Reference in New Issue
Block a user