From 4674aea25b54baf08594c54f061dee9e44190f02 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Thu, 18 Jan 2024 19:27:07 +0900
Subject: [PATCH 01/22] Fix display latest sync time for pull mirrors on the
repo page (#28841)
Follow #28712
1. Missing Locale word `mirror_sync`
2. Maybe forgot checking the conflict from #27760
Before:

After:

---
options/locale/locale_en-US.ini | 1 +
templates/repo/header.tmpl | 15 +++++++--------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 90e3ac503a..6f9ba8c884 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -987,6 +987,7 @@ mirror_prune = Prune
mirror_prune_desc = Remove obsolete remote-tracking references
mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable periodic sync. (Minimum interval: %s)
mirror_interval_invalid = The mirror interval is not valid.
+mirror_sync = synced
mirror_sync_on_commit = Sync when commits are pushed
mirror_address = Clone From URL
mirror_address_desc = Put any required credentials in the Authorization section.
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index c362059ef3..a5ef8daa9a 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -28,13 +28,6 @@
{{svg "octicon-repo-template" 18}}
{{end}}
- {{if $.PullMirror}}
-
- {{ctx.Locale.Tr "repo.mirror_from"}}
-
{{$.PullMirror.RemoteAddress}}
- {{if $.PullMirror.UpdatedUnix}}{{ctx.Locale.Tr "repo.mirror_sync"}} {{TimeSinceUnix $.PullMirror.UpdatedUnix ctx.Locale}}{{end}}
-
- {{end}}
{{if not (or .IsBeingCreated .IsBroken)}}
@@ -147,7 +140,13 @@
{{end}}
- {{if $.PullMirror}}{{end}}
+ {{if $.PullMirror}}
+
+ {{ctx.Locale.Tr "repo.mirror_from"}}
+
{{$.PullMirror.RemoteAddress}}
+ {{if $.PullMirror.UpdatedUnix}}{{ctx.Locale.Tr "repo.mirror_sync"}} {{TimeSinceUnix $.PullMirror.UpdatedUnix ctx.Locale}}{{end}}
+
+ {{end}}
{{if .IsFork}}{{end}}
{{if .IsGenerated}}{{end}}
From b60a7c3358cdeec3e0a731b68be33b6ba63a6563 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Fri, 19 Jan 2024 11:45:23 +0900
Subject: [PATCH 02/22] Return `responseText` instead of string in some
functions (#28836)
Follow
https://github.com/go-gitea/gitea/pull/28796#issuecomment-1891727591
---
cmd/actions.go | 2 +-
cmd/keys.go | 2 +-
cmd/mailer.go | 2 +-
modules/private/actions.go | 8 ++------
modules/private/hook.go | 6 +++---
modules/private/key.go | 10 +++-------
modules/private/mail.go | 8 ++------
modules/private/request.go | 10 +++++-----
8 files changed, 18 insertions(+), 30 deletions(-)
diff --git a/cmd/actions.go b/cmd/actions.go
index 275fd7904e..f582c16c81 100644
--- a/cmd/actions.go
+++ b/cmd/actions.go
@@ -50,6 +50,6 @@ func runGenerateActionsRunnerToken(c *cli.Context) error {
if extra.HasError() {
return handleCliResponseExtra(extra)
}
- _, _ = fmt.Printf("%s\n", respText)
+ _, _ = fmt.Printf("%s\n", respText.Text)
return nil
}
diff --git a/cmd/keys.go b/cmd/keys.go
index 9d5278f109..ceeec48486 100644
--- a/cmd/keys.go
+++ b/cmd/keys.go
@@ -78,6 +78,6 @@ func runKeys(c *cli.Context) error {
if extra.Error != nil {
return extra.Error
}
- _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString))
+ _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
return nil
}
diff --git a/cmd/mailer.go b/cmd/mailer.go
index 646330e85a..0c5f2c8c8d 100644
--- a/cmd/mailer.go
+++ b/cmd/mailer.go
@@ -45,6 +45,6 @@ func runSendMail(c *cli.Context) error {
if extra.HasError() {
return handleCliResponseExtra(extra)
}
- _, _ = fmt.Printf("Sent %s email(s) to all users\n", respText)
+ _, _ = fmt.Printf("Sent %s email(s) to all users\n", respText.Text)
return nil
}
diff --git a/modules/private/actions.go b/modules/private/actions.go
index a22833632e..311a283650 100644
--- a/modules/private/actions.go
+++ b/modules/private/actions.go
@@ -14,16 +14,12 @@ type GenerateTokenRequest struct {
}
// GenerateActionsRunnerToken calls the internal GenerateActionsRunnerToken function
-func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, ResponseExtra) {
+func GenerateActionsRunnerToken(ctx context.Context, scope string) (*ResponseText, ResponseExtra) {
reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token"
req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{
Scope: scope,
})
- resp, extra := requestJSONResp(req, &responseText{})
- if extra.HasError() {
- return "", extra
- }
- return resp.Text, extra
+ return requestJSONResp(req, &ResponseText{})
}
diff --git a/modules/private/hook.go b/modules/private/hook.go
index 23e03896e4..cab8c81224 100644
--- a/modules/private/hook.go
+++ b/modules/private/hook.go
@@ -101,7 +101,7 @@ func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOp
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequest(ctx, reqURL, "POST", opts)
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
- _, extra := requestJSONResp(req, &responseText{})
+ _, extra := requestJSONResp(req, &ResponseText{})
return extra
}
@@ -130,7 +130,7 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R
url.PathEscape(branch),
)
req := newInternalRequest(ctx, reqURL, "POST")
- _, extra := requestJSONResp(req, &responseText{})
+ _, extra := requestJSONResp(req, &ResponseText{})
return extra
}
@@ -138,6 +138,6 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R
func SSHLog(ctx context.Context, isErr bool, msg string) error {
reqURL := setting.LocalURL + "api/internal/ssh/log"
req := newInternalRequest(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg})
- _, extra := requestJSONResp(req, &responseText{})
+ _, extra := requestJSONResp(req, &ResponseText{})
return extra.Error
}
diff --git a/modules/private/key.go b/modules/private/key.go
index 08762bd401..dcd1714856 100644
--- a/modules/private/key.go
+++ b/modules/private/key.go
@@ -15,20 +15,16 @@ func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update/%d", keyID, repoID)
req := newInternalRequest(ctx, reqURL, "POST")
- _, extra := requestJSONResp(req, &responseText{})
+ _, extra := requestJSONResp(req, &ResponseText{})
return extra.Error
}
// AuthorizedPublicKeyByContent searches content as prefix (leak e-mail part)
// and returns public key found.
-func AuthorizedPublicKeyByContent(ctx context.Context, content string) (string, ResponseExtra) {
+func AuthorizedPublicKeyByContent(ctx context.Context, content string) (*ResponseText, ResponseExtra) {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + "api/internal/ssh/authorized_keys"
req := newInternalRequest(ctx, reqURL, "POST")
req.Param("content", content)
- resp, extra := requestJSONResp(req, &responseText{})
- if extra.HasError() {
- return "", extra
- }
- return resp.Text, extra
+ return requestJSONResp(req, &ResponseText{})
}
diff --git a/modules/private/mail.go b/modules/private/mail.go
index ac55d6fe4d..08de5b7e28 100644
--- a/modules/private/mail.go
+++ b/modules/private/mail.go
@@ -20,7 +20,7 @@ type Email struct {
// It accepts a list of usernames.
// If DB contains these users it will send the email to them.
// If to list == nil, it's supposed to send emails to every user present in DB
-func SendEmail(ctx context.Context, subject, message string, to []string) (string, ResponseExtra) {
+func SendEmail(ctx context.Context, subject, message string, to []string) (*ResponseText, ResponseExtra) {
reqURL := setting.LocalURL + "api/internal/mail/send"
req := newInternalRequest(ctx, reqURL, "POST", Email{
@@ -29,9 +29,5 @@ func SendEmail(ctx context.Context, subject, message string, to []string) (strin
To: to,
})
- resp, extra := requestJSONResp(req, &responseText{})
- if extra.HasError() {
- return "", extra
- }
- return resp.Text, extra
+ return requestJSONResp(req, &ResponseText{})
}
diff --git a/modules/private/request.go b/modules/private/request.go
index 2bc43b972d..58cd261239 100644
--- a/modules/private/request.go
+++ b/modules/private/request.go
@@ -12,8 +12,8 @@ import (
"code.gitea.io/gitea/modules/json"
)
-// responseText is used to get the response as text, instead of parsing it as JSON.
-type responseText struct {
+// ResponseText is used to get the response as text, instead of parsing it as JSON.
+type ResponseText struct {
Text string
}
@@ -50,7 +50,7 @@ func (re responseError) Error() string {
// Caller should check the ResponseExtra.HasError() first to see whether the request fails.
//
// * If the "res" is a struct pointer, the response will be parsed as JSON
-// * If the "res" is responseText pointer, the response will be stored as text in it
+// * If the "res" is ResponseText pointer, the response will be stored as text in it
// * If the "res" is responseCallback pointer, the callback function should set the ResponseExtra fields accordingly
func requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra ResponseExtra) {
resp, err := req.Response()
@@ -81,7 +81,7 @@ func requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra Respons
// now, the StatusCode must be 2xx
var v any = res
- if respText, ok := v.(*responseText); ok {
+ if respText, ok := v.(*ResponseText); ok {
// get the whole response as a text string
bs, err := io.ReadAll(resp.Body)
if err != nil {
@@ -119,7 +119,7 @@ func requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra Respons
// requestJSONClientMsg sends a request to the gitea server, server only responds text message status=200 with "success" body
// If the request succeeds (200), the argument clientSuccessMsg will be used as ResponseExtra.UserMsg.
func requestJSONClientMsg(req *httplib.Request, clientSuccessMsg string) ResponseExtra {
- _, extra := requestJSONResp(req, &responseText{})
+ _, extra := requestJSONResp(req, &ResponseText{})
if extra.HasError() {
return extra
}
From 1167d523c4c24eba4c159cda04e49a49f458afbf Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel
Date: Fri, 19 Jan 2024 06:49:18 +0100
Subject: [PATCH 03/22] Fix archive creating LFS hooks and breaking pull
requests (#28848)
When LFS hooks are present in gitea-repositories, operations like git
push for creating a pull request fail. These repositories are not meant
to include LFS files or git push them, that is handled separately. And
so they should not have LFS hooks.
Installing git-lfs on some systems (like Debian Linux) will
automatically set up /etc/gitconfig to create LFS hooks in repositories.
For most git commands in Gitea this is not a problem, either because
they run on a temporary clone or the git command does not create LFS
hooks.
But one case where this happens is git archive for creating repository
archives. To fix that, add a GIT_CONFIG_NOSYSTEM=1 to disable using the
system configuration for that command.
According to a comment, GIT_CONFIG_NOSYSTEM is not used for all git
commands because the system configuration can be intentionally set up
for Gitea to use.
Resolves #19810, #21148
---
modules/git/repo_archive.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go
index 2b45a50f19..1bf1aa41b9 100644
--- a/modules/git/repo_archive.go
+++ b/modules/git/repo_archive.go
@@ -8,6 +8,7 @@ import (
"context"
"fmt"
"io"
+ "os"
"path/filepath"
"strings"
)
@@ -62,11 +63,15 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
cmd.AddOptionFormat("--format=%s", format.String())
cmd.AddDynamicArguments(commitID)
+ // Avoid LFS hooks getting installed because of /etc/gitconfig, which can break pull requests.
+ env := append(os.Environ(), "GIT_CONFIG_NOSYSTEM=1")
+
var stderr strings.Builder
err := cmd.Run(&RunOpts{
Dir: repo.Path,
Stdout: target,
Stderr: &stderr,
+ Env: env,
})
if err != nil {
return ConcatenateError(err, stderr.String())
From 075c4c89ee28590bd4ab8f6cf7338d723c4696eb Mon Sep 17 00:00:00 2001
From: Adam Majer
Date: Fri, 19 Jan 2024 07:12:21 +0000
Subject: [PATCH 04/22] tests: missing refs/ in bare repositories (#28844)
Git 2.43.0 will not detect a git repository as valid without refs/
subdirectory present. `git gc` cleans this up and puts it in
packed-refs. We must keep refs/ non-empty.
---
.../user2/test_commit_revert.git/refs/heads/main | 1 +
1 file changed, 1 insertion(+)
create mode 100644 tests/gitea-repositories-meta/user2/test_commit_revert.git/refs/heads/main
diff --git a/tests/gitea-repositories-meta/user2/test_commit_revert.git/refs/heads/main b/tests/gitea-repositories-meta/user2/test_commit_revert.git/refs/heads/main
new file mode 100644
index 0000000000..ab80ca3ca6
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/test_commit_revert.git/refs/heads/main
@@ -0,0 +1 @@
+deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7
From 461d8b53c2e51a8a6a1715ba40ac61d7e9f93971 Mon Sep 17 00:00:00 2001
From: KN4CK3R
Date: Fri, 19 Jan 2024 12:37:10 +0100
Subject: [PATCH 05/22] Fix some RPM registry flaws (#28782)
Related #26984
(https://github.com/go-gitea/gitea/pull/26984#issuecomment-1889588912)
Fix admin cleanup message.
Fix models `Get` not respecting default values.
Rebuild RPM repository files after cleanup.
Do not add RPM group to package version name.
Force stable sorting of Alpine/Debian/RPM repository data.
Fix missing deferred `Close`.
Add tests for multiple RPM groups.
Removed non-cached `ReplaceAllStringRegex`.
If there are multiple groups available, it's stated in the package
installation screen:

---
docs/content/usage/packages/rpm.en-us.md | 49 +-
models/packages/package.go | 14 +-
models/packages/package_blob.go | 12 +-
models/packages/package_file.go | 26 +-
models/packages/package_version.go | 12 +-
models/packages/rpm/search.go | 23 +
modules/packages/rpm/metadata.go | 5 +-
modules/templates/util_string.go | 5 -
modules/util/slice.go | 8 +
options/locale/locale_en-US.ini | 3 +
routers/api/packages/api.go | 148 +++--
routers/api/packages/rpm/rpm.go | 31 +-
routers/web/admin/packages.go | 2 +-
routers/web/user/package.go | 30 +-
services/packages/cleanup/cleanup.go | 5 +
services/packages/rpm/repository.go | 69 ++-
templates/package/content/rpm.tmpl | 32 +-
tests/integration/api_packages_rpm_test.go | 638 +++++++++++----------
18 files changed, 634 insertions(+), 478 deletions(-)
create mode 100644 models/packages/rpm/search.go
diff --git a/docs/content/usage/packages/rpm.en-us.md b/docs/content/usage/packages/rpm.en-us.md
index 586e48d47f..1f93376b7b 100644
--- a/docs/content/usage/packages/rpm.en-us.md
+++ b/docs/content/usage/packages/rpm.en-us.md
@@ -24,16 +24,26 @@ The following examples use `dnf`.
## Configuring the package registry
-To register the RPM registry add the url to the list of known apt sources:
+To register the RPM registry add the url to the list of known sources:
```shell
dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo
```
-| Placeholder | Description |
-| ----------- |----------------------------------------------------|
-| `owner` | The owner of the package. |
-| `group` | Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.|
+| Placeholder | Description |
+| ----------- | ----------- |
+| `owner` | The owner of the package. |
+| `group` | Optional: Everything, e.g. empty, `el7`, `rocky/el9`, `test/fc38`. |
+
+Example:
+
+```shell
+# without a group
+dnf config-manager --add-repo https://gitea.example.com/api/packages/testuser/rpm.repo
+
+# with the group 'centos/el7'
+dnf config-manager --add-repo https://gitea.example.com/api/packages/testuser/rpm/centos/el7.repo
+```
If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication):
@@ -41,7 +51,7 @@ If the registry is private, provide credentials in the url. You can use a passwo
dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo
```
-You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum.repos.d` too.
+You have to add the credentials to the urls in the created `.repo` file in `/etc/yum.repos.d` too.
## Publish a package
@@ -54,11 +64,17 @@ PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload
| Parameter | Description |
| --------- | ----------- |
| `owner` | The owner of the package. |
-| `group` | Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.|
+| `group` | Optional: Everything, e.g. empty, `el7`, `rocky/el9`, `test/fc38`. |
Example request using HTTP Basic authentication:
```shell
+# without a group
+curl --user your_username:your_password_or_token \
+ --upload-file path/to/file.rpm \
+ https://gitea.example.com/api/packages/testuser/rpm/upload
+
+# with the group 'centos/el7'
curl --user your_username:your_password_or_token \
--upload-file path/to/file.rpm \
https://gitea.example.com/api/packages/testuser/rpm/centos/el7/upload
@@ -83,17 +99,22 @@ To delete an RPM package perform a HTTP DELETE operation. This will delete the p
DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture}
```
-| Parameter | Description |
-|-------------------|----------------------------|
-| `owner` | The owner of the package. |
-| `group` | The package group . |
-| `package_name` | The package name. |
-| `package_version` | The package version. |
-| `architecture` | The package architecture. |
+| Parameter | Description |
+| ----------------- | ----------- |
+| `owner` | The owner of the package. |
+| `group` | Optional: The package group. |
+| `package_name` | The package name. |
+| `package_version` | The package version. |
+| `architecture` | The package architecture. |
Example request using HTTP Basic authentication:
```shell
+# without a group
+curl --user your_username:your_token_or_password -X DELETE \
+ https://gitea.example.com/api/packages/testuser/rpm/package/test-package/1.0.0/x86_64
+
+# with the group 'centos/el7'
curl --user your_username:your_token_or_password -X DELETE \
https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64
```
diff --git a/models/packages/package.go b/models/packages/package.go
index 380a076f9d..65a2574150 100644
--- a/models/packages/package.go
+++ b/models/packages/package.go
@@ -191,18 +191,18 @@ type Package struct {
func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
e := db.GetEngine(ctx)
- key := &Package{
- OwnerID: p.OwnerID,
- Type: p.Type,
- LowerName: p.LowerName,
- }
+ existing := &Package{}
- has, err := e.Get(key)
+ has, err := e.Where(builder.Eq{
+ "owner_id": p.OwnerID,
+ "type": p.Type,
+ "lower_name": p.LowerName,
+ }).Get(existing)
if err != nil {
return nil, err
}
if has {
- return key, ErrDuplicatePackage
+ return existing, ErrDuplicatePackage
}
if _, err = e.Insert(p); err != nil {
return nil, err
diff --git a/models/packages/package_blob.go b/models/packages/package_blob.go
index d1f470d520..d9c30b6533 100644
--- a/models/packages/package_blob.go
+++ b/models/packages/package_blob.go
@@ -41,12 +41,20 @@ type PackageBlob struct {
func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool, error) {
e := db.GetEngine(ctx)
- has, err := e.Get(pb)
+ existing := &PackageBlob{}
+
+ has, err := e.Where(builder.Eq{
+ "size": pb.Size,
+ "hash_md5": pb.HashMD5,
+ "hash_sha1": pb.HashSHA1,
+ "hash_sha256": pb.HashSHA256,
+ "hash_sha512": pb.HashSHA512,
+ }).Get(existing)
if err != nil {
return nil, false, err
}
if has {
- return pb, true, nil
+ return existing, true, nil
}
if _, err = e.Insert(pb); err != nil {
return nil, false, err
diff --git a/models/packages/package_file.go b/models/packages/package_file.go
index 1c2c9ac072..1bb6b57a34 100644
--- a/models/packages/package_file.go
+++ b/models/packages/package_file.go
@@ -46,18 +46,18 @@ type PackageFile struct {
func TryInsertFile(ctx context.Context, pf *PackageFile) (*PackageFile, error) {
e := db.GetEngine(ctx)
- key := &PackageFile{
- VersionID: pf.VersionID,
- LowerName: pf.LowerName,
- CompositeKey: pf.CompositeKey,
- }
+ existing := &PackageFile{}
- has, err := e.Get(key)
+ has, err := e.Where(builder.Eq{
+ "version_id": pf.VersionID,
+ "lower_name": pf.LowerName,
+ "composite_key": pf.CompositeKey,
+ }).Get(existing)
if err != nil {
return nil, err
}
if has {
- return pf, ErrDuplicatePackageFile
+ return existing, ErrDuplicatePackageFile
}
if _, err = e.Insert(pf); err != nil {
return nil, err
@@ -93,13 +93,13 @@ func GetFileForVersionByName(ctx context.Context, versionID int64, name, key str
return nil, ErrPackageFileNotExist
}
- pf := &PackageFile{
- VersionID: versionID,
- LowerName: strings.ToLower(name),
- CompositeKey: key,
- }
+ pf := &PackageFile{}
- has, err := db.GetEngine(ctx).Get(pf)
+ has, err := db.GetEngine(ctx).Where(builder.Eq{
+ "version_id": versionID,
+ "lower_name": strings.ToLower(name),
+ "composite_key": key,
+ }).Get(pf)
if err != nil {
return nil, err
}
diff --git a/models/packages/package_version.go b/models/packages/package_version.go
index 9999fc4dab..8fc475691b 100644
--- a/models/packages/package_version.go
+++ b/models/packages/package_version.go
@@ -39,17 +39,17 @@ type PackageVersion struct {
func GetOrInsertVersion(ctx context.Context, pv *PackageVersion) (*PackageVersion, error) {
e := db.GetEngine(ctx)
- key := &PackageVersion{
- PackageID: pv.PackageID,
- LowerVersion: pv.LowerVersion,
- }
+ existing := &PackageVersion{}
- has, err := e.Get(key)
+ has, err := e.Where(builder.Eq{
+ "package_id": pv.PackageID,
+ "lower_version": pv.LowerVersion,
+ }).Get(existing)
if err != nil {
return nil, err
}
if has {
- return key, ErrDuplicatePackageVersion
+ return existing, ErrDuplicatePackageVersion
}
if _, err = e.Insert(pv); err != nil {
return nil, err
diff --git a/models/packages/rpm/search.go b/models/packages/rpm/search.go
new file mode 100644
index 0000000000..e697421b49
--- /dev/null
+++ b/models/packages/rpm/search.go
@@ -0,0 +1,23 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package rpm
+
+import (
+ "context"
+
+ packages_model "code.gitea.io/gitea/models/packages"
+ rpm_module "code.gitea.io/gitea/modules/packages/rpm"
+)
+
+// GetGroups gets all available groups
+func GetGroups(ctx context.Context, ownerID int64) ([]string, error) {
+ return packages_model.GetDistinctPropertyValues(
+ ctx,
+ packages_model.TypeRpm,
+ ownerID,
+ packages_model.PropertyTypeFile,
+ rpm_module.PropertyGroup,
+ nil,
+ )
+}
diff --git a/modules/packages/rpm/metadata.go b/modules/packages/rpm/metadata.go
index 1ba4c73e8d..7fc47a53e6 100644
--- a/modules/packages/rpm/metadata.go
+++ b/modules/packages/rpm/metadata.go
@@ -15,7 +15,10 @@ import (
)
const (
- PropertyMetadata = "rpm.metadata"
+ PropertyMetadata = "rpm.metadata"
+ PropertyGroup = "rpm.group"
+ PropertyArchitecture = "rpm.architecture"
+
SettingKeyPrivate = "rpm.key.private"
SettingKeyPublic = "rpm.key.public"
diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go
index 613940ccdc..18a0d5cacc 100644
--- a/modules/templates/util_string.go
+++ b/modules/templates/util_string.go
@@ -4,7 +4,6 @@
package templates
import (
- "regexp"
"strings"
"code.gitea.io/gitea/modules/base"
@@ -26,10 +25,6 @@ func (su *StringUtils) Contains(s, substr string) bool {
return strings.Contains(s, substr)
}
-func (su *StringUtils) ReplaceAllStringRegex(s, regex, new string) string {
- return regexp.MustCompile(regex).ReplaceAllString(s, new)
-}
-
func (su *StringUtils) Split(s, sep string) []string {
return strings.Split(s, sep)
}
diff --git a/modules/util/slice.go b/modules/util/slice.go
index 6d63ab4a77..a7073fedee 100644
--- a/modules/util/slice.go
+++ b/modules/util/slice.go
@@ -4,6 +4,7 @@
package util
import (
+ "cmp"
"slices"
"strings"
)
@@ -45,3 +46,10 @@ func SliceSortedEqual[T comparable](s1, s2 []T) bool {
func SliceRemoveAll[T comparable](slice []T, target T) []T {
return slices.DeleteFunc(slice, func(t T) bool { return t == target })
}
+
+// Sorted returns the sorted slice
+// Note: The parameter is sorted inline.
+func Sorted[S ~[]E, E cmp.Ordered](values S) S {
+ slices.Sort(values)
+ return values
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 6f9ba8c884..e8345595aa 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3414,6 +3414,9 @@ rpm.registry = Setup this registry from the command line:
rpm.distros.redhat = on RedHat based distributions
rpm.distros.suse = on SUSE based distributions
rpm.install = To install the package, run the following command:
+rpm.repository = Repository Info
+rpm.repository.architectures = Architectures
+rpm.repository.multiple_groups = This package is available in multiple groups.
rubygems.install = To install the package using gem, run the following command:
rubygems.install2 = or add it to the Gemfile:
rubygems.dependencies.runtime = Runtime Dependencies
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index 9026387129..d990ebb56a 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -512,7 +512,77 @@ func CommonRoutes() *web.Route {
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
r.Get("/simple/{id}", pypi.PackageMetadata)
}, reqPackageAccess(perm.AccessModeRead))
- r.Group("/rpm", RpmRoutes(r), reqPackageAccess(perm.AccessModeRead))
+ r.Group("/rpm", func() {
+ r.Group("/repository.key", func() {
+ r.Head("", rpm.GetRepositoryKey)
+ r.Get("", rpm.GetRepositoryKey)
+ })
+
+ var (
+ repoPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
+ uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
+ filePattern = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
+ repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
+ )
+
+ r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
+ path := ctx.Params("*")
+ isHead := ctx.Req.Method == "HEAD"
+ isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
+ isPut := ctx.Req.Method == "PUT"
+ isDelete := ctx.Req.Method == "DELETE"
+
+ m := repoPattern.FindStringSubmatch(path)
+ if len(m) == 2 && isGetHead {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ rpm.GetRepositoryConfig(ctx)
+ return
+ }
+
+ m = repoFilePattern.FindStringSubmatch(path)
+ if len(m) == 3 && isGetHead {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ ctx.SetParams("filename", m[2])
+ if isHead {
+ rpm.CheckRepositoryFileExistence(ctx)
+ } else {
+ rpm.GetRepositoryFile(ctx)
+ }
+ return
+ }
+
+ m = uploadPattern.FindStringSubmatch(path)
+ if len(m) == 2 && isPut {
+ reqPackageAccess(perm.AccessModeWrite)(ctx)
+ if ctx.Written() {
+ return
+ }
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ rpm.UploadPackageFile(ctx)
+ return
+ }
+
+ m = filePattern.FindStringSubmatch(path)
+ if len(m) == 6 && (isGetHead || isDelete) {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ ctx.SetParams("name", m[2])
+ ctx.SetParams("version", m[3])
+ ctx.SetParams("architecture", m[4])
+ if isGetHead {
+ rpm.DownloadPackageFile(ctx)
+ } else {
+ reqPackageAccess(perm.AccessModeWrite)(ctx)
+ if ctx.Written() {
+ return
+ }
+ rpm.DeletePackageFile(ctx)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNotFound)
+ })
+ }, reqPackageAccess(perm.AccessModeRead))
r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@@ -577,82 +647,6 @@ func CommonRoutes() *web.Route {
return r
}
-// Support for uploading rpm packages with arbitrary depth paths
-func RpmRoutes(r *web.Route) func() {
- var (
- groupRepoInfo = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)\.repo\z`)
- groupUpload = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/upload\z`)
- groupRpm = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
- groupMetadata = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/repodata/([^/]+)\z`)
- )
-
- return func() {
- r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "*", func(ctx *context.Context) {
- path := ctx.Params("*")
- isHead := ctx.Req.Method == "HEAD"
- isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
- isPut := ctx.Req.Method == "PUT"
- isDelete := ctx.Req.Method == "DELETE"
-
- if path == "/repository.key" && isGetHead {
- rpm.GetRepositoryKey(ctx)
- return
- }
-
- // get repo
- m := groupRepoInfo.FindStringSubmatch(path)
- if len(m) == 2 && isGetHead {
- ctx.SetParams("group", strings.Trim(m[1], "/"))
- rpm.GetRepositoryConfig(ctx)
- return
- }
- // get meta
- m = groupMetadata.FindStringSubmatch(path)
- if len(m) == 3 && isGetHead {
- ctx.SetParams("group", strings.Trim(m[1], "/"))
- ctx.SetParams("filename", m[2])
- if isHead {
- rpm.CheckRepositoryFileExistence(ctx)
- } else {
- rpm.GetRepositoryFile(ctx)
- }
- return
- }
- // upload
- m = groupUpload.FindStringSubmatch(path)
- if len(m) == 2 && isPut {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- ctx.SetParams("group", strings.Trim(m[1], "/"))
- rpm.UploadPackageFile(ctx)
- return
- }
- // rpm down/delete
- m = groupRpm.FindStringSubmatch(path)
- if len(m) == 6 {
- ctx.SetParams("group", strings.Trim(m[1], "/"))
- ctx.SetParams("name", m[2])
- ctx.SetParams("version", m[3])
- ctx.SetParams("architecture", m[4])
- if isGetHead {
- rpm.DownloadPackageFile(ctx)
- return
- } else if isDelete {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- rpm.DeletePackageFile(ctx)
- }
- }
- // default
- ctx.Status(http.StatusNotFound)
- })
- }
-}
-
// ContainerRoutes provides endpoints that implement the OCI API to serve containers
// These have to be mounted on `/v2/...` to comply with the OCI spec:
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go
index 75d19e2b43..5d06680552 100644
--- a/routers/api/packages/rpm/rpm.go
+++ b/routers/api/packages/rpm/rpm.go
@@ -34,13 +34,17 @@ func apiError(ctx *context.Context, status int, obj any) {
// https://dnf.readthedocs.io/en/latest/conf_ref.html
func GetRepositoryConfig(ctx *context.Context) {
group := ctx.Params("group")
+
+ var groupParts []string
if group != "" {
- group = fmt.Sprintf("/%s", group)
+ groupParts = strings.Split(group, "/")
}
+
url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
- ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+strings.ReplaceAll(group, "/", "-")+`]
-name=`+ctx.Package.Owner.Name+` - `+setting.AppName+strings.ReplaceAll(group, "/", " - ")+`
-baseurl=`+url+group+`/
+
+ ctx.PlainText(http.StatusOK, `[gitea-`+strings.Join(append([]string{ctx.Package.Owner.LowerName}, groupParts...), "-")+`]
+name=`+strings.Join(append([]string{ctx.Package.Owner.Name, setting.AppName}, groupParts...), " - ")+`
+baseurl=`+strings.Join(append([]string{url}, groupParts...), "/")+`
enabled=1
gpgcheck=1
gpgkey=`+url+`/repository.key`)
@@ -157,7 +161,7 @@ func UploadPackageFile(ctx *context.Context) {
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: pck.Name,
- Version: strings.Trim(fmt.Sprintf("%s/%s", group, pck.Version), "/"),
+ Version: pck.Version,
},
Creator: ctx.Doer,
Metadata: pck.VersionMetadata,
@@ -171,7 +175,9 @@ func UploadPackageFile(ctx *context.Context) {
Data: buf,
IsLead: true,
Properties: map[string]string{
- rpm_module.PropertyMetadata: string(fileMetadataRaw),
+ rpm_module.PropertyGroup: group,
+ rpm_module.PropertyArchitecture: pck.FileMetadata.Architecture,
+ rpm_module.PropertyMetadata: string(fileMetadataRaw),
},
},
)
@@ -187,7 +193,7 @@ func UploadPackageFile(ctx *context.Context) {
return
}
- if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
+ if err := rpm_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
@@ -196,20 +202,20 @@ func UploadPackageFile(ctx *context.Context) {
}
func DownloadPackageFile(ctx *context.Context) {
- group := ctx.Params("group")
name := ctx.Params("name")
version := ctx.Params("version")
+
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: name,
- Version: strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
+ Version: version,
},
&packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
- CompositeKey: group,
+ CompositeKey: ctx.Params("group"),
},
)
if err != nil {
@@ -229,6 +235,7 @@ func DeletePackageFile(webctx *context.Context) {
name := webctx.Params("name")
version := webctx.Params("version")
architecture := webctx.Params("architecture")
+
var pd *packages_model.PackageDescriptor
err := db.WithTx(webctx, func(ctx stdctx.Context) error {
@@ -236,7 +243,7 @@ func DeletePackageFile(webctx *context.Context) {
webctx.Package.Owner.ID,
packages_model.TypeRpm,
name,
- strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
+ version,
)
if err != nil {
return err
@@ -286,7 +293,7 @@ func DeletePackageFile(webctx *context.Context) {
notify_service.PackageDelete(webctx, webctx.Doer, pd)
}
- if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
+ if err := rpm_service.BuildSpecificRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
apiError(webctx, http.StatusInternalServerError, err)
return
}
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index f4e1a93a22..35ce215be4 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -108,6 +108,6 @@ func CleanupExpiredData(ctx *context.Context) {
return
}
- ctx.Flash.Success(ctx.Tr("packages.cleanup.success"))
+ ctx.Flash.Success(ctx.Tr("admin.packages.cleanup.success"))
ctx.Redirect(setting.AppSubURL + "/admin/packages")
}
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index d8da6a192e..708af3e43c 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/log"
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
debian_module "code.gitea.io/gitea/modules/packages/debian"
+ rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
@@ -195,9 +196,9 @@ func ViewPackageVersion(ctx *context.Context) {
}
}
- ctx.Data["Branches"] = branches.Values()
- ctx.Data["Repositories"] = repositories.Values()
- ctx.Data["Architectures"] = architectures.Values()
+ ctx.Data["Branches"] = util.Sorted(branches.Values())
+ ctx.Data["Repositories"] = util.Sorted(repositories.Values())
+ ctx.Data["Architectures"] = util.Sorted(architectures.Values())
case packages_model.TypeDebian:
distributions := make(container.Set[string])
components := make(container.Set[string])
@@ -216,9 +217,26 @@ func ViewPackageVersion(ctx *context.Context) {
}
}
- ctx.Data["Distributions"] = distributions.Values()
- ctx.Data["Components"] = components.Values()
- ctx.Data["Architectures"] = architectures.Values()
+ ctx.Data["Distributions"] = util.Sorted(distributions.Values())
+ ctx.Data["Components"] = util.Sorted(components.Values())
+ ctx.Data["Architectures"] = util.Sorted(architectures.Values())
+ case packages_model.TypeRpm:
+ groups := make(container.Set[string])
+ architectures := make(container.Set[string])
+
+ for _, f := range pd.Files {
+ for _, pp := range f.Properties {
+ switch pp.Name {
+ case rpm_module.PropertyGroup:
+ groups.Add(pp.Value)
+ case rpm_module.PropertyArchitecture:
+ architectures.Add(pp.Value)
+ }
+ }
+ }
+
+ ctx.Data["Groups"] = util.Sorted(groups.Values())
+ ctx.Data["Architectures"] = util.Sorted(architectures.Values())
}
var (
diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go
index 04ee6c4419..0ff8077bc9 100644
--- a/services/packages/cleanup/cleanup.go
+++ b/services/packages/cleanup/cleanup.go
@@ -19,6 +19,7 @@ import (
cargo_service "code.gitea.io/gitea/services/packages/cargo"
container_service "code.gitea.io/gitea/services/packages/container"
debian_service "code.gitea.io/gitea/services/packages/debian"
+ rpm_service "code.gitea.io/gitea/services/packages/rpm"
)
// Task method to execute cleanup rules and cleanup expired package data
@@ -127,6 +128,10 @@ func ExecuteCleanupRules(outerCtx context.Context) error {
if err := alpine_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
return fmt.Errorf("CleanupRule [%d]: alpine.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
}
+ } else if pcr.Type == packages_model.TypeRpm {
+ if err := rpm_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
+ return fmt.Errorf("CleanupRule [%d]: rpm.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
+ }
}
}
return nil
diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go
index 7a49efde4f..c52c8a5dd9 100644
--- a/services/packages/rpm/repository.go
+++ b/services/packages/rpm/repository.go
@@ -18,6 +18,7 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
+ rpm_model "code.gitea.io/gitea/models/packages/rpm"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -96,6 +97,39 @@ func generateKeypair() (string, string, error) {
return priv.String(), pub.String(), nil
}
+// BuildAllRepositoryFiles (re)builds all repository files for every available group
+func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
+ pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
+ if err != nil {
+ return err
+ }
+
+ // 1. Delete all existing repository files
+ pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID)
+ if err != nil {
+ return err
+ }
+
+ for _, pf := range pfs {
+ if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
+ return err
+ }
+ }
+
+ // 2. (Re)Build repository files for existing packages
+ groups, err := rpm_model.GetGroups(ctx, ownerID)
+ if err != nil {
+ return err
+ }
+ for _, group := range groups {
+ if err := BuildSpecificRepositoryFiles(ctx, ownerID, group); err != nil {
+ return fmt.Errorf("failed to build repository files [%s]: %w", group, err)
+ }
+ }
+
+ return nil
+}
+
type repoChecksum struct {
Value string `xml:",chardata"`
Type string `xml:"type,attr"`
@@ -126,7 +160,7 @@ type packageData struct {
type packageCache = map[*packages_model.PackageFile]*packageData
// BuildSpecificRepositoryFiles builds metadata files for the repository
-func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey string) error {
+func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, group string) error {
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
if err != nil {
return err
@@ -136,7 +170,7 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey strin
OwnerID: ownerID,
PackageType: packages_model.TypeRpm,
Query: "%.rpm",
- CompositeKey: compositeKey,
+ CompositeKey: group,
})
if err != nil {
return err
@@ -195,15 +229,15 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey strin
cache[pf] = pd
}
- primary, err := buildPrimary(ctx, pv, pfs, cache, compositeKey)
+ primary, err := buildPrimary(ctx, pv, pfs, cache, group)
if err != nil {
return err
}
- filelists, err := buildFilelists(ctx, pv, pfs, cache, compositeKey)
+ filelists, err := buildFilelists(ctx, pv, pfs, cache, group)
if err != nil {
return err
}
- other, err := buildOther(ctx, pv, pfs, cache, compositeKey)
+ other, err := buildOther(ctx, pv, pfs, cache, group)
if err != nil {
return err
}
@@ -217,12 +251,12 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey strin
filelists,
other,
},
- compositeKey,
+ group,
)
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml
-func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData, compositeKey string) error {
+func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData, group string) error {
type Repomd struct {
XMLName xml.Name `xml:"repomd"`
Xmlns string `xml:"xmlns,attr"`
@@ -278,7 +312,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: file.Name,
- CompositeKey: compositeKey,
+ CompositeKey: group,
},
Creator: user_model.NewGhostUser(),
Data: file.Data,
@@ -295,7 +329,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml
-func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) {
+func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) {
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
@@ -434,11 +468,11 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
PackageCount: len(pfs),
Packages: packages,
- }, compositeKey)
+ }, group)
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml
-func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
+func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
@@ -481,12 +515,11 @@ func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs
Xmlns: "http://linux.duke.edu/metadata/other",
PackageCount: len(pfs),
Packages: packages,
- },
- compositeKey)
+ }, group)
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml
-func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
+func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
@@ -529,7 +562,7 @@ func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*p
Xmlns: "http://linux.duke.edu/metadata/other",
PackageCount: len(pfs),
Packages: packages,
- }, compositeKey)
+ }, group)
}
// writtenCounter counts all written bytes
@@ -549,8 +582,10 @@ func (wc *writtenCounter) Written() int64 {
return wc.written
}
-func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any, compositeKey string) (*repoData, error) {
+func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any, group string) (*repoData, error) {
content, _ := packages_module.NewHashedBuffer()
+ defer content.Close()
+
gzw := gzip.NewWriter(content)
wc := &writtenCounter{}
h := sha256.New()
@@ -574,7 +609,7 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion,
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: filename,
- CompositeKey: compositeKey,
+ CompositeKey: group,
},
Creator: user_model.NewGhostUser(),
Data: content,
diff --git a/templates/package/content/rpm.tmpl b/templates/package/content/rpm.tmpl
index 4fd54a3197..0f128fd3fb 100644
--- a/templates/package/content/rpm.tmpl
+++ b/templates/package/content/rpm.tmpl
@@ -4,15 +4,21 @@
+
+
+
+
+
+ {{ctx.Locale.Tr "packages.rpm.repository.architectures"}} |
+ {{StringUtils.Join .Architectures ", "}} |
+
+
+
+
+
{{if or .PackageDescriptor.Metadata.Summary .PackageDescriptor.Metadata.Description}}
{{if .PackageDescriptor.Metadata.Summary}}{{.PackageDescriptor.Metadata.Summary}}
{{end}}
diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go
index 822b0b040e..1dcec6099e 100644
--- a/tests/integration/api_packages_rpm_test.go
+++ b/tests/integration/api_packages_rpm_test.go
@@ -12,6 +12,7 @@ import (
"io"
"net/http"
"net/http/httptest"
+ "strings"
"testing"
"code.gitea.io/gitea/models/db"
@@ -20,6 +21,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@@ -73,346 +75,362 @@ Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5
rootURL := fmt.Sprintf("/api/packages/%s/rpm", user.Name)
- t.Run("RepositoryConfig", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
+ for _, group := range []string{"", "el9", "el9/stable"} {
+ t.Run(fmt.Sprintf("[Group:%s]", group), func(t *testing.T) {
+ var groupParts []string
+ if group != "" {
+ groupParts = strings.Split(group, "/")
+ }
+ groupURL := strings.Join(append([]string{rootURL}, groupParts...), "/")
- req := NewRequest(t, "GET", rootURL+"/el9/stable.repo")
- resp := MakeRequest(t, req, http.StatusOK)
+ t.Run("RepositoryConfig", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
- expected := fmt.Sprintf(`[gitea-%s-el9-stable]
-name=%s - %s - el9 - stable
-baseurl=%sapi/packages/%s/rpm/el9/stable/
+ req := NewRequest(t, "GET", groupURL+".repo")
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ expected := fmt.Sprintf(`[gitea-%s]
+name=%s
+baseurl=%s
enabled=1
gpgcheck=1
-gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppName, setting.AppURL, user.Name, setting.AppURL, user.Name)
+gpgkey=%sapi/packages/%s/rpm/repository.key`,
+ strings.Join(append([]string{user.LowerName}, groupParts...), "-"),
+ strings.Join(append([]string{user.Name, setting.AppName}, groupParts...), " - "),
+ util.URLJoin(setting.AppURL, groupURL),
+ setting.AppURL,
+ user.Name,
+ )
- assert.Equal(t, expected, resp.Body.String())
- })
+ assert.Equal(t, expected, resp.Body.String())
+ })
- t.Run("RepositoryKey", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
+ t.Run("RepositoryKey", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
- req := NewRequest(t, "GET", rootURL+"/repository.key")
- resp := MakeRequest(t, req, http.StatusOK)
+ req := NewRequest(t, "GET", rootURL+"/repository.key")
+ resp := MakeRequest(t, req, http.StatusOK)
- assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
- assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
- })
+ assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
+ assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
+ })
- t.Run("Upload", func(t *testing.T) {
- url := rootURL + "/el9/stable/upload"
+ t.Run("Upload", func(t *testing.T) {
+ url := groupURL + "/upload"
- req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
- MakeRequest(t, req, http.StatusUnauthorized)
+ req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
+ MakeRequest(t, req, http.StatusUnauthorized)
- req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
- AddBasicAuth(user.Name)
- MakeRequest(t, req, http.StatusCreated)
+ req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
+ AddBasicAuth(user.Name)
+ MakeRequest(t, req, http.StatusCreated)
- pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
- assert.NoError(t, err)
- assert.Len(t, pvs, 1)
+ pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
+ assert.NoError(t, err)
+ assert.Len(t, pvs, 1)
- pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
- assert.NoError(t, err)
- assert.Nil(t, pd.SemVer)
- assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata)
- assert.Equal(t, packageName, pd.Package.Name)
- assert.Equal(t, fmt.Sprintf("el9/stable/%s", packageVersion), pd.Version.Version)
+ pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
+ assert.NoError(t, err)
+ assert.Nil(t, pd.SemVer)
+ assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata)
+ assert.Equal(t, packageName, pd.Package.Name)
+ assert.Equal(t, packageVersion, pd.Version.Version)
- pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
- assert.NoError(t, err)
- assert.Len(t, pfs, 1)
- assert.Equal(t, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), pfs[0].Name)
- assert.True(t, pfs[0].IsLead)
+ pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
+ assert.NoError(t, err)
+ assert.Len(t, pfs, 1)
+ assert.Equal(t, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), pfs[0].Name)
+ assert.True(t, pfs[0].IsLead)
- pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
- assert.NoError(t, err)
- assert.Equal(t, int64(len(content)), pb.Size)
+ pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
+ assert.NoError(t, err)
+ assert.Equal(t, int64(len(content)), pb.Size)
- req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
- AddBasicAuth(user.Name)
- MakeRequest(t, req, http.StatusConflict)
- })
+ req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
+ AddBasicAuth(user.Name)
+ MakeRequest(t, req, http.StatusConflict)
+ })
- t.Run("Download", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
+ t.Run("Download", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
- req := NewRequest(t, "GET", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
- resp := MakeRequest(t, req, http.StatusOK)
+ req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
+ resp := MakeRequest(t, req, http.StatusOK)
- assert.Equal(t, content, resp.Body.Bytes())
- })
+ assert.Equal(t, content, resp.Body.Bytes())
+ })
- t.Run("Repository", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
+ t.Run("Repository", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
- url := rootURL + "/el9/stable/repodata"
+ url := groupURL + "/repodata"
- req := NewRequest(t, "HEAD", url+"/dummy.xml")
- MakeRequest(t, req, http.StatusNotFound)
+ req := NewRequest(t, "HEAD", url+"/dummy.xml")
+ MakeRequest(t, req, http.StatusNotFound)
- req = NewRequest(t, "GET", url+"/dummy.xml")
- MakeRequest(t, req, http.StatusNotFound)
+ req = NewRequest(t, "GET", url+"/dummy.xml")
+ MakeRequest(t, req, http.StatusNotFound)
- t.Run("repomd.xml", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
+ t.Run("repomd.xml", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
- req = NewRequest(t, "HEAD", url+"/repomd.xml")
- MakeRequest(t, req, http.StatusOK)
+ req = NewRequest(t, "HEAD", url+"/repomd.xml")
+ MakeRequest(t, req, http.StatusOK)
- req = NewRequest(t, "GET", url+"/repomd.xml")
- resp := MakeRequest(t, req, http.StatusOK)
+ req = NewRequest(t, "GET", url+"/repomd.xml")
+ resp := MakeRequest(t, req, http.StatusOK)
- type Repomd struct {
- XMLName xml.Name `xml:"repomd"`
- Xmlns string `xml:"xmlns,attr"`
- XmlnsRpm string `xml:"xmlns:rpm,attr"`
- Data []struct {
- Type string `xml:"type,attr"`
- Checksum struct {
- Value string `xml:",chardata"`
- Type string `xml:"type,attr"`
- } `xml:"checksum"`
- OpenChecksum struct {
- Value string `xml:",chardata"`
- Type string `xml:"type,attr"`
- } `xml:"open-checksum"`
- Location struct {
- Href string `xml:"href,attr"`
- } `xml:"location"`
- Timestamp int64 `xml:"timestamp"`
- Size int64 `xml:"size"`
- OpenSize int64 `xml:"open-size"`
- } `xml:"data"`
- }
+ type Repomd struct {
+ XMLName xml.Name `xml:"repomd"`
+ Xmlns string `xml:"xmlns,attr"`
+ XmlnsRpm string `xml:"xmlns:rpm,attr"`
+ Data []struct {
+ Type string `xml:"type,attr"`
+ Checksum struct {
+ Value string `xml:",chardata"`
+ Type string `xml:"type,attr"`
+ } `xml:"checksum"`
+ OpenChecksum struct {
+ Value string `xml:",chardata"`
+ Type string `xml:"type,attr"`
+ } `xml:"open-checksum"`
+ Location struct {
+ Href string `xml:"href,attr"`
+ } `xml:"location"`
+ Timestamp int64 `xml:"timestamp"`
+ Size int64 `xml:"size"`
+ OpenSize int64 `xml:"open-size"`
+ } `xml:"data"`
+ }
- var result Repomd
- decodeXML(t, resp, &result)
+ var result Repomd
+ decodeXML(t, resp, &result)
- assert.Len(t, result.Data, 3)
- for _, d := range result.Data {
- assert.Equal(t, "sha256", d.Checksum.Type)
- assert.NotEmpty(t, d.Checksum.Value)
- assert.Equal(t, "sha256", d.OpenChecksum.Type)
- assert.NotEmpty(t, d.OpenChecksum.Value)
- assert.NotEqual(t, d.Checksum.Value, d.OpenChecksum.Value)
- assert.Greater(t, d.OpenSize, d.Size)
+ assert.Len(t, result.Data, 3)
+ for _, d := range result.Data {
+ assert.Equal(t, "sha256", d.Checksum.Type)
+ assert.NotEmpty(t, d.Checksum.Value)
+ assert.Equal(t, "sha256", d.OpenChecksum.Type)
+ assert.NotEmpty(t, d.OpenChecksum.Value)
+ assert.NotEqual(t, d.Checksum.Value, d.OpenChecksum.Value)
+ assert.Greater(t, d.OpenSize, d.Size)
- switch d.Type {
- case "primary":
- assert.EqualValues(t, 722, d.Size)
- assert.EqualValues(t, 1759, d.OpenSize)
- assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href)
- case "filelists":
- assert.EqualValues(t, 257, d.Size)
- assert.EqualValues(t, 326, d.OpenSize)
- assert.Equal(t, "repodata/filelists.xml.gz", d.Location.Href)
- case "other":
- assert.EqualValues(t, 306, d.Size)
- assert.EqualValues(t, 394, d.OpenSize)
- assert.Equal(t, "repodata/other.xml.gz", d.Location.Href)
+ switch d.Type {
+ case "primary":
+ assert.EqualValues(t, 722, d.Size)
+ assert.EqualValues(t, 1759, d.OpenSize)
+ assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href)
+ case "filelists":
+ assert.EqualValues(t, 257, d.Size)
+ assert.EqualValues(t, 326, d.OpenSize)
+ assert.Equal(t, "repodata/filelists.xml.gz", d.Location.Href)
+ case "other":
+ assert.EqualValues(t, 306, d.Size)
+ assert.EqualValues(t, 394, d.OpenSize)
+ assert.Equal(t, "repodata/other.xml.gz", d.Location.Href)
+ }
+ }
+ })
+
+ t.Run("repomd.xml.asc", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req = NewRequest(t, "GET", url+"/repomd.xml.asc")
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----")
+ })
+
+ decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
+ t.Helper()
+
+ zr, err := gzip.NewReader(resp.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, xml.NewDecoder(zr).Decode(v))
}
- }
+
+ t.Run("primary.xml.gz", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req = NewRequest(t, "GET", url+"/primary.xml.gz")
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ type EntryList struct {
+ Entries []*rpm_module.Entry `xml:"entry"`
+ }
+
+ type Metadata struct {
+ XMLName xml.Name `xml:"metadata"`
+ Xmlns string `xml:"xmlns,attr"`
+ XmlnsRpm string `xml:"xmlns:rpm,attr"`
+ PackageCount int `xml:"packages,attr"`
+ Packages []struct {
+ XMLName xml.Name `xml:"package"`
+ Type string `xml:"type,attr"`
+ Name string `xml:"name"`
+ Architecture string `xml:"arch"`
+ Version struct {
+ Epoch string `xml:"epoch,attr"`
+ Version string `xml:"ver,attr"`
+ Release string `xml:"rel,attr"`
+ } `xml:"version"`
+ Checksum struct {
+ Checksum string `xml:",chardata"`
+ Type string `xml:"type,attr"`
+ Pkgid string `xml:"pkgid,attr"`
+ } `xml:"checksum"`
+ Summary string `xml:"summary"`
+ Description string `xml:"description"`
+ Packager string `xml:"packager"`
+ URL string `xml:"url"`
+ Time struct {
+ File uint64 `xml:"file,attr"`
+ Build uint64 `xml:"build,attr"`
+ } `xml:"time"`
+ Size struct {
+ Package int64 `xml:"package,attr"`
+ Installed uint64 `xml:"installed,attr"`
+ Archive uint64 `xml:"archive,attr"`
+ } `xml:"size"`
+ Location struct {
+ Href string `xml:"href,attr"`
+ } `xml:"location"`
+ Format struct {
+ License string `xml:"license"`
+ Vendor string `xml:"vendor"`
+ Group string `xml:"group"`
+ Buildhost string `xml:"buildhost"`
+ Sourcerpm string `xml:"sourcerpm"`
+ Provides EntryList `xml:"provides"`
+ Requires EntryList `xml:"requires"`
+ Conflicts EntryList `xml:"conflicts"`
+ Obsoletes EntryList `xml:"obsoletes"`
+ Files []*rpm_module.File `xml:"file"`
+ } `xml:"format"`
+ } `xml:"package"`
+ }
+
+ var result Metadata
+ decodeGzipXML(t, resp, &result)
+
+ assert.EqualValues(t, 1, result.PackageCount)
+ assert.Len(t, result.Packages, 1)
+ p := result.Packages[0]
+ assert.Equal(t, "rpm", p.Type)
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageArchitecture, p.Architecture)
+ assert.Equal(t, "YES", p.Checksum.Pkgid)
+ assert.Equal(t, "sha256", p.Checksum.Type)
+ assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
+ assert.Equal(t, "https://gitea.io", p.URL)
+ assert.EqualValues(t, len(content), p.Size.Package)
+ assert.EqualValues(t, 13, p.Size.Installed)
+ assert.EqualValues(t, 272, p.Size.Archive)
+ assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
+ f := p.Format
+ assert.Equal(t, "MIT", f.License)
+ assert.Len(t, f.Provides.Entries, 2)
+ assert.Len(t, f.Requires.Entries, 7)
+ assert.Empty(t, f.Conflicts.Entries)
+ assert.Empty(t, f.Obsoletes.Entries)
+ assert.Len(t, f.Files, 1)
+ })
+
+ t.Run("filelists.xml.gz", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req = NewRequest(t, "GET", url+"/filelists.xml.gz")
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ type Filelists struct {
+ XMLName xml.Name `xml:"filelists"`
+ Xmlns string `xml:"xmlns,attr"`
+ PackageCount int `xml:"packages,attr"`
+ Packages []struct {
+ Pkgid string `xml:"pkgid,attr"`
+ Name string `xml:"name,attr"`
+ Architecture string `xml:"arch,attr"`
+ Version struct {
+ Epoch string `xml:"epoch,attr"`
+ Version string `xml:"ver,attr"`
+ Release string `xml:"rel,attr"`
+ } `xml:"version"`
+ Files []*rpm_module.File `xml:"file"`
+ } `xml:"package"`
+ }
+
+ var result Filelists
+ decodeGzipXML(t, resp, &result)
+
+ assert.EqualValues(t, 1, result.PackageCount)
+ assert.Len(t, result.Packages, 1)
+ p := result.Packages[0]
+ assert.NotEmpty(t, p.Pkgid)
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageArchitecture, p.Architecture)
+ assert.Len(t, p.Files, 1)
+ f := p.Files[0]
+ assert.Equal(t, "/usr/local/bin/hello", f.Path)
+ })
+
+ t.Run("other.xml.gz", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req = NewRequest(t, "GET", url+"/other.xml.gz")
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ type Other struct {
+ XMLName xml.Name `xml:"otherdata"`
+ Xmlns string `xml:"xmlns,attr"`
+ PackageCount int `xml:"packages,attr"`
+ Packages []struct {
+ Pkgid string `xml:"pkgid,attr"`
+ Name string `xml:"name,attr"`
+ Architecture string `xml:"arch,attr"`
+ Version struct {
+ Epoch string `xml:"epoch,attr"`
+ Version string `xml:"ver,attr"`
+ Release string `xml:"rel,attr"`
+ } `xml:"version"`
+ Changelogs []*rpm_module.Changelog `xml:"changelog"`
+ } `xml:"package"`
+ }
+
+ var result Other
+ decodeGzipXML(t, resp, &result)
+
+ assert.EqualValues(t, 1, result.PackageCount)
+ assert.Len(t, result.Packages, 1)
+ p := result.Packages[0]
+ assert.NotEmpty(t, p.Pkgid)
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageArchitecture, p.Architecture)
+ assert.Len(t, p.Changelogs, 1)
+ c := p.Changelogs[0]
+ assert.Equal(t, "KN4CK3R ", c.Author)
+ assert.EqualValues(t, 1678276800, c.Date)
+ assert.Equal(t, "- Changelog message.", c.Text)
+ })
+ })
+
+ t.Run("Delete", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
+ MakeRequest(t, req, http.StatusUnauthorized)
+
+ req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
+ AddBasicAuth(user.Name)
+ MakeRequest(t, req, http.StatusNoContent)
+
+ pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
+ assert.NoError(t, err)
+ assert.Empty(t, pvs)
+ req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
+ AddBasicAuth(user.Name)
+ MakeRequest(t, req, http.StatusNotFound)
+ })
})
-
- t.Run("repomd.xml.asc", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
-
- req = NewRequest(t, "GET", url+"/repomd.xml.asc")
- resp := MakeRequest(t, req, http.StatusOK)
-
- assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----")
- })
-
- decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
- t.Helper()
-
- zr, err := gzip.NewReader(resp.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, xml.NewDecoder(zr).Decode(v))
- }
-
- t.Run("primary.xml.gz", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
-
- req = NewRequest(t, "GET", url+"/primary.xml.gz")
- resp := MakeRequest(t, req, http.StatusOK)
-
- type EntryList struct {
- Entries []*rpm_module.Entry `xml:"entry"`
- }
-
- type Metadata struct {
- XMLName xml.Name `xml:"metadata"`
- Xmlns string `xml:"xmlns,attr"`
- XmlnsRpm string `xml:"xmlns:rpm,attr"`
- PackageCount int `xml:"packages,attr"`
- Packages []struct {
- XMLName xml.Name `xml:"package"`
- Type string `xml:"type,attr"`
- Name string `xml:"name"`
- Architecture string `xml:"arch"`
- Version struct {
- Epoch string `xml:"epoch,attr"`
- Version string `xml:"ver,attr"`
- Release string `xml:"rel,attr"`
- } `xml:"version"`
- Checksum struct {
- Checksum string `xml:",chardata"`
- Type string `xml:"type,attr"`
- Pkgid string `xml:"pkgid,attr"`
- } `xml:"checksum"`
- Summary string `xml:"summary"`
- Description string `xml:"description"`
- Packager string `xml:"packager"`
- URL string `xml:"url"`
- Time struct {
- File uint64 `xml:"file,attr"`
- Build uint64 `xml:"build,attr"`
- } `xml:"time"`
- Size struct {
- Package int64 `xml:"package,attr"`
- Installed uint64 `xml:"installed,attr"`
- Archive uint64 `xml:"archive,attr"`
- } `xml:"size"`
- Location struct {
- Href string `xml:"href,attr"`
- } `xml:"location"`
- Format struct {
- License string `xml:"license"`
- Vendor string `xml:"vendor"`
- Group string `xml:"group"`
- Buildhost string `xml:"buildhost"`
- Sourcerpm string `xml:"sourcerpm"`
- Provides EntryList `xml:"provides"`
- Requires EntryList `xml:"requires"`
- Conflicts EntryList `xml:"conflicts"`
- Obsoletes EntryList `xml:"obsoletes"`
- Files []*rpm_module.File `xml:"file"`
- } `xml:"format"`
- } `xml:"package"`
- }
-
- var result Metadata
- decodeGzipXML(t, resp, &result)
-
- assert.EqualValues(t, 1, result.PackageCount)
- assert.Len(t, result.Packages, 1)
- p := result.Packages[0]
- assert.Equal(t, "rpm", p.Type)
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageArchitecture, p.Architecture)
- assert.Equal(t, "YES", p.Checksum.Pkgid)
- assert.Equal(t, "sha256", p.Checksum.Type)
- assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
- assert.Equal(t, "https://gitea.io", p.URL)
- assert.EqualValues(t, len(content), p.Size.Package)
- assert.EqualValues(t, 13, p.Size.Installed)
- assert.EqualValues(t, 272, p.Size.Archive)
- assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
- f := p.Format
- assert.Equal(t, "MIT", f.License)
- assert.Len(t, f.Provides.Entries, 2)
- assert.Len(t, f.Requires.Entries, 7)
- assert.Empty(t, f.Conflicts.Entries)
- assert.Empty(t, f.Obsoletes.Entries)
- assert.Len(t, f.Files, 1)
- })
-
- t.Run("filelists.xml.gz", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
-
- req = NewRequest(t, "GET", url+"/filelists.xml.gz")
- resp := MakeRequest(t, req, http.StatusOK)
-
- type Filelists struct {
- XMLName xml.Name `xml:"filelists"`
- Xmlns string `xml:"xmlns,attr"`
- PackageCount int `xml:"packages,attr"`
- Packages []struct {
- Pkgid string `xml:"pkgid,attr"`
- Name string `xml:"name,attr"`
- Architecture string `xml:"arch,attr"`
- Version struct {
- Epoch string `xml:"epoch,attr"`
- Version string `xml:"ver,attr"`
- Release string `xml:"rel,attr"`
- } `xml:"version"`
- Files []*rpm_module.File `xml:"file"`
- } `xml:"package"`
- }
-
- var result Filelists
- decodeGzipXML(t, resp, &result)
-
- assert.EqualValues(t, 1, result.PackageCount)
- assert.Len(t, result.Packages, 1)
- p := result.Packages[0]
- assert.NotEmpty(t, p.Pkgid)
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageArchitecture, p.Architecture)
- assert.Len(t, p.Files, 1)
- f := p.Files[0]
- assert.Equal(t, "/usr/local/bin/hello", f.Path)
- })
-
- t.Run("other.xml.gz", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
-
- req = NewRequest(t, "GET", url+"/other.xml.gz")
- resp := MakeRequest(t, req, http.StatusOK)
-
- type Other struct {
- XMLName xml.Name `xml:"otherdata"`
- Xmlns string `xml:"xmlns,attr"`
- PackageCount int `xml:"packages,attr"`
- Packages []struct {
- Pkgid string `xml:"pkgid,attr"`
- Name string `xml:"name,attr"`
- Architecture string `xml:"arch,attr"`
- Version struct {
- Epoch string `xml:"epoch,attr"`
- Version string `xml:"ver,attr"`
- Release string `xml:"rel,attr"`
- } `xml:"version"`
- Changelogs []*rpm_module.Changelog `xml:"changelog"`
- } `xml:"package"`
- }
-
- var result Other
- decodeGzipXML(t, resp, &result)
-
- assert.EqualValues(t, 1, result.PackageCount)
- assert.Len(t, result.Packages, 1)
- p := result.Packages[0]
- assert.NotEmpty(t, p.Pkgid)
- assert.Equal(t, packageName, p.Name)
- assert.Equal(t, packageArchitecture, p.Architecture)
- assert.Len(t, p.Changelogs, 1)
- c := p.Changelogs[0]
- assert.Equal(t, "KN4CK3R ", c.Author)
- assert.EqualValues(t, 1678276800, c.Date)
- assert.Equal(t, "- Changelog message.", c.Text)
- })
- })
-
- t.Run("Delete", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
-
- req := NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
- MakeRequest(t, req, http.StatusUnauthorized)
-
- req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)).
- AddBasicAuth(user.Name)
- MakeRequest(t, req, http.StatusNoContent)
-
- pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
- assert.NoError(t, err)
- assert.Empty(t, pvs)
- req = NewRequest(t, "DELETE", fmt.Sprintf("%s/el9/stable/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture)).
- AddBasicAuth(user.Name)
- MakeRequest(t, req, http.StatusNotFound)
- })
+ }
}
From 07ba4d9f87cf21b7ce87158ae5651cae3bb35604 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Fri, 19 Jan 2024 23:05:49 +0900
Subject: [PATCH 06/22] Fix incorrect action duration time when rerun the job
before executed once (#28364)
Fix #28323
Reason was mentioned here:
https://github.com/go-gitea/gitea/issues/28323#issuecomment-1841867298
### Changes: (maybe breaking)
We can rerun jobs in Gitea, so there will be some problems in
calculating duration time.
In this PR, I use the exist `Started` and `Stopped` column to record the
last run time instead of the total time,
and add a new `PreviousDuration` column to record the previous duration
time.
You can also check the cost time of last run:

---
models/actions/run.go | 13 ++++++++-----
models/migrations/migrations.go | 2 ++
models/migrations/v1_22/v285.go | 18 ++++++++++++++++++
routers/web/repo/actions/view.go | 11 +++++++++++
4 files changed, 39 insertions(+), 5 deletions(-)
create mode 100644 models/migrations/v1_22/v285.go
diff --git a/models/actions/run.go b/models/actions/run.go
index 1a3701b0b0..fcac58d515 100644
--- a/models/actions/run.go
+++ b/models/actions/run.go
@@ -46,10 +46,13 @@ type ActionRun struct {
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
Status Status `xorm:"index"`
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
- Started timeutil.TimeStamp
- Stopped timeutil.TimeStamp
- Created timeutil.TimeStamp `xorm:"created"`
- Updated timeutil.TimeStamp `xorm:"updated"`
+ // Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0
+ Started timeutil.TimeStamp
+ Stopped timeutil.TimeStamp
+ // PreviousDuration is used for recording previous duration
+ PreviousDuration time.Duration
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
}
func init() {
@@ -118,7 +121,7 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error {
}
func (run *ActionRun) Duration() time.Duration {
- return calculateDuration(run.Started, run.Stopped, run.Status)
+ return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration
}
func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 3b4ac24a2c..21675cab8a 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -554,6 +554,8 @@ var migrations = []Migration{
NewMigration("Add combined Index to issue_user.uid and issue_id", v1_22.AddCombinedIndexToIssueUser),
// v284 -> v285
NewMigration("Add ignore stale approval column on branch table", v1_22.AddIgnoreStaleApprovalsColumnToProtectedBranchTable),
+ // v285 -> v286
+ NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_22/v285.go b/models/migrations/v1_22/v285.go
new file mode 100644
index 0000000000..c0dacd40bc
--- /dev/null
+++ b/models/migrations/v1_22/v285.go
@@ -0,0 +1,18 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "time"
+
+ "xorm.io/xorm"
+)
+
+func AddPreviousDurationToActionRun(x *xorm.Engine) error {
+ type ActionRun struct {
+ PreviousDuration time.Duration
+ }
+
+ return x.Sync(&ActionRun{})
+}
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 1cdae32a32..9cda30d23d 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -279,6 +279,17 @@ func Rerun(ctx *context_module.Context) {
return
}
+ // reset run's start and stop time when it is done
+ if run.Status.IsDone() {
+ run.PreviousDuration = run.Duration()
+ run.Started = 0
+ run.Stopped = 0
+ if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "previous_duration"); err != nil {
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ }
+
job, jobs := getRunJobs(ctx, runIndex, jobIndex)
if ctx.Written() {
return
From d68a613ba8fd860863a3465b5b5945b191b87b25 Mon Sep 17 00:00:00 2001
From: Adam Majer
Date: Fri, 19 Jan 2024 16:05:02 +0000
Subject: [PATCH 07/22] Add support for sha256 repositories (#23894)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently only SHA1 repositories are supported by Gitea. This adds
support for alternate SHA256 with the additional aim of easier support
for additional hash types in the future.
Fixes: #13794
Limited by: https://github.com/go-git/go-git/issues/899
Depend on: #28138
---------
Co-authored-by: Lunny Xiao
Co-authored-by: 6543 <6543@obermui.de>
---
models/git/commit_status.go | 2 +-
models/issues/comment.go | 2 +-
models/issues/pull.go | 4 +-
models/issues/review.go | 2 +-
.../Test_RepositoryFormat/repository.yml | 11 +
models/migrations/migrations.go | 2 +
models/migrations/v1_22/v286.go | 104 ++++++++++
models/migrations/v1_22/v286_test.go | 62 ++++++
models/pull/review_state.go | 2 +-
models/repo/archiver.go | 2 +-
models/repo/release.go | 2 +-
models/repo/repo.go | 6 +-
models/repo/repo_indexer.go | 2 +-
modules/git/blame_sha256_test.go | 144 +++++++++++++
modules/git/commit_reader.go | 2 +
modules/git/commit_sha256_test.go | 195 ++++++++++++++++++
modules/git/git.go | 8 +-
modules/git/object_format.go | 65 +++++-
modules/git/object_id.go | 16 ++
modules/git/object_id_gogit.go | 2 +
modules/git/repo.go | 13 +-
modules/git/repo_base.go | 2 +
modules/git/repo_base_gogit.go | 4 +
modules/git/repo_base_nogogit.go | 4 +
.../git/tests/repos/repo1_bare_sha256/HEAD | 1 +
.../git/tests/repos/repo1_bare_sha256/config | 6 +
.../tests/repos/repo1_bare_sha256/description | 1 +
.../repos/repo1_bare_sha256/info/exclude | 6 +
.../tests/repos/repo1_bare_sha256/info/refs | 7 +
.../objects/info/commit-graph | Bin 0 -> 2048 bytes
.../repo1_bare_sha256/objects/info/packs | 2 +
...65970b0c38c6b495d5fc034bc7a7b95334b.bitmap | Bin 0 -> 710 bytes
...78665970b0c38c6b495d5fc034bc7a7b95334b.idx | Bin 0 -> 2576 bytes
...8665970b0c38c6b495d5fc034bc7a7b95334b.pack | Bin 0 -> 5656 bytes
...78665970b0c38c6b495d5fc034bc7a7b95334b.rev | Bin 0 -> 224 bytes
.../tests/repos/repo1_bare_sha256/packed-refs | 8 +
.../repos/repo1_bare_sha256/refs/heads/main | 1 +
.../git/tests/repos/repo5_pulls_sha256/HEAD | 1 +
.../git/tests/repos/repo5_pulls_sha256/config | 6 +
.../repos/repo5_pulls_sha256/description | 1 +
.../tests/repos/repo5_pulls_sha256/info/refs | 4 +
.../objects/info/commit-graph | Bin 0 -> 1544 bytes
.../repo5_pulls_sha256/objects/info/packs | 2 +
...45d4a02b788eb26c31022a362312660a29d.bitmap | Bin 0 -> 414 bytes
...fe145d4a02b788eb26c31022a362312660a29d.idx | Bin 0 -> 1736 bytes
...e145d4a02b788eb26c31022a362312660a29d.pack | Bin 0 -> 3140 bytes
...fe145d4a02b788eb26c31022a362312660a29d.rev | Bin 0 -> 140 bytes
.../repos/repo5_pulls_sha256/packed-refs | 5 +
.../repos/repo5_pulls_sha256/refs/heads/main | 1 +
.../git/tests/repos/repo6_blame_sha256/HEAD | 1 +
.../git/tests/repos/repo6_blame_sha256/config | 6 +
.../repos/repo6_blame_sha256/description | 1 +
.../repos/repo6_blame_sha256/info/exclude | 6 +
.../tests/repos/repo6_blame_sha256/info/refs | 1 +
.../objects/info/commit-graph | Bin 0 -> 1376 bytes
.../repo6_blame_sha256/objects/info/packs | 2 +
...1ac24312a5ffc3d3703d7c5cc906bdaee8e.bitmap | Bin 0 -> 318 bytes
...b611ac24312a5ffc3d3703d7c5cc906bdaee8e.idx | Bin 0 -> 1456 bytes
...611ac24312a5ffc3d3703d7c5cc906bdaee8e.pack | Bin 0 -> 904 bytes
...b611ac24312a5ffc3d3703d7c5cc906bdaee8e.rev | Bin 0 -> 112 bytes
.../repos/repo6_blame_sha256/packed-refs | 2 +
.../repos/repo6_blame_sha256/refs/refs/main | 1 +
.../git/tests/repos/repo6_merge_sha256/HEAD | 1 +
.../git/tests/repos/repo6_merge_sha256/config | 6 +
.../repos/repo6_merge_sha256/description | 1 +
.../repos/repo6_merge_sha256/info/exclude | 6 +
.../tests/repos/repo6_merge_sha256/info/refs | 4 +
.../objects/info/commit-graph | Bin 0 -> 1564 bytes
.../repo6_merge_sha256/objects/info/packs | 3 +
...096c7530f577d5c0a79c63d9ac2b44f7510.bitmap | Bin 0 -> 410 bytes
...b6a096c7530f577d5c0a79c63d9ac2b44f7510.idx | Bin 0 -> 1696 bytes
...6a096c7530f577d5c0a79c63d9ac2b44f7510.pack | Bin 0 -> 1556 bytes
...b6a096c7530f577d5c0a79c63d9ac2b44f7510.rev | Bin 0 -> 136 bytes
...e67bb2a4a5501ca1fa3528fad8a9474fba7e50.idx | Bin 0 -> 1176 bytes
...bb2a4a5501ca1fa3528fad8a9474fba7e50.mtimes | Bin 0 -> 84 bytes
...67bb2a4a5501ca1fa3528fad8a9474fba7e50.pack | Bin 0 -> 447 bytes
...e67bb2a4a5501ca1fa3528fad8a9474fba7e50.rev | Bin 0 -> 84 bytes
.../repos/repo6_merge_sha256/packed-refs | 5 +
.../repos/repo6_merge_sha256/refs/heads/main | 1 +
modules/markup/html.go | 36 ++--
modules/markup/html_internal_test.go | 6 +-
modules/references/references.go | 2 +-
modules/references/references_test.go | 2 +-
modules/structs/repo.go | 6 +
modules/templates/util_string.go | 4 +
options/locale/locale_en-US.ini | 3 +
routers/api/v1/repo/repo.go | 2 +-
routers/web/githttp.go | 6 +-
routers/web/repo/repo.go | 3 +-
routers/web/web.go | 14 +-
services/repository/create.go | 4 +
services/repository/fork.go | 23 ++-
templates/admin/repo/list.tmpl | 3 +
templates/explore/repo_list.tmpl | 3 +
templates/repo/commits_list.tmpl | 2 +-
templates/repo/create.tmpl | 13 ++
templates/repo/header.tmpl | 3 +
templates/swagger/v1_json.tmpl | 18 ++
98 files changed, 834 insertions(+), 76 deletions(-)
create mode 100644 models/migrations/fixtures/Test_RepositoryFormat/repository.yml
create mode 100644 models/migrations/v1_22/v286.go
create mode 100644 models/migrations/v1_22/v286_test.go
create mode 100644 modules/git/blame_sha256_test.go
create mode 100644 modules/git/commit_sha256_test.go
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/HEAD
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/config
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/description
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/info/exclude
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/info/refs
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/objects/info/commit-graph
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/objects/info/packs
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/objects/pack/pack-c01aa121b9c5e345fe0da2f9be78665970b0c38c6b495d5fc034bc7a7b95334b.bitmap
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/objects/pack/pack-c01aa121b9c5e345fe0da2f9be78665970b0c38c6b495d5fc034bc7a7b95334b.idx
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/objects/pack/pack-c01aa121b9c5e345fe0da2f9be78665970b0c38c6b495d5fc034bc7a7b95334b.pack
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/objects/pack/pack-c01aa121b9c5e345fe0da2f9be78665970b0c38c6b495d5fc034bc7a7b95334b.rev
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/packed-refs
create mode 100644 modules/git/tests/repos/repo1_bare_sha256/refs/heads/main
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/HEAD
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/config
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/description
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/info/refs
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/objects/info/commit-graph
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/objects/info/packs
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/objects/pack/pack-bfe8f09d42ef5dd1610bf42641fe145d4a02b788eb26c31022a362312660a29d.bitmap
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/objects/pack/pack-bfe8f09d42ef5dd1610bf42641fe145d4a02b788eb26c31022a362312660a29d.idx
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/objects/pack/pack-bfe8f09d42ef5dd1610bf42641fe145d4a02b788eb26c31022a362312660a29d.pack
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/objects/pack/pack-bfe8f09d42ef5dd1610bf42641fe145d4a02b788eb26c31022a362312660a29d.rev
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/packed-refs
create mode 100644 modules/git/tests/repos/repo5_pulls_sha256/refs/heads/main
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/HEAD
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/config
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/description
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/info/exclude
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/info/refs
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/objects/info/commit-graph
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/objects/info/packs
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/objects/pack/pack-fcb8a221b76025fd8415d3c562b611ac24312a5ffc3d3703d7c5cc906bdaee8e.bitmap
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/objects/pack/pack-fcb8a221b76025fd8415d3c562b611ac24312a5ffc3d3703d7c5cc906bdaee8e.idx
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/objects/pack/pack-fcb8a221b76025fd8415d3c562b611ac24312a5ffc3d3703d7c5cc906bdaee8e.pack
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/objects/pack/pack-fcb8a221b76025fd8415d3c562b611ac24312a5ffc3d3703d7c5cc906bdaee8e.rev
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/packed-refs
create mode 100644 modules/git/tests/repos/repo6_blame_sha256/refs/refs/main
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/HEAD
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/config
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/description
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/info/exclude
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/info/refs
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/info/commit-graph
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/info/packs
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-2fff0848f8d8eab8f7902ac91ab6a096c7530f577d5c0a79c63d9ac2b44f7510.bitmap
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-2fff0848f8d8eab8f7902ac91ab6a096c7530f577d5c0a79c63d9ac2b44f7510.idx
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-2fff0848f8d8eab8f7902ac91ab6a096c7530f577d5c0a79c63d9ac2b44f7510.pack
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-2fff0848f8d8eab8f7902ac91ab6a096c7530f577d5c0a79c63d9ac2b44f7510.rev
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-65162b86afdbac3c566696d487e67bb2a4a5501ca1fa3528fad8a9474fba7e50.idx
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-65162b86afdbac3c566696d487e67bb2a4a5501ca1fa3528fad8a9474fba7e50.mtimes
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-65162b86afdbac3c566696d487e67bb2a4a5501ca1fa3528fad8a9474fba7e50.pack
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/objects/pack/pack-65162b86afdbac3c566696d487e67bb2a4a5501ca1fa3528fad8a9474fba7e50.rev
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/packed-refs
create mode 100644 modules/git/tests/repos/repo6_merge_sha256/refs/heads/main
diff --git a/models/git/commit_status.go b/models/git/commit_status.go
index c126d17f20..1118b6cc8c 100644
--- a/models/git/commit_status.go
+++ b/models/git/commit_status.go
@@ -37,7 +37,7 @@ type CommitStatus struct {
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
TargetURL string `xorm:"TEXT"`
Description string `xorm:"TEXT"`
- ContextHash string `xorm:"char(40) index"`
+ ContextHash string `xorm:"VARCHAR(64) index"`
Context string `xorm:"TEXT"`
Creator *user_model.User `xorm:"-"`
CreatorID int64
diff --git a/models/issues/comment.go b/models/issues/comment.go
index a1698d4824..8a3bae5b88 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -270,7 +270,7 @@ type Comment struct {
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message
- CommitSHA string `xorm:"VARCHAR(40)"`
+ CommitSHA string `xorm:"VARCHAR(64)"`
Attachments []*repo_model.Attachment `xorm:"-"`
Reactions ReactionList `xorm:"-"`
diff --git a/models/issues/pull.go b/models/issues/pull.go
index 614ee9a87c..4ae6e38ae1 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -171,11 +171,11 @@ type PullRequest struct {
HeadBranch string
HeadCommitID string `xorm:"-"`
BaseBranch string
- MergeBase string `xorm:"VARCHAR(40)"`
+ MergeBase string `xorm:"VARCHAR(64)"`
AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
HasMerged bool `xorm:"INDEX"`
- MergedCommitID string `xorm:"VARCHAR(40)"`
+ MergedCommitID string `xorm:"VARCHAR(64)"`
MergerID int64 `xorm:"INDEX"`
Merger *user_model.User `xorm:"-"`
MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"`
diff --git a/models/issues/review.go b/models/issues/review.go
index 4cbfa4f443..f2022ae0aa 100644
--- a/models/issues/review.go
+++ b/models/issues/review.go
@@ -116,7 +116,7 @@ type Review struct {
Content string `xorm:"TEXT"`
// Official is a review made by an assigned approver (counts towards approval)
Official bool `xorm:"NOT NULL DEFAULT false"`
- CommitID string `xorm:"VARCHAR(40)"`
+ CommitID string `xorm:"VARCHAR(64)"`
Stale bool `xorm:"NOT NULL DEFAULT false"`
Dismissed bool `xorm:"NOT NULL DEFAULT false"`
diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repository.yml b/models/migrations/fixtures/Test_RepositoryFormat/repository.yml
new file mode 100644
index 0000000000..5a3675917c
--- /dev/null
+++ b/models/migrations/fixtures/Test_RepositoryFormat/repository.yml
@@ -0,0 +1,11 @@
+# type Repository struct {
+# ID int64 `xorm:"pk autoincr"`
+# }
+-
+ id: 1
+-
+ id: 2
+-
+ id: 3
+-
+ id: 10
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 21675cab8a..beb1f3bb96 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -556,6 +556,8 @@ var migrations = []Migration{
NewMigration("Add ignore stale approval column on branch table", v1_22.AddIgnoreStaleApprovalsColumnToProtectedBranchTable),
// v285 -> v286
NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
+ // v286 -> v287
+ NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go
new file mode 100644
index 0000000000..ef19f64221
--- /dev/null
+++ b/models/migrations/v1_22/v286.go
@@ -0,0 +1,104 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+package v1_22 //nolint
+
+import (
+ "errors"
+ "fmt"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "xorm.io/xorm"
+)
+
+func expandHashReferencesToSha256(x *xorm.Engine) error {
+ alteredTables := [][2]string{
+ {"commit_status", "context_hash"},
+ {"comment", "commit_sha"},
+ {"pull_request", "merge_base"},
+ {"pull_request", "merged_commit_id"},
+ {"review", "commit_id"},
+ {"review_state", "commit_sha"},
+ {"repo_archiver", "commit_id"},
+ {"release", "sha1"},
+ {"repo_indexer_status", "commit_sha"},
+ }
+
+ db := x.NewSession()
+ defer db.Close()
+
+ if err := db.Begin(); err != nil {
+ return err
+ }
+
+ if !setting.Database.Type.IsSQLite3() {
+ if setting.Database.Type.IsMSSQL() {
+ // drop indexes that need to be re-created afterwards
+ droppedIndexes := []string{
+ "DROP INDEX commit_status.IDX_commit_status_context_hash",
+ "DROP INDEX review_state.UQE_review_state_pull_commit_user",
+ "DROP INDEX repo_archiver.UQE_repo_archiver_s",
+ }
+ for _, s := range droppedIndexes {
+ _, err := db.Exec(s)
+ if err != nil {
+ return errors.New(s + " " + err.Error())
+ }
+ }
+ }
+
+ for _, alts := range alteredTables {
+ var err error
+ if setting.Database.Type.IsMySQL() {
+ _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
+ } else if setting.Database.Type.IsMSSQL() {
+ _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
+ } else {
+ _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1]))
+ }
+ if err != nil {
+ return fmt.Errorf("alter column '%s' of table '%s' failed: %w", alts[1], alts[0], err)
+ }
+ }
+
+ if setting.Database.Type.IsMSSQL() {
+ recreateIndexes := []string{
+ "CREATE INDEX IDX_commit_status_context_hash ON commit_status(context_hash)",
+ "CREATE UNIQUE INDEX UQE_review_state_pull_commit_user ON review_state(user_id, pull_id, commit_sha)",
+ "CREATE UNIQUE INDEX UQE_repo_archiver_s ON repo_archiver(repo_id, type, commit_id)",
+ }
+ for _, s := range recreateIndexes {
+ _, err := db.Exec(s)
+ if err != nil {
+ return errors.New(s + " " + err.Error())
+ }
+ }
+ }
+ }
+ log.Debug("Updated database tables to hold SHA256 git hash references")
+
+ return db.Commit()
+}
+
+func addObjectFormatNameToRepository(x *xorm.Engine) error {
+ type Repository struct {
+ ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"`
+ }
+
+ if err := x.Sync(new(Repository)); err != nil {
+ return err
+ }
+
+ // Here to catch weird edge-cases where column constraints above are
+ // not applied by the DB backend
+ _, err := x.Exec("UPDATE repository set object_format_name = 'sha1' WHERE object_format_name = '' or object_format_name IS NULL")
+ return err
+}
+
+func AdjustDBForSha256(x *xorm.Engine) error {
+ if err := expandHashReferencesToSha256(x); err != nil {
+ return err
+ }
+ return addObjectFormatNameToRepository(x)
+}
diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go
new file mode 100644
index 0000000000..e36a18a116
--- /dev/null
+++ b/models/migrations/v1_22/v286_test.go
@@ -0,0 +1,62 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "xorm.io/xorm"
+)
+
+func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) {
+ type Repository struct { // old struct
+ ID int64 `xorm:"pk autoincr"`
+ }
+
+ // Prepare and load the testing database
+ return base.PrepareTestEnv(t, 0, new(Repository))
+}
+
+func Test_RepositoryFormat(t *testing.T) {
+ x, deferable := PrepareOldRepository(t)
+ defer deferable()
+
+ type Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ ObjectFormatName string `xorg:"not null default('sha1')"`
+ }
+
+ repo := new(Repository)
+
+ // check we have some records to migrate
+ count, err := x.Count(new(Repository))
+ assert.NoError(t, err)
+ assert.EqualValues(t, 4, count)
+
+ assert.NoError(t, AdjustDBForSha256(x))
+
+ repo.ID = 20
+ repo.ObjectFormatName = "sha256"
+ _, err = x.Insert(repo)
+ assert.NoError(t, err)
+
+ count, err = x.Count(new(Repository))
+ assert.NoError(t, err)
+ assert.EqualValues(t, 5, count)
+
+ repo = new(Repository)
+ ok, err := x.ID(2).Get(repo)
+ assert.NoError(t, err)
+ assert.EqualValues(t, true, ok)
+ assert.EqualValues(t, "sha1", repo.ObjectFormatName)
+
+ repo = new(Repository)
+ ok, err = x.ID(20).Get(repo)
+ assert.NoError(t, err)
+ assert.EqualValues(t, true, ok)
+ assert.EqualValues(t, "sha256", repo.ObjectFormatName)
+}
diff --git a/models/pull/review_state.go b/models/pull/review_state.go
index 1a2b1e165f..e46a22a49d 100644
--- a/models/pull/review_state.go
+++ b/models/pull/review_state.go
@@ -39,7 +39,7 @@ type ReviewState struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL UNIQUE(pull_commit_user)"`
PullID int64 `xorm:"NOT NULL INDEX UNIQUE(pull_commit_user) DEFAULT 0"` // Which PR was the review on?
- CommitSHA string `xorm:"NOT NULL VARCHAR(40) UNIQUE(pull_commit_user)"` // Which commit was the head commit for the review?
+ CommitSHA string `xorm:"NOT NULL VARCHAR(64) UNIQUE(pull_commit_user)"` // Which commit was the head commit for the review?
UpdatedFiles map[string]ViewedState `xorm:"NOT NULL LONGTEXT JSON"` // Stores for each of the changed files of a PR whether they have been viewed, changed since last viewed, or not viewed
UpdatedUnix timeutil.TimeStamp `xorm:"updated"` // Is an accurate indicator of the order of commits as we do not expect it to be possible to make reviews on previous commits
}
diff --git a/models/repo/archiver.go b/models/repo/archiver.go
index d9520c670c..14ffa1d89b 100644
--- a/models/repo/archiver.go
+++ b/models/repo/archiver.go
@@ -33,7 +33,7 @@ type RepoArchiver struct { //revive:disable-line:exported
RepoID int64 `xorm:"index unique(s)"`
Type git.ArchiveType `xorm:"unique(s)"`
Status ArchiverStatus
- CommitID string `xorm:"VARCHAR(40) unique(s)"`
+ CommitID string `xorm:"VARCHAR(64) unique(s)"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
}
diff --git a/models/repo/release.go b/models/repo/release.go
index 72a73f8e80..1f37f11b2e 100644
--- a/models/repo/release.go
+++ b/models/repo/release.go
@@ -75,7 +75,7 @@ type Release struct {
Target string
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
Title string
- Sha1 string `xorm:"VARCHAR(40)"`
+ Sha1 string `xorm:"VARCHAR(64)"`
NumCommits int64
NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"`
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 3695e1f78b..4401041cdd 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -180,7 +180,7 @@ type Repository struct {
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"`
- ObjectFormatName string `xorm:"-"`
+ ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"`
TrustModel TrustModelType
@@ -276,10 +276,6 @@ func (repo *Repository) AfterLoad() {
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns
-
- // this is a temporary behaviour to support old repos, next step is to store the object format in the database
- // and read from database so this line could be removed. To not depend on git module, we use a constant variable here
- repo.ObjectFormatName = "sha1"
}
// LoadAttributes loads attributes of the repository.
diff --git a/models/repo/repo_indexer.go b/models/repo/repo_indexer.go
index bad1248b40..6e19d8f937 100644
--- a/models/repo/repo_indexer.go
+++ b/models/repo/repo_indexer.go
@@ -27,7 +27,7 @@ const (
type RepoIndexerStatus struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX(s)"`
- CommitSha string `xorm:"VARCHAR(40)"`
+ CommitSha string `xorm:"VARCHAR(64)"`
IndexerType RepoIndexerType `xorm:"INDEX(s) NOT NULL DEFAULT 0"`
}
diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go
new file mode 100644
index 0000000000..01de0454a3
--- /dev/null
+++ b/modules/git/blame_sha256_test.go
@@ -0,0 +1,144 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestReadingBlameOutputSha256(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
+ repo, err := OpenRepository(ctx, "./tests/repos/repo5_pulls_sha256")
+ assert.NoError(t, err)
+ defer repo.Close()
+
+ commit, err := repo.GetCommit("0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345")
+ assert.NoError(t, err)
+
+ parts := []*BlamePart{
+ {
+ Sha: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca",
+ Lines: []string{
+ "# test_repo",
+ "Test repository for testing migration from github to gitea",
+ },
+ },
+ {
+ Sha: "0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345",
+ Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"},
+ PreviousSha: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca",
+ PreviousPath: "README.md",
+ },
+ }
+
+ for _, bypass := range []bool{false, true} {
+ blameReader, err := CreateBlameReader(ctx, Sha256ObjectFormat, "./tests/repos/repo5_pulls_sha256", commit, "README.md", bypass)
+ assert.NoError(t, err)
+ assert.NotNil(t, blameReader)
+ defer blameReader.Close()
+
+ assert.False(t, blameReader.UsesIgnoreRevs())
+
+ for _, part := range parts {
+ actualPart, err := blameReader.NextPart()
+ assert.NoError(t, err)
+ assert.Equal(t, part, actualPart)
+ }
+
+ // make sure all parts have been read
+ actualPart, err := blameReader.NextPart()
+ assert.Nil(t, actualPart)
+ assert.NoError(t, err)
+ }
+ })
+
+ t.Run("With .git-blame-ignore-revs", func(t *testing.T) {
+ repo, err := OpenRepository(ctx, "./tests/repos/repo6_blame_sha256")
+ assert.NoError(t, err)
+ defer repo.Close()
+
+ full := []*BlamePart{
+ {
+ Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
+ Lines: []string{"line", "line"},
+ },
+ {
+ Sha: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe",
+ Lines: []string{"changed line"},
+ PreviousSha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
+ PreviousPath: "blame.txt",
+ },
+ {
+ Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
+ Lines: []string{"line", "line", ""},
+ },
+ }
+
+ cases := []struct {
+ CommitID string
+ UsesIgnoreRevs bool
+ Bypass bool
+ Parts []*BlamePart
+ }{
+ {
+ CommitID: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3",
+ UsesIgnoreRevs: true,
+ Bypass: false,
+ Parts: []*BlamePart{
+ {
+ Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc",
+ Lines: []string{"line", "line", "changed line", "line", "line", ""},
+ },
+ },
+ },
+ {
+ CommitID: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3",
+ UsesIgnoreRevs: false,
+ Bypass: true,
+ Parts: full,
+ },
+ {
+ CommitID: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe",
+ UsesIgnoreRevs: false,
+ Bypass: false,
+ Parts: full,
+ },
+ {
+ CommitID: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe",
+ UsesIgnoreRevs: false,
+ Bypass: false,
+ Parts: full,
+ },
+ }
+
+ for _, c := range cases {
+ commit, err := repo.GetCommit(c.CommitID)
+ assert.NoError(t, err)
+
+ blameReader, err := CreateBlameReader(ctx, repo.objectFormat, "./tests/repos/repo6_blame_sha256", commit, "blame.txt", c.Bypass)
+ assert.NoError(t, err)
+ assert.NotNil(t, blameReader)
+ defer blameReader.Close()
+
+ assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs())
+
+ for _, part := range c.Parts {
+ actualPart, err := blameReader.NextPart()
+ assert.NoError(t, err)
+ assert.Equal(t, part, actualPart)
+ }
+
+ // make sure all parts have been read
+ actualPart, err := blameReader.NextPart()
+ assert.Nil(t, actualPart)
+ assert.NoError(t, err)
+ }
+ })
+}
diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go
index d74bcffed8..4809d6c7ed 100644
--- a/modules/git/commit_reader.go
+++ b/modules/git/commit_reader.go
@@ -85,6 +85,8 @@ readLoop:
commit.Committer.Decode(data)
_, _ = payloadSB.Write(line)
case "gpgsig":
+ fallthrough
+ case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
_, _ = signatureSB.Write(data)
_ = signatureSB.WriteByte('\n')
pgpsig = true
diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go
new file mode 100644
index 0000000000..82112cb409
--- /dev/null
+++ b/modules/git/commit_sha256_test.go
@@ -0,0 +1,195 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+//go:build !gogit
+
+package git
+
+import (
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestCommitsCountSha256(t *testing.T) {
+ bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
+
+ commitsCount, err := CommitsCount(DefaultContext,
+ CommitsCountOptions{
+ RepoPath: bareRepo1Path,
+ Revision: []string{"f004f41359117d319dedd0eaab8c5259ee2263da839dcba33637997458627fdc"},
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, int64(3), commitsCount)
+}
+
+func TestCommitsCountWithoutBaseSha256(t *testing.T) {
+ bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
+
+ commitsCount, err := CommitsCount(DefaultContext,
+ CommitsCountOptions{
+ RepoPath: bareRepo1Path,
+ Not: "main",
+ Revision: []string{"branch1"},
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, int64(2), commitsCount)
+}
+
+func TestGetFullCommitIDSha256(t *testing.T) {
+ bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
+
+ id, err := GetFullCommitID(DefaultContext, bareRepo1Path, "f004f4")
+ assert.NoError(t, err)
+ assert.Equal(t, "f004f41359117d319dedd0eaab8c5259ee2263da839dcba33637997458627fdc", id)
+}
+
+func TestGetFullCommitIDErrorSha256(t *testing.T) {
+ bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
+
+ id, err := GetFullCommitID(DefaultContext, bareRepo1Path, "unknown")
+ assert.Empty(t, id)
+ if assert.Error(t, err) {
+ assert.EqualError(t, err, "object does not exist [id: unknown, rel_path: ]")
+ }
+}
+
+func TestCommitFromReaderSha256(t *testing.T) {
+ commitString := `9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 commit 1114
+tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
+parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
+author Adam Majer 1698676906 +0100
+committer Adam Majer 1698676906 +0100
+gpgsig-sha256 -----BEGIN PGP SIGNATURE-----
+` + " " + `
+ iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz
+ dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd
+ aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK
+ WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx
+ 1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4
+ JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP
+ oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6
+ U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy
+ zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI
+ VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
+ HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
+ 8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
+ =xybZ
+ -----END PGP SIGNATURE-----
+
+signed commit`
+
+ sha := &Sha256Hash{
+ 0x94, 0x33, 0xb2, 0xa6, 0x2b, 0x96, 0x4c, 0x17, 0xa4, 0x48, 0x5a, 0xe1, 0x80, 0xf4, 0x5f, 0x59,
+ 0x5d, 0x3e, 0x69, 0xd3, 0x1b, 0x78, 0x60, 0x87, 0x77, 0x5e, 0x28, 0xc6, 0xb6, 0x39, 0x9d, 0xf0,
+ }
+ gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare_sha256"))
+ assert.NoError(t, err)
+ assert.NotNil(t, gitRepo)
+ defer gitRepo.Close()
+
+ commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
+ assert.NoError(t, err)
+ if !assert.NotNil(t, commitFromReader) {
+ return
+ }
+ assert.EqualValues(t, sha, commitFromReader.ID)
+ assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
+
+iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz
+dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd
+aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK
+WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx
+1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4
+JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP
+oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6
+U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy
+zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI
+VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
+HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
+8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
+=xybZ
+-----END PGP SIGNATURE-----
+`, commitFromReader.Signature.Signature)
+ assert.EqualValues(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
+parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
+author Adam Majer 1698676906 +0100
+committer Adam Majer 1698676906 +0100
+
+signed commit`, commitFromReader.Signature.Payload)
+ assert.EqualValues(t, "Adam Majer ", commitFromReader.Author.String())
+
+ commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
+ assert.NoError(t, err)
+ commitFromReader.CommitMessage += "\n\n"
+ commitFromReader.Signature.Payload += "\n\n"
+ assert.EqualValues(t, commitFromReader, commitFromReader2)
+}
+
+func TestHasPreviousCommitSha256(t *testing.T) {
+ bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256")
+
+ repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
+ assert.NoError(t, err)
+ defer repo.Close()
+
+ commit, err := repo.GetCommit("f004f41359117d319dedd0eaab8c5259ee2263da839dcba33637997458627fdc")
+ assert.NoError(t, err)
+
+ parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c")
+ notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236")
+ assert.Equal(t, repo.objectFormat, parentSHA.Type())
+ assert.Equal(t, repo.objectFormat.Name(), "sha256")
+
+ haz, err := commit.HasPreviousCommit(parentSHA)
+ assert.NoError(t, err)
+ assert.True(t, haz)
+
+ hazNot, err := commit.HasPreviousCommit(notParentSHA)
+ assert.NoError(t, err)
+ assert.False(t, hazNot)
+
+ selfNot, err := commit.HasPreviousCommit(commit.ID)
+ assert.NoError(t, err)
+ assert.False(t, selfNot)
+}
+
+func TestGetCommitFileStatusMergesSha256(t *testing.T) {
+ bareRepo1Path := filepath.Join(testReposDir, "repo6_merge_sha256")
+
+ commitFileStatus, err := GetCommitFileStatus(DefaultContext, bareRepo1Path, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1")
+ assert.NoError(t, err)
+
+ expected := CommitFileStatus{
+ []string{
+ "add_file.txt",
+ },
+ []string{},
+ []string{
+ "to_modify.txt",
+ },
+ }
+
+ assert.Equal(t, expected.Added, commitFileStatus.Added)
+ assert.Equal(t, expected.Removed, commitFileStatus.Removed)
+ assert.Equal(t, expected.Modified, commitFileStatus.Modified)
+
+ expected = CommitFileStatus{
+ []string{},
+ []string{
+ "to_remove.txt",
+ },
+ []string{},
+ }
+
+ commitFileStatus, err = GetCommitFileStatus(DefaultContext, bareRepo1Path, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172")
+ assert.NoError(t, err)
+
+ assert.Equal(t, expected.Added, commitFileStatus.Added)
+ assert.Equal(t, expected.Removed, commitFileStatus.Removed)
+ assert.Equal(t, expected.Modified, commitFileStatus.Modified)
+}
diff --git a/modules/git/git.go b/modules/git/git.go
index 24eff05afc..89c23ff230 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -185,7 +185,13 @@ func InitFull(ctx context.Context) (err error) {
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
}
SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
- SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil
+ SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit
+ if SupportHashSha256 {
+ SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat)
+ } else {
+ log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported")
+ }
+
if setting.LFS.StartServer {
if CheckGitVersionAtLeast("2.1.2") != nil {
return errors.New("LFS server support requires Git >= 2.1.2")
diff --git a/modules/git/object_format.go b/modules/git/object_format.go
index 27771e7459..a056b20e8a 100644
--- a/modules/git/object_format.go
+++ b/modules/git/object_format.go
@@ -5,6 +5,7 @@ package git
import (
"crypto/sha1"
+ "crypto/sha256"
"regexp"
"strconv"
)
@@ -12,6 +13,9 @@ import (
// sha1Pattern can be used to determine if a string is an valid sha
var sha1Pattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
+// sha256Pattern can be used to determine if a string is an valid sha
+var sha256Pattern = regexp.MustCompile(`^[0-9a-f]{4,64}$`)
+
type ObjectFormat interface {
// Name returns the name of the object format
Name() string
@@ -29,11 +33,12 @@ type ObjectFormat interface {
ComputeHash(t ObjectType, content []byte) ObjectID
}
+/* SHA1 Type */
type Sha1ObjectFormatImpl struct{}
var (
- emptyObjectID = &Sha1Hash{}
- emptyTree = &Sha1Hash{
+ emptySha1ObjectID = &Sha1Hash{}
+ emptySha1Tree = &Sha1Hash{
0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60,
0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04,
}
@@ -41,11 +46,11 @@ var (
func (Sha1ObjectFormatImpl) Name() string { return "sha1" }
func (Sha1ObjectFormatImpl) EmptyObjectID() ObjectID {
- return emptyObjectID
+ return emptySha1ObjectID
}
func (Sha1ObjectFormatImpl) EmptyTree() ObjectID {
- return emptyTree
+ return emptySha1Tree
}
func (Sha1ObjectFormatImpl) FullLength() int { return 40 }
func (Sha1ObjectFormatImpl) IsValid(input string) bool {
@@ -72,11 +77,59 @@ func (h Sha1ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID
return &sha1
}
-var Sha1ObjectFormat ObjectFormat = Sha1ObjectFormatImpl{}
+/* SHA256 Type */
+type Sha256ObjectFormatImpl struct{}
+
+var (
+ emptySha256ObjectID = &Sha256Hash{}
+ emptySha256Tree = &Sha256Hash{
+ 0x6e, 0xf1, 0x9b, 0x41, 0x22, 0x5c, 0x53, 0x69, 0xf1, 0xc1,
+ 0x04, 0xd4, 0x5d, 0x8d, 0x85, 0xef, 0xa9, 0xb0, 0x57, 0xb5,
+ 0x3b, 0x14, 0xb4, 0xb9, 0xb9, 0x39, 0xdd, 0x74, 0xde, 0xcc,
+ 0x53, 0x21,
+ }
+)
+
+func (Sha256ObjectFormatImpl) Name() string { return "sha256" }
+func (Sha256ObjectFormatImpl) EmptyObjectID() ObjectID {
+ return emptySha256ObjectID
+}
+
+func (Sha256ObjectFormatImpl) EmptyTree() ObjectID {
+ return emptySha256Tree
+}
+func (Sha256ObjectFormatImpl) FullLength() int { return 64 }
+func (Sha256ObjectFormatImpl) IsValid(input string) bool {
+ return sha256Pattern.MatchString(input)
+}
+
+func (Sha256ObjectFormatImpl) MustID(b []byte) ObjectID {
+ var id Sha256Hash
+ copy(id[0:32], b)
+ return &id
+}
+
+// ComputeHash compute the hash for a given ObjectType and content
+func (h Sha256ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID {
+ hasher := sha256.New()
+ _, _ = hasher.Write(t.Bytes())
+ _, _ = hasher.Write([]byte(" "))
+ _, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
+ _, _ = hasher.Write([]byte{0})
+
+ // HashSum generates a SHA256 for the provided hash
+ var sha256 Sha1Hash
+ copy(sha256[:], hasher.Sum(nil))
+ return &sha256
+}
+
+var (
+ Sha1ObjectFormat ObjectFormat = Sha1ObjectFormatImpl{}
+ Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
+)
var SupportedObjectFormats = []ObjectFormat{
Sha1ObjectFormat,
- // TODO: add sha256
}
func ObjectFormatFromName(name string) ObjectFormat {
diff --git a/modules/git/object_id.go b/modules/git/object_id.go
index 01c23ed3da..4f8c39ee1d 100644
--- a/modules/git/object_id.go
+++ b/modules/git/object_id.go
@@ -16,6 +16,7 @@ type ObjectID interface {
Type() ObjectFormat
}
+/* SHA1 */
type Sha1Hash [20]byte
func (h *Sha1Hash) String() string {
@@ -39,6 +40,21 @@ func MustIDFromString(hexHash string) ObjectID {
return id
}
+/* SHA256 */
+type Sha256Hash [32]byte
+
+func (h *Sha256Hash) String() string {
+ return hex.EncodeToString(h[:])
+}
+
+func (h *Sha256Hash) IsZero() bool {
+ empty := Sha256Hash{}
+ return bytes.Equal(empty[:], h[:])
+}
+func (h *Sha256Hash) RawValue() []byte { return h[:] }
+func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
+
+/* utility */
func NewIDFromString(hexHash string) (ObjectID, error) {
var theObjectFormat ObjectFormat
for _, objectFormat := range SupportedObjectFormats {
diff --git a/modules/git/object_id_gogit.go b/modules/git/object_id_gogit.go
index 0cebb0d50b..db4c4ae0bd 100644
--- a/modules/git/object_id_gogit.go
+++ b/modules/git/object_id_gogit.go
@@ -13,6 +13,8 @@ func ParseGogitHash(h plumbing.Hash) ObjectID {
switch hash.Size {
case 20:
return Sha1ObjectFormat.MustID(h[:])
+ case 32:
+ return Sha256ObjectFormat.MustID(h[:])
}
return nil
diff --git a/modules/git/repo.go b/modules/git/repo.go
index 7ccce0ba20..c3de2eb0ad 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -97,15 +97,12 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
}
cmd := NewCommand(ctx, "init")
- if SupportHashSha256 {
- if objectFormatName == "" {
- objectFormatName = Sha1ObjectFormat.Name()
- }
- if !IsValidObjectFormat(objectFormatName) {
- return fmt.Errorf("invalid object format: %s", objectFormatName)
- }
- cmd.AddOptionValues("--object-format", objectFormatName)
+
+ if !IsValidObjectFormat(objectFormatName) {
+ return fmt.Errorf("invalid object format: %s", objectFormatName)
}
+ cmd.AddOptionValues("--object-format", objectFormatName)
+
if bare {
cmd.AddArguments("--bare")
}
diff --git a/modules/git/repo_base.go b/modules/git/repo_base.go
index 2c6df8b9c4..a9d91d2deb 100644
--- a/modules/git/repo_base.go
+++ b/modules/git/repo_base.go
@@ -8,6 +8,8 @@ import (
"io"
)
+var isGogit bool
+
// contextKey is a value for use with context.WithValue.
type contextKey struct {
name string
diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go
index d0b8e79368..90123ee84b 100644
--- a/modules/git/repo_base_gogit.go
+++ b/modules/git/repo_base_gogit.go
@@ -21,6 +21,10 @@ import (
"github.com/go-git/go-git/v5/storage/filesystem"
)
+func init() {
+ isGogit = true
+}
+
// Repository represents a Git repository.
type Repository struct {
Path string
diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go
index a783366cc1..d5a350a926 100644
--- a/modules/git/repo_base_nogogit.go
+++ b/modules/git/repo_base_nogogit.go
@@ -15,6 +15,10 @@ import (
"code.gitea.io/gitea/modules/log"
)
+func init() {
+ isGogit = false
+}
+
// Repository represents a Git repository.
type Repository struct {
Path string
diff --git a/modules/git/tests/repos/repo1_bare_sha256/HEAD b/modules/git/tests/repos/repo1_bare_sha256/HEAD
new file mode 100644
index 0000000000..b870d82622
--- /dev/null
+++ b/modules/git/tests/repos/repo1_bare_sha256/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/main
diff --git a/modules/git/tests/repos/repo1_bare_sha256/config b/modules/git/tests/repos/repo1_bare_sha256/config
new file mode 100644
index 0000000000..2388a50b2f
--- /dev/null
+++ b/modules/git/tests/repos/repo1_bare_sha256/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 1
+ filemode = true
+ bare = true
+[extensions]
+ objectformat = sha256
diff --git a/modules/git/tests/repos/repo1_bare_sha256/description b/modules/git/tests/repos/repo1_bare_sha256/description
new file mode 100644
index 0000000000..498b267a8c
--- /dev/null
+++ b/modules/git/tests/repos/repo1_bare_sha256/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/modules/git/tests/repos/repo1_bare_sha256/info/exclude b/modules/git/tests/repos/repo1_bare_sha256/info/exclude
new file mode 100644
index 0000000000..a5196d1be8
--- /dev/null
+++ b/modules/git/tests/repos/repo1_bare_sha256/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/modules/git/tests/repos/repo1_bare_sha256/info/refs b/modules/git/tests/repos/repo1_bare_sha256/info/refs
new file mode 100644
index 0000000000..b4de954582
--- /dev/null
+++ b/modules/git/tests/repos/repo1_bare_sha256/info/refs
@@ -0,0 +1,7 @@
+42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236 refs/heads/branch1
+5bc2249e32e0ba40a08879fba2bd4e97a13cb345831549f4bc5649525da8f6cc refs/heads/branch2
+9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 refs/heads/main
+29a82d4fc02e19190fb489cc90d5730ed91970b49f4e39acda2798b3dd4f814e refs/tags/signed-tag
+9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 refs/tags/signed-tag^{}
+171822a62559f3aa28a00aa3785dbe915d6a8eb02712682740db44fc8bd2187a refs/tags/test
+6aae864a3d1d0d6a5be0cc64028c1e7021e2632b031fd8eb82afc5a283d1c3d1 refs/tags/test^{}
diff --git a/modules/git/tests/repos/repo1_bare_sha256/objects/info/commit-graph b/modules/git/tests/repos/repo1_bare_sha256/objects/info/commit-graph
new file mode 100644
index 0000000000000000000000000000000000000000..2985d3e4361fa5d2453795c1d2fa235bc162232b
GIT binary patch
literal 2048
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
V03y}m<@UOrf!d;j&yBtl{0h7u+fV=i
literal 0
HcmV?d00001
diff --git a/modules/git/tests/repos/repo1_bare_sha256/objects/pack/pack-c01aa121b9c5e345fe0da2f9be78665970b0c38c6b495d5fc034bc7a7b95334b.pack b/modules/git/tests/repos/repo1_bare_sha256/objects/pack/pack-c01aa121b9c5e345fe0da2f9be78665970b0c38c6b495d5fc034bc7a7b95334b.pack
new file mode 100644
index 0000000000000000000000000000000000000000..c77bf2024c8938b19faa05798afe14293fd5f6f5
GIT binary patch
literal 5656
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
p_MwB@Q03^{&{Qn;;!@$6XOU3 |