Add support for sha256 repositories (#23894)

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

<img width="776" alt="图片" src="https://github.com/go-gitea/gitea/assets/81045/5448c9a7-608e-4341-a149-5dd0069c9447">

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Adam Majer 2024-01-19 16:05:02 +00:00 committed by GitHub
parent 07ba4d9f87
commit d68a613ba8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
98 changed files with 834 additions and 76 deletions

View file

@ -37,7 +37,7 @@ type CommitStatus struct {
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"` SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
TargetURL string `xorm:"TEXT"` TargetURL string `xorm:"TEXT"`
Description string `xorm:"TEXT"` Description string `xorm:"TEXT"`
ContextHash string `xorm:"char(40) index"` ContextHash string `xorm:"VARCHAR(64) index"`
Context string `xorm:"TEXT"` Context string `xorm:"TEXT"`
Creator *user_model.User `xorm:"-"` Creator *user_model.User `xorm:"-"`
CreatorID int64 CreatorID int64

View file

@ -270,7 +270,7 @@ type Comment struct {
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message // Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"` CommitSHA string `xorm:"VARCHAR(64)"`
Attachments []*repo_model.Attachment `xorm:"-"` Attachments []*repo_model.Attachment `xorm:"-"`
Reactions ReactionList `xorm:"-"` Reactions ReactionList `xorm:"-"`

View file

@ -171,11 +171,11 @@ type PullRequest struct {
HeadBranch string HeadBranch string
HeadCommitID string `xorm:"-"` HeadCommitID string `xorm:"-"`
BaseBranch string BaseBranch string
MergeBase string `xorm:"VARCHAR(40)"` MergeBase string `xorm:"VARCHAR(64)"`
AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"` AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
HasMerged bool `xorm:"INDEX"` HasMerged bool `xorm:"INDEX"`
MergedCommitID string `xorm:"VARCHAR(40)"` MergedCommitID string `xorm:"VARCHAR(64)"`
MergerID int64 `xorm:"INDEX"` MergerID int64 `xorm:"INDEX"`
Merger *user_model.User `xorm:"-"` Merger *user_model.User `xorm:"-"`
MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"` MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"`

View file

@ -116,7 +116,7 @@ type Review struct {
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
// Official is a review made by an assigned approver (counts towards approval) // Official is a review made by an assigned approver (counts towards approval)
Official bool `xorm:"NOT NULL DEFAULT false"` Official bool `xorm:"NOT NULL DEFAULT false"`
CommitID string `xorm:"VARCHAR(40)"` CommitID string `xorm:"VARCHAR(64)"`
Stale bool `xorm:"NOT NULL DEFAULT false"` Stale bool `xorm:"NOT NULL DEFAULT false"`
Dismissed bool `xorm:"NOT NULL DEFAULT false"` Dismissed bool `xorm:"NOT NULL DEFAULT false"`

View file

@ -0,0 +1,11 @@
# type Repository struct {
# ID int64 `xorm:"pk autoincr"`
# }
-
id: 1
-
id: 2
-
id: 3
-
id: 10

View file

@ -556,6 +556,8 @@ var migrations = []Migration{
NewMigration("Add ignore stale approval column on branch table", v1_22.AddIgnoreStaleApprovalsColumnToProtectedBranchTable), NewMigration("Add ignore stale approval column on branch table", v1_22.AddIgnoreStaleApprovalsColumnToProtectedBranchTable),
// v285 -> v286 // v285 -> v286
NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun), 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 // GetCurrentDBVersion returns the current db version

View file

@ -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)
}

View file

@ -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)
}

View file

@ -39,7 +39,7 @@ type ReviewState struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL UNIQUE(pull_commit_user)"` 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? 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 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 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
} }

View file

@ -33,7 +33,7 @@ type RepoArchiver struct { //revive:disable-line:exported
RepoID int64 `xorm:"index unique(s)"` RepoID int64 `xorm:"index unique(s)"`
Type git.ArchiveType `xorm:"unique(s)"` Type git.ArchiveType `xorm:"unique(s)"`
Status ArchiverStatus Status ArchiverStatus
CommitID string `xorm:"VARCHAR(40) unique(s)"` CommitID string `xorm:"VARCHAR(64) unique(s)"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
} }

View file

@ -75,7 +75,7 @@ type Release struct {
Target string Target string
TargetBehind string `xorm:"-"` // to handle non-existing or empty target TargetBehind string `xorm:"-"` // to handle non-existing or empty target
Title string Title string
Sha1 string `xorm:"VARCHAR(40)"` Sha1 string `xorm:"VARCHAR(64)"`
NumCommits int64 NumCommits int64
NumCommitsBehind int64 `xorm:"-"` NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"` Note string `xorm:"TEXT"`

View file

@ -180,7 +180,7 @@ type Repository struct {
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"` Topics []string `xorm:"TEXT JSON"`
ObjectFormatName string `xorm:"-"` ObjectFormatName string `xorm:"VARCHAR(6) NOT NULL DEFAULT 'sha1'"`
TrustModel TrustModelType TrustModel TrustModelType
@ -276,10 +276,6 @@ func (repo *Repository) AfterLoad() {
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns 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. // LoadAttributes loads attributes of the repository.

View file

@ -27,7 +27,7 @@ const (
type RepoIndexerStatus struct { //revive:disable-line:exported type RepoIndexerStatus struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX(s)"` RepoID int64 `xorm:"INDEX(s)"`
CommitSha string `xorm:"VARCHAR(40)"` CommitSha string `xorm:"VARCHAR(64)"`
IndexerType RepoIndexerType `xorm:"INDEX(s) NOT NULL DEFAULT 0"` IndexerType RepoIndexerType `xorm:"INDEX(s) NOT NULL DEFAULT 0"`
} }

View file

@ -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)
}
})
}

