From 45ac90eb54d9f6b6877f68ebd0d84ebd12de170f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 6 Jul 2023 19:18:37 +0800 Subject: [PATCH 1/8] Sync branches when mirroring (#25722) Caused by #22743 --------- Co-authored-by: KN4CK3R --- modules/git/git.go | 1 - services/mirror/mirror_pull.go | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/git/git.go b/modules/git/git.go index f78a496d534c..c9d174e11811 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -188,7 +188,6 @@ func InitFull(ctx context.Context) (err error) { if CheckGitVersionAtLeast("2.9") == nil { globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") } - SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil if setting.LFS.StartServer { diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 53ab632b01c1..51c7de58b645 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -307,6 +307,11 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo return nil, false } + log.Trace("SyncMirrors [repo: %-v]: syncing branches...", m.Repo) + if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, m.Repo, gitRepo, 0); err != nil { + log.Error("SyncMirrors [repo: %-v]: failed to synchronize branches: %v", m.Repo, err) + } + log.Trace("SyncMirrors [repo: %-v]: syncing releases with tags...", m.Repo) if err = repo_module.SyncReleasesWithTags(m.Repo, gitRepo); err != nil { log.Error("SyncMirrors [repo: %-v]: failed to synchronize tags to releases: %v", m.Repo, err) From 5b7b7c4f3cc66c30276d0b172244ac7440419101 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Thu, 6 Jul 2023 23:00:38 +0800 Subject: [PATCH 2/8] Correct permissions for `.ssh` and `authorized_keys` (#25721) Set the correct permissions on the .ssh directory and authorized_keys file, or sshd will refuse to use them and lead to clone/push/pull failures. It could happen when users have copied their data to a new volume and changed the file permission by accident, and it would be very hard to troubleshoot unless users know how to check the logs of sshd which is started by s6. Co-authored-by: Giteabot --- docker/root/etc/s6/gitea/setup | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docker/root/etc/s6/gitea/setup b/docker/root/etc/s6/gitea/setup index d8f6a3b319ee..b801ef4e0354 100755 --- a/docker/root/etc/s6/gitea/setup +++ b/docker/root/etc/s6/gitea/setup @@ -2,7 +2,15 @@ if [ ! -d /data/git/.ssh ]; then mkdir -p /data/git/.ssh - chmod 700 /data/git/.ssh +fi + +# Set the correct permissions on the .ssh directory and authorized_keys file, +# or sshd will refuse to use them and lead to clone/push/pull failures. +# It could happen when users have copied their data to a new volume and changed the file permission by accident, +# and it would be very hard to troubleshoot unless users know how to check the logs of sshd which is started by s6. +chmod 700 /data/git/.ssh +if [ -f /data/git/.ssh/authorized_keys ]; then + chmod 600 /data/git/.ssh/authorized_keys fi if [ ! -f /data/git/.ssh/environment ]; then From f03d95f0a9b76414347f340e6cfa107bcb37846d Mon Sep 17 00:00:00 2001 From: sebastian-sauer Date: Thu, 6 Jul 2023 17:33:04 +0200 Subject: [PATCH 3/8] Allow/fix review (approve/reject) of empty PRs (#25690) gitea allows to create empty PRs. Currently when you need approvals for a merge, you have to manually add /files to the url to get to the files tab to approve / reject the PR. This PR allows to open the files tab via the normal tab / link and then fixes the layout of the files tab. **Screenshots:** Before: ![image](https://github.com/go-gitea/gitea/assets/1135157/b5082e5e-8c32-4412-993e-b854905e96d3) After: ![image](https://github.com/go-gitea/gitea/assets/1135157/1f5e056e-396f-4dfb-8d14-e17a2f6495d9) --------- Co-authored-by: silverwind Co-authored-by: Giteabot --- templates/repo/diff/box.tmpl | 332 ++++++++++++++--------------- templates/repo/pulls/tab_menu.tmpl | 2 +- 2 files changed, 164 insertions(+), 170 deletions(-) diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 8f570e5efcc6..d12042800d2d 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -1,20 +1,7 @@ -{{if .DiffNotAvailable}} -
-
-
- {{template "repo/diff/whitespace_dropdown" .}} - {{template "repo/diff/options_dropdown" .}} - {{if and .PageIsPullFiles $.SignedUserID (not .IsArchived)}} - {{template "repo/diff/new_review" .}} - {{end}} -
-
-
-

{{.locale.Tr "repo.diff.data_not_available"}}

-{{else}} -
-
-
+
+
+
+ {{if not .DiffNotAvailable}}
-
- {{if and .PageIsPullFiles $.SignedUserID (not .IsArchived)}} -
- - -
- {{end}} - {{template "repo/diff/whitespace_dropdown" .}} - {{template "repo/diff/options_dropdown" .}} - {{if and .PageIsPullFiles $.SignedUserID (not .IsArchived)}} - {{template "repo/diff/new_review" .}} - {{end}} -
+ {{end}}
+
+ {{if and .PageIsPullFiles $.SignedUserID (not .IsArchived) (not .DiffNotAvailable)}} +
+ + +
+ {{end}} + {{template "repo/diff/whitespace_dropdown" .}} + {{template "repo/diff/options_dropdown" .}} + {{if and .PageIsPullFiles $.SignedUserID (not .IsArchived)}} + {{template "repo/diff/new_review" .}} + {{end}} +
+
+ {{if not .DiffNotAvailable}}
-
-
- -
- {{range $i, $file := .Diff.Files}} - {{/*notice: the index of Diff.Files should not be used for element ID, because the index will be restarted from 0 when doing load-more for PRs with a lot of files*/}} - {{$blobBase := call $.GetBlobByPathForCommit $.BeforeCommit $file.OldName}} - {{$blobHead := call $.GetBlobByPathForCommit $.HeadCommit $file.Name}} - {{$isImage := or (call $.IsBlobAnImage $blobBase) (call $.IsBlobAnImage $blobHead)}} - {{$isCsv := (call $.IsCsvFile $file)}} - {{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}} - {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} - {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.IsArchived)}} -
-

