From 36943e56d66a2d711a6b0c27219ce91a3ddc020a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 17 Jan 2020 07:03:40 +0100 Subject: [PATCH] Add "Update Branch" button to Pull Requests (#9784) * add Divergence * add Update Button * first working version * re-use code * split raw merge commands and db-change functions (notify, cache, ...) * use rawMerge (remove redundant code) * own function to get Diverging of PRs * use FlashError * correct Error Msg * hook is triggerd ... so remove comment * add "branch2" to "user2/repo1" because it unit-test "TestPullView_ReviewerMissed" use it but dont exist jet :/ * move GetPerm to IsUserAllowedToUpdate * add Flash Success MSG * imprufe code - remove useless js chage * fix-lint * TEST: add PullRequest ID:5 Repo: user2/repo1 Base: branch1 Head: pr-to-update * correct comments * make PR5 outdated * fix Tests * WIP: add pull update test * update revs * update locales * working TEST * update UI * misspell * change style * add 1s delay so rev exist * move row up (before merge row) * fix lint nit * UI remove divider * Update style * nits * do it right * introduce IsSameRepo * remove useless check Co-authored-by: Lauris BH --- integrations/api_issue_test.go | 6 +- .../user2/repo1.git/info/refs | 2 + .../5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2 | Bin 0 -> 833 bytes .../62/fb502a7172d4453f0322a2cc85bddffa57f07a | Bin 0 -> 839 bytes .../6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb | Bin 0 -> 86 bytes .../7c/4df115542e05c700f297519e906fd63c9c9804 | Bin 0 -> 54 bytes .../94/922e1295c678267de1193b7b84ad8a086c27f9 | Bin 0 -> 54 bytes .../98/5f0301dba5e7b34be866819cd15ad3d8f508ee | Bin 0 -> 842 bytes .../a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a | Bin 0 -> 76 bytes .../a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d | Bin 0 -> 61 bytes .../b2/60587271671842af0b036e4fe643c9d45b7ddd | Bin 0 -> 20 bytes .../user2/repo1.git/refs/heads/branch2 | 1 + .../user2/repo1.git/refs/heads/pr-to-update | 1 + .../user2/repo1.git/refs/pull/2/head | 1 + .../user2/repo1.git/refs/pull/5/head | 1 + integrations/pull_update_test.go | 136 ++++++++++++++++++ integrations/repo_activity_test.go | 4 +- models/fixtures/issue.yml | 12 ++ models/fixtures/pull_request.yml | 15 +- models/fixtures/repository.yml | 2 +- models/issue_test.go | 8 +- models/issue_user_test.go | 2 +- models/pull.go | 5 + models/pull_test.go | 18 +-- modules/indexer/issues/indexer_test.go | 4 +- options/locale/locale_en-US.ini | 4 + routers/repo/pull.go | 82 ++++++++++- routers/routes/routes.go | 1 + services/pull/merge.go | 100 +++++++------ services/pull/update.go | 125 ++++++++++++++++ templates/repo/issue/view_content/pull.tmpl | 20 +++ web_src/less/_repository.less | 7 + 32 files changed, 489 insertions(+), 68 deletions(-) create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2 create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804 create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9 create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/branch2 create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/pr-to-update create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/2/head create mode 100644 integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/5/head create mode 100644 integrations/pull_update_test.go create mode 100644 services/pull/update.go diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index ce1c4b7d33..906dbb2dc7 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -134,7 +134,7 @@ func TestAPISearchIssue(t *testing.T) { var apiIssues []*api.Issue DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 8) + assert.Len(t, apiIssues, 9) query := url.Values{} query.Add("token", token) @@ -142,7 +142,7 @@ func TestAPISearchIssue(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 8) + assert.Len(t, apiIssues, 9) query.Add("state", "closed") link.RawQuery = query.Encode() @@ -163,5 +163,5 @@ func TestAPISearchIssue(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 0) + assert.Len(t, apiIssues, 1) } diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/info/refs b/integrations/gitea-repositories-meta/user2/repo1.git/info/refs index ca1df85e2e..fa3009793d 100644 --- a/integrations/gitea-repositories-meta/user2/repo1.git/info/refs +++ b/integrations/gitea-repositories-meta/user2/repo1.git/info/refs @@ -1 +1,3 @@ 65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master +985f0301dba5e7b34be866819cd15ad3d8f508ee refs/heads/branch2 +62fb502a7172d4453f0322a2cc85bddffa57f07a refs/heads/pr-to-update diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2 b/integrations/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2 new file mode 100644 index 0000000000000000000000000000000000000000..c0cb626359e600503128d5c65213dd0fcb1c1fcf GIT binary patch literal 833 zcmb5&`5>6`Hv%e4xtbIU{Th3sh(Kfo<<=4_t6#CvYGKVK`2cRn+)da+>A zs*(vF*G-=CNN8-VKa)I-(dCCpx`g9{n><`Bjx#n(aPpip5cwSPghMT&$>!?u*C|G||v$^W9dy{-eBN`G&45zFSVL>a@P(eB;u;eOGyRKI^{ne$QMnL-w}{>T5!S z8%%hF*_}#1r$m3f`g^%8tK~KOCuep@_1czC+|^xGu=v>P_1cxZFETcV@?0*d{QUlb zOQY8DZ#kRpGORU?`}-<*)wby`@0EXfaruzkwD#b=mn~ktiuqn*dJewH&oJeN#vyZ6fL_SFg> zrDQXHkqu@n8`nzd^3Qc~zc}Om;TM@VG$o>TE@||NE@ z4|^=Er!obxKQGvwZL;jTerxQsCtGh+pHsN|N@}X@p+LE6-8WV1HDBM9IW4EeMHm4 z48KdNJwjYB9>37t-n4IC>-1$Ck0sqRn_IPI=jVNUZi_!RTXHNnPuII?L)rY~#P{pn zM^n=S8??{b4&JyU_$%gZ-~ zzj<`=L!E-Q9_zipdcOMsle!mfcy#Ty-uv#VVE(W@y7`B1d{8uNnfr84$9d|2t({qr73z26 zP;l=ao2PGY^ccua6EQfwc8!PXt%~?~ksnUqlYRbvSNpr*@BQg3Bqmhf5tIC?KSk5; F7XZ*)t&RWy literal 0 HcmV?d00001 diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a b/integrations/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a new file mode 100644 index 0000000000000000000000000000000000000000..ee494a8ca8c4a75d3d5ee3d519428e0a5b0a50d9 GIT binary patch literal 839 zcmbuRZZr1)O# zz)}_WG#@5oEANc9L!6-wX6YH*76{Ey@KKxMu+*gS`OAC!3`IK3|NgD~|Had8OYzt6 zY5y5Sjw{bnQG9fuZ;8hwk9yYGpYE4WIbXEK>%;T&ldmjJWBQ%J>~B45k^GKV>+Kmi z0xWlFbuLorepIs}Wa`?zX*}}bmF2N6kV5dviZ_8Jx>L5zIo#7EzH!RWTdAT#OQ#-v`t-@c z8r6o`qGzAG-O_j6D95zc=2qp0nZbLwdcIXCzlbl3P`)U^Id{w9XAc#eC+4+(Skgaz ztywhF6s{h(ptY->uN2Uj5p}j&e%agAR~H?-bnfWSIsH!ZD>X~=mwzgG@Z;Hrui`tq z(*M`&UCmOl=}!;O^5;kOrb_!nt85WT?p+`*Y4CO4;mEz-tM+CVrdNe}pZ=}*Z(UkR z_WbkizA6oY>eZ#mvd^Z@3jXtm-M-Ku)L?>8v~yDIYTf%^KSf@Td**Ea;m7NLpDX7U z^wcc8bytMx2G^wiKaOhG17_dw*t6z&u0Z%vNx3Hh@ALZZ6>^!S~UpUb^-_N+j`Zhea(gPUHk2YkC+~tv)|maXVa zE-!ok9ecO;&a&_`Q&>LiO6b|Ul8061?A@}J7lWP6=1fH{a>z5G)lf=T&%_rs{37{%qOY!U`3kf{S|+=@l3NSS+`=1k@i8G@2A#wpVP6c z&fCp7f9g#=wwHnI;(Pz)U3&cf{S3A2=dZgN;=ks7?D(G%vFY3=eAQ&pzdJk0<1@ImX+`hM?o-BVc^+|}f)0V+x+i2wiq literal 0 HcmV?d00001 diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804 b/integrations/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804 new file mode 100644 index 0000000000000000000000000000000000000000..3bf67a206abf6a29f1e69ed3c9c1aa119c5581f8 GIT binary patch literal 54 zcmbp-pZ3pI&r&~3 KjKQ-*P#yphauiYk literal 0 HcmV?d00001 diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9 b/integrations/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9 new file mode 100644 index 0000000000000000000000000000000000000000..60692df6ec48948b93f23166b72d863bb7a09950 GIT binary patch literal 54 zcmb`$6x#g2darmBj LtPCcP_$LAY9rqRp literal 0 HcmV?d00001 diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee b/integrations/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee new file mode 100644 index 0000000000000000000000000000000000000000..81fd6a50fda008977f5a303d34ff0442fc0a3c18 GIT binary patch literal 842 zcmbiQ6nIxX#N-mOp$IBIi9@V zq<+c|{R1464rwq=5pi&4b6WKyH0Rj=`+@eaSFSwx-oEh4`DN^S&$&6yZf;+cz2N^P zO^3!~+rKC~xr;7*dgX=He~#UndBIct;yp}7gKQLK_!_NWs9*V5YA?EP;nJSvl^W@5 z`TY+o9iH*v&mQf&p+3G>w^&`f&}H@Mlww+DX^8%;*v_R!QSYV) zzZFUFJ+a64`0~)N^ITr-nD;I7sN2&|HhjCXx7AzzUG8q7n3Ws(VE50j%aY54g8!Vj zo?2e7GND3tt*%x2ODJWdhEQmD_YOLtytCM z@1EM=gbT};ExD{09KZ5mhxH|-Q{y+I zIb(wgS4mj?+Wk7~Pp~dO>p$nI)6a96oA#G(PxZW`yTSKi#Dh!Uk9}1<E8vxO>S2*laD$Zov9G>+g`19XV`Qm)$H-4 KU+i!CLZtxB?4lF^ literal 0 HcmV?d00001 diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a b/integrations/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a new file mode 100644 index 0000000000000000000000000000000000000000..887669883b75550827b0fa1e0f499a8b085e4eed GIT binary patch literal 76 zcmb 512 { + x = "..." + string(runes[len(runes)-512:]) + } + + return strings.Replace(html.EscapeString(x), "\n", "
", -1) + } + if models.IsErrMergeConflicts(err) { + conflictError := err.(models.ErrMergeConflicts) + ctx.Flash.Error(ctx.Tr("repo.pulls.merge_conflict", sanitize(conflictError.StdErr), sanitize(conflictError.StdOut))) + ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + return + } + ctx.Flash.Error(err.Error()) + ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + } + + time.Sleep(1 * time.Second) + + ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) +} + // MergePullRequest response for merging pull request func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { issue := checkPullInfo(ctx) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 58a2da82fc..7e81f55de6 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -855,6 +855,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get(".patch", repo.DownloadPullPatch) m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) m.Post("/merge", context.RepoMustNotBeArchived(), reqRepoPullsWriter, bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest) + m.Post("/update", repo.UpdatePullRequest) m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) m.Group("/files", func() { m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles) diff --git a/services/pull/merge.go b/services/pull/merge.go index b423c663ea..26c9ab3d1c 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -33,11 +33,6 @@ import ( // Caller should check PR is ready to be merged (review and status checks) // FIXME: add repoWorkingPull make sure two merges does not happen at same time. func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repository, mergeStyle models.MergeStyle, message string) (err error) { - binVersion, err := git.BinVersion() - if err != nil { - log.Error("git.BinVersion: %v", err) - return fmt.Errorf("Unable to get git version: %v", err) - } if err = pr.GetHeadRepo(); err != nil { log.Error("GetHeadRepo: %v", err) @@ -63,6 +58,61 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "") }() + if err := rawMerge(pr, doer, mergeStyle, message); err != nil { + return err + } + + pr.MergedCommitID, err = baseGitRepo.GetBranchCommitID(pr.BaseBranch) + if err != nil { + return fmt.Errorf("GetBranchCommit: %v", err) + } + + pr.MergedUnix = timeutil.TimeStampNow() + pr.Merger = doer + pr.MergerID = doer.ID + + if err = pr.SetMerged(); err != nil { + log.Error("setMerged [%d]: %v", pr.ID, err) + } + + notification.NotifyMergePullRequest(pr, doer) + + // Reset cached commit count + cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true)) + + // Resolve cross references + refs, err := pr.ResolveCrossReferences() + if err != nil { + log.Error("ResolveCrossReferences: %v", err) + return nil + } + + for _, ref := range refs { + if err = ref.LoadIssue(); err != nil { + return err + } + if err = ref.Issue.LoadRepo(); err != nil { + return err + } + close := (ref.RefAction == references.XRefActionCloses) + if close != ref.Issue.IsClosed { + if err = issue_service.ChangeStatus(ref.Issue, doer, close); err != nil { + return err + } + } + } + + return nil +} + +// rawMerge perform the merge operation without changing any pull information in database +func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.MergeStyle, message string) (err error) { + binVersion, err := git.BinVersion() + if err != nil { + log.Error("git.BinVersion: %v", err) + return fmt.Errorf("Unable to get git version: %v", err) + } + // Clone base repo. tmpBasePath, err := createTemporaryRepo(pr) if err != nil { @@ -337,46 +387,6 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor outbuf.Reset() errbuf.Reset() - pr.MergedCommitID, err = baseGitRepo.GetBranchCommitID(pr.BaseBranch) - if err != nil { - return fmt.Errorf("GetBranchCommit: %v", err) - } - - pr.MergedUnix = timeutil.TimeStampNow() - pr.Merger = doer - pr.MergerID = doer.ID - - if err = pr.SetMerged(); err != nil { - log.Error("setMerged [%d]: %v", pr.ID, err) - } - - notification.NotifyMergePullRequest(pr, doer) - - // Reset cached commit count - cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true)) - - // Resolve cross references - refs, err := pr.ResolveCrossReferences() - if err != nil { - log.Error("ResolveCrossReferences: %v", err) - return nil - } - - for _, ref := range refs { - if err = ref.LoadIssue(); err != nil { - return err - } - if err = ref.Issue.LoadRepo(); err != nil { - return err - } - close := (ref.RefAction == references.XRefActionCloses) - if close != ref.Issue.IsClosed { - if err = issue_service.ChangeStatus(ref.Issue, doer, close); err != nil { - return err - } - } - } - return nil } diff --git a/services/pull/update.go b/services/pull/update.go new file mode 100644 index 0000000000..5f055827e1 --- /dev/null +++ b/services/pull/update.go @@ -0,0 +1,125 @@ +// Copyright 2020 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 pull + +import ( + "fmt" + "strconv" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +// Update updates pull request with base branch. +func Update(pull *models.PullRequest, doer *models.User, message string) error { + //use merge functions but switch repo's and branch's + pr := &models.PullRequest{ + HeadRepoID: pull.BaseRepoID, + BaseRepoID: pull.HeadRepoID, + HeadBranch: pull.BaseBranch, + BaseBranch: pull.HeadBranch, + } + + if err := pr.LoadHeadRepo(); err != nil { + log.Error("LoadHeadRepo: %v", err) + return fmt.Errorf("LoadHeadRepo: %v", err) + } else if err = pr.LoadBaseRepo(); err != nil { + log.Error("LoadBaseRepo: %v", err) + return fmt.Errorf("LoadBaseRepo: %v", err) + } + + diffCount, err := GetDiverging(pull) + if err != nil { + return err + } else if diffCount.Behind == 0 { + return fmt.Errorf("HeadBranch of PR %d is up to date", pull.Index) + } + + defer func() { + go AddTestPullRequestTask(doer, pr.HeadRepo.ID, pr.HeadBranch, false, "", "") + }() + + return rawMerge(pr, doer, models.MergeStyleMerge, message) +} + +// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections +func IsUserAllowedToUpdate(pull *models.PullRequest, user *models.User) (bool, error) { + headRepoPerm, err := models.GetUserRepoPermission(pull.HeadRepo, user) + if err != nil { + return false, err + } + + pr := &models.PullRequest{ + HeadRepoID: pull.BaseRepoID, + BaseRepoID: pull.HeadRepoID, + HeadBranch: pull.BaseBranch, + BaseBranch: pull.HeadBranch, + } + return IsUserAllowedToMerge(pr, headRepoPerm, user) +} + +// GetDiverging determines how many commits a PR is ahead or behind the PR base branch +func GetDiverging(pr *models.PullRequest) (*git.DivergeObject, error) { + log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName()) + if err := pr.LoadBaseRepo(); err != nil { + return nil, err + } + if err := pr.LoadHeadRepo(); err != nil { + return nil, err + } + + headRepoPath := pr.HeadRepo.RepoPath() + headGitRepo, err := git.OpenRepository(headRepoPath) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %v", err) + } + defer headGitRepo.Close() + + if pr.IsSameRepo() { + diff, err := git.GetDivergingCommits(pr.HeadRepo.RepoPath(), pr.BaseBranch, pr.HeadBranch) + return &diff, err + } + + tmpRemoteName := fmt.Sprintf("tmp-pull-%d-base", pr.ID) + if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), true); err != nil { + return nil, fmt.Errorf("headGitRepo.AddRemote: %v", err) + } + // Make sure to remove the remote even if the push fails + defer func() { + if err := headGitRepo.RemoveRemote(tmpRemoteName); err != nil { + log.Error("CountDiverging: RemoveRemote: %s", err) + } + }() + + // $(git rev-list --count tmp-pull-1-base/master..feature) commits ahead of master + ahead, errorAhead := checkDivergence(headRepoPath, fmt.Sprintf("%s/%s", tmpRemoteName, pr.BaseBranch), pr.HeadBranch) + if errorAhead != nil { + return &git.DivergeObject{}, errorAhead + } + + // $(git rev-list --count feature..tmp-pull-1-base/master) commits behind master + behind, errorBehind := checkDivergence(headRepoPath, pr.HeadBranch, fmt.Sprintf("%s/%s", tmpRemoteName, pr.BaseBranch)) + if errorBehind != nil { + return &git.DivergeObject{}, errorBehind + } + + return &git.DivergeObject{Ahead: ahead, Behind: behind}, nil +} + +func checkDivergence(repoPath string, baseBranch string, targetBranch string) (int, error) { + branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch) + cmd := git.NewCommand("rev-list", "--count", branches) + stdout, err := cmd.RunInDir(repoPath) + if err != nil { + return -1, err + } + outInteger, errInteger := strconv.Atoi(strings.Trim(stdout, "\n")) + if errInteger != nil { + return -1, errInteger + } + return outInteger, nil +} diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index f8a82f1a0f..d15237137d 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -157,6 +157,26 @@ {{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }} {{end}} + {{if and .Divergence (gt .Divergence.Behind 0)}} +
+
+
+ + {{$.i18n.Tr "repo.pulls.outdated_with_base_branch"}} +
+ {{if .UpdateAllowed}} +
+
+ {{.CsrfTokenHtml}} + +
+
+ {{end}} +
+
+ {{end}} {{if .AllowMerge}} {{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} {{$approvers := .Issue.PullRequest.GetApprovers}} diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index 27a0698f7b..a1b55e86aa 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -655,6 +655,13 @@ .icon-octicon { padding-left: 2px; } + .branch-update.grid { + margin-bottom: -1.5rem; + margin-top: -0.5rem; + .row { + padding-bottom: 0; + } + } } .review-item {