Browse Source

#2246 add HTMLURL to webhook type

- Fill Milestone and Assignee field when available in webhook payload
Unknwon 3 years ago
parent
commit
6f9a95f830

+ 1 - 1
.gopmfile

@@ -19,7 +19,7 @@ github.com/go-xorm/xorm = commit:c6c7056
19 19
 github.com/gogits/chardet = commit:2404f77
20 20
 github.com/gogits/cron = commit:7f3990a
21 21
 github.com/gogits/git-module = commit:f78bf3b
22
-github.com/gogits/go-gogs-client = commit:e363d3f
22
+github.com/gogits/go-gogs-client = commit:51c4df8
23 23
 github.com/issue9/identicon = commit:d36b545
24 24
 github.com/jaytaylor/html2text = commit:52d9b78
25 25
 github.com/kardianos/minwinsvc = commit:cad6b2b

+ 1 - 1
README.md

@@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
3 3
 
4 4
 ![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
5 5
 
6
-##### Current tip version: 0.9.81 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
6
+##### Current tip version: 0.9.82 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
7 7
 
8 8
 | Web | UI  | Preview  |
9 9
 |:-------------:|:-------:|:-------:|

+ 2 - 2
cmd/web.go

@@ -88,8 +88,8 @@ func checkVersion() {
88 88
 		{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
89 89
 		{"gopkg.in/ini.v1", ini.Version, "1.8.4"},
90 90
 		{"gopkg.in/macaron.v1", macaron.Version, "1.1.7"},
91
-		{"github.com/gogits/git-module", git.Version, "0.3.6"},
92
-		{"github.com/gogits/go-gogs-client", gogs.Version, "0.12.0"},
91
+		{"github.com/gogits/git-module", git.Version, "0.3.7"},
92
+		{"github.com/gogits/go-gogs-client", gogs.Version, "0.12.1"},
93 93
 	}
94 94
 	for _, c := range checkers {
95 95
 		if !version.Compare(c.Version(), c.Expected, ">=") {

+ 1 - 1
glide.lock

@@ -43,7 +43,7 @@ imports:
43 43
 - name: github.com/gogits/git-module
44 44
   version: f78bf3bf703cb3eb0e85a9475d26826939feda4f
45 45
 - name: github.com/gogits/go-gogs-client
46
-  version: e363d3ff8f70d0fe813324eedf228684af41c29c
46
+  version: 51c4df8c350b32f095c8eb236aae2e306025eead
47 47
 - name: github.com/issue9/identicon
48 48
   version: d36b54562f4cf70c83653e13dc95c220c79ef521
49 49
 - name: github.com/jaytaylor/html2text

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
17 17
 	"github.com/gogits/gogs/modules/setting"
18 18
 )
19 19
 
20
-const APP_VER = "0.9.81.0816"
20
+const APP_VER = "0.9.82.0816"
21 21
 
22 22
 func init() {
23 23
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 1 - 1
models/action.go

@@ -520,7 +520,7 @@ func CommitRepoAction(
520 520
 			Before:     oldCommitID,
521 521
 			After:      newCommitID,
522 522
 			CompareURL: setting.AppUrl + commit.CompareURL,
523
-			Commits:    commit.ToApiPayloadCommits(repo.FullLink()),
523
+			Commits:    commit.ToApiPayloadCommits(repo.HTMLURL()),
524 524
 			Repo:       apiRepo,
525 525
 			Pusher:     apiPusher,
526 526
 			Sender:     apiPusher,

+ 54 - 32
models/issue.go

@@ -32,23 +32,23 @@ var (
32 32
 type Issue struct {
33 33
 	ID              int64       `xorm:"pk autoincr"`
34 34
 	RepoID          int64       `xorm:"INDEX UNIQUE(repo_index)"`
35
-	Index           int64       `xorm:"UNIQUE(repo_index)"` // Index in one repository.
36
-	Title           string      `xorm:"name"`
37 35
 	Repo            *Repository `xorm:"-"`
36
+	Index           int64       `xorm:"UNIQUE(repo_index)"` // Index in one repository.
38 37
 	PosterID        int64
39 38
 	Poster          *User    `xorm:"-"`
39
+	Title           string   `xorm:"name"`
40
+	Content         string   `xorm:"TEXT"`
41
+	RenderedContent string   `xorm:"-"`
40 42
 	Labels          []*Label `xorm:"-"`
41 43
 	MilestoneID     int64
42 44
 	Milestone       *Milestone `xorm:"-"`
45
+	Priority        int
43 46
 	AssigneeID      int64
44 47
 	Assignee        *User `xorm:"-"`
45
-	IsRead          bool  `xorm:"-"`
46
-	IsPull          bool  // Indicates whether is a pull request or not.
47
-	*PullRequest    `xorm:"-"`
48 48
 	IsClosed        bool
49
-	Content         string `xorm:"TEXT"`
50
-	RenderedContent string `xorm:"-"`
51
-	Priority        int
49
+	IsRead          bool         `xorm:"-"`
50
+	IsPull          bool         // Indicates whether is a pull request or not.
51
+	PullRequest     *PullRequest `xorm:"-"`
52 52
 	NumComments     int
53 53
 
54 54
 	Deadline     time.Time `xorm:"-"`
@@ -155,6 +155,16 @@ func (issue *Issue) LoadAttributes() error {
155 155
 	return issue.loadAttributes(x)
156 156
 }
157 157
 
158
+func (issue *Issue) HTMLURL() string {
159
+	var path string
160
+	if issue.IsPull {
161
+		path = "pulls"
162
+	} else {
163
+		path = "issues"
164
+	}
165
+	return fmt.Sprintf("%s/%s/%d", issue.Repo.HTMLURL(), path, issue.Index)
166
+}
167
+
158 168
 // State returns string representation of issue status.
159 169
 func (i *Issue) State() api.StateType {
160 170
 	if i.IsClosed {
@@ -175,11 +185,11 @@ func (issue *Issue) APIFormat() *api.Issue {
175 185
 	apiIssue := &api.Issue{
176 186
 		ID:       issue.ID,
177 187
 		Index:    issue.Index,
178
-		State:    issue.State(),
188
+		Poster:   issue.Poster.APIFormat(),
179 189
 		Title:    issue.Title,
180 190
 		Body:     issue.Content,
181
-		User:     issue.Poster.APIFormat(),
182 191
 		Labels:   apiLabels,
192
+		State:    issue.State(),
183 193
 		Comments: issue.NumComments,
184 194
 		Created:  issue.Created,
185 195
 		Updated:  issue.Updated,
@@ -208,16 +218,6 @@ func (i *Issue) HashTag() string {
208 218
 	return "issue-" + com.ToStr(i.ID)
209 219
 }
210 220
 
211
-func (issue *Issue) FullLink() string {
212
-	var path string
213
-	if issue.IsPull {
214
-		path = "pulls"
215
-	} else {
216
-		path = "issues"
217
-	}
218
-	return fmt.Sprintf("%s/%s/%d", issue.Repo.FullLink(), path, issue.Index)
219
-}
220
-
221 221
 // IsPoster returns true if given user by ID is the poster.
222 222
 func (i *Issue) IsPoster(uid int64) bool {
223 223
 	return i.PosterID == uid
@@ -591,16 +591,44 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
591 591
 	opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
592 592
 	opts.Issue.Index = opts.Repo.NextIssueIndex()
593 593
 
594
+	if opts.Issue.MilestoneID > 0 {
595
+		milestone, err := getMilestoneByID(e, opts.Issue.MilestoneID)
596
+		if err != nil && !IsErrMilestoneNotExist(err) {
597
+			return fmt.Errorf("getMilestoneByID: %v", err)
598
+		}
599
+
600
+		// Assume milestone is invalid and drop silently.
601
+		opts.Issue.MilestoneID = 0
602
+		if milestone != nil {
603
+			opts.Issue.MilestoneID = milestone.ID
604
+			opts.Issue.Milestone = milestone
605
+			if err = changeMilestoneAssign(e, opts.Issue, -1); err != nil {
606
+				return err
607
+			}
608
+		}
609
+	}
610
+
594 611
 	if opts.Issue.AssigneeID > 0 {
595
-		// Silently drop invalid assignee.
596
-		valid, err := hasAccess(e, &User{ID: opts.Issue.AssigneeID}, opts.Repo, ACCESS_MODE_WRITE)
597
-		if err != nil {
598
-			return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", opts.Issue.AssigneeID, opts.Repo.ID, err)
599
-		} else if !valid {
600
-			opts.Issue.AssigneeID = 0
612
+		assignee, err := getUserByID(e, opts.Issue.AssigneeID)
613
+		if err != nil && !IsErrUserNotExist(err) {
614
+			return fmt.Errorf("getUserByID: %v", err)
615
+		}
616
+
617
+		// Assume assignee is invalid and drop silently.
618
+		opts.Issue.AssigneeID = 0
619
+		if assignee != nil {
620
+			valid, err := hasAccess(e, assignee, opts.Repo, ACCESS_MODE_WRITE)
621
+			if err != nil {
622
+				return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assignee.ID, opts.Repo.ID, err)
623
+			}
624
+			if valid {
625
+				opts.Issue.AssigneeID = assignee.ID
626
+				opts.Issue.Assignee = assignee
627
+			}
601 628
 		}
602 629
 	}
603 630
 
631
+	// Milestone and assignee validation should happen before insert actual object.
604 632
 	if _, err = e.Insert(opts.Issue); err != nil {
605 633
 		return err
606 634
 	}
@@ -634,12 +662,6 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
634 662
 		}
635 663
 	}
636 664
 
637
-	if opts.Issue.MilestoneID > 0 {
638
-		if err = changeMilestoneAssign(e, opts.Issue, -1); err != nil {
639
-			return err
640
-		}
641
-	}
642
-
643 665
 	if err = newIssueUsers(e, opts.Repo, opts.Issue); err != nil {
644 666
 		return err
645 667
 	}

+ 3 - 3
models/mail.go

@@ -129,7 +129,7 @@ func SendCollaboratorMail(u, doer *User, repo *Repository) {
129 129
 	data := map[string]interface{}{
130 130
 		"Subject":  subject,
131 131
 		"RepoName": repoName,
132
-		"Link":     repo.FullLink(),
132
+		"Link":     repo.HTMLURL(),
133 133
 	}
134 134
 	body, err := mailRender.HTMLString(string(MAIL_NOTIFY_COLLABORATOR), data)
135 135
 	if err != nil {
@@ -153,8 +153,8 @@ func composeTplData(subject, body, link string) map[string]interface{} {
153 153
 
154 154
 func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []string, info string) *mailer.Message {
155 155
 	subject := issue.MailSubject()
156
-	body := string(markdown.RenderSpecialLink([]byte(issue.Content), issue.Repo.FullLink(), issue.Repo.ComposeMetas()))
157
-	data := composeTplData(subject, body, issue.FullLink())
156
+	body := string(markdown.RenderSpecialLink([]byte(issue.Content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))
157
+	data := composeTplData(subject, body, issue.HTMLURL())
158 158
 	data["Doer"] = doer
159 159
 	content, err := mailRender.HTMLString(string(tplName), data)
160 160
 	if err != nil {

+ 8 - 3
models/pull.go

@@ -100,6 +100,10 @@ func (pr *PullRequest) LoadAttributes() error {
100 100
 }
101 101
 
102 102
 func (pr *PullRequest) LoadIssue() (err error) {
103
+	if pr.Issue != nil {
104
+		return nil
105
+	}
106
+
103 107
 	pr.Issue, err = GetIssueByID(pr.IssueID)
104 108
 	return err
105 109
 }
@@ -112,14 +116,15 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
112 116
 	apiPullRequest := &api.PullRequest{
113 117
 		ID:        pr.ID,
114 118
 		Index:     pr.Index,
115
-		State:     apiIssue.State,
119
+		Poster:    apiIssue.Poster,
116 120
 		Title:     apiIssue.Title,
117 121
 		Body:      apiIssue.Body,
118
-		User:      apiIssue.User,
119 122
 		Labels:    apiIssue.Labels,
120 123
 		Milestone: apiIssue.Milestone,
121 124
 		Assignee:  apiIssue.Assignee,
125
+		State:     apiIssue.State,
122 126
 		Comments:  apiIssue.Comments,
127
+		HTMLURL:   pr.Issue.HTMLURL(),
123 128
 		HasMerged: pr.HasMerged,
124 129
 	}
125 130
 
@@ -312,7 +317,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error
312 317
 		Before:     pr.MergeBase,
313 318
 		After:      pr.MergedCommitID,
314 319
 		CompareURL: setting.AppUrl + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
315
-		Commits:    ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.FullLink()),
320
+		Commits:    ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.HTMLURL()),
316 321
 		Repo:       pr.BaseRepo.APIFormat(nil),
317 322
 		Pusher:     pr.HeadRepo.MustOwner().APIFormat(),
318 323
 		Sender:     doer.APIFormat(),

+ 2 - 2
models/repo.go

@@ -233,7 +233,7 @@ func (repo *Repository) FullName() string {
233 233
 	return repo.MustOwner().Name + "/" + repo.Name
234 234
 }
235 235
 
236
-func (repo *Repository) FullLink() string {
236
+func (repo *Repository) HTMLURL() string {
237 237
 	return setting.AppUrl + repo.FullName()
238 238
 }
239 239
 
@@ -248,7 +248,7 @@ func (repo *Repository) APIFormat(permission *api.Permission) *api.Repository {
248 248
 		Description:   repo.Description,
249 249
 		Private:       repo.IsPrivate,
250 250
 		Fork:          repo.IsFork,
251
-		HTMLURL:       repo.FullLink(),
251
+		HTMLURL:       repo.HTMLURL(),
252 252
 		SSHURL:        cloneLink.SSH,
253 253
 		CloneURL:      cloneLink.HTTPS,
254 254
 		Website:       repo.Website,

+ 5 - 5
routers/repo/issue.go

@@ -538,8 +538,8 @@ func ViewIssue(ctx *context.Context) {
538 538
 
539 539
 	// Get more information if it's a pull request.
540 540
 	if issue.IsPull {
541
-		if issue.HasMerged {
542
-			ctx.Data["DisableStatusChange"] = issue.HasMerged
541
+		if issue.PullRequest.HasMerged {
542
+			ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
543 543
 			PrepareMergedViewPullInfo(ctx, issue)
544 544
 		} else {
545 545
 			PrepareViewPullInfo(ctx, issue)
@@ -822,7 +822,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
822 822
 		// Check if issue admin/poster changes the status of issue.
823 823
 		if (ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))) &&
824 824
 			(form.Status == "reopen" || form.Status == "close") &&
825
-			!(issue.IsPull && issue.HasMerged) {
825
+			!(issue.IsPull && issue.PullRequest.HasMerged) {
826 826
 
827 827
 			// Duplication and conflict check should apply to reopen pull request.
828 828
 			var pr *models.PullRequest
@@ -839,12 +839,12 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
839 839
 
840 840
 				// Regenerate patch and test conflict.
841 841
 				if pr == nil {
842
-					if err = issue.UpdatePatch(); err != nil {
842
+					if err = issue.PullRequest.UpdatePatch(); err != nil {
843 843
 						ctx.Handle(500, "UpdatePatch", err)
844 844
 						return
845 845
 					}
846 846
 
847
-					issue.AddToTaskQueue()
847
+					issue.PullRequest.AddToTaskQueue()
848 848
 				}
849 849
 			}
850 850
 

+ 14 - 10
routers/repo/pull.go

@@ -156,7 +156,7 @@ func checkPullInfo(ctx *context.Context) *models.Issue {
156 156
 		return nil
157 157
 	}
158 158
 
159
-	if err = issue.GetHeadRepo(); err != nil {
159
+	if err = issue.PullRequest.GetHeadRepo(); err != nil {
160 160
 		ctx.Handle(500, "GetHeadRepo", err)
161 161
 		return nil
162 162
 	}
@@ -172,9 +172,10 @@ func checkPullInfo(ctx *context.Context) *models.Issue {
172 172
 	return issue
173 173
 }
174 174
 
175
-func PrepareMergedViewPullInfo(ctx *context.Context, pull *models.Issue) {
175
+func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) {
176
+	pull := issue.PullRequest
176 177
 	ctx.Data["HasMerged"] = true
177
-	ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBranch
178
+	ctx.Data["HeadTarget"] = issue.PullRequest.HeadUserName + "/" + pull.HeadBranch
178 179
 	ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.BaseBranch
179 180
 
180 181
 	var err error
@@ -190,8 +191,9 @@ func PrepareMergedViewPullInfo(ctx *context.Context, pull *models.Issue) {
190 191
 	}
191 192
 }
192 193
 
193
-func PrepareViewPullInfo(ctx *context.Context, pull *models.Issue) *git.PullRequestInfo {
194
+func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullRequestInfo {
194 195
 	repo := ctx.Repo.Repository
196
+	pull := issue.PullRequest
195 197
 
196 198
 	ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBranch
197 199
 	ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.BaseBranch
@@ -245,16 +247,17 @@ func ViewPullCommits(ctx *context.Context) {
245 247
 	ctx.Data["PageIsPullList"] = true
246 248
 	ctx.Data["PageIsPullCommits"] = true
247 249
 
248
-	pull := checkPullInfo(ctx)
250
+	issue := checkPullInfo(ctx)
249 251
 	if ctx.Written() {
250 252
 		return
251 253
 	}
254
+	pull := issue.PullRequest
252 255
 	ctx.Data["Username"] = pull.HeadUserName
253 256
 	ctx.Data["Reponame"] = pull.HeadRepo.Name
254 257
 
255 258
 	var commits *list.List
256 259
 	if pull.HasMerged {
257
-		PrepareMergedViewPullInfo(ctx, pull)
260
+		PrepareMergedViewPullInfo(ctx, issue)
258 261
 		if ctx.Written() {
259 262
 			return
260 263
 		}
@@ -275,7 +278,7 @@ func ViewPullCommits(ctx *context.Context) {
275 278
 		}
276 279
 
277 280
 	} else {
278
-		prInfo := PrepareViewPullInfo(ctx, pull)
281
+		prInfo := PrepareViewPullInfo(ctx, issue)
279 282
 		if ctx.Written() {
280 283
 			return
281 284
 		} else if prInfo == nil {
@@ -296,10 +299,11 @@ func ViewPullFiles(ctx *context.Context) {
296 299
 	ctx.Data["PageIsPullList"] = true
297 300
 	ctx.Data["PageIsPullFiles"] = true
298 301
 
299
-	pull := checkPullInfo(ctx)
302
+	issue := checkPullInfo(ctx)
300 303
 	if ctx.Written() {
301 304
 		return
302 305
 	}
306
+	pull := issue.PullRequest
303 307
 
304 308
 	var (
305 309
 		diffRepoPath  string
@@ -309,7 +313,7 @@ func ViewPullFiles(ctx *context.Context) {
309 313
 	)
310 314
 
311 315
 	if pull.HasMerged {
312
-		PrepareMergedViewPullInfo(ctx, pull)
316
+		PrepareMergedViewPullInfo(ctx, issue)
313 317
 		if ctx.Written() {
314 318
 			return
315 319
 		}
@@ -319,7 +323,7 @@ func ViewPullFiles(ctx *context.Context) {
319 323
 		endCommitID = pull.MergedCommitID
320 324
 		gitRepo = ctx.Repo.GitRepo
321 325
 	} else {
322
-		prInfo := PrepareViewPullInfo(ctx, pull)
326
+		prInfo := PrepareViewPullInfo(ctx, issue)
323 327
 		if ctx.Written() {
324 328
 			return
325 329
 		} else if prInfo == nil {

+ 1 - 1
routers/repo/webhook.go

@@ -368,7 +368,7 @@ func TestWebhook(ctx *context.Context) {
368 368
 			{
369 369
 				ID:      commit.ID.String(),
370 370
 				Message: commit.Message(),
371
-				URL:     ctx.Repo.Repository.FullLink() + "/commit/" + commit.ID.String(),
371
+				URL:     ctx.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(),
372 372
 				Author: &api.PayloadUser{
373 373
 					Name:  commit.Author.Name,
374 374
 					Email: commit.Author.Email,

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
1
-0.9.81.0816
1
+0.9.82.0816

+ 6 - 6
templates/repo/issue/view_content.tmpl

@@ -151,15 +151,15 @@
151 151
 			{{if .Issue.IsPull}}
152 152
 				<div class="comment merge box">
153 153
 					<a class="avatar text
154
-					{{if .Issue.HasMerged}}purple
154
+					{{if .Issue.PullRequest.HasMerged}}purple
155 155
 					{{else if .Issue.IsClosed}}grey
156 156
 					{{else if .IsPullReuqestBroken}}red
157
-					{{else if .Issue.IsChecking}}yellow
158
-					{{else if .Issue.CanAutoMerge}}green
157
+					{{else if .Issue.PullRequest.IsChecking}}yellow
158
+					{{else if .Issue.PullRequest.CanAutoMerge}}green
159 159
 					{{else}}red{{end}}"><span class="mega-octicon octicon-git-merge"></span></a>
160 160
 					<div class="content">
161 161
 						<div class="ui merge segment">
162
-							{{if .Issue.HasMerged}}
162
+							{{if .Issue.PullRequest.HasMerged}}
163 163
 								<div class="item text purple">
164 164
 									{{$.i18n.Tr "repo.pulls.has_merged"}}
165 165
 								</div>
@@ -172,12 +172,12 @@
172 172
 									<span class="octicon octicon-x"></span>
173 173
 									{{$.i18n.Tr "repo.pulls.data_broken"}}
174 174
 								</div>
175
-							{{else if .Issue.IsChecking}}
175
+							{{else if .Issue.PullRequest.IsChecking}}
176 176
 								<div class="item text yellow">
177 177
 									<span class="octicon octicon-sync"></span>
178 178
 									{{$.i18n.Tr "repo.pulls.is_checking"}}
179 179
 								</div>
180
-							{{else if .Issue.CanAutoMerge}}
180
+							{{else if .Issue.PullRequest.CanAutoMerge}}
181 181
 								<div class="item text green">
182 182
 									<span class="octicon octicon-check"></span>
183 183
 									{{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}}

+ 3 - 3
templates/repo/issue/view_title.tmpl

@@ -25,9 +25,9 @@
25 25
 	{{end}}
26 26
 
27 27
 	{{if .Issue.IsPull}}
28
-		{{if .Issue.HasMerged}}
29
-			{{ $mergedStr:= TimeSince .Issue.Merged $.Lang }}
30
-			<a {{if gt .Issue.Merger.ID 0}}href="{{.Issue.Merger.HomeLink}}"{{end}}>{{.Issue.Merger.Name}}</a>
28
+		{{if .Issue.PullRequest.HasMerged}}
29
+			{{ $mergedStr:= TimeSince .Issue.PullRequest.Merged $.Lang }}
30
+			<a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.Name}}</a>
31 31
 			<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Safe}}</span>
32 32
 		{{else}}
33 33
 			<a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a>