-
- -
- {{if $file.IsBin}} - - {{$.locale.Tr "repo.diff.bin"}} - - {{else}} - {{template "repo/diff/stats" dict "file" . "root" $}} - {{end}} -
- {{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}{{if .IsLFSFile}} ({{$.locale.Tr "repo.stored_lfs"}}){{end}} - - {{if $file.IsGenerated}} - {{$.locale.Tr "repo.diff.generated"}} - {{end}} - {{if $file.IsVendored}} - {{$.locale.Tr "repo.diff.vendored"}} - {{end}} - {{if and $file.Mode $file.OldMode}} - {{$file.OldMode}} → {{$file.Mode}} - {{else if $file.Mode}} - {{$file.Mode}} - {{end}} -
-
- {{if $showFileViewToggle}} -
- - -
- {{end}} - {{if $file.IsProtected}} - {{$.locale.Tr "repo.diff.protected"}} - {{end}} - {{if and $isReviewFile $file.HasChangedSinceLastReview}} - {{$.locale.Tr "repo.pulls.has_changed_since_last_review"}} - {{end}} - {{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}} - - - {{end}} - {{if and (not $file.IsSubmodule) (not $.PageIsWiki)}} - {{if $file.IsDeleted}} - {{$.locale.Tr "repo.diff.view_file"}} - {{else}} - {{$.locale.Tr "repo.diff.view_file"}} - {{end}} - {{end}} - {{if $isReviewFile}} - + {{end}} +
+ {{if .DiffNotAvailable}} +

{{.locale.Tr "repo.diff.data_not_available"}}

+ {{else}} +
+ +
+ {{range $i, $file := .Diff.Files}} + {{/*notice: the index of Diff.Files should not be used for element ID, because the index will be restarted from 0 when doing load-more for PRs with a lot of files*/}} + {{$blobBase := call $.GetBlobByPathForCommit $.BeforeCommit $file.OldName}} + {{$blobHead := call $.GetBlobByPathForCommit $.HeadCommit $file.Name}} + {{$isImage := or (call $.IsBlobAnImage $blobBase) (call $.IsBlobAnImage $blobHead)}} + {{$isCsv := (call $.IsCsvFile $file)}} + {{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}} + {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} + {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.IsArchived)}} +
+

+
+
-

