From dbecdd2be2ec2f9337d4cf07994e303d1db79145 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 6 May 2024 10:01:22 +0800 Subject: [PATCH 01/16] Have time.js use UTC-related getters/setters (#30857) (#30869) Backport #30857 by kemzeb Co-authored-by: Kemal Zebari <60799661+kemzeb@users.noreply.github.com> Co-authored-by: Sam Fisher (cherry picked from commit 2252a7bf84c26aee0dfa1b1b826dba148f507a3a) --- web_src/js/components/RepoCodeFrequency.vue | 2 +- web_src/js/components/RepoContributors.vue | 2 +- web_src/js/components/RepoRecentCommits.vue | 2 +- web_src/js/utils/time.js | 37 ++++++++++++--------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/web_src/js/components/RepoCodeFrequency.vue b/web_src/js/components/RepoCodeFrequency.vue index adce431264..1d40d6d417 100644 --- a/web_src/js/components/RepoCodeFrequency.vue +++ b/web_src/js/components/RepoCodeFrequency.vue @@ -67,7 +67,7 @@ export default { const weekValues = Object.values(this.data); const start = weekValues[0].week; const end = firstStartDateAfterDate(new Date()); - const startDays = startDaysBetween(new Date(start), new Date(end)); + const startDays = startDaysBetween(start, end); this.data = fillEmptyStartDaysWithZeroes(startDays, this.data); this.errorText = ''; } else { diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue index 2347c41ae4..f7b05831e0 100644 --- a/web_src/js/components/RepoContributors.vue +++ b/web_src/js/components/RepoContributors.vue @@ -114,7 +114,7 @@ export default { const weekValues = Object.values(total.weeks); this.xAxisStart = weekValues[0].week; this.xAxisEnd = firstStartDateAfterDate(new Date()); - const startDays = startDaysBetween(new Date(this.xAxisStart), new Date(this.xAxisEnd)); + const startDays = startDaysBetween(this.xAxisStart, this.xAxisEnd); total.weeks = fillEmptyStartDaysWithZeroes(startDays, total.weeks); this.xAxisMin = this.xAxisStart; this.xAxisMax = this.xAxisEnd; diff --git a/web_src/js/components/RepoRecentCommits.vue b/web_src/js/components/RepoRecentCommits.vue index 502af533da..8759978e78 100644 --- a/web_src/js/components/RepoRecentCommits.vue +++ b/web_src/js/components/RepoRecentCommits.vue @@ -62,7 +62,7 @@ export default { const data = await response.json(); const start = Object.values(data)[0].week; const end = firstStartDateAfterDate(new Date()); - const startDays = startDaysBetween(new Date(start), new Date(end)); + const startDays = startDaysBetween(start, end); this.data = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52); this.errorText = ''; } else { diff --git a/web_src/js/utils/time.js b/web_src/js/utils/time.js index 1848792c98..7c7eabd1a3 100644 --- a/web_src/js/utils/time.js +++ b/web_src/js/utils/time.js @@ -1,25 +1,30 @@ import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc.js'; import {getCurrentLocale} from '../utils.js'; -// Returns an array of millisecond-timestamps of start-of-week days (Sundays) -export function startDaysBetween(startDate, endDate) { - // Ensure the start date is a Sunday - while (startDate.getDay() !== 0) { - startDate.setDate(startDate.getDate() + 1); - } +dayjs.extend(utc); - const start = dayjs(startDate); - const end = dayjs(endDate); - const startDays = []; +/** + * Returns an array of millisecond-timestamps of start-of-week days (Sundays) + * + * @param startConfig The start date. Can take any type that `Date` accepts. + * @param endConfig The end date. Can take any type that `Date` accepts. + */ +export function startDaysBetween(startDate, endDate) { + const start = dayjs.utc(startDate); + const end = dayjs.utc(endDate); let current = start; + + // Ensure the start date is a Sunday + while (current.day() !== 0) { + current = current.add(1, 'day'); + } + + const startDays = []; while (current.isBefore(end)) { startDays.push(current.valueOf()); - // we are adding 7 * 24 hours instead of 1 week because we don't want - // date library to use local time zone to calculate 1 week from now. - // local time zone is problematic because of daylight saving time (dst) - // used on some countries - current = current.add(7 * 24, 'hour'); + current = current.add(1, 'week'); } return startDays; @@ -29,10 +34,10 @@ export function firstStartDateAfterDate(inputDate) { if (!(inputDate instanceof Date)) { throw new Error('Invalid date'); } - const dayOfWeek = inputDate.getDay(); + const dayOfWeek = inputDate.getUTCDay(); const daysUntilSunday = 7 - dayOfWeek; const resultDate = new Date(inputDate.getTime()); - resultDate.setDate(resultDate.getDate() + daysUntilSunday); + resultDate.setUTCDate(resultDate.getUTCDate() + daysUntilSunday); return resultDate.valueOf(); } From 1582b1e83a4da7dc0443bfd32299fa8c3c0955b9 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 6 May 2024 23:06:45 +0800 Subject: [PATCH 02/16] Get repo list with OrderBy alpha should respect owner too (#30784) (#30875) Backport #30784 by @6543 instead of: - zowner/gcode - awesome/nul - zowner/nul - zowner/zzz we will get: - awesome/nul - zowner/gcode - zowner/nul - zowner/zzz Co-authored-by: 6543 <6543@obermui.de> (cherry picked from commit cfe6779d4eb2f3869357768fe58863642f79c5a9) --- models/repo/search.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/repo/search.go b/models/repo/search.go index 4d64acf8cf..54d6dcfb44 100644 --- a/models/repo/search.go +++ b/models/repo/search.go @@ -8,14 +8,14 @@ import "code.gitea.io/gitea/models/db" // SearchOrderByMap represents all possible search order var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{ "asc": { - "alpha": db.SearchOrderByAlphabetically, + "alpha": "owner_name ASC, name ASC", "created": db.SearchOrderByOldest, "updated": db.SearchOrderByLeastUpdated, "size": db.SearchOrderBySize, "id": db.SearchOrderByID, }, "desc": { - "alpha": db.SearchOrderByAlphabeticallyReverse, + "alpha": "owner_name DESC, name DESC", "created": db.SearchOrderByNewest, "updated": db.SearchOrderByRecentUpdated, "size": db.SearchOrderBySizeReverse, From 220594baccdc0dccefc772bf081ae14750c1b3ba Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 7 May 2024 08:33:28 +0800 Subject: [PATCH 03/16] Make "sync branch" also sync object format and add tests (#30878) (#30880) Backport #30878 by wxiaoguang Co-authored-by: wxiaoguang (cherry picked from commit ad5a8d043c6818c0c496ebae2f5ea9373219bcd6) --- modules/git/repo.go | 27 --------------------------- modules/repository/branch.go | 10 ++++++++++ modules/repository/branch_test.go | 31 +++++++++++++++++++++++++++++++ services/repository/adopt.go | 4 ++++ 4 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 modules/repository/branch_test.go diff --git a/modules/git/repo.go b/modules/git/repo.go index e8a7016d99..857424fcd4 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -7,7 +7,6 @@ package git import ( "bytes" "context" - "errors" "fmt" "io" "net/url" @@ -63,32 +62,6 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool { return err == nil } -// GetObjectFormatOfRepo returns the hash type of repository at a given path -func GetObjectFormatOfRepo(ctx context.Context, repoPath string) (ObjectFormat, error) { - var stdout, stderr strings.Builder - - err := NewCommand(ctx, "hash-object", "--stdin").Run(&RunOpts{ - Dir: repoPath, - Stdout: &stdout, - Stderr: &stderr, - Stdin: &strings.Reader{}, - }) - if err != nil { - return nil, err - } - - if stderr.Len() > 0 { - return nil, errors.New(stderr.String()) - } - - h, err := NewIDFromString(strings.TrimRight(stdout.String(), "\n")) - if err != nil { - return nil, err - } - - return h.Type(), nil -} - // InitRepository initializes a new Git repository. func InitRepository(ctx context.Context, repoPath string, bare bool, objectFormatName string) error { err := os.MkdirAll(repoPath, os.ModePerm) diff --git a/modules/repository/branch.go b/modules/repository/branch.go index e448490f4a..a3fca7c7ce 100644 --- a/modules/repository/branch.go +++ b/modules/repository/branch.go @@ -5,6 +5,7 @@ package repository import ( "context" + "fmt" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" @@ -36,6 +37,15 @@ func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) } func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) { + objFmt, err := gitRepo.GetObjectFormat() + if err != nil { + return 0, fmt.Errorf("GetObjectFormat: %w", err) + } + _, err = db.GetEngine(ctx).ID(repo.ID).Update(&repo_model.Repository{ObjectFormatName: objFmt.Name()}) + if err != nil { + return 0, fmt.Errorf("UpdateRepository: %w", err) + } + allBranches := container.Set[string]{} { branches, _, err := gitRepo.GetBranchNames(0, 0) diff --git a/modules/repository/branch_test.go b/modules/repository/branch_test.go new file mode 100644 index 0000000000..acf75a1ac0 --- /dev/null +++ b/modules/repository/branch_test.go @@ -0,0 +1,31 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestSyncRepoBranches(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + _, err := db.GetEngine(db.DefaultContext).ID(1).Update(&repo_model.Repository{ObjectFormatName: "bad-fmt"}) + assert.NoError(t, db.TruncateBeans(db.DefaultContext, &git_model.Branch{})) + assert.NoError(t, err) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + assert.Equal(t, "bad-fmt", repo.ObjectFormatName) + _, err = SyncRepoBranches(db.DefaultContext, 1, 0) + assert.NoError(t, err) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + assert.Equal(t, "sha1", repo.ObjectFormatName) + branch, err := git_model.GetBranch(db.DefaultContext, 1, "master") + assert.NoError(t, err) + assert.EqualValues(t, "master", branch.Name) +} diff --git a/services/repository/adopt.go b/services/repository/adopt.go index b337eac38a..572b20ec91 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -195,6 +195,10 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r } defer gitRepo.Close() + if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, 0); err != nil { + return fmt.Errorf("SyncRepoBranches: %w", err) + } + if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { return fmt.Errorf("SyncReleasesWithTags: %w", err) } From e5f9482745ebca2c3f764215183b82827e2076db Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 7 May 2024 21:59:00 +0800 Subject: [PATCH 04/16] Fix missing migrate actions artifacts (#30874) (#30886) Backport #30874 by @lunny The actions artifacts should be able to be migrate to the new storage place. Co-authored-by: Lunny Xiao (cherry picked from commit 216c8eada3c0727288dc5565ae9fdd798b17c463) --- cmd/migrate_storage.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index aa49445a89..357416fc33 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -34,7 +34,7 @@ var CmdMigrateStorage = &cli.Command{ Name: "type", Aliases: []string{"t"}, Value: "", - Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'", + Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts", }, &cli.StringFlag{ Name: "storage", @@ -160,6 +160,13 @@ func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) er }) } +func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error { + return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error { + _, err := storage.Copy(dstStorage, artifact.ArtifactPath, storage.ActionsArtifacts, artifact.ArtifactPath) + return err + }) +} + func runMigrateStorage(ctx *cli.Context) error { stdCtx, cancel := installSignals() defer cancel() @@ -223,13 +230,14 @@ func runMigrateStorage(ctx *cli.Context) error { } migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{ - "attachments": migrateAttachments, - "lfs": migrateLFS, - "avatars": migrateAvatars, - "repo-avatars": migrateRepoAvatars, - "repo-archivers": migrateRepoArchivers, - "packages": migratePackages, - "actions-log": migrateActionsLog, + "attachments": migrateAttachments, + "lfs": migrateLFS, + "avatars": migrateAvatars, + "repo-avatars": migrateRepoAvatars, + "repo-archivers": migrateRepoArchivers, + "packages": migratePackages, + "actions-log": migrateActionsLog, + "actions-artifacts": migrateActionsArtifacts, } tp := strings.ToLower(ctx.String("type")) From 99bd29f02f7ae3f8f6c99299a2aea2f83aeb86d4 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 7 May 2024 18:35:02 +0200 Subject: [PATCH 05/16] Repository explore alphabetically order respect owner name (#30882) similar to #30784 but only for the repo explore page is covered by #30876 for the main branch (cherry picked from commit d410e2acce22e5b3518a9bf64a9152b32a91fe18) --- routers/web/explore/repo.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index cf7381512b..0ec4dbbfe3 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -71,9 +71,9 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { case "leastupdate": orderBy = db.SearchOrderByLeastUpdated case "reversealphabetically": - orderBy = db.SearchOrderByAlphabeticallyReverse + orderBy = "owner_name DESC, name DESC" case "alphabetically": - orderBy = db.SearchOrderByAlphabetically + orderBy = "owner_name ASC, name ASC" case "reversesize": orderBy = db.SearchOrderBySizeReverse case "size": From 7e8177518412f1d600210a981bb5435bffdbe3b4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 7 May 2024 15:36:48 +0800 Subject: [PATCH 06/16] Move database operations of merging a pull request to post receive hook and add a transaction (#30805) Merging PR may fail because of various problems. The pull request may have a dirty state because there is no transaction when merging a pull request. ref https://github.com/go-gitea/gitea/pull/25741#issuecomment-2074126393 This PR moves all database update operations to post-receive handler for merging a pull request and having a database transaction. That means if database operations fail, then the git merging will fail, the git client will get a fail result. There are already many tests for pull request merging, so we don't need to add a new one. --------- Co-authored-by: wxiaoguang (cherry picked from commit ebf0c969403d91ed80745ff5bd7dfbdb08174fc7) Conflicts: modules/private/hook.go routers/private/hook_post_receive.go trivial conflicts because 263a716cb5 * Performance optimization for git push (#30104) was not cherry-picked and because of 998a431747a15cc95f7056a2029b736551eb037b Do not update PRs based on events that happened before they existed (cherry picked from commit eb792d9f8a4c6972f5a4cfea6e9cb643b1d6a7ce) (cherry picked from commit ec3f5f9992d7ff8250c044a4467524d53bd50210) --- cmd/hook.go | 3 ++ modules/private/hook.go | 2 + modules/repository/env.go | 8 +++ routers/private/hook_post_receive.go | 63 +++++++++++++++++++++++ routers/private/hook_post_receive_test.go | 49 ++++++++++++++++++ services/contexttest/context_tests.go | 13 +++++ services/pull/merge.go | 27 ++++------ services/pull/update.go | 3 +- 8 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 routers/private/hook_post_receive_test.go diff --git a/cmd/hook.go b/cmd/hook.go index 04df7ce18c..bb86713c46 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -366,6 +366,7 @@ Forgejo or set your environment appropriately.`, "") isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) repoName := os.Getenv(repo_module.EnvRepoName) pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) + prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) pusherName := os.Getenv(repo_module.EnvPusherName) hookOptions := private.HookOptions{ @@ -375,6 +376,8 @@ Forgejo or set your environment appropriately.`, "") GitObjectDirectory: os.Getenv(private.GitObjectDirectory), GitQuarantinePath: os.Getenv(private.GitQuarantinePath), GitPushOptions: pushOptions(), + PullRequestID: prID, + PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)), } oldCommitIDs := make([]string, hookBatchSize) newCommitIDs := make([]string, hookBatchSize) diff --git a/modules/private/hook.go b/modules/private/hook.go index cab8c81224..1d0ef4e3a9 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" ) @@ -53,6 +54,7 @@ type HookOptions struct { GitQuarantinePath string GitPushOptions GitPushOptions PullRequestID int64 + PushTrigger repository.PushTrigger DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user. IsWiki bool ActionPerm int diff --git a/modules/repository/env.go b/modules/repository/env.go index 30edd1c9e3..e4f32092fc 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -25,11 +25,19 @@ const ( EnvKeyID = "GITEA_KEY_ID" // public key ID EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID" EnvPRID = "GITEA_PR_ID" + EnvPushTrigger = "GITEA_PUSH_TRIGGER" EnvIsInternal = "GITEA_INTERNAL_PUSH" EnvAppURL = "GITEA_ROOT_URL" EnvActionPerm = "GITEA_ACTION_PERM" ) +type PushTrigger string + +const ( + PushTriggerPRMergeToBase PushTrigger = "pr-merge-to-base" + PushTriggerPRUpdateWithBase PushTrigger = "pr-update-with-base" +) + // InternalPushingEnvironment returns an os environment to switch off hooks on push // It is recommended to avoid using this unless you are pushing within a transaction // or if you absolutely are sure that post-receive and pre-receive will do nothing diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 2558ffe1ab..6e754f4acc 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -4,20 +4,26 @@ package private import ( + "context" "fmt" "net/http" "strconv" "time" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" + pull_model "code.gitea.io/gitea/models/pull" repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" + timeutil "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" gitea_context "code.gitea.io/gitea/services/context" @@ -157,6 +163,14 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { } } + // handle pull request merging, a pull request action should push at least 1 commit + if opts.PushTrigger == repo_module.PushTriggerPRMergeToBase { + handlePullRequestMerging(ctx, opts, ownerName, repoName, updates) + if ctx.Written() { + return + } + } + // Handle Push Options if len(opts.GitPushOptions) > 0 { // load the repository @@ -304,3 +318,52 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { RepoWasEmpty: wasEmpty, }) } + +func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) { + return cache.GetWithContextCache(ctx, "hook_post_receive_user", id, func() (*user_model.User, error) { + return user_model.GetUserByID(ctx, id) + }) +} + +// handlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit +func handlePullRequestMerging(ctx *gitea_context.PrivateContext, opts *private.HookOptions, ownerName, repoName string, updates []*repo_module.PushUpdateOptions) { + if len(updates) == 0 { + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Pushing a merged PR (pr:%d) no commits pushed ", opts.PullRequestID), + }) + return + } + + pr, err := issues_model.GetPullRequestByID(ctx, opts.PullRequestID) + if err != nil { + log.Error("GetPullRequestByID[%d]: %v", opts.PullRequestID, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "GetPullRequestByID failed"}) + return + } + + pusher, err := loadContextCacheUser(ctx, opts.UserID) + if err != nil { + log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Load pusher user failed"}) + return + } + + pr.MergedCommitID = updates[len(updates)-1].NewCommitID + pr.MergedUnix = timeutil.TimeStampNow() + pr.Merger = pusher + pr.MergerID = pusher.ID + err = db.WithTx(ctx, func(ctx context.Context) error { + // Removing an auto merge pull and ignore if not exist + if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) { + return fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", opts.PullRequestID, err) + } + if _, err := pr.SetMerged(ctx); err != nil { + return fmt.Errorf("SetMerged failed: %s/%s Error: %v", ownerName, repoName, err) + } + return nil + }) + if err != nil { + log.Error("Failed to update PR to merged: %v", err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Failed to update PR to merged"}) + } +} diff --git a/routers/private/hook_post_receive_test.go b/routers/private/hook_post_receive_test.go new file mode 100644 index 0000000000..658557d3cf --- /dev/null +++ b/routers/private/hook_post_receive_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + pull_model "code.gitea.io/gitea/models/pull" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/private" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestHandlePullRequestMerging(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + pr, err := issues_model.GetUnmergedPullRequest(db.DefaultContext, 1, 1, "branch2", "master", issues_model.PullRequestFlowGithub) + assert.NoError(t, err) + assert.NoError(t, pr.LoadBaseRepo(db.DefaultContext)) + + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + err = pull_model.ScheduleAutoMerge(db.DefaultContext, user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr") + assert.NoError(t, err) + + autoMerge := unittest.AssertExistsAndLoadBean(t, &pull_model.AutoMerge{PullID: pr.ID}) + + ctx, resp := contexttest.MockPrivateContext(t, "/") + handlePullRequestMerging(ctx, &private.HookOptions{ + PullRequestID: pr.ID, + UserID: 2, + }, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, []*repo_module.PushUpdateOptions{ + {NewCommitID: "01234567"}, + }) + assert.Equal(t, 0, len(resp.Body.String())) + pr, err = issues_model.GetPullRequestByID(db.DefaultContext, pr.ID) + assert.NoError(t, err) + assert.True(t, pr.HasMerged) + assert.EqualValues(t, "01234567", pr.MergedCommitID) + + unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{ID: autoMerge.ID}) +} diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go index 7bfab2ed16..073af213a2 100644 --- a/services/contexttest/context_tests.go +++ b/services/contexttest/context_tests.go @@ -86,6 +86,19 @@ func MockAPIContext(t *testing.T, reqPath string) (*context.APIContext, *httptes return ctx, resp } +func MockPrivateContext(t *testing.T, reqPath string) (*context.PrivateContext, *httptest.ResponseRecorder) { + resp := httptest.NewRecorder() + req := mockRequest(t, reqPath) + base, baseCleanUp := context.NewBaseContext(resp, req) + base.Data = middleware.GetContextData(req.Context()) + base.Locale = &translation.MockLocale{} + ctx := &context.PrivateContext{Base: base} + _ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later + chiCtx := chi.NewRouteContext() + ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx) + return ctx, resp +} + // LoadRepo load a repo into a test context. func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) { var doer *user_model.User diff --git a/services/pull/merge.go b/services/pull/merge.go index 2989d77c6a..3cb54d1b04 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -18,7 +18,6 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" - pull_model "code.gitea.io/gitea/models/pull" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -168,12 +167,6 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U pullWorkingPool.CheckIn(fmt.Sprint(pr.ID)) defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID)) - // Removing an auto merge pull and ignore if not exist - // FIXME: is this the correct point to do this? Shouldn't this be after IsMergeStyleAllowed? - if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) { - return err - } - prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests) if err != nil { log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err) @@ -190,17 +183,15 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U AddTestPullRequestTask(ctx, doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "", 0) }() - pr.MergedCommitID, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message) + _, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message, repo_module.PushTriggerPRMergeToBase) if err != nil { return err } - pr.MergedUnix = timeutil.TimeStampNow() - pr.Merger = doer - pr.MergerID = doer.ID - - if _, err := pr.SetMerged(ctx); err != nil { - log.Error("SetMerged %-v: %v", pr, err) + // reload pull request because it has been updated by post receive hook + pr, err = issues_model.GetPullRequestByID(ctx, pr.ID) + if err != nil { + return err } if err := pr.LoadIssue(ctx); err != nil { @@ -251,7 +242,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U } // doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository -func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) (string, error) { +func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) { // Clone base repo. mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID) if err != nil { @@ -324,11 +315,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use pr.BaseRepo.Name, pr.ID, ) + + mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger)) pushCmd := git.NewCommand(ctx, "push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch) // Push back to upstream. - // TODO: this cause an api call to "/api/internal/hook/post-receive/...", - // that prevents us from doint the whole merge in one db transaction + // This cause an api call to "/api/internal/hook/post-receive/...", + // If it's merge, all db transaction and operations should be there but not here to prevent deadlock. if err := pushCmd.Run(mergeCtx.RunOpts()); err != nil { if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") { return "", &git.ErrPushOutOfDate{ diff --git a/services/pull/update.go b/services/pull/update.go index 1de125eb4d..cc1b5fd83f 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -15,6 +15,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/repository" ) // Update updates pull request with base branch. @@ -72,7 +73,7 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model. BaseBranch: pr.HeadBranch, } - _, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message) + _, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message, repository.PushTriggerPRUpdateWithBase) defer func() { AddTestPullRequestTask(ctx, doer, reversePR.HeadRepo.ID, reversePR.HeadBranch, false, "", "", 0) From 8f8d85da47290aaeaca1303062c7243cb34d1fdf Mon Sep 17 00:00:00 2001 From: Giteabot Date: Wed, 8 May 2024 23:12:37 +0800 Subject: [PATCH 07/16] Fix wrong transfer hint (#30889) (#30900) Backport #30889 by @lunny Fix #30187 Co-authored-by: Lunny Xiao (cherry picked from commit 271e8748a2035ebc836cc2d1e03f4e68b063697e) --- routers/web/repo/setting/setting.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index e2c6a9902e..a7d4e75ff6 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -798,6 +798,7 @@ func SettingsPost(ctx *context.Context) { ctx.Repo.GitRepo = nil } + oldFullname := repo.FullName() if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil { if errors.Is(err, user_model.ErrBlockedByUser) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_blocked_doer"), tplSettingsOptions, nil) @@ -812,8 +813,13 @@ func SettingsPost(ctx *context.Context) { return } - log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner) - ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName())) + if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer { + log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner) + ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName())) + } else { + log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed")) + } ctx.Redirect(repo.Link() + "/settings") case "cancel_transfer": From c6d2c1805251de71169dea9156e5ce9dc7165908 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Sat, 30 Mar 2024 20:36:28 +0300 Subject: [PATCH 08/16] Remove jQuery class from the project page (#30183) - Switched from jQuery class functions to plain JavaScript `classList` - Tested the edit column modal functionality and it works as before Signed-off-by: Yarden Shoham (cherry picked from commit b535c6ca7b9e8c4bcf5637091ee5ad6d9c807c31) (cherry picked from commit 702f112602f5070961338eaa385880c5bb68db68) --- web_src/js/features/repo-projects.js | 45 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/web_src/js/features/repo-projects.js b/web_src/js/features/repo-projects.js index d9ae85a8d2..80e945a0f2 100644 --- a/web_src/js/features/repo-projects.js +++ b/web_src/js/features/repo-projects.js @@ -94,47 +94,46 @@ async function initRepoProjectSortable() { } export function initRepoProject() { - if (!$('.repository.projects').length) { + if (!document.querySelector('.repository.projects')) { return; } const _promise = initRepoProjectSortable(); - $('.edit-project-column-modal').each(function () { - const $projectHeader = $(this).closest('.project-column-header'); - const $projectTitleLabel = $projectHeader.find('.project-column-title'); - const $projectTitleInput = $(this).find('.project-column-title-input'); - const $projectColorInput = $(this).find('#new_project_column_color'); - const $boardColumn = $(this).closest('.project-column'); + for (const modal of document.getElementsByClassName('edit-project-column-modal')) { + const projectHeader = modal.closest('.project-column-header'); + const projectTitleLabel = projectHeader?.querySelector('.project-column-title'); + const projectTitleInput = modal.querySelector('.project-column-title-input'); + const projectColorInput = modal.querySelector('#new_project_column_color'); + const boardColumn = modal.closest('.project-column'); + const bgColor = boardColumn?.style.backgroundColor; - const bgColor = $boardColumn[0].style.backgroundColor; if (bgColor) { - setLabelColor($projectHeader, rgbToHex(bgColor)); + setLabelColor(projectHeader, rgbToHex(bgColor)); } - $(this).find('.edit-project-column-button').on('click', async function (e) { + modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) { e.preventDefault(); - try { - await PUT($(this).data('url'), { + await PUT(this.getAttribute('data-url'), { data: { - title: $projectTitleInput.val(), - color: $projectColorInput.val(), + title: projectTitleInput?.value, + color: projectColorInput?.value, }, }); } catch (error) { console.error(error); } finally { - $projectTitleLabel.text($projectTitleInput.val()); - $projectTitleInput.closest('form').removeClass('dirty'); - if ($projectColorInput.val()) { - setLabelColor($projectHeader, $projectColorInput.val()); + projectTitleLabel.textContent = projectTitleInput?.value; + projectTitleInput.closest('form')?.classList.remove('dirty'); + if (projectColorInput?.value) { + setLabelColor(projectHeader, projectColorInput.value); } - $boardColumn[0].style = `background: ${$projectColorInput.val()} !important`; + boardColumn.style = `background: ${projectColorInput.value} !important`; $('.ui.modal').modal('hide'); } }); - }); + } $('.default-project-column-modal').each(function () { const $boardColumn = $(this).closest('.project-column'); @@ -187,9 +186,11 @@ export function initRepoProject() { function setLabelColor(label, color) { const {r, g, b} = tinycolor(color).toRgb(); if (useLightTextOnBackground(r, g, b)) { - label.removeClass('dark-label').addClass('light-label'); + label.classList.remove('dark-label'); + label.classList.add('light-label'); } else { - label.removeClass('light-label').addClass('dark-label'); + label.classList.remove('light-label'); + label.classList.add('dark-label'); } } From 9934931f1ff4093936b757186bd5d36b9a511c75 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 7 Apr 2024 18:19:25 +0200 Subject: [PATCH 09/16] [PORT] gitea##30237: Fix and rewrite contrast color calculation, fix project-related bugs 1. The previous color contrast calculation function was incorrect at least for the `#84b6eb` where it output low-contrast white instead of black. I've rewritten these functions now to accept hex colors and to match GitHub's calculation and to output pure white/black for maximum contrast. Before and after: Screenshot 2024-04-02 at 01 53 46Screenshot 2024-04-02 at 01 51 30 2. Fix project-related issues: - Expose the new `ContrastColor` function as template helper and use it for project cards, replacing the previous JS solution which eliminates a flash of wrong color on page load. - Fix a bug where if editing a project title, the counter would get lost. - Move `rgbToHex` function to color utils. Co-authored-by: delvh Co-authored-by: Giteabot --- Conflict resolution: Trivial. (cherry picked from commit 36887ed3921d03f1864360c95bd2ecf853bfbe72) (cherry picked from commit f6c0c39f1aef167bb14375a009cf463c6bf031fb) --- modules/templates/helper.go | 6 +-- modules/templates/util_render.go | 12 ++--- modules/util/color.go | 42 +++++++--------- modules/util/color_test.go | 46 +++++++++--------- templates/projects/view.tmpl | 8 ++-- web_src/css/features/projects.css | 27 +++++------ web_src/css/repo.css | 15 +++++- web_src/css/repo/issue-list.css | 17 ------- web_src/css/themes/theme-gitea-dark.css | 2 - web_src/css/themes/theme-gitea-light.css | 2 - web_src/js/components/ContextPopup.vue | 24 ++++------ web_src/js/features/repo-projects.js | 61 ++++++++---------------- web_src/js/utils/color.js | 30 ++++++------ web_src/js/utils/color.test.js | 39 +++++++-------- 14 files changed, 136 insertions(+), 195 deletions(-) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index c9799a38ec..3558dcf94c 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -53,13 +53,13 @@ func NewFuncMap() template.FuncMap { "JsonUtils": NewJsonUtils, // ----------------------------------------------------------------- - // svg / avatar / icon + // svg / avatar / icon / color "svg": svg.RenderHTML, "EntryIcon": base.EntryIcon, "MigrationIcon": MigrationIcon, "ActionIcon": ActionIcon, - - "SortArrow": SortArrow, + "SortArrow": SortArrow, + "ContrastColor": util.ContrastColor, // ----------------------------------------------------------------- // time / number / format diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 5446741287..c4c5376afd 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -135,16 +135,9 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML { var ( archivedCSSClass string - textColor = "#111" + textColor = util.ContrastColor(label.Color) labelScope = label.ExclusiveScope() ) - r, g, b := util.HexToRBGColor(label.Color) - - // Determine if label text should be light or dark to be readable on background color - // this doesn't account for saturation or transparency - if util.UseLightTextOnBackground(r, g, b) { - textColor = "#eee" - } description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) @@ -168,7 +161,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m // Make scope and item background colors slightly darker and lighter respectively. // More contrast needed with higher luminance, empirically tweaked. - luminance := util.GetLuminance(r, g, b) + luminance := util.GetRelativeLuminance(label.Color) contrast := 0.01 + luminance*0.03 // Ensure we add the same amount of contrast also near 0 and 1. darken := contrast + math.Max(luminance+contrast-1.0, 0.0) @@ -178,6 +171,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0) opacity := GetLabelOpacityByte(label.IsArchived()) + r, g, b := util.HexToRBGColor(label.Color) scopeBytes := []byte{ uint8(math.Min(math.Round(r*darkenFactor), 255)), uint8(math.Min(math.Round(g*darkenFactor), 255)), diff --git a/modules/util/color.go b/modules/util/color.go index 240b045c28..9c520dce78 100644 --- a/modules/util/color.go +++ b/modules/util/color.go @@ -4,22 +4,10 @@ package util import ( "fmt" - "math" "strconv" "strings" ) -// Check similar implementation in web_src/js/utils/color.js and keep synchronization - -// Return R, G, B values defined in reletive luminance -func getLuminanceRGB(channel float64) float64 { - sRGB := channel / 255 - if sRGB <= 0.03928 { - return sRGB / 12.92 - } - return math.Pow((sRGB+0.055)/1.055, 2.4) -} - // Get color as RGB values in 0..255 range from the hex color string (with or without #) func HexToRBGColor(colorString string) (float64, float64, float64) { hexString := colorString @@ -47,19 +35,23 @@ func HexToRBGColor(colorString string) (float64, float64, float64) { return r, g, b } -// return luminance given RGB channels -// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance -func GetLuminance(r, g, b float64) float64 { - R := getLuminanceRGB(r) - G := getLuminanceRGB(g) - B := getLuminanceRGB(b) - luminance := 0.2126*R + 0.7152*G + 0.0722*B - return luminance +// Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance +// Keep this in sync with web_src/js/utils/color.js +func GetRelativeLuminance(color string) float64 { + r, g, b := HexToRBGColor(color) + return (0.2126729*r + 0.7151522*g + 0.0721750*b) / 255 } -// Reference from: https://firsching.ch/github_labels.html -// In the future WCAG 3 APCA may be a better solution. -// Check if text should use light color based on RGB of background -func UseLightTextOnBackground(r, g, b float64) bool { - return GetLuminance(r, g, b) < 0.453 +func UseLightText(backgroundColor string) bool { + return GetRelativeLuminance(backgroundColor) < 0.453 +} + +// Given a background color, returns a black or white foreground color that the highest +// contrast ratio. In the future, the APCA contrast function, or CSS `contrast-color` will be better. +// https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42 +func ContrastColor(backgroundColor string) string { + if UseLightText(backgroundColor) { + return "#fff" + } + return "#000" } diff --git a/modules/util/color_test.go b/modules/util/color_test.go index d96ac36730..be6e6b122a 100644 --- a/modules/util/color_test.go +++ b/modules/util/color_test.go @@ -33,33 +33,31 @@ func Test_HexToRBGColor(t *testing.T) { } } -func Test_UseLightTextOnBackground(t *testing.T) { +func Test_UseLightText(t *testing.T) { cases := []struct { - r float64 - g float64 - b float64 - expected bool + color string + expected string }{ - {215, 58, 74, true}, - {0, 117, 202, true}, - {207, 211, 215, false}, - {162, 238, 239, false}, - {112, 87, 255, true}, - {0, 134, 114, true}, - {228, 230, 105, false}, - {216, 118, 227, true}, - {255, 255, 255, false}, - {43, 134, 133, true}, - {43, 135, 134, true}, - {44, 135, 134, true}, - {59, 182, 179, true}, - {124, 114, 104, true}, - {126, 113, 108, true}, - {129, 112, 109, true}, - {128, 112, 112, true}, + {"#d73a4a", "#fff"}, + {"#0075ca", "#fff"}, + {"#cfd3d7", "#000"}, + {"#a2eeef", "#000"}, + {"#7057ff", "#fff"}, + {"#008672", "#fff"}, + {"#e4e669", "#000"}, + {"#d876e3", "#000"}, + {"#ffffff", "#000"}, + {"#2b8684", "#fff"}, + {"#2b8786", "#fff"}, + {"#2c8786", "#000"}, + {"#3bb6b3", "#000"}, + {"#7c7268", "#fff"}, + {"#7e716c", "#fff"}, + {"#81706d", "#fff"}, + {"#807070", "#fff"}, + {"#84b6eb", "#000"}, } for n, c := range cases { - result := UseLightTextOnBackground(c.r, c.g, c.b) - assert.Equal(t, c.expected, result, "case %d: error should match", n) + assert.Equal(t, c.expected, ContrastColor(c.color), "case %d: error should match", n) } } diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index d89750862e..861c7ef5a9 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -66,13 +66,13 @@
{{range .Columns}} -
+
{{.NumIssues ctx}}
- {{.Title}} + {{.Title}}
{{if $canWriteProject}} {{end}}
- -
- +
{{range (index $.IssuesMap .ID)}}
diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index 30df994c38..ff2a65e03a 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -22,34 +22,27 @@ cursor: default; } +.project-column .issue-card { + color: var(--color-text); +} + .project-column-header { display: flex; align-items: center; justify-content: space-between; } -.project-column-header.dark-label { - color: var(--color-project-board-dark-label) !important; -} - -.project-column-header.dark-label .project-column-title { - color: var(--color-project-board-dark-label) !important; -} - -.project-column-header.light-label { - color: var(--color-project-board-light-label) !important; -} - -.project-column-header.light-label .project-column-title { - color: var(--color-project-board-light-label) !important; -} - .project-column-title { background: none !important; line-height: 1.25 !important; cursor: inherit; } +.project-column-title, +.project-column-issue-count { + color: inherit !important; +} + .project-column > .cards { flex: 1; display: flex; @@ -64,6 +57,8 @@ .project-column > .divider { margin: 5px 0; + border-color: currentcolor; + opacity: .5; } .project-column:first-child { diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 3c43c0b079..3c65a0500e 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -2461,8 +2461,21 @@ td .commit-summary { height: 0.5em; } +.labels-list { + display: flex; + flex-wrap: wrap; + gap: 0.25em; +} + +.labels-list a { + display: flex; + text-decoration: none; +} + .labels-list .label { - margin: 2px 0; + padding: 0 6px; + margin: 0 !important; + min-height: 20px; display: inline-flex !important; line-height: 1.3; /* there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly */ } diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css index d19421fcbc..37090f71b4 100644 --- a/web_src/css/repo/issue-list.css +++ b/web_src/css/repo/issue-list.css @@ -69,23 +69,6 @@ } } -#issue-list .flex-item-title .labels-list { - display: flex; - flex-wrap: wrap; - gap: 0.25em; -} - -#issue-list .flex-item-title .labels-list a { - display: flex; - text-decoration: none; -} - -#issue-list .flex-item-title .labels-list .label { - padding: 0 6px; - margin: 0; - min-height: 20px; -} - #issue-list .flex-item-body .branches { display: inline-flex; } diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css index ed6718e40c..c74f334c2d 100644 --- a/web_src/css/themes/theme-gitea-dark.css +++ b/web_src/css/themes/theme-gitea-dark.css @@ -215,8 +215,6 @@ --color-placeholder-text: var(--color-text-light-3); --color-editor-line-highlight: var(--color-primary-light-5); --color-project-board-bg: var(--color-secondary-light-2); - --color-project-board-dark-label: #0e1011; - --color-project-board-light-label: #dde0e2; --color-caret: var(--color-text); /* should ideally be --color-text-dark, see #15651 */ --color-reaction-bg: #e8e8ff12; --color-reaction-hover-bg: var(--color-primary-light-4); diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css index b10ad7d840..01dd8ba4f7 100644 --- a/web_src/css/themes/theme-gitea-light.css +++ b/web_src/css/themes/theme-gitea-light.css @@ -215,8 +215,6 @@ --color-placeholder-text: var(--color-text-light-3); --color-editor-line-highlight: var(--color-primary-light-6); --color-project-board-bg: var(--color-secondary-light-4); - --color-project-board-dark-label: #0e1114; - --color-project-board-light-label: #eaeef2; --color-caret: var(--color-text-dark); --color-reaction-bg: #0000170a; --color-reaction-hover-bg: var(--color-primary-light-5); diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue index ac6a8f3bb6..70b12dcb28 100644 --- a/web_src/js/components/ContextPopup.vue +++ b/web_src/js/components/ContextPopup.vue @@ -1,7 +1,6 @@