From de5913d6471614468096c0b99c352c17bb1115bf Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 22 Sep 2026 00:54:27 +0000 Subject: [PATCH 1/7] [skip ci] Updated translations via Crowdin --- options/locale/locale_zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json index fb644f73651..84c538b1a3d 100644 --- a/options/locale/locale_zh-CN.json +++ b/options/locale/locale_zh-CN.json @@ -129,7 +129,7 @@ "confirm_delete_artifact": "您确定要删除产物「%s」吗?", "archived": "已归档", "concept_system_global": "全局", - "concept_user_user": "发送者帐号", + "concept_user_user": "用户", "concept_code_repository": "仓库", "concept_user_organization": "组织", "concept_user_bot": "机器人", From 7637b1b816b7377fd67c81e963c18d8cdbee974a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 22 Sep 2026 22:49:36 +0800 Subject: [PATCH 2/7] chore: refactor StaticRootPath (#39384) When need to use some settings in testing code, always call `SetupGiteaTestEnv` --- modules/setting/server.go | 5 ++--- modules/timeutil/since_test.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/setting/server.go b/modules/setting/server.go index 641e18bdb4c..3ce32d73e43 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -13,6 +13,7 @@ import ( "time" "gitea.dev/modules/log" + "gitea.dev/modules/util" ) // Scheme describes protocol types @@ -272,9 +273,7 @@ func loadServerFrom(rootCfg ConfigProvider) { RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) - if len(StaticRootPath) == 0 { - StaticRootPath = AppWorkPath - } + StaticRootPath = util.IfZero(StaticRootPath, AppWorkPath) StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(StaticRootPath) StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour) AppDataPath = sec.Key("APP_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data")) diff --git a/modules/timeutil/since_test.go b/modules/timeutil/since_test.go index 927265ff191..e5ef72d9b3d 100644 --- a/modules/timeutil/since_test.go +++ b/modules/timeutil/since_test.go @@ -26,7 +26,7 @@ const ( ) func TestMain(m *testing.M) { - setting.StaticRootPath = "../../" + setting.SetupGiteaTestEnv() setting.Names = []string{"english"} setting.Langs = []string{"en-US"} // setup From 8164130349836e75d35471fd056cb449310e5548 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:12:56 +0000 Subject: [PATCH 3/7] fix(markup): raise KaTeX MAX_CHARS limit to 10000 (#39387) --- web_src/js/markup/math.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/markup/math.ts b/web_src/js/markup/math.ts index 69cd164f8d2..a7de30e7de0 100644 --- a/web_src/js/markup/math.ts +++ b/web_src/js/markup/math.ts @@ -21,7 +21,7 @@ export async function initMarkupCodeMath(elMarkup: HTMLElement): Promise { import('katex/dist/katex.css'), ]); - const MAX_CHARS = 1000; + const MAX_CHARS = 10000; const MAX_SIZE = 25; const MAX_EXPAND = 1000; From 6146a4869e07c934e7fa04ff425895e48e005374 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 23 Sep 2026 16:30:58 +0800 Subject: [PATCH 4/7] fix: use clearer message for ldap auth failure (#39392) * fix #34942 log: `user does not exist ...: not in LDAP database or invalid password` --- models/user/error.go | 11 ++++++++--- services/auth/source/ldap/source_authenticate.go | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/models/user/error.go b/models/user/error.go index a0dc1f9172b..21d50ec8040 100644 --- a/models/user/error.go +++ b/models/user/error.go @@ -31,8 +31,9 @@ func (err ErrUserAlreadyExist) Unwrap() error { // ErrUserNotExist represents a "UserNotExist" kind of error. type ErrUserNotExist struct { - UID int64 - Name string + UID int64 + Name string + ExtraMsg string } // IsErrUserNotExist checks if an error is a ErrUserNotExist. @@ -42,7 +43,11 @@ func IsErrUserNotExist(err error) bool { } func (err ErrUserNotExist) Error() string { - return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name) + ret := fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name) + if err.ExtraMsg != "" { + ret += ": " + err.ExtraMsg + } + return ret } // Unwrap unwraps this error as a ErrNotExist error diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index fe735d192f0..c20ba13803f 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -31,8 +31,8 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u } sr := source.SearchEntry(loginName, password, source.AuthSource.Type == auth.DLDAP) if sr == nil { - // User not in LDAP, do nothing - return nil, user_model.ErrUserNotExist{Name: loginName} + // User is not in LDAP database, or password is invalid (direct bind) + return nil, user_model.ErrUserNotExist{Name: loginName, ExtraMsg: "not in LDAP database or invalid password"} } // Fallback. // FIXME: this fallback would cause problems when the "Username" attribute is not set and a user inputs their email. From 08149f9bec9d9c8f3acb980b9e289f058cd29b93 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 23 Sep 2026 16:51:58 +0800 Subject: [PATCH 5/7] refactor: make git http respond error message (#39390) * Refactor some bad smells in legacy code * Fix #37999: instead of creating an empty (undesired) wiki page, just tell users to create a wiki page first --- routers/common/errpage.go | 2 +- routers/web/repo/githttp.go | 128 ++++++++++++++--------- routers/web/repo/githttp_test.go | 42 -------- services/context/context_response.go | 2 +- tests/integration/git_smart_http_test.go | 21 ++-- 5 files changed, 93 insertions(+), 102 deletions(-) delete mode 100644 routers/web/repo/githttp_test.go diff --git a/routers/common/errpage.go b/routers/common/errpage.go index 6152a0c05ce..f010ec95057 100644 --- a/routers/common/errpage.go +++ b/routers/common/errpage.go @@ -68,7 +68,7 @@ func renderPanicErrorPage(w http.ResponseWriter, req *http.Request, recovered an // This recovery handler could be called without Gitea's web context, so we shouldn't touch that context too much. // Otherwise, the 500-page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic. user, _ := ctxData[middleware.ContextDataKeySignedUser].(*user_model.User) - if !setting.IsProd || (user != nil && user.IsAdmin) { + if !setting.IsProd || setting.IsInTesting || (user != nil && user.IsAdmin) { plainMsg = "PANIC: " + combinedErr.Error() ctxData["ErrorMsg"] = plainMsg } diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index ee0ed77a799..da2bf49557e 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -7,9 +7,9 @@ package repo import ( "compress/gzip" "fmt" + "io" "net/http" "os" - "path" "regexp" "slices" "strconv" @@ -25,6 +25,7 @@ import ( "gitea.dev/modules/git" "gitea.dev/modules/git/gitcmd" "gitea.dev/modules/git/gitrepo" + "gitea.dev/modules/httplib" "gitea.dev/modules/log" repo_module "gitea.dev/modules/repository" "gitea.dev/modules/setting" @@ -232,8 +233,8 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler { repo, err = repo_service.PushCreateRepo(ctx, ctx.Doer, owner, repoName) if err != nil { - log.Error("pushCreateRepo: %v", err) - ctx.Status(http.StatusNotFound) + log.Debug("PushCreateRepo: %v", err) + ctx.Status(http.StatusNotFound) // TODO: need to refactor PushCreateRepo and its returned errors return nil } } @@ -281,7 +282,7 @@ func dummyInfoRefs(ctx *context.Context) { WithDir(tmpEmptyRepoDir). RunStdBytes(ctx) if err != nil { - log.Error(fmt.Sprintf("%v - %s", err, string(refs))) + log.Error("Failed to prepare git-receive-pack cache: %v", err) } log.Debug("populating infoRefsCache: \n%s", string(refs)) @@ -292,9 +293,9 @@ func dummyInfoRefs(ctx *context.Context) { ctx.RespHeader().Set("Pragma", "no-cache") ctx.RespHeader().Set("Cache-Control", "no-cache, max-age=0, must-revalidate") ctx.RespHeader().Set("Content-Type", "application/x-git-receive-pack-advertisement") - _, _ = ctx.Write(packetWrite("# service=git-receive-pack\n")) - _, _ = ctx.Write([]byte("0000")) - _, _ = ctx.Write(infoRefsCache) + _ = pktLineWriteText(ctx.Resp, "# service=git-receive-pack") + _ = pktLineWriteFlush(ctx.Resp) + _, _ = ctx.Resp.Write(infoRefsCache) } type serviceHandler struct { @@ -326,29 +327,27 @@ func setHeaderCacheForever(ctx *context.Context) { ctx.Resp.Header().Set("Cache-Control", "public, max-age=31536000") } -func containsParentDirectorySeparator(v string) bool { - if !strings.Contains(v, "..") { - return false - } - return slices.Contains(strings.FieldsFunc(v, isSlashRune), "..") -} - -func isSlashRune(r rune) bool { return r == '/' || r == '\\' } - func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string) { - if containsParentDirectorySeparator(file) { - log.Debug("request file path contains invalid path: %v", file) - ctx.Resp.WriteHeader(http.StatusBadRequest) - return - } - fs := gitrepo.RepoLocalFS(h.getStorageRepo()) ctx.Resp.Header().Set("Content-Type", contentType) - http.ServeFileFS(ctx.Resp, ctx.Req, fs, path.Clean(file)) + relPath := util.PathJoinRelX(file) + http.ServeFileFS(ctx.Resp, ctx.Req, fs, relPath) } // one or more key=value pairs separated by colons -var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`) +var safeGitProtocolHeader = sync.OnceValue(func() *regexp.Regexp { + return regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`) +}) + +func prepareGitCmdEnvs(ctx *context.Context, h *serviceHandler, more ...string) []string { + envs := slices.Clone(os.Environ()) + envs = append(envs, h.environ...) + envs = append(envs, more...) + if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader().MatchString(protocol) { + envs = append(envs, "GIT_PROTOCOL="+protocol) + } + return envs +} func prepareGitCmdWithAllowedService(service string, allowedServices []string) *gitcmd.Command { if !slices.Contains(allowedServices, service) { @@ -404,23 +403,15 @@ func serviceRPC(ctx *context.Context, service string) { } } - // set this for allow pre-receive and post-receive execute - h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service) - - if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) { - h.environ = append(h.environ, "GIT_PROTOCOL="+protocol) - } - + // set SSH_ORIGINAL_COMMAND to allow pre-receive and post-receive hooks + gitCmdEnvs := prepareGitCmdEnvs(ctx, h, "SSH_ORIGINAL_COMMAND="+service) err := cmd.AddArguments("."). - WithRepo(h.getStorageRepo()).WithEnv(append(os.Environ(), h.environ...)). + WithRepo(h.getStorageRepo()).WithEnv(gitCmdEnvs). WithStdinCopy(reqBody). WithStdoutCopy(ctx.Resp). RunWithStderr(ctx) - if err != nil { - if !gitcmd.IsErrorCanceledOrKilled(err) { - repoLogName := h.repo.FullName() + util.Iif(h.isWiki, ".wiki", "") - log.Error("Fail to serve RPC(%s) for repo %s: %v", service, repoLogName, err) - } + if err != nil && !gitcmd.IsErrorCanceledOrKilled(err) && !httplib.IsClientOrNetworkError(ctx, err) { + log.Error("Fail to serve RPC(%s) for repo %s: %v", service, h.getStorageRepo().LogString(), err) } } @@ -444,25 +435,43 @@ func ServiceUploadArchive(ctx *context.Context) { serviceRPC(ctx, ServiceTypeUploadArchive) } -func packetWrite(str string) []byte { - s := strconv.FormatInt(int64(len(str)+4), 16) - if len(s)%4 != 0 { - s = strings.Repeat("0", 4-len(s)%4) + s +func pktLineWriteText(w io.Writer, str string) error { + // https://git-scm.com/docs/gitprotocol-common + prefix := strconv.FormatInt(int64(len(str)+4+1), 16) + if len(prefix)%4 != 0 { + prefix = "0000" + prefix + prefix = prefix[len(prefix)-4:] } - return []byte(s + str) + if _, err := io.WriteString(w, prefix); err != nil { + return err + } + if _, err := io.WriteString(w, str); err != nil { + return err + } + _, err := io.WriteString(w, "\n") + return err +} + +func pktLineWriteFlush(w io.Writer) error { + _, err := io.WriteString(w, "0000") + return err } // GetInfoRefs implements Git dumb HTTP +// ref: https://git-scm.com/docs/gitprotocol-http , https://git-scm.com/docs/gitprotocol-v2 func GetInfoRefs(ctx *context.Context) { h := httpBase(ctx, ctx.FormString("service")) // git http protocol: "?service=git-" if h == nil { return } + + repo := h.getStorageRepo() setHeaderNoCache(ctx) + if h.serviceType == "" { // it's said that some legacy git clients will send requests to "/info/refs" without "service" parameter, // although there should be no such case client in the modern days. TODO: not quite sure why we need this UpdateServerInfo logic - if err := git.UpdateServerInfo(ctx, h.getStorageRepo()); err != nil { + if err := git.UpdateServerInfo(ctx, repo); err != nil { ctx.ServerError("UpdateServerInfo", err) return } @@ -470,28 +479,43 @@ func GetInfoRefs(ctx *context.Context) { return } + gitCmdEnvs := prepareGitCmdEnvs(ctx, h) cmd := prepareGitCmdWithAllowedService(h.serviceType, []string{ServiceTypeUploadPack, ServiceTypeReceivePack}) if cmd == nil { ctx.Resp.WriteHeader(http.StatusBadRequest) return } - if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) { - h.environ = append(h.environ, "GIT_PROTOCOL="+protocol) - } - h.environ = append(os.Environ(), h.environ...) + ctx.Resp.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", h.serviceType)) - cmd = cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").WithEnv(h.environ) - refs, _, err := cmd.WithRepo(h.getStorageRepo()).RunStdBytes(ctx) + repoExists, err := git.IsRepositoryExist(ctx, repo) + if err != nil { + ctx.ServerError("IsRepositoryExist", err) + return + } + + if !repoExists { + ctx.Resp.WriteHeader(http.StatusOK) + // error-line = PKT-LINE("ERR" SP explanation-text) + errMsg := "repository doesn't exist" + if h.isWiki { + errMsg = "wiki doesn't exist, please initialize the wiki by creating a new page first" + } + _ = pktLineWriteText(ctx.Resp, "ERR "+errMsg) + return + } + + cmd = cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").WithEnv(gitCmdEnvs) + refs, _, err := cmd.WithRepo(repo).RunStdBytes(ctx) if err != nil { ctx.ServerError("RunGitServiceAdvertiseRefs", err) return } - ctx.Resp.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", h.serviceType)) + // https://git-scm.com/docs/gitprotocol-pack ctx.Resp.WriteHeader(http.StatusOK) - _, _ = ctx.Resp.Write(packetWrite("# service=git-" + h.serviceType + "\n")) - _, _ = ctx.Resp.Write([]byte("0000")) + _ = pktLineWriteText(ctx.Resp, "# service=git-"+h.serviceType) + _ = pktLineWriteFlush(ctx.Resp) _, _ = ctx.Resp.Write(refs) } diff --git a/routers/web/repo/githttp_test.go b/routers/web/repo/githttp_test.go deleted file mode 100644 index 0164b11f66c..00000000000 --- a/routers/web/repo/githttp_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestContainsParentDirectorySeparator(t *testing.T) { - tests := []struct { - v string - b bool - }{ - { - v: `user2/repo1/info/refs`, - b: false, - }, - { - v: `user2/repo1/HEAD`, - b: false, - }, - { - v: `user2/repo1/some.../strange_file...mp3`, - b: false, - }, - { - v: `user2/repo1/../../custom/conf/app.ini`, - b: true, - }, - { - v: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, - b: true, - }, - } - - for i := range tests { - assert.Equal(t, tests[i].b, containsParentDirectorySeparator(tests[i].v)) - } -} diff --git a/services/context/context_response.go b/services/context/context_response.go index 4d03ae754a9..bd342c43fa4 100644 --- a/services/context/context_response.go +++ b/services/context/context_response.go @@ -155,7 +155,7 @@ func (ctx *Context) notFoundInternal(skip int, logMsg string, logErr error) { func (ctx *Context) buildUserErrorMessage(msg string, err error) (userErrorMsg string) { // it's safe to show internal error to admin users, and it helps - if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) { + if !setting.IsProd || setting.IsInTesting || (ctx.Doer != nil && ctx.Doer.IsAdmin) { userErrorMsg = msg if err != nil { userErrorMsg += ", error: " + err.Error() diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go index df8bc1caeb1..9fec0cf392d 100644 --- a/tests/integration/git_smart_http_test.go +++ b/tests/integration/git_smart_http_test.go @@ -34,9 +34,10 @@ func TestGitSmartHTTP(t *testing.T) { } func testGitSmartHTTP(t *testing.T, u *url.URL) { - kases := []struct { + cases := []struct { method, path string code int + contains string }{ { path: "user2/repo1/info/refs", @@ -51,6 +52,11 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) { path: "user2/repo1/HEAD", code: http.StatusOK, }, + { + path: "user2/repo2.wiki/info/refs?service=git-upload-pack", + code: http.StatusOK, + contains: "ERR wiki doesn't exist", + }, { path: "user2/repo1/objects/info/alternates", code: http.StatusNotFound, @@ -73,17 +79,20 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) { }, } - for _, kase := range kases { - t.Run(kase.path, func(t *testing.T) { - req, err := http.NewRequest(util.IfZero(kase.method, "GET"), u.String()+kase.path, nil) + for _, tc := range cases { + t.Run(tc.path, func(t *testing.T) { + req, err := http.NewRequest(util.IfZero(tc.method, "GET"), u.String()+tc.path, nil) require.NoError(t, err) req.SetBasicAuth("user2", userPassword) resp, err := http.DefaultClient.Do(req) require.NoError(t, err) defer resp.Body.Close() - assert.Equal(t, kase.code, resp.StatusCode) - _, err = io.ReadAll(resp.Body) + assert.Equal(t, tc.code, resp.StatusCode) + respBody, err := io.ReadAll(resp.Body) require.NoError(t, err) + if tc.contains != "" { + assert.Contains(t, string(respBody), tc.contains) + } }) } } From 191287d8be98127b369176acbd2d5114da3cb658 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Wed, 23 Sep 2026 22:34:19 +0800 Subject: [PATCH 6/7] fix(repo): commit page fails to render unsigned commits with a different committer (#39381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since #39229 the commit page header dereferences `.Verification.CommittingUser` when the committer is not the author. `Verification` is `nil` for unsigned commits (see `repo.Diff`), so opening such a commit — a rebased or cherry-picked one, for example — logs a template error and the page comes out truncated: ``` Render failed: failed to render template: repo/commit_page, error: template error: builtin(bindata):repo/commit_page:138:22 : executing "repo/commit_page" at <.Verification.CommittingUser>: nil pointer evaluating interface {}.CommittingUser ``` This guards the access and adds an integration test that creates a commit with distinct author and committer identities and checks the page renders completely (the status stays 200 on a mid-render failure, so the test looks at the body). _The fix was worked out with help from an AI assistant; I reviewed and tested it myself._ --------- Co-authored-by: wxiaoguang --- modules/git/fastimport.go | 31 +++++++++++++++++++++----- modules/git/signature_nogogit.go | 9 +++++++- modules/test/utils.go | 2 +- routers/common/errpage.go | 6 ++++- routers/common/errpage_test.go | 6 ++--- routers/web/repo/pull_review_test.go | 5 +++-- templates/repo/commit_page.tmpl | 2 +- templates/repo/settings/options.tmpl | 3 ++- tests/integration/integration_test.go | 4 ++++ tests/integration/repo_commits_test.go | 22 +++++++++++++++++- 10 files changed, 73 insertions(+), 17 deletions(-) diff --git a/modules/git/fastimport.go b/modules/git/fastimport.go index 048bd4a3e56..6bf0c5e32e9 100644 --- a/modules/git/fastimport.go +++ b/modules/git/fastimport.go @@ -29,6 +29,8 @@ type FastImportCommit struct { Ref string Message string Files []FastImportFile + + Author, Committer *Signature } // ForceFastImportWithInit is for mainly for testing purpose @@ -48,15 +50,32 @@ func ForceFastImportWithInit(ctx context.Context, repoLocalPath string, commits // ForceFastImport is for mainly for testing purpose func ForceFastImport(ctx context.Context, repo RepositoryFacade, commits []FastImportCommit) error { - var buf bytes.Buffer + buf := &bytes.Buffer{} for i, c := range commits { - msg := util.IfZero(c.Message, fmt.Sprintf("commit %d", i+1)) - _, _ = fmt.Fprintf(&buf, "reset %s\n", c.Ref) - _, _ = fmt.Fprintf(&buf, "commit %s\nmark :%d\ncommitter Gitea 1500000000 +0000\n", c.Ref, i+1) - _, _ = fmt.Fprintf(&buf, "data %d\n%s\n", len(msg), msg) + _, _ = fmt.Fprintf(buf, "reset %s\n", c.Ref) + _, _ = fmt.Fprintf(buf, "commit %s\n", c.Ref) + _, _ = fmt.Fprintf(buf, "mark :%d\n", i+1) + + if c.Author != nil { + buf.WriteString("author ") + _ = c.Author.Encode(buf) + buf.WriteByte('\n') + } + if c.Committer != nil { + buf.WriteString("committer ") + _ = c.Committer.Encode(buf) + buf.WriteByte('\n') + } else { + // "committer" is required, so we use a default one if not provided + buf.WriteString("committer Gitea 1500000000 +0000\n") + } + + msg := util.IfZero(c.Message, fmt.Sprintf("test commit %d", i+1)) + _, _ = fmt.Fprintf(buf, "data %d\n%s\n", len(msg), msg) + for _, f := range c.Files { mode := util.IfZero(f.Mode, EntryModeBlob) - _, _ = fmt.Fprintf(&buf, "M %s inline %s\ndata %d\n%s\n", mode.String(), f.Path, len(f.Content), f.Content) + _, _ = fmt.Fprintf(buf, "M %s inline %s\ndata %d\n%s\n", mode.String(), f.Path, len(f.Content), f.Content) } } buf.WriteString("done\n") diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go index d4ddfb23ce8..7a61d8d7eb0 100644 --- a/modules/git/signature_nogogit.go +++ b/modules/git/signature_nogogit.go @@ -8,6 +8,7 @@ package git import ( "fmt" + "io" "time" "gitea.dev/modules/util" @@ -24,7 +25,13 @@ func (s *Signature) String() string { return fmt.Sprintf("%s <%s>", s.Name, s.Email) } -// Decode decodes a byte array representing a signature to signature +// Encode writes the signature for git commit object (same as gogit's object.Signature Encode method) +func (s *Signature) Encode(w io.Writer) error { + _, err := fmt.Fprintf(w, "%s <%s> %d %s", s.Name, s.Email, max(0, s.When.Unix()), s.When.Format("-0700")) + return err +} + +// Decode parses the signature for git commit object (same as gogit's object.Signature Decode method) func (s *Signature) Decode(b []byte) { *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) } diff --git a/modules/test/utils.go b/modules/test/utils.go index 514a9a1141b..fc1211c1e97 100644 --- a/modules/test/utils.go +++ b/modules/test/utils.go @@ -64,7 +64,7 @@ func ParseJSONRedirect(buf []byte) (ret struct { } func IsNormalPageCompleted(s string) bool { - return strings.Contains(s, `