From 56bff7ae234ee21d0e4524e401a49385c383ccaf Mon Sep 17 00:00:00 2001 From: Marcell Mars Date: Wed, 20 Nov 2024 15:22:48 +0100 Subject: [PATCH 1/6] Support HTTP POST requests to `/userinfo`, aligning to OpenID Core specification (#32578) This PR adds support for the HTTP POST requests to `/userinfo` endpoint. While the OpenID Core specification says both are supported and recommends using HTTP GET. ref: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo --- routers/web/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/web.go b/routers/web/web.go index 137c677306..b96d06ed66 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -561,7 +561,7 @@ func registerRoutes(m *web.Router) { m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth) }, optSignInIgnoreCsrf, reqSignIn) - m.Methods("GET, OPTIONS", "/userinfo", optionsCorsHandler(), optSignInIgnoreCsrf, auth.InfoOAuth) + m.Methods("GET, POST, OPTIONS", "/userinfo", optionsCorsHandler(), optSignInIgnoreCsrf, auth.InfoOAuth) m.Methods("POST, OPTIONS", "/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), optSignInIgnoreCsrf, auth.AccessTokenOAuth) m.Methods("GET, OPTIONS", "/keys", optionsCorsHandler(), optSignInIgnoreCsrf, auth.OIDCKeys) m.Methods("POST, OPTIONS", "/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), optSignInIgnoreCsrf, auth.IntrospectOAuth) From 407b6e6dfc7ee9ebb8a16c7f1a786e4c24d0516e Mon Sep 17 00:00:00 2001 From: Rowan Bohde Date: Wed, 20 Nov 2024 09:24:09 -0600 Subject: [PATCH 2/6] allow the actions user to login via the jwt token (#32527) We have some actions that leverage the Gitea API that began receiving 401 errors, with a message that the user was not found. These actions use the `ACTIONS_RUNTIME_TOKEN` env var in the actions job to authenticate with the Gitea API. The format of this env var in actions jobs changed with go-gitea/gitea/pull/28885 to be a JWT (with a corresponding update to `act_runner`) Since it was a JWT, the OAuth parsing logic attempted to parse it as an OAuth token, and would return user not found, instead of falling back to look up the running task and assigning it to the actions user. Make ACTIONS_RUNTIME_TOKEN in action runners could be used, attempting to parse Oauth JWTs. The code to parse potential old `ACTION_RUNTIME_TOKEN` was kept in case someone is running an older version of act_runner that doesn't support the Actions JWT. --- models/fixtures/action_task.yml | 19 ++++++++++++ services/actions/auth.go | 11 +++++-- services/auth/oauth2.go | 23 ++++++++++++++ services/auth/oauth2_test.go | 55 +++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 services/auth/oauth2_test.go diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml index 443effe08c..d88a8ed8a9 100644 --- a/models/fixtures/action_task.yml +++ b/models/fixtures/action_task.yml @@ -1,3 +1,22 @@ +- + id: 46 + attempt: 3 + runner_id: 1 + status: 3 # 3 is the status code for "cancelled" + started: 1683636528 + stopped: 1683636626 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4260c64a69a2cc1508825121b7b8394e48e00b1bf8718b2aaaaa + token_salt: eeeeeeee + token_last_eight: eeeeeeee + log_filename: artifact-test2/2f/47.log + log_in_storage: 1 + log_length: 707 + log_size: 90179 + log_expired: 0 - id: 47 job_id: 192 diff --git a/services/actions/auth.go b/services/actions/auth.go index 8e934d89a8..1ef21f6e0e 100644 --- a/services/actions/auth.go +++ b/services/actions/auth.go @@ -83,7 +83,12 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) { return 0, fmt.Errorf("split token failed") } - token, err := jwt.ParseWithClaims(parts[1], &actionsClaims{}, func(t *jwt.Token) (any, error) { + return TokenToTaskID(parts[1]) +} + +// TokenToTaskID returns the TaskID associated with the provided JWT token +func TokenToTaskID(token string) (int64, error) { + parsedToken, err := jwt.ParseWithClaims(token, &actionsClaims{}, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } @@ -93,8 +98,8 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) { return 0, err } - c, ok := token.Claims.(*actionsClaims) - if !token.Valid || !ok { + c, ok := parsedToken.Claims.(*actionsClaims) + if !parsedToken.Valid || !ok { return 0, fmt.Errorf("invalid token claim") } diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go index d0aec085b1..251ae5a244 100644 --- a/services/auth/oauth2.go +++ b/services/auth/oauth2.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/oauth2_provider" ) @@ -54,6 +55,18 @@ func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 { return grant.UserID } +// CheckTaskIsRunning verifies that the TaskID corresponds to a running task +func CheckTaskIsRunning(ctx context.Context, taskID int64) bool { + // Verify the task exists + task, err := actions_model.GetTaskByID(ctx, taskID) + if err != nil { + return false + } + + // Verify that it's running + return task.Status == actions_model.StatusRunning +} + // OAuth2 implements the Auth interface and authenticates requests // (API requests only) by looking for an OAuth token in query parameters or the // "Authorization" header. @@ -97,6 +110,16 @@ func parseToken(req *http.Request) (string, bool) { func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 { // Let's see if token is valid. if strings.Contains(tokenSHA, ".") { + // First attempt to decode an actions JWT, returning the actions user + if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil { + if CheckTaskIsRunning(ctx, taskID) { + store.GetData()["IsActionsToken"] = true + store.GetData()["ActionsTaskID"] = taskID + return user_model.ActionsUserID + } + } + + // Otherwise, check if this is an OAuth access token uid := CheckOAuthAccessToken(ctx, tokenSHA) if uid != 0 { store.GetData()["IsApiToken"] = true diff --git a/services/auth/oauth2_test.go b/services/auth/oauth2_test.go new file mode 100644 index 0000000000..75c231ff7a --- /dev/null +++ b/services/auth/oauth2_test.go @@ -0,0 +1,55 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package auth + +import ( + "context" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/actions" + + "github.com/stretchr/testify/assert" +) + +func TestUserIDFromToken(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("Actions JWT", func(t *testing.T) { + const RunningTaskID = 47 + token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2) + assert.NoError(t, err) + + ds := make(middleware.ContextData) + + o := OAuth2{} + uid := o.userIDFromToken(context.Background(), token, ds) + assert.Equal(t, int64(user_model.ActionsUserID), uid) + assert.Equal(t, ds["IsActionsToken"], true) + assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID)) + }) +} + +func TestCheckTaskIsRunning(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + cases := map[string]struct { + TaskID int64 + Expected bool + }{ + "Running": {TaskID: 47, Expected: true}, + "Missing": {TaskID: 1, Expected: false}, + "Cancelled": {TaskID: 46, Expected: false}, + } + + for name := range cases { + c := cases[name] + t.Run(name, func(t *testing.T) { + actual := CheckTaskIsRunning(context.Background(), c.TaskID) + assert.Equal(t, c.Expected, actual) + }) + } +} From 33850a83fe4ebd23a762a7aac81614c42e303bfa Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 20 Nov 2024 11:26:12 -0800 Subject: [PATCH 3/6] Fix submodule parsing (#32571) Fix #32568, parse `.gitmodules` correctly --------- Co-authored-by: wxiaoguang --- modules/git/commit.go | 66 +---- modules/git/commit_info.go | 2 +- modules/git/commit_info_gogit.go | 4 +- modules/git/commit_info_nogogit.go | 2 +- modules/git/commit_submodule.go | 47 ++++ ...{submodule.go => commit_submodule_file.go} | 24 +- ..._test.go => commit_submodule_file_test.go} | 2 +- modules/git/commit_test.go | 4 +- modules/git/config.go | 187 +++++++++++++++ modules/git/config_submodule.go | 75 ++++++ modules/git/config_submodule_test.go | 49 ++++ modules/git/config_test.go | 66 +++++ modules/git/fsck.go | 14 ++ modules/git/git.go | 227 ++---------------- modules/git/git_test.go | 53 ---- modules/git/repo_base_gogit.go | 4 +- modules/git/repo_base_nogogit.go | 4 +- modules/git/repo_tag_gogit.go | 2 +- modules/git/repo_tag_nogogit.go | 2 +- modules/git/utils.go | 18 +- 20 files changed, 492 insertions(+), 360 deletions(-) create mode 100644 modules/git/commit_submodule.go rename modules/git/{submodule.go => commit_submodule_file.go} (83%) rename modules/git/{submodule_test.go => commit_submodule_file_test.go} (97%) create mode 100644 modules/git/config.go create mode 100644 modules/git/config_submodule.go create mode 100644 modules/git/config_submodule_test.go create mode 100644 modules/git/config_test.go create mode 100644 modules/git/fsck.go diff --git a/modules/git/commit.go b/modules/git/commit.go index 86adaa79a6..010b56948e 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -9,7 +9,6 @@ import ( "bytes" "context" "errors" - "fmt" "io" "os/exec" "strconv" @@ -29,7 +28,7 @@ type Commit struct { Signature *CommitSignature Parents []ObjectID // ID strings - submoduleCache *ObjectCache + submoduleCache *ObjectCache[*SubModule] } // CommitSignature represents a git commit signature part. @@ -357,69 +356,6 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) { return string(bytes), nil } -// GetSubModules get all the sub modules of current revision git tree -func (c *Commit) GetSubModules() (*ObjectCache, error) { - if c.submoduleCache != nil { - return c.submoduleCache, nil - } - - entry, err := c.GetTreeEntryByPath(".gitmodules") - if err != nil { - if _, ok := err.(ErrNotExist); ok { - return nil, nil - } - return nil, err - } - - rd, err := entry.Blob().DataAsync() - if err != nil { - return nil, err - } - - defer rd.Close() - scanner := bufio.NewScanner(rd) - c.submoduleCache = newObjectCache() - var ismodule bool - var path string - for scanner.Scan() { - if strings.HasPrefix(scanner.Text(), "[submodule") { - ismodule = true - continue - } - if ismodule { - fields := strings.Split(scanner.Text(), "=") - k := strings.TrimSpace(fields[0]) - if k == "path" { - path = strings.TrimSpace(fields[1]) - } else if k == "url" { - c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])}) - ismodule = false - } - } - } - if err = scanner.Err(); err != nil { - return nil, fmt.Errorf("GetSubModules scan: %w", err) - } - - return c.submoduleCache, nil -} - -// GetSubModule get the sub module according entryname -func (c *Commit) GetSubModule(entryname string) (*SubModule, error) { - modules, err := c.GetSubModules() - if err != nil { - return nil, err - } - - if modules != nil { - module, has := modules.Get(entryname) - if has { - return module.(*SubModule), nil - } - } - return nil, nil -} - // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') func (c *Commit) GetBranchName() (string, error) { cmd := NewCommand(c.repo.Ctx, "name-rev") diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index c740a4e13e..545081275b 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -7,5 +7,5 @@ package git type CommitInfo struct { Entry *TreeEntry Commit *Commit - SubModuleFile *SubModuleFile + SubModuleFile *CommitSubModuleFile } diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go index 31ffc9aec1..11b44f7c35 100644 --- a/modules/git/commit_info_gogit.go +++ b/modules/git/commit_info_gogit.go @@ -71,7 +71,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath commitsInfo[i].Commit = entryCommit } - // If the entry if a submodule add a submodule file for this + // If the entry is a submodule add a submodule file for this if entry.IsSubModule() { subModuleURL := "" var fullPath string @@ -85,7 +85,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath } else if subModule != nil { subModuleURL = subModule.URL } - subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) + subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) commitsInfo[i].SubModuleFile = subModuleFile } } diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index cfde64a033..20d586f0ff 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -79,7 +79,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath } else if subModule != nil { subModuleURL = subModule.URL } - subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) + subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) commitsInfo[i].SubModuleFile = subModuleFile } } diff --git a/modules/git/commit_submodule.go b/modules/git/commit_submodule.go new file mode 100644 index 0000000000..6603061da2 --- /dev/null +++ b/modules/git/commit_submodule.go @@ -0,0 +1,47 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +// GetSubModules get all the submodules of current revision git tree +func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) { + if c.submoduleCache != nil { + return c.submoduleCache, nil + } + + entry, err := c.GetTreeEntryByPath(".gitmodules") + if err != nil { + if _, ok := err.(ErrNotExist); ok { + return nil, nil + } + return nil, err + } + + rd, err := entry.Blob().DataAsync() + if err != nil { + return nil, err + } + defer rd.Close() + + // at the moment we do not strictly limit the size of the .gitmodules file because some users would have huge .gitmodules files (>1MB) + c.submoduleCache, err = configParseSubModules(rd) + if err != nil { + return nil, err + } + return c.submoduleCache, nil +} + +// GetSubModule get the submodule according entry name +func (c *Commit) GetSubModule(entryName string) (*SubModule, error) { + modules, err := c.GetSubModules() + if err != nil { + return nil, err + } + + if modules != nil { + if module, has := modules.Get(entryName); has { + return module, nil + } + } + return nil, nil +} diff --git a/modules/git/submodule.go b/modules/git/commit_submodule_file.go similarity index 83% rename from modules/git/submodule.go rename to modules/git/commit_submodule_file.go index b99c81582b..bdec35f682 100644 --- a/modules/git/submodule.go +++ b/modules/git/commit_submodule_file.go @@ -15,24 +15,15 @@ import ( var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`) -// SubModule submodule is a reference on git repository -type SubModule struct { - Name string - URL string -} - -// SubModuleFile represents a file with submodule type. -type SubModuleFile struct { - *Commit - +// CommitSubModuleFile represents a file with submodule type. +type CommitSubModuleFile struct { refURL string refID string } -// NewSubModuleFile create a new submodule file -func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile { - return &SubModuleFile{ - Commit: c, +// NewCommitSubModuleFile create a new submodule file +func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile { + return &CommitSubModuleFile{ refURL: refURL, refID: refID, } @@ -109,11 +100,12 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { } // RefURL guesses and returns reference URL. -func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string { +// FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore) +func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string { return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain) } // RefID returns reference ID. -func (sf *SubModuleFile) RefID() string { +func (sf *CommitSubModuleFile) RefID() string { return sf.refID } diff --git a/modules/git/submodule_test.go b/modules/git/commit_submodule_file_test.go similarity index 97% rename from modules/git/submodule_test.go rename to modules/git/commit_submodule_file_test.go index e05f2510c4..473b996b82 100644 --- a/modules/git/submodule_test.go +++ b/modules/git/commit_submodule_file_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGetRefURL(t *testing.T) { +func TestCommitSubModuleFileGetRefURL(t *testing.T) { kases := []struct { refURL string prefixURL string diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index 0ddeb182ef..bf381a5350 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -135,7 +135,7 @@ author KN4CK3R 1711702962 +0100 committer KN4CK3R 1711702962 +0100 encoding ISO-8859-1 gpgsig -----BEGIN PGP SIGNATURE----- - + iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq @@ -150,7 +150,7 @@ gpgsig -----BEGIN PGP SIGNATURE----- -----END PGP SIGNATURE----- ISO-8859-1` - + commitString = strings.ReplaceAll(commitString, "", " ") sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2} gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) assert.NoError(t, err) diff --git a/modules/git/config.go b/modules/git/config.go new file mode 100644 index 0000000000..9c36cf1654 --- /dev/null +++ b/modules/git/config.go @@ -0,0 +1,187 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "fmt" + "os" + "regexp" + "runtime" + "strings" + + "code.gitea.io/gitea/modules/setting" +) + +// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem) +func syncGitConfig() (err error) { + if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil { + return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err) + } + + // first, write user's git config options to git config file + // user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes + for k, v := range setting.GitConfig.Options { + if err = configSet(strings.ToLower(k), v); err != nil { + return err + } + } + + // Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults" + // TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used. + // If these values are not really used, then they can be set (overwritten) directly without considering about existence. + for configKey, defaultValue := range map[string]string{ + "user.name": "Gitea", + "user.email": "gitea@fake.local", + } { + if err := configSetNonExist(configKey, defaultValue); err != nil { + return err + } + } + + // Set git some configurations - these must be set to these values for gitea to work correctly + if err := configSet("core.quotePath", "false"); err != nil { + return err + } + + if DefaultFeatures().CheckVersionAtLeast("2.10") { + if err := configSet("receive.advertisePushOptions", "true"); err != nil { + return err + } + } + + if DefaultFeatures().CheckVersionAtLeast("2.18") { + if err := configSet("core.commitGraph", "true"); err != nil { + return err + } + if err := configSet("gc.writeCommitGraph", "true"); err != nil { + return err + } + if err := configSet("fetch.writeCommitGraph", "true"); err != nil { + return err + } + } + + if DefaultFeatures().SupportProcReceive { + // set support for AGit flow + if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { + return err + } + } else { + if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil { + return err + } + } + + // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user. + // However, some docker users and samba users find it difficult to configure their systems correctly, + // so that Gitea's git repositories are owned by the Gitea user. + // (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.) + // See issue: https://github.com/go-gitea/gitea/issues/19455 + // As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea, + // it is now safe to set "safe.directory=*" for internal usage only. + // Although this setting is only supported by some new git versions, it is also tolerated by earlier versions + if err := configAddNonExist("safe.directory", "*"); err != nil { + return err + } + + if runtime.GOOS == "windows" { + if err := configSet("core.longpaths", "true"); err != nil { + return err + } + if setting.Git.DisableCoreProtectNTFS { + err = configSet("core.protectNTFS", "false") + } else { + err = configUnsetAll("core.protectNTFS", "false") + } + if err != nil { + return err + } + } + + // By default partial clones are disabled, enable them from git v2.22 + if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") { + if err = configSet("uploadpack.allowfilter", "true"); err != nil { + return err + } + err = configSet("uploadpack.allowAnySHA1InWant", "true") + } else { + if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil { + return err + } + err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true") + } + + return err +} + +func configSet(key, value string) error { + stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) + if err != nil && !IsErrorExitCode(err, 1) { + return fmt.Errorf("failed to get git config %s, err: %w", key, err) + } + + currValue := strings.TrimSpace(stdout) + if currValue == value { + return nil + } + + _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) + if err != nil { + return fmt.Errorf("failed to set git global config %s, err: %w", key, err) + } + + return nil +} + +func configSetNonExist(key, value string) error { + _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) + if err == nil { + // already exist + return nil + } + if IsErrorExitCode(err, 1) { + // not exist, set new config + _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) + if err != nil { + return fmt.Errorf("failed to set git global config %s, err: %w", key, err) + } + return nil + } + + return fmt.Errorf("failed to get git config %s, err: %w", key, err) +} + +func configAddNonExist(key, value string) error { + _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) + if err == nil { + // already exist + return nil + } + if IsErrorExitCode(err, 1) { + // not exist, add new config + _, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil) + if err != nil { + return fmt.Errorf("failed to add git global config %s, err: %w", key, err) + } + return nil + } + return fmt.Errorf("failed to get git config %s, err: %w", key, err) +} + +func configUnsetAll(key, value string) error { + _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) + if err == nil { + // exist, need to remove + _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) + if err != nil { + return fmt.Errorf("failed to unset git global config %s, err: %w", key, err) + } + return nil + } + if IsErrorExitCode(err, 1) { + // not exist + return nil + } + return fmt.Errorf("failed to get git config %s, err: %w", key, err) +} diff --git a/modules/git/config_submodule.go b/modules/git/config_submodule.go new file mode 100644 index 0000000000..fe33208850 --- /dev/null +++ b/modules/git/config_submodule.go @@ -0,0 +1,75 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +// SubModule is a reference on git repository +type SubModule struct { + Path string + URL string + Branch string // this field is newly added but not really used +} + +// configParseSubModules this is not a complete parse for gitmodules file, it only +// parses the url and path of submodules. At the moment it only parses well-formed gitmodules files. +// In the future, there should be a complete implementation of https://git-scm.com/docs/git-config#_syntax +func configParseSubModules(r io.Reader) (*ObjectCache[*SubModule], error) { + var subModule *SubModule + subModules := newObjectCache[*SubModule]() + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // Skip empty lines and comments + if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { + continue + } + + // Section header [section] + if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + if subModule != nil { + subModules.Set(subModule.Path, subModule) + } + if strings.HasPrefix(line, "[submodule") { + subModule = &SubModule{} + } else { + subModule = nil + } + continue + } + + if subModule == nil { + continue + } + + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + switch key { + case "path": + subModule.Path = value + case "url": + subModule.URL = value + case "branch": + subModule.Branch = value + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading file: %w", err) + } + if subModule != nil { + subModules.Set(subModule.Path, subModule) + } + return subModules, nil +} diff --git a/modules/git/config_submodule_test.go b/modules/git/config_submodule_test.go new file mode 100644 index 0000000000..f0846c7bfb --- /dev/null +++ b/modules/git/config_submodule_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigSubmodule(t *testing.T) { + input := ` +[core] +path = test + +[submodule "submodule1"] + path = path1 + url = https://gitea.io/foo/foo + #branch = b1 + +[other1] +branch = master + +[submodule "submodule2"] + path = path2 + url = https://gitea.io/bar/bar + branch = b2 + +[other2] +branch = main + +[submodule "submodule3"] + path = path3 + url = https://gitea.io/xxx/xxx +` + + subModules, err := configParseSubModules(strings.NewReader(input)) + assert.NoError(t, err) + assert.Len(t, subModules.cache, 3) + + sm1, _ := subModules.Get("path1") + assert.Equal(t, &SubModule{Path: "path1", URL: "https://gitea.io/foo/foo", Branch: ""}, sm1) + sm2, _ := subModules.Get("path2") + assert.Equal(t, &SubModule{Path: "path2", URL: "https://gitea.io/bar/bar", Branch: "b2"}, sm2) + sm3, _ := subModules.Get("path3") + assert.Equal(t, &SubModule{Path: "path3", URL: "https://gitea.io/xxx/xxx", Branch: ""}, sm3) +} diff --git a/modules/git/config_test.go b/modules/git/config_test.go new file mode 100644 index 0000000000..59f70c99e2 --- /dev/null +++ b/modules/git/config_test.go @@ -0,0 +1,66 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "os" + "strings" + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func gitConfigContains(sub string) bool { + if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { + return strings.Contains(string(b), sub) + } + return false +} + +func TestGitConfig(t *testing.T) { + assert.False(t, gitConfigContains("key-a")) + + assert.NoError(t, configSetNonExist("test.key-a", "val-a")) + assert.True(t, gitConfigContains("key-a = val-a")) + + assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed")) + assert.False(t, gitConfigContains("key-a = val-a-changed")) + + assert.NoError(t, configSet("test.key-a", "val-a-changed")) + assert.True(t, gitConfigContains("key-a = val-a-changed")) + + assert.NoError(t, configAddNonExist("test.key-b", "val-b")) + assert.True(t, gitConfigContains("key-b = val-b")) + + assert.NoError(t, configAddNonExist("test.key-b", "val-2b")) + assert.True(t, gitConfigContains("key-b = val-b")) + assert.True(t, gitConfigContains("key-b = val-2b")) + + assert.NoError(t, configUnsetAll("test.key-b", "val-b")) + assert.False(t, gitConfigContains("key-b = val-b")) + assert.True(t, gitConfigContains("key-b = val-2b")) + + assert.NoError(t, configUnsetAll("test.key-b", "val-2b")) + assert.False(t, gitConfigContains("key-b = val-2b")) + + assert.NoError(t, configSet("test.key-x", "*")) + assert.True(t, gitConfigContains("key-x = *")) + assert.NoError(t, configSetNonExist("test.key-x", "*")) + assert.NoError(t, configUnsetAll("test.key-x", "*")) + assert.False(t, gitConfigContains("key-x = *")) +} + +func TestSyncConfig(t *testing.T) { + oldGitConfig := setting.GitConfig + defer func() { + setting.GitConfig = oldGitConfig + }() + + setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA" + assert.NoError(t, syncGitConfig()) + assert.True(t, gitConfigContains("[sync-test]")) + assert.True(t, gitConfigContains("cfg-key-a = CfgValA")) +} diff --git a/modules/git/fsck.go b/modules/git/fsck.go new file mode 100644 index 0000000000..cec27f165b --- /dev/null +++ b/modules/git/fsck.go @@ -0,0 +1,14 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + "time" +) + +// Fsck verifies the connectivity and validity of the objects in the database +func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error { + return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath}) +} diff --git a/modules/git/git.go b/modules/git/git.go index a19dd7771b..e3e5b83274 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -11,7 +11,6 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "runtime" "strings" "time" @@ -95,17 +94,18 @@ func parseGitVersionLine(s string) (*version.Version, error) { return version.NewVersion(versionString) } -// SetExecutablePath changes the path of git executable and checks the file permission and version. -func SetExecutablePath(path string) error { - // If path is empty, we use the default value of GitExecutable "git" to search for the location of git. - if path != "" { - GitExecutable = path +func checkGitVersionCompatibility(gitVer *version.Version) error { + badVersions := []struct { + Version *version.Version + Reason string + }{ + {version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"}, } - absPath, err := exec.LookPath(GitExecutable) - if err != nil { - return fmt.Errorf("git not found: %w", err) + for _, bad := range badVersions { + if gitVer.Equal(bad.Version) { + return errors.New(bad.Reason) + } } - GitExecutable = absPath return nil } @@ -128,6 +128,20 @@ func ensureGitVersion() error { return nil } +// SetExecutablePath changes the path of git executable and checks the file permission and version. +func SetExecutablePath(path string) error { + // If path is empty, we use the default value of GitExecutable "git" to search for the location of git. + if path != "" { + GitExecutable = path + } + absPath, err := exec.LookPath(GitExecutable) + if err != nil { + return fmt.Errorf("git not found: %w", err) + } + GitExecutable = absPath + return nil +} + // HomeDir is the home dir for git to store the global config file used by Gitea internally func HomeDir() string { if setting.Git.HomePath == "" { @@ -204,196 +218,3 @@ func InitFull(ctx context.Context) (err error) { return syncGitConfig() } - -// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem) -func syncGitConfig() (err error) { - if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil { - return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err) - } - - // first, write user's git config options to git config file - // user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes - for k, v := range setting.GitConfig.Options { - if err = configSet(strings.ToLower(k), v); err != nil { - return err - } - } - - // Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults" - // TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used. - // If these values are not really used, then they can be set (overwritten) directly without considering about existence. - for configKey, defaultValue := range map[string]string{ - "user.name": "Gitea", - "user.email": "gitea@fake.local", - } { - if err := configSetNonExist(configKey, defaultValue); err != nil { - return err - } - } - - // Set git some configurations - these must be set to these values for gitea to work correctly - if err := configSet("core.quotePath", "false"); err != nil { - return err - } - - if DefaultFeatures().CheckVersionAtLeast("2.10") { - if err := configSet("receive.advertisePushOptions", "true"); err != nil { - return err - } - } - - if DefaultFeatures().CheckVersionAtLeast("2.18") { - if err := configSet("core.commitGraph", "true"); err != nil { - return err - } - if err := configSet("gc.writeCommitGraph", "true"); err != nil { - return err - } - if err := configSet("fetch.writeCommitGraph", "true"); err != nil { - return err - } - } - - if DefaultFeatures().SupportProcReceive { - // set support for AGit flow - if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } - } else { - if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } - } - - // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user. - // However, some docker users and samba users find it difficult to configure their systems correctly, - // so that Gitea's git repositories are owned by the Gitea user. - // (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.) - // See issue: https://github.com/go-gitea/gitea/issues/19455 - // As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea, - // it is now safe to set "safe.directory=*" for internal usage only. - // Although this setting is only supported by some new git versions, it is also tolerated by earlier versions - if err := configAddNonExist("safe.directory", "*"); err != nil { - return err - } - - if runtime.GOOS == "windows" { - if err := configSet("core.longpaths", "true"); err != nil { - return err - } - if setting.Git.DisableCoreProtectNTFS { - err = configSet("core.protectNTFS", "false") - } else { - err = configUnsetAll("core.protectNTFS", "false") - } - if err != nil { - return err - } - } - - // By default partial clones are disabled, enable them from git v2.22 - if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") { - if err = configSet("uploadpack.allowfilter", "true"); err != nil { - return err - } - err = configSet("uploadpack.allowAnySHA1InWant", "true") - } else { - if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil { - return err - } - err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true") - } - - return err -} - -func checkGitVersionCompatibility(gitVer *version.Version) error { - badVersions := []struct { - Version *version.Version - Reason string - }{ - {version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"}, - } - for _, bad := range badVersions { - if gitVer.Equal(bad.Version) { - return errors.New(bad.Reason) - } - } - return nil -} - -func configSet(key, value string) error { - stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) - if err != nil && !IsErrorExitCode(err, 1) { - return fmt.Errorf("failed to get git config %s, err: %w", key, err) - } - - currValue := strings.TrimSpace(stdout) - if currValue == value { - return nil - } - - _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) - if err != nil { - return fmt.Errorf("failed to set git global config %s, err: %w", key, err) - } - - return nil -} - -func configSetNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) - if err == nil { - // already exist - return nil - } - if IsErrorExitCode(err, 1) { - // not exist, set new config - _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) - if err != nil { - return fmt.Errorf("failed to set git global config %s, err: %w", key, err) - } - return nil - } - - return fmt.Errorf("failed to get git config %s, err: %w", key, err) -} - -func configAddNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) - if err == nil { - // already exist - return nil - } - if IsErrorExitCode(err, 1) { - // not exist, add new config - _, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil) - if err != nil { - return fmt.Errorf("failed to add git global config %s, err: %w", key, err) - } - return nil - } - return fmt.Errorf("failed to get git config %s, err: %w", key, err) -} - -func configUnsetAll(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) - if err == nil { - // exist, need to remove - _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) - if err != nil { - return fmt.Errorf("failed to unset git global config %s, err: %w", key, err) - } - return nil - } - if IsErrorExitCode(err, 1) { - // not exist - return nil - } - return fmt.Errorf("failed to get git config %s, err: %w", key, err) -} - -// Fsck verifies the connectivity and validity of the objects in the database -func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error { - return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath}) -} diff --git a/modules/git/git_test.go b/modules/git/git_test.go index fc92bebe04..5472842b76 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "os" - "strings" "testing" "code.gitea.io/gitea/modules/setting" @@ -43,58 +42,6 @@ func TestMain(m *testing.M) { } } -func gitConfigContains(sub string) bool { - if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { - return strings.Contains(string(b), sub) - } - return false -} - -func TestGitConfig(t *testing.T) { - assert.False(t, gitConfigContains("key-a")) - - assert.NoError(t, configSetNonExist("test.key-a", "val-a")) - assert.True(t, gitConfigContains("key-a = val-a")) - - assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed")) - assert.False(t, gitConfigContains("key-a = val-a-changed")) - - assert.NoError(t, configSet("test.key-a", "val-a-changed")) - assert.True(t, gitConfigContains("key-a = val-a-changed")) - - assert.NoError(t, configAddNonExist("test.key-b", "val-b")) - assert.True(t, gitConfigContains("key-b = val-b")) - - assert.NoError(t, configAddNonExist("test.key-b", "val-2b")) - assert.True(t, gitConfigContains("key-b = val-b")) - assert.True(t, gitConfigContains("key-b = val-2b")) - - assert.NoError(t, configUnsetAll("test.key-b", "val-b")) - assert.False(t, gitConfigContains("key-b = val-b")) - assert.True(t, gitConfigContains("key-b = val-2b")) - - assert.NoError(t, configUnsetAll("test.key-b", "val-2b")) - assert.False(t, gitConfigContains("key-b = val-2b")) - - assert.NoError(t, configSet("test.key-x", "*")) - assert.True(t, gitConfigContains("key-x = *")) - assert.NoError(t, configSetNonExist("test.key-x", "*")) - assert.NoError(t, configUnsetAll("test.key-x", "*")) - assert.False(t, gitConfigContains("key-x = *")) -} - -func TestSyncConfig(t *testing.T) { - oldGitConfig := setting.GitConfig - defer func() { - setting.GitConfig = oldGitConfig - }() - - setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA" - assert.NoError(t, syncGitConfig()) - assert.True(t, gitConfigContains("[sync-test]")) - assert.True(t, gitConfigContains("cfg-key-a = CfgValA")) -} - func TestParseGitVersion(t *testing.T) { v, err := parseGitVersionLine("git version 2.29.3") assert.NoError(t, err) diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go index a1127f4e6c..0ca1ea79c2 100644 --- a/modules/git/repo_base_gogit.go +++ b/modules/git/repo_base_gogit.go @@ -28,7 +28,7 @@ const isGogit = true type Repository struct { Path string - tagCache *ObjectCache + tagCache *ObjectCache[*Tag] gogitRepo *gogit.Repository gogitStorage *filesystem.Storage @@ -79,7 +79,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { Path: repoPath, gogitRepo: gogitRepo, gogitStorage: storage, - tagCache: newObjectCache(), + tagCache: newObjectCache[*Tag](), Ctx: ctx, objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(), }, nil diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go index 3eb2e2ee6b..477e3b8742 100644 --- a/modules/git/repo_base_nogogit.go +++ b/modules/git/repo_base_nogogit.go @@ -21,7 +21,7 @@ const isGogit = false type Repository struct { Path string - tagCache *ObjectCache + tagCache *ObjectCache[*Tag] gpgSettings *GPGSettings @@ -53,7 +53,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { return &Repository{ Path: repoPath, - tagCache: newObjectCache(), + tagCache: newObjectCache[*Tag](), Ctx: ctx, }, nil } diff --git a/modules/git/repo_tag_gogit.go b/modules/git/repo_tag_gogit.go index 4a7a06e9bd..3e1b4e89ad 100644 --- a/modules/git/repo_tag_gogit.go +++ b/modules/git/repo_tag_gogit.go @@ -72,7 +72,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) { t, ok := repo.tagCache.Get(tagID.String()) if ok { log.Debug("Hit cache: %s", tagID) - tagClone := *t.(*Tag) + tagClone := *t tagClone.Name = name // This is necessary because lightweight tags may have same id return &tagClone, nil } diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go index 8b06a6a1c3..e0a3104249 100644 --- a/modules/git/repo_tag_nogogit.go +++ b/modules/git/repo_tag_nogogit.go @@ -51,7 +51,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) { t, ok := repo.tagCache.Get(tagID.String()) if ok { log.Debug("Hit cache: %s", tagID) - tagClone := *t.(*Tag) + tagClone := *t tagClone.Name = name // This is necessary because lightweight tags may have same id return &tagClone, nil } diff --git a/modules/git/utils.go b/modules/git/utils.go index 53211c6451..56cba9087a 100644 --- a/modules/git/utils.go +++ b/modules/git/utils.go @@ -15,27 +15,25 @@ import ( ) // ObjectCache provides thread-safe cache operations. -type ObjectCache struct { +type ObjectCache[T any] struct { lock sync.RWMutex - cache map[string]any + cache map[string]T } -func newObjectCache() *ObjectCache { - return &ObjectCache{ - cache: make(map[string]any, 10), - } +func newObjectCache[T any]() *ObjectCache[T] { + return &ObjectCache[T]{cache: make(map[string]T, 10)} } -// Set add obj to cache -func (oc *ObjectCache) Set(id string, obj any) { +// Set adds obj to cache +func (oc *ObjectCache[T]) Set(id string, obj T) { oc.lock.Lock() defer oc.lock.Unlock() oc.cache[id] = obj } -// Get get cached obj by id -func (oc *ObjectCache) Get(id string) (any, bool) { +// Get gets cached obj by id +func (oc *ObjectCache[T]) Get(id string) (T, bool) { oc.lock.RLock() defer oc.lock.RUnlock() From 23d0f9083e2d8b735b1b0578c348f73c0e4ae3dc Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Thu, 21 Nov 2024 09:23:50 +0800 Subject: [PATCH 4/6] make search box in issue sidebar dropdown list always show when scrolling (#32576) as title, replace #31597 after #32460 --------- Signed-off-by: a1012112796 <1012112796@qq.com> --- .../repo/issue/sidebar/assignee_list.tmpl | 14 ++++---- templates/repo/issue/sidebar/label_list.tmpl | 36 ++++++++++--------- .../repo/issue/sidebar/milestone_list.tmpl | 36 ++++++++++--------- .../repo/issue/sidebar/project_list.tmpl | 34 +++++++++--------- .../repo/issue/sidebar/reviewer_list.tmpl | 36 ++++++++++--------- web_src/css/repo.css | 6 ++++ 6 files changed, 89 insertions(+), 73 deletions(-) diff --git a/templates/repo/issue/sidebar/assignee_list.tmpl b/templates/repo/issue/sidebar/assignee_list.tmpl index bee6123e52..d8ccd73387 100644 --- a/templates/repo/issue/sidebar/assignee_list.tmpl +++ b/templates/repo/issue/sidebar/assignee_list.tmpl @@ -16,12 +16,14 @@
{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}
- {{range $data.CandidateAssignees}} - - {{svg "octicon-check"}} - {{ctx.AvatarUtils.Avatar . 20}} {{template "repo/search_name" .}} - - {{end}} +
diff --git a/templates/repo/issue/sidebar/label_list.tmpl b/templates/repo/issue/sidebar/label_list.tmpl index ed80047661..526eb1ec04 100644 --- a/templates/repo/issue/sidebar/label_list.tmpl +++ b/templates/repo/issue/sidebar/label_list.tmpl @@ -17,25 +17,27 @@
{{ctx.Locale.Tr "repo.issues.new.clear_labels"}} - {{$previousExclusiveScope := "_no_scope"}} - {{range $data.RepoLabels}} - {{$exclusiveScope := .ExclusiveScope}} - {{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}} -
+ {{end}} diff --git a/templates/repo/issue/sidebar/milestone_list.tmpl b/templates/repo/issue/sidebar/milestone_list.tmpl index 4f2b4cb06f..2d16c6e1b4 100644 --- a/templates/repo/issue/sidebar/milestone_list.tmpl +++ b/templates/repo/issue/sidebar/milestone_list.tmpl @@ -20,25 +20,27 @@
{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}
- {{if $data.OpenMilestones}} -
-
{{ctx.Locale.Tr "repo.issues.new.open_milestone"}}
- {{range $data.OpenMilestones}} - - {{svg "octicon-milestone" 18}} {{.Name}} - + diff --git a/templates/repo/issue/sidebar/project_list.tmpl b/templates/repo/issue/sidebar/project_list.tmpl index ab1243cadd..6ca6156d2c 100644 --- a/templates/repo/issue/sidebar/project_list.tmpl +++ b/templates/repo/issue/sidebar/project_list.tmpl @@ -18,24 +18,26 @@ {{end}}
{{ctx.Locale.Tr "repo.issues.new.clear_projects"}}
- {{if $data.OpenProjects}} -
-
{{ctx.Locale.Tr "repo.issues.new.open_projects"}}
- {{range $data.OpenProjects}} - - {{svg .IconName 18}} {{.Title}} - +
diff --git a/templates/repo/issue/sidebar/reviewer_list.tmpl b/templates/repo/issue/sidebar/reviewer_list.tmpl index e990fc5afc..16eea23d69 100644 --- a/templates/repo/issue/sidebar/reviewer_list.tmpl +++ b/templates/repo/issue/sidebar/reviewer_list.tmpl @@ -17,27 +17,29 @@
{{end}} - {{range $data.Reviewers}} - {{if .User}} - - {{svg "octicon-check"}} - {{ctx.AvatarUtils.Avatar .User 20}} {{template "repo/search_name" .User}} - - {{end}} - {{end}} - {{if $data.TeamReviewers}} - {{if $data.Reviewers}}
{{end}} - {{range $data.TeamReviewers}} - {{if .Team}} - + diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 01ddab97e5..7307b97870 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -66,6 +66,12 @@ overflow-x: auto; } +.issue-content-right .dropdown > .menu .item-secondary-info small { + display: block; + text-overflow: ellipsis; + overflow: hidden; +} + @media (max-width: 767.98px) { .issue-content-left, .issue-content-right { From efb55cd8ef484bbe949e95465b4c17d6e31e7270 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 21 Nov 2024 10:42:37 +0800 Subject: [PATCH 5/6] Supplement and Improvement for #32558 (#32585) Thank you for @wxiaoguang's reminders and suggestions: https://github.com/go-gitea/gitea/pull/32558#discussion_r1849972913 --- options/locale/locale_en-US.ini | 2 +- templates/install.tmpl | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 122b70924a..d75827be5c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -352,7 +352,7 @@ enable_update_checker = Enable Update Checker enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io. env_config_keys = Environment Configuration env_config_keys_prompt = The following environment variables will also be applied to your configuration file: -config_write_file_prompt = These configuration options will be written into: +config_write_file_prompt = These configuration options will be written into: %s [home] nav_menu = Navigation Menu diff --git a/templates/install.tmpl b/templates/install.tmpl index ea4023d409..6c4cc7df01 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -338,7 +338,9 @@
- {{ctx.Locale.Tr "install.config_write_file_prompt"}} {{.CustomConfFile}} + {{$copyBtn := svg "octicon-copy" 14}} + {{$filePath := HTMLFormat `%s ` .CustomConfFile .CustomConfFile $copyBtn}} + {{ctx.Locale.Tr "install.config_write_file_prompt" $filePath}}
From 07373f1d5dd315966bc2f6085a8dd8cca193ec5c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 21 Nov 2024 11:31:54 +0800 Subject: [PATCH 6/6] Improve issue sidebar UI (#32587) 1. remove duplicate dividers 2. align reviewer items 3. merge & remove unused CSS styles Before:
![image](https://github.com/user-attachments/assets/1b3121ee-b5fa-4fe9-b0f2-344d96dc5fbc) ![image](https://github.com/user-attachments/assets/ba8b97e6-114d-488c-adee-48f6c7a3b580)
After:
![image](https://github.com/user-attachments/assets/978eab3e-a5d7-4b68-90ce-079b61994d25) ![image](https://github.com/user-attachments/assets/a8b58a27-dd05-4c8d-be60-816439ce77c6) ![image](https://github.com/user-attachments/assets/b7e6a16c-bf98-4465-a805-9f4a642d366e)
--- templates/repo/issue/sidebar/label_list.tmpl | 2 +- templates/repo/issue/sidebar/milestone_list.tmpl | 3 +-- templates/repo/issue/sidebar/project_list.tmpl | 3 +-- templates/repo/issue/sidebar/reviewer_list.tmpl | 2 +- web_src/css/base.css | 4 ++++ web_src/css/repo.css | 10 ---------- 6 files changed, 8 insertions(+), 16 deletions(-) diff --git a/templates/repo/issue/sidebar/label_list.tmpl b/templates/repo/issue/sidebar/label_list.tmpl index 526eb1ec04..fb8f1a667e 100644 --- a/templates/repo/issue/sidebar/label_list.tmpl +++ b/templates/repo/issue/sidebar/label_list.tmpl @@ -27,7 +27,7 @@ {{$previousExclusiveScope = $exclusiveScope}} {{template "repo/issue/sidebar/label_list_item" dict "Label" .}} {{end}} -
+ {{if and $data.RepoLabels $data.OrgLabels}}
{{end}} {{$previousExclusiveScope = "_no_scope"}} {{range $data.OrgLabels}} {{$exclusiveScope := .ExclusiveScope}} diff --git a/templates/repo/issue/sidebar/milestone_list.tmpl b/templates/repo/issue/sidebar/milestone_list.tmpl index 2d16c6e1b4..a5ed0eef55 100644 --- a/templates/repo/issue/sidebar/milestone_list.tmpl +++ b/templates/repo/issue/sidebar/milestone_list.tmpl @@ -22,7 +22,6 @@
{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}