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
This commit is contained in:
mrsdizzie 2019-07-07 22:14:12 -04:00 committed by Lunny Xiao
parent fcda2d5b35
commit 1f1ecda541
28 changed files with 263 additions and 118 deletions

View File

@ -31,6 +31,8 @@ type Issue struct {
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository. Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
PosterID int64 `xorm:"INDEX"` PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"` Poster *User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64
Title string `xorm:"name"` Title string `xorm:"name"`
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"` RenderedContent string `xorm:"-"`

View File

@ -103,6 +103,8 @@ type Comment struct {
Type CommentType Type CommentType
PosterID int64 `xorm:"INDEX"` PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"` Poster *User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64
IssueID int64 `xorm:"INDEX"` IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"` Issue *Issue `xorm:"-"`
LabelID int64 LabelID int64

View File

@ -232,6 +232,8 @@ var migrations = []Migration{
NewMigration("add avatar field to repository", addAvatarFieldToRepository), NewMigration("add avatar field to repository", addAvatarFieldToRepository),
// v88 -> v89 // v88 -> v89
NewMigration("add commit status context field to commit_status", addCommitStatusContext), 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 // Migrate database to current version

36
models/migrations/v89.go Normal file
View File

@ -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))
}

View File

@ -136,6 +136,7 @@ type Repository struct {
Name string `xorm:"INDEX NOT NULL"` Name string `xorm:"INDEX NOT NULL"`
Description string Description string
Website string Website string
OriginalURL string
DefaultBranch string DefaultBranch string
NumWatches int NumWatches int
@ -847,6 +848,7 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
type MigrateRepoOptions struct { type MigrateRepoOptions struct {
Name string Name string
Description string Description string
OriginalURL string
IsPrivate bool IsPrivate bool
IsMirror bool IsMirror bool
RemoteAddr string RemoteAddr string
@ -878,6 +880,7 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
repo, err := CreateRepository(doer, u, CreateRepoOptions{ repo, err := CreateRepository(doer, u, CreateRepoOptions{
Name: opts.Name, Name: opts.Name,
Description: opts.Description, Description: opts.Description,
OriginalURL: opts.OriginalURL,
IsPrivate: opts.IsPrivate, IsPrivate: opts.IsPrivate,
IsMirror: opts.IsMirror, IsMirror: opts.IsMirror,
}) })
@ -1092,6 +1095,7 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
type CreateRepoOptions struct { type CreateRepoOptions struct {
Name string Name string
Description string Description string
OriginalURL string
Gitignores string Gitignores string
License string License string
Readme string Readme string
@ -1358,6 +1362,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
Name: opts.Name, Name: opts.Name,
LowerName: strings.ToLower(opts.Name), LowerName: strings.ToLower(opts.Name),
Description: opts.Description, Description: opts.Description,
OriginalURL: opts.OriginalURL,
IsPrivate: opts.IsPrivate, IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror, IsFsckEnabled: !opts.IsMirror,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
@ -2678,3 +2683,13 @@ func (repo *Repository) DeleteAvatar() error {
} }
return sess.Commit() 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
}

View File

@ -10,6 +10,7 @@ import "time"
// Comment is a standard comment information // Comment is a standard comment information
type Comment struct { type Comment struct {
IssueIndex int64 IssueIndex int64
PosterID int64
PosterName string PosterName string
PosterEmail string PosterEmail string
Created time.Time Created time.Time

View File

@ -10,6 +10,7 @@ import "time"
// Issue is a standard issue information // Issue is a standard issue information
type Issue struct { type Issue struct {
Number int64 Number int64
PosterID int64
PosterName string PosterName string
PosterEmail string PosterEmail string
Title string Title string

View File

@ -12,6 +12,7 @@ type MigrateOptions struct {
AuthPassword string AuthPassword string
Name string Name string
Description string Description string
OriginalURL string
Wiki bool Wiki bool
Issues bool Issues bool
@ -22,5 +23,4 @@ type MigrateOptions struct {
PullRequests bool PullRequests bool
Private bool Private bool
Mirror bool Mirror bool
IgnoreIssueAuthor bool // if true will not add original author information before issues or comments content.
} }

View File

@ -15,6 +15,7 @@ type PullRequest struct {
Number int64 Number int64
Title string Title string
PosterName string PosterName string
PosterID int64
PosterEmail string PosterEmail string
Content string Content string
Milestone string Milestone string

View File

@ -15,4 +15,5 @@ type Repository struct {
AuthUsername string AuthUsername string
AuthPassword string AuthPassword string
CloneURL string CloneURL string
OriginalURL string
} }

View File

@ -82,6 +82,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
r, err := models.MigrateRepository(g.doer, owner, models.MigrateRepoOptions{ r, err := models.MigrateRepository(g.doer, owner, models.MigrateRepoOptions{
Name: g.repoName, Name: g.repoName,
Description: repo.Description, Description: repo.Description,
OriginalURL: repo.OriginalURL,
IsMirror: repo.IsMirror, IsMirror: repo.IsMirror,
RemoteAddr: repo.CloneURL, RemoteAddr: repo.CloneURL,
IsPrivate: repo.IsPrivate, IsPrivate: repo.IsPrivate,
@ -251,6 +252,8 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
Repo: g.repo, Repo: g.repo,
Index: issue.Number, Index: issue.Number,
PosterID: g.doer.ID, PosterID: g.doer.ID,
OriginalAuthor: issue.PosterName,
OriginalAuthorID: issue.PosterID,
Title: issue.Title, Title: issue.Title,
Content: issue.Content, Content: issue.Content,
IsClosed: issue.State == "closed", IsClosed: issue.State == "closed",
@ -296,6 +299,8 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
IssueID: issueID, IssueID: issueID,
Type: models.CommentTypeComment, Type: models.CommentTypeComment,
PosterID: g.doer.ID, PosterID: g.doer.ID,
OriginalAuthor: comment.PosterName,
OriginalAuthorID: comment.PosterID,
Content: comment.Content, Content: comment.Content,
CreatedUnix: util.TimeStamp(comment.Created.Unix()), CreatedUnix: util.TimeStamp(comment.Created.Unix()),
}) })
@ -435,6 +440,8 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
Title: pr.Title, Title: pr.Title,
Index: pr.Number, Index: pr.Number,
PosterID: g.doer.ID, PosterID: g.doer.ID,
OriginalAuthor: pr.PosterName,
OriginalAuthorID: pr.PosterID,
Content: pr.Content, Content: pr.Content,
MilestoneID: milestoneID, MilestoneID: milestoneID,
IsPull: true, IsPull: true,

View File

@ -43,7 +43,6 @@ func TestGiteaUploadRepo(t *testing.T) {
PullRequests: true, PullRequests: true,
Private: true, Private: true,
Mirror: false, Mirror: false,
IgnoreIssueAuthor: false,
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -107,13 +107,13 @@ func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// convert github repo to stand Repo // convert github repo to stand Repo
return &base.Repository{ return &base.Repository{
Owner: g.repoOwner, Owner: g.repoOwner,
Name: gr.GetName(), Name: gr.GetName(),
IsPrivate: *gr.Private, IsPrivate: *gr.Private,
Description: gr.GetDescription(), Description: gr.GetDescription(),
OriginalURL: gr.GetHTMLURL(),
CloneURL: gr.GetCloneURL(), CloneURL: gr.GetCloneURL(),
}, nil }, nil
} }
@ -317,6 +317,7 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
allIssues = append(allIssues, &base.Issue{ allIssues = append(allIssues, &base.Issue{
Title: *issue.Title, Title: *issue.Title,
Number: int64(*issue.Number), Number: int64(*issue.Number),
PosterID: *issue.User.ID,
PosterName: *issue.User.Login, PosterName: *issue.User.Login,
PosterEmail: email, PosterEmail: email,
Content: body, Content: body,
@ -359,6 +360,7 @@ func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, er
} }
allComments = append(allComments, &base.Comment{ allComments = append(allComments, &base.Comment{
IssueIndex: issueNumber, IssueIndex: issueNumber,
PosterID: *comment.User.ID,
PosterName: *comment.User.Login, PosterName: *comment.User.Login,
PosterEmail: email, PosterEmail: email,
Content: *comment.Body, Content: *comment.Body,
@ -451,6 +453,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
Title: *pr.Title, Title: *pr.Title,
Number: int64(*pr.Number), Number: int64(*pr.Number),
PosterName: *pr.User.Login, PosterName: *pr.User.Login,
PosterID: *pr.User.ID,
PosterEmail: email, PosterEmail: email,
Content: body, Content: body,
Milestone: milestone, Milestone: milestone,

View File

@ -68,6 +68,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
Owner: "go-gitea", Owner: "go-gitea",
Description: "Git with a cup of tea, painless self-hosted git service", Description: "Git with a cup of tea, painless self-hosted git service",
CloneURL: "https://github.com/go-gitea/gitea.git", CloneURL: "https://github.com/go-gitea/gitea.git",
OriginalURL: "https://github.com/go-gitea/gitea",
}, repo) }, repo)
milestones, err := downloader.GetMilestones() milestones, err := downloader.GetMilestones()
@ -180,6 +181,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
Title: "Contribution system: History heatmap for user", 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", 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", Milestone: "1.7.0",
PosterID: 1520407,
PosterName: "joubertredrat", PosterName: "joubertredrat",
State: "closed", State: "closed",
Created: time.Date(2016, 11, 02, 18, 51, 55, 0, time.UTC), 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", 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", 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", Milestone: "1.x.x",
PosterID: 1520407,
PosterName: "joubertredrat", PosterName: "joubertredrat",
State: "open", State: "open",
Created: time.Date(2016, 11, 02, 18, 57, 32, 0, time.UTC), Created: time.Date(2016, 11, 02, 18, 57, 32, 0, time.UTC),
@ -238,6 +241,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
Title: "audit logs", 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", 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", Milestone: "1.x.x",
PosterID: 1520407,
PosterName: "joubertredrat", PosterName: "joubertredrat",
State: "open", State: "open",
Created: time.Date(2016, 11, 02, 18, 59, 20, 0, time.UTC), 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{ assert.EqualValues(t, []*base.Comment{
{ {
IssueIndex: 6, IssueIndex: 6,
PosterID: 4726179,
PosterName: "bkcsoft", PosterName: "bkcsoft",
Created: time.Date(2016, 11, 02, 18, 59, 48, 0, time.UTC), 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: 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, IssueIndex: 6,
PosterID: 1520407,
PosterName: "joubertredrat", PosterName: "joubertredrat",
Created: time.Date(2016, 11, 02, 19, 16, 56, 0, time.UTC), 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. 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, IssueIndex: 6,
PosterID: 1799009,
PosterName: "xinity", PosterName: "xinity",
Created: time.Date(2016, 11, 03, 13, 04, 56, 0, time.UTC), 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. 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\"", Title: "Rename import paths: \"github.com/gogits/gogs\" -> \"github.com/go-gitea/gitea\"",
Content: "", Content: "",
Milestone: "1.0.0", Milestone: "1.0.0",
PosterID: 7011819,
PosterName: "andreynering", PosterName: "andreynering",
State: "closed", State: "closed",
Created: time.Date(2016, 11, 02, 17, 01, 19, 0, time.UTC), 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", 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", 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", Milestone: "1.0.0",
PosterID: 289678,
PosterName: "strk", PosterName: "strk",
State: "closed", State: "closed",
Created: time.Date(2016, 11, 02, 17, 24, 19, 0, time.UTC), 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", Title: "Use proper url for libravatar dep",
Content: "Fetch go-libravatar from its official source, rather than from an unmaintained fork\r\n", Content: "Fetch go-libravatar from its official source, rather than from an unmaintained fork\r\n",
Milestone: "1.0.0", Milestone: "1.0.0",
PosterID: 289678,
PosterName: "strk", PosterName: "strk",
State: "closed", State: "closed",
Created: time.Date(2016, 11, 02, 17, 34, 31, 0, time.UTC), Created: time.Date(2016, 11, 02, 17, 34, 31, 0, time.UTC),

View File

@ -6,8 +6,6 @@
package migrations package migrations
import ( import (
"fmt"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/migrations/base"
@ -155,11 +153,6 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
if err != nil { if err != nil {
return err 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 { if err := uploader.CreateIssues(issues...); err != nil {
return err return err
@ -175,11 +168,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
if err != nil { if err != nil {
return err 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...) allComments = append(allComments, comments...)
if len(allComments) >= commentBatchSize { if len(allComments) >= commentBatchSize {
@ -212,11 +201,6 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
return err 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 { if err := uploader.CreatePullRequests(prs...); err != nil {
return err return err
} }
@ -231,11 +215,6 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
if err != nil { if err != nil {
return err 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...) allComments = append(allComments, comments...)

View File

@ -33,6 +33,8 @@ type Issue struct {
URL string `json:"url"` URL string `json:"url"`
Index int64 `json:"number"` Index int64 `json:"number"`
Poster *User `json:"user"` Poster *User `json:"user"`
OriginalAuthor string `json:"original_author"`
OriginalAuthorID int64 `json:"original_author_id"`
Title string `json:"title"` Title string `json:"title"`
Body string `json:"body"` Body string `json:"body"`
Labels []*Label `json:"labels"` Labels []*Label `json:"labels"`

View File

@ -15,6 +15,8 @@ type Comment struct {
PRURL string `json:"pull_request_url"` PRURL string `json:"pull_request_url"`
IssueURL string `json:"issue_url"` IssueURL string `json:"issue_url"`
Poster *User `json:"user"` Poster *User `json:"user"`
OriginalAuthor string `json:"original_author"`
OriginalAuthorID int64 `json:"original_author_id"`
Body string `json:"body"` Body string `json:"body"`
// swagger:strfmt date-time // swagger:strfmt date-time
Created time.Time `json:"created_at"` Created time.Time `json:"created_at"`

View File

@ -31,6 +31,7 @@ type Repository struct {
HTMLURL string `json:"html_url"` HTMLURL string `json:"html_url"`
SSHURL string `json:"ssh_url"` SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"` CloneURL string `json:"clone_url"`
OriginalURL string `json:"original_url"`
Website string `json:"website"` Website string `json:"website"`
Stars int `json:"stars_count"` Stars int `json:"stars_count"`
Forks int `json:"forks_count"` Forks int `json:"forks_count"`

View File

@ -82,6 +82,7 @@ func NewFuncMap() []template.FuncMap {
"FileSize": base.FileSize, "FileSize": base.FileSize,
"Subtract": base.Subtract, "Subtract": base.Subtract,
"EntryIcon": base.EntryIcon, "EntryIcon": base.EntryIcon,
"MigrationIcon": MigrationIcon,
"Add": func(a, b int) int { "Add": func(a, b int) int {
return a + b return a + b
}, },
@ -540,3 +541,13 @@ func TrN(lang string, cnt interface{}, key1, keyN string) string {
} }
return keyN 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"
}
}

View File

@ -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.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.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. 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 mirror_from = mirror of
forked_from = forked from forked_from = forked from

View File

@ -137,6 +137,9 @@ a{cursor:pointer}
.ui .background.purple{background-color:#6e5494!important} .ui .background.purple{background-color:#6e5494!important}
.ui .background.yellow{background-color:#fbbf09!important} .ui .background.yellow{background-color:#fbbf09!important}
.ui .background.gold{background-color:#a1882b!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} .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} @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}
} }

View File

@ -588,6 +588,18 @@ code,
} }
} }
.migrate {
color: #888888 !important;
opacity: 0.5;
a {
color: #444444 !important;
&:hover {
color: #000000 !important;
}
}
}
.branch-tag-choice { .branch-tag-choice {
line-height: 20px; line-height: 20px;
} }

View File

@ -234,8 +234,9 @@
<p class="desc"> <p class="desc">
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }} {{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }}
{{if .OriginalAuthor }}
{{if gt .Poster.ID 0}} {{$.i18n.Tr .GetLastEventLabelFake $timeStr .OriginalAuthor | Safe}}
{{else if gt .Poster.ID 0}}
{{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName | Escape) | Safe}} {{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName | Escape) | Safe}}
{{else}} {{else}}
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}} {{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}}

View File

@ -12,12 +12,20 @@
<div class="twelve wide column comment-list prevent-before-timeline"> <div class="twelve wide column comment-list prevent-before-timeline">
<ui class="ui comments timeline-line"> <ui class="ui comments timeline-line">
<div class="comment"> <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}}> <a class="avatar" {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>
<img src="{{.Issue.Poster.RelAvatarLink}}"> <img src="{{.Issue.Poster.RelAvatarLink}}">
</a> </a>
{{end}}
<div class="content"> <div class="content">
<div class="ui top attached header"> <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> <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}} {{if not $.Repository.IsArchived}}
<div class="ui right actions"> <div class="ui right actions">
{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) }} {{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) }}

View File

@ -9,12 +9,20 @@
22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED --> 22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED -->
{{if eq .Type 0}} {{if eq .Type 0}}
<div class="comment" id="{{.HashTag}}"> <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}}> <a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
<img src="{{.Poster.RelAvatarLink}}"> <img src="{{.Poster.RelAvatarLink}}">
</a> </a>
{{end}}
<div class="content"> <div class="content">
<div class="ui top attached header"> <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> <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}} {{if not $.Repository.IsArchived}}
<div class="ui right actions"> <div class="ui right actions">
{{if gt .ShowTag 0}} {{if gt .ShowTag 0}}

View File

@ -27,16 +27,28 @@
{{if .Issue.IsPull}} {{if .Issue.IsPull}}
{{if .Issue.PullRequest.HasMerged}} {{if .Issue.PullRequest.HasMerged}}
{{ $mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix $.Lang }} {{ $mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix $.Lang }}
{{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> <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> <span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Str2html}}</span>
{{end}}
{{else}}
{{if .Issue.OriginalAuthor }}
{{.Issue.OriginalAuthor}}
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span>
{{else}} {{else}}
<a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a> <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> <span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span>
{{end}} {{end}}
{{end}}
{{else}} {{else}}
{{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.Lang }} {{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.Lang }}
<span class="time-desc"> <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}} {{$.i18n.Tr "repo.issues.opened_by" $createdStr .Issue.Poster.HomeLink (.Issue.Poster.GetDisplayName|Escape) | Safe}}
{{else}} {{else}}
{{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr (.Issue.Poster.GetDisplayName|Escape) | Safe}} {{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr (.Issue.Poster.GetDisplayName|Escape) | Safe}}

View File

@ -6971,6 +6971,15 @@
"type": "string", "type": "string",
"x-go-name": "IssueURL" "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": { "pull_request_url": {
"type": "string", "type": "string",
"x-go-name": "PRURL" "x-go-name": "PRURL"
@ -8669,6 +8678,15 @@
"format": "int64", "format": "int64",
"x-go-name": "Index" "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": { "pull_request": {
"$ref": "#/definitions/PullRequestMeta" "$ref": "#/definitions/PullRequestMeta"
}, },
@ -9489,6 +9507,10 @@
"format": "int64", "format": "int64",
"x-go-name": "OpenIssues" "x-go-name": "OpenIssues"
}, },
"original_url": {
"type": "string",
"x-go-name": "OriginalURL"
},
"owner": { "owner": {
"$ref": "#/definitions/User" "$ref": "#/definitions/User"
}, },

View File

@ -93,7 +93,9 @@
{{end}} {{end}}
<p class="desc"> <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}} {{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName|Escape) | Safe}}
{{else}} {{else}}
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName|Escape) | Safe}} {{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName|Escape) | Safe}}