-
-
- {{if or $file.IsIncomplete $file.IsBin}} -
- {{if $file.IsIncomplete}} - {{if $file.IsIncompleteLineTooLong}} - {{$.locale.Tr "repo.diff.file_suppressed_line_too_long"}} - {{else}} - {{$.locale.Tr "repo.diff.file_suppressed"}} - {{$.locale.Tr "repo.diff.load"}} - {{end}} - {{else}} - {{$.locale.Tr "repo.diff.bin_not_shown"}} - {{end}} -
+ +
+ {{if $file.IsBin}} + + {{$.locale.Tr "repo.diff.bin"}} + {{else}} - - {{if $.IsSplitStyle}} - {{template "repo/diff/section_split" dict "file" . "root" $}} - {{else}} - {{template "repo/diff/section_unified" dict "file" . "root" $}} - {{end}} -
+ {{template "repo/diff/stats" dict "file" . "root" $}} {{end}}
+ {{if $file.IsRenamed}}{{$file.OldName}} → {{end}}{{$file.Name}}{{if .IsLFSFile}} ({{$.locale.Tr "repo.stored_lfs"}}){{end}} + + {{if $file.IsGenerated}} + {{$.locale.Tr "repo.diff.generated"}} + {{end}} + {{if $file.IsVendored}} + {{$.locale.Tr "repo.diff.vendored"}} + {{end}} + {{if and $file.Mode $file.OldMode}} + {{$file.OldMode}} → {{$file.Mode}} + {{else if $file.Mode}} + {{$file.Mode}} + {{end}} +
+
{{if $showFileViewToggle}} - {{/* for image or CSV, it can have a horizontal scroll bar, there won't be review comment context menu (position absolute) which would be clipped by "overflow" */}} -
- - {{if $isImage}} - {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} +
+ + +
+ {{end}} + {{if $file.IsProtected}} + {{$.locale.Tr "repo.diff.protected"}} + {{end}} + {{if and $isReviewFile $file.HasChangedSinceLastReview}} + {{$.locale.Tr "repo.pulls.has_changed_since_last_review"}} + {{end}} + {{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}} + + + {{end}} + {{if and (not $file.IsSubmodule) (not $.PageIsWiki)}} + {{if $file.IsDeleted}} + {{$.locale.Tr "repo.diff.view_file"}} + {{else}} + {{$.locale.Tr "repo.diff.view_file"}} + {{end}} + {{end}} + {{if $isReviewFile}} + + {{end}} + + +
+
+ {{if or $file.IsIncomplete $file.IsBin}} +
+ {{if $file.IsIncomplete}} + {{if $file.IsIncompleteLineTooLong}} + {{$.locale.Tr "repo.diff.file_suppressed_line_too_long"}} {{else}} - {{template "repo/diff/csv_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} + {{$.locale.Tr "repo.diff.file_suppressed"}} + {{$.locale.Tr "repo.diff.load"}} {{end}} -
+ {{else}} + {{$.locale.Tr "repo.diff.bin_not_shown"}} + {{end}}
+ {{else}} + + {{if $.IsSplitStyle}} + {{template "repo/diff/section_split" dict "file" . "root" $}} + {{else}} + {{template "repo/diff/section_unified" dict "file" . "root" $}} + {{end}} +
{{end}}
+ {{if $showFileViewToggle}} + {{/* for image or CSV, it can have a horizontal scroll bar, there won't be review comment context menu (position absolute) which would be clipped by "overflow" */}} +
+ + {{if $isImage}} + {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} + {{else}} + {{template "repo/diff/csv_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} + {{end}} +
+
+ {{end}}
- {{end}} - - {{if .Diff.IsIncomplete}} -
-

- {{$.locale.Tr "repo.diff.too_many_files"}} - {{.locale.Tr "repo.diff.show_more"}} -

-
- {{end}} -
-
+
+ {{end}} - {{if not $.Repository.IsArchived}} - + {{end}} +
{{end}} +

+ {{if and (not $.Repository.IsArchived) (not .DiffNotAvailable)}} + + {{end}} + {{if (not .DiffNotAvailable)}} {{template "repo/issue/view_content/reference_issue_dialog" .}} -
-{{end}} + {{end}} +
diff --git a/templates/repo/pulls/tab_menu.tmpl b/templates/repo/pulls/tab_menu.tmpl index 6c121bdd1f6b..c91d09d9e1ab 100644 --- a/templates/repo/pulls/tab_menu.tmpl +++ b/templates/repo/pulls/tab_menu.tmpl @@ -9,7 +9,7 @@ {{$.locale.Tr "repo.pulls.tab_commits"}} {{if .NumCommits}}{{.NumCommits}}{{else}}-{{end}} - + {{svg "octicon-diff"}} {{$.locale.Tr "repo.pulls.tab_files"}} {{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}} From f0bde0e4f902970d447e3aae628f2dcf6f79e539 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 7 Jul 2023 00:52:41 +0800 Subject: [PATCH 4/8] Simplify the LFS GC logger usage (#25717) Remove unnecessary `if opts.Logger != nil` checks. * For "CLI doctor" mode, output to the console's "logger.Info". * For "Web Task" mode, output to the default "logger.Debug", to avoid flooding the server's log in a busy production instance. Co-authored-by: Giteabot --- modules/doctor/lfs.go | 4 ++-- services/repository/lfs.go | 38 +++++++++++++++++--------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/modules/doctor/lfs.go b/modules/doctor/lfs.go index 64ee4c40bfeb..5f110b8f97d1 100644 --- a/modules/doctor/lfs.go +++ b/modules/doctor/lfs.go @@ -31,8 +31,8 @@ func garbageCollectLFSCheck(ctx context.Context, logger log.Logger, autofix bool } if err := repository.GarbageCollectLFSMetaObjects(ctx, repository.GarbageCollectLFSMetaObjectsOptions{ - Logger: logger, - AutoFix: autofix, + LogDetail: logger.Info, + AutoFix: autofix, // Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload // and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby // an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid diff --git a/services/repository/lfs.go b/services/repository/lfs.go index aeb808a72f33..0bd4d53a5c4d 100644 --- a/services/repository/lfs.go +++ b/services/repository/lfs.go @@ -19,7 +19,7 @@ import ( // GarbageCollectLFSMetaObjectsOptions provides options for GarbageCollectLFSMetaObjects function type GarbageCollectLFSMetaObjectsOptions struct { - Logger log.Logger + LogDetail func(format string, v ...any) AutoFix bool OlderThan time.Time UpdatedLessRecentlyThan time.Time @@ -32,10 +32,12 @@ func GarbageCollectLFSMetaObjects(ctx context.Context, opts GarbageCollectLFSMet log.Trace("Doing: GarbageCollectLFSMetaObjects") defer log.Trace("Finished: GarbageCollectLFSMetaObjects") + if opts.LogDetail == nil { + opts.LogDetail = log.Debug + } + if !setting.LFS.StartServer { - if opts.Logger != nil { - opts.Logger.Info("LFS support is disabled") - } + opts.LogDetail("LFS support is disabled") return nil } @@ -54,21 +56,17 @@ func GarbageCollectLFSMetaObjects(ctx context.Context, opts GarbageCollectLFSMet // GarbageCollectLFSMetaObjectsForRepo garbage collects LFS objects for a specific repository func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.Repository, opts GarbageCollectLFSMetaObjectsOptions) error { - if opts.Logger != nil { - opts.Logger.Info("Checking %-v", repo) - } + opts.LogDetail("Checking %-v", repo) total, orphaned, collected, deleted := int64(0), 0, 0, 0 - if opts.Logger != nil { - defer func() { - if orphaned == 0 { - opts.Logger.Info("Found %d total LFSMetaObjects in %-v", total, repo) - } else if !opts.AutoFix { - opts.Logger.Info("Found %d/%d orphaned LFSMetaObjects in %-v", orphaned, total, repo) - } else { - opts.Logger.Info("Collected %d/%d orphaned/%d total LFSMetaObjects in %-v. %d removed from storage.", collected, orphaned, total, repo, deleted) - } - }() - } + defer func() { + if orphaned == 0 { + opts.LogDetail("Found %d total LFSMetaObjects in %-v", total, repo) + } else if !opts.AutoFix { + opts.LogDetail("Found %d/%d orphaned LFSMetaObjects in %-v", orphaned, total, repo) + } else { + opts.LogDetail("Collected %d/%d orphaned/%d total LFSMetaObjects in %-v. %d removed from storage.", collected, orphaned, total, repo, deleted) + } + }() gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) if err != nil { @@ -129,9 +127,7 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R }) if err == errStop { - if opts.Logger != nil { - opts.Logger.Info("Processing stopped at %d total LFSMetaObjects in %-v", total, repo) - } + opts.LogDetail("Processing stopped at %d total LFSMetaObjects in %-v", total, repo) return nil } else if err != nil { return err From 2af30f715e64dbb0a3900168e3768ffb36c06392 Mon Sep 17 00:00:00 2001 From: puni9869 <80308335+puni9869@users.noreply.github.com> Date: Fri, 7 Jul 2023 00:29:24 +0530 Subject: [PATCH 5/8] Fix inconsistent user profile layout across tabs (#25625) Fix ::User Profile Page Project Tab Have Inconsistent Layout and Style Added the big_avator for consistency in the all header_items tabs. Fixes: #24871 > ### Description > in the user profile page the `Packages` and `Projects` tab have small icons for user but other tabs have bigger profile picture with user info: > > ### Screenshots > ### **For Packages And Projects:** > ![image](https://user-images.githubusercontent.com/25511175/240148601-2420d77b-ba25-4718-9ccb-c5d0d95e3079.png) > > ### **For Other Tabs:** > ![image](https://user-images.githubusercontent.com/25511175/240148461-ce9636b3-fe11-4c46-a230-30d83eee5947.png) > ## Before ![image](https://github.com/go-gitea/gitea/assets/80308335/975ad038-07ca-4b10-b75d-ccf259be7b9d) ## After changes Project View image Packages View image ## Org view for projects page image ## Org view for packages page image --------- Co-authored-by: wxiaoguang Co-authored-by: Giteabot Co-authored-by: silverwind --- models/user/user.go | 2 +- modules/context/org.go | 1 - routers/web/org/projects.go | 1 + routers/web/shared/user/header.go | 106 +++++++++++-- routers/web/user/code.go | 4 +- routers/web/user/home.go | 2 +- routers/web/user/package.go | 2 + routers/web/user/profile.go | 148 +++++------------- templates/code/searchcombo.tmpl | 17 ++ templates/explore/code.tmpl | 19 +-- templates/org/menu.tmpl | 4 +- templates/org/projects/list.tmpl | 29 +++- templates/org/projects/new.tmpl | 3 + templates/org/projects/view.tmpl | 3 + templates/package/settings.tmpl | 3 +- templates/package/shared/list.tmpl | 4 +- templates/package/shared/versionlist.tmpl | 2 - templates/package/view.tmpl | 3 +- templates/projects/list.tmpl | 10 +- templates/projects/new.tmpl | 4 - templates/repo/packages.tmpl | 2 + templates/repo/projects/new.tmpl | 2 + templates/shared/user/org_profile_avatar.tmpl | 16 ++ templates/shared/user/profile_big_avatar.tmpl | 116 ++++++++++++++ templates/user/code.tmpl | 40 ++--- templates/user/overview/header.tmpl | 135 +++++++--------- templates/user/overview/package_versions.tmpl | 27 +++- templates/user/overview/packages.tmpl | 29 +++- templates/user/profile.tmpl | 125 +-------------- web_src/css/helpers.css | 1 + 30 files changed, 461 insertions(+), 399 deletions(-) create mode 100644 templates/code/searchcombo.tmpl create mode 100644 templates/shared/user/org_profile_avatar.tmpl create mode 100644 templates/shared/user/profile_big_avatar.tmpl diff --git a/models/user/user.go b/models/user/user.go index 6f9c2f5b35a8..4b19eda67b61 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -336,7 +336,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO // GetUserFollowing returns range of user's following. func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { - sess := db.GetEngine(db.DefaultContext). + sess := db.GetEngine(ctx). Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). diff --git a/modules/context/org.go b/modules/context/org.go index 355ba0ebd01f..835c761372fa 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -161,7 +161,6 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { } ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember - ctx.Data["IsProjectEnabled"] = true ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsPublicMember"] = func(uid int64) bool { diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 4b33d943b379..21cb23000d2e 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -41,6 +41,7 @@ func MustEnableProjects(ctx *context.Context) { // Projects renders the home page of projects func Projects(ctx *context.Context) { + shared_user.PrepareContextForProfileBigAvatar(ctx) ctx.Data["Title"] = ctx.Tr("repo.project_board") sortType := ctx.FormTrim("sort") diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 9594e6975a8e..516c853b02e3 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -4,35 +4,109 @@ package user import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" ) -func RenderUserHeader(ctx *context.Context) { - ctx.Data["IsProjectEnabled"] = true +// prepareContextForCommonProfile store some common data into context data for user's profile related pages (including the nav menu) +// It is designed to be fast and safe to be called multiple times in one request +func prepareContextForCommonProfile(ctx *context.Context) { ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["ContextUser"] = ctx.ContextUser - tab := ctx.FormString("tab") - ctx.Data["TabName"] = tab - repo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile") - if err == nil && !repo.IsEmpty { - gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) + ctx.Data["EnableFeed"] = setting.Other.EnableFeed + ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() +} + +// PrepareContextForProfileBigAvatar set the context for big avatar view on the profile page +func PrepareContextForProfileBigAvatar(ctx *context.Context) { + prepareContextForCommonProfile(ctx) + + ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx.Doer.ID, ctx.ContextUser.ID) + ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate + + // Show OpenID URIs + openIDs, err := user_model.GetUserOpenIDs(ctx.ContextUser.ID) + if err != nil { + ctx.ServerError("GetUserOpenIDs", err) + return + } + ctx.Data["OpenIDs"] = openIDs + + if len(ctx.ContextUser.Description) != 0 { + content, err := markdown.RenderString(&markup.RenderContext{ + URLPrefix: ctx.Repo.RepoLink, + Metas: map[string]string{"mode": "document"}, + GitRepo: ctx.Repo.GitRepo, + Ctx: ctx, + }, ctx.ContextUser.Description) if err != nil { - ctx.ServerError("OpenRepository", err) + ctx.ServerError("RenderString", err) return } - defer gitRepo.Close() - commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return + ctx.Data["RenderedDescription"] = content + } + + showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) + orgs, err := organization.FindOrgs(organization.FindOrgOptions{ + UserID: ctx.ContextUser.ID, + IncludePrivate: showPrivate, + }) + if err != nil { + ctx.ServerError("FindOrgs", err) + return + } + ctx.Data["Orgs"] = orgs + ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(orgs, ctx.Doer) + + badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser) + if err != nil { + ctx.ServerError("GetUserBadges", err) + return + } + ctx.Data["Badges"] = badges + + // in case the numbers are already provided by other functions, no need to query again (which is slow) + if _, ok := ctx.Data["NumFollowers"]; !ok { + _, ctx.Data["NumFollowers"], _ = user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1}) + } + if _, ok := ctx.Data["NumFollowing"]; !ok { + _, ctx.Data["NumFollowing"], _ = user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1}) + } +} + +func FindUserProfileReadme(ctx *context.Context) (profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) { + profileDbRepo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile") + if err == nil && !profileDbRepo.IsEmpty { + if profileGitRepo, err = git.OpenRepository(ctx, profileDbRepo.RepoPath()); err != nil { + log.Error("FindUserProfileReadme failed to OpenRepository: %v", err) + } else { + if commit, err := profileGitRepo.GetBranchCommit(profileDbRepo.DefaultBranch); err != nil { + log.Error("FindUserProfileReadme failed to GetBranchCommit: %v", err) + } else { + profileReadmeBlob, _ = commit.GetBlobByPath("README.md") + } } - blob, err := commit.GetBlobByPath("README.md") - if err == nil && blob != nil { - ctx.Data["ProfileReadme"] = true + } + return profileGitRepo, profileReadmeBlob, func() { + if profileGitRepo != nil { + _ = profileGitRepo.Close() } } } + +func RenderUserHeader(ctx *context.Context) { + prepareContextForCommonProfile(ctx) + + _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx) + defer profileClose() + ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil +} diff --git a/routers/web/user/code.go b/routers/web/user/code.go index 15524de7d651..033f65c9c06c 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/context" code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/setting" + shared_user "code.gitea.io/gitea/routers/web/shared/user" ) const ( @@ -23,8 +24,9 @@ func CodeSearch(ctx *context.Context) { ctx.Redirect(ctx.ContextUser.HomeLink()) return } + shared_user.PrepareContextForProfileBigAvatar(ctx) + shared_user.RenderUserHeader(ctx) - ctx.Data["IsProjectEnabled"] = true ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["Title"] = ctx.Tr("explore.code") diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 1b0f651b07da..6a89c507a9fd 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -857,7 +857,7 @@ func UsernameSubRoute(ctx *context.Context) { context_service.UserAssignmentWeb()(ctx) if !ctx.Written() { ctx.Data["EnableFeed"] = setting.Other.EnableFeed - Profile(ctx) + OwnerProfile(ctx) } } } diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 551e7f54c854..2e2c2a6e1f9d 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -37,6 +37,7 @@ const ( // ListPackages displays a list of all packages of the context user func ListPackages(ctx *context.Context) { + shared_user.PrepareContextForProfileBigAvatar(ctx) page := ctx.FormInt("page") if page <= 1 { page = 1 @@ -259,6 +260,7 @@ func ViewPackageVersion(ctx *context.Context) { // ListPackageVersions lists all versions of a package func ListPackageVersions(ctx *context.Context) { + shared_user.PrepareContextForProfileBigAvatar(ctx) p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.Params("type")), ctx.Params("name")) if err != nil { if err == packages_model.ErrPackageNotExist { diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 6f9f84d60dbd..442fd0433a72 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -11,22 +11,22 @@ import ( activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/organization" - project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/routers/web/org" + shared_user "code.gitea.io/gitea/routers/web/shared/user" ) -// Profile render user's profile page -func Profile(ctx *context.Context) { +// OwnerProfile render profile page for a user or a organization (aka, repo owner) +func OwnerProfile(ctx *context.Context) { if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { feed.ShowUserFeedRSS(ctx) return @@ -38,36 +38,22 @@ func Profile(ctx *context.Context) { if ctx.ContextUser.IsOrganization() { org.Home(ctx) - return + } else { + userProfile(ctx) } +} +func userProfile(ctx *context.Context) { // check view permissions if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) { ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name)) return } - // advertise feed via meta tag - ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() - - // Show OpenID URIs - openIDs, err := user_model.GetUserOpenIDs(ctx.ContextUser.ID) - if err != nil { - ctx.ServerError("GetUserOpenIDs", err) - return - } - - var isFollowing bool - if ctx.Doer != nil { - isFollowing = user_model.IsFollowing(ctx.Doer.ID, ctx.ContextUser.ID) - } - ctx.Data["Title"] = ctx.ContextUser.DisplayName() ctx.Data["PageIsUserProfile"] = true - ctx.Data["ContextUser"] = ctx.ContextUser - ctx.Data["OpenIDs"] = openIDs - ctx.Data["IsFollowing"] = isFollowing + // prepare heatmap data if setting.Service.EnableUserHeatmap { data, err := activities_model.GetUserHeatmapDataByUser(ctx.ContextUser, ctx.Doer) if err != nil { @@ -78,75 +64,28 @@ func Profile(ctx *context.Context) { ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } - if len(ctx.ContextUser.Description) != 0 { - content, err := markdown.RenderString(&markup.RenderContext{ - URLPrefix: ctx.Repo.RepoLink, - Metas: map[string]string{"mode": "document"}, - GitRepo: ctx.Repo.GitRepo, - Ctx: ctx, - }, ctx.ContextUser.Description) - if err != nil { - ctx.ServerError("RenderString", err) - return - } - ctx.Data["RenderedDescription"] = content - } - - repo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile") - if err == nil && !repo.IsEmpty { - gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) - if err != nil { - ctx.ServerError("OpenRepository", err) - return - } - defer gitRepo.Close() - commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return - } - blob, err := commit.GetBlobByPath("README.md") - if err == nil { - bytes, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize) - if err != nil { - ctx.ServerError("GetBlobContent", err) - return - } - profileContent, err := markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - GitRepo: gitRepo, - }, bytes) - if err != nil { - ctx.ServerError("RenderString", err) - return - } - ctx.Data["ProfileReadme"] = profileContent - } - } + profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx) + defer profileClose() showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) + prepareUserProfileTabData(ctx, showPrivate, profileGitRepo, profileReadmeBlob) + // call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing + shared_user.PrepareContextForProfileBigAvatar(ctx) + ctx.HTML(http.StatusOK, tplProfile) +} - orgs, err := organization.FindOrgs(organization.FindOrgOptions{ - UserID: ctx.ContextUser.ID, - IncludePrivate: showPrivate, - }) - if err != nil { - ctx.ServerError("FindOrgs", err) - return - } - - ctx.Data["Orgs"] = orgs - ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(orgs, ctx.Doer) - - badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser) - if err != nil { - ctx.ServerError("GetUserBadges", err) - return - } - ctx.Data["Badges"] = badges - +func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGitRepo *git.Repository, profileReadme *git.Blob) { + // if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page tab := ctx.FormString("tab") + if tab == "" { + if profileReadme != nil { + tab = "overview" + } else { + tab = "repositories" + } + } ctx.Data["TabName"] = tab + ctx.Data["HasProfileReadme"] = profileReadme != nil page := ctx.FormInt("page") if page <= 0 { @@ -154,12 +93,7 @@ func Profile(ctx *context.Context) { } pagingNum := setting.UI.User.RepoPagingNum - if tab == "activity" { - pagingNum = setting.UI.FeedPagingNum - } - topicOnly := ctx.FormBool("topic") - var ( repos []*repo_model.Repository count int64 @@ -228,6 +162,7 @@ func Profile(ctx *context.Context) { total = int(count) case "activity": date := ctx.FormString("date") + pagingNum = setting.UI.FeedPagingNum items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ RequestedUser: ctx.ContextUser, Actor: ctx.Doer, @@ -271,16 +206,6 @@ func Profile(ctx *context.Context) { } total = int(count) - case "projects": - ctx.Data["OpenProjects"], _, err = project_model.FindProjects(ctx, project_model.SearchOptions{ - Page: -1, - IsClosed: util.OptionalBoolFalse, - Type: project_model.TypeIndividual, - }) - if err != nil { - ctx.ServerError("GetProjects", err) - return - } case "watching": repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ @@ -303,7 +228,17 @@ func Profile(ctx *context.Context) { } total = int(count) - default: + case "overview": + if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { + log.Error("failed to GetBlobContent: %v", err) + } else { + if profileContent, err := markdown.RenderString(&markup.RenderContext{Ctx: ctx, GitRepo: profileGitRepo}, bytes); err != nil { + log.Error("failed to RenderString: %v", err) + } else { + ctx.Data["ProfileReadme"] = profileContent + } + } + default: // default to "repositories" repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: pagingNum, @@ -339,13 +274,6 @@ func Profile(ctx *context.Context) { pager.AddParam(ctx, "date", "Date") } ctx.Data["Page"] = pager - ctx.Data["IsProjectEnabled"] = true - ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled - ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled - - ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate - - ctx.HTML(http.StatusOK, tplProfile) } // Action response for follow/unfollow user request diff --git a/templates/code/searchcombo.tmpl b/templates/code/searchcombo.tmpl new file mode 100644 index 000000000000..e495b3b454cd --- /dev/null +++ b/templates/code/searchcombo.tmpl @@ -0,0 +1,17 @@ +{{template "code/searchform" .}} +
+
+ {{if .CodeIndexerUnavailable}} +
+

{{$.locale.Tr "explore.code_search_unavailable"}}

+
+ {{else if .SearchResults}} +

+ {{.locale.Tr "explore.code_search_results" (.Keyword|Escape) | Str2html}} +

+ {{template "code/searchresults" .}} + {{else if .Keyword}} +
{{$.locale.Tr "explore.code_no_results"}}
+ {{end}} +
+{{template "base/paginate" .}} diff --git a/templates/explore/code.tmpl b/templates/explore/code.tmpl index c537cca05e8d..229857588726 100644 --- a/templates/explore/code.tmpl +++ b/templates/explore/code.tmpl @@ -2,24 +2,7 @@
{{template "explore/navbar" .}}
- {{template "code/searchform" .}} -
-
- {{if .CodeIndexerUnavailable}} -
-

{{$.locale.Tr "explore.code_search_unavailable"}}

-
- {{else if .SearchResults}} -

- {{.locale.Tr "explore.code_search_results" (.Keyword|Escape) | Str2html}} -

- {{template "code/searchresults" .}} - {{else if .Keyword}} -
{{$.locale.Tr "explore.code_no_results"}}
- {{end}} -
- - {{template "base/paginate" .}} + {{template "code/searchcombo" .}}
{{template "base/footer" .}} diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl index 1bb19a0673db..6492e5e668eb 100644 --- a/templates/org/menu.tmpl +++ b/templates/org/menu.tmpl @@ -1,4 +1,4 @@ -
+