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 <lauris@nix.lv>
---
 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
zcmV-H1HSxt0gY41vf@Sn%h_MicPepvZFg1PRJ?*0zzZ16&2BFk?+XU}^?5V7<}f<w
zppvR|kVw}w<qQ}ov46~CmH|ZgG-nt=;Moic1VNay7zqr{FckAt<On8Mf;`p7W7f_<
z6mllXS(Fei$q~#&UKEfhNFpa>q6jgcr3p#+l!~A8y&J#%JO}>%$^UdoHa5@luQdAs
z7y)5M`c`?cfeeHc`BiMrem_z4#Z(sH&!15At$6_M)&+sNJ;<lCum931K!MV;bVZgG
zRhFMdsq6eBsHd=s)J?R=s);+&L)g;(zSRx6=8M$tyqy*?2kjsytW{HhGz=35LUr?G
zTlLlZr#%HZ)<5LTxEhT;cage_dT^u0>#r+$4d`gIRSeT~dGqh!M1d;D_H44<0IfA%
z{pwSywwLMNcl7Af!XB1~Q@ztT>FQkfb~#`%HXP}sux3o$9vvyb`IDfe%=#(j`W58j
z^>m%um?fTXsMdzH(EILi501YC%=k&9lZM{vO54?VOd?}84-~LA1hK6eEf1YUN70zy
z!{}+)DHvM0q=ZR2)JCp6bd00u*x_}(tDVN~diC(UI6IXBQU~X7yK6M2*Dr-#mgKeA
zd(I%;>Kc_j!ciW3E$n))DeKu9%)D7WTgB!&L{(I@6!7fevzD(+>8m?^8qyML{Vj_M
z^4SSt%1xJ*#*@Um!(s+Of@kG%uULZNXim~4j1mgy<LE87g1(-Gm!`4CV%|2LdbGLo
zzP#HP=QgCqZEh_~g0Oa<V`1;FMjj56%lI-cLkf&)=sMYM8@@23HD(A|$Dy+=%mPvg
ziB%Yz25$0Ar!Z|UH4t6Xr?OY<;?eMaAS*5f-cf%J-%yf^p|IlC+b-{mdQ%Tpye)6T
z*zh4Xgq`KQbft;H;eL5`$rIn`fTS}gDk24@E3GKefkw0+O|GHDug4CVT`8Q0a^B5y
zHm!(NEum)z*YkbmAz#b;BFLn3s~3I#-sF)-HI3qVxv52rgt|rDeRs14-63*)F-5s&
zn&&dDnQ&@VPT0azjl4YN?bwVk9hMX@%dM<PCE9pjUljO4_hU@`_a^<I{oj|U7?6A1
L78&&~lqpX7&|IyK

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
zcmV-N1GxNn0gaPMui{n|gtPXqxO<e~@!D6)YsC{_gXa-;*CRF_z$50@=XGbBMW012
zj#N^qN?+QyZ8ZUk6yP7zkmmqm%wUcYI7m?`mnQ-eV2Ti=FiIH~BPLBS&WeP1;vw%Q
zz@;QdW=sMzI!kDV!U<JCHfLzIpc0rMOeT~-r8I%(>D~_jax4S>{;B`;Nj|i5^;eev
z02Id~nj|6Oz>cIyl1P6AoATdxl+SXkO7Q2;lhmVT0{dtKPdl2TAGqqTKLH3(Il3x|
zq9lvre5h2Ft-WRpA6I#IT|`+!eflKHZtohMdJr47951PJQm&M#m|{Zf4FS@YqOiw3
zZjN}CUHQIszE>c1OT+r8>``<rl(wA1jDi=jl=<3a6%wVD#pUJX!F(lvn-$IHM%ph$
zu^s}gJlc8inNz$9jP`gV=wEhNBheTMowUQ|;UGhiZin!skC&}BTLP2{j7Czes^_T?
zD41H!dmpCltJR{$($2;CoR2~usVQ}Dr}A{*_~x+n7rBaO|9rix1bDLij0~se#V(Z_
zOj{(h6l0B`7a1`1p2JzaORBwTac6o}OUwHq{;p<pYoE_YO(cL(Cwp~c9p;sqQ~crw
zKXEWsFpv^kLt<U4E#LL>S=V0XLqG8N>;Ch3op6kNq1xRP0@w<YkNiO<*HD|-NW84)
zZ4g(*86M<N?{1FXaMc{x&z<vHUOpYK^wrj9sgv|vGxd=Gi^Lvy-MI~3B6p{wmza+I
z*|#}bl~cSit7*MI8mbFgtg3SA`B+VDHPp)8B}(egHW46X6Adz~>qXT`?Z!;p<BvPV
z;rq^R-llhMu&;v9v*Sm|52nc7eCyqW1!O)*J>*63y~U|(JF3dQgK-~<zdsYiHO8F3
zH%W}Trg?~c<uz~?8B&dnJzqrWjsC{&z1*f(%#;N1x?qgGsSE`i&E0mX(NjY<oR=V&
zWho70-N;<b$Gh&d)YHg1Mb^Lc)sZq|)H8D?097sb7<Nozg~51c72l}+whWd!bgrnZ
zGAqG6_sXq{&MZEAZo3JemDw%^=}`w4z5Z^}<L~d7CTr*GivVBsZt;l!Xi`n(|5ze$
RmVu~`z@ch#;$O#7Rc2yytXu#9

literal 0
HcmV?d00001

diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb b/integrations/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb
new file mode 100644
index 0000000000000000000000000000000000000000..09aed946f2fa49948e15cc8ccbb0b523f356ad6c
GIT binary patch
literal 86
zcmV-c0IC0Y0V^p=O;s>AW-v4`Ff%bxaLdd|)eX-NXV{bwQB;^N;k2HcInV!@^T{jG
swRfRvf?OS4d|mZ&Qy7+qA9$4{mwe%G{^8(-rGJl0&Wn`=07oVsJ1R;hi2wiq

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
zcmb<m)YkO!4K*-JH!v|UFg6V2C@L-aSbFueZ(@X8-Gk6cF`ruZ|7>p-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
zcmV-60LlM&0V^p=O;s>9XD~D{Ff%bx2y%6F@paY9O<`Czsr-c8gq>YAT$`d<wDL4_
MSG=AD05swcksbFI2mk;8

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
zcmV-Q1GW5k0gY41j;lrh%$cv~J4)Q_AZ4Po0h@iyVhnR*X)tDK4~qeRecpSy=8!tn
zp`<F6N~(hNeKP|FD%@}Lh%r!jg=I0rDncPjGAl|YW*CLbrCjE-QqG_(7OYe$)H56L
zFax200xY>)EZH(=IVQ`QO5`!e@)eVdxst)0k(q*}vSq%L5r_iMfj@up-z3Llzchc8
z_%FZ+lENqgloUWi212U%Rcyxp-%&s7sj0#5KXKI0rUx$P3kc1#hoWns|I#TyfyTG=
zBSJ?Nq2<)*I{yf}DQ;7hPG3kh6;eDQ91ejy=qJ?ibw3rpp{0zcc_?SCjsiu`Fj*+H
zUp}(cK)vrn6jqA97nyX_ks1vT(Fv9CB83d*OggAf^;8u0O@pUF=kZ8^UX3P`4awy}
zawkUIw!@g<{JbmORZLCQv^v(%iaPSjA!ccHR4<xciKQ}H?w3>T6kturyiLcaRrQ`k
z>bRcvX~jn6@;nZ@Yqozm{ijDbA!=<|;Jf+trek&zQ~b!+Wp{rhka!)fEjnkDX}>n;
zt4Ce_=6n)wnjjfLyV*}uYr-dtxtU10*1B6RPlj_PH2u7NQ((}irliv@Q(vj!h&$2(
zq=tt*?!PTIZhWa+v^%Mpz?p-%a?KlAOzoU}PMCveup|m}R)4L(EU(A~r_E2C<wE(+
zX|lg{w`ED(EwD}DSm4t4#`Ph}O%jXgz`x7~3F>nhQNXDO{KUs9x_+y+2HRA+drsIL
zx>u;X=%9ad!yp{q@|DhVS{!8hO74EjVW<hi7`rbDv_W=RdBd8Q6uNRVW)*)xS@SvG
zmeLYXTyiAvs<#k@>5*R-JyBRyCoUFl{JIT=Nr6dw_hV~^u`?ra*|TdCj0l}Gcs<DT
z!I*=$6K~J*;hxG3oxAv+-<5>l4NIIDvDG~aEKvftHTq2I7s9iVJ9Nv!ac&!038jL}
zQfb@{jfQT=ho7v^g&m&6kUDnsZ1wP+1{C0UMR|ud+)2+2>qQwG`*2f&M$~Cz#X~a8
zcot5#dnUhp=JTGEi6*H>+e>#OJ{3~_Sn`IknN^v&etb~iqfuXk`u9z`r~TKLs6J~k
UcrINh0J6+wHsg}|2keej8qMsY6aWAK

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
zcmV-S0JHyi0ZYosPf{>9Vo+8nN-fAY<l=HkElw`VEGWs$&r?XtFM<g>=A|ekXC&sO
ir-DV3iW2jZGmN+rfx?Lj#i_~pKt_IEDi;6+g&FhS2q5GD

literal 0
HcmV?d00001

diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d b/integrations/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d
new file mode 100644
index 0000000000000000000000000000000000000000..c3111a08b847838e2f8aac8f97e9ab2dad37bad9
GIT binary patch
literal 61
zcmV-D0K)%x0ZYosPf{>7Wl&ZqN-fAY<l=HkElw`VEGWs$&r?XtFM<g>=A|ekXC&sO
Tr-DV3iW2jZGmN+Zc=HrE6jK^Y

literal 0
HcmV?d00001

diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd b/integrations/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd
new file mode 100644
index 0000000000000000000000000000000000000000..9182ac038166ac30f7f550603dc8b87c1b102bc9
GIT binary patch
literal 20
bcmb<m^geacKgb}J<2--(6D9@;XJ%UfOmPNG

literal 0
HcmV?d00001

diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/branch2 b/integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/branch2
new file mode 100644
index 0000000000..5add7256cd
--- /dev/null
+++ b/integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/branch2
@@ -0,0 +1 @@
+985f0301dba5e7b34be866819cd15ad3d8f508ee
diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/pr-to-update b/integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/pr-to-update
new file mode 100644
index 0000000000..e0ee44dd14
--- /dev/null
+++ b/integrations/gitea-repositories-meta/user2/repo1.git/refs/heads/pr-to-update
@@ -0,0 +1 @@
+62fb502a7172d4453f0322a2cc85bddffa57f07a
diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/2/head b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/2/head
new file mode 100644
index 0000000000..98593d6537
--- /dev/null
+++ b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/2/head
@@ -0,0 +1 @@
+4a357436d925b5c974181ff12a994538ddc5a269
diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/5/head b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/5/head
new file mode 100644
index 0000000000..e0ee44dd14
--- /dev/null
+++ b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/5/head
@@ -0,0 +1 @@
+62fb502a7172d4453f0322a2cc85bddffa57f07a
diff --git a/integrations/pull_update_test.go b/integrations/pull_update_test.go
new file mode 100644
index 0000000000..484390001c
--- /dev/null
+++ b/integrations/pull_update_test.go
@@ -0,0 +1,136 @@
+// 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 integrations
+
+import (
+	"fmt"
+	"net/url"
+	"testing"
+	"time"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/repofiles"
+	repo_module "code.gitea.io/gitea/modules/repository"
+	pull_service "code.gitea.io/gitea/services/pull"
+	repo_service "code.gitea.io/gitea/services/repository"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPullUpdate(t *testing.T) {
+	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+		//Create PR to test
+		user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+		org26 := models.AssertExistsAndLoadBean(t, &models.User{ID: 26}).(*models.User)
+		pr := createOutdatedPR(t, user, org26)
+
+		//Test GetDiverging
+		diffCount, err := pull_service.GetDiverging(pr)
+		assert.NoError(t, err)
+		assert.EqualValues(t, 1, diffCount.Behind)
+		assert.EqualValues(t, 1, diffCount.Ahead)
+
+		message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
+		err = pull_service.Update(pr, user, message)
+		assert.NoError(t, err)
+
+		//Test GetDiverging after update
+		diffCount, err = pull_service.GetDiverging(pr)
+		assert.NoError(t, err)
+		assert.EqualValues(t, 0, diffCount.Behind)
+		assert.EqualValues(t, 2, diffCount.Ahead)
+
+	})
+}
+
+func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullRequest {
+	baseRepo, err := repo_service.CreateRepository(actor, actor, models.CreateRepoOptions{
+		Name:        "repo-pr-update",
+		Description: "repo-tmp-pr-update description",
+		AutoInit:    true,
+		Gitignores:  "C,C++",
+		License:     "MIT",
+		Readme:      "Default",
+		IsPrivate:   false,
+	})
+	assert.NoError(t, err)
+	assert.NotEmpty(t, baseRepo)
+
+	headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc")
+	assert.NoError(t, err)
+	assert.NotEmpty(t, headRepo)
+
+	//create a commit on base Repo
+	_, err = repofiles.CreateOrUpdateRepoFile(baseRepo, actor, &repofiles.UpdateRepoFileOptions{
+		TreePath:  "File_A",
+		Message:   "Add File A",
+		Content:   "File A",
+		IsNewFile: true,
+		OldBranch: "master",
+		NewBranch: "master",
+		Author: &repofiles.IdentityOptions{
+			Name:  actor.Name,
+			Email: actor.Email,
+		},
+		Committer: &repofiles.IdentityOptions{
+			Name:  actor.Name,
+			Email: actor.Email,
+		},
+		Dates: &repofiles.CommitDateOptions{
+			Author:    time.Now(),
+			Committer: time.Now(),
+		},
+	})
+	assert.NoError(t, err)
+
+	//create a commit on head Repo
+	_, err = repofiles.CreateOrUpdateRepoFile(headRepo, actor, &repofiles.UpdateRepoFileOptions{
+		TreePath:  "File_B",
+		Message:   "Add File on PR branch",
+		Content:   "File B",
+		IsNewFile: true,
+		OldBranch: "master",
+		NewBranch: "newBranch",
+		Author: &repofiles.IdentityOptions{
+			Name:  actor.Name,
+			Email: actor.Email,
+		},
+		Committer: &repofiles.IdentityOptions{
+			Name:  actor.Name,
+			Email: actor.Email,
+		},
+		Dates: &repofiles.CommitDateOptions{
+			Author:    time.Now(),
+			Committer: time.Now(),
+		},
+	})
+	assert.NoError(t, err)
+
+	//create Pull
+	pullIssue := &models.Issue{
+		RepoID:   baseRepo.ID,
+		Title:    "Test Pull -to-update-",
+		PosterID: actor.ID,
+		Poster:   actor,
+		IsPull:   true,
+	}
+	pullRequest := &models.PullRequest{
+		HeadRepoID: headRepo.ID,
+		BaseRepoID: baseRepo.ID,
+		HeadBranch: "newBranch",
+		BaseBranch: "master",
+		HeadRepo:   headRepo,
+		BaseRepo:   baseRepo,
+		Type:       models.PullRequestGitea,
+	}
+	err = pull_service.NewPullRequest(baseRepo, pullIssue, nil, nil, pullRequest, nil)
+	assert.NoError(t, err)
+
+	issue := models.AssertExistsAndLoadBean(t, &models.Issue{Title: "Test Pull -to-update-"}).(*models.Issue)
+	pr, err := models.GetPullRequestByIssueID(issue.ID)
+	assert.NoError(t, err)
+
+	return pr
+}
diff --git a/integrations/repo_activity_test.go b/integrations/repo_activity_test.go
index cec5c79c4d..e21f27893d 100644
--- a/integrations/repo_activity_test.go
+++ b/integrations/repo_activity_test.go
@@ -56,9 +56,9 @@ func TestRepoActivity(t *testing.T) {
 		list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc")
 		assert.Len(t, list.Nodes, 1)
 
-		// Should be 2 merged proposed pull requests
+		// Should be 3 merged proposed pull requests
 		list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc")
-		assert.Len(t, list.Nodes, 2)
+		assert.Len(t, list.Nodes, 3)
 
 		// Should be 3 new issues
 		list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc")
diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml
index ecee7499f6..e52a23a46b 100644
--- a/models/fixtures/issue.yml
+++ b/models/fixtures/issue.yml
@@ -122,3 +122,15 @@
   created_unix: 946684830
   updated_unix: 999307200
   deadline_unix: 1019307200
+
+-
+  id: 11
+  repo_id: 1
+  index: 5
+  poster_id: 1
+  name: pull5
+  content: content for the a pull request
+  is_closed: false
+  is_pull: true
+  created_unix: 1579194806
+  updated_unix: 1579194806
diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml
index e8d81a0007..da9566bc48 100644
--- a/models/fixtures/pull_request.yml
+++ b/models/fixtures/pull_request.yml
@@ -49,4 +49,17 @@
   head_branch: branch1
   base_branch: master
   merge_base: abcdef1234567890
-  has_merged: false
\ No newline at end of file
+  has_merged: false
+
+-
+  id: 5 # this PR is outdated (one commit behind branch1 )
+  type: 0 # gitea pull request
+  status: 2 # mergable
+  issue_id: 11
+  index: 5
+  head_repo_id: 1
+  base_repo_id: 1
+  head_branch: pr-to-update
+  base_branch: branch1
+  merge_base: 1234567890abcdef
+  has_merged: false
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index a68e63e309..05989d9030 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -7,7 +7,7 @@
   is_private: false
   num_issues: 2
   num_closed_issues: 1
-  num_pulls: 2
+  num_pulls: 3
   num_closed_pulls: 0
   num_milestones: 3
   num_closed_milestones: 1
diff --git a/models/issue_test.go b/models/issue_test.go
index ec4867d075..d65345a508 100644
--- a/models/issue_test.go
+++ b/models/issue_test.go
@@ -276,8 +276,8 @@ func TestIssue_SearchIssueIDsByKeyword(t *testing.T) {
 
 	total, ids, err = SearchIssueIDsByKeyword("for", []int64{1}, 10, 0)
 	assert.NoError(t, err)
-	assert.EqualValues(t, 4, total)
-	assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
+	assert.EqualValues(t, 5, total)
+	assert.EqualValues(t, []int64{1, 2, 3, 5, 11}, ids)
 
 	// issue1's comment id 2
 	total, ids, err = SearchIssueIDsByKeyword("good", []int64{1}, 10, 0)
@@ -305,8 +305,8 @@ func testInsertIssue(t *testing.T, title, content string) {
 	assert.True(t, has)
 	assert.EqualValues(t, issue.Title, newIssue.Title)
 	assert.EqualValues(t, issue.Content, newIssue.Content)
-	// there are 4 issues and max index is 4 on repository 1, so this one should 5
-	assert.EqualValues(t, 5, newIssue.Index)
+	// there are 5 issues and max index is 5 on repository 1, so this one should 6
+	assert.EqualValues(t, 6, newIssue.Index)
 
 	_, err = x.ID(issue.ID).Delete(new(Issue))
 	assert.NoError(t, err)
diff --git a/models/issue_user_test.go b/models/issue_user_test.go
index a57ab33f9e..01e0bdc644 100644
--- a/models/issue_user_test.go
+++ b/models/issue_user_test.go
@@ -17,7 +17,7 @@ func Test_newIssueUsers(t *testing.T) {
 	newIssue := &Issue{
 		RepoID:   repo.ID,
 		PosterID: 4,
-		Index:    5,
+		Index:    6,
 		Title:    "newTestIssueTitle",
 		Content:  "newTestIssueContent",
 	}
diff --git a/models/pull.go b/models/pull.go
index 1edd890035..fcfcd221c4 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -742,3 +742,8 @@ func (pr *PullRequest) IsHeadEqualWithBranch(branchName string) (bool, error) {
 	}
 	return baseCommit.HasPreviousCommit(headCommit.ID)
 }
+
+// IsSameRepo returns true if base repo and head repo is the same
+func (pr *PullRequest) IsSameRepo() bool {
+	return pr.BaseRepoID == pr.HeadRepoID
+}
diff --git a/models/pull_test.go b/models/pull_test.go
index 9c27b603aa..6ceeae6653 100644
--- a/models/pull_test.go
+++ b/models/pull_test.go
@@ -61,10 +61,11 @@ func TestPullRequestsNewest(t *testing.T) {
 		Labels:   []string{},
 	})
 	assert.NoError(t, err)
-	assert.Equal(t, int64(2), count)
-	if assert.Len(t, prs, 2) {
-		assert.Equal(t, int64(2), prs[0].ID)
-		assert.Equal(t, int64(1), prs[1].ID)
+	assert.EqualValues(t, 3, count)
+	if assert.Len(t, prs, 3) {
+		assert.EqualValues(t, 5, prs[0].ID)
+		assert.EqualValues(t, 2, prs[1].ID)
+		assert.EqualValues(t, 1, prs[2].ID)
 	}
 }
 
@@ -77,10 +78,11 @@ func TestPullRequestsOldest(t *testing.T) {
 		Labels:   []string{},
 	})
 	assert.NoError(t, err)
-	assert.Equal(t, int64(2), count)
-	if assert.Len(t, prs, 2) {
-		assert.Equal(t, int64(1), prs[0].ID)
-		assert.Equal(t, int64(2), prs[1].ID)
+	assert.EqualValues(t, 3, count)
+	if assert.Len(t, prs, 3) {
+		assert.EqualValues(t, 1, prs[0].ID)
+		assert.EqualValues(t, 2, prs[1].ID)
+		assert.EqualValues(t, 5, prs[2].ID)
 	}
 }
 
diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go
index 4028a6c8b5..8a54c200ff 100644
--- a/modules/indexer/issues/indexer_test.go
+++ b/modules/indexer/issues/indexer_test.go
@@ -65,7 +65,7 @@ func TestBleveSearchIssues(t *testing.T) {
 
 	ids, err = SearchIssuesByKeyword([]int64{1}, "for")
 	assert.NoError(t, err)
-	assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
+	assert.EqualValues(t, []int64{1, 2, 3, 5, 11}, ids)
 
 	ids, err = SearchIssuesByKeyword([]int64{1}, "good")
 	assert.NoError(t, err)
@@ -89,7 +89,7 @@ func TestDBSearchIssues(t *testing.T) {
 
 	ids, err = SearchIssuesByKeyword([]int64{1}, "for")
 	assert.NoError(t, err)
-	assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
+	assert.EqualValues(t, []int64{1, 2, 3, 5, 11}, ids)
 
 	ids, err = SearchIssuesByKeyword([]int64{1}, "good")
 	assert.NoError(t, err)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 60df796e07..9a4f0535e8 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1082,6 +1082,10 @@ pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because
 pulls.status_checking = Some checks are pending
 pulls.status_checks_success = All checks were successful
 pulls.status_checks_error = Some checks failed
+pulls.update_branch = Update branch
+pulls.update_branch_success = Branch update was successful
+pulls.update_not_allowed = You are not allowed to update branch
+pulls.outdated_with_base_branch = This branch is out-of-date with the base branch
 
 milestones.new = New Milestone
 milestones.open_tab = %d Open
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index 901ab48856..fc0012ffbe 100644
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -14,6 +14,7 @@ import (
 	"net/http"
 	"path"
 	"strings"
+	"time"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/auth"
@@ -342,8 +343,21 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
 
 	setMergeTarget(ctx, pull)
 
+	divergence, err := pull_service.GetDiverging(pull)
+	if err != nil {
+		ctx.ServerError("GetDiverging", err)
+		return nil
+	}
+	ctx.Data["Divergence"] = divergence
+	allowUpdate, err := pull_service.IsUserAllowedToUpdate(pull, ctx.User)
+	if err != nil {
+		ctx.ServerError("GetDiverging", err)
+		return nil
+	}
+	ctx.Data["UpdateAllowed"] = allowUpdate
+
 	if err := pull.LoadProtectedBranch(); err != nil {
-		ctx.ServerError("GetLatestCommitStatus", err)
+		ctx.ServerError("LoadProtectedBranch", err)
 		return nil
 	}
 	ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
@@ -587,6 +601,72 @@ func ViewPullFiles(ctx *context.Context) {
 	ctx.HTML(200, tplPullFiles)
 }
 
+// UpdatePullRequest merge master into PR
+func UpdatePullRequest(ctx *context.Context) {
+	issue := checkPullInfo(ctx)
+	if ctx.Written() {
+		return
+	}
+	if issue.IsClosed {
+		ctx.NotFound("MergePullRequest", nil)
+		return
+	}
+	if issue.PullRequest.HasMerged {
+		ctx.NotFound("MergePullRequest", nil)
+		return
+	}
+
+	if err := issue.PullRequest.LoadBaseRepo(); err != nil {
+		ctx.InternalServerError(err)
+		return
+	}
+	if err := issue.PullRequest.LoadHeadRepo(); err != nil {
+		ctx.InternalServerError(err)
+		return
+	}
+
+	allowedUpdate, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User)
+	if err != nil {
+		ctx.ServerError("IsUserAllowedToMerge", err)
+		return
+	}
+
+	// ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
+	if !allowedUpdate {
+		ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
+		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
+		return
+	}
+
+	// default merge commit message
+	message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
+
+	if err = pull_service.Update(issue.PullRequest, ctx.User, message); err != nil {
+		sanitize := func(x string) string {
+			runes := []rune(x)
+
+			if len(runes) > 512 {
+				x = "..." + string(runes[len(runes)-512:])
+			}
+
+			return strings.Replace(html.EscapeString(x), "\n", "<br>", -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) }}
 						</div>
 					{{end}}
+					{{if and .Divergence (gt .Divergence.Behind 0)}}
+					<div class="ui very compact branch-update grid">
+  						<div class="row">
+							<div class="item text gray eleven wide left floated column">
+								<i class="icon icon-octicon"><span class="octicon octicon-alert"></span></i>
+								{{$.i18n.Tr "repo.pulls.outdated_with_base_branch"}}
+							</div>
+							{{if .UpdateAllowed}}
+								<div class="item text five wide right floated column">
+									<form action="{{.Link}}/update" method="post">
+										{{.CsrfTokenHtml}}
+										<button class="ui button" data-do="update">
+											<span class="item text">{{$.i18n.Tr "repo.pulls.update_branch"}}</span>
+										</button>
+									</form>
+								</div>
+							{{end}}
+						</div>
+					</div>
+					{{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 {