mirror of
https://github.com/go-gitea/gitea
synced 2025-02-07 05:57:01 +01:00
Merge branch 'main' into lunny/merge_getuserfork
This commit is contained in:
commit
09e02c1c4f
6
.github/workflows/release-nightly.yml
vendored
6
.github/workflows/release-nightly.yml
vendored
@ -10,7 +10,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
nightly-binary:
|
||||
runs-on: nscloud
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
@ -58,7 +58,7 @@ jobs:
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
nightly-docker-rootful:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
@ -95,7 +95,7 @@ jobs:
|
||||
push: true
|
||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||
nightly-docker-rootless:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
|
6
.github/workflows/release-tag-rc.yml
vendored
6
.github/workflows/release-tag-rc.yml
vendored
@ -11,7 +11,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
binary:
|
||||
runs-on: nscloud
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
docker-rootful:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
@ -99,7 +99,7 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
docker-rootless:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
|
6
.github/workflows/release-tag-version.yml
vendored
6
.github/workflows/release-tag-version.yml
vendored
@ -13,7 +13,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
binary:
|
||||
runs-on: nscloud
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
@ -70,7 +70,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
docker-rootful:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
@ -105,7 +105,7 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
docker-rootless:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
|
@ -37,6 +37,7 @@ type ActionRun struct {
|
||||
TriggerUser *user_model.User `xorm:"-"`
|
||||
ScheduleID int64
|
||||
Ref string `xorm:"index"` // the commit/tag/… that caused the run
|
||||
IsRefDeleted bool `xorm:"-"`
|
||||
CommitSHA string
|
||||
IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
|
||||
NeedApproval bool // may need approval if it's a fork pull request
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
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/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
@ -169,9 +170,22 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
|
||||
return &branch, nil
|
||||
}
|
||||
|
||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string) ([]*Branch, error) {
|
||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
|
||||
branches := make([]*Branch, 0, len(branchNames))
|
||||
return branches, db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames).Find(&branches)
|
||||
|
||||
sess := db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames)
|
||||
if !includeDeleted {
|
||||
sess.And("is_deleted=?", false)
|
||||
}
|
||||
return branches, sess.Find(&branches)
|
||||
}
|
||||
|
||||
func BranchesToNamesSet(branches []*Branch) container.Set[string] {
|
||||
names := make(container.Set[string], len(branches))
|
||||
for _, branch := range branches {
|
||||
names.Add(branch.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func AddBranches(ctx context.Context, branches []*Branch) error {
|
||||
|
@ -474,3 +474,17 @@ func (c *Commit) GetRepositoryDefaultPublicGPGKey(forceUpdate bool) (*GPGSetting
|
||||
}
|
||||
return c.repo.GetDefaultPublicGPGKey(forceUpdate)
|
||||
}
|
||||
|
||||
func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) bool {
|
||||
minLen := util.OptionalArg(minLength, objFmt.FullLength())
|
||||
if len(s) < minLen || len(s) > objFmt.FullLength() {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
|
||||
if !isHex {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -142,7 +142,6 @@ func (ref RefName) RemoteName() string {
|
||||
|
||||
// ShortName returns the short name of the reference name
|
||||
func (ref RefName) ShortName() string {
|
||||
refName := string(ref)
|
||||
if ref.IsBranch() {
|
||||
return ref.BranchName()
|
||||
}
|
||||
@ -158,8 +157,7 @@ func (ref RefName) ShortName() string {
|
||||
if ref.IsFor() {
|
||||
return ref.ForBranchName()
|
||||
}
|
||||
|
||||
return refName
|
||||
return string(ref) // usually it is a commit ID
|
||||
}
|
||||
|
||||
// RefGroup returns the group type of the reference
|
||||
|
@ -61,3 +61,31 @@ func parseTags(refs []string) []string {
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// UnstableGuessRefByShortName does the best guess to see whether a "short name" provided by user is a branch, tag or commit.
|
||||
// It could guess wrongly if the input is already ambiguous. For example:
|
||||
// * "refs/heads/the-name" vs "refs/heads/refs/heads/the-name"
|
||||
// * "refs/tags/1234567890" vs commit "1234567890"
|
||||
// In most cases, it SHOULD AVOID using this function, unless there is an irresistible reason (eg: make API friendly to end users)
|
||||
// If the function is used, the caller SHOULD CHECK the ref type carefully.
|
||||
func (repo *Repository) UnstableGuessRefByShortName(shortName string) RefName {
|
||||
if repo.IsBranchExist(shortName) {
|
||||
return RefNameFromBranch(shortName)
|
||||
}
|
||||
if repo.IsTagExist(shortName) {
|
||||
return RefNameFromTag(shortName)
|
||||
}
|
||||
if strings.HasPrefix(shortName, "refs/") {
|
||||
if repo.IsReferenceExist(shortName) {
|
||||
return RefName(shortName)
|
||||
}
|
||||
}
|
||||
commit, err := repo.GetCommit(shortName)
|
||||
if err == nil {
|
||||
commitIDString := commit.ID.String()
|
||||
if strings.HasPrefix(commitIDString, shortName) {
|
||||
return RefName(commitIDString)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func TestLockAndDo(t *testing.T) {
|
||||
}
|
||||
|
||||
func testLockAndDo(t *testing.T) {
|
||||
const concurrency = 1000
|
||||
const concurrency = 50
|
||||
|
||||
ctx := context.Background()
|
||||
count := 0
|
||||
|
@ -278,6 +278,16 @@ type CreateBranchRepoOption struct {
|
||||
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
|
||||
}
|
||||
|
||||
// UpdateBranchRepoOption options when updating a branch in a repository
|
||||
// swagger:model
|
||||
type UpdateBranchRepoOption struct {
|
||||
// New branch name
|
||||
//
|
||||
// required: true
|
||||
// unique: true
|
||||
Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
|
||||
}
|
||||
|
||||
// TransferRepoOption options when transfer a repository's ownership
|
||||
// swagger:model
|
||||
type TransferRepoOption struct {
|
||||
|
@ -1195,6 +1195,7 @@ func Routes() *web.Router {
|
||||
m.Get("/*", repo.GetBranch)
|
||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
|
||||
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
|
||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
||||
m.Group("/branch_protections", func() {
|
||||
m.Get("", repo.ListBranchProtections)
|
||||
|
@ -386,6 +386,77 @@ func ListBranches(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, apiBranches)
|
||||
}
|
||||
|
||||
// UpdateBranch updates a repository's branch.
|
||||
func UpdateBranch(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
|
||||
// ---
|
||||
// summary: Update a branch
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: branch
|
||||
// in: path
|
||||
// description: name of the branch
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateBranchRepoOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
|
||||
|
||||
oldName := ctx.PathParam("*")
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if repo.IsEmpty {
|
||||
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
|
||||
return
|
||||
}
|
||||
|
||||
if repo.IsMirror {
|
||||
ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
|
||||
return
|
||||
}
|
||||
if msg == "target_exist" {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||
return
|
||||
}
|
||||
if msg == "from_not_exist" {
|
||||
ctx.Error(http.StatusNotFound, "", "Branch doesn't exist.")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetBranchProtection gets a branch protection
|
||||
func GetBranchProtection(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
|
||||
|
@ -64,22 +64,19 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
_, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{
|
||||
Base: infos[0],
|
||||
Head: infos[1],
|
||||
})
|
||||
compareResult, closer := parseCompareInfo(ctx, api.CreatePullRequestOption{Base: infos[0], Head: infos[1]})
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
defer closer()
|
||||
|
||||
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
||||
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
||||
|
||||
apiCommits := make([]*api.Commit, 0, len(ci.Commits))
|
||||
apiCommits := make([]*api.Commit, 0, len(compareResult.compareInfo.Commits))
|
||||
userCache := make(map[string]*user_model.User)
|
||||
for i := 0; i < len(ci.Commits); i++ {
|
||||
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache,
|
||||
for i := 0; i < len(compareResult.compareInfo.Commits); i++ {
|
||||
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, compareResult.compareInfo.Commits[i], userCache,
|
||||
convert.ToCommitOptions{
|
||||
Stat: true,
|
||||
Verification: verification,
|
||||
@ -93,7 +90,7 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &api.Compare{
|
||||
TotalCommits: len(ci.Commits),
|
||||
TotalCommits: len(compareResult.compareInfo.Commits),
|
||||
Commits: apiCommits,
|
||||
})
|
||||
}
|
||||
|
@ -389,8 +389,7 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
|
||||
form := *web.GetForm(ctx).(*api.CreatePullRequestOption)
|
||||
if form.Head == form.Base {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame",
|
||||
"Invalid PullRequest: There are no changes between the head and the base")
|
||||
ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame", "Invalid PullRequest: There are no changes between the head and the base")
|
||||
return
|
||||
}
|
||||
|
||||
@ -401,14 +400,22 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
)
|
||||
|
||||
// Get repo/branch information
|
||||
headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
|
||||
compareResult, closer := parseCompareInfo(ctx, form)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
defer closer()
|
||||
|
||||
if !compareResult.baseRef.IsBranch() || !compareResult.headRef.IsBranch() {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "BaseHeadInvalidRefType", "Invalid PullRequest: base and head must be branches")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if another PR exists with the same targets
|
||||
existingPr, err := issues_model.GetUnmergedPullRequest(ctx, headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch, issues_model.PullRequestFlowGithub)
|
||||
existingPr, err := issues_model.GetUnmergedPullRequest(ctx, compareResult.headRepo.ID, ctx.Repo.Repository.ID,
|
||||
compareResult.headRef.ShortName(), compareResult.baseRef.ShortName(),
|
||||
issues_model.PullRequestFlowGithub,
|
||||
)
|
||||
if err != nil {
|
||||
if !issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
|
||||
@ -484,13 +491,13 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
DeadlineUnix: deadlineUnix,
|
||||
}
|
||||
pr := &issues_model.PullRequest{
|
||||
HeadRepoID: headRepo.ID,
|
||||
HeadRepoID: compareResult.headRepo.ID,
|
||||
BaseRepoID: repo.ID,
|
||||
HeadBranch: headBranch,
|
||||
BaseBranch: baseBranch,
|
||||
HeadRepo: headRepo,
|
||||
HeadBranch: compareResult.headRef.ShortName(),
|
||||
BaseBranch: compareResult.baseRef.ShortName(),
|
||||
HeadRepo: compareResult.headRepo,
|
||||
BaseRepo: repo,
|
||||
MergeBase: compareInfo.MergeBase,
|
||||
MergeBase: compareResult.compareInfo.MergeBase,
|
||||
Type: issues_model.PullRequestGitea,
|
||||
}
|
||||
|
||||
@ -1080,32 +1087,32 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) {
|
||||
baseRepo := ctx.Repo.Repository
|
||||
type parseCompareInfoResult struct {
|
||||
headRepo *repo_model.Repository
|
||||
headGitRepo *git.Repository
|
||||
compareInfo *git.CompareInfo
|
||||
baseRef git.RefName
|
||||
headRef git.RefName
|
||||
}
|
||||
|
||||
// parseCompareInfo returns non-nil if it succeeds, it always writes to the context and returns nil if it fails
|
||||
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (result *parseCompareInfoResult, closer func()) {
|
||||
var err error
|
||||
// Get compared branches information
|
||||
// format: <base branch>...[<head repo>:]<head branch>
|
||||
// base<-head: master...head:feature
|
||||
// same repo: master...feature
|
||||
baseRepo := ctx.Repo.Repository
|
||||
baseRefToGuess := form.Base
|
||||
|
||||
// TODO: Validate form first?
|
||||
|
||||
baseBranch := form.Base
|
||||
|
||||
var (
|
||||
headUser *user_model.User
|
||||
headBranch string
|
||||
isSameRepo bool
|
||||
err error
|
||||
)
|
||||
|
||||
// If there is no head repository, it means pull request between same repository.
|
||||
headInfos := strings.Split(form.Head, ":")
|
||||
if len(headInfos) == 1 {
|
||||
isSameRepo = true
|
||||
headUser = ctx.Repo.Owner
|
||||
headBranch = headInfos[0]
|
||||
headUser := ctx.Repo.Owner
|
||||
headRefToGuess := form.Head
|
||||
if headInfos := strings.Split(form.Head, ":"); len(headInfos) == 1 {
|
||||
// If there is no head repository, it means pull request between same repository.
|
||||
// Do nothing here because the head variables have been assigned above.
|
||||
} else if len(headInfos) == 2 {
|
||||
// There is a head repository (the head repository could also be the same base repo)
|
||||
headRefToGuess = headInfos[1]
|
||||
headUser, err = user_model.GetUserByName(ctx, headInfos[0])
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
@ -1113,23 +1120,14 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
||||
}
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
headBranch = headInfos[1]
|
||||
// The head repository can also point to the same repo
|
||||
isSameRepo = ctx.Repo.Owner.ID == headUser.ID
|
||||
} else {
|
||||
ctx.NotFound()
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ctx.Repo.PullRequest.SameRepo = isSameRepo
|
||||
log.Trace("Repo path: %q, base branch: %q, head branch: %q", ctx.Repo.GitRepo.Path, baseBranch, headBranch)
|
||||
// Check if base branch is valid.
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
|
||||
ctx.NotFound("BaseNotExist")
|
||||
return nil, nil, nil, "", ""
|
||||
}
|
||||
isSameRepo := ctx.Repo.Owner.ID == headUser.ID
|
||||
|
||||
// Check if current user has fork of repository or in the same repository.
|
||||
headRepo, err := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID)
|
||||
@ -1138,17 +1136,17 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||
return nil, nil, nil, "", ""
|
||||
}
|
||||
if headRepo == nil && !isSameRepo {
|
||||
err := baseRepo.GetBaseRepo(ctx)
|
||||
err = baseRepo.GetBaseRepo(ctx)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check if baseRepo's base repository is the same as headUser's repository.
|
||||
if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID {
|
||||
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
|
||||
ctx.NotFound("GetBaseRepo")
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
// Assign headRepo so it can be used below.
|
||||
headRepo = baseRepo.BaseRepo
|
||||
@ -1158,67 +1156,68 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||
if isSameRepo {
|
||||
headRepo = ctx.Repo.Repository
|
||||
headGitRepo = ctx.Repo.GitRepo
|
||||
closer = func() {} // no need to close the head repo because it shares the base repo
|
||||
} else {
|
||||
headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
closer = func() { _ = headGitRepo.Close() }
|
||||
}
|
||||
defer func() {
|
||||
if result == nil && !isSameRepo {
|
||||
_ = headGitRepo.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
|
||||
permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
headGitRepo.Close()
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return nil, nil, nil, "", ""
|
||||
}
|
||||
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
|
||||
if log.IsTrace() {
|
||||
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v",
|
||||
ctx.Doer,
|
||||
baseRepo,
|
||||
permBase)
|
||||
}
|
||||
headGitRepo.Close()
|
||||
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// user should have permission to read headrepo's codes
|
||||
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
|
||||
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
|
||||
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// user should have permission to read headRepo's codes
|
||||
// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
|
||||
permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
headGitRepo.Close()
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
if !permHead.CanRead(unit.TypeCode) {
|
||||
if log.IsTrace() {
|
||||
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
|
||||
ctx.Doer,
|
||||
headRepo,
|
||||
permHead)
|
||||
}
|
||||
headGitRepo.Close()
|
||||
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.Doer, headRepo, permHead)
|
||||
ctx.NotFound("Can't read headRepo UnitTypeCode")
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check if head branch is valid.
|
||||
if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
|
||||
headGitRepo.Close()
|
||||
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefToGuess)
|
||||
headRef := headGitRepo.UnstableGuessRefByShortName(headRefToGuess)
|
||||
|
||||
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.GitRepo.Path, baseRefToGuess, baseRef, headRefToGuess, headRef)
|
||||
|
||||
baseRefValid := baseRef.IsBranch() || baseRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName), baseRef.ShortName())
|
||||
headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName())
|
||||
// Check if base&head ref are valid.
|
||||
if !baseRefValid || !headRefValid {
|
||||
ctx.NotFound()
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
|
||||
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseRef.ShortName(), headRef.ShortName(), false, false)
|
||||
if err != nil {
|
||||
headGitRepo.Close()
|
||||
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
|
||||
return nil, nil, nil, "", ""
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return headRepo, headGitRepo, compareInfo, baseBranch, headBranch
|
||||
result = &parseCompareInfoResult{headRepo: headRepo, headGitRepo: headGitRepo, compareInfo: compareInfo, baseRef: baseRef, headRef: headRef}
|
||||
return result, closer
|
||||
}
|
||||
|
||||
// UpdatePullRequest merge PR's baseBranch into headBranch
|
||||
|
@ -90,6 +90,8 @@ type swaggerParameterBodies struct {
|
||||
// in:body
|
||||
EditRepoOption api.EditRepoOption
|
||||
// in:body
|
||||
UpdateBranchRepoOption api.UpdateBranchRepoOption
|
||||
// in:body
|
||||
TransferRepoOption api.TransferRepoOption
|
||||
// in:body
|
||||
CreateForkOption api.CreateForkOption
|
||||
|
@ -245,6 +245,10 @@ func List(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := loadIsRefDeleted(ctx, runs); err != nil {
|
||||
log.Error("LoadIsRefDeleted", err)
|
||||
}
|
||||
|
||||
ctx.Data["Runs"] = runs
|
||||
|
||||
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
|
||||
@ -267,6 +271,34 @@ func List(ctx *context.Context) {
|
||||
ctx.HTML(http.StatusOK, tplListActions)
|
||||
}
|
||||
|
||||
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
|
||||
// TODO: move this function to models/actions/run_list.go but now it will result in a circular import.
|
||||
func loadIsRefDeleted(ctx *context.Context, runs actions_model.RunList) error {
|
||||
branches := make(container.Set[string], len(runs))
|
||||
for _, run := range runs {
|
||||
refName := git.RefName(run.Ref)
|
||||
if refName.IsBranch() {
|
||||
branches.Add(refName.ShortName())
|
||||
}
|
||||
}
|
||||
if len(branches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
branchInfos, err := git_model.GetBranches(ctx, ctx.Repo.Repository.ID, branches.Values(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
branchSet := git_model.BranchesToNamesSet(branchInfos)
|
||||
for _, run := range runs {
|
||||
refName := git.RefName(run.Ref)
|
||||
if refName.IsBranch() && !branchSet.Contains(run.Ref) {
|
||||
run.IsRefDeleted = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WorkflowDispatchInput struct {
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@ -136,8 +137,9 @@ type ViewUser struct {
|
||||
}
|
||||
|
||||
type ViewBranch struct {
|
||||
Name string `json:"name"`
|
||||
Link string `json:"link"`
|
||||
Name string `json:"name"`
|
||||
Link string `json:"link"`
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
}
|
||||
|
||||
type ViewJobStep struct {
|
||||
@ -236,6 +238,16 @@ func ViewPost(ctx *context_module.Context) {
|
||||
Name: run.PrettyRef(),
|
||||
Link: run.RefLink(),
|
||||
}
|
||||
refName := git.RefName(run.Ref)
|
||||
if refName.IsBranch() {
|
||||
b, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, refName.ShortName())
|
||||
if err != nil && !git_model.IsErrBranchNotExist(err) {
|
||||
log.Error("GetBranch: %v", err)
|
||||
} else if git_model.IsErrBranchNotExist(err) || (b != nil && b.IsDeleted) {
|
||||
branch.IsDeleted = true
|
||||
}
|
||||
}
|
||||
|
||||
resp.State.Run.Commit = ViewCommit{
|
||||
ShortSha: base.ShortSha(run.CommitSHA),
|
||||
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -114,7 +113,6 @@ func MustAllowPulls(ctx *context.Context) {
|
||||
// User can send pull request if owns a forked repository.
|
||||
if ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
|
||||
ctx.Repo.PullRequest.Allowed = true
|
||||
ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Doer.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,10 +39,9 @@ import (
|
||||
|
||||
// PullRequest contains information to make a pull request
|
||||
type PullRequest struct {
|
||||
BaseRepo *repo_model.Repository
|
||||
Allowed bool
|
||||
SameRepo bool
|
||||
HeadInfoSubURL string // [<user>:]<branch> url segment
|
||||
BaseRepo *repo_model.Repository
|
||||
Allowed bool // it only used by the web tmpl: "PullRequestCtx.Allowed"
|
||||
SameRepo bool // it only used by the web tmpl: "PullRequestCtx.SameRepo"
|
||||
}
|
||||
|
||||
// Repository contains information to operate a repository
|
||||
@ -401,6 +400,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
||||
// RepoAssignment returns a middleware to handle repository assignment
|
||||
func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
|
||||
// FIXME: it should panic in dev/test modes to have a clear behavior
|
||||
log.Trace("RepoAssignment was exec already, skipping second call ...")
|
||||
return nil
|
||||
}
|
||||
@ -697,7 +697,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
ctx.Data["BaseRepo"] = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Repo.Owner.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName)
|
||||
} else if repo.AllowsPulls(ctx) {
|
||||
// Or, this is repository accepts pull requests between branches.
|
||||
canCompare = true
|
||||
@ -705,7 +704,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
ctx.Repo.PullRequest.BaseRepo = repo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
ctx.Repo.PullRequest.SameRepo = true
|
||||
ctx.Repo.PullRequest.HeadInfoSubURL = util.PathEscapeSegments(ctx.Repo.BranchName)
|
||||
}
|
||||
ctx.Data["CanCompareOrPull"] = canCompare
|
||||
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
|
||||
@ -771,20 +769,6 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
|
||||
return ""
|
||||
}
|
||||
|
||||
func isStringLikelyCommitID(objFmt git.ObjectFormat, s string, minLength ...int) bool {
|
||||
minLen := util.OptionalArg(minLength, objFmt.FullLength())
|
||||
if len(s) < minLen || len(s) > objFmt.FullLength() {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
|
||||
if !isHex {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
|
||||
extraRef := util.OptionalArg(optionalExtraRef)
|
||||
reqPath := ctx.PathParam("*")
|
||||
@ -799,7 +783,7 @@ func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (
|
||||
|
||||
// For legacy support only full commit sha
|
||||
parts := strings.Split(reqPath, "/")
|
||||
if isStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
|
||||
if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
|
||||
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
|
||||
repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0], RepoRefCommit
|
||||
@ -849,7 +833,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
||||
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
|
||||
case RepoRefCommit:
|
||||
parts := strings.Split(path, "/")
|
||||
if isStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
|
||||
if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
|
||||
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
|
||||
repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
@ -985,7 +969,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
return cancel
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if isStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
|
||||
} else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
|
||||
ctx.Repo.IsViewCommit = true
|
||||
ctx.Repo.CommitID = refName
|
||||
|
||||
|
@ -305,7 +305,7 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames,
|
||||
}
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
branches, err := git_model.GetBranches(ctx, repoID, branchNames)
|
||||
branches, err := git_model.GetBranches(ctx, repoID, branchNames, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git_model.GetBranches: %v", err)
|
||||
}
|
||||
|
@ -27,10 +27,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
{{if .RefLink}}
|
||||
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}">{{.PrettyRef}}</a>
|
||||
{{if .IsRefDeleted}}
|
||||
<span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</span>
|
||||
{{else}}
|
||||
<span class="ui label run-list-ref gt-ellipsis">{{.PrettyRef}}</span>
|
||||
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</a>
|
||||
{{end}}
|
||||
<div class="run-list-item-right">
|
||||
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince .Updated}}</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
||||
<div id="repo-contributors-chart"
|
||||
data-repo-link="{{.RepoLink}}"
|
||||
data-repo-default-branch-name="{{.Repository.DefaultBranch}}"
|
||||
data-locale-filter-label="{{ctx.Locale.Tr "repo.contributors.contribution_type.filter_label"}}"
|
||||
data-locale-contribution-type-commits="{{ctx.Locale.Tr "repo.contributors.contribution_type.commits"}}"
|
||||
data-locale-contribution-type-additions="{{ctx.Locale.Tr "repo.contributors.contribution_type.additions"}}"
|
||||
|
@ -167,8 +167,8 @@
|
||||
<button class="btn diff-header-popup-btn tw-p-1">{{svg "octicon-kebab-horizontal" 18}}</button>
|
||||
<div class="tippy-target">
|
||||
{{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}}
|
||||
<button class="unescape-button item" data-file-content-elem-id="diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
|
||||
<button class="escape-button tw-hidden item" data-file-content-elem-id="diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
|
||||
<button class="unescape-button item" data-unicode-content-selector="#diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
|
||||
<button class="escape-button tw-hidden item" data-unicode-content-selector="#diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
|
||||
{{end}}
|
||||
{{if and (not $file.IsSubmodule) (not $.PageIsWiki)}}
|
||||
{{if $file.IsDeleted}}
|
||||
|
@ -44,13 +44,13 @@
|
||||
</div>
|
||||
<div class="repo-button-row">
|
||||
{{if .EscapeStatus.Escaped}}
|
||||
<a class="ui small button unescape-button tw-m-0 tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
|
||||
<a class="ui small button escape-button tw-m-0">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
|
||||
<a class="ui small button unescape-button tw-hidden" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
|
||||
<a class="ui small button escape-button" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
|
||||
{{end}}
|
||||
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
|
||||
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}</a>
|
||||
<a class="ui small primary button" href="{{.RepoLink}}/wiki?action=_new">{{ctx.Locale.Tr "repo.wiki.new_page_button"}}</a>
|
||||
<a class="ui small red button tw-m-0 delete-button" href="" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete" data-id="{{.PageURL}}">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
|
||||
<a class="ui small red button delete-button" href="" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete" data-id="{{.PageURL}}">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
73
templates/swagger/v1_json.tmpl
generated
73
templates/swagger/v1_json.tmpl
generated
@ -5045,6 +5045,63 @@
|
||||
"$ref": "#/responses/repoArchivedError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Update a branch",
|
||||
"operationId": "repoUpdateBranch",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the branch",
|
||||
"name": "branch",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/UpdateBranchRepoOption"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"$ref": "#/responses/empty"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
},
|
||||
"422": {
|
||||
"$ref": "#/responses/validationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/collaborators": {
|
||||
@ -24968,6 +25025,22 @@
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"UpdateBranchRepoOption": {
|
||||
"description": "UpdateBranchRepoOption options when updating a branch in a repository",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "New branch name",
|
||||
"type": "string",
|
||||
"uniqueItems": true,
|
||||
"x-go-name": "Name"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"UpdateFileOptions": {
|
||||
"description": "UpdateFileOptions options for updating files\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)",
|
||||
"type": "object",
|
||||
|
@ -5,6 +5,7 @@ package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
@ -186,6 +187,37 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran
|
||||
return resp.Result().StatusCode == status
|
||||
}
|
||||
|
||||
func TestAPIUpdateBranch(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||
t.Run("UpdateBranchWithEmptyRepo", func(t *testing.T) {
|
||||
testAPIUpdateBranch(t, "user10", "repo6", "master", "test", http.StatusNotFound)
|
||||
})
|
||||
t.Run("UpdateBranchWithSameBranchNames", func(t *testing.T) {
|
||||
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "master", http.StatusUnprocessableEntity)
|
||||
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||
})
|
||||
t.Run("UpdateBranchThatAlreadyExists", func(t *testing.T) {
|
||||
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "branch2", http.StatusUnprocessableEntity)
|
||||
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||
})
|
||||
t.Run("UpdateBranchWithNonExistentBranch", func(t *testing.T) {
|
||||
resp := testAPIUpdateBranch(t, "user2", "repo1", "i-dont-exist", "new-branch-name", http.StatusNotFound)
|
||||
assert.Contains(t, resp.Body.String(), "Branch doesn't exist.")
|
||||
})
|
||||
t.Run("RenameBranchNormalScenario", func(t *testing.T) {
|
||||
testAPIUpdateBranch(t, "user2", "repo1", "branch2", "new-branch-name", http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIUpdateBranch(t *testing.T, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder {
|
||||
token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.UpdateBranchRepoOption{
|
||||
Name: to,
|
||||
}).AddTokenAuth(token)
|
||||
return MakeRequest(t, req, expectedHTTPStatus)
|
||||
}
|
||||
|
||||
func TestAPIBranchProtection(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
@ -24,15 +24,27 @@ func TestAPICompareBranches(t *testing.T) {
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
repoName := "repo20"
|
||||
t.Run("CompareBranches", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b").AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var apiResp *api.Compare
|
||||
DecodeJSON(t, resp, &apiResp)
|
||||
|
||||
var apiResp *api.Compare
|
||||
DecodeJSON(t, resp, &apiResp)
|
||||
assert.Equal(t, 2, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 2)
|
||||
})
|
||||
|
||||
assert.Equal(t, 2, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 2)
|
||||
t.Run("CompareCommits", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f").AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var apiResp *api.Compare
|
||||
DecodeJSON(t, resp, &apiResp)
|
||||
|
||||
assert.Equal(t, 1, apiResp.TotalCommits)
|
||||
assert.Len(t, apiResp.Commits, 1)
|
||||
})
|
||||
}
|
||||
|
@ -440,7 +440,7 @@ func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v any) {
|
||||
t.Helper()
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
assert.NoError(t, decoder.Decode(v))
|
||||
require.NoError(t, decoder.Decode(v))
|
||||
}
|
||||
|
||||
func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) {
|
||||
|
@ -429,7 +429,8 @@ export function initRepositoryActionView() {
|
||||
<a class="muted" :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a>
|
||||
</template>
|
||||
<span class="ui label tw-max-w-full" v-if="run.commit.shortSHA">
|
||||
<a class="gt-ellipsis" :href="run.commit.branch.link">{{ run.commit.branch.name }}</a>
|
||||
<span v-if="run.commit.branch.isDeleted" class="gt-ellipsis tw-line-through" :data-tooltip-content="run.commit.branch.name">{{ run.commit.branch.name }}</span>
|
||||
<a v-else class="gt-ellipsis" :href="run.commit.branch.link" :data-tooltip-content="run.commit.branch.name">{{ run.commit.branch.name }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {SvgIcon} from '../svg.ts';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
Chart,
|
||||
Title,
|
||||
@ -26,6 +27,7 @@ import {sleep} from '../utils.ts';
|
||||
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import type {Entries} from 'type-fest';
|
||||
import {pathEscapeSegments} from '../utils/url.ts';
|
||||
|
||||
const customEventListener: Plugin = {
|
||||
id: 'customEventListener',
|
||||
@ -65,6 +67,10 @@ export default {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
repoDefaultBranchName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
isLoading: false,
|
||||
@ -100,6 +106,15 @@ export default {
|
||||
.slice(0, 100);
|
||||
},
|
||||
|
||||
getContributorSearchQuery(contributorEmail: string) {
|
||||
const min = dayjs(this.xAxisMin).format('YYYY-MM-DD');
|
||||
const max = dayjs(this.xAxisMax).format('YYYY-MM-DD');
|
||||
const params = new URLSearchParams({
|
||||
'q': `after:${min}, before:${max}, author:${contributorEmail}`,
|
||||
});
|
||||
return `${this.repoLink}/commits/branch/${pathEscapeSegments(this.repoDefaultBranchName)}/search?${params.toString()}`;
|
||||
},
|
||||
|
||||
async fetchGraphData() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
@ -167,7 +182,7 @@ export default {
|
||||
// for details.
|
||||
user.max_contribution_type += 1;
|
||||
|
||||
filteredData[key] = {...user, weeks: filteredWeeks};
|
||||
filteredData[key] = {...user, weeks: filteredWeeks, email: key};
|
||||
}
|
||||
|
||||
return filteredData;
|
||||
@ -215,7 +230,7 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
updateOtherCharts({chart}: {chart: Chart}, reset?: boolean = false) {
|
||||
updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) {
|
||||
const minVal = chart.options.scales.x.min;
|
||||
const maxVal = chart.options.scales.x.max;
|
||||
if (reset) {
|
||||
@ -381,7 +396,7 @@ export default {
|
||||
<div class="ui top attached header tw-flex tw-flex-1">
|
||||
<b class="ui right">#{{ index + 1 }}</b>
|
||||
<a :href="contributor.home_link">
|
||||
<img class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link">
|
||||
<img class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link" alt="">
|
||||
</a>
|
||||
<div class="tw-ml-2">
|
||||
<a v-if="contributor.home_link !== ''" :href="contributor.home_link"><h4>{{ contributor.name }}</h4></a>
|
||||
@ -389,7 +404,11 @@ export default {
|
||||
{{ contributor.name }}
|
||||
</h4>
|
||||
<p class="tw-text-12 tw-flex tw-gap-1">
|
||||
<strong v-if="contributor.total_commits">{{ contributor.total_commits.toLocaleString() }} {{ locale.contributionType.commits }}</strong>
|
||||
<strong v-if="contributor.total_commits">
|
||||
<a class="silenced" :href="getContributorSearchQuery(contributor.email)">
|
||||
{{ contributor.total_commits.toLocaleString() }} {{ locale.contributionType.commits }}
|
||||
</a>
|
||||
</strong>
|
||||
<strong v-if="contributor.total_additions" class="text green">{{ contributor.total_additions.toLocaleString() }}++ </strong>
|
||||
<strong v-if="contributor.total_deletions" class="text red">
|
||||
{{ contributor.total_deletions.toLocaleString() }}--</strong>
|
||||
|
@ -178,6 +178,7 @@ export function initTextareaEvents(textarea, dropzoneEl) {
|
||||
});
|
||||
textarea.addEventListener('drop', (e) => {
|
||||
if (!e.dataTransfer.files.length) return;
|
||||
if (!dropzoneEl) return;
|
||||
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, e.dataTransfer.files, e);
|
||||
});
|
||||
dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => {
|
||||
|
@ -8,6 +8,7 @@ export async function initRepoContributors() {
|
||||
try {
|
||||
const View = createApp(RepoContributors, {
|
||||
repoLink: el.getAttribute('data-repo-link'),
|
||||
repoDefaultBranchName: el.getAttribute('data-repo-default-branch-name'),
|
||||
locale: {
|
||||
filterLabel: el.getAttribute('data-locale-filter-label'),
|
||||
contributionType: {
|
||||
|
@ -1,27 +1,28 @@
|
||||
import {addDelegatedEventListener, hideElem, queryElemSiblings, showElem, toggleElem} from '../utils/dom.ts';
|
||||
|
||||
export function initUnicodeEscapeButton() {
|
||||
// buttons might appear on these pages: file view (code, rendered markdown), diff (commit, pr conversation, pr diff), blame, wiki
|
||||
addDelegatedEventListener(document, 'click', '.escape-button, .unescape-button, .toggle-escape-button', (btn, e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const fileContentElemId = btn.getAttribute('data-file-content-elem-id');
|
||||
const fileContent = fileContentElemId ?
|
||||
document.querySelector(`#${fileContentElemId}`) :
|
||||
const unicodeContentSelector = btn.getAttribute('data-unicode-content-selector');
|
||||
const container = unicodeContentSelector ?
|
||||
document.querySelector(unicodeContentSelector) :
|
||||
btn.closest('.file-content, .non-diff-file-content');
|
||||
const fileView = fileContent?.querySelectorAll('.file-code, .file-view');
|
||||
const fileView = container.querySelector('.file-code, .file-view') ?? container;
|
||||
if (btn.matches('.escape-button')) {
|
||||
for (const el of fileView) el.classList.add('unicode-escaped');
|
||||
fileView.classList.add('unicode-escaped');
|
||||
hideElem(btn);
|
||||
showElem(queryElemSiblings(btn, '.unescape-button'));
|
||||
} else if (btn.matches('.unescape-button')) {
|
||||
for (const el of fileView) el.classList.remove('unicode-escaped');
|
||||
fileView.classList.remove('unicode-escaped');
|
||||
hideElem(btn);
|
||||
showElem(queryElemSiblings(btn, '.escape-button'));
|
||||
} else if (btn.matches('.toggle-escape-button')) {
|
||||
const isEscaped = fileView[0]?.classList.contains('unicode-escaped');
|
||||
for (const el of fileView) el.classList.toggle('unicode-escaped', !isEscaped);
|
||||
toggleElem(fileContent.querySelectorAll('.unescape-button'), !isEscaped);
|
||||
toggleElem(fileContent.querySelectorAll('.escape-button'), isEscaped);
|
||||
const isEscaped = fileView.classList.contains('unicode-escaped');
|
||||
fileView.classList.toggle('unicode-escaped', !isEscaped);
|
||||
toggleElem(container.querySelectorAll('.unescape-button'), !isEscaped);
|
||||
toggleElem(container.querySelectorAll('.escape-button'), isEscaped);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user