View file

@ -85,6 +85,8 @@ readLoop:
commit.Committer.Decode(data) commit.Committer.Decode(data)
_, _ = payloadSB.Write(line) _, _ = payloadSB.Write(line)
case "gpgsig": case "gpgsig":
fallthrough
case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
_, _ = signatureSB.Write(data) _, _ = signatureSB.Write(data)
_ = signatureSB.WriteByte('\n') _ = signatureSB.WriteByte('\n')
pgpsig = true pgpsig = true

View file

@ -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 <amajer@suse.de> 1698676906 +0100
committer Adam Majer <amajer@suse.de> 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 <amajer@suse.de> 1698676906 +0100
committer Adam Majer <amajer@suse.de> 1698676906 +0100
signed commit`, commitFromReader.Signature.Payload)
assert.EqualValues(t, "Adam Majer <amajer@suse.de>", 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)
}

View file

@ -185,7 +185,13 @@ func InitFull(ctx context.Context) (err error) {
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
} }
SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil 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 setting.LFS.StartServer {
if CheckGitVersionAtLeast("2.1.2") != nil { if CheckGitVersionAtLeast("2.1.2") != nil {
return errors.New("LFS server support requires Git >= 2.1.2") return errors.New("LFS server support requires Git >= 2.1.2")

View file

@ -5,6 +5,7 @@ package git
import ( import (
"crypto/sha1" "crypto/sha1"
"crypto/sha256"
"regexp" "regexp"
"strconv" "strconv"
) )
@ -12,6 +13,9 @@ import (
// sha1Pattern can be used to determine if a string is an valid sha // sha1Pattern can be used to determine if a string is an valid sha
var sha1Pattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`) 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 { type ObjectFormat interface {
// Name returns the name of the object format // Name returns the name of the object format
Name() string Name() string
@ -29,11 +33,12 @@ type ObjectFormat interface {
ComputeHash(t ObjectType, content []byte) ObjectID ComputeHash(t ObjectType, content []byte) ObjectID
} }
/* SHA1 Type */
type Sha1ObjectFormatImpl struct{} type Sha1ObjectFormatImpl struct{}
var ( var (
emptyObjectID = &Sha1Hash{} emptySha1ObjectID = &Sha1Hash{}
emptyTree = &Sha1Hash{ emptySha1Tree = &Sha1Hash{
0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60,
0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04, 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04,
} }
@ -41,11 +46,11 @@ var (
func (Sha1ObjectFormatImpl) Name() string { return "sha1" } func (Sha1ObjectFormatImpl) Name() string { return "sha1" }
func (Sha1ObjectFormatImpl) EmptyObjectID() ObjectID { func (Sha1ObjectFormatImpl) EmptyObjectID() ObjectID {
return emptyObjectID return emptySha1ObjectID
} }
func (Sha1ObjectFormatImpl) EmptyTree() ObjectID { func (Sha1ObjectFormatImpl) EmptyTree() ObjectID {
return emptyTree return emptySha1Tree
} }
func (Sha1ObjectFormatImpl) FullLength() int { return 40 } func (Sha1ObjectFormatImpl) FullLength() int { return 40 }
func (Sha1ObjectFormatImpl) IsValid(input string) bool { func (Sha1ObjectFormatImpl) IsValid(input string) bool {
@ -72,11 +77,59 @@ func (h Sha1ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID
return &sha1 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{ var SupportedObjectFormats = []ObjectFormat{
Sha1ObjectFormat, Sha1ObjectFormat,
// TODO: add sha256
} }
func ObjectFormatFromName(name string) ObjectFormat { func ObjectFormatFromName(name string) ObjectFormat {

View file

@ -16,6 +16,7 @@ type ObjectID interface {
Type() ObjectFormat Type() ObjectFormat
} }
/* SHA1 */
type Sha1Hash [20]byte type Sha1Hash [20]byte
func (h *Sha1Hash) String() string { func (h *Sha1Hash) String() string {
@ -39,6 +40,21 @@ func MustIDFromString(hexHash string) ObjectID {
return id 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) { func NewIDFromString(hexHash string) (ObjectID, error) {
var theObjectFormat ObjectFormat var theObjectFormat ObjectFormat
for _, objectFormat := range SupportedObjectFormats { for _, objectFormat := range SupportedObjectFormats {

View file

@ -13,6 +13,8 @@ func ParseGogitHash(h plumbing.Hash) ObjectID {
switch hash.Size { switch hash.Size {
case 20: case 20:
return Sha1ObjectFormat.MustID(h[:]) return Sha1ObjectFormat.MustID(h[:])
case 32:
return Sha256ObjectFormat.MustID(h[:])
} }
return nil return nil

View file

@ -97,15 +97,12 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
} }
cmd := NewCommand(ctx, "init") cmd := NewCommand(ctx, "init")
if SupportHashSha256 {
if objectFormatName == "" {
objectFormatName = Sha1ObjectFormat.Name()
}
if !IsValidObjectFormat(objectFormatName) { if !IsValidObjectFormat(objectFormatName) {
return fmt.Errorf("invalid object format: %s", objectFormatName) return fmt.Errorf("invalid object format: %s", objectFormatName)
} }
cmd.AddOptionValues("--object-format", objectFormatName) cmd.AddOptionValues("--object-format", objectFormatName)
}
if bare { if bare {
cmd.AddArguments("--bare") cmd.AddArguments("--bare")
} }

View file

@ -8,6 +8,8 @@ import (
"io" "io"
) )
var isGogit bool
// contextKey is a value for use with context.WithValue. // contextKey is a value for use with context.WithValue.
type contextKey struct { type contextKey struct {
name string name string

View file

@ -21,6 +21,10 @@ import (
"github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/filesystem"
) )
func init() {
isGogit = true
}
// Repository represents a Git repository. // Repository represents a Git repository.
type Repository struct { type Repository struct {
Path string Path string

View file

@ -15,6 +15,10 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
func init() {
isGogit = false
}
// Repository represents a Git repository. // Repository represents a Git repository.
type Repository struct { type Repository struct {
Path string Path string

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 1
filemode = true
bare = true
[extensions]
objectformat = sha256

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -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]
# *~

View file

@ -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^{}

View file

@ -0,0 +1,2 @@
P pack-c01aa121b9c5e345fe0da2f9be78665970b0c38c6b495d5fc034bc7a7b95334b.pack

View file

@ -0,0 +1,8 @@
# pack-refs with: peeled fully-peeled sorted
42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236 refs/heads/branch1
5bc2249e32e0ba40a08879fba2bd4e97a13cb345831549f4bc5649525da8f6cc refs/heads/branch2
9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 refs/heads/main
29a82d4fc02e19190fb489cc90d5730ed91970b49f4e39acda2798b3dd4f814e refs/tags/signed-tag
^9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0
171822a62559f3aa28a00aa3785dbe915d6a8eb02712682740db44fc8bd2187a refs/tags/test
^6aae864a3d1d0d6a5be0cc64028c1e7021e2632b031fd8eb82afc5a283d1c3d1

View file

@ -0,0 +1 @@
9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 1
filemode = true
bare = true
[extensions]
objectformat = sha256

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -0,0 +1,4 @@
35ecd0f946c8baeb76fa5a3876f46bf35218655e2304d8505026fa4bfb496a4b refs/heads/main
35ecd0f946c8baeb76fa5a3876f46bf35218655e2304d8505026fa4bfb496a4b refs/heads/main-clone
7f50a4906503378b0bbb7d61bd2ca8d8d8ff4f7a2474980f99402d742ccc9665 refs/heads/test-patch-1
1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca refs/tags/v0.9.99

View file

@ -0,0 +1,2 @@
P pack-bfe8f09d42ef5dd1610bf42641fe145d4a02b788eb26c31022a362312660a29d.pack

View file

@ -0,0 +1,5 @@
# pack-refs with: peeled fully-peeled sorted
35ecd0f946c8baeb76fa5a3876f46bf35218655e2304d8505026fa4bfb496a4b refs/heads/main
35ecd0f946c8baeb76fa5a3876f46bf35218655e2304d8505026fa4bfb496a4b refs/heads/main-clone
7f50a4906503378b0bbb7d61bd2ca8d8d8ff4f7a2474980f99402d742ccc9665 refs/heads/test-patch-1
1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca refs/tags/v0.9.99

View file

@ -0,0 +1 @@
35ecd0f946c8baeb76fa5a3876f46bf35218655e2304d8505026fa4bfb496a4b

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 1
filemode = true
bare = true
[extensions]
objectformat = sha256

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -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]
# *~

View file

@ -0,0 +1 @@
e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3 refs/heads/main

View file

@ -0,0 +1,2 @@
P pack-fcb8a221b76025fd8415d3c562b611ac24312a5ffc3d3703d7c5cc906bdaee8e.pack

View file

@ -0,0 +1,2 @@
# pack-refs with: peeled fully-peeled sorted
e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3 refs/heads/main

View file

@ -0,0 +1 @@
e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,6 @@
[core]
repositoryformatversion = 1
filemode = true
bare = true
[extensions]
objectformat = sha256

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -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]
# *~

View file

@ -0,0 +1,4 @@
d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1 refs/heads/main
b45258e9823233edea2d40d183742f29630e1e69300479fb4a55eabfe9b1d8bf refs/heads/merge/add_file
ff2b996e2fa366146300e4c9e51ccb6818147b360e46fa1437334f4a690955ce refs/heads/merge/modify_file
da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172 refs/heads/merge/remove_file

View file

@ -0,0 +1,3 @@
P pack-2fff0848f8d8eab8f7902ac91ab6a096c7530f577d5c0a79c63d9ac2b44f7510.pack
P pack-65162b86afdbac3c566696d487e67bb2a4a5501ca1fa3528fad8a9474fba7e50.pack

View file

@ -0,0 +1,5 @@
# pack-refs with: peeled fully-peeled sorted
d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1 refs/heads/main
b45258e9823233edea2d40d183742f29630e1e69300479fb4a55eabfe9b1d8bf refs/heads/merge/add_file
ff2b996e2fa366146300e4c9e51ccb6818147b360e46fa1437334f4a690955ce refs/heads/merge/modify_file
da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172 refs/heads/merge/remove_file

View file

@ -0,0 +1 @@
d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1

View file

@ -45,19 +45,19 @@ var (
// valid chars in encoded path and parameter: [-+~_%.a-zA-Z0-9/] // valid chars in encoded path and parameter: [-+~_%.a-zA-Z0-9/]
// sha1CurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae // hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
// Although SHA1 hashes are 40 chars long, the regex matches the hash from 7 to 40 chars in length // Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
// so that abbreviated hash links can be used as well. This matches git and GitHub usability. // so that abbreviated hash links can be used as well. This matches git and GitHub usability.
sha1CurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,40})(?:\s|$|\)|\]|[.,](\s|$))`) hashCurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,](\s|$))`)
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax // shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`) shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
// anySHA1Pattern splits url containing SHA into parts // anySHA1Pattern splits url containing SHA into parts
anySHA1Pattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`) anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash" // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,40})(\.\.\.?)([0-9a-f]{7,40})?(#[-+~_%.a-zA-Z0-9]+)?`) comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`)
validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`) validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`)
@ -171,13 +171,13 @@ type processor func(ctx *RenderContext, node *html.Node)
var defaultProcessors = []processor{ var defaultProcessors = []processor{
fullIssuePatternProcessor, fullIssuePatternProcessor,
comparePatternProcessor, comparePatternProcessor,
fullSha1PatternProcessor, fullHashPatternProcessor,
shortLinkProcessor, shortLinkProcessor,
linkProcessor, linkProcessor,
mentionProcessor, mentionProcessor,
issueIndexPatternProcessor, issueIndexPatternProcessor,
commitCrossReferencePatternProcessor, commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor, hashCurrentPatternProcessor,
emailAddressProcessor, emailAddressProcessor,
emojiProcessor, emojiProcessor,
emojiShortCodeProcessor, emojiShortCodeProcessor,
@ -199,12 +199,12 @@ func PostProcess(
var commitMessageProcessors = []processor{ var commitMessageProcessors = []processor{
fullIssuePatternProcessor, fullIssuePatternProcessor,
comparePatternProcessor, comparePatternProcessor,
fullSha1PatternProcessor, fullHashPatternProcessor,
linkProcessor, linkProcessor,
mentionProcessor, mentionProcessor,
issueIndexPatternProcessor, issueIndexPatternProcessor,
commitCrossReferencePatternProcessor, commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor, hashCurrentPatternProcessor,
emailAddressProcessor, emailAddressProcessor,
emojiProcessor, emojiProcessor,
emojiShortCodeProcessor, emojiShortCodeProcessor,
@ -231,12 +231,12 @@ func RenderCommitMessage(
var commitMessageSubjectProcessors = []processor{ var commitMessageSubjectProcessors = []processor{
fullIssuePatternProcessor, fullIssuePatternProcessor,
comparePatternProcessor, comparePatternProcessor,
fullSha1PatternProcessor, fullHashPatternProcessor,
linkProcessor, linkProcessor,
mentionProcessor, mentionProcessor,
issueIndexPatternProcessor, issueIndexPatternProcessor,
commitCrossReferencePatternProcessor, commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor, hashCurrentPatternProcessor,
emojiShortCodeProcessor, emojiShortCodeProcessor,
emojiProcessor, emojiProcessor,
} }
@ -273,7 +273,7 @@ func RenderIssueTitle(
return renderProcessString(ctx, []processor{ return renderProcessString(ctx, []processor{
issueIndexPatternProcessor, issueIndexPatternProcessor,
commitCrossReferencePatternProcessor, commitCrossReferencePatternProcessor,
sha1CurrentPatternProcessor, hashCurrentPatternProcessor,
emojiShortCodeProcessor, emojiShortCodeProcessor,
emojiProcessor, emojiProcessor,
}, title) }, title)
@ -946,15 +946,15 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
} }
} }
// fullSha1PatternProcessor renders SHA containing URLs // fullHashPatternProcessor renders SHA containing URLs
func fullSha1PatternProcessor(ctx *RenderContext, node *html.Node) { func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil { if ctx.Metas == nil {
return return
} }
next := node.NextSibling next := node.NextSibling
for node != nil && node != next { for node != nil && node != next {
m := anySHA1Pattern.FindStringSubmatchIndex(node.Data) m := anyHashPattern.FindStringSubmatchIndex(node.Data)
if m == nil { if m == nil {
return return
} }
@ -1111,9 +1111,9 @@ func emojiProcessor(ctx *RenderContext, node *html.Node) {
} }
} }
// sha1CurrentPatternProcessor renders SHA1 strings to corresponding links that // hashCurrentPatternProcessor renders SHA1 strings to corresponding links that
// are assumed to be in the same repository. // are assumed to be in the same repository.
func sha1CurrentPatternProcessor(ctx *RenderContext, node *html.Node) { func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil || ctx.Metas["user"] == "" || ctx.Metas["repo"] == "" || ctx.Metas["repoPath"] == "" { if ctx.Metas == nil || ctx.Metas["user"] == "" || ctx.Metas["repo"] == "" || ctx.Metas["repoPath"] == "" {
return return
} }
@ -1124,7 +1124,7 @@ func sha1CurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
ctx.ShaExistCache = make(map[string]bool) ctx.ShaExistCache = make(map[string]bool)
} }
for node != nil && node != next && start < len(node.Data) { for node != nil && node != next && start < len(node.Data) {
m := sha1CurrentPattern.FindStringSubmatchIndex(node.Data[start:]) m := hashCurrentPattern.FindStringSubmatchIndex(node.Data[start:])
if m == nil { if m == nil {
return return
} }

View file

@ -390,10 +390,10 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
} }
for _, testCase := range trueTestCases { for _, testCase := range trueTestCases {
assert.True(t, sha1CurrentPattern.MatchString(testCase)) assert.True(t, hashCurrentPattern.MatchString(testCase))
} }
for _, testCase := range falseTestCases { for _, testCase := range falseTestCases {
assert.False(t, sha1CurrentPattern.MatchString(testCase)) assert.False(t, hashCurrentPattern.MatchString(testCase))
} }
} }
@ -427,7 +427,7 @@ func TestRegExp_anySHA1Pattern(t *testing.T) {
} }
for k, v := range testCases { for k, v := range testCases {
assert.Equal(t, anySHA1Pattern.FindStringSubmatch(k)[1:], v) assert.Equal(t, anyHashPattern.FindStringSubmatch(k)[1:], v)
} }
} }

View file

@ -39,7 +39,7 @@ var (
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
// crossReferenceCommitPattern matches a string that references a commit in a different repository // crossReferenceCommitPattern matches a string that references a commit in a different repository
// e.g. go-gitea/gitea@d8a994ef, go-gitea/gitea@d8a994ef243349f321568f9e36d5c3f444b99cae (7-40 characters) // e.g. go-gitea/gitea@d8a994ef, go-gitea/gitea@d8a994ef243349f321568f9e36d5c3f444b99cae (7-40 characters)
crossReferenceCommitPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)@([0-9a-f]{7,40})(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) crossReferenceCommitPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)@([0-9a-f]{7,64})(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
// spaceTrimmedPattern let's find the trailing space // spaceTrimmedPattern let's find the trailing space
spaceTrimmedPattern = regexp.MustCompile(`(?:.*[0-9a-zA-Z-_])\s`) spaceTrimmedPattern = regexp.MustCompile(`(?:.*[0-9a-zA-Z-_])\s`)
// timeLogPattern matches string for time tracking // timeLogPattern matches string for time tracking

View file

@ -343,7 +343,7 @@ func TestFindRenderizableCommitCrossReference(t *testing.T) {
}, },
}, },
{ {
Input: "go-gitea/gitea@abcd1234abcd1234abcd1234abcd1234abcd12340", // longer than 40 characters Input: "go-gitea/gitea@abcd1234abcd1234abcd1234abcd1234abcd12341234512345123451234512345", // longer than 64 characters
Expected: nil, Expected: nil,
}, },
{ {

View file

@ -105,6 +105,9 @@ type Repository struct {
AvatarURL string `json:"avatar_url"` AvatarURL string `json:"avatar_url"`
Internal bool `json:"internal"` Internal bool `json:"internal"`
MirrorInterval string `json:"mirror_interval"` MirrorInterval string `json:"mirror_interval"`
// ObjectFormatName of the underlying git repository
// enum: sha1,sha256
ObjectFormatName string `json:"object_format_name"`
// swagger:strfmt date-time // swagger:strfmt date-time
MirrorUpdated time.Time `json:"mirror_updated,omitempty"` MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
RepoTransfer *RepoTransfer `json:"repo_transfer"` RepoTransfer *RepoTransfer `json:"repo_transfer"`
@ -139,6 +142,9 @@ type CreateRepoOption struct {
// TrustModel of the repository // TrustModel of the repository
// enum: default,collaborator,committer,collaboratorcommitter // enum: default,collaborator,committer,collaboratorcommitter
TrustModel string `json:"trust_model"` TrustModel string `json:"trust_model"`
// ObjectFormatName of the underlying git repository
// enum: sha1,sha256
ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"`
} }
// EditRepoOption options when editing a repository's properties // EditRepoOption options when editing a repository's properties

View file

@ -41,3 +41,7 @@ func (su *StringUtils) Cut(s, sep string) []any {
func (su *StringUtils) EllipsisString(s string, max int) string { func (su *StringUtils) EllipsisString(s string, max int) string {
return base.EllipsisString(s, max) return base.EllipsisString(s, max)
} }
func (su *StringUtils) ToUpper(s string) string {
return strings.ToUpper(s)
}

View file

@ -970,6 +970,8 @@ issue_labels_helper = Select an issue label set.
license = License license = License
license_helper = Select a license file. license_helper = Select a license file.
license_helper_desc = A license governs what others can and can't do with your code. Not sure which one is right for your project? See <a target="_blank" rel="noopener noreferrer" href="%s">Choose a license.</a> license_helper_desc = A license governs what others can and can't do with your code. Not sure which one is right for your project? See <a target="_blank" rel="noopener noreferrer" href="%s">Choose a license.</a>
object_format = Object Format
object_format_helper = Object format of the repository. Cannot be changed later. SHA1 is most compatible.
readme = README readme = README
readme_helper = Select a README file template. readme_helper = Select a README file template.
readme_helper_desc = This is the place where you can write a complete description for your project. readme_helper_desc = This is the place where you can write a complete description for your project.
@ -1038,6 +1040,7 @@ desc.public = Public
desc.template = Template desc.template = Template
desc.internal = Internal desc.internal = Internal
desc.archived = Archived desc.archived = Archived
desc.sha256 = SHA256
template.items = Template Items template.items = Template Items
template.git_content = Git Content (Default Branch) template.git_content = Git Content (Default Branch)

View file

@ -253,7 +253,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
DefaultBranch: opt.DefaultBranch, DefaultBranch: opt.DefaultBranch,
TrustModel: repo_model.ToTrustModel(opt.TrustModel), TrustModel: repo_model.ToTrustModel(opt.TrustModel),
IsTemplate: opt.Template, IsTemplate: opt.Template,
ObjectFormatName: git.Sha1ObjectFormat.Name(), ObjectFormatName: opt.ObjectFormatName,
}) })
if err != nil { if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) { if repo_model.IsErrRepoAlreadyExist(err) {

View file

@ -36,8 +36,8 @@ func gitHTTPRouters(m *web.Route) {
m.Methods("GET,OPTIONS", "/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates")) m.Methods("GET,OPTIONS", "/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
m.Methods("GET,OPTIONS", "/objects/info/packs", repo.GetInfoPacks) m.Methods("GET,OPTIONS", "/objects/info/packs", repo.GetInfoPacks)
m.Methods("GET,OPTIONS", "/objects/info/{file:[^/]*}", repo.GetTextFile("")) m.Methods("GET,OPTIONS", "/objects/info/{file:[^/]*}", repo.GetTextFile(""))
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject) m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile) m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile) m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb()) }, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
} }

