diff --git a/CHANGELOG.md b/CHANGELOG.md index d4bb000c937e6..679de331e5f80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.15.4](https://github.com/go-gitea/gitea/releases/tag/v1.15.4) - 2021-10-08 +* BUGFIXES + * Raw file API: don't try to interpret 40char filenames as commit SHA (#17185) (#17272) + * Don't allow merged PRs to be reopened (#17192) (#17271) + * Fix incorrect repository count on organization tab of dashboard (#17256) (#17266) + * Fix unwanted team review request deletion (#17257) (#17264) + * Fix broken Activities link in team dashboard (#17255) (#17258) + * API pull's head/base have correct permission(#17214) (#17245) + * Fix stange behavior of DownloadPullDiffOrPatch in incorect index (#17223) (#17227) + * Upgrade xorm to v1.2.5 (#17177) (#17188) + * Fix missing repo link in issue/pull assigned emails (#17183) (#17184) + * Fix bug of get context user (#17169) (#17172) + * Nicely handle missing user in collaborations (#17049) (#17166) + * Add Horizontal scrollbar to inner menu on Chrome (#17086) (#17164) + * Fix wrong i18n keys (#17150) (#17153) + * Fix Archive Creation: correct transaction ending (#17151) + * Prevent panic in Org mode HighlightCodeBlock (#17140) (#17141) + * Create doctor command to fix repo_units broken by dumps from 1.14.3-1.14.6 (#17136) (#17137) +* ENHANCEMENT + * Check user instead of organization when creating a repo from a template via API (#16346) (#17195) +* TRANSLATION + * v1.15 fix Sprintf format 'verbs' in locale files (#17187) + ## [1.15.3](https://github.com/go-gitea/gitea/releases/tag/v1.15.3) - 2021-09-19 * ENHANCEMENTS diff --git a/docs/config.yaml b/docs/config.yaml index 05e038af553a0..9d4b8c5aca9d4 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -18,7 +18,7 @@ params: description: Git with a cup of tea author: The Gitea Authors website: https://docs.gitea.io - version: 1.15.3 + version: 1.15.4 minGoVersion: 1.16 goVersion: 1.17 minNodeVersion: 12.17 diff --git a/integrations/rename_branch_test.go b/integrations/rename_branch_test.go new file mode 100644 index 0000000000000..90c1f4d15f1ba --- /dev/null +++ b/integrations/rename_branch_test.go @@ -0,0 +1,44 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "github.com/stretchr/testify/assert" +) + +func TestRenameBranch(t *testing.T) { + // get branch setting page + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/settings/branches") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + postData := map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "from": "master", + "to": "main", + } + req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", postData) + session.MakeRequest(t, req, http.StatusFound) + + // check new branch link + req = NewRequestWithValues(t, "GET", "/user2/repo1/src/branch/main/README.md", postData) + session.MakeRequest(t, req, http.StatusOK) + + // check old branch link + req = NewRequestWithValues(t, "GET", "/user2/repo1/src/branch/master/README.md", postData) + resp = session.MakeRequest(t, req, http.StatusFound) + location := resp.HeaderMap.Get("Location") + assert.Equal(t, "/user2/repo1/src/branch/main/README.md", location) + + // check db + repo1 := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + assert.Equal(t, "main", repo1.DefaultBranch) +} diff --git a/models/branches.go b/models/branches.go index 8eaa4b6fd74b6..3c62c7a87bd83 100644 --- a/models/branches.go +++ b/models/branches.go @@ -53,6 +53,7 @@ type ProtectedBranch struct { func init() { db.RegisterModel(new(ProtectedBranch)) db.RegisterModel(new(DeletedBranch)) + db.RegisterModel(new(RenamedBranch)) } // IsProtected returns if the branch is protected @@ -588,3 +589,83 @@ func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { log.Error("DeletedBranchesCleanup: %v", err) } } + +// RenamedBranch provide renamed branch log +// will check it when a branch can't be found +type RenamedBranch struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX NOT NULL"` + From string + To string + CreatedUnix timeutil.TimeStamp `xorm:"created"` +} + +// FindRenamedBranch check if a branch was renamed +func FindRenamedBranch(repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { + branch = &RenamedBranch{ + RepoID: repoID, + From: from, + } + exist, err = db.GetEngine(db.DefaultContext).Get(branch) + + return +} + +// RenameBranch rename a branch +func (repo *Repository) RenameBranch(from, to string, gitAction func(isDefault bool) error) (err error) { + sess := db.NewSession(db.DefaultContext) + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + // 1. update default branch if needed + isDefault := repo.DefaultBranch == from + if isDefault { + repo.DefaultBranch = to + _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) + if err != nil { + return err + } + } + + // 2. Update protected branch if needed + protectedBranch, err := getProtectedBranchBy(sess, repo.ID, from) + if err != nil { + return err + } + + if protectedBranch != nil { + protectedBranch.BranchName = to + _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) + if err != nil { + return err + } + } + + // 3. Update all not merged pull request base branch name + _, err = sess.Table(new(PullRequest)).Where("base_repo_id=? AND base_branch=? AND has_merged=?", + repo.ID, from, false). + Update(map[string]interface{}{"base_branch": to}) + if err != nil { + return err + } + + // 4. do git action + if err = gitAction(isDefault); err != nil { + return err + } + + // 5. insert renamed branch record + renamedBranch := &RenamedBranch{ + RepoID: repo.ID, + From: from, + To: to, + } + _, err = sess.Insert(renamedBranch) + if err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/branches_test.go b/models/branches_test.go index 02a9e81a7efed..f1dcfecfa8be2 100644 --- a/models/branches_test.go +++ b/models/branches_test.go @@ -79,3 +79,52 @@ func getDeletedBranch(t *testing.T, branch *DeletedBranch) *DeletedBranch { return deletedBranch } + +func TestFindRenamedBranch(t *testing.T) { + assert.NoError(t, db.PrepareTestDatabase()) + branch, exist, err := FindRenamedBranch(1, "dev") + assert.NoError(t, err) + assert.Equal(t, true, exist) + assert.Equal(t, "master", branch.To) + + _, exist, err = FindRenamedBranch(1, "unknow") + assert.NoError(t, err) + assert.Equal(t, false, exist) +} + +func TestRenameBranch(t *testing.T) { + assert.NoError(t, db.PrepareTestDatabase()) + repo1 := db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + _isDefault := false + + err := UpdateProtectBranch(repo1, &ProtectedBranch{ + RepoID: repo1.ID, + BranchName: "master", + }, WhitelistOptions{}) + assert.NoError(t, err) + + assert.NoError(t, repo1.RenameBranch("master", "main", func(isDefault bool) error { + _isDefault = isDefault + return nil + })) + + assert.Equal(t, true, _isDefault) + repo1 = db.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + assert.Equal(t, "main", repo1.DefaultBranch) + + pull := db.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) // merged + assert.Equal(t, "master", pull.BaseBranch) + + pull = db.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) // open + assert.Equal(t, "main", pull.BaseBranch) + + renamedBranch := db.AssertExistsAndLoadBean(t, &RenamedBranch{ID: 2}).(*RenamedBranch) + assert.Equal(t, "master", renamedBranch.From) + assert.Equal(t, "main", renamedBranch.To) + assert.Equal(t, int64(1), renamedBranch.RepoID) + + db.AssertExistsAndLoadBean(t, &ProtectedBranch{ + RepoID: repo1.ID, + BranchName: "main", + }) +} diff --git a/models/fixtures/renamed_branch.yml b/models/fixtures/renamed_branch.yml new file mode 100644 index 0000000000000..efa5130a2b9e9 --- /dev/null +++ b/models/fixtures/renamed_branch.yml @@ -0,0 +1,5 @@ +- + id: 1 + repo_id: 1 + from: dev + to: master diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 33b094e48ccbb..6f6296dabfb92 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -346,6 +346,8 @@ var migrations = []Migration{ NewMigration("Add table commit_status_index", addTableCommitStatusIndex), // v196 -> v197 NewMigration("Add Color to ProjectBoard table", addColorColToProjectBoard), + // v197 -> v198 + NewMigration("Add renamed_branch table", addRenamedBranchTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v197.go b/models/migrations/v197.go new file mode 100644 index 0000000000000..3517896a23dd6 --- /dev/null +++ b/models/migrations/v197.go @@ -0,0 +1,20 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "xorm.io/xorm" +) + +func addRenamedBranchTable(x *xorm.Engine) error { + type RenamedBranch struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX NOT NULL"` + From string + To string + CreatedUnix int64 `xorm:"created"` + } + return x.Sync2(new(RenamedBranch)) +} diff --git a/modules/context/repo.go b/modules/context/repo.go index eceefd9e5925d..8972cd28bc763 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -705,7 +705,28 @@ func getRefName(ctx *Context, pathType RepoRefType) string { ctx.Repo.TreePath = path return ctx.Repo.Repository.DefaultBranch case RepoRefBranch: - return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsBranchExist) + ref := getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsBranchExist) + if len(ref) == 0 { + // maybe it's a renamed branch + return getRefNameFromPath(ctx, path, func(s string) bool { + b, exist, err := models.FindRenamedBranch(ctx.Repo.Repository.ID, s) + if err != nil { + log.Error("FindRenamedBranch", err) + return false + } + + if !exist { + return false + } + + ctx.Data["IsRenamedBranch"] = true + ctx.Data["RenamedBranchName"] = b.To + + return true + }) + } + + return ref case RepoRefTag: return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist) case RepoRefCommit: @@ -784,6 +805,15 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context } else { refName = getRefName(ctx, refType) ctx.Repo.BranchName = refName + isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool) + if isRenamedBranch && has { + renamedBranchName := ctx.Data["RenamedBranchName"].(string) + ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName)) + link := strings.Replace(ctx.Req.RequestURI, refName, renamedBranchName, 1) + ctx.Redirect(link) + return + } + if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) { ctx.Repo.IsViewBranch = true diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 7c30b1fb20446..96f692826ec93 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -164,3 +164,9 @@ func (repo *Repository) RemoveRemote(name string) error { func (branch *Branch) GetCommit() (*Commit, error) { return branch.gitRepo.GetBranchCommit(branch.Name) } + +// RenameBranch rename a branch +func (repo *Repository) RenameBranch(from, to string) error { + _, err := NewCommand("branch", "-m", from, to).RunInDir(repo.Path) + return err +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index bc03f86619412..d5af933f4041a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1985,6 +1985,12 @@ settings.lfs_pointers.inRepo=In Repo settings.lfs_pointers.exists=Exists in store settings.lfs_pointers.accessible=Accessible to User settings.lfs_pointers.associateAccessible=Associate accessible %d OIDs +settings.rename_branch_failed_exist=Cannot rename branch because target branch %s exists. +settings.rename_branch_failed_not_exist=Cannot rename branch %s because it does not exist. +settings.rename_branch_success =Branch %s was successfully renamed to %s. +settings.rename_branch_from=old branch name +settings.rename_branch_to=new branch name +settings.rename_branch=Rename branch diff.browse_source = Browse Source diff.parent = parent @@ -2106,6 +2112,7 @@ branch.create_new_branch = Create branch from branch: branch.confirm_create_branch = Create branch branch.new_branch = Create new branch branch.new_branch_from = Create new branch from '%s' +branch.renamed = Branch %s was renamed to %s. tag.create_tag = Create tag %s tag.create_success = Tag '%s' has been created. diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 376693b882161..a280076e39d0a 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1982,6 +1982,10 @@ settings.lfs_pointers.inRepo=В репозитории settings.lfs_pointers.exists=Существуют в хранилище settings.lfs_pointers.accessible=Доступно для пользователя settings.lfs_pointers.associateAccessible=Связать доступные %d OID +settings.rename_branch_success=Ветка %s была успешно переименована в %s. +settings.rename_branch_from=старое название ветки +settings.rename_branch_to=новое название ветки +settings.rename_branch=Переименовать ветку diff.browse_source=Просмотр исходного кода diff.parent=Родитель @@ -2103,6 +2107,7 @@ branch.create_new_branch=Создать ветку из ветви: branch.confirm_create_branch=Создать ветку branch.new_branch=Создать новую ветку branch.new_branch_from=Создать новую ветку из '%s' +branch.renamed=Ветка %s была переименована в %s. tag.create_tag=Создать тег %s tag.create_success=Тег '%s' был создан. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 775891da17a0d..b450db7c5a103 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -96,6 +96,7 @@ error=错误 error404=您正尝试访问的页面 不存在 或 您尚未被授权 查看该页面。 never=从不 +color=颜色 [error] occurred=发生错误 @@ -345,6 +346,8 @@ reset_password.text=请点击以下链接,恢复你在 %s 的账户: register_success=注册成功 +issue_assigned.pull=@%[1]s 已将仓库 %[3]s 中的合并请求 %[2]s 指派给您 +issue_assigned.issue=@%[1]s 已将仓库 %[3]s 中的工单 %[2]s 指派给您 issue.x_mentioned_you=@%s 提到了您: issue.action.force_push=%[1]s 强制从 %[3]s 推送 %[2]s 至 [4]s。 @@ -967,6 +970,7 @@ file_view_rendered=渲染模式 file_view_raw=查看原始文件 file_permalink=永久链接 file_too_large=文件过大,无法显示。 +file_copy_permalink=复制永久链接 video_not_supported_in_browser=您的浏览器不支持使用 HTML5 'video' 标签。 audio_not_supported_in_browser=您的浏览器不支持使用 HTML5 'video' 标签。 stored_lfs=存储到Git LFS @@ -1194,6 +1198,11 @@ issues.action_milestone_no_select=无里程碑 issues.action_assignee=指派人筛选 issues.action_assignee_no_select=未指派 issues.opened_by=由 %[3]s 于 %[1]s创建 +pulls.merged_by=由 %[3] 于 %[1]s 合并 +pulls.merged_by_fake=由 %[2]s 于 %[1]s 合并 +issues.closed_by=由 %[3]s 于 %[1]s 关闭 +issues.opened_by_fake=由 %[2]s 于 %[1]s 打开 +issues.closed_by_fake=由 %[2]s 于 %[1]s 关闭 issues.previous=上一页 issues.next=下一页 issues.open_title=开启中 @@ -1378,6 +1387,8 @@ pulls.compare_changes=创建合并请求 pulls.compare_changes_desc=选择合并的目标分支和源分支。 pulls.compare_base=合并到 pulls.compare_compare=拉取从 +pulls.switch_comparison_type=切换比较类型 +pulls.switch_head_and_base=切换 head 和 base pulls.filter_branch=过滤分支 pulls.no_results=未找到结果 pulls.nothing_to_compare=分支内容相同,无需创建合并请求。 @@ -2412,6 +2423,7 @@ auths.attribute_name=名字属性 auths.attribute_surname=姓氏属性 auths.attribute_mail=电子邮箱属性 auths.attribute_ssh_public_key=SSH公钥属性 +auths.attribute_avatar=头像属性 auths.attributes_in_bind=从 Bind DN 中拉取属性信息 auths.allow_deactivate_all=允许在搜索结果为空时停用所有用户 auths.use_paged_search=使用分页搜索 diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index c48ab9471a21c..876ff9ba460b6 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" pull_service "code.gitea.io/gitea/services/pull" + "code.gitea.io/gitea/services/repository" ) // ProtectedBranch render the page to protect the repository @@ -285,3 +286,40 @@ func SettingsProtectedBranchPost(ctx *context.Context) { ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) } } + +// RenameBranchPost responses for rename a branch +func RenameBranchPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RenameBranchForm) + + if !ctx.Repo.CanCreateBranch() { + ctx.NotFound("RenameBranch", nil) + return + } + + if ctx.HasError() { + ctx.Flash.Error(ctx.GetErrMsg()) + ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + return + } + + msg, err := repository.RenameBranch(ctx.Repo.Repository, ctx.User, ctx.Repo.GitRepo, form.From, form.To) + if err != nil { + ctx.ServerError("RenameBranch", err) + return + } + + if msg == "target_exist" { + ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To)) + ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + return + } + + if msg == "from_not_exist" { + ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From)) + ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To)) + ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) +} diff --git a/routers/web/web.go b/routers/web/web.go index 01d90d206fd3b..b4103ccad3f14 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -612,6 +612,7 @@ func RegisterRoutes(m *web.Route) { m.Combo("/*").Get(repo.SettingsProtectedBranch). Post(bindIgnErr(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost) }, repo.MustBeNotEmpty) + m.Post("/rename_branch", bindIgnErr(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo.RenameBranchPost) m.Group("/tags", func() { m.Get("", repo.Tags) diff --git a/services/forms/repo_branch_form.go b/services/forms/repo_branch_form.go index 88a069b8310c8..f9262aaede77a 100644 --- a/services/forms/repo_branch_form.go +++ b/services/forms/repo_branch_form.go @@ -24,3 +24,15 @@ func (f *NewBranchForm) Validate(req *http.Request, errs binding.Errors) binding ctx := context.GetContext(req) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } + +// RenameBranchForm form for rename a branch +type RenameBranchForm struct { + From string `binding:"Required;MaxSize(100);GitRefName"` + To string `binding:"Required;MaxSize(100);GitRefName"` +} + +// Validate validates the fields +func (f *RenameBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { + ctx := context.GetContext(req) + return middleware.Validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/services/repository/branch.go b/services/repository/branch.go index 28d24f121d06b..5e246cbec67ca 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -10,10 +10,49 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" pull_service "code.gitea.io/gitea/services/pull" ) +// RenameBranch rename a branch +func RenameBranch(repo *models.Repository, doer *models.User, gitRepo *git.Repository, from, to string) (string, error) { + if from == to { + return "target_exist", nil + } + + if gitRepo.IsBranchExist(to) { + return "target_exist", nil + } + + if !gitRepo.IsBranchExist(from) { + return "from_not_exist", nil + } + + if err := repo.RenameBranch(from, to, func(isDefault bool) error { + err2 := gitRepo.RenameBranch(from, to) + if err2 != nil { + return err2 + } + + if isDefault { + err2 = gitRepo.SetDefaultBranch(to) + if err2 != nil { + return err2 + } + } + + return nil + }); err != nil { + return "", err + } + + notification.NotifyDeleteRef(doer, repo, "branch", "refs/heads/"+from) + notification.NotifyCreateRef(doer, repo, "branch", "refs/heads/"+to) + + return "", nil +} + // enmuerates all branch related errors var ( ErrBranchIsDefault = errors.New("branch is default") diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl index ccf6abbb81c01..89d7c6db77a31 100644 --- a/templates/repo/settings/branches.tmpl +++ b/templates/repo/settings/branches.tmpl @@ -77,6 +77,28 @@ + + {{if $.Repository.CanCreateBranch}} +