mirror of
https://github.com/go-gitea/gitea.git
synced 2025-12-21 00:15:54 +08:00
Merge branch 'main' into feat/actions-token-permissions
This commit is contained in:
commit
0b6380935f
@ -869,16 +869,6 @@ func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repos
|
|||||||
return repos, db.GetEngine(ctx).In("id", ids).Find(&repos)
|
return repos, db.GetEngine(ctx).In("id", ids).Find(&repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed.
|
|
||||||
func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
|
||||||
has, err := IsRepositoryModelExist(ctx, u, repoName)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
isDir, err := util.IsDir(RepoPath(u.Name, repoName))
|
|
||||||
return has || isDir, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||||
return db.GetEngine(ctx).Get(&Repository{
|
return db.GetEngine(ctx).Get(&Repository{
|
||||||
OwnerID: u.ID,
|
OwnerID: u.ID,
|
||||||
|
|||||||
@ -9,8 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,35 +104,6 @@ func (err ErrRepoFilesAlreadyExist) Unwrap() error {
|
|||||||
return util.ErrAlreadyExist
|
return util.ErrAlreadyExist
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckCreateRepository check if doer could create a repository in new owner
|
|
||||||
func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error {
|
|
||||||
if !doer.CanCreateRepoIn(owner) {
|
|
||||||
return ErrReachLimitOfRepo{owner.MaxRepoCreation}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := IsUsableRepoName(name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
has, err := IsRepositoryModelOrDirExist(ctx, owner, name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
|
||||||
} else if has {
|
|
||||||
return ErrRepoAlreadyExist{owner.Name, name}
|
|
||||||
}
|
|
||||||
|
|
||||||
repoPath := RepoPath(owner.Name, name)
|
|
||||||
isExist, err := util.IsExist(repoPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !overwriteOrAdopt && isExist {
|
|
||||||
return ErrRepoFilesAlreadyExist{owner.Name, name}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
||||||
func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error {
|
func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error {
|
||||||
_, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{
|
_, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{
|
||||||
|
|||||||
@ -189,6 +189,7 @@ type PushOptions struct {
|
|||||||
Remote string
|
Remote string
|
||||||
Branch string
|
Branch string
|
||||||
Force bool
|
Force bool
|
||||||
|
ForceWithLease string
|
||||||
Mirror bool
|
Mirror bool
|
||||||
Env []string
|
Env []string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
@ -197,7 +198,9 @@ type PushOptions struct {
|
|||||||
// Push pushs local commits to given remote branch.
|
// Push pushs local commits to given remote branch.
|
||||||
func Push(ctx context.Context, repoPath string, opts PushOptions) error {
|
func Push(ctx context.Context, repoPath string, opts PushOptions) error {
|
||||||
cmd := gitcmd.NewCommand("push")
|
cmd := gitcmd.NewCommand("push")
|
||||||
if opts.Force {
|
if opts.ForceWithLease != "" {
|
||||||
|
cmd.AddOptionFormat("--force-with-lease=%s", opts.ForceWithLease)
|
||||||
|
} else if opts.Force {
|
||||||
cmd.AddArguments("-f")
|
cmd.AddArguments("-f")
|
||||||
}
|
}
|
||||||
if opts.Mirror {
|
if opts.Mirror {
|
||||||
|
|||||||
@ -9,6 +9,19 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Push(ctx context.Context, repo Repository, opts git.PushOptions) error {
|
// PushToExternal pushes a managed repository to an external remote.
|
||||||
|
func PushToExternal(ctx context.Context, repo Repository, opts git.PushOptions) error {
|
||||||
return git.Push(ctx, repoPath(repo), opts)
|
return git.Push(ctx, repoPath(repo), opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push pushes from one managed repository to another managed repository.
|
||||||
|
func Push(ctx context.Context, fromRepo, toRepo Repository, opts git.PushOptions) error {
|
||||||
|
opts.Remote = repoPath(toRepo)
|
||||||
|
return git.Push(ctx, repoPath(fromRepo), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushFromLocal pushes from a local path to a managed repository.
|
||||||
|
func PushFromLocal(ctx context.Context, fromLocalPath string, toRepo Repository, opts git.PushOptions) error {
|
||||||
|
opts.Remote = repoPath(toRepo)
|
||||||
|
return git.Push(ctx, fromLocalPath, opts)
|
||||||
|
}
|
||||||
|
|||||||
@ -34,6 +34,10 @@ func TestMathRender(t *testing.T) {
|
|||||||
"$a$$b$",
|
"$a$$b$",
|
||||||
`<p><code class="language-math">a</code><code class="language-math">b</code></p>` + nl,
|
`<p><code class="language-math">a</code><code class="language-math">b</code></p>` + nl,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"$a$ $b$",
|
||||||
|
`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`\(a\) \(b\)`,
|
`\(a\) \(b\)`,
|
||||||
`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
|
`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
|
||||||
@ -59,7 +63,7 @@ func TestMathRender(t *testing.T) {
|
|||||||
`<p>a$b $a a$b b$</p>` + nl,
|
`<p>a$b $a a$b b$</p>` + nl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"a$x$",
|
"a$x$", // Pattern: "word$other$" The real world example is: "Price is between US$1 and US$2.", so don't parse this.
|
||||||
`<p>a$x$</p>` + nl,
|
`<p>a$x$</p>` + nl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -70,6 +74,10 @@ func TestMathRender(t *testing.T) {
|
|||||||
"$a$ ($b$) [$c$] {$d$}",
|
"$a$ ($b$) [$c$] {$d$}",
|
||||||
`<p><code class="language-math">a</code> (<code class="language-math">b</code>) [$c$] {$d$}</p>` + nl,
|
`<p><code class="language-math">a</code> (<code class="language-math">b</code>) [$c$] {$d$}</p>` + nl,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"[$a$](link)",
|
||||||
|
`<p><a href="/link" rel="nofollow"><code class="language-math">a</code></a></p>` + nl,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"$$a$$",
|
"$$a$$",
|
||||||
`<p><code class="language-math">a</code></p>` + nl,
|
`<p><code class="language-math">a</code></p>` + nl,
|
||||||
|
|||||||
@ -54,6 +54,10 @@ func isAlphanumeric(b byte) bool {
|
|||||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isInMarkdownLinkText(block text.Reader, lineAfter []byte) bool {
|
||||||
|
return block.PrecendingCharacter() == '[' && bytes.HasPrefix(lineAfter, []byte("]("))
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses the current line and returns a result of parsing.
|
// Parse parses the current line and returns a result of parsing.
|
||||||
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||||
line, _ := block.PeekLine()
|
line, _ := block.PeekLine()
|
||||||
@ -115,7 +119,9 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
|||||||
}
|
}
|
||||||
// check valid ending character
|
// check valid ending character
|
||||||
isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) ||
|
isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) ||
|
||||||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
|
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 ||
|
||||||
|
succeedingCharacter == '$' ||
|
||||||
|
isInMarkdownLinkText(block, line[i+len(stopMark):])
|
||||||
if checkSurrounding && !isValidEndingChar {
|
if checkSurrounding && !isValidEndingChar {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@ -337,14 +337,14 @@ func LogStartupProblem(skip int, level log.Level, format string, args ...any) {
|
|||||||
|
|
||||||
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
|
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
|
||||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||||
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
|
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
||||||
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
||||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||||
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
|
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -292,6 +292,21 @@ type RenameBranchRepoOption struct {
|
|||||||
Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
|
Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBranchRepoOption options when updating a branch reference in a repository
|
||||||
|
// swagger:model
|
||||||
|
type UpdateBranchRepoOption struct {
|
||||||
|
// New commit SHA (or any ref) the branch should point to
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
NewCommitID string `json:"new_commit_id" binding:"Required"`
|
||||||
|
|
||||||
|
// Expected old commit SHA of the branch; if provided it must match the current tip
|
||||||
|
OldCommitID string `json:"old_commit_id"`
|
||||||
|
|
||||||
|
// Force update even if the change is not a fast-forward
|
||||||
|
Force bool `json:"force"`
|
||||||
|
}
|
||||||
|
|
||||||
// TransferRepoOption options when transfer a repository's ownership
|
// TransferRepoOption options when transfer a repository's ownership
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type TransferRepoOption struct {
|
type TransferRepoOption struct {
|
||||||
|
|||||||
@ -1242,6 +1242,7 @@ func Routes() *web.Router {
|
|||||||
m.Get("/*", repo.GetBranch)
|
m.Get("/*", repo.GetBranch)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
|
||||||
|
m.Put("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
|
||||||
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.RenameBranchRepoOption{}), repo.RenameBranch)
|
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.RenameBranchRepoOption{}), repo.RenameBranch)
|
||||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
||||||
m.Group("/branch_protections", func() {
|
m.Group("/branch_protections", func() {
|
||||||
|
|||||||
@ -380,6 +380,81 @@ func ListBranches(ctx *context.APIContext) {
|
|||||||
ctx.JSON(http.StatusOK, apiBranches)
|
ctx.JSON(http.StatusOK, apiBranches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBranch moves a branch reference to a new commit.
|
||||||
|
func UpdateBranch(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
|
||||||
|
// ---
|
||||||
|
// summary: Update a branch reference to a new commit
|
||||||
|
// 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"
|
||||||
|
// "409":
|
||||||
|
// "$ref": "#/responses/conflict"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
|
||||||
|
|
||||||
|
branchName := ctx.PathParam("*")
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
if repo.IsEmpty {
|
||||||
|
ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.IsMirror {
|
||||||
|
ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// permission check has been done in api.go
|
||||||
|
if err := repo_service.UpdateBranch(ctx, repo, ctx.Repo.GitRepo, ctx.Doer, branchName, opt.NewCommitID, opt.OldCommitID, opt.Force); err != nil {
|
||||||
|
switch {
|
||||||
|
case git_model.IsErrBranchNotExist(err):
|
||||||
|
ctx.APIErrorNotFound(err)
|
||||||
|
case errors.Is(err, util.ErrInvalidArgument):
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||||
|
case git.IsErrPushRejected(err):
|
||||||
|
rej := err.(*git.ErrPushRejected)
|
||||||
|
ctx.APIError(http.StatusForbidden, rej.Message)
|
||||||
|
default:
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
// RenameBranch renames a repository's branch.
|
// RenameBranch renames a repository's branch.
|
||||||
func RenameBranch(ctx *context.APIContext) {
|
func RenameBranch(ctx *context.APIContext) {
|
||||||
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoRenameBranch
|
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoRenameBranch
|
||||||
|
|||||||
@ -147,6 +147,8 @@ type swaggerParameterBodies struct {
|
|||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateBranchRepoOption api.CreateBranchRepoOption
|
CreateBranchRepoOption api.CreateBranchRepoOption
|
||||||
|
// in:body
|
||||||
|
UpdateBranchRepoOption api.UpdateBranchRepoOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateBranchProtectionOption api.CreateBranchProtectionOption
|
CreateBranchProtectionOption api.CreateBranchProtectionOption
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
@ -133,8 +134,7 @@ func RestoreBranchPost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
|
if err := gitrepo.Push(ctx, ctx.Repo.Repository, ctx.Repo.Repository, git.PushOptions{
|
||||||
Remote: ctx.Repo.Repository.RepoPath(),
|
|
||||||
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name),
|
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name),
|
||||||
Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
|
Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ 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/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
@ -102,8 +103,7 @@ func getUniqueRepositoryName(ctx context.Context, ownerID int64, name string) st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func editorPushBranchToForkedRepository(ctx context.Context, doer *user_model.User, baseRepo *repo_model.Repository, baseBranchName string, targetRepo *repo_model.Repository, targetBranchName string) error {
|
func editorPushBranchToForkedRepository(ctx context.Context, doer *user_model.User, baseRepo *repo_model.Repository, baseBranchName string, targetRepo *repo_model.Repository, targetBranchName string) error {
|
||||||
return git.Push(ctx, baseRepo.RepoPath(), git.PushOptions{
|
return gitrepo.Push(ctx, baseRepo, targetRepo, git.PushOptions{
|
||||||
Remote: targetRepo.RepoPath(),
|
|
||||||
Branch: baseBranchName + ":" + targetBranchName,
|
Branch: baseBranchName + ":" + targetBranchName,
|
||||||
Env: repo_module.PushingEnvironment(doer, targetRepo),
|
Env: repo_module.PushingEnvironment(doer, targetRepo),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/renderhelper"
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
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/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/htmlutil"
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
@ -141,8 +142,7 @@ func NewComment(ctx *context.Context) {
|
|||||||
|
|
||||||
if prHeadCommitID != headBranchCommitID {
|
if prHeadCommitID != headBranchCommitID {
|
||||||
// force push to base repo
|
// force push to base repo
|
||||||
err := git.Push(ctx, pull.HeadRepo.RepoPath(), git.PushOptions{
|
err := gitrepo.Push(ctx, pull.HeadRepo, pull.BaseRepo, git.PushOptions{
|
||||||
Remote: pull.BaseRepo.RepoPath(),
|
|
||||||
Branch: pull.HeadBranch + ":" + prHeadRef,
|
Branch: pull.HeadBranch + ":" + prHeadRef,
|
||||||
Force: true,
|
Force: true,
|
||||||
Env: repo_module.InternalPushingEnvironment(pull.Issue.Poster, pull.BaseRepo),
|
Env: repo_module.InternalPushingEnvironment(pull.Issue.Poster, pull.BaseRepo),
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
"code.gitea.io/gitea/services/migrations"
|
"code.gitea.io/gitea/services/migrations"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
"code.gitea.io/gitea/services/task"
|
"code.gitea.io/gitea/services/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -237,7 +238,7 @@ func MigratePost(ctx *context.Context) {
|
|||||||
opts.AWSSecretAccessKey = form.AWSSecretAccessKey
|
opts.AWSSecretAccessKey = form.AWSSecretAccessKey
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repo_model.CheckCreateRepository(ctx, ctx.Doer, ctxUser, opts.RepoName, false)
|
err = repo_service.CheckCreateRepository(ctx, ctx.Doer, ctxUser, opts.RepoName, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form)
|
handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/git/attribute"
|
"code.gitea.io/gitea/modules/git/attribute"
|
||||||
"code.gitea.io/gitea/modules/git/pipeline"
|
"code.gitea.io/gitea/modules/git/pipeline"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/lfs"
|
"code.gitea.io/gitea/modules/lfs"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
@ -112,7 +113,7 @@ func LFSLocks(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{
|
if err := gitrepo.CloneRepoToLocal(ctx, ctx.Repo.Repository, tmpBasePath, git.CloneRepoOptions{
|
||||||
Bare: true,
|
Bare: true,
|
||||||
Shared: true,
|
Shared: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@ -118,7 +118,7 @@ func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
|
|||||||
if uidChanged {
|
if uidChanged {
|
||||||
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
|
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
|
||||||
} else if cookieToken != "" {
|
} else if cookieToken != "" {
|
||||||
// If cookie token presents, re-use existing unexpired token, else generate a new one.
|
// If cookie token present, re-use existing unexpired token, else generate a new one.
|
||||||
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
|
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
|
||||||
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
|
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
|
||||||
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
|
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
|
||||||
|
|||||||
@ -215,7 +215,7 @@ func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) erro
|
|||||||
if !isExist {
|
if !isExist {
|
||||||
numNeedUpdate++
|
numNeedUpdate++
|
||||||
if autofix {
|
if autofix {
|
||||||
if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil {
|
if err := gitrepo.WriteCommitGraph(ctx, repo); err != nil {
|
||||||
logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
|
logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,7 +153,7 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
|
|||||||
log.Trace("Pushing %s mirror[%d] remote %s", storageRepo.RelativePath(), m.ID, m.RemoteName)
|
log.Trace("Pushing %s mirror[%d] remote %s", storageRepo.RelativePath(), m.ID, m.RemoteName)
|
||||||
|
|
||||||
envs := proxy.EnvWithProxy(remoteURL.URL)
|
envs := proxy.EnvWithProxy(remoteURL.URL)
|
||||||
if err := gitrepo.Push(ctx, storageRepo, git.PushOptions{
|
if err := gitrepo.PushToExternal(ctx, storageRepo, git.PushOptions{
|
||||||
Remote: m.RemoteName,
|
Remote: m.RemoteName,
|
||||||
Force: true,
|
Force: true,
|
||||||
Mirror: true,
|
Mirror: true,
|
||||||
|
|||||||
@ -570,13 +570,11 @@ func pushToBaseRepoHelper(ctx context.Context, pr *issues_model.PullRequest, pre
|
|||||||
log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
|
log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
headRepoPath := pr.HeadRepo.RepoPath()
|
|
||||||
|
|
||||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||||
log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
|
log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
baseRepoPath := pr.BaseRepo.RepoPath()
|
|
||||||
|
|
||||||
if err = pr.LoadIssue(ctx); err != nil {
|
if err = pr.LoadIssue(ctx); err != nil {
|
||||||
return fmt.Errorf("unable to load issue %d for pr %d: %w", pr.IssueID, pr.ID, err)
|
return fmt.Errorf("unable to load issue %d for pr %d: %w", pr.IssueID, pr.ID, err)
|
||||||
@ -587,8 +585,7 @@ func pushToBaseRepoHelper(ctx context.Context, pr *issues_model.PullRequest, pre
|
|||||||
|
|
||||||
gitRefName := pr.GetGitHeadRefName()
|
gitRefName := pr.GetGitHeadRefName()
|
||||||
|
|
||||||
if err := git.Push(ctx, headRepoPath, git.PushOptions{
|
if err := gitrepo.Push(ctx, pr.HeadRepo, pr.BaseRepo, git.PushOptions{
|
||||||
Remote: baseRepoPath,
|
|
||||||
Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
|
Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
|
||||||
Force: true,
|
Force: true,
|
||||||
// Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
|
// Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
|
||||||
|
|||||||
@ -385,8 +385,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
|
if err := gitrepo.Push(ctx, repo, repo, git.PushOptions{
|
||||||
Remote: repo.RepoPath(),
|
|
||||||
Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
|
Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
|
||||||
Env: repo_module.PushingEnvironment(doer, repo),
|
Env: repo_module.PushingEnvironment(doer, repo),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -483,6 +482,64 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBranch moves a branch reference to the provided commit. permission check should be done before calling this function.
|
||||||
|
func UpdateBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doer *user_model.User, branchName, newCommitID, expectedOldCommitID string, force bool) error {
|
||||||
|
branch, err := git_model.GetBranch(ctx, repo.ID, branchName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if branch.IsDeleted {
|
||||||
|
return git_model.ErrBranchNotExist{
|
||||||
|
BranchName: branchName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedOldCommitID != "" {
|
||||||
|
expectedID, err := gitRepo.ConvertToGitID(expectedOldCommitID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ConvertToGitID(old): %w", err)
|
||||||
|
}
|
||||||
|
if expectedID.String() != branch.CommitID {
|
||||||
|
return util.NewInvalidArgumentErrorf("branch commit does not match [expected: %s, given: %s]", expectedID.String(), branch.CommitID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newID, err := gitRepo.ConvertToGitID(newCommitID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ConvertToGitID(new): %w", err)
|
||||||
|
}
|
||||||
|
newCommit, err := gitRepo.GetCommit(newID.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newCommit.ID.String() == branch.CommitID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isForcePush, err := newCommit.IsForcePush(branch.CommitID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isForcePush && !force {
|
||||||
|
return util.NewInvalidArgumentErrorf("Force push %s need a confirm force parameter", branchName)
|
||||||
|
}
|
||||||
|
|
||||||
|
pushOpts := git.PushOptions{
|
||||||
|
Remote: repo.RepoPath(),
|
||||||
|
Branch: fmt.Sprintf("%s:%s%s", newCommit.ID.String(), git.BranchPrefix, branchName),
|
||||||
|
Env: repo_module.PushingEnvironment(doer, repo),
|
||||||
|
Force: isForcePush || force,
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedOldCommitID != "" {
|
||||||
|
pushOpts.ForceWithLease = fmt.Sprintf("%s:%s", git.BranchPrefix+branchName, branch.CommitID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// branch protection will be checked in the pre received hook, so that we don't need any check here
|
||||||
|
return gitrepo.Push(ctx, repo, repo, pushOpts)
|
||||||
|
}
|
||||||
|
|
||||||
var ErrBranchIsDefault = util.ErrorWrap(util.ErrPermissionDenied, "branch is default")
|
var ErrBranchIsDefault = util.ErrorWrap(util.ErrPermissionDenied, "branch is default")
|
||||||
|
|
||||||
func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchName string, doer *user_model.User) error {
|
func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchName string, doer *user_model.User) error {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
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/git/gitcmd"
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -362,8 +363,7 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
|
|||||||
func (t *TemporaryUploadRepository) Push(ctx context.Context, doer *user_model.User, commitHash, branch string, force bool) error {
|
func (t *TemporaryUploadRepository) Push(ctx context.Context, doer *user_model.User, commitHash, branch string, force bool) error {
|
||||||
// Because calls hooks we need to pass in the environment
|
// Because calls hooks we need to pass in the environment
|
||||||
env := repo_module.PushingEnvironment(doer, t.repo)
|
env := repo_module.PushingEnvironment(doer, t.repo)
|
||||||
if err := git.Push(ctx, t.basePath, git.PushOptions{
|
if err := gitrepo.PushFromLocal(ctx, t.basePath, t.repo, git.PushOptions{
|
||||||
Remote: t.repo.RepoPath(),
|
|
||||||
Branch: strings.TrimSpace(commitHash) + ":" + git.BranchPrefix + strings.TrimSpace(branch),
|
Branch: strings.TrimSpace(commitHash) + ":" + git.BranchPrefix + strings.TrimSpace(branch),
|
||||||
Env: env,
|
Env: env,
|
||||||
Force: force,
|
Force: force,
|
||||||
|
|||||||
@ -230,8 +230,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Clone to temporary path and do the init commit.
|
// Clone to temporary path and do the init commit.
|
||||||
templateRepoPath := templateRepo.RepoPath()
|
if err := gitrepo.CloneRepoToLocal(ctx, templateRepo, tmpDir, git.CloneRepoOptions{
|
||||||
if err := git.Clone(ctx, templateRepoPath, tmpDir, git.CloneRepoOptions{
|
|
||||||
Depth: 1,
|
Depth: 1,
|
||||||
Branch: templateRepo.DefaultBranch,
|
Branch: templateRepo.DefaultBranch,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ 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/gitrepo"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/reqctx"
|
"code.gitea.io/gitea/modules/reqctx"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -33,8 +34,7 @@ func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_
|
|||||||
return "up-to-date", nil
|
return "up-to-date", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
|
err = gitrepo.Push(ctx, repo.BaseRepo, repo, git.PushOptions{
|
||||||
Remote: repo.RepoPath(),
|
|
||||||
Branch: fmt.Sprintf("%s:%s", divergingInfo.BaseBranchName, branch),
|
Branch: fmt.Sprintf("%s:%s", divergingInfo.BaseBranchName, branch),
|
||||||
Env: repo_module.PushingEnvironment(doer, repo),
|
Env: repo_module.PushingEnvironment(doer, repo),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -74,8 +74,6 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
|||||||
repo *repo_model.Repository, opts migration.MigrateOptions,
|
repo *repo_model.Repository, opts migration.MigrateOptions,
|
||||||
httpTransport *http.Transport,
|
httpTransport *http.Transport,
|
||||||
) (*repo_model.Repository, error) {
|
) (*repo_model.Repository, error) {
|
||||||
repoPath := repo.RepoPath()
|
|
||||||
|
|
||||||
if u.IsOrganization() {
|
if u.IsOrganization() {
|
||||||
t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
|
t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -92,7 +90,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
|||||||
return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repo.FullName(), err)
|
return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repo.FullName(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
|
if err := gitrepo.CloneExternalRepo(ctx, opts.CloneAddr, repo, git.CloneRepoOptions{
|
||||||
Mirror: true,
|
Mirror: true,
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
Timeout: migrateTimeout,
|
Timeout: migrateTimeout,
|
||||||
@ -104,7 +102,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
|||||||
return repo, fmt.Errorf("clone error: %w", err)
|
return repo, fmt.Errorf("clone error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
|
if err := gitrepo.WriteCommitGraph(ctx, repo); err != nil {
|
||||||
return repo, err
|
return repo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -345,3 +345,31 @@ func HasWiki(ctx context.Context, repo *repo_model.Repository) bool {
|
|||||||
}
|
}
|
||||||
return hasWiki && err == nil
|
return hasWiki && err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckCreateRepository check if doer could create a repository in new owner
|
||||||
|
func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error {
|
||||||
|
if !doer.CanCreateRepoIn(owner) {
|
||||||
|
return repo_model.ErrReachLimitOfRepo{Limit: owner.MaxRepoCreation}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo_model.IsUsableRepoName(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := repo_model.IsRepositoryModelExist(ctx, owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
return repo_model.ErrRepoAlreadyExist{Uname: owner.Name, Name: name}
|
||||||
|
}
|
||||||
|
repo := repo_model.StorageRepo(repo_model.RelativePath(owner.Name, name))
|
||||||
|
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to check if %s exists. Error: %v", repo.RelativePath(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !overwriteOrAdopt && isExist {
|
||||||
|
return repo_model.ErrRepoFilesAlreadyExist{Uname: owner.Name, Name: name}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -90,6 +90,17 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isRepositoryModelOrDirExist returns true if the repository with given name under user has already existed.
|
||||||
|
func isRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||||
|
has, err := repo_model.IsRepositoryModelExist(ctx, u, repoName)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
repo := repo_model.StorageRepo(repo_model.RelativePath(u.Name, repoName))
|
||||||
|
isExist, err := gitrepo.IsRepositoryExist(ctx, repo)
|
||||||
|
return has || isExist, err
|
||||||
|
}
|
||||||
|
|
||||||
// transferOwnership transfers all corresponding repository items from old user to new one.
|
// transferOwnership transfers all corresponding repository items from old user to new one.
|
||||||
func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName string, repo *repo_model.Repository, teams []*organization.Team) (err error) {
|
func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName string, repo *repo_model.Repository, teams []*organization.Team) (err error) {
|
||||||
repoRenamed := false
|
repoRenamed := false
|
||||||
@ -143,7 +154,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
|||||||
newOwnerName = newOwner.Name // ensure capitalisation matches
|
newOwnerName = newOwner.Name // ensure capitalisation matches
|
||||||
|
|
||||||
// Check if new owner has repository with same name.
|
// Check if new owner has repository with same name.
|
||||||
if has, err := repo_model.IsRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil {
|
if has, err := isRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil {
|
||||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||||
} else if has {
|
} else if has {
|
||||||
return repo_model.ErrRepoAlreadyExist{
|
return repo_model.ErrRepoAlreadyExist{
|
||||||
@ -345,7 +356,7 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
has, err := repo_model.IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName)
|
has, err := isRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||||
} else if has {
|
} else if has {
|
||||||
|
|||||||
@ -25,8 +25,6 @@ import (
|
|||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultRemote = "origin"
|
|
||||||
|
|
||||||
func getWikiWorkingLockKey(repoID int64) string {
|
func getWikiWorkingLockKey(repoID int64) string {
|
||||||
return fmt.Sprintf("wiki_working_%d", repoID)
|
return fmt.Sprintf("wiki_working_%d", repoID)
|
||||||
}
|
}
|
||||||
@ -214,8 +212,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
|
if err := gitrepo.PushFromLocal(gitRepo.Ctx, basePath, repo.WikiStorageRepo(), git.PushOptions{
|
||||||
Remote: DefaultRemote,
|
|
||||||
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch),
|
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch),
|
||||||
Env: repo_module.FullPushingEnvironment(
|
Env: repo_module.FullPushingEnvironment(
|
||||||
doer,
|
doer,
|
||||||
@ -333,8 +330,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
|
if err := gitrepo.PushFromLocal(gitRepo.Ctx, basePath, repo.WikiStorageRepo(), git.PushOptions{
|
||||||
Remote: DefaultRemote,
|
|
||||||
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch),
|
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch),
|
||||||
Env: repo_module.FullPushingEnvironment(
|
Env: repo_module.FullPushingEnvironment(
|
||||||
doer,
|
doer,
|
||||||
|
|||||||
85
templates/swagger/v1_json.tmpl
generated
85
templates/swagger/v1_json.tmpl
generated
@ -6912,6 +6912,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"put": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Update a branch reference to a new commit",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"$ref": "#/responses/conflict"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@ -28981,6 +29041,31 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"UpdateBranchRepoOption": {
|
||||||
|
"description": "UpdateBranchRepoOption options when updating a branch reference in a repository",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"new_commit_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"force": {
|
||||||
|
"description": "Force update even if the change is not a fast-forward",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Force"
|
||||||
|
},
|
||||||
|
"new_commit_id": {
|
||||||
|
"description": "New commit SHA (or any ref) the branch should point to",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NewCommitID"
|
||||||
|
},
|
||||||
|
"old_commit_id": {
|
||||||
|
"description": "Expected old commit SHA of the branch; if provided it must match the current tip",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "OldCommitID"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"UpdateFileOptions": {
|
"UpdateFileOptions": {
|
||||||
"description": "UpdateFileOptions options for updating or creating a file\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)",
|
"description": "UpdateFileOptions options for updating or creating a file\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",
|
"type": "object",
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -243,6 +245,79 @@ func TestAPIRenameBranch(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIUpdateBranchReference(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
ctx := NewAPITestContext(t, "user2", "update-branch", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
giteaURL.Path = ctx.GitPath()
|
||||||
|
|
||||||
|
var defaultBranch string
|
||||||
|
t.Run("CreateRepo", doAPICreateRepository(ctx, false, func(t *testing.T, repo api.Repository) {
|
||||||
|
defaultBranch = repo.DefaultBranch
|
||||||
|
}))
|
||||||
|
|
||||||
|
createBranchReq := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/branches", ctx.Username, ctx.Reponame), &api.CreateBranchRepoOption{
|
||||||
|
BranchName: "feature",
|
||||||
|
OldRefName: defaultBranch,
|
||||||
|
}).AddTokenAuth(ctx.Token)
|
||||||
|
ctx.Session.MakeRequest(t, createBranchReq, http.StatusCreated)
|
||||||
|
|
||||||
|
var featureInitialCommit string
|
||||||
|
t.Run("LoadFeatureBranch", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) {
|
||||||
|
featureInitialCommit = branch.Commit.ID
|
||||||
|
assert.NotEmpty(t, featureInitialCommit)
|
||||||
|
}))
|
||||||
|
|
||||||
|
content := base64.StdEncoding.EncodeToString([]byte("branch update test"))
|
||||||
|
var newCommit string
|
||||||
|
doAPICreateFile(ctx, "docs/update.txt", &api.CreateFileOptions{
|
||||||
|
FileOptions: api.FileOptions{
|
||||||
|
BranchName: defaultBranch,
|
||||||
|
NewBranchName: defaultBranch,
|
||||||
|
Message: "add docs/update.txt",
|
||||||
|
},
|
||||||
|
ContentBase64: content,
|
||||||
|
}, func(t *testing.T, resp api.FileResponse) {
|
||||||
|
newCommit = resp.Commit.SHA
|
||||||
|
assert.NotEmpty(t, newCommit)
|
||||||
|
})(t)
|
||||||
|
|
||||||
|
updateReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||||
|
NewCommitID: newCommit,
|
||||||
|
OldCommitID: featureInitialCommit,
|
||||||
|
}).AddTokenAuth(ctx.Token)
|
||||||
|
ctx.Session.MakeRequest(t, updateReq, http.StatusNoContent)
|
||||||
|
|
||||||
|
t.Run("FastForwardApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) {
|
||||||
|
assert.Equal(t, newCommit, branch.Commit.ID)
|
||||||
|
}))
|
||||||
|
|
||||||
|
staleReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||||
|
NewCommitID: newCommit,
|
||||||
|
OldCommitID: featureInitialCommit,
|
||||||
|
}).AddTokenAuth(ctx.Token)
|
||||||
|
ctx.Session.MakeRequest(t, staleReq, http.StatusUnprocessableEntity)
|
||||||
|
|
||||||
|
nonFFReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||||
|
NewCommitID: featureInitialCommit,
|
||||||
|
OldCommitID: newCommit,
|
||||||
|
}).AddTokenAuth(ctx.Token)
|
||||||
|
ctx.Session.MakeRequest(t, nonFFReq, http.StatusUnprocessableEntity)
|
||||||
|
|
||||||
|
forceReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{
|
||||||
|
NewCommitID: featureInitialCommit,
|
||||||
|
OldCommitID: newCommit,
|
||||||
|
Force: true,
|
||||||
|
}).AddTokenAuth(ctx.Token)
|
||||||
|
ctx.Session.MakeRequest(t, forceReq, http.StatusNoContent)
|
||||||
|
|
||||||
|
t.Run("ForceApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) {
|
||||||
|
assert.Equal(t, featureInitialCommit, branch.Commit.ID)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder {
|
func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder {
|
||||||
token := getUserToken(t, doerName, auth_model.AccessTokenScopeWriteRepository)
|
token := getUserToken(t, doerName, auth_model.AccessTokenScopeWriteRepository)
|
||||||
req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.RenameBranchRepoOption{
|
req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.RenameBranchRepoOption{
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {POST} from '../../modules/fetch.ts';
|
|||||||
import {
|
import {
|
||||||
EventEditorContentChanged,
|
EventEditorContentChanged,
|
||||||
initTextareaMarkdown,
|
initTextareaMarkdown,
|
||||||
textareaInsertText,
|
replaceTextareaSelection,
|
||||||
triggerEditorContentChanged,
|
triggerEditorContentChanged,
|
||||||
} from './EditorMarkdown.ts';
|
} from './EditorMarkdown.ts';
|
||||||
import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
|
import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
|
||||||
@ -273,7 +273,7 @@ export class ComboMarkdownEditor {
|
|||||||
let cols = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=cols]')!.value);
|
let cols = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=cols]')!.value);
|
||||||
rows = Math.max(1, Math.min(100, rows));
|
rows = Math.max(1, Math.min(100, rows));
|
||||||
cols = Math.max(1, Math.min(100, cols));
|
cols = Math.max(1, Math.min(100, cols));
|
||||||
textareaInsertText(this.textarea, `\n${this.generateMarkdownTable(rows, cols)}\n\n`);
|
replaceTextareaSelection(this.textarea, `\n${this.generateMarkdownTable(rows, cols)}\n\n`);
|
||||||
addTablePanelTippy.hide();
|
addTablePanelTippy.hide();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,15 +4,24 @@ export function triggerEditorContentChanged(target: HTMLElement) {
|
|||||||
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
|
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function textareaInsertText(textarea: HTMLTextAreaElement, value: string) {
|
/** replace selected text or insert text by creating a new edit history entry,
|
||||||
const startPos = textarea.selectionStart;
|
* e.g. CTRL-Z works after this */
|
||||||
const endPos = textarea.selectionEnd;
|
export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: string) {
|
||||||
textarea.value = textarea.value.substring(0, startPos) + value + textarea.value.substring(endPos);
|
const before = textarea.value.slice(0, textarea.selectionStart);
|
||||||
textarea.selectionStart = startPos;
|
const after = textarea.value.slice(textarea.selectionEnd);
|
||||||
textarea.selectionEnd = startPos + value.length;
|
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
let success = false;
|
||||||
|
try {
|
||||||
|
success = document.execCommand('insertText', false, text); // eslint-disable-line @typescript-eslint/no-deprecated
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// fall back to regular replacement
|
||||||
|
if (!success) {
|
||||||
|
textarea.value = `${before}${text}${after}`;
|
||||||
triggerEditorContentChanged(textarea);
|
triggerEditorContentChanged(textarea);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type TextareaValueSelection = {
|
type TextareaValueSelection = {
|
||||||
value: string;
|
value: string;
|
||||||
@ -176,7 +185,7 @@ export function markdownHandleIndention(tvs: TextareaValueSelection): MarkdownHa
|
|||||||
return {handled: true, valueSelection: {value: linesBuf.lines.join('\n'), selStart: newPos, selEnd: newPos}};
|
return {handled: true, valueSelection: {value: linesBuf.lines.join('\n'), selStart: newPos, selEnd: newPos}};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNewline(textarea: HTMLTextAreaElement, e: Event) {
|
function handleNewline(textarea: HTMLTextAreaElement, e: KeyboardEvent) {
|
||||||
const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd});
|
const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd});
|
||||||
if (!ret.handled || !ret.valueSelection) return; // FIXME: the "handled" seems redundant, only valueSelection is enough (null for unhandled)
|
if (!ret.handled || !ret.valueSelection) return; // FIXME: the "handled" seems redundant, only valueSelection is enough (null for unhandled)
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -185,6 +194,28 @@ function handleNewline(textarea: HTMLTextAreaElement, e: Event) {
|
|||||||
triggerEditorContentChanged(textarea);
|
triggerEditorContentChanged(textarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keys that act as dead keys will not work because the spec dictates that such keys are
|
||||||
|
// emitted as `Dead` in e.key instead of the actual key.
|
||||||
|
const pairs = new Map<string, string>([
|
||||||
|
["'", "'"],
|
||||||
|
['"', '"'],
|
||||||
|
['`', '`'],
|
||||||
|
['(', ')'],
|
||||||
|
['[', ']'],
|
||||||
|
['{', '}'],
|
||||||
|
['<', '>'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
function handlePairCharacter(textarea: HTMLTextAreaElement, e: KeyboardEvent): void {
|
||||||
|
const selStart = textarea.selectionStart;
|
||||||
|
const selEnd = textarea.selectionEnd;
|
||||||
|
if (selEnd === selStart) return; // do not process when no selection
|
||||||
|
e.preventDefault();
|
||||||
|
const inner = textarea.value.substring(selStart, selEnd);
|
||||||
|
replaceTextareaSelection(textarea, `${e.key}${inner}${pairs.get(e.key)}`);
|
||||||
|
textarea.setSelectionRange(selStart + 1, selEnd + 1);
|
||||||
|
}
|
||||||
|
|
||||||
function isTextExpanderShown(textarea: HTMLElement): boolean {
|
function isTextExpanderShown(textarea: HTMLElement): boolean {
|
||||||
return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions'));
|
return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions'));
|
||||||
}
|
}
|
||||||
@ -198,6 +229,8 @@ export function initTextareaMarkdown(textarea: HTMLTextAreaElement) {
|
|||||||
} else if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
} else if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||||
// use Enter to insert a new line with the same indention and prefix
|
// use Enter to insert a new line with the same indention and prefix
|
||||||
handleNewline(textarea, e);
|
handleNewline(textarea, e);
|
||||||
|
} else if (pairs.has(e.key)) {
|
||||||
|
handlePairCharacter(textarea, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {imageInfo} from '../../utils/image.ts';
|
import {imageInfo} from '../../utils/image.ts';
|
||||||
import {textareaInsertText, triggerEditorContentChanged} from './EditorMarkdown.ts';
|
import {replaceTextareaSelection, triggerEditorContentChanged} from './EditorMarkdown.ts';
|
||||||
import {
|
import {
|
||||||
DropzoneCustomEventRemovedFile,
|
DropzoneCustomEventRemovedFile,
|
||||||
DropzoneCustomEventUploadDone,
|
DropzoneCustomEventUploadDone,
|
||||||
@ -43,7 +43,7 @@ class TextareaEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertPlaceholder(value: string) {
|
insertPlaceholder(value: string) {
|
||||||
textareaInsertText(this.editor, value);
|
replaceTextareaSelection(this.editor, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
replacePlaceholder(oldVal: string, newVal: string) {
|
replacePlaceholder(oldVal: string, newVal: string) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user