View file

@ -159,7 +159,6 @@ func Create(ctx *context.Context) {
ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["private"] = getRepoPrivate(ctx)
ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
ctx.Data["default_branch"] = setting.Repository.DefaultBranch ctx.Data["default_branch"] = setting.Repository.DefaultBranch
ctx.Data["hash_type"] = "sha1"
ctxUser := checkContextUser(ctx, ctx.FormInt64("org")) ctxUser := checkContextUser(ctx, ctx.FormInt64("org"))
if ctx.Written() { if ctx.Written() {
@ -179,6 +178,8 @@ func Create(ctx *context.Context) {
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
ctx.Data["SupportedObjectFormats"] = git.SupportedObjectFormats
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
ctx.HTML(http.StatusOK, tplCreate) ctx.HTML(http.StatusOK, tplCreate)
} }

View file

@ -1235,7 +1235,7 @@ func registerRoutes(m *web.Route) {
Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost) Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost)
m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch). m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost) Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
m.Combo("/_cherrypick/{sha:([a-f0-9]{7,40})}/*").Get(repo.CherryPick). m.Combo("/_cherrypick/{sha:([a-f0-9]{7,64})}/*").Get(repo.CherryPick).
Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost) Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost)
}, repo.MustBeEditable) }, repo.MustBeEditable)
m.Group("", func() { m.Group("", func() {
@ -1377,8 +1377,8 @@ func registerRoutes(m *web.Route) {
m.Combo("/*"). m.Combo("/*").
Get(repo.Wiki). Get(repo.Wiki).
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
m.Get("/commit/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff)
}, repo.MustEnableWiki, func(ctx *context.Context) { }, repo.MustEnableWiki, func(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true ctx.Data["PageIsWiki"] = true
ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink() ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink()
@ -1498,9 +1498,9 @@ func registerRoutes(m *web.Route) {
m.Group("", func() { m.Group("", func() {
m.Get("/graph", repo.Graph) m.Get("/graph", repo.Graph)
m.Get("/commit/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) m.Get("/commit/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:([a-f0-9]{7,40})$}/load-branches-and-tags", repo.LoadBranchesAndTags) m.Get("/commit/{sha:([a-f0-9]{7,64})$}/load-branches-and-tags", repo.LoadBranchesAndTags)
m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed) m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
@ -1517,7 +1517,7 @@ func registerRoutes(m *web.Route) {
m.Group("", func() { m.Group("", func() {
m.Get("/forks", repo.Forks) m.Get("/forks", repo.Forks)
}, context.RepoRef(), reqRepoCodeReader) }, context.RepoRef(), reqRepoCodeReader)
m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
}, ignSignIn, context.RepoAssignment, context.UnitTypes()) }, ignSignIn, context.RepoAssignment, context.UnitTypes())
m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit) m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit)

View file

@ -217,6 +217,10 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt
} }
} }
if opts.ObjectFormatName == "" {
opts.ObjectFormatName = git.Sha1ObjectFormat.Name()
}
repo := &repo_model.Repository{ repo := &repo_model.Repository{
OwnerID: u.ID, OwnerID: u.ID,
Owner: u, Owner: u,

View file

@ -87,6 +87,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
IsEmpty: opts.BaseRepo.IsEmpty, IsEmpty: opts.BaseRepo.IsEmpty,
IsFork: true, IsFork: true,
ForkID: opts.BaseRepo.ID, ForkID: opts.BaseRepo.ID,
ObjectFormatName: opts.BaseRepo.ObjectFormatName,
} }
oldRepoPath := opts.BaseRepo.RepoPath() oldRepoPath := opts.BaseRepo.RepoPath()

View file

@ -67,6 +67,9 @@
{{if .IsTemplate}} {{if .IsTemplate}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span>
{{end}} {{end}}
{{if eq .ObjectFormatName "sha256"}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.sha256"}}</span>
{{end}}
{{if .IsMirror}} {{if .IsMirror}}
{{svg "octicon-mirror"}} {{svg "octicon-mirror"}}
{{else if .IsFork}} {{else if .IsFork}}

View file

@ -25,6 +25,9 @@
{{if .IsTemplate}} {{if .IsTemplate}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span>
{{end}} {{end}}
{{if eq .ObjectFormatName "sha256"}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.sha256"}}</span>
{{end}}
</span> </span>
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">

View file

@ -3,7 +3,7 @@
<thead> <thead>
<tr> <tr>
<th class="three wide">{{ctx.Locale.Tr "repo.commits.author"}}</th> <th class="three wide">{{ctx.Locale.Tr "repo.commits.author"}}</th>
<th class="two wide sha">SHA1</th> <th class="two wide sha">{{StringUtils.ToUpper $.Repository.ObjectFormatName}}</th>
<th class="eight wide message">{{ctx.Locale.Tr "repo.commits.message"}}</th> <th class="eight wide message">{{ctx.Locale.Tr "repo.commits.message"}}</th>
<th class="two wide right aligned">{{ctx.Locale.Tr "repo.commits.date"}}</th> <th class="two wide right aligned">{{ctx.Locale.Tr "repo.commits.date"}}</th>
<th class="one wide"></th> <th class="one wide"></th>

View file

@ -185,6 +185,19 @@
<input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}"> <input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}">
<span class="help">{{ctx.Locale.Tr "repo.default_branch_helper"}}</span> <span class="help">{{ctx.Locale.Tr "repo.default_branch_helper"}}</span>
</div> </div>
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.object_format"}}</label>
<div class="ui selection owner dropdown">
<input type="hidden" id="object_format_name" name="object_format_name" value="{{.DefaultObjectFormat.Name}}" required>
<div class="default text">{{.DefaultObjectFormat.Name}}</div>
<div class="menu">
{{range .SupportedObjectFormats}}
<div class="item" data-value="{{.Name}}">{{.Name}}</div>
{{end}}
</div>
</div>
<span class="help">{{ctx.Locale.Tr "repo.object_format_helper"}}</span>
</div>
<div class="inline field"> <div class="inline field">
<label>{{ctx.Locale.Tr "repo.template"}}</label> <label>{{ctx.Locale.Tr "repo.template"}}</label>
<div class="ui checkbox"> <div class="ui checkbox">

View file

@ -27,6 +27,9 @@
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span> <span class="ui basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span>
<div class="repo-icon" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.template"}}">{{svg "octicon-repo-template" 18}}</div> <div class="repo-icon" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.template"}}">{{svg "octicon-repo-template" 18}}</div>
{{end}} {{end}}
{{if eq .ObjectFormatName "sha256"}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.sha256"}}</span>
{{end}}
</div> </div>
</div> </div>
{{if not (or .IsBeingCreated .IsBroken)}} {{if not (or .IsBeingCreated .IsBroken)}}

View file

@ -18370,6 +18370,15 @@
"uniqueItems": true, "uniqueItems": true,
"x-go-name": "Name" "x-go-name": "Name"
}, },
"object_format_name": {
"description": "ObjectFormatName of the underlying git repository",
"type": "string",
"enum": [
"sha1",
"sha256"
],
"x-go-name": "ObjectFormatName"
},
"private": { "private": {
"description": "Whether the repository is private", "description": "Whether the repository is private",
"type": "boolean", "type": "boolean",
@ -22185,6 +22194,15 @@
"type": "string", "type": "string",
"x-go-name": "Name" "x-go-name": "Name"
}, },
"object_format_name": {
"description": "ObjectFormatName of the underlying git repository",
"type": "string",
"enum": [
"sha1",
"sha256"
],
"x-go-name": "ObjectFormatName"
},
"open_issues_count": { "open_issues_count": {
"type": "integer", "type": "integer",
"format": "int64", "format": "int64",