mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-10-03 08:52:03 +00:00
Update
This commit is contained in:
parent
dc5c8ca750
commit
2410415e60
13 changed files with 452 additions and 52 deletions
|
@ -439,23 +439,7 @@ func (c *Commit) GetBranchName() (string, error) {
|
||||||
|
|
||||||
// GetAllBranches returns a slice with all branches that contains this commit
|
// GetAllBranches returns a slice with all branches that contains this commit
|
||||||
func (c *Commit) GetAllBranches() ([]string, error) {
|
func (c *Commit) GetAllBranches() ([]string, error) {
|
||||||
branchList := make([]string, 0)
|
return c.repo.getBranches(c, 1)
|
||||||
|
|
||||||
cmd := NewCommand(c.repo.Ctx, "branch", "--format=%(refname:short)", "--contains").AddDynamicArguments(c.ID.String())
|
|
||||||
data, _, err := cmd.RunStdString(&RunOpts{Dir: c.repo.Path})
|
|
||||||
if err != nil {
|
|
||||||
return branchList, err
|
|
||||||
}
|
|
||||||
|
|
||||||
branchNames := strings.Split(strings.ReplaceAll(data, "\r\n", "\n"), "\n")
|
|
||||||
for _, branch := range branchNames {
|
|
||||||
branch = strings.TrimSpace(branch)
|
|
||||||
if branch != "" {
|
|
||||||
branchList = append(branchList, branch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return branchList, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitFileStatus represents status of files in a commit.
|
// CommitFileStatus represents status of files in a commit.
|
||||||
|
|
|
@ -10,3 +10,10 @@ type CreateForkOption struct {
|
||||||
// name of the forked repository
|
// name of the forked repository
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncForkInfo information about syncing a fork
|
||||||
|
type SyncForkInfo struct {
|
||||||
|
Allowed bool `json:"allowed"`
|
||||||
|
ForkCommit string `json:"fork_commit"`
|
||||||
|
BaseCommit string `json:"base_commit"`
|
||||||
|
}
|
||||||
|
|
|
@ -1092,6 +1092,9 @@ archive.title_date = This repository has been archived on %s. You can view files
|
||||||
archive.issue.nocomment = This repo is archived. You cannot comment on issues.
|
archive.issue.nocomment = This repo is archived. You cannot comment on issues.
|
||||||
archive.pull.nocomment = This repo is archived. You cannot comment on pull requests.
|
archive.pull.nocomment = This repo is archived. You cannot comment on pull requests.
|
||||||
|
|
||||||
|
sync_fork.text = This branch is behind %s
|
||||||
|
sync_fork.button = Sync
|
||||||
|
|
||||||
form.reach_limit_of_creation_1 = The owner has already reached the limit of %d repository.
|
form.reach_limit_of_creation_1 = The owner has already reached the limit of %d repository.
|
||||||
form.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories.
|
form.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories.
|
||||||
form.name_reserved = The repository name "%s" is reserved.
|
form.name_reserved = The repository name "%s" is reserved.
|
||||||
|
|
|
@ -1342,6 +1342,9 @@ func Routes() *web.Route {
|
||||||
m.Delete("", repo.DeleteAvatar)
|
m.Delete("", repo.DeleteAvatar)
|
||||||
}, reqAdmin(), reqToken())
|
}, reqAdmin(), reqToken())
|
||||||
m.Group("/sync_fork", func() {
|
m.Group("/sync_fork", func() {
|
||||||
|
m.Get("", repo.SyncForkDefaultInfo)
|
||||||
|
m.Post("", repo.SyncForkDefault)
|
||||||
|
m.Get("/{branch}", repo.SyncForkBranchInfo)
|
||||||
m.Post("/{branch}", repo.SyncForkBranch)
|
m.Post("/{branch}", repo.SyncForkBranch)
|
||||||
}, reqToken(), reqRepoWriter(unit.TypeCode))
|
}, reqToken(), reqRepoWriter(unit.TypeCode))
|
||||||
}, repoAssignment())
|
}, repoAssignment())
|
||||||
|
|
|
@ -3,15 +3,64 @@ package repo
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyncForkBranch syncs a fork branch with the base branch
|
func getSyncForkInfo(ctx *context.APIContext, branch string) {
|
||||||
func SyncForkBranch(ctx *context.APIContext) {
|
if !ctx.Repo.Repository.IsFork {
|
||||||
// swagger:operation POST /repos/{owner}/{repo}/sync_fork/{branch} repository repoSyncForkBranch
|
ctx.Error(http.StatusBadRequest, "NoFork", "The Repo must be a fork")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
syncForkInfo, err := repo_service.GetSyncForkInfo(ctx, ctx.Repo.Repository, branch)
|
||||||
|
if err != nil {
|
||||||
|
if git_model.IsErrBranchNotExist(err) {
|
||||||
|
ctx.NotFound(err, branch)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetSyncForkInfo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, syncForkInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncForkBranchInfo returns information about syncing the default fork branch with the base branch
|
||||||
|
func SyncForkDefaultInfo(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/sync_fork repository repoSyncForkDefaultInfo
|
||||||
// ---
|
// ---
|
||||||
// summary: Syncs a fork
|
// summary: Gets information about syncing the fork default branch with the base branch
|
||||||
|
// 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
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/SyncForkInfo"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
getSyncForkInfo(ctx, ctx.Repo.Repository.DefaultBranch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncForkBranchInfo returns information about syncing a fork branch with the base branch
|
||||||
|
func SyncForkBranchInfo(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/sync_fork/{branch} repository repoSyncForkBranchInfo
|
||||||
|
// ---
|
||||||
|
// summary: Gets information about syncing a fork branch with the base branch
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// parameters:
|
// parameters:
|
||||||
|
@ -31,24 +80,103 @@ func SyncForkBranch(ctx *context.APIContext) {
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// responses:
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/SyncForkInfo"
|
||||||
// "400":
|
// "400":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "204":
|
|
||||||
// "$ref": "#/responses/empty"
|
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
getSyncForkInfo(ctx, ctx.Params("branch"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncForkBranch(ctx *context.APIContext, branch string) {
|
||||||
if !ctx.Repo.Repository.IsFork {
|
if !ctx.Repo.Repository.IsFork {
|
||||||
ctx.Error(http.StatusBadRequest, "NoFork", "The Repo must be a fork")
|
ctx.Error(http.StatusBadRequest, "NoFork", "The Repo must be a fork")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
branch := ctx.Params("branch")
|
syncForkInfo, err := repo_service.GetSyncForkInfo(ctx, ctx.Repo.Repository, branch)
|
||||||
|
|
||||||
err := repo_service.SyncFork(ctx, ctx.Doer, ctx.Repo.Repository, branch)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
|
if git_model.IsErrBranchNotExist(err) {
|
||||||
|
ctx.NotFound(err, branch)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetSyncForkInfo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !syncForkInfo.Allowed {
|
||||||
|
ctx.Error(http.StatusBadRequest, "NotAllowed", "You can't sync this branch")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo_service.SyncFork(ctx, ctx.Doer, ctx.Repo.Repository, branch)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "SyncFork", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncForkBranch syncs the default of a fork with the base branch
|
||||||
|
func SyncForkDefault(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/sync_fork repository repoSyncForkDefault
|
||||||
|
// ---
|
||||||
|
// summary: Syncs the default branch of a fork with the base branch
|
||||||
|
// 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
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
syncForkBranch(ctx, ctx.Repo.Repository.DefaultBranch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncForkBranch syncs a fork branch with the base branch
|
||||||
|
func SyncForkBranch(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/sync_fork/{branch} repository repoSyncForkBranch
|
||||||
|
// ---
|
||||||
|
// summary: Syncs a fork branch with the base branch
|
||||||
|
// 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: The branch
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
syncForkBranch(ctx, ctx.Params("branch"))
|
||||||
|
}
|
||||||
|
|
|
@ -421,3 +421,10 @@ type swaggerBlockedUserList struct {
|
||||||
// in:body
|
// in:body
|
||||||
Body []api.BlockedUser `json:"body"`
|
Body []api.BlockedUser `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncForkInfo
|
||||||
|
// swagger:response SyncForkInfo
|
||||||
|
type swaggerSyncForkInfo struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.SyncForkInfo `json:"body"`
|
||||||
|
}
|
||||||
|
|
|
@ -739,13 +739,25 @@ func PrepareBranchList(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SyncFork(ctx *context.Context) {
|
func SyncFork(ctx *context.Context) {
|
||||||
|
redirectURL := fmt.Sprintf("%s/src/branch/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(ctx.Repo.BranchName))
|
||||||
branch := ctx.Params("branch")
|
branch := ctx.Params("branch")
|
||||||
|
|
||||||
err := repo_service.SyncFork(ctx, ctx.Doer, ctx.Repo.Repository, branch)
|
syncForkInfo, err := repo_service.GetSyncForkInfo(ctx, ctx.Repo.Repository, branch)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetSyncForkInfo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !syncForkInfo.Allowed {
|
||||||
|
ctx.Redirect(redirectURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo_service.SyncFork(ctx, ctx.Doer, ctx.Repo.Repository, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SyncFork", err)
|
ctx.ServerError("SyncFork", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Redirect(fmt.Sprintf("%s/src/branch/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(ctx.Repo.BranchName)))
|
ctx.Redirect(redirectURL)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,8 +49,8 @@ import (
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/routers/web/feed"
|
"code.gitea.io/gitea/routers/web/feed"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
files_service "code.gitea.io/gitea/services/repository/files"
|
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
files_service "code.gitea.io/gitea/services/repository/files"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
|
|
||||||
|
@ -1115,14 +1115,14 @@ PostRecentBranchCheck:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
canSync, err := repo_service.CanSyncFork(ctx, ctx.Repo.Repository, ctx.Repo.BranchName)
|
syncForkInfo, err := repo_service.GetSyncForkInfo(ctx, ctx.Repo.Repository, ctx.Repo.BranchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("CanSync", err)
|
ctx.ServerError("CanSync", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if canSync {
|
if syncForkInfo.Allowed {
|
||||||
ctx.Data["CanSyncFork"] = canSync
|
ctx.Data["CanSyncFork"] = true
|
||||||
ctx.Data["SyncForkLink"] = fmt.Sprintf("%s/sync_fork/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(ctx.Repo.BranchName))
|
ctx.Data["SyncForkLink"] = fmt.Sprintf("%s/sync_fork/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(ctx.Repo.BranchName))
|
||||||
ctx.Data["BaseBranchLink"] = fmt.Sprintf("%s/src/branch/%s", ctx.Repo.Repository.BaseRepo.HTMLURL(), util.PathEscapeSegments(ctx.Repo.BranchName))
|
ctx.Data["BaseBranchLink"] = fmt.Sprintf("%s/src/branch/%s", ctx.Repo.Repository.BaseRepo.HTMLURL(), util.PathEscapeSegments(ctx.Repo.BranchName))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,7 +12,9 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyncFork syncs a branch of a fork with the base repo
|
// SyncFork syncs a branch of a fork with the base repo
|
||||||
|
@ -23,11 +28,15 @@ func SyncFork(ctx context.Context, doer *user_model.User, repo *repo_model.Repos
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer repo_module.RemoveTemporaryPath(tmpPath)
|
defer func() {
|
||||||
|
if err := repo_module.RemoveTemporaryPath(tmpPath); err != nil {
|
||||||
|
log.Error("SyncFork: RemoveTemporaryPath: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = git.NewCommand(ctx, "clone", "-b").AddDynamicArguments(branch, repo.RepoPath(), tmpPath).Run(&git.RunOpts{Dir: tmpPath})
|
err = git.Clone(ctx, repo.RepoPath(), tmpPath, git.CloneRepoOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Clone: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(ctx, tmpPath)
|
gitRepo, err := git.OpenRepository(ctx, tmpPath)
|
||||||
|
@ -71,51 +80,69 @@ func SyncFork(ctx context.Context, doer *user_model.User, repo *repo_model.Repos
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanSyncFork returns if a branch of the fork can be synced with the base repo
|
// CanSyncFork returns inofmrtaion about syncing a fork
|
||||||
func CanSyncFork(ctx context.Context, repo *repo_model.Repository, branch string) (bool, error) {
|
func GetSyncForkInfo(ctx context.Context, repo *repo_model.Repository, branch string) (*api.SyncForkInfo, error) {
|
||||||
|
info := new(api.SyncForkInfo)
|
||||||
|
info.Allowed = false
|
||||||
|
|
||||||
|
if !repo.IsFork {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.GetBaseRepo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
forkBranch, err := git_model.GetBranch(ctx, repo.ID, branch)
|
forkBranch, err := git_model.GetBranch(ctx, repo.ID, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info.ForkCommit = forkBranch.CommitID
|
||||||
|
|
||||||
baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, branch)
|
baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git_model.IsErrBranchNotExist(err) {
|
if git_model.IsErrBranchNotExist(err) {
|
||||||
// If the base repo don't have the branch, we don't need to continue
|
// If the base repo don't have the branch, we don't need to continue
|
||||||
return false, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info.BaseCommit = baseBranch.CommitID
|
||||||
|
|
||||||
// If both branches has the same latest commit, we don't need to sync
|
// If both branches has the same latest commit, we don't need to sync
|
||||||
if forkBranch.CommitID == baseBranch.CommitID {
|
if forkBranch.CommitID == baseBranch.CommitID {
|
||||||
return false, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the fork has newer commits, we can't sync
|
// If the fork has newer commits, we can't sync
|
||||||
if forkBranch.CommitTime >= baseBranch.CommitTime {
|
if forkBranch.CommitTime >= baseBranch.CommitTime {
|
||||||
return false, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the latest commit of the fork is also in the base
|
// Check if the latest commit of the fork is also in the base
|
||||||
gitRepo, err := git.OpenRepository(ctx, repo.BaseRepo.RepoPath())
|
gitRepo, err := git.OpenRepository(ctx, repo.BaseRepo.RepoPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
commit, err := gitRepo.GetCommit(forkBranch.CommitID)
|
commit, err := gitRepo.GetCommit(forkBranch.CommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
if git.IsErrNotExist(err) {
|
||||||
return false, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
branchList, err := commit.GetAllBranches()
|
branchList, err := commit.GetAllBranches()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return slices.Contains(branchList, branch), nil
|
info.Allowed = slices.Contains(branchList, branch)
|
||||||
|
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,10 +182,10 @@
|
||||||
{{if .CanSyncFork}}
|
{{if .CanSyncFork}}
|
||||||
<div class="ui positive message gt-df gt-ac">
|
<div class="ui positive message gt-df gt-ac">
|
||||||
<div class="gt-f1">
|
<div class="gt-f1">
|
||||||
This branch is behind <a href="{{.BaseBranchLink}}">{{printf "%s/%s:%s" .Repository.BaseRepo.OwnerName .Repository.BaseRepo.Name .BranchName}}</a>
|
{{ctx.Locale.Tr "repo.sync_fork.text" (printf "<a href='%s'>%s/%s:%s</a>" .BaseBranchLink .Repository.BaseRepo.OwnerName .Repository.BaseRepo.Name .BranchName | Safe)}}
|
||||||
</div>
|
</div>
|
||||||
<a role="button" class="ui compact positive button gt-m-0" href="{{.SyncForkLink}}">
|
<a role="button" class="ui compact positive button gt-m-0" href="{{.SyncForkLink}}">
|
||||||
Sync
|
{{ctx.Locale.Tr "repo.sync_fork.button"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
152
templates/swagger/v1_json.tmpl
generated
152
templates/swagger/v1_json.tmpl
generated
|
@ -13388,7 +13388,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/repos/{owner}/{repo}/sync_fork/{branch}": {
|
"/repos/{owner}/{repo}/sync_fork": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Gets information about syncing the fork default branch with the base branch",
|
||||||
|
"operationId": "repoSyncForkDefaultInfo",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/SyncForkInfo"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -13396,7 +13433,90 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"repository"
|
"repository"
|
||||||
],
|
],
|
||||||
"summary": "Syncs a fork",
|
"summary": "Syncs the default branch of a fork with the base branch",
|
||||||
|
"operationId": "repoSyncForkDefault",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/sync_fork/{branch}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Gets information about syncing a fork branch with the base branch",
|
||||||
|
"operationId": "repoSyncForkBranchInfo",
|
||||||
|
"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": "The branch",
|
||||||
|
"name": "branch",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/SyncForkInfo"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Syncs a fork branch with the base branch",
|
||||||
"operationId": "repoSyncForkBranch",
|
"operationId": "repoSyncForkBranch",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
@ -23232,6 +23352,25 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"SyncForkInfo": {
|
||||||
|
"description": "SyncForkInfo information about syncing a fork",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allowed": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Allowed"
|
||||||
|
},
|
||||||
|
"base_commit": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "BaseCommit"
|
||||||
|
},
|
||||||
|
"fork_commit": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "ForkCommit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"Tag": {
|
"Tag": {
|
||||||
"description": "Tag represents a repository tag",
|
"description": "Tag represents a repository tag",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -24797,6 +24936,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"SyncForkInfo": {
|
||||||
|
"description": "SyncForkInfo",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/SyncForkInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Tag": {
|
"Tag": {
|
||||||
"description": "Tag",
|
"description": "Tag",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
|
80
tests/integration/api_repo_sync_fork_test.go
Normal file
80
tests/integration/api_repo_sync_fork_test.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func syncForkTest(t *testing.T, forkName, urlPart string) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20})
|
||||||
|
|
||||||
|
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
baseUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: baseRepo.OwnerID})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
/// Create a new fork
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseUser.Name, baseRepo.LowerName), &api.CreateForkOption{Name: &forkName}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusAccepted)
|
||||||
|
|
||||||
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/%s", user.Name, forkName, urlPart).AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var syncForkInfo *api.SyncForkInfo
|
||||||
|
DecodeJSON(t, resp, &syncForkInfo)
|
||||||
|
|
||||||
|
// This is a new fork, so the commits in both branches should be the same
|
||||||
|
assert.False(t, syncForkInfo.Allowed)
|
||||||
|
assert.Equal(t, syncForkInfo.BaseCommit, syncForkInfo.ForkCommit)
|
||||||
|
|
||||||
|
// Make a commit on the base branch
|
||||||
|
err := createOrReplaceFileInBranch(baseUser, baseRepo, "sync_fork.txt", "master", "Hello")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/%s", user.Name, forkName, urlPart).AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
DecodeJSON(t, resp, &syncForkInfo)
|
||||||
|
|
||||||
|
// The commits should no longer be the same and we can sync
|
||||||
|
assert.True(t, syncForkInfo.Allowed)
|
||||||
|
assert.NotEqual(t, syncForkInfo.BaseCommit, syncForkInfo.ForkCommit)
|
||||||
|
|
||||||
|
// Sync the fork
|
||||||
|
req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/%s", user.Name, forkName, urlPart).AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/%s", user.Name, forkName, urlPart).AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
DecodeJSON(t, resp, &syncForkInfo)
|
||||||
|
|
||||||
|
// After the sync both commits should be the same again
|
||||||
|
assert.False(t, syncForkInfo.Allowed)
|
||||||
|
assert.Equal(t, syncForkInfo.BaseCommit, syncForkInfo.ForkCommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIRepoSyncForkDefault(t *testing.T) {
|
||||||
|
syncForkTest(t, "SyncForkDefault", "sync_fork")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIRepoSyncForkBranch(t *testing.T) {
|
||||||
|
syncForkTest(t, "SyncForkBranch", "sync_fork/master")
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import (
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
files_service "code.gitea.io/gitea/services/repository/files"
|
files_service "code.gitea.io/gitea/services/repository/files"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue