From 1f1ecda541d7f526c004e7bfabab814dbc84dc2c Mon Sep 17 00:00:00 2001
From: mrsdizzie <info@mrsdizzie.com>
Date: Sun, 7 Jul 2019 22:14:12 -0400
Subject: [PATCH] Display original author and URL information when showing
 migrated issues/comments (#7352)

* Store original author info for migrated issues and comments

Keep original author name for displaying in Gitea interface and also
store original author user ID for potential future use in linking
accounts from old location.

* Add original_url for repo

Store the original URL for a migrated repo

Clean up migrations/tests

* fix migration

* fix golangci-lint

* make 'make revive' happy also

* Modify templates to use OriginalAuthor if set

Use the original author name in templates if it is set rather than the
user who migrated/currently owns the issues

* formatting fixes

* make generate-swagger

* Use default avatar for imported comments

* Remove no longer used IgnoreIssueAuthor option

* Add OriginalAuthorID to swagger also
---
 models/issue.go                               | 44 ++++++-------
 models/issue_comment.go                       |  6 +-
 models/migrations/migrations.go               |  2 +
 models/migrations/v89.go                      | 36 +++++++++++
 models/repo.go                                | 15 +++++
 modules/migrations/base/comment.go            |  1 +
 modules/migrations/base/issue.go              |  1 +
 modules/migrations/base/options.go            | 20 +++---
 modules/migrations/base/pullrequest.go        |  1 +
 modules/migrations/base/repo.go               |  1 +
 modules/migrations/gitea.go                   | 63 ++++++++++---------
 modules/migrations/gitea_test.go              | 19 +++---
 modules/migrations/github.go                  |  5 +-
 modules/migrations/github_test.go             | 10 +++
 modules/migrations/migrate.go                 | 23 +------
 modules/structs/issue.go                      | 22 ++++---
 modules/structs/issue_comment.go              | 14 +++--
 modules/structs/repo.go                       |  1 +
 modules/templates/helper.go                   | 11 ++++
 options/locale/locale_en-US.ini               |  2 +
 public/css/index.css                          |  3 +
 public/less/_base.less                        | 12 ++++
 templates/repo/issue/list.tmpl                |  5 +-
 templates/repo/issue/view_content.tmpl        |  8 +++
 .../repo/issue/view_content/comments.tmpl     |  8 +++
 templates/repo/issue/view_title.tmpl          | 22 +++++--
 templates/swagger/v1_json.tmpl                | 22 +++++++
 templates/user/dashboard/issues.tmpl          |  4 +-
 28 files changed, 263 insertions(+), 118 deletions(-)
 create mode 100644 models/migrations/v89.go

diff --git a/models/issue.go b/models/issue.go
index b5504beb71..63074cd40c 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -25,27 +25,29 @@ import (
 
 // Issue represents an issue or pull request of repository.
 type Issue struct {
-	ID              int64       `xorm:"pk autoincr"`
-	RepoID          int64       `xorm:"INDEX UNIQUE(repo_index)"`
-	Repo            *Repository `xorm:"-"`
-	Index           int64       `xorm:"UNIQUE(repo_index)"` // Index in one repository.
-	PosterID        int64       `xorm:"INDEX"`
-	Poster          *User       `xorm:"-"`
-	Title           string      `xorm:"name"`
-	Content         string      `xorm:"TEXT"`
-	RenderedContent string      `xorm:"-"`
-	Labels          []*Label    `xorm:"-"`
-	MilestoneID     int64       `xorm:"INDEX"`
-	Milestone       *Milestone  `xorm:"-"`
-	Priority        int
-	AssigneeID      int64        `xorm:"-"`
-	Assignee        *User        `xorm:"-"`
-	IsClosed        bool         `xorm:"INDEX"`
-	IsRead          bool         `xorm:"-"`
-	IsPull          bool         `xorm:"INDEX"` // Indicates whether is a pull request or not.
-	PullRequest     *PullRequest `xorm:"-"`
-	NumComments     int
-	Ref             string
+	ID               int64       `xorm:"pk autoincr"`
+	RepoID           int64       `xorm:"INDEX UNIQUE(repo_index)"`
+	Repo             *Repository `xorm:"-"`
+	Index            int64       `xorm:"UNIQUE(repo_index)"` // Index in one repository.
+	PosterID         int64       `xorm:"INDEX"`
+	Poster           *User       `xorm:"-"`
+	OriginalAuthor   string
+	OriginalAuthorID int64
+	Title            string     `xorm:"name"`
+	Content          string     `xorm:"TEXT"`
+	RenderedContent  string     `xorm:"-"`
+	Labels           []*Label   `xorm:"-"`
+	MilestoneID      int64      `xorm:"INDEX"`
+	Milestone        *Milestone `xorm:"-"`
+	Priority         int
+	AssigneeID       int64        `xorm:"-"`
+	Assignee         *User        `xorm:"-"`
+	IsClosed         bool         `xorm:"INDEX"`
+	IsRead           bool         `xorm:"-"`
+	IsPull           bool         `xorm:"INDEX"` // Indicates whether is a pull request or not.
+	PullRequest      *PullRequest `xorm:"-"`
+	NumComments      int
+	Ref              string
 
 	DeadlineUnix util.TimeStamp `xorm:"INDEX"`
 
diff --git a/models/issue_comment.go b/models/issue_comment.go
index c9f1bd9d5f..b930b0b12a 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -101,8 +101,10 @@ const (
 type Comment struct {
 	ID               int64 `xorm:"pk autoincr"`
 	Type             CommentType
-	PosterID         int64  `xorm:"INDEX"`
-	Poster           *User  `xorm:"-"`
+	PosterID         int64 `xorm:"INDEX"`
+	Poster           *User `xorm:"-"`
+	OriginalAuthor   string
+	OriginalAuthorID int64
 	IssueID          int64  `xorm:"INDEX"`
 	Issue            *Issue `xorm:"-"`
 	LabelID          int64
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index ef43b0453b..62fadf5f36 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -232,6 +232,8 @@ var migrations = []Migration{
 	NewMigration("add avatar field to repository", addAvatarFieldToRepository),
 	// v88 -> v89
 	NewMigration("add commit status context field to commit_status", addCommitStatusContext),
+	// v89 -> v90
+	NewMigration("add original author/url migration info to issues, comments, and repo ", addOriginalMigrationInfo),
 }
 
 // Migrate database to current version
diff --git a/models/migrations/v89.go b/models/migrations/v89.go
new file mode 100644
index 0000000000..83d0b1a8b9
--- /dev/null
+++ b/models/migrations/v89.go
@@ -0,0 +1,36 @@
+// Copyright 2019 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 "github.com/go-xorm/xorm"
+
+func addOriginalMigrationInfo(x *xorm.Engine) error {
+	// Issue see models/issue.go
+	type Issue struct {
+		OriginalAuthor   string
+		OriginalAuthorID int64
+	}
+
+	if err := x.Sync2(new(Issue)); err != nil {
+		return err
+	}
+
+	// Issue see models/issue_comment.go
+	type Comment struct {
+		OriginalAuthor   string
+		OriginalAuthorID int64
+	}
+
+	if err := x.Sync2(new(Comment)); err != nil {
+		return err
+	}
+
+	// Issue see models/repo.go
+	type Repository struct {
+		OriginalURL string
+	}
+
+	return x.Sync2(new(Repository))
+}
diff --git a/models/repo.go b/models/repo.go
index 59ce18fa88..9bedeba952 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -136,6 +136,7 @@ type Repository struct {
 	Name          string `xorm:"INDEX NOT NULL"`
 	Description   string
 	Website       string
+	OriginalURL   string
 	DefaultBranch string
 
 	NumWatches          int
@@ -847,6 +848,7 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
 type MigrateRepoOptions struct {
 	Name                 string
 	Description          string
+	OriginalURL          string
 	IsPrivate            bool
 	IsMirror             bool
 	RemoteAddr           string
@@ -878,6 +880,7 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
 	repo, err := CreateRepository(doer, u, CreateRepoOptions{
 		Name:        opts.Name,
 		Description: opts.Description,
+		OriginalURL: opts.OriginalURL,
 		IsPrivate:   opts.IsPrivate,
 		IsMirror:    opts.IsMirror,
 	})
@@ -1092,6 +1095,7 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
 type CreateRepoOptions struct {
 	Name        string
 	Description string
+	OriginalURL string
 	Gitignores  string
 	License     string
 	Readme      string
@@ -1358,6 +1362,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
 		Name:                            opts.Name,
 		LowerName:                       strings.ToLower(opts.Name),
 		Description:                     opts.Description,
+		OriginalURL:                     opts.OriginalURL,
 		IsPrivate:                       opts.IsPrivate,
 		IsFsckEnabled:                   !opts.IsMirror,
 		CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
@@ -2678,3 +2683,13 @@ func (repo *Repository) DeleteAvatar() error {
 	}
 	return sess.Commit()
 }
+
+// GetOriginalURLHostname returns the hostname of a URL or the URL
+func (repo *Repository) GetOriginalURLHostname() string {
+	u, err := url.Parse(repo.OriginalURL)
+	if err != nil {
+		return repo.OriginalURL
+	}
+
+	return u.Host
+}
diff --git a/modules/migrations/base/comment.go b/modules/migrations/base/comment.go
index d89ec3a3f5..38c544d6e0 100644
--- a/modules/migrations/base/comment.go
+++ b/modules/migrations/base/comment.go
@@ -10,6 +10,7 @@ import "time"
 // Comment is a standard comment information
 type Comment struct {
 	IssueIndex  int64
+	PosterID    int64
 	PosterName  string
 	PosterEmail string
 	Created     time.Time
diff --git a/modules/migrations/base/issue.go b/modules/migrations/base/issue.go
index ddadd0c2b3..08d947b05d 100644
--- a/modules/migrations/base/issue.go
+++ b/modules/migrations/base/issue.go
@@ -10,6 +10,7 @@ import "time"
 // Issue is a standard issue information
 type Issue struct {
 	Number      int64
+	PosterID    int64
 	PosterName  string
 	PosterEmail string
 	Title       string
diff --git a/modules/migrations/base/options.go b/modules/migrations/base/options.go
index 262981b933..ba7fdc6815 100644
--- a/modules/migrations/base/options.go
+++ b/modules/migrations/base/options.go
@@ -12,15 +12,15 @@ type MigrateOptions struct {
 	AuthPassword string
 	Name         string
 	Description  string
+	OriginalURL  string
 
-	Wiki              bool
-	Issues            bool
-	Milestones        bool
-	Labels            bool
-	Releases          bool
-	Comments          bool
-	PullRequests      bool
-	Private           bool
-	Mirror            bool
-	IgnoreIssueAuthor bool // if true will not add original author information before issues or comments content.
+	Wiki         bool
+	Issues       bool
+	Milestones   bool
+	Labels       bool
+	Releases     bool
+	Comments     bool
+	PullRequests bool
+	Private      bool
+	Mirror       bool
 }
diff --git a/modules/migrations/base/pullrequest.go b/modules/migrations/base/pullrequest.go
index 515cab3f91..42456fd314 100644
--- a/modules/migrations/base/pullrequest.go
+++ b/modules/migrations/base/pullrequest.go
@@ -15,6 +15,7 @@ type PullRequest struct {
 	Number         int64
 	Title          string
 	PosterName     string
+	PosterID       int64
 	PosterEmail    string
 	Content        string
 	Milestone      string
diff --git a/modules/migrations/base/repo.go b/modules/migrations/base/repo.go
index 907d8fc09e..5cfb0de920 100644
--- a/modules/migrations/base/repo.go
+++ b/modules/migrations/base/repo.go
@@ -15,4 +15,5 @@ type Repository struct {
 	AuthUsername string
 	AuthPassword string
 	CloneURL     string
+	OriginalURL  string
 }
diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go
index 1df824c94f..b15aed5f4b 100644
--- a/modules/migrations/gitea.go
+++ b/modules/migrations/gitea.go
@@ -82,6 +82,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
 	r, err := models.MigrateRepository(g.doer, owner, models.MigrateRepoOptions{
 		Name:                 g.repoName,
 		Description:          repo.Description,
+		OriginalURL:          repo.OriginalURL,
 		IsMirror:             repo.IsMirror,
 		RemoteAddr:           repo.CloneURL,
 		IsPrivate:            repo.IsPrivate,
@@ -247,17 +248,19 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
 		}
 
 		var is = models.Issue{
-			RepoID:      g.repo.ID,
-			Repo:        g.repo,
-			Index:       issue.Number,
-			PosterID:    g.doer.ID,
-			Title:       issue.Title,
-			Content:     issue.Content,
-			IsClosed:    issue.State == "closed",
-			IsLocked:    issue.IsLocked,
-			MilestoneID: milestoneID,
-			Labels:      labels,
-			CreatedUnix: util.TimeStamp(issue.Created.Unix()),
+			RepoID:           g.repo.ID,
+			Repo:             g.repo,
+			Index:            issue.Number,
+			PosterID:         g.doer.ID,
+			OriginalAuthor:   issue.PosterName,
+			OriginalAuthorID: issue.PosterID,
+			Title:            issue.Title,
+			Content:          issue.Content,
+			IsClosed:         issue.State == "closed",
+			IsLocked:         issue.IsLocked,
+			MilestoneID:      milestoneID,
+			Labels:           labels,
+			CreatedUnix:      util.TimeStamp(issue.Created.Unix()),
 		}
 		if issue.Closed != nil {
 			is.ClosedUnix = util.TimeStamp(issue.Closed.Unix())
@@ -293,11 +296,13 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
 		}
 
 		cms = append(cms, &models.Comment{
-			IssueID:     issueID,
-			Type:        models.CommentTypeComment,
-			PosterID:    g.doer.ID,
-			Content:     comment.Content,
-			CreatedUnix: util.TimeStamp(comment.Created.Unix()),
+			IssueID:          issueID,
+			Type:             models.CommentTypeComment,
+			PosterID:         g.doer.ID,
+			OriginalAuthor:   comment.PosterName,
+			OriginalAuthorID: comment.PosterID,
+			Content:          comment.Content,
+			CreatedUnix:      util.TimeStamp(comment.Created.Unix()),
 		})
 
 		// TODO: Reactions
@@ -430,18 +435,20 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
 		HasMerged:    pr.Merged,
 
 		Issue: &models.Issue{
-			RepoID:      g.repo.ID,
-			Repo:        g.repo,
-			Title:       pr.Title,
-			Index:       pr.Number,
-			PosterID:    g.doer.ID,
-			Content:     pr.Content,
-			MilestoneID: milestoneID,
-			IsPull:      true,
-			IsClosed:    pr.State == "closed",
-			IsLocked:    pr.IsLocked,
-			Labels:      labels,
-			CreatedUnix: util.TimeStamp(pr.Created.Unix()),
+			RepoID:           g.repo.ID,
+			Repo:             g.repo,
+			Title:            pr.Title,
+			Index:            pr.Number,
+			PosterID:         g.doer.ID,
+			OriginalAuthor:   pr.PosterName,
+			OriginalAuthorID: pr.PosterID,
+			Content:          pr.Content,
+			MilestoneID:      milestoneID,
+			IsPull:           true,
+			IsClosed:         pr.State == "closed",
+			IsLocked:         pr.IsLocked,
+			Labels:           labels,
+			CreatedUnix:      util.TimeStamp(pr.Created.Unix()),
 		},
 	}
 
diff --git a/modules/migrations/gitea_test.go b/modules/migrations/gitea_test.go
index 22da7da171..88a3a6d218 100644
--- a/modules/migrations/gitea_test.go
+++ b/modules/migrations/gitea_test.go
@@ -34,16 +34,15 @@ func TestGiteaUploadRepo(t *testing.T) {
 		Name:         repoName,
 		AuthUsername: "",
 
-		Wiki:              true,
-		Issues:            true,
-		Milestones:        true,
-		Labels:            true,
-		Releases:          true,
-		Comments:          true,
-		PullRequests:      true,
-		Private:           true,
-		Mirror:            false,
-		IgnoreIssueAuthor: false,
+		Wiki:         true,
+		Issues:       true,
+		Milestones:   true,
+		Labels:       true,
+		Releases:     true,
+		Comments:     true,
+		PullRequests: true,
+		Private:      true,
+		Mirror:       false,
 	})
 	assert.NoError(t, err)
 
diff --git a/modules/migrations/github.go b/modules/migrations/github.go
index e6b532df9c..93ba108548 100644
--- a/modules/migrations/github.go
+++ b/modules/migrations/github.go
@@ -107,13 +107,13 @@ func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) {
 	if err != nil {
 		return nil, err
 	}
-
 	// convert github repo to stand Repo
 	return &base.Repository{
 		Owner:       g.repoOwner,
 		Name:        gr.GetName(),
 		IsPrivate:   *gr.Private,
 		Description: gr.GetDescription(),
+		OriginalURL: gr.GetHTMLURL(),
 		CloneURL:    gr.GetCloneURL(),
 	}, nil
 }
@@ -317,6 +317,7 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
 		allIssues = append(allIssues, &base.Issue{
 			Title:       *issue.Title,
 			Number:      int64(*issue.Number),
+			PosterID:    *issue.User.ID,
 			PosterName:  *issue.User.Login,
 			PosterEmail: email,
 			Content:     body,
@@ -359,6 +360,7 @@ func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, er
 			}
 			allComments = append(allComments, &base.Comment{
 				IssueIndex:  issueNumber,
+				PosterID:    *comment.User.ID,
 				PosterName:  *comment.User.Login,
 				PosterEmail: email,
 				Content:     *comment.Body,
@@ -451,6 +453,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
 			Title:          *pr.Title,
 			Number:         int64(*pr.Number),
 			PosterName:     *pr.User.Login,
+			PosterID:       *pr.User.ID,
 			PosterEmail:    email,
 			Content:        body,
 			Milestone:      milestone,
diff --git a/modules/migrations/github_test.go b/modules/migrations/github_test.go
index 700183bdc1..0f52a62eb2 100644
--- a/modules/migrations/github_test.go
+++ b/modules/migrations/github_test.go
@@ -68,6 +68,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
 		Owner:       "go-gitea",
 		Description: "Git with a cup of tea, painless self-hosted git service",
 		CloneURL:    "https://github.com/go-gitea/gitea.git",
+		OriginalURL: "https://github.com/go-gitea/gitea",
 	}, repo)
 
 	milestones, err := downloader.GetMilestones()
@@ -180,6 +181,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
 			Title:      "Contribution system: History heatmap for user",
 			Content:    "Hi guys,\r\n\r\nI think that is a possible feature, a history heatmap similar to github or gitlab.\r\nActually exists a plugin called Calendar HeatMap. I used this on mine project to heat application log and worked fine here.\r\nThen, is only a idea, what you think? :)\r\n\r\nhttp://cal-heatmap.com/\r\nhttps://github.com/wa0x6e/cal-heatmap\r\n\r\nReference: https://github.com/gogits/gogs/issues/1640",
 			Milestone:  "1.7.0",
+			PosterID:   1520407,
 			PosterName: "joubertredrat",
 			State:      "closed",
 			Created:    time.Date(2016, 11, 02, 18, 51, 55, 0, time.UTC),
@@ -209,6 +211,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
 			Title:      "display page revisions on wiki",
 			Content:    "Hi guys,\r\n\r\nWiki on Gogs is very fine, I liked a lot, but I think that is good idea to be possible see other revisions from page as a page history.\r\n\r\nWhat you think?\r\n\r\nReference: https://github.com/gogits/gogs/issues/2991",
 			Milestone:  "1.x.x",
+			PosterID:   1520407,
 			PosterName: "joubertredrat",
 			State:      "open",
 			Created:    time.Date(2016, 11, 02, 18, 57, 32, 0, time.UTC),
@@ -238,6 +241,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
 			Title:      "audit logs",
 			Content:    "Hi,\r\n\r\nI think that is good idea to have user operation log to admin see what the user is doing at Gogs. Similar to example below\r\n\r\n| user | operation | information |\r\n| --- | --- | --- |\r\n| joubertredrat | repo.create | Create repo MyProjectData |\r\n| joubertredrat | user.settings | Edit settings |\r\n| tboerger | repo.fork | Create Fork from MyProjectData to ForkMyProjectData |\r\n| bkcsoft | repo.remove | Remove repo MySource |\r\n| tboerger | admin.auth | Edit auth LDAP org-connection |\r\n\r\nThis resource can be used on user page too, as user activity, set that log row is public (repo._) or private (user._, admin.*) and display only public activity.\r\n\r\nWhat you think?\r\n\r\n[Chat summary from March 14, 2017](https://github.com/go-gitea/gitea/issues/8#issuecomment-286463807)\r\n\r\nReferences:\r\nhttps://github.com/gogits/gogs/issues/3016",
 			Milestone:  "1.x.x",
+			PosterID:   1520407,
 			PosterName: "joubertredrat",
 			State:      "open",
 			Created:    time.Date(2016, 11, 02, 18, 59, 20, 0, time.UTC),
@@ -270,6 +274,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
 	assert.EqualValues(t, []*base.Comment{
 		{
 			IssueIndex: 6,
+			PosterID:   4726179,
 			PosterName: "bkcsoft",
 			Created:    time.Date(2016, 11, 02, 18, 59, 48, 0, time.UTC),
 			Content: `I would prefer a solution that is in the backend, unless it's required to have it update without reloading. Unfortunately I can't seem to find anything that does that :unamused: 
@@ -288,6 +293,7 @@ Also this would _require_ caching, since it will fetch huge amounts of data from
 		},
 		{
 			IssueIndex: 6,
+			PosterID:   1520407,
 			PosterName: "joubertredrat",
 			Created:    time.Date(2016, 11, 02, 19, 16, 56, 0, time.UTC),
 			Content: `Yes, this plugin build on front-end, with backend I don't know too, but we can consider make component for this.
@@ -306,6 +312,7 @@ In my case I use ajax to get data, but build on frontend anyway
 		},
 		{
 			IssueIndex: 6,
+			PosterID:   1799009,
 			PosterName: "xinity",
 			Created:    time.Date(2016, 11, 03, 13, 04, 56, 0, time.UTC),
 			Content: `following  @bkcsoft retention strategy in cache is a must if we don't want gitea to waste ressources.
@@ -345,6 +352,7 @@ something like in the latest 15days could be enough don't you think ?
 			Title:      "Rename import paths: \"github.com/gogits/gogs\" -> \"github.com/go-gitea/gitea\"",
 			Content:    "",
 			Milestone:  "1.0.0",
+			PosterID:   7011819,
 			PosterName: "andreynering",
 			State:      "closed",
 			Created:    time.Date(2016, 11, 02, 17, 01, 19, 0, time.UTC),
@@ -380,6 +388,7 @@ something like in the latest 15days could be enough don't you think ?
 			Title:      "Fix sender of issue notifications",
 			Content:    "It is the FROM field in mailer configuration that needs be used,\r\nnot the USER field, which is for authentication.\r\n\r\nMigrated from https://github.com/gogits/gogs/pull/3616\r\n",
 			Milestone:  "1.0.0",
+			PosterID:   289678,
 			PosterName: "strk",
 			State:      "closed",
 			Created:    time.Date(2016, 11, 02, 17, 24, 19, 0, time.UTC),
@@ -417,6 +426,7 @@ something like in the latest 15days could be enough don't you think ?
 			Title:      "Use proper url for libravatar dep",
 			Content:    "Fetch go-libravatar from its official source, rather than from an unmaintained fork\r\n",
 			Milestone:  "1.0.0",
+			PosterID:   289678,
 			PosterName: "strk",
 			State:      "closed",
 			Created:    time.Date(2016, 11, 02, 17, 34, 31, 0, time.UTC),
diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go
index 7fc7911f21..a86614c317 100644
--- a/modules/migrations/migrate.go
+++ b/modules/migrations/migrate.go
@@ -6,8 +6,6 @@
 package migrations
 
 import (
-	"fmt"
-
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/migrations/base"
@@ -155,11 +153,6 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
 			if err != nil {
 				return err
 			}
-			for _, issue := range issues {
-				if !opts.IgnoreIssueAuthor {
-					issue.Content = fmt.Sprintf("Author: @%s \n\n%s", issue.PosterName, issue.Content)
-				}
-			}
 
 			if err := uploader.CreateIssues(issues...); err != nil {
 				return err
@@ -175,11 +168,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
 				if err != nil {
 					return err
 				}
-				for _, comment := range comments {
-					if !opts.IgnoreIssueAuthor {
-						comment.Content = fmt.Sprintf("Author: @%s \n\n%s", comment.PosterName, comment.Content)
-					}
-				}
+
 				allComments = append(allComments, comments...)
 
 				if len(allComments) >= commentBatchSize {
@@ -212,11 +201,6 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
 				return err
 			}
 
-			for _, pr := range prs {
-				if !opts.IgnoreIssueAuthor {
-					pr.Content = fmt.Sprintf("Author: @%s \n\n%s", pr.PosterName, pr.Content)
-				}
-			}
 			if err := uploader.CreatePullRequests(prs...); err != nil {
 				return err
 			}
@@ -231,11 +215,6 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
 				if err != nil {
 					return err
 				}
-				for _, comment := range comments {
-					if !opts.IgnoreIssueAuthor {
-						comment.Content = fmt.Sprintf("Author: @%s \n\n%s", comment.PosterName, comment.Content)
-					}
-				}
 
 				allComments = append(allComments, comments...)
 
diff --git a/modules/structs/issue.go b/modules/structs/issue.go
index 6d7517bdc7..58fd7344b4 100644
--- a/modules/structs/issue.go
+++ b/modules/structs/issue.go
@@ -29,16 +29,18 @@ type PullRequestMeta struct {
 // Issue represents an issue in a repository
 // swagger:model
 type Issue struct {
-	ID        int64      `json:"id"`
-	URL       string     `json:"url"`
-	Index     int64      `json:"number"`
-	Poster    *User      `json:"user"`
-	Title     string     `json:"title"`
-	Body      string     `json:"body"`
-	Labels    []*Label   `json:"labels"`
-	Milestone *Milestone `json:"milestone"`
-	Assignee  *User      `json:"assignee"`
-	Assignees []*User    `json:"assignees"`
+	ID               int64      `json:"id"`
+	URL              string     `json:"url"`
+	Index            int64      `json:"number"`
+	Poster           *User      `json:"user"`
+	OriginalAuthor   string     `json:"original_author"`
+	OriginalAuthorID int64      `json:"original_author_id"`
+	Title            string     `json:"title"`
+	Body             string     `json:"body"`
+	Labels           []*Label   `json:"labels"`
+	Milestone        *Milestone `json:"milestone"`
+	Assignee         *User      `json:"assignee"`
+	Assignees        []*User    `json:"assignees"`
 	// Whether the issue is open or closed
 	//
 	// type: string
diff --git a/modules/structs/issue_comment.go b/modules/structs/issue_comment.go
index 185f3910ed..0c8ac20017 100644
--- a/modules/structs/issue_comment.go
+++ b/modules/structs/issue_comment.go
@@ -10,12 +10,14 @@ import (
 
 // Comment represents a comment on a commit or issue
 type Comment struct {
-	ID       int64  `json:"id"`
-	HTMLURL  string `json:"html_url"`
-	PRURL    string `json:"pull_request_url"`
-	IssueURL string `json:"issue_url"`
-	Poster   *User  `json:"user"`
-	Body     string `json:"body"`
+	ID               int64  `json:"id"`
+	HTMLURL          string `json:"html_url"`
+	PRURL            string `json:"pull_request_url"`
+	IssueURL         string `json:"issue_url"`
+	Poster           *User  `json:"user"`
+	OriginalAuthor   string `json:"original_author"`
+	OriginalAuthorID int64  `json:"original_author_id"`
+	Body             string `json:"body"`
 	// swagger:strfmt date-time
 	Created time.Time `json:"created_at"`
 	// swagger:strfmt date-time
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index b4d162b776..81203319e0 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -31,6 +31,7 @@ type Repository struct {
 	HTMLURL       string      `json:"html_url"`
 	SSHURL        string      `json:"ssh_url"`
 	CloneURL      string      `json:"clone_url"`
+	OriginalURL   string      `json:"original_url"`
 	Website       string      `json:"website"`
 	Stars         int         `json:"stars_count"`
 	Forks         int         `json:"forks_count"`
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index c4551bb4be..5a3969c098 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -82,6 +82,7 @@ func NewFuncMap() []template.FuncMap {
 		"FileSize":      base.FileSize,
 		"Subtract":      base.Subtract,
 		"EntryIcon":     base.EntryIcon,
+		"MigrationIcon": MigrationIcon,
 		"Add": func(a, b int) int {
 			return a + b
 		},
@@ -540,3 +541,13 @@ func TrN(lang string, cnt interface{}, key1, keyN string) string {
 	}
 	return keyN
 }
+
+// MigrationIcon returns a Font Awesome name matching the service an issue/comment was migrated from
+func MigrationIcon(hostname string) string {
+	switch hostname {
+	case "github.com":
+		return "fa-github"
+	default:
+		return "fa-git-alt"
+	}
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 223df91fda..0c83a7aef1 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -621,6 +621,8 @@ migrate.invalid_local_path = "The local path is invalid. It does not exist or is
 migrate.failed = Migration failed: %v
 migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead.
 migrate.migrate_items_options = When migrating from github, input a username and migration options will be displayed.
+migrated_from = Migrated from <a href="%[1]s">%[2]s</a>
+migrated_from_fake = Migrated From %[1]s
 
 mirror_from = mirror of
 forked_from = forked from
diff --git a/public/css/index.css b/public/css/index.css
index 437605e1d3..9039409f14 100644
--- a/public/css/index.css
+++ b/public/css/index.css
@@ -137,6 +137,9 @@ a{cursor:pointer}
 .ui .background.purple{background-color:#6e5494!important}
 .ui .background.yellow{background-color:#fbbf09!important}
 .ui .background.gold{background-color:#a1882b!important}
+.ui .migrate{color:#888!important;opacity:.5}
+.ui .migrate a{color:#444!important}
+.ui .migrate a:hover{color:#000!important}
 .ui .branch-tag-choice{line-height:20px}
 @media only screen and (max-width:767px){.ui.pagination.menu .item.navigation span.navigation_label,.ui.pagination.menu .item:not(.active):not(.navigation){display:none}
 }
diff --git a/public/less/_base.less b/public/less/_base.less
index 4ce1b8eff9..aae3b97c72 100644
--- a/public/less/_base.less
+++ b/public/less/_base.less
@@ -588,6 +588,18 @@ code,
         }
     }
 
+    .migrate {
+        color: #888888 !important;
+        opacity: 0.5;
+        a {
+            color: #444444 !important;
+
+            &:hover {
+                color: #000000 !important;
+            }
+        }
+    }
+
     .branch-tag-choice {
         line-height: 20px;
     }
diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl
index 3007c99106..45b2cc67e3 100644
--- a/templates/repo/issue/list.tmpl
+++ b/templates/repo/issue/list.tmpl
@@ -234,8 +234,9 @@
 
 					<p class="desc">
 						{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }}
-
-						{{if gt .Poster.ID 0}}
+						{{if .OriginalAuthor }}
+							{{$.i18n.Tr .GetLastEventLabelFake $timeStr .OriginalAuthor | Safe}}
+						{{else if gt .Poster.ID 0}}
 							{{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName | Escape) | Safe}}
 						{{else}}
 							{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}}
diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl
index 16700a8dc1..b3f88c662d 100644
--- a/templates/repo/issue/view_content.tmpl
+++ b/templates/repo/issue/view_content.tmpl
@@ -12,12 +12,20 @@
 	<div class="twelve wide column comment-list prevent-before-timeline">
 		<ui class="ui comments timeline-line">
 			<div class="comment">
+			{{if .Issue.OriginalAuthor }}
+				<span class="avatar"><img src="/img/avatar_default.png"></span>
+			{{else}}
 				<a class="avatar" {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>
 					<img src="{{.Issue.Poster.RelAvatarLink}}">
 				</a>
+			{{end}}
 				<div class="content">
 					<div class="ui top attached header">
+					{{if .Issue.OriginalAuthor }}
+						<span class="text black"><i class="fa {{MigrationIcon .Repository.GetOriginalURLHostname}}" aria-hidden="true"></i> {{ .Issue.OriginalAuthor }}</span><span class="text grey"> {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}}<span> <span class="text migrate">{{if .Repository.OriginalURL}} ({{$.i18n.Tr "repo.migrated_from" .Repository.OriginalURL .Repository.GetOriginalURLHostname | Safe }}){{end}}</span>
+					{{else}}
 						<span class="text grey"><a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a> {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}}</span>
+					{{end}}
 						{{if not $.Repository.IsArchived}}
 							<div class="ui right actions">
 								{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) }}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 20474f6302..68303cf1ca 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -9,12 +9,20 @@
 	 22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED -->
 	{{if eq .Type 0}}
 		<div class="comment" id="{{.HashTag}}">
+		{{if .OriginalAuthor }}
+			<span class="avatar"><img src="/img/avatar_default.png"></span>
+		{{else}}
 			<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
 				<img src="{{.Poster.RelAvatarLink}}">
 			</a>
+		{{end}}
 			<div class="content">
 				<div class="ui top attached header">
+				{{if .OriginalAuthor }}
+					<span class="text black"><i class="fa {{MigrationIcon $.Repository.GetOriginalURLHostname}}" aria-hidden="true"></i> {{ .OriginalAuthor }}</span><span class="text grey"> {{$.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}} {{if $.Repository.OriginalURL}}</span><span class="text migrate">({{$.i18n.Tr "repo.migrated_from" $.Repository.OriginalURL $.Repository.GetOriginalURLHostname | Safe }}){{end}}</span>
+				{{else}}
 					<span class="text grey"><a {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>{{.Poster.GetDisplayName}}</a> {{$.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}}</span>
+				{{end}}
                     {{if not $.Repository.IsArchived}}
                         <div class="ui right actions">
                             {{if gt .ShowTag 0}}
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index 78c892fa4d..4b254d2c4b 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -27,16 +27,28 @@
 	{{if .Issue.IsPull}}
 		{{if .Issue.PullRequest.HasMerged}}
 			{{ $mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix $.Lang }}
-			<a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.GetDisplayName}}</a>
-			<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}}</span>
+			{{if .Issue.OriginalAuthor }}
+				{{.Issue.OriginalAuthor}}
+				<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}}</span>
+			{{else}}
+				<a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.GetDisplayName}}</a>
+				<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}}</span>
+			{{end}}
 		{{else}}
-			<a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a>
-			<span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span>
+			{{if .Issue.OriginalAuthor }}
+				{{.Issue.OriginalAuthor}}
+				<span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span>
+			{{else}}
+				<a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a>
+				<span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span>
+			{{end}}
 		{{end}}
 	{{else}}
 		{{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.Lang }}
 		<span class="time-desc">
-			{{if gt .Issue.Poster.ID 0}}
+			{{if .Issue.OriginalAuthor }}
+				{{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr .Issue.OriginalAuthor | Safe}}
+			{{else if gt .Issue.Poster.ID 0}}
 				{{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink (.Issue.Poster.GetDisplayName|Escape) | Safe}}
 			{{else}}
 				{{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr (.Issue.Poster.GetDisplayName|Escape) | Safe}}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index d6d501ed22..11f4161172 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -6971,6 +6971,15 @@
           "type": "string",
           "x-go-name": "IssueURL"
         },
+        "original_author": {
+          "type": "string",
+          "x-go-name": "OriginalAuthor"
+        },
+        "original_author_id": {
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "OriginalAuthorID"
+        },
         "pull_request_url": {
           "type": "string",
           "x-go-name": "PRURL"
@@ -8669,6 +8678,15 @@
           "format": "int64",
           "x-go-name": "Index"
         },
+        "original_author": {
+          "type": "string",
+          "x-go-name": "OriginalAuthor"
+        },
+        "original_author_id": {
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "OriginalAuthorID"
+        },
         "pull_request": {
           "$ref": "#/definitions/PullRequestMeta"
         },
@@ -9489,6 +9507,10 @@
           "format": "int64",
           "x-go-name": "OpenIssues"
         },
+        "original_url": {
+          "type": "string",
+          "x-go-name": "OriginalURL"
+        },
         "owner": {
           "$ref": "#/definitions/User"
         },
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl
index b69509d799..7083569164 100644
--- a/templates/user/dashboard/issues.tmpl
+++ b/templates/user/dashboard/issues.tmpl
@@ -93,7 +93,9 @@
 							{{end}}
 
 							<p class="desc">
-								{{if gt .Poster.ID 0}}
+								{{if .OriginalAuthor}}
+									{{$.i18n.Tr .GetLastEventLabelFake $timeStr .OriginalAuthor | Safe}}
+								{{else if gt .Poster.ID 0}}
 									{{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName|Escape) | Safe}}
 								{{else}}
 									{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName|Escape) | Safe}}