Compare commits

..

3 commits

Author SHA1 Message Date
7ad7e3ec12 gitea.nulo.in: Use Alpine 3.16
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-08-19 00:54:16 +02:00
729109fbfa Add .woodpecker.yml for gitea.nulo.in 2022-08-19 00:54:16 +02:00
c19e74847d Allow hidden .READMEs 2022-08-19 00:54:16 +02:00
132 changed files with 4022 additions and 2691 deletions

View file

@ -4,50 +4,6 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io). been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.17.2](https://github.com/go-gitea/gitea/releases/tag/v1.17.2) - 2022-09-06
* SECURITY
* Double check CloneURL is acceptable (#20869) (#20892)
* Add more checks in migration code (#21011) (#21050)
* ENHANCEMENTS
* Fix hard-coded timeout and error panic in API archive download endpoint (#20925) (#21051)
* Improve arc-green code theme (#21039) (#21042)
* Enable contenthash in filename for dynamic assets (#20813) (#20932)
* Don't open new page for ext wiki on same repository (#20725) (#20910)
* Disable doctor logging on panic (#20847) (#20898)
* Remove calls to load Mirrors in user.Dashboard (#20855) (#20897)
* Update codemirror to 5.65.8 (#20875)
* Rework repo buttons (#20602, #20718) (#20719)
* BUGFIXES
* Ensure delete user deletes all comments (#21067) (#21068)
* Delete unreferenced packages when deleting a package version (#20977) (#21060)
* Redirect if user does not exist on admin pages (#20981) (#21059)
* Set uploadpack.allowFilter etc on gitea serv to enable partial clones with ssh (#20902) (#21058)
* Fix 500 on time in timeline API (#21052) (#21057)
* Fill the specified ref in webhook test payload (#20961) (#21055)
* Add another index for Action table on postgres (#21033) (#21054)
* Fix broken insecureskipverify handling in redis connection uris (#20967) (#21053)
* Add Dev, Peer and Optional dependencies to npm PackageMetadataVersion (#21017) (#21044)
* Do not add links to Posters or Assignees with ID < 0 (#20577) (#21037)
* Fix modified due date message (#20388) (#21032)
* Fix missed sort bug (#21006)
* Fix input.value attr for RequiredClaimName/Value (#20946) (#21001)
* Change review buttons to icons to make space for text (#20934) (#20978)
* Fix download archiver of a commit (#20962) (#20971)
* Return 404 NotFound if requested attachment does not exist (#20886) (#20941)
* Set no-tags in git fetch on compare (#20893) (#20936)
* Allow multiple metadata files for Maven packages (#20674) (#20916)
* Increase Content field size of gpg_key and public_key to MEDIUMTEXT (#20896) (#20911)
* Fix mirror address setting not working (#20850) (#20904)
* Fix push mirror address backend get error Address cause setting page display error (#20593) (#20901)
* Fix panic when an invalid oauth2 name is passed (#20820) (#20900)
* In PushMirrorsIterate and MirrorsIterate if limit is negative do not set it (#20837) (#20899)
* Ensure that graceful start-up is informed of unused SSH listener (#20877) (#20888)
* Pad GPG Key ID with preceding zeroes (#20878) (#20885)
* Fix SQL Query for `SearchTeam` (#20844) (#20872)
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
* Fix UI mis-align for PR commit history (#20845) (#20859)
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17 ## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
* SECURITY * SECURITY

View file

@ -5,7 +5,6 @@
package cmd package cmd
import ( import (
"errors"
"fmt" "fmt"
golog "log" golog "log"
"os" "os"
@ -124,47 +123,6 @@ func runRecreateTable(ctx *cli.Context) error {
}) })
} }
func setDoctorLogger(ctx *cli.Context) {
logFile := ctx.String("log-file")
if !ctx.IsSet("log-file") {
logFile = "doctor.log"
}
colorize := log.CanColorStdout
if ctx.IsSet("color") {
colorize = ctx.Bool("color")
}
if len(logFile) == 0 {
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
return
}
defer func() {
recovered := recover()
if recovered == nil {
return
}
err, ok := recovered.(error)
if !ok {
panic(recovered)
}
if errors.Is(err, os.ErrPermission) {
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file due to permissions error: %s\n %v\n", logFile, err)
} else {
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file: %s\n %v\n", logFile, err)
}
fmt.Fprintf(os.Stderr, "WARN: Logging will be disabled\n Use `--log-file` to configure log file location\n")
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
}()
if logFile == "-" {
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize))
} else {
log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
}
}
func runDoctor(ctx *cli.Context) error { func runDoctor(ctx *cli.Context) error {
// Silence the default loggers // Silence the default loggers
log.DelNamedLogger("console") log.DelNamedLogger("console")
@ -174,13 +132,24 @@ func runDoctor(ctx *cli.Context) error {
defer cancel() defer cancel()
// Now setup our own // Now setup our own
setDoctorLogger(ctx) logFile := ctx.String("log-file")
if !ctx.IsSet("log-file") {
logFile = "doctor.log"
}
colorize := log.CanColorStdout colorize := log.CanColorStdout
if ctx.IsSet("color") { if ctx.IsSet("color") {
colorize = ctx.Bool("color") colorize = ctx.Bool("color")
} }
if len(logFile) == 0 {
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
} else if logFile == "-" {
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize))
} else {
log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
}
// Finally redirect the default golog to here // Finally redirect the default golog to here
golog.SetFlags(0) golog.SetFlags(0)
golog.SetPrefix("") golog.SetPrefix("")

View file

@ -112,8 +112,11 @@ func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) e
func migrateRepoArchivers(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateRepoArchivers(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.IterateObjects(ctx, func(archiver *repo_model.RepoArchiver) error { return db.IterateObjects(ctx, func(archiver *repo_model.RepoArchiver) error {
p := archiver.RelativePath() p, err := archiver.RelativePath()
_, err := storage.Copy(dstStorage, p, storage.RepoArchives, p) if err != nil {
return err
}
_, err = storage.Copy(dstStorage, p, storage.RepoArchives, p)
return err return err
}) })
} }

View file

@ -5,7 +5,7 @@ mkdir -p ${HOME} && chmod 0700 ${HOME}
if [ ! -w ${HOME} ]; then echo "${HOME} is not writable"; exit 1; fi if [ ! -w ${HOME} ]; then echo "${HOME} is not writable"; exit 1; fi
# Prepare custom folder # Prepare custom folder
mkdir -p ${GITEA_CUSTOM} && chmod 0700 ${GITEA_CUSTOM} mkdir -p ${GITEA_CUSTOM} && chmod 0500 ${GITEA_CUSTOM}
# Prepare temp folder # Prepare temp folder
mkdir -p ${GITEA_TEMP} && chmod 0700 ${GITEA_TEMP} mkdir -p ${GITEA_TEMP} && chmod 0700 ${GITEA_TEMP}

View file

@ -42,7 +42,6 @@ func TestPackageMaven(t *testing.T) {
defer PrintCurrentTest(t)() defer PrintCurrentTest(t)()
putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusCreated) putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusCreated)
putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusBadRequest)
putFile(t, "/maven-metadata.xml", "test", http.StatusOK) putFile(t, "/maven-metadata.xml", "test", http.StatusOK)
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven)
@ -136,14 +135,12 @@ func TestPackageMaven(t *testing.T) {
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, pfs, 2) assert.Len(t, pfs, 2)
for _, pf := range pfs { i := 0
if strings.HasSuffix(pf.Name, ".pom") { if strings.HasSuffix(pfs[1].Name, ".pom") {
assert.Equal(t, filename+".pom", pf.Name) i = 1
assert.True(t, pf.IsLead)
} else {
assert.False(t, pf.IsLead)
}
} }
assert.Equal(t, filename+".pom", pfs[i].Name)
assert.True(t, pfs[i].IsLead)
}) })
t.Run("DownloadPOM", func(t *testing.T) { t.Run("DownloadPOM", func(t *testing.T) {
@ -205,13 +202,4 @@ func TestPackageMaven(t *testing.T) {
assert.Equal(t, checksum, resp.Body.String()) assert.Equal(t, checksum, resp.Body.String())
} }
}) })
t.Run("UploadSnapshot", func(t *testing.T) {
snapshotVersion := packageVersion + "-SNAPSHOT"
putFile(t, fmt.Sprintf("/%s/%s", snapshotVersion, filename), "test", http.StatusCreated)
putFile(t, "/maven-metadata.xml", "test", http.StatusOK)
putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test", http.StatusCreated)
putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test-overwrite", http.StatusCreated)
})
} }

View file

@ -223,7 +223,7 @@ func TestAPITeamSearch(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}).(*user_model.User) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
var results TeamSearchResults var results TeamSearchResults

View file

@ -26,19 +26,8 @@ func TestUserOrgs(t *testing.T) {
orgs := getUserOrgs(t, adminUsername, normalUsername) orgs := getUserOrgs(t, adminUsername, normalUsername)
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}).(*user_model.User) user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}).(*user_model.User)
user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}).(*user_model.User)
assert.Equal(t, []*api.Organization{ assert.Equal(t, []*api.Organization{
{
ID: 17,
UserName: user17.Name,
FullName: user17.FullName,
AvatarURL: user17.AvatarLink(),
Description: "",
Website: "",
Location: "",
Visibility: "public",
},
{ {
ID: 3, ID: 3,
UserName: user3.Name, UserName: user3.Name,
@ -93,19 +82,8 @@ func TestMyOrgs(t *testing.T) {
var orgs []*api.Organization var orgs []*api.Organization
DecodeJSON(t, resp, &orgs) DecodeJSON(t, resp, &orgs)
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}).(*user_model.User) user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}).(*user_model.User)
user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}).(*user_model.User)
assert.Equal(t, []*api.Organization{ assert.Equal(t, []*api.Organization{
{
ID: 17,
UserName: user17.Name,
FullName: user17.FullName,
AvatarURL: user17.AvatarLink(),
Description: "",
Website: "",
Location: "",
Visibility: "public",
},
{ {
ID: 3, ID: 3,
UserName: user3.Name, UserName: user3.Name,

View file

@ -179,8 +179,8 @@ func TestOrgRestrictedUser(t *testing.T) {
func TestTeamSearch(t *testing.T) { func TestTeamSearch(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}).(*user_model.User) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
var results TeamSearchResults var results TeamSearchResults
@ -190,9 +190,9 @@ func TestTeamSearch(t *testing.T) {
req.Header.Add("X-Csrf-Token", csrf) req.Header.Add("X-Csrf-Token", csrf)
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &results) DecodeJSON(t, resp, &results)
assert.Len(t, results.Data, 2) assert.NotEmpty(t, results.Data)
assert.Equal(t, "review_team", results.Data[0].Name) assert.Len(t, results.Data, 1)
assert.Equal(t, "test_team", results.Data[1].Name) assert.Equal(t, "test_team", results.Data[0].Name)
// no access if not organization member // no access if not organization member
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)

View file

@ -1,7 +1,7 @@
export default { export default {
rootDir: 'web_src', rootDir: 'web_src',
setupFilesAfterEnv: ['jest-extended/all'], setupFilesAfterEnv: ['jest-extended/all'],
testEnvironment: 'jest-environment-jsdom', testEnvironment: '@happy-dom/jest-environment',
testMatch: ['<rootDir>/**/*.test.js'], testMatch: ['<rootDir>/**/*.test.js'],
testTimeout: 20000, testTimeout: 20000,
transform: { transform: {

View file

@ -98,14 +98,7 @@ func (a *Action) TableIndices() []*schemas.Index {
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
indices := []*schemas.Index{actUserIndex, repoIndex} return []*schemas.Index{actUserIndex, repoIndex}
if setting.Database.UsePostgreSQL {
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
indices = append(indices, cudIndex)
}
return indices
} }
// GetOpType gets the ActionType of this action. // GetOpType gets the ActionType of this action.
@ -282,7 +275,7 @@ func (a *Action) GetRefLink() string {
return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix)) return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix))
case strings.HasPrefix(a.RefName, git.TagPrefix): case strings.HasPrefix(a.RefName, git.TagPrefix):
return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.TagPrefix)) return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.TagPrefix))
case len(a.RefName) == 40 && git.IsValidSHAPattern(a.RefName): case len(a.RefName) == 40 && git.SHAPattern.MatchString(a.RefName):
return a.GetRepoLink() + "/src/commit/" + a.RefName return a.GetRepoLink() + "/src/commit/" + a.RefName
default: default:
// FIXME: we will just assume it's a branch - this was the old way - at some point we may want to enforce that there is always a ref here. // FIXME: we will just assume it's a branch - this was the old way - at some point we may want to enforce that there is always a ref here.

View file

@ -33,7 +33,7 @@ type GPGKey struct {
OwnerID int64 `xorm:"INDEX NOT NULL"` OwnerID int64 `xorm:"INDEX NOT NULL"`
KeyID string `xorm:"INDEX CHAR(16) NOT NULL"` KeyID string `xorm:"INDEX CHAR(16) NOT NULL"`
PrimaryKeyID string `xorm:"CHAR(16)"` PrimaryKeyID string `xorm:"CHAR(16)"`
Content string `xorm:"MEDIUMTEXT NOT NULL"` Content string `xorm:"TEXT NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
ExpiredUnix timeutil.TimeStamp ExpiredUnix timeutil.TimeStamp
AddedUnix timeutil.TimeStamp AddedUnix timeutil.TimeStamp
@ -63,15 +63,6 @@ func (key *GPGKey) AfterLoad(session *xorm.Session) {
} }
} }
// PaddedKeyID show KeyID padded to 16 characters
func (key *GPGKey) PaddedKeyID() string {
if len(key.KeyID) > 15 {
return key.KeyID
}
zeros := "0000000000000000"
return zeros[0:16-len(key.KeyID)] + key.KeyID
}
// ListGPGKeys returns a list of public keys belongs to given user. // ListGPGKeys returns a list of public keys belongs to given user.
func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) { func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) {
sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid) sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid)

View file

@ -41,7 +41,7 @@ type PublicKey struct {
OwnerID int64 `xorm:"INDEX NOT NULL"` OwnerID int64 `xorm:"INDEX NOT NULL"`
Name string `xorm:"NOT NULL"` Name string `xorm:"NOT NULL"`
Fingerprint string `xorm:"INDEX NOT NULL"` Fingerprint string `xorm:"INDEX NOT NULL"`
Content string `xorm:"MEDIUMTEXT NOT NULL"` Content string `xorm:"TEXT NOT NULL"`
Mode perm.AccessMode `xorm:"NOT NULL DEFAULT 2"` Mode perm.AccessMode `xorm:"NOT NULL DEFAULT 2"`
Type KeyType `xorm:"NOT NULL DEFAULT 1"` Type KeyType `xorm:"NOT NULL DEFAULT 1"`
LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"`

View file

@ -512,14 +512,10 @@ func GetActiveOAuth2ProviderSources() ([]*Source, error) {
func GetActiveOAuth2SourceByName(name string) (*Source, error) { func GetActiveOAuth2SourceByName(name string) (*Source, error) {
authSource := new(Source) authSource := new(Source)
has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource) has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource)
if err != nil { if !has || err != nil {
return nil, err return nil, err
} }
if !has {
return nil, fmt.Errorf("oauth2 source not found, name: %q", name)
}
return authSource, nil return authSource, nil
} }

View file

@ -63,9 +63,3 @@
uid: 29 uid: 29
org_id: 17 org_id: 17
is_public: true is_public: true
-
id: 12
uid: 2
org_id: 17
is_public: true

View file

@ -309,7 +309,7 @@
avatar_email: user17@example.com avatar_email: user17@example.com
num_repos: 2 num_repos: 2
is_active: true is_active: true
num_members: 4 num_members: 3
num_teams: 3 num_teams: 3
- -

View file

@ -68,7 +68,6 @@ func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
issues, err := Issues(&IssuesOptions{ issues, err := Issues(&IssuesOptions{
ProjectBoardID: b.ID, ProjectBoardID: b.ID,
ProjectID: b.ProjectID, ProjectID: b.ProjectID,
SortType: "project-column-sorting",
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -80,7 +79,6 @@ func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
issues, err := Issues(&IssuesOptions{ issues, err := Issues(&IssuesOptions{
ProjectBoardID: -1, // Issues without ProjectBoardID ProjectBoardID: -1, // Issues without ProjectBoardID
ProjectID: b.ProjectID, ProjectID: b.ProjectID,
SortType: "project-column-sorting",
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -5,7 +5,6 @@
package migrations package migrations
import ( import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm" "xorm.io/xorm"
@ -38,14 +37,8 @@ func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index {
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
indices := []*schemas.Index{actUserIndex, repoIndex}
if setting.Database.UsePostgreSQL {
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
indices = append(indices, cudIndex)
}
return indices return []*schemas.Index{actUserIndex, repoIndex}
} }
func improveActionTableIndices(x *xorm.Engine) error { func improveActionTableIndices(x *xorm.Engine) error {

View file

@ -96,7 +96,16 @@ type SearchTeamOptions struct {
IncludeDesc bool IncludeDesc bool
} }
func (opts *SearchTeamOptions) toCond() builder.Cond { // SearchTeam search for teams. Caller is responsible to check permissions.
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
if opts.Page <= 0 {
opts.Page = 1
}
if opts.PageSize == 0 {
// Default limit
opts.PageSize = 10
}
cond := builder.NewCond() cond := builder.NewCond()
if len(opts.Keyword) > 0 { if len(opts.Keyword) > 0 {
@ -108,28 +117,10 @@ func (opts *SearchTeamOptions) toCond() builder.Cond {
cond = cond.And(keywordCond) cond = cond.And(keywordCond)
} }
if opts.OrgID > 0 { cond = cond.And(builder.Eq{"org_id": opts.OrgID})
cond = cond.And(builder.Eq{"`team`.org_id": opts.OrgID})
}
if opts.UserID > 0 {
cond = cond.And(builder.Eq{"team_user.uid": opts.UserID})
}
return cond
}
// SearchTeam search for teams. Caller is responsible to check permissions.
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
sess := db.GetEngine(db.DefaultContext) sess := db.GetEngine(db.DefaultContext)
opts.SetDefaultValues()
cond := opts.toCond()
if opts.UserID > 0 {
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
}
count, err := sess. count, err := sess.
Where(cond). Where(cond).
Count(new(Team)) Count(new(Team))
@ -137,10 +128,6 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
return nil, 0, err return nil, 0, err
} }
if opts.UserID > 0 {
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
}
sess = sess.Where(cond) sess = sess.Where(cond)
if opts.PageSize == -1 { if opts.PageSize == -1 {
opts.PageSize = int(count) opts.PageSize = int(count)
@ -150,7 +137,6 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
teams := make([]*Team, 0, opts.PageSize) teams := make([]*Team, 0, opts.PageSize)
if err = sess. if err = sess.
Where(cond).
OrderBy("lower_name"). OrderBy("lower_name").
Find(&teams); err != nil { Find(&teams); err != nil {
return nil, 0, err return nil, 0, err

View file

@ -214,16 +214,9 @@ func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) {
Find(&ps) Find(&ps)
} }
// HasOwnerPackages tests if a user/org has accessible packages // HasOwnerPackages tests if a user/org has packages
func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) { func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) {
return db.GetEngine(ctx). return db.GetEngine(ctx).Where("owner_id = ?", ownerID).Exist(&Package{})
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Where(builder.Eq{
"package_version.is_internal": false,
"package.owner_id": ownerID,
}).
Exist(&PackageVersion{})
} }
// HasRepositoryPackages tests if a repository has packages // HasRepositoryPackages tests if a repository has packages

View file

@ -1,69 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package packages_test
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
_ "code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", ".."),
})
}
func TestHasOwnerPackages(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
p, err := packages_model.TryInsertPackage(db.DefaultContext, &packages_model.Package{
OwnerID: owner.ID,
LowerName: "package",
})
assert.NotNil(t, p)
assert.NoError(t, err)
// A package without package versions gets automatically cleaned up and should return false
has, err := packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
assert.False(t, has)
assert.NoError(t, err)
pv, err := packages_model.GetOrInsertVersion(db.DefaultContext, &packages_model.PackageVersion{
PackageID: p.ID,
LowerVersion: "internal",
IsInternal: true,
})
assert.NotNil(t, pv)
assert.NoError(t, err)
// A package with an internal package version gets automaticaly cleaned up and should return false
has, err = packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
assert.False(t, has)
assert.NoError(t, err)
pv, err = packages_model.GetOrInsertVersion(db.DefaultContext, &packages_model.PackageVersion{
PackageID: p.ID,
LowerVersion: "normal",
IsInternal: false,
})
assert.NotNil(t, pv)
assert.NoError(t, err)
// A package with a normal package version should return true
has, err = packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
assert.True(t, has)
assert.NoError(t, err)
}

View file

@ -385,7 +385,8 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
archivePaths := make([]string, 0, len(archives)) archivePaths := make([]string, 0, len(archives))
for _, v := range archives { for _, v := range archives {
archivePaths = append(archivePaths, v.RelativePath()) p, _ := v.RelativePath()
archivePaths = append(archivePaths, p)
} }
if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil { if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil {

View file

@ -39,9 +39,9 @@ func init() {
db.RegisterModel(new(RepoArchiver)) db.RegisterModel(new(RepoArchiver))
} }
// RelativePath returns the archive path relative to the archive storage root. // RelativePath returns relative path
func (archiver *RepoArchiver) RelativePath() string { func (archiver *RepoArchiver) RelativePath() (string, error) {
return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String()) return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String()), nil
} }
var delRepoArchiver = new(RepoArchiver) var delRepoArchiver = new(RepoArchiver)

View file

@ -8,6 +8,7 @@ package repo
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -107,14 +108,12 @@ func DeleteMirrorByRepoID(repoID int64) error {
// MirrorsIterate iterates all mirror repositories. // MirrorsIterate iterates all mirror repositories.
func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error { func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
sess := db.GetEngine(db.DefaultContext). return db.GetEngine(db.DefaultContext).
Where("next_update_unix<=?", time.Now().Unix()). Where("next_update_unix<=?", time.Now().Unix()).
And("next_update_unix!=0"). And("next_update_unix!=0").
OrderBy("updated_unix ASC") OrderBy("updated_unix ASC").
if limit > 0 { Limit(limit).
sess = sess.Limit(limit) Iterate(new(Mirror), f)
}
return sess.Iterate(new(Mirror), f)
} }
// InsertMirror inserts a mirror to database // InsertMirror inserts a mirror to database
@ -122,3 +121,55 @@ func InsertMirror(ctx context.Context, mirror *Mirror) error {
_, err := db.GetEngine(ctx).Insert(mirror) _, err := db.GetEngine(ctx).Insert(mirror)
return err return err
} }
// MirrorRepositoryList contains the mirror repositories
type MirrorRepositoryList []*Repository
func (repos MirrorRepositoryList) loadAttributes(ctx context.Context) error {
if len(repos) == 0 {
return nil
}
// Load mirrors.
repoIDs := make([]int64, 0, len(repos))
for i := range repos {
if !repos[i].IsMirror {
continue
}
repoIDs = append(repoIDs, repos[i].ID)
}
mirrors := make([]*Mirror, 0, len(repoIDs))
if err := db.GetEngine(ctx).
Where("id > 0").
In("repo_id", repoIDs).
Find(&mirrors); err != nil {
return fmt.Errorf("find mirrors: %v", err)
}
set := make(map[int64]*Mirror)
for i := range mirrors {
set[mirrors[i].RepoID] = mirrors[i]
}
for i := range repos {
repos[i].Mirror = set[repos[i].ID]
if repos[i].Mirror != nil {
repos[i].Mirror.Repo = repos[i]
}
}
return nil
}
// LoadAttributes loads the attributes for the given MirrorRepositoryList
func (repos MirrorRepositoryList) LoadAttributes() error {
return repos.loadAttributes(db.DefaultContext)
}
// GetUserMirrorRepositories returns a list of mirror repositories of given user.
func GetUserMirrorRepositories(userID int64) ([]*Repository, error) {
repos := make([]*Repository, 0, 10)
return repos, db.GetEngine(db.DefaultContext).
Where("owner_id = ?", userID).
And("is_mirror = ?", true).
Find(&repos)
}

View file

@ -95,12 +95,10 @@ func GetPushMirrorsByRepoID(repoID int64) ([]*PushMirror, error) {
// PushMirrorsIterate iterates all push-mirror repositories. // PushMirrorsIterate iterates all push-mirror repositories.
func PushMirrorsIterate(limit int, f func(idx int, bean interface{}) error) error { func PushMirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
sess := db.GetEngine(db.DefaultContext). return db.GetEngine(db.DefaultContext).
Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()). Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()).
And("`interval` != 0"). And("`interval` != 0").
OrderBy("last_update ASC") OrderBy("last_update ASC").
if limit > 0 { Limit(limit).
sess = sess.Limit(limit) Iterate(new(PushMirror), f)
}
return sess.Iterate(new(PushMirror), f)
} }

View file

@ -100,9 +100,9 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
// Delete Comments // Delete Comments
const batchSize = 50 const batchSize = 50
for { for start := 0; ; start += batchSize {
comments := make([]*issues_model.Comment, 0, batchSize) comments := make([]*issues_model.Comment, 0, batchSize)
if err = e.Where("type=? AND poster_id=?", issues_model.CommentTypeComment, u.ID).Limit(batchSize, 0).Find(&comments); err != nil { if err = e.Where("type=? AND poster_id=?", issues_model.CommentTypeComment, u.ID).Limit(batchSize, start).Find(&comments); err != nil {
return err return err
} }
if len(comments) == 0 { if len(comments) == 0 {
@ -200,7 +200,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
// ***** END: ExternalLoginUser ***** // ***** END: ExternalLoginUser *****
if _, err = e.ID(u.ID).Delete(new(user_model.User)); err != nil { if _, err = e.ID(u.ID).Delete(new(user_model.User)); err != nil {
return fmt.Errorf("delete: %v", err) return fmt.Errorf("Delete: %v", err)
} }
return nil return nil

View file

@ -986,7 +986,6 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
} }
ctx.Data["BranchName"] = ctx.Repo.BranchName ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["RefName"] = ctx.Repo.RefName
ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
ctx.Data["TagName"] = ctx.Repo.TagName ctx.Data["TagName"] = ctx.Repo.TagName
ctx.Data["CommitID"] = ctx.Repo.CommitID ctx.Data["CommitID"] = ctx.Repo.CommitID

View file

@ -101,12 +101,6 @@ func ToTimelineComment(c *issues_model.Comment, doer *user_model.User) *api.Time
} }
if c.Time != nil { if c.Time != nil {
err = c.Time.LoadAttributes()
if err != nil {
log.Error("Time.LoadAttributes: %v", err)
return nil
}
comment.TrackedTime = ToTrackedTime(c.Time) comment.TrackedTime = ToTrackedTime(c.Time)
} }

View file

@ -287,20 +287,7 @@ func syncGitConfig() (err error) {
} }
} }
// By default partial clones are disabled, enable them from git v2.22 return nil
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
return err
}
err = configSet("uploadpack.allowAnySHA1InWant", "true")
} else {
if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
return err
}
err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
}
return err
} }
// CheckGitVersionAtLeast check git version is at least the constraint version // CheckGitVersionAtLeast check git version is at least the constraint version

View file

@ -4,10 +4,7 @@
package git package git
import ( import "strings"
"regexp"
"strings"
)
const ( const (
// RemotePrefix is the base directory of the remotes information of git. // RemotePrefix is the base directory of the remotes information of git.
@ -18,29 +15,6 @@ const (
pullLen = len(PullPrefix) pullLen = len(PullPrefix)
) )
// refNamePatternInvalid is regular expression with unallowed characters in git reference name
// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
var refNamePatternInvalid = regexp.MustCompile(
`[\000-\037\177 \\~^:?*[]|` + // No absolutely invalid characters
`(?:^[/.])|` + // Not HasPrefix("/") or "."
`(?:/\.)|` + // no "/."
`(?:\.lock$)|(?:\.lock/)|` + // No ".lock/"" or ".lock" at the end
`(?:\.\.)|` + // no ".." anywhere
`(?://)|` + // no "//" anywhere
`(?:@{)|` + // no "@{"
`(?:[/.]$)|` + // no terminal '/' or '.'
`(?:^@$)`) // Not "@"
// IsValidRefPattern ensures that the provided string could be a valid reference
func IsValidRefPattern(name string) bool {
return !refNamePatternInvalid.MatchString(name)
}
func SanitizeRefPattern(name string) string {
return refNamePatternInvalid.ReplaceAllString(name, "_")
}
// Reference represents a Git ref. // Reference represents a Git ref.
type Reference struct { type Reference struct {
Name string Name string

View file

@ -138,7 +138,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
// ConvertToSHA1 returns a Hash object from a potential ID string // ConvertToSHA1 returns a Hash object from a potential ID string
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
if len(commitID) == 40 && IsValidSHAPattern(commitID) { if len(commitID) == 40 && SHAPattern.MatchString(commitID) {
sha1, err := NewIDFromString(commitID) sha1, err := NewIDFromString(commitID)
if err == nil { if err == nil {
return sha1, nil return sha1, nil

View file

@ -40,7 +40,7 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
if tmpRemote != "origin" { if tmpRemote != "origin" {
tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags // Fetch commit into a temporary branch in order to be able to handle commits and tags
_, _, err := NewCommand(repo.Ctx, "fetch", "--no-tags", tmpRemote, "--", base+":"+tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path}) _, _, err := NewCommand(repo.Ctx, "fetch", tmpRemote, base+":"+tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path})
if err == nil { if err == nil {
base = tmpBaseName base = tmpBaseName
} }

View file

@ -19,12 +19,7 @@ const EmptySHA = "0000000000000000000000000000000000000000"
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
// SHAPattern can be used to determine if a string is an valid sha // SHAPattern can be used to determine if a string is an valid sha
var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`) var SHAPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
// IsValidSHAPattern will check if the provided string matches the SHA Pattern
func IsValidSHAPattern(sha string) bool {
return shaPattern.MatchString(sha)
}
// MustID always creates a new SHA1 from a [20]byte array with no validation of input. // MustID always creates a new SHA1 from a [20]byte array with no validation of input.
func MustID(b []byte) SHA1 { func MustID(b []byte) SHA1 {

View file

@ -114,9 +114,9 @@ func (g *Manager) start() {
// Execute makes Manager implement svc.Handler // Execute makes Manager implement svc.Handler
func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
if setting.StartupTimeout > 0 { if setting.StartupTimeout > 0 {
status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
} else {
status <- svc.Status{State: svc.StartPending} status <- svc.Status{State: svc.StartPending}
} else {
status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
} }
log.Trace("Awaiting server start-up") log.Trace("Awaiting server start-up")

View file

@ -33,7 +33,7 @@ func newLogger(name string, buffer int64) *MultiChannelledLogger {
func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error { func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error {
eventLogger, err := NewChannelledLog(l.ctx, name, provider, config, l.bufferLength) eventLogger, err := NewChannelledLog(l.ctx, name, provider, config, l.bufferLength)
if err != nil { if err != nil {
return fmt.Errorf("failed to create sublogger (%s): %w", name, err) return fmt.Errorf("Failed to create sublogger (%s): %v", name, err)
} }
l.MultiChannelledLog.DelLogger(name) l.MultiChannelledLog.DelLogger(name)
@ -41,9 +41,9 @@ func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error {
err = l.MultiChannelledLog.AddLogger(eventLogger) err = l.MultiChannelledLog.AddLogger(eventLogger)
if err != nil { if err != nil {
if IsErrDuplicateName(err) { if IsErrDuplicateName(err) {
return fmt.Errorf("%w other names: %v", err, l.MultiChannelledLog.GetEventLoggerNames()) return fmt.Errorf("Duplicate named sublogger %s %v", name, l.MultiChannelledLog.GetEventLoggerNames())
} }
return fmt.Errorf("failed to add sublogger (%s): %w", name, err) return fmt.Errorf("Failed to add sublogger (%s): %v", name, err)
} }
return nil return nil

View file

@ -26,7 +26,7 @@ type PullRequest struct {
Updated time.Time Updated time.Time
Closed *time.Time Closed *time.Time
Labels []*Label Labels []*Label
PatchURL string `yaml:"patch_url"` // SECURITY: This must be safe to download directly from PatchURL string `yaml:"patch_url"`
Merged bool Merged bool
MergedTime *time.Time `yaml:"merged_time"` MergedTime *time.Time `yaml:"merged_time"`
MergeCommitSHA string `yaml:"merge_commit_sha"` MergeCommitSHA string `yaml:"merge_commit_sha"`
@ -37,7 +37,6 @@ type PullRequest struct {
Reactions []*Reaction Reactions []*Reaction
ForeignIndex int64 ForeignIndex int64
Context DownloaderContext `yaml:"-"` Context DownloaderContext `yaml:"-"`
EnsuredSafe bool `yaml:"ensured_safe"`
} }
func (p *PullRequest) GetLocalIndex() int64 { return p.Number } func (p *PullRequest) GetLocalIndex() int64 { return p.Number }
@ -56,9 +55,9 @@ func (p PullRequest) GetGitRefName() string {
// PullRequestBranch represents a pull request branch // PullRequestBranch represents a pull request branch
type PullRequestBranch struct { type PullRequestBranch struct {
CloneURL string `yaml:"clone_url"` // SECURITY: This must be safe to download from CloneURL string `yaml:"clone_url"`
Ref string // SECURITY: this must be a git.IsValidRefPattern Ref string
SHA string // SECURITY: this must be a git.IsValidSHAPattern SHA string
RepoName string `yaml:"repo_name"` RepoName string `yaml:"repo_name"`
OwnerName string `yaml:"owner_name"` OwnerName string `yaml:"owner_name"`
} }

View file

@ -18,16 +18,15 @@ type ReleaseAsset struct {
DownloadCount *int `yaml:"download_count"` DownloadCount *int `yaml:"download_count"`
Created time.Time Created time.Time
Updated time.Time Updated time.Time
DownloadURL *string `yaml:"download_url"`
DownloadURL *string `yaml:"download_url"` // SECURITY: It is the responsibility of downloader to make sure this is safe
// if DownloadURL is nil, the function should be invoked // if DownloadURL is nil, the function should be invoked
DownloadFunc func() (io.ReadCloser, error) `yaml:"-"` // SECURITY: It is the responsibility of downloader to make sure this is safe DownloadFunc func() (io.ReadCloser, error) `yaml:"-"`
} }
// Release represents a release // Release represents a release
type Release struct { type Release struct {
TagName string `yaml:"tag_name"` // SECURITY: This must pass git.IsValidRefPattern TagName string `yaml:"tag_name"`
TargetCommitish string `yaml:"target_commitish"` // SECURITY: This must pass git.IsValidRefPattern TargetCommitish string `yaml:"target_commitish"`
Name string Name string
Body string Body string
Draft bool Draft bool

View file

@ -12,7 +12,7 @@ type Repository struct {
IsPrivate bool `yaml:"is_private"` IsPrivate bool `yaml:"is_private"`
IsMirror bool `yaml:"is_mirror"` IsMirror bool `yaml:"is_mirror"`
Description string Description string
CloneURL string `yaml:"clone_url"` // SECURITY: This must be checked to ensure that is safe to be used CloneURL string `yaml:"clone_url"`
OriginalURL string `yaml:"original_url"` OriginalURL string `yaml:"original_url"`
DefaultBranch string DefaultBranch string
} }

View file

@ -245,7 +245,7 @@ func getRedisTLSOptions(uri *url.URL) *tls.Config {
if len(skipverify) > 0 { if len(skipverify) > 0 {
skipverify, err := strconv.ParseBool(skipverify) skipverify, err := strconv.ParseBool(skipverify)
if err == nil { if err != nil {
tlsConfig.InsecureSkipVerify = skipverify tlsConfig.InsecureSkipVerify = skipverify
} }
} }
@ -254,7 +254,7 @@ func getRedisTLSOptions(uri *url.URL) *tls.Config {
if len(insecureskipverify) > 0 { if len(insecureskipverify) > 0 {
insecureskipverify, err := strconv.ParseBool(insecureskipverify) insecureskipverify, err := strconv.ParseBool(insecureskipverify)
if err == nil { if err != nil {
tlsConfig.InsecureSkipVerify = insecureskipverify tlsConfig.InsecureSkipVerify = insecureskipverify
} }
} }

View file

@ -27,24 +27,6 @@ func TestRedisPasswordOpt(t *testing.T) {
} }
} }
func TestSkipVerifyOpt(t *testing.T) {
uri, _ := url.Parse("rediss://myredis/0?skipverify=true")
tlsConfig := getRedisTLSOptions(uri)
if !tlsConfig.InsecureSkipVerify {
t.Fail()
}
}
func TestInsecureSkipVerifyOpt(t *testing.T) {
uri, _ := url.Parse("rediss://myredis/0?insecureskipverify=true")
tlsConfig := getRedisTLSOptions(uri)
if !tlsConfig.InsecureSkipVerify {
t.Fail()
}
}
func TestRedisSentinelUsernameOpt(t *testing.T) { func TestRedisSentinelUsernameOpt(t *testing.T) {
uri, _ := url.Parse("redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass") uri, _ := url.Parse("redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass")
opts := getRedisOptions(uri).Failover() opts := getRedisOptions(uri).Failover()

View file

@ -91,8 +91,6 @@ var (
// LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix // LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix
// It maps to ini:"LOCAL_ROOT_URL" // It maps to ini:"LOCAL_ROOT_URL"
LocalURL string LocalURL string
// AssetVersion holds a opaque value that is used for cache-busting assets
AssetVersion string
// Server settings // Server settings
Protocol Scheme Protocol Scheme
@ -751,7 +749,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
} }
AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix) AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix)
AssetVersion = strings.ReplaceAll(AppVer, "+", "~") // make sure the version string is clear (no real escaping is needed)
manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL) manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL)
ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes) ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes)

View file

@ -18,7 +18,6 @@ import (
func Init() error { func Init() error {
if setting.SSH.Disabled { if setting.SSH.Disabled {
builtinUnused()
return nil return nil
} }

View file

@ -81,9 +81,6 @@ func NewFuncMap() []template.FuncMap {
"AppDomain": func() string { "AppDomain": func() string {
return setting.Domain return setting.Domain
}, },
"AssetVersion": func() string {
return setting.AssetVersion
},
"DisableGravatar": func() bool { "DisableGravatar": func() bool {
return setting.DisableGravatar return setting.DisableGravatar
}, },
@ -154,6 +151,7 @@ func NewFuncMap() []template.FuncMap {
"DiffTypeToStr": DiffTypeToStr, "DiffTypeToStr": DiffTypeToStr,
"DiffLineTypeToStr": DiffLineTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr,
"ShortSha": base.ShortSha, "ShortSha": base.ShortSha,
"MD5": base.EncodeMD5,
"ActionContent2Commits": ActionContent2Commits, "ActionContent2Commits": ActionContent2Commits,
"PathEscape": url.PathEscape, "PathEscape": url.PathEscape,
"PathEscapeSegments": util.PathEscapeSegments, "PathEscapeSegments": util.PathEscapeSegments,
@ -456,7 +454,6 @@ func NewFuncMap() []template.FuncMap {
} }
return items return items
}, },
"HasPrefix": strings.HasPrefix,
}} }}
} }
@ -977,11 +974,11 @@ type remoteAddress struct {
Password string Password string
} }
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress { func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
a := remoteAddress{} a := remoteAddress{}
remoteURL := m.OriginalURL remoteURL := m.OriginalURL
if ignoreOriginalURL || remoteURL == "" { if remoteURL == "" {
var err error var err error
remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName) remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
if err != nil { if err != nil {

View file

@ -54,11 +54,6 @@ func (ts TimeStamp) AsTime() (tm time.Time) {
return ts.AsTimeInLocation(setting.DefaultUILocation) return ts.AsTimeInLocation(setting.DefaultUILocation)
} }
// AsLocalTime convert timestamp as time.Time in local location
func (ts TimeStamp) AsLocalTime() time.Time {
return time.Unix(int64(ts), 0)
}
// AsTimeInLocation convert timestamp as time.Time in Local locale // AsTimeInLocation convert timestamp as time.Time in Local locale
func (ts TimeStamp) AsTimeInLocation(loc *time.Location) (tm time.Time) { func (ts TimeStamp) AsTimeInLocation(loc *time.Location) (tm time.Time) {
tm = time.Unix(int64(ts), 0).In(loc) tm = time.Unix(int64(ts), 0).In(loc)

View file

@ -9,8 +9,6 @@ import (
"regexp" "regexp"
"strings" "strings"
"code.gitea.io/gitea/modules/git"
"gitea.com/go-chi/binding" "gitea.com/go-chi/binding"
"github.com/gobwas/glob" "github.com/gobwas/glob"
) )
@ -26,6 +24,30 @@ const (
ErrRegexPattern = "RegexPattern" ErrRegexPattern = "RegexPattern"
) )
// GitRefNamePatternInvalid is regular expression with unallowed characters in git reference name
// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
var GitRefNamePatternInvalid = regexp.MustCompile(`[\000-\037\177 \\~^:?*[]+`)
// CheckGitRefAdditionalRulesValid check name is valid on additional rules
func CheckGitRefAdditionalRulesValid(name string) bool {
// Additional rules as described at https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
if strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") ||
strings.HasSuffix(name, ".") || strings.Contains(name, "..") ||
strings.Contains(name, "//") || strings.Contains(name, "@{") ||
name == "@" {
return false
}
parts := strings.Split(name, "/")
for _, part := range parts {
if strings.HasSuffix(part, ".lock") || strings.HasPrefix(part, ".") {
return false
}
}
return true
}
// AddBindingRules adds additional binding rules // AddBindingRules adds additional binding rules
func AddBindingRules() { func AddBindingRules() {
addGitRefNameBindingRule() addGitRefNameBindingRule()
@ -45,10 +67,16 @@ func addGitRefNameBindingRule() {
IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) {
str := fmt.Sprintf("%v", val) str := fmt.Sprintf("%v", val)
if !git.IsValidRefPattern(str) { if GitRefNamePatternInvalid.MatchString(str) {
errs.Add([]string{name}, ErrGitRefName, "GitRefName") errs.Add([]string{name}, ErrGitRefName, "GitRefName")
return false, errs return false, errs
} }
if !CheckGitRefAdditionalRulesValid(str) {
errs.Add([]string{name}, ErrGitRefName, "GitRefName")
return false, errs
}
return true, errs return true, errs
}, },
}) })

View file

@ -1061,7 +1061,6 @@ normal_view=Normale Ansicht
line=zeile line=zeile
lines=Zeilen lines=Zeilen
editor.add_file=Datei hinzufügen
editor.new_file=Neue Datei editor.new_file=Neue Datei
editor.upload_file=Datei hochladen editor.upload_file=Datei hochladen
editor.edit_file=Datei bearbeiten editor.edit_file=Datei bearbeiten

View file

@ -1061,7 +1061,6 @@ normal_view = Normal View
line = line line = line
lines = lines lines = lines
editor.add_file = Add File
editor.new_file = New File editor.new_file = New File
editor.upload_file = Upload File editor.upload_file = Upload File
editor.edit_file = Edit File editor.edit_file = Edit File
@ -1420,7 +1419,7 @@ issues.due_date_form_remove = "Remove"
issues.due_date_not_writer = "You need repository write access to update an issue's due date." issues.due_date_not_writer = "You need repository write access to update an issue's due date."
issues.due_date_not_set = "No due date set." issues.due_date_not_set = "No due date set."
issues.due_date_added = "added the due date %s %s" issues.due_date_added = "added the due date %s %s"
issues.due_date_modified = "modified the due date from %[2]s to %[1]s %[3]s" issues.due_date_modified = "modified the due date to %s from %s %s"
issues.due_date_remove = "removed the due date %s %s" issues.due_date_remove = "removed the due date %s %s"
issues.due_date_overdue = "Overdue" issues.due_date_overdue = "Overdue"
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'." issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'."

View file

@ -1061,7 +1061,6 @@ normal_view=Vista normal
line=línea line=línea
lines=líneas lines=líneas
editor.add_file=Añadir archivo
editor.new_file=Nuevo Archivo editor.new_file=Nuevo Archivo
editor.upload_file=Subir archivo editor.upload_file=Subir archivo
editor.edit_file=Editar Archivo editor.edit_file=Editar Archivo

View file

@ -820,7 +820,6 @@ normal_view=Vista normale
line=riga line=riga
lines=righe lines=righe
editor.add_file=Aggiungi file
editor.new_file=Nuovo file editor.new_file=Nuovo file
editor.upload_file=Carica File editor.upload_file=Carica File
editor.edit_file=Modifica File editor.edit_file=Modifica File

View file

@ -1061,7 +1061,6 @@ normal_view=Parastais skats
line=rinda line=rinda
lines=rindas lines=rindas
editor.add_file=Pievienot
editor.new_file=Jauna datne editor.new_file=Jauna datne
editor.upload_file=Augšupielādēt failu editor.upload_file=Augšupielādēt failu
editor.edit_file=Labot failu editor.edit_file=Labot failu

View file

@ -838,7 +838,6 @@ normal_view=Normale weergave
line=regel line=regel
lines=regels lines=regels
editor.add_file=Bestand toevoegen
editor.new_file=Nieuw bestand editor.new_file=Nieuw bestand
editor.upload_file=Upload bestand editor.upload_file=Upload bestand
editor.edit_file=Bewerk bestand editor.edit_file=Bewerk bestand

View file

@ -1061,7 +1061,6 @@ normal_view=Vista normal
line=linha line=linha
lines=linhas lines=linhas
editor.add_file=Adicionar ficheiro
editor.new_file=Novo ficheiro editor.new_file=Novo ficheiro
editor.upload_file=Carregar ficheiro editor.upload_file=Carregar ficheiro
editor.edit_file=Editar ficheiro editor.edit_file=Editar ficheiro

View file

@ -948,7 +948,6 @@ normal_view=Normal Görünüm
line=satır line=satır
lines=satır lines=satır
editor.add_file=Dosya Ekle
editor.new_file=Yeni dosya editor.new_file=Yeni dosya
editor.upload_file=Dosya Yükle editor.upload_file=Dosya Yükle
editor.edit_file=Dosyayı Düzenle editor.edit_file=Dosyayı Düzenle

View file

@ -1061,7 +1061,6 @@ normal_view=普通视图
line= line=
lines= lines=
editor.add_file=添加文件
editor.new_file=新建文件 editor.new_file=新建文件
editor.upload_file=上传文件 editor.upload_file=上传文件
editor.edit_file=编辑文件 editor.edit_file=编辑文件

View file

@ -1061,7 +1061,6 @@ normal_view=標準檢視
line= line=
lines= lines=
editor.add_file=加入檔案
editor.new_file=新增文件 editor.new_file=新增文件
editor.upload_file=上傳文件 editor.upload_file=上傳文件
editor.edit_file=編輯文件 editor.edit_file=編輯文件

3984
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,7 @@
"wrap-ansi": "8.0.1" "wrap-ansi": "8.0.1"
}, },
"devDependencies": { "devDependencies": {
"@happy-dom/jest-environment": "4.0.1",
"eslint": "8.15.0", "eslint": "8.15.0",
"eslint-plugin-html": "6.2.0", "eslint-plugin-html": "6.2.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
@ -51,7 +52,6 @@
"eslint-plugin-unicorn": "42.0.0", "eslint-plugin-unicorn": "42.0.0",
"eslint-plugin-vue": "9.0.1", "eslint-plugin-vue": "9.0.1",
"jest": "28.1.0", "jest": "28.1.0",
"jest-environment-jsdom": "28.1.3",
"jest-extended": "2.0.0", "jest-extended": "2.0.0",
"postcss-less": "6.0.0", "postcss-less": "6.0.0",
"stylelint": "14.8.2", "stylelint": "14.8.2",

View file

@ -1 +0,0 @@
<svg viewBox="0 0 16 16" class="svg gitea-join" width="16" height="16" aria-hidden="true"><path d="M14 10.9V8.75h1.25a.75.75 0 0 0 0-1.5H14V5.1a.25.25 0 0 0-.43-.17l-2.9 2.9a.25.25 0 0 0 0 .35l2.9 2.9a.25.25 0 0 0 .43-.18ZM.75 8.75a.75.75 0 0 1 0-1.5H2V5.1a.25.25 0 0 1 .43-.17l2.9 2.9a.25.25 0 0 1 0 .35l-2.9 2.9A.25.25 0 0 1 2 10.9V8.75Zm6.5-6.5a.75.75 0 0 0 1.5 0v-.5a.75.75 0 0 0-1.5 0zM8 6a.75.75 0 0 1-.75-.75v-.5a.75.75 0 0 1 1.5 0v.5A.75.75 0 0 1 8 6Zm-.75 2.25a.75.75 0 0 0 1.5 0v-.5a.75.75 0 0 0-1.5 0zM8 12a.75.75 0 0 1-.75-.75v-.5a.75.75 0 0 1 1.5 0v.5A.75.75 0 0 1 8 12Zm-.75 2.25a.75.75 0 0 0 1.5 0v-.5a.75.75 0 0 0-1.5 0z"/></svg>

Before

Width:  |  Height:  |  Size: 645 B

View file

@ -1 +0,0 @@
<svg viewBox="0 0 16 16" class="svg gitea-split" width="16" height="16" aria-hidden="true"><path d="M7.25 14.25a.75.75 0 0 0 1.5 0v-.5a.75.75 0 0 0-1.5 0zM8 12a.75.75 0 0 1-.75-.75v-.5a.75.75 0 0 1 1.5 0v.5A.75.75 0 0 1 8 12Zm-.75-3.75a.75.75 0 0 0 1.5 0v-.5a.75.75 0 0 0-1.5 0zM8 6a.75.75 0 0 1-.75-.75v-.5a.75.75 0 0 1 1.5 0v.5A.75.75 0 0 1 8 6Zm-.75-3.75a.75.75 0 0 0 1.5 0v-.5a.75.75 0 0 0-1.5 0zm4.1 6.5a.75.75 0 0 1 0-1.5h1.25V5.1a.25.25 0 0 1 .43-.17l2.9 2.9a.25.25 0 0 1 0 .35l-2.9 2.9a.25.25 0 0 1-.43-.18V8.75ZM3.4 10.9V8.75h1.25a.75.75 0 0 0 0-1.5H3.4V5.1a.25.25 0 0 0-.43-.17l-2.9 2.9a.25.25 0 0 0 0 .35l2.9 2.9a.25.25 0 0 0 .43-.18z"/></svg>

Before

Width:  |  Height:  |  Size: 654 B

View file

@ -1 +0,0 @@
<svg viewBox="0 0 15 15" class="svg gitea-whitespace" width="16" height="16" aria-hidden="true"><path d="m2.5 7.5.35.35a.5.5 0 0 0 0-.7l-.35.35ZM3 4h12V3H3v1Zm4 4h8V7H7v1Zm-4 4h12v-1H3v1ZM.85 9.85l2-2-.7-.7-2 2 .7.7Zm2-2.7-2-2-.7.7 2 2 .7-.7Z"/></svg>

Before

Width:  |  Height:  |  Size: 251 B

View file

@ -268,7 +268,6 @@ func UploadPackageFile(ctx *context.Context) {
}, },
Data: buf, Data: buf,
IsLead: false, IsLead: false,
OverwriteExisting: params.IsMeta,
} }
// If it's the package pom file extract the metadata // If it's the package pom file extract the metadata

View file

@ -63,9 +63,6 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
Homepage: metadata.ProjectURL, Homepage: metadata.ProjectURL,
License: metadata.License, License: metadata.License,
Dependencies: metadata.Dependencies, Dependencies: metadata.Dependencies,
DevDependencies: metadata.DevelopmentDependencies,
PeerDependencies: metadata.PeerDependencies,
OptionalDependencies: metadata.OptionalDependencies,
Readme: metadata.Readme, Readme: metadata.Readme,
Dist: npm_module.PackageDistribution{ Dist: npm_module.PackageDistribution{
Shasum: pd.Files[0].Blob.HashSHA1, Shasum: pd.Files[0].Blob.HashSHA1,

View file

@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -52,7 +53,7 @@ func GetSingleCommit(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
sha := ctx.Params(":sha") sha := ctx.Params(":sha")
if !git.IsValidRefPattern(sha) { if (validation.GitRefNamePatternInvalid.MatchString(sha) || !validation.CheckGitRefAdditionalRulesValid(sha)) && !git.SHAPattern.MatchString(sha) {
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha)) ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
return return
} }

View file

@ -8,7 +8,6 @@ package repo
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -30,7 +29,7 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/routers/common"
archiver_service "code.gitea.io/gitea/services/repository/archiver" "code.gitea.io/gitea/routers/web/repo"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
) )
@ -295,53 +294,7 @@ func GetArchive(ctx *context.APIContext) {
defer gitRepo.Close() defer gitRepo.Close()
} }
archiveDownload(ctx) repo.Download(ctx.Context)
}
func archiveDownload(ctx *context.APIContext) {
uri := ctx.Params("*")
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
ctx.Error(http.StatusNotFound, "unrecognized reference", err)
} else {
ctx.ServerError("archiver_service.NewRequest", err)
}
return
}
archiver, err := aReq.Await(ctx)
if err != nil {
ctx.ServerError("archiver.Await", err)
return
}
download(ctx, aReq.GetArchiveName(), archiver)
}
func download(ctx *context.APIContext, archiveName string, archiver *repo_model.RepoArchiver) {
downloadName := ctx.Repo.Repository.Name + "-" + archiveName
rPath := archiver.RelativePath()
if setting.RepoArchive.ServeDirect {
// If we have a signed url (S3, object storage), redirect to this directly.
u, err := storage.RepoArchives.URL(rPath, downloadName)
if u != nil && err == nil {
ctx.Redirect(u.String())
return
}
}
// If we have matched and access to release or issue
fr, err := storage.RepoArchives.Open(rPath)
if err != nil {
ctx.ServerError("Open", err)
return
}
defer fr.Close()
ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())
} }
// GetEditorconfig get editor config of a repository // GetEditorconfig get editor config of a repository

View file

@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
@ -141,7 +140,7 @@ func TestHook(ctx *context.APIContext) {
// required: true // required: true
// - name: ref // - name: ref
// in: query // in: query
// description: "The name of the commit/branch/tag, indicates which commit will be loaded to the webhook payload." // description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string // type: string
// required: false // required: false
// responses: // responses:
@ -154,11 +153,6 @@ func TestHook(ctx *context.APIContext) {
return return
} }
ref := git.BranchPrefix + ctx.Repo.Repository.DefaultBranch
if r := ctx.FormTrim("ref"); r != "" {
ref = r
}
hookID := ctx.ParamsInt64(":id") hookID := ctx.ParamsInt64(":id")
hook, err := utils.GetRepoHook(ctx, ctx.Repo.Repository.ID, hookID) hook, err := utils.GetRepoHook(ctx, ctx.Repo.Repository.ID, hookID)
if err != nil { if err != nil {
@ -167,12 +161,10 @@ func TestHook(ctx *context.APIContext) {
commit := convert.ToPayloadCommit(ctx.Repo.Repository, ctx.Repo.Commit) commit := convert.ToPayloadCommit(ctx.Repo.Repository, ctx.Repo.Commit)
commitID := ctx.Repo.Commit.ID.String()
if err := webhook_service.PrepareWebhook(hook, ctx.Repo.Repository, webhook.HookEventPush, &api.PushPayload{ if err := webhook_service.PrepareWebhook(hook, ctx.Repo.Repository, webhook.HookEventPush, &api.PushPayload{
Ref: ref, Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch,
Before: commitID, Before: ctx.Repo.Commit.ID.String(),
After: commitID, After: ctx.Repo.Commit.ID.String(),
CompareURL: setting.AppURL + ctx.Repo.Repository.ComposeCompareURL(commitID, commitID),
Commits: []*api.PayloadCommit{commit}, Commits: []*api.PayloadCommit{commit},
HeadCommit: commit, HeadCommit: commit,
Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone), Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone),

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/validation"
) )
// GetNote Get a note corresponding to a single commit from a repository // GetNote Get a note corresponding to a single commit from a repository
@ -46,7 +47,7 @@ func GetNote(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
sha := ctx.Params(":sha") sha := ctx.Params(":sha")
if !git.IsValidRefPattern(sha) { if (validation.GitRefNamePatternInvalid.MatchString(sha) || !validation.CheckGitRefAdditionalRulesValid(sha)) && !git.SHAPattern.MatchString(sha) {
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha)) ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
return return
} }

View file

@ -57,10 +57,6 @@ func GetReleaseAttachment(ctx *context.APIContext) {
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":asset")
attach, err := repo_model.GetAttachmentByID(ctx, attachID) attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil { if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err) ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
return return
} }
@ -104,10 +100,6 @@ func ListReleaseAttachments(ctx *context.APIContext) {
releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
release, err := models.GetReleaseByID(ctx, releaseID) release, err := models.GetReleaseByID(ctx, releaseID)
if err != nil { if err != nil {
if models.IsErrReleaseNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err) ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }
@ -174,10 +166,6 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
release, err := models.GetReleaseByID(ctx, releaseID) release, err := models.GetReleaseByID(ctx, releaseID)
if err != nil { if err != nil {
if models.IsErrReleaseNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err) ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return return
} }
@ -256,10 +244,6 @@ func EditReleaseAttachment(ctx *context.APIContext) {
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":asset")
attach, err := repo_model.GetAttachmentByID(ctx, attachID) attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil { if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err) ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
return return
} }
@ -318,10 +302,6 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":asset")
attach, err := repo_model.GetAttachmentByID(ctx, attachID) attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil { if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err) ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
return return
} }

View file

@ -7,7 +7,6 @@ package user
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -178,12 +177,6 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
token := asymkey_model.VerificationToken(ctx.Doer, 1) token := asymkey_model.VerificationToken(ctx.Doer, 1)
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0) lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
form.KeyID = strings.TrimLeft(form.KeyID, "0")
if form.KeyID == "" {
ctx.NotFound()
return
}
_, err := asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, token, form.Signature) _, err := asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, token, form.Signature)
if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) { if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) {
_, err = asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, lastToken, form.Signature) _, err = asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, lastToken, form.Signature)

View file

@ -209,11 +209,7 @@ func NewUserPost(ctx *context.Context) {
func prepareUserInfo(ctx *context.Context) *user_model.User { func prepareUserInfo(ctx *context.Context) *user_model.User {
u, err := user_model.GetUserByID(ctx.ParamsInt64(":userid")) u, err := user_model.GetUserByID(ctx.ParamsInt64(":userid"))
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Redirect(setting.AppSubURL + "/admin/users")
} else {
ctx.ServerError("GetUserByID", err) ctx.ServerError("GetUserByID", err)
}
return nil return nil
} }
ctx.Data["User"] = u ctx.Data["User"] = u

View file

@ -339,7 +339,7 @@ func SearchTeam(ctx *context.Context) {
} }
opts := &organization.SearchTeamOptions{ opts := &organization.SearchTeamOptions{
// UserID is not set because the router already requires the doer to be an org admin. Thus, we don't need to restrict to teams that the user belongs in UserID: ctx.Doer.ID,
Keyword: ctx.FormTrim("q"), Keyword: ctx.FormTrim("q"),
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"), IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),

View file

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
"time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -20,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -387,27 +389,68 @@ func Download(ctx *context.Context) {
if err != nil { if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
ctx.Error(http.StatusBadRequest, err.Error()) ctx.Error(http.StatusBadRequest, err.Error())
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
ctx.Error(http.StatusNotFound, err.Error())
} else { } else {
ctx.ServerError("archiver_service.NewRequest", err) ctx.ServerError("archiver_service.NewRequest", err)
} }
return return
} }
if aReq == nil {
archiver, err := aReq.Await(ctx) ctx.Error(http.StatusNotFound)
if err != nil {
ctx.ServerError("archiver.Await", err)
return return
} }
archiver, err := repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID)
if err != nil {
ctx.ServerError("models.GetRepoArchiver", err)
return
}
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
download(ctx, aReq.GetArchiveName(), archiver) download(ctx, aReq.GetArchiveName(), archiver)
return
}
if err := archiver_service.StartArchive(aReq); err != nil {
ctx.ServerError("archiver_service.StartArchive", err)
return
}
var times int
t := time.NewTicker(time.Second * 1)
defer t.Stop()
for {
select {
case <-graceful.GetManager().HammerContext().Done():
log.Warn("exit archive download because system stop")
return
case <-t.C:
if times > 20 {
ctx.ServerError("wait download timeout", nil)
return
}
times++
archiver, err = repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID)
if err != nil {
ctx.ServerError("archiver_service.StartArchive", err)
return
}
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
download(ctx, aReq.GetArchiveName(), archiver)
return
}
}
}
} }
func download(ctx *context.Context, archiveName string, archiver *repo_model.RepoArchiver) { func download(ctx *context.Context, archiveName string, archiver *repo_model.RepoArchiver) {
downloadName := ctx.Repo.Repository.Name + "-" + archiveName downloadName := ctx.Repo.Repository.Name + "-" + archiveName
rPath := archiver.RelativePath() rPath, err := archiver.RelativePath()
if err != nil {
ctx.ServerError("archiver.RelativePath", err)
return
}
if setting.RepoArchive.ServeDirect { if setting.RepoArchive.ServeDirect {
// If we have a signed url (S3, object storage), redirect to this directly. // If we have a signed url (S3, object storage), redirect to this directly.
u, err := storage.RepoArchives.URL(rPath, downloadName) u, err := storage.RepoArchives.URL(rPath, downloadName)

View file

@ -227,17 +227,14 @@ func SettingsPost(ctx *context.Context) {
form.MirrorPassword, _ = u.User.Password() form.MirrorPassword, _ = u.User.Password()
} }
address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) err = migrations.IsMigrateURLAllowed(u.String(), ctx.Doer)
if err == nil {
err = migrations.IsMigrateURLAllowed(address, ctx.Doer)
}
if err != nil { if err != nil {
ctx.Data["Err_MirrorAddress"] = true ctx.Data["Err_MirrorAddress"] = true
handleSettingRemoteAddrError(ctx, err, form) handleSettingRemoteAddrError(ctx, err, form)
return return
} }
if err := mirror_service.UpdateAddress(ctx, ctx.Repo.Mirror, address); err != nil { if err := mirror_service.UpdateAddress(ctx, ctx.Repo.Mirror, u.String()); err != nil {
ctx.ServerError("UpdateAddress", err) ctx.ServerError("UpdateAddress", err)
return return
} }

View file

@ -1271,12 +1271,10 @@ func TestWebhook(ctx *context.Context) {
}, },
} }
commitID := commit.ID.String()
p := &api.PushPayload{ p := &api.PushPayload{
Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch, Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch,
Before: commitID, Before: commit.ID.String(),
After: commitID, After: commit.ID.String(),
CompareURL: setting.AppURL + ctx.Repo.Repository.ComposeCompareURL(commitID, commitID),
Commits: []*api.PayloadCommit{apiCommit}, Commits: []*api.PayloadCommit{apiCommit},
HeadCommit: apiCommit, HeadCommit: apiCommit,
Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone), Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone),

View file

@ -100,6 +100,39 @@ func Dashboard(ctx *context.Context) {
} }
var err error var err error
var mirrors []*repo_model.Repository
if ctxUser.IsOrganization() {
var env organization.AccessibleReposEnvironment
if ctx.Org.Team != nil {
env = organization.OrgFromUser(ctxUser).AccessibleTeamReposEnv(ctx.Org.Team)
} else {
env, err = organization.AccessibleReposEnv(ctx, organization.OrgFromUser(ctxUser), ctx.Doer.ID)
if err != nil {
ctx.ServerError("AccessibleReposEnv", err)
return
}
}
mirrors, err = env.MirrorRepos()
if err != nil {
ctx.ServerError("env.MirrorRepos", err)
return
}
} else {
mirrors, err = repo_model.GetUserMirrorRepositories(ctxUser.ID)
if err != nil {
ctx.ServerError("GetUserMirrorRepositories", err)
return
}
}
ctx.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum
if err := repo_model.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil {
ctx.ServerError("MirrorRepositoryList.LoadAttributes", err)
return
}
ctx.Data["MirrorCount"] = len(mirrors)
ctx.Data["Mirrors"] = mirrors
ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{ ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{
RequestedUser: ctxUser, RequestedUser: ctxUser,
RequestedTeam: ctx.Org.Team, RequestedTeam: ctx.Org.Team,

View file

@ -107,24 +107,9 @@ func NewCodebaseDownloader(ctx context.Context, projectURL *url.URL, project, re
commitMap: make(map[string]string), commitMap: make(map[string]string),
} }
log.Trace("Create Codebase downloader. BaseURL: %s Project: %s RepoName: %s", baseURL, project, repoName)
return downloader return downloader
} }
// String implements Stringer
func (d *CodebaseDownloader) String() string {
return fmt.Sprintf("migration from codebase server %s %s/%s", d.baseURL, d.project, d.repoName)
}
// ColorFormat provides a basic color format for a GogsDownloader
func (d *CodebaseDownloader) ColorFormat(s fmt.State) {
if d == nil {
log.ColorFprintf(s, "<nil: CodebaseDownloader>")
return
}
log.ColorFprintf(s, "migration from codebase server %s %s/%s", d.baseURL, d.project, d.repoName)
}
// FormatCloneURL add authentication into remote URLs // FormatCloneURL add authentication into remote URLs
func (d *CodebaseDownloader) FormatCloneURL(opts base.MigrateOptions, remoteAddr string) (string, error) { func (d *CodebaseDownloader) FormatCloneURL(opts base.MigrateOptions, remoteAddr string) (string, error) {
return opts.CloneAddr, nil return opts.CloneAddr, nil
@ -466,8 +451,8 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq
Value int64 `xml:",chardata"` Value int64 `xml:",chardata"`
Type string `xml:"type,attr"` Type string `xml:"type,attr"`
} `xml:"id"` } `xml:"id"`
SourceRef string `xml:"source-ref"` // NOTE: from the documentation these are actually just branches NOT full refs SourceRef string `xml:"source-ref"`
TargetRef string `xml:"target-ref"` // NOTE: from the documentation these are actually just branches NOT full refs TargetRef string `xml:"target-ref"`
Subject string `xml:"subject"` Subject string `xml:"subject"`
Status string `xml:"status"` Status string `xml:"status"`
UserID struct { UserID struct {
@ -579,9 +564,6 @@ func (d *CodebaseDownloader) GetPullRequests(page, perPage int) ([]*base.PullReq
Comments: comments[1:], Comments: comments[1:],
}, },
}) })
// SECURITY: Ensure that the PR is safe
_ = CheckAndEnsureSafePR(pullRequests[len(pullRequests)-1], d.baseURL.String(), d)
} }
return pullRequests, true, nil return pullRequests, true, nil

View file

@ -1,82 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"strings"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
)
// WarnAndNotice will log the provided message and send a repository notice
func WarnAndNotice(fmtStr string, args ...interface{}) {
log.Warn(fmtStr, args...)
if err := admin_model.CreateRepositoryNotice(fmt.Sprintf(fmtStr, args...)); err != nil {
log.Error("create repository notice failed: ", err)
}
}
func hasBaseURL(toCheck, baseURL string) bool {
if len(baseURL) > 0 && baseURL[len(baseURL)-1] != '/' {
baseURL += "/"
}
return strings.HasPrefix(toCheck, baseURL)
}
// CheckAndEnsureSafePR will check that a given PR is safe to download
func CheckAndEnsureSafePR(pr *base.PullRequest, commonCloneBaseURL string, g base.Downloader) bool {
valid := true
// SECURITY: the patchURL must be checked to have the same baseURL as the current to prevent open redirect
if pr.PatchURL != "" && !hasBaseURL(pr.PatchURL, commonCloneBaseURL) {
// TODO: Should we check that this url has the expected format for a patch url?
WarnAndNotice("PR #%d in %s has invalid PatchURL: %s baseURL: %s", pr.Number, g, pr.PatchURL, commonCloneBaseURL)
pr.PatchURL = ""
valid = false
}
// SECURITY: the headCloneURL must be checked to have the same baseURL as the current to prevent open redirect
if pr.Head.CloneURL != "" && !hasBaseURL(pr.Head.CloneURL, commonCloneBaseURL) {
// TODO: Should we check that this url has the expected format for a patch url?
WarnAndNotice("PR #%d in %s has invalid HeadCloneURL: %s baseURL: %s", pr.Number, g, pr.Head.CloneURL, commonCloneBaseURL)
pr.Head.CloneURL = ""
valid = false
}
// SECURITY: SHAs Must be a SHA
if pr.MergeCommitSHA != "" && !git.IsValidSHAPattern(pr.MergeCommitSHA) {
WarnAndNotice("PR #%d in %s has invalid MergeCommitSHA: %s", pr.Number, g, pr.MergeCommitSHA)
pr.MergeCommitSHA = ""
}
if pr.Head.SHA != "" && !git.IsValidSHAPattern(pr.Head.SHA) {
WarnAndNotice("PR #%d in %s has invalid HeadSHA: %s", pr.Number, g, pr.Head.SHA)
pr.Head.SHA = ""
valid = false
}
if pr.Base.SHA != "" && !git.IsValidSHAPattern(pr.Base.SHA) {
WarnAndNotice("PR #%d in %s has invalid BaseSHA: %s", pr.Number, g, pr.Base.SHA)
pr.Base.SHA = ""
valid = false
}
// SECURITY: Refs must be valid refs or SHAs
if pr.Head.Ref != "" && !git.IsValidRefPattern(pr.Head.Ref) {
WarnAndNotice("PR #%d in %s has invalid HeadRef: %s", pr.Number, g, pr.Head.Ref)
pr.Head.Ref = ""
valid = false
}
if pr.Base.Ref != "" && !git.IsValidRefPattern(pr.Base.Ref) {
WarnAndNotice("PR #%d in %s has invalid BaseRef: %s", pr.Number, g, pr.Base.Ref)
pr.Base.Ref = ""
valid = false
}
pr.EnsuredSafe = true
return valid
}

View file

@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -25,8 +26,6 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"github.com/google/uuid"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -48,7 +47,7 @@ type RepositoryDumper struct {
reviewFiles map[int64]*os.File reviewFiles map[int64]*os.File
gitRepo *git.Repository gitRepo *git.Repository
prHeadCache map[string]string prHeadCache map[string]struct{}
} }
// NewRepositoryDumper creates an gitea Uploader // NewRepositoryDumper creates an gitea Uploader
@ -63,7 +62,7 @@ func NewRepositoryDumper(ctx context.Context, baseDir, repoOwner, repoName strin
baseDir: baseDir, baseDir: baseDir,
repoOwner: repoOwner, repoOwner: repoOwner,
repoName: repoName, repoName: repoName,
prHeadCache: make(map[string]string), prHeadCache: make(map[string]struct{}),
commentFiles: make(map[int64]*os.File), commentFiles: make(map[int64]*os.File),
reviewFiles: make(map[int64]*os.File), reviewFiles: make(map[int64]*os.File),
}, nil }, nil
@ -297,10 +296,8 @@ func (g *RepositoryDumper) CreateReleases(releases ...*base.Release) error {
} }
for _, asset := range release.Assets { for _, asset := range release.Assets {
attachLocalPath := filepath.Join(attachDir, asset.Name) attachLocalPath := filepath.Join(attachDir, asset.Name)
// SECURITY: We cannot check the DownloadURL and DownloadFunc are safe here
// ... we must assume that they are safe and simply download the attachment
// download attachment // download attachment
err := func(attachPath string) error { err := func(attachPath string) error {
var rc io.ReadCloser var rc io.ReadCloser
var err error var err error
@ -320,7 +317,7 @@ func (g *RepositoryDumper) CreateReleases(releases ...*base.Release) error {
fw, err := os.Create(attachPath) fw, err := os.Create(attachPath)
if err != nil { if err != nil {
return fmt.Errorf("create: %w", err) return fmt.Errorf("Create: %v", err)
} }
defer fw.Close() defer fw.Close()
@ -388,18 +385,9 @@ func (g *RepositoryDumper) createItems(dir string, itemFiles map[int64]*os.File,
} }
for number, items := range itemsMap { for number, items := range itemsMap {
if err := g.encodeItems(number, items, dir, itemFiles); err != nil { var err error
return err
}
}
return nil
}
func (g *RepositoryDumper) encodeItems(number int64, items []interface{}, dir string, itemFiles map[int64]*os.File) error {
itemFile := itemFiles[number] itemFile := itemFiles[number]
if itemFile == nil { if itemFile == nil {
var err error
itemFile, err = os.Create(filepath.Join(dir, fmt.Sprintf("%d.yml", number))) itemFile, err = os.Create(filepath.Join(dir, fmt.Sprintf("%d.yml", number)))
if err != nil { if err != nil {
return err return err
@ -407,10 +395,17 @@ func (g *RepositoryDumper) encodeItems(number int64, items []interface{}, dir st
itemFiles[number] = itemFile itemFiles[number] = itemFile
} }
encoder := yaml.NewEncoder(itemFile) bs, err := yaml.Marshal(items)
defer encoder.Close() if err != nil {
return err
}
return encoder.Encode(items) if _, err := itemFile.Write(bs); err != nil {
return err
}
}
return nil
} }
// CreateComments creates comments of issues // CreateComments creates comments of issues
@ -423,30 +418,16 @@ func (g *RepositoryDumper) CreateComments(comments ...*base.Comment) error {
return g.createItems(g.commentDir(), g.commentFiles, commentsMap) return g.createItems(g.commentDir(), g.commentFiles, commentsMap)
} }
func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error { // CreatePullRequests creates pull requests
// SECURITY: this pr must have been ensured safe func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error {
if !pr.EnsuredSafe { for _, pr := range prs {
log.Error("PR #%d in %s/%s has not been checked for safety ... We will ignore this.", pr.Number, g.repoOwner, g.repoName) // download patch file
return fmt.Errorf("unsafe PR #%d", pr.Number)
}
// First we download the patch file
err := func() error { err := func() error {
// if the patchURL is empty there is nothing to download
if pr.PatchURL == "" {
return nil
}
// SECURITY: We will assume that the pr.PatchURL has been checked
// pr.PatchURL maybe a local file - but note EnsureSafe should be asserting that this safe
u, err := g.setURLToken(pr.PatchURL) u, err := g.setURLToken(pr.PatchURL)
if err != nil { if err != nil {
return err return err
} }
resp, err := http.Get(u)
// SECURITY: We will assume that the pr.PatchURL has been checked
// pr.PatchURL maybe a local file - but note EnsureSafe should be asserting that this safe
resp, err := http.Get(u) // TODO: This probably needs to use the downloader as there may be rate limiting issues here
if err != nil { if err != nil {
return err return err
} }
@ -461,8 +442,6 @@ func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error {
return err return err
} }
defer f.Close() defer f.Close()
// TODO: Should there be limits on the size of this file?
if _, err = io.Copy(f, resp.Body); err != nil { if _, err = io.Copy(f, resp.Body); err != nil {
return err return err
} }
@ -471,127 +450,70 @@ func (g *RepositoryDumper) handlePullRequest(pr *base.PullRequest) error {
return nil return nil
}() }()
if err != nil { if err != nil {
log.Error("PR #%d in %s/%s unable to download patch: %v", pr.Number, g.repoOwner, g.repoName, err)
return err return err
} }
isFork := pr.IsForkPullRequest() // set head information
pullHead := filepath.Join(g.gitPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number))
// Even if it's a forked repo PR, we have to change head info as the same as the base info if err := os.MkdirAll(pullHead, os.ModePerm); err != nil {
oldHeadOwnerName := pr.Head.OwnerName return err
pr.Head.OwnerName, pr.Head.RepoName = pr.Base.OwnerName, pr.Base.RepoName
if !isFork || pr.State == "closed" {
return nil
} }
p, err := os.Create(filepath.Join(pullHead, "head"))
// OK we want to fetch the current head as a branch from its CloneURL
// 1. Is there a head clone URL available?
// 2. Is there a head ref available?
if pr.Head.CloneURL == "" || pr.Head.Ref == "" {
// Set head information if pr.Head.SHA is available
if pr.Head.SHA != "" {
_, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref", pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()})
if err != nil { if err != nil {
log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err) return err
} }
} _, err = p.WriteString(pr.Head.SHA)
return nil p.Close()
if err != nil {
return err
} }
// 3. We need to create a remote for this clone url if pr.IsForkPullRequest() && pr.State != "closed" {
// ... maybe we already have a name for this remote if pr.Head.OwnerName != "" {
remote, ok := g.prHeadCache[pr.Head.CloneURL+":"] remote := pr.Head.OwnerName
_, ok := g.prHeadCache[remote]
if !ok { if !ok {
// ... let's try ownername as a reasonable name // git remote add
remote = oldHeadOwnerName // TODO: how to handle private CloneURL?
if !git.IsValidRefPattern(remote) {
// ... let's try something less nice
remote = "head-pr-" + strconv.FormatInt(pr.Number, 10)
}
// ... now add the remote
err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true) err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
if err != nil { if err != nil {
log.Error("PR #%d in %s/%s AddRemote[%s] failed: %v", pr.Number, g.repoOwner, g.repoName, remote, err) log.Error("AddRemote failed: %s", err)
} else { } else {
g.prHeadCache[pr.Head.CloneURL+":"] = remote g.prHeadCache[remote] = struct{}{}
ok = true ok = true
} }
} }
if !ok {
// Set head information if pr.Head.SHA is available
if pr.Head.SHA != "" {
_, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref", pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()})
if err != nil {
log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err)
}
}
return nil if ok {
} _, _, err = git.NewCommand(g.ctx, "fetch", remote, pr.Head.Ref).RunStdString(&git.RunOpts{Dir: g.gitPath()})
// 4. Check if we already have this ref?
localRef, ok := g.prHeadCache[pr.Head.CloneURL+":"+pr.Head.Ref]
if !ok {
// ... We would normally name this migrated branch as <OwnerName>/<HeadRef> but we need to ensure that is safe
localRef = git.SanitizeRefPattern(oldHeadOwnerName + "/" + pr.Head.Ref)
// ... Now we must assert that this does not exist
if g.gitRepo.IsBranchExist(localRef) {
localRef = "head-pr-" + strconv.FormatInt(pr.Number, 10) + "/" + localRef
i := 0
for g.gitRepo.IsBranchExist(localRef) {
if i > 5 {
// ... We tried, we really tried but this is just a seriously unfriendly repo
return fmt.Errorf("unable to create unique local reference from %s", pr.Head.Ref)
}
// OK just try some uuids!
localRef = git.SanitizeRefPattern("head-pr-" + strconv.FormatInt(pr.Number, 10) + uuid.New().String())
i++
}
}
fetchArg := pr.Head.Ref + ":" + git.BranchPrefix + localRef
if strings.HasPrefix(fetchArg, "-") {
fetchArg = git.BranchPrefix + fetchArg
}
_, _, err = git.NewCommand(g.ctx, "fetch", "--no-tags", "--", remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.gitPath()})
if err != nil { if err != nil {
log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
// We need to continue here so that the Head.Ref is reset and we attempt to set the gitref for the PR
// (This last step will likely fail but we should try to do as much as we can.)
} else { } else {
// Cache the localRef as the Head.Ref - if we've failed we can always try again. // a new branch name with <original_owner_name/original_branchname> will be created to as new head branch
g.prHeadCache[pr.Head.CloneURL+":"+pr.Head.Ref] = localRef ref := path.Join(pr.Head.OwnerName, pr.Head.Ref)
headBranch := filepath.Join(g.gitPath(), "refs", "heads", ref)
if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil {
return err
} }
} b, err := os.Create(headBranch)
// Set the pr.Head.Ref to the localRef
pr.Head.Ref = localRef
// 5. Now if pr.Head.SHA == "" we should recover this to the head of this branch
if pr.Head.SHA == "" {
headSha, err := g.gitRepo.GetBranchCommitID(localRef)
if err != nil { if err != nil {
log.Error("unable to get head SHA of local head for PR #%d from %s in %s/%s. Error: %v", pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) return err
return nil
} }
pr.Head.SHA = headSha _, err = b.WriteString(pr.Head.SHA)
} b.Close()
if pr.Head.SHA != "" {
_, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref", pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()})
if err != nil { if err != nil {
log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) return err
} }
pr.Head.Ref = ref
}
}
}
}
// whatever it's a forked repo PR, we have to change head info as the same as the base info
pr.Head.OwnerName = pr.Base.OwnerName
pr.Head.RepoName = pr.Base.RepoName
} }
return nil
}
// CreatePullRequests creates pull requests
func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error {
var err error var err error
if g.pullrequestFile == nil { if g.pullrequestFile == nil {
if err := os.MkdirAll(g.baseDir, os.ModePerm); err != nil { if err := os.MkdirAll(g.baseDir, os.ModePerm); err != nil {
@ -603,22 +525,16 @@ func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error {
} }
} }
encoder := yaml.NewEncoder(g.pullrequestFile) bs, err := yaml.Marshal(prs)
defer encoder.Close() if err != nil {
return err
count := 0
for i := 0; i < len(prs); i++ {
pr := prs[i]
if err := g.handlePullRequest(pr); err != nil {
log.Error("PR #%d in %s/%s failed - skipping", pr.Number, g.repoOwner, g.repoName, err)
continue
} }
prs[count] = pr
count++
}
prs = prs[:count]
return encoder.Encode(prs) if _, err := g.pullrequestFile.Write(bs); err != nil {
return err
}
return nil
} }
// CreateReviews create pull request reviews // CreateReviews create pull request reviews
@ -644,10 +560,6 @@ func (g *RepositoryDumper) Finish() error {
// DumpRepository dump repository according MigrateOptions to a local directory // DumpRepository dump repository according MigrateOptions to a local directory
func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error { func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error {
doer, err := user_model.GetAdminUser()
if err != nil {
return err
}
downloader, err := newDownloader(ctx, ownerName, opts) downloader, err := newDownloader(ctx, ownerName, opts)
if err != nil { if err != nil {
return err return err
@ -657,7 +569,7 @@ func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.Mi
return err return err
} }
if err := migrateRepository(doer, downloader, uploader, opts, nil); err != nil { if err := migrateRepository(downloader, uploader, opts, nil); err != nil {
if err1 := uploader.Rollback(); err1 != nil { if err1 := uploader.Rollback(); err1 != nil {
log.Error("rollback failed: %v", err1) log.Error("rollback failed: %v", err1)
} }
@ -729,7 +641,7 @@ func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string,
return err return err
} }
if err = migrateRepository(doer, downloader, uploader, migrateOpts, nil); err != nil { if err = migrateRepository(downloader, uploader, migrateOpts, nil); err != nil {
if err1 := uploader.Rollback(); err1 != nil { if err1 := uploader.Rollback(); err1 != nil {
log.Error("rollback failed: %v", err1) log.Error("rollback failed: %v", err1)
} }

View file

@ -6,11 +6,9 @@ package migrations
import ( import (
"context" "context"
"fmt"
"net/url" "net/url"
"strings" "strings"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
) )
@ -39,7 +37,6 @@ func (f *GitBucketDownloaderFactory) New(ctx context.Context, opts base.MigrateO
oldOwner := fields[1] oldOwner := fields[1]
oldName := strings.TrimSuffix(fields[2], ".git") oldName := strings.TrimSuffix(fields[2], ".git")
log.Trace("Create GitBucket downloader. BaseURL: %s RepoOwner: %s RepoName: %s", baseURL, oldOwner, oldName)
return NewGitBucketDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil return NewGitBucketDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
} }
@ -54,20 +51,6 @@ type GitBucketDownloader struct {
*GithubDownloaderV3 *GithubDownloaderV3
} }
// String implements Stringer
func (g *GitBucketDownloader) String() string {
return fmt.Sprintf("migration from gitbucket server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
// ColorFormat provides a basic color format for a GitBucketDownloader
func (g *GitBucketDownloader) ColorFormat(s fmt.State) {
if g == nil {
log.ColorFprintf(s, "<nil: GitBucketDownloader>")
return
}
log.ColorFprintf(s, "migration from gitbucket server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
// NewGitBucketDownloader creates a GitBucket downloader // NewGitBucketDownloader creates a GitBucket downloader
func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader { func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader {
githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName) githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName)

View file

@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
@ -70,7 +71,6 @@ type GiteaDownloader struct {
base.NullDownloader base.NullDownloader
ctx context.Context ctx context.Context
client *gitea_sdk.Client client *gitea_sdk.Client
baseURL string
repoOwner string repoOwner string
repoName string repoName string
pagination bool pagination bool
@ -116,7 +116,6 @@ func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, passwo
return &GiteaDownloader{ return &GiteaDownloader{
ctx: ctx, ctx: ctx,
client: giteaClient, client: giteaClient,
baseURL: baseURL,
repoOwner: path[0], repoOwner: path[0],
repoName: path[1], repoName: path[1],
pagination: paginationSupport, pagination: paginationSupport,
@ -129,20 +128,6 @@ func (g *GiteaDownloader) SetContext(ctx context.Context) {
g.ctx = ctx g.ctx = ctx
} }
// String implements Stringer
func (g *GiteaDownloader) String() string {
return fmt.Sprintf("migration from gitea server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
// ColorFormat provides a basic color format for a GiteaDownloader
func (g *GiteaDownloader) ColorFormat(s fmt.State) {
if g == nil {
log.ColorFprintf(s, "<nil: GiteaDownloader>")
return
}
log.ColorFprintf(s, "migration from gitea server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
// GetRepoInfo returns a repository information // GetRepoInfo returns a repository information
func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) { func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) {
if g == nil { if g == nil {
@ -298,12 +283,6 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !hasBaseURL(asset.DownloadURL, g.baseURL) {
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, asset.DownloadURL)
return io.NopCloser(strings.NewReader(asset.DownloadURL)), nil
}
// FIXME: for a private download? // FIXME: for a private download?
req, err := http.NewRequest("GET", asset.DownloadURL, nil) req, err := http.NewRequest("GET", asset.DownloadURL, nil)
if err != nil { if err != nil {
@ -423,7 +402,11 @@ func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, err
reactions, err := g.getIssueReactions(issue.Index) reactions, err := g.getIssueReactions(issue.Index)
if err != nil { if err != nil {
WarnAndNotice("Unable to load reactions during migrating issue #%d in %s. Error: %v", issue.Index, g, err) log.Warn("Unable to load reactions during migrating issue #%d to %s/%s. Error: %v", issue.Index, g.repoOwner, g.repoName, err)
if err2 := admin_model.CreateRepositoryNotice(
fmt.Sprintf("Unable to load reactions during migrating issue #%d to %s/%s. Error: %v", issue.Index, g.repoOwner, g.repoName, err)); err2 != nil {
log.Error("create repository notice failed: ", err2)
}
} }
var assignees []string var assignees []string
@ -481,7 +464,11 @@ func (g *GiteaDownloader) GetComments(commentable base.Commentable) ([]*base.Com
for _, comment := range comments { for _, comment := range comments {
reactions, err := g.getCommentReactions(comment.ID) reactions, err := g.getCommentReactions(comment.ID)
if err != nil { if err != nil {
WarnAndNotice("Unable to load comment reactions during migrating issue #%d for comment %d in %s. Error: %v", commentable.GetForeignIndex(), comment.ID, g, err) log.Warn("Unable to load comment reactions during migrating issue #%d for comment %d to %s/%s. Error: %v", commentable.GetForeignIndex(), comment.ID, g.repoOwner, g.repoName, err)
if err2 := admin_model.CreateRepositoryNotice(
fmt.Sprintf("Unable to load reactions during migrating issue #%d for comment %d to %s/%s. Error: %v", commentable.GetForeignIndex(), comment.ID, g.repoOwner, g.repoName, err)); err2 != nil {
log.Error("create repository notice failed: ", err2)
}
} }
allComments = append(allComments, &base.Comment{ allComments = append(allComments, &base.Comment{
@ -556,7 +543,11 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
reactions, err := g.getIssueReactions(pr.Index) reactions, err := g.getIssueReactions(pr.Index)
if err != nil { if err != nil {
WarnAndNotice("Unable to load reactions during migrating pull #%d in %s. Error: %v", pr.Index, g, err) log.Warn("Unable to load reactions during migrating pull #%d to %s/%s. Error: %v", pr.Index, g.repoOwner, g.repoName, err)
if err2 := admin_model.CreateRepositoryNotice(
fmt.Sprintf("Unable to load reactions during migrating pull #%d to %s/%s. Error: %v", pr.Index, g.repoOwner, g.repoName, err)); err2 != nil {
log.Error("create repository notice failed: ", err2)
}
} }
var assignees []string var assignees []string
@ -613,8 +604,6 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
}, },
ForeignIndex: pr.Index, ForeignIndex: pr.Index,
}) })
// SECURITY: Ensure that the PR is safe
_ = CheckAndEnsureSafePR(allPRs[len(allPRs)-1], g.baseURL, g)
} }
isEnd := len(prs) < perPage isEnd := len(prs) < perPage

View file

@ -10,7 +10,6 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -33,7 +32,7 @@ import (
"code.gitea.io/gitea/modules/uri" "code.gitea.io/gitea/modules/uri"
"code.gitea.io/gitea/services/pull" "code.gitea.io/gitea/services/pull"
"github.com/google/uuid" gouuid "github.com/google/uuid"
) )
var _ base.Uploader = &GiteaLocalUploader{} var _ base.Uploader = &GiteaLocalUploader{}
@ -49,7 +48,7 @@ type GiteaLocalUploader struct {
milestones map[string]int64 milestones map[string]int64
issues map[int64]*issues_model.Issue issues map[int64]*issues_model.Issue
gitRepo *git.Repository gitRepo *git.Repository
prHeadCache map[string]string prHeadCache map[string]struct{}
sameApp bool sameApp bool
userMap map[int64]int64 // external user id mapping to user id userMap map[int64]int64 // external user id mapping to user id
prCache map[int64]*issues_model.PullRequest prCache map[int64]*issues_model.PullRequest
@ -66,7 +65,7 @@ func NewGiteaLocalUploader(ctx context.Context, doer *user_model.User, repoOwner
labels: make(map[string]*issues_model.Label), labels: make(map[string]*issues_model.Label),
milestones: make(map[string]int64), milestones: make(map[string]int64),
issues: make(map[int64]*issues_model.Issue), issues: make(map[int64]*issues_model.Issue),
prHeadCache: make(map[string]string), prHeadCache: make(map[string]struct{}),
userMap: make(map[int64]int64), userMap: make(map[int64]int64),
prCache: make(map[int64]*issues_model.PullRequest), prCache: make(map[int64]*issues_model.PullRequest),
} }
@ -126,7 +125,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
Mirror: repo.IsMirror, Mirror: repo.IsMirror,
LFS: opts.LFS, LFS: opts.LFS,
LFSEndpoint: opts.LFSEndpoint, LFSEndpoint: opts.LFSEndpoint,
CloneAddr: repo.CloneURL, // SECURITY: we will assume that this has already been checked CloneAddr: repo.CloneURL,
Private: repo.IsPrivate, Private: repo.IsPrivate,
Wiki: opts.Wiki, Wiki: opts.Wiki,
Releases: opts.Releases, // if didn't get releases, then sync them from tags Releases: opts.Releases, // if didn't get releases, then sync them from tags
@ -151,16 +150,14 @@ func (g *GiteaLocalUploader) Close() {
// CreateTopics creates topics // CreateTopics creates topics
func (g *GiteaLocalUploader) CreateTopics(topics ...string) error { func (g *GiteaLocalUploader) CreateTopics(topics ...string) error {
// Ignore topics too long for the db // ignore topics to long for the db
c := 0 c := 0
for _, topic := range topics { for i := range topics {
if len(topic) > 50 { if len(topics[i]) <= 50 {
continue topics[c] = topics[i]
}
topics[c] = topic
c++ c++
} }
}
topics = topics[:c] topics = topics[:c]
return repo_model.SaveTopics(g.repo.ID, topics...) return repo_model.SaveTopics(g.repo.ID, topics...)
} }
@ -220,17 +217,11 @@ func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) err
func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error { func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
lbs := make([]*issues_model.Label, 0, len(labels)) lbs := make([]*issues_model.Label, 0, len(labels))
for _, label := range labels { for _, label := range labels {
// We must validate color here:
if !issues_model.LabelColorPattern.MatchString("#" + label.Color) {
log.Warn("Invalid label color: #%s for label: %s in migration to %s/%s", label.Color, label.Name, g.repoOwner, g.repoName)
label.Color = "ffffff"
}
lbs = append(lbs, &issues_model.Label{ lbs = append(lbs, &issues_model.Label{
RepoID: g.repo.ID, RepoID: g.repo.ID,
Name: label.Name, Name: label.Name,
Description: label.Description, Description: label.Description,
Color: "#" + label.Color, Color: fmt.Sprintf("#%s", label.Color),
}) })
} }
@ -256,16 +247,6 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
} }
} }
// SECURITY: The TagName must be a valid git ref
if release.TagName != "" && !git.IsValidRefPattern(release.TagName) {
release.TagName = ""
}
// SECURITY: The TargetCommitish must be a valid git ref
if release.TargetCommitish != "" && !git.IsValidRefPattern(release.TargetCommitish) {
release.TargetCommitish = ""
}
rel := models.Release{ rel := models.Release{
RepoID: g.repo.ID, RepoID: g.repo.ID,
TagName: release.TagName, TagName: release.TagName,
@ -307,15 +288,14 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
} }
} }
attach := repo_model.Attachment{ attach := repo_model.Attachment{
UUID: uuid.New().String(), UUID: gouuid.New().String(),
Name: asset.Name, Name: asset.Name,
DownloadCount: int64(*asset.DownloadCount), DownloadCount: int64(*asset.DownloadCount),
Size: int64(*asset.Size), Size: int64(*asset.Size),
CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()), CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
} }
// SECURITY: We cannot check the DownloadURL and DownloadFunc are safe here // download attachment
// ... we must assume that they are safe and simply download the attachment
err := func() error { err := func() error {
// asset.DownloadURL maybe a local file // asset.DownloadURL maybe a local file
var rc io.ReadCloser var rc io.ReadCloser
@ -385,12 +365,6 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
} }
} }
// SECURITY: issue.Ref needs to be a valid reference
if !git.IsValidRefPattern(issue.Ref) {
log.Warn("Invalid issue.Ref[%s] in issue #%d in %s/%s", issue.Ref, issue.Number, g.repoOwner, g.repoName)
issue.Ref = ""
}
is := issues_model.Issue{ is := issues_model.Issue{
RepoID: g.repo.ID, RepoID: g.repo.ID,
Repo: g.repo, Repo: g.repo,
@ -522,151 +496,102 @@ func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error
} }
func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head string, err error) { func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head string, err error) {
// SECURITY: this pr must have been must have been ensured safe // download patch file
if !pr.EnsuredSafe {
log.Error("PR #%d in %s/%s has not been checked for safety.", pr.Number, g.repoOwner, g.repoName)
return "", fmt.Errorf("the PR[%d] was not checked for safety", pr.Number)
}
// Anonymous function to download the patch file (allows us to use defer)
err = func() error { err = func() error {
// if the patchURL is empty there is nothing to download
if pr.PatchURL == "" { if pr.PatchURL == "" {
return nil return nil
} }
// pr.PatchURL maybe a local file
// SECURITY: We will assume that the pr.PatchURL has been checked ret, err := uri.Open(pr.PatchURL)
// pr.PatchURL maybe a local file - but note EnsureSafe should be asserting that this safe
ret, err := uri.Open(pr.PatchURL) // TODO: This probably needs to use the downloader as there may be rate limiting issues here
if err != nil { if err != nil {
return err return err
} }
defer ret.Close() defer ret.Close()
pullDir := filepath.Join(g.repo.RepoPath(), "pulls") pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
if err = os.MkdirAll(pullDir, os.ModePerm); err != nil { if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
return err return err
} }
f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number))) f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer f.Close()
// TODO: Should there be limits on the size of this file?
_, err = io.Copy(f, ret) _, err = io.Copy(f, ret)
return err return err
}() }()
if err != nil { if err != nil {
return "", err return "", err
} }
head = "unknown repository" // set head information
if pr.IsForkPullRequest() && pr.State != "closed" { pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number))
// OK we want to fetch the current head as a branch from its CloneURL if err := os.MkdirAll(pullHead, os.ModePerm); err != nil {
return "", err
// 1. Is there a head clone URL available?
// 2. Is there a head ref available?
if pr.Head.CloneURL == "" || pr.Head.Ref == "" {
return head, nil
} }
p, err := os.Create(filepath.Join(pullHead, "head"))
// 3. We need to create a remote for this clone url
// ... maybe we already have a name for this remote
remote, ok := g.prHeadCache[pr.Head.CloneURL+":"]
if !ok {
// ... let's try ownername as a reasonable name
remote = pr.Head.OwnerName
if !git.IsValidRefPattern(remote) {
// ... let's try something less nice
remote = "head-pr-" + strconv.FormatInt(pr.Number, 10)
}
// ... now add the remote
err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
if err != nil { if err != nil {
log.Error("PR #%d in %s/%s AddRemote[%s] failed: %v", pr.Number, g.repoOwner, g.repoName, remote, err) return "", err
} else {
g.prHeadCache[pr.Head.CloneURL+":"] = remote
ok = true
} }
} _, err = p.WriteString(pr.Head.SHA)
if !ok { p.Close()
return head, nil
}
// 4. Check if we already have this ref?
localRef, ok := g.prHeadCache[pr.Head.CloneURL+":"+pr.Head.Ref]
if !ok {
// ... We would normally name this migrated branch as <OwnerName>/<HeadRef> but we need to ensure that is safe
localRef = git.SanitizeRefPattern(pr.Head.OwnerName + "/" + pr.Head.Ref)
// ... Now we must assert that this does not exist
if g.gitRepo.IsBranchExist(localRef) {
localRef = "head-pr-" + strconv.FormatInt(pr.Number, 10) + "/" + localRef
i := 0
for g.gitRepo.IsBranchExist(localRef) {
if i > 5 {
// ... We tried, we really tried but this is just a seriously unfriendly repo
return head, nil
}
// OK just try some uuids!
localRef = git.SanitizeRefPattern("head-pr-" + strconv.FormatInt(pr.Number, 10) + uuid.New().String())
i++
}
}
fetchArg := pr.Head.Ref + ":" + git.BranchPrefix + localRef
if strings.HasPrefix(fetchArg, "-") {
fetchArg = git.BranchPrefix + fetchArg
}
_, _, err = git.NewCommand(g.ctx, "fetch", "--no-tags", "--", remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()})
if err != nil {
log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
return head, nil
}
g.prHeadCache[pr.Head.CloneURL+":"+pr.Head.Ref] = localRef
head = localRef
}
// 5. Now if pr.Head.SHA == "" we should recover this to the head of this branch
if pr.Head.SHA == "" {
headSha, err := g.gitRepo.GetBranchCommitID(localRef)
if err != nil {
log.Error("unable to get head SHA of local head for PR #%d from %s in %s/%s. Error: %v", pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err)
return head, nil
}
pr.Head.SHA = headSha
}
_, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref", pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()})
if err != nil { if err != nil {
return "", err return "", err
} }
return head, nil head = "unknown repository"
} if pr.IsForkPullRequest() && pr.State != "closed" {
if pr.Head.OwnerName != "" {
if pr.Head.Ref != "" { remote := pr.Head.OwnerName
head = pr.Head.Ref _, ok := g.prHeadCache[remote]
} if !ok {
// git remote add
// Ensure the closed PR SHA still points to an existing ref err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
if pr.Head.SHA == "" { if err != nil {
// The SHA is empty log.Error("AddRemote failed: %s", err)
log.Warn("Empty reference, no pull head for PR #%d in %s/%s", pr.Number, g.repoOwner, g.repoName)
} else { } else {
g.prHeadCache[remote] = struct{}{}
ok = true
}
}
if ok {
_, _, err = git.NewCommand(g.ctx, "fetch", "--no-tags", "--", remote, pr.Head.Ref).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()})
if err != nil {
log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
} else {
headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref)
if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil {
return "", err
}
b, err := os.Create(headBranch)
if err != nil {
return "", err
}
_, err = b.WriteString(pr.Head.SHA)
b.Close()
if err != nil {
return "", err
}
head = pr.Head.OwnerName + "/" + pr.Head.Ref
}
}
}
} else {
head = pr.Head.Ref
// Ensure the closed PR SHA still points to an existing ref
_, _, err = git.NewCommand(g.ctx, "rev-list", "--quiet", "-1", pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) _, _, err = git.NewCommand(g.ctx, "rev-list", "--quiet", "-1", pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()})
if err != nil { if err != nil {
if pr.Head.SHA != "" {
// Git update-ref remove bad references with a relative path // Git update-ref remove bad references with a relative path
log.Warn("Deprecated local head %s for PR #%d in %s/%s, removing %s", pr.Head.SHA, pr.Number, g.repoOwner, g.repoName, pr.GetGitRefName()) log.Warn("Deprecated local head, removing : %v", pr.Head.SHA)
err = g.gitRepo.RemoveReference(pr.GetGitRefName())
} else { } else {
// set head information // The SHA is empty, remove the head file
_, _, err = git.NewCommand(g.ctx, "update-ref", "--no-deref", pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) log.Warn("Empty reference, removing : %v", pullHead)
err = os.Remove(filepath.Join(pullHead, "head"))
}
if err != nil { if err != nil {
log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) log.Error("Cannot remove local head ref, %v", err)
} }
} }
} }
@ -690,20 +615,6 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model
return nil, fmt.Errorf("updateGitForPullRequest: %w", err) return nil, fmt.Errorf("updateGitForPullRequest: %w", err)
} }
// Now we may need to fix the mergebase
if pr.Base.SHA == "" {
if pr.Base.Ref != "" && pr.Head.SHA != "" {
// A PR against a tag base does not make sense - therefore pr.Base.Ref must be a branch
// TODO: should we be checking for the refs/heads/ prefix on the pr.Base.Ref? (i.e. are these actually branches or refs)
pr.Base.SHA, _, err = g.gitRepo.GetMergeBase("", git.BranchPrefix+pr.Base.Ref, pr.Head.SHA)
if err != nil {
log.Error("Cannot determine the merge base for PR #%d in %s/%s. Error: %v", pr.Number, g.repoOwner, g.repoName, err)
}
} else {
log.Error("Cannot determine the merge base for PR #%d in %s/%s. Not enough information", pr.Number, g.repoOwner, g.repoName)
}
}
if pr.Created.IsZero() { if pr.Created.IsZero() {
if pr.Closed != nil { if pr.Closed != nil {
pr.Created = *pr.Closed pr.Created = *pr.Closed
@ -817,8 +728,6 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
return err return err
} }
cms = append(cms, &cm)
// get pr // get pr
pr, ok := g.prCache[issue.ID] pr, ok := g.prCache[issue.ID]
if !ok { if !ok {
@ -829,17 +738,6 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
} }
g.prCache[issue.ID] = pr g.prCache[issue.ID] = pr
} }
if pr.MergeBase == "" {
// No mergebase -> no basis for any patches
log.Warn("PR #%d in %s/%s: does not have a merge base, all review comments will be ignored", pr.Index, g.repoOwner, g.repoName)
continue
}
headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
log.Warn("PR #%d GetRefCommitID[%s] in %s/%s: %v, all review comments will be ignored", pr.Index, pr.GetGitRefName(), g.repoOwner, g.repoName, err)
continue
}
for _, comment := range review.Comments { for _, comment := range review.Comments {
line := comment.Line line := comment.Line
@ -848,9 +746,11 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
} else { } else {
_, _, line, _ = git.ParseDiffHunkString(comment.DiffHunk) _, _, line, _ = git.ParseDiffHunkString(comment.DiffHunk)
} }
headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName())
// SECURITY: The TreePath must be cleaned! if err != nil {
comment.TreePath = path.Clean("/" + comment.TreePath)[1:] log.Warn("GetRefCommitID[%s]: %v, the review comment will be ignored", pr.GetGitRefName(), err)
continue
}
var patch string var patch string
reader, writer := io.Pipe() reader, writer := io.Pipe()
@ -875,11 +775,6 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
comment.UpdatedAt = comment.CreatedAt comment.UpdatedAt = comment.CreatedAt
} }
if !git.IsValidSHAPattern(comment.CommitID) {
log.Warn("Invalid comment CommitID[%s] on comment[%d] in PR #%d of %s/%s replaced with %s", comment.CommitID, pr.Index, g.repoOwner, g.repoName, headCommitID)
comment.CommitID = headCommitID
}
c := issues_model.Comment{ c := issues_model.Comment{
Type: issues_model.CommentTypeCode, Type: issues_model.CommentTypeCode,
IssueID: issue.ID, IssueID: issue.ID,
@ -898,6 +793,8 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
cm.Comments = append(cm.Comments, &c) cm.Comments = append(cm.Comments, &c)
} }
cms = append(cms, &cm)
} }
return issues_model.InsertReviews(cms) return issues_model.InsertReviews(cms)

View file

@ -46,7 +46,7 @@ func TestGiteaUploadRepo(t *testing.T) {
uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName) uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName)
) )
err := migrateRepository(user, downloader, uploader, base.MigrateOptions{ err := migrateRepository(downloader, uploader, base.MigrateOptions{
CloneAddr: "https://github.com/go-xorm/builder", CloneAddr: "https://github.com/go-xorm/builder",
RepoName: repoName, RepoName: repoName,
AuthUsername: "", AuthUsername: "",
@ -391,7 +391,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
}, },
}, },
assertContent: func(t *testing.T, content string) { assertContent: func(t *testing.T, content string) {
assert.Contains(t, content, "AddRemote") assert.Contains(t, content, "AddRemote failed")
}, },
}, },
{ {
@ -440,7 +440,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
}, },
}, },
assertContent: func(t *testing.T, content string) { assertContent: func(t *testing.T, content string) {
assert.Contains(t, content, "Empty reference") assert.Contains(t, content, "Empty reference, removing")
assert.NotContains(t, content, "Cannot remove local head") assert.NotContains(t, content, "Cannot remove local head")
}, },
}, },
@ -468,6 +468,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
}, },
assertContent: func(t *testing.T, content string) { assertContent: func(t *testing.T, content string) {
assert.Contains(t, content, "Deprecated local head") assert.Contains(t, content, "Deprecated local head")
assert.Contains(t, content, "Cannot remove local head")
}, },
}, },
{ {
@ -504,8 +505,6 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
logger.SetLogger("buffer", "buffer", "{}") logger.SetLogger("buffer", "buffer", "{}")
defer logger.DelLogger("buffer") defer logger.DelLogger("buffer")
testCase.pr.EnsuredSafe = true
head, err := uploader.updateGitForPullRequest(&testCase.pr) head, err := uploader.updateGitForPullRequest(&testCase.pr)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, testCase.head, head) assert.EqualValues(t, testCase.head, head)

View file

@ -51,7 +51,7 @@ func (f *GithubDownloaderV3Factory) New(ctx context.Context, opts base.MigrateOp
oldOwner := fields[1] oldOwner := fields[1]
oldName := strings.TrimSuffix(fields[2], ".git") oldName := strings.TrimSuffix(fields[2], ".git")
log.Trace("Create github downloader BaseURL: %s %s/%s", baseURL, oldOwner, oldName) log.Trace("Create github downloader: %s/%s", oldOwner, oldName)
return NewGithubDownloaderV3(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil return NewGithubDownloaderV3(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
} }
@ -67,7 +67,6 @@ type GithubDownloaderV3 struct {
base.NullDownloader base.NullDownloader
ctx context.Context ctx context.Context
clients []*github.Client clients []*github.Client
baseURL string
repoOwner string repoOwner string
repoName string repoName string
userName string userName string
@ -82,7 +81,6 @@ type GithubDownloaderV3 struct {
func NewGithubDownloaderV3(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 { func NewGithubDownloaderV3(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 {
downloader := GithubDownloaderV3{ downloader := GithubDownloaderV3{
userName: userName, userName: userName,
baseURL: baseURL,
password: password, password: password,
ctx: ctx, ctx: ctx,
repoOwner: repoOwner, repoOwner: repoOwner,
@ -120,20 +118,6 @@ func NewGithubDownloaderV3(ctx context.Context, baseURL, userName, password, tok
return &downloader return &downloader
} }
// String implements Stringer
func (g *GithubDownloaderV3) String() string {
return fmt.Sprintf("migration from github server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
// ColorFormat provides a basic color format for a GithubDownloader
func (g *GithubDownloaderV3) ColorFormat(s fmt.State) {
if g == nil {
log.ColorFprintf(s, "<nil: GithubDownloaderV3>")
return
}
log.ColorFprintf(s, "migration from github server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) { func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) {
githubClient := github.NewClient(client) githubClient := github.NewClient(client)
if baseURL != "https://github.com" { if baseURL != "https://github.com" {
@ -338,30 +322,15 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease)
Updated: asset.UpdatedAt.Time, Updated: asset.UpdatedAt.Time,
DownloadFunc: func() (io.ReadCloser, error) { DownloadFunc: func() (io.ReadCloser, error) {
g.waitAndPickClient() g.waitAndPickClient()
readCloser, redirectURL, err := g.getClient().Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, assetID, nil) asset, redirectURL, err := g.getClient().Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, assetID, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := g.RefreshRate(); err != nil { if err := g.RefreshRate(); err != nil {
log.Error("g.getClient().RateLimits: %s", err) log.Error("g.getClient().RateLimits: %s", err)
} }
if asset == nil {
if readCloser != nil { if redirectURL != "" {
return readCloser, nil
}
if redirectURL == "" {
return nil, fmt.Errorf("no release asset found for %d", assetID)
}
// Prevent open redirect
if !hasBaseURL(redirectURL, g.baseURL) &&
!hasBaseURL(redirectURL, "https://objects.githubusercontent.com/") {
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.GetID(), g, redirectURL)
return io.NopCloser(strings.NewReader(redirectURL)), nil
}
g.waitAndPickClient() g.waitAndPickClient()
req, err := http.NewRequestWithContext(g.ctx, "GET", redirectURL, nil) req, err := http.NewRequestWithContext(g.ctx, "GET", redirectURL, nil)
if err != nil { if err != nil {
@ -370,12 +339,16 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease)
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
err1 := g.RefreshRate() err1 := g.RefreshRate()
if err1 != nil { if err1 != nil {
log.Error("g.RefreshRate(): %s", err1) log.Error("g.getClient().RateLimits: %s", err1)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
return resp.Body, nil return resp.Body, nil
}
return nil, fmt.Errorf("No release asset found for %d", assetID)
}
return asset, nil
}, },
}) })
} }
@ -724,7 +697,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
SHA: pr.GetHead().GetSHA(), SHA: pr.GetHead().GetSHA(),
OwnerName: pr.GetHead().GetUser().GetLogin(), OwnerName: pr.GetHead().GetUser().GetLogin(),
RepoName: pr.GetHead().GetRepo().GetName(), RepoName: pr.GetHead().GetRepo().GetName(),
CloneURL: pr.GetHead().GetRepo().GetCloneURL(), // see below for SECURITY related issues here CloneURL: pr.GetHead().GetRepo().GetCloneURL(),
}, },
Base: base.PullRequestBranch{ Base: base.PullRequestBranch{
Ref: pr.GetBase().GetRef(), Ref: pr.GetBase().GetRef(),
@ -732,13 +705,10 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
RepoName: pr.GetBase().GetRepo().GetName(), RepoName: pr.GetBase().GetRepo().GetName(),
OwnerName: pr.GetBase().GetUser().GetLogin(), OwnerName: pr.GetBase().GetUser().GetLogin(),
}, },
PatchURL: pr.GetPatchURL(), // see below for SECURITY related issues here PatchURL: pr.GetPatchURL(),
Reactions: reactions, Reactions: reactions,
ForeignIndex: int64(*pr.Number), ForeignIndex: int64(*pr.Number),
}) })
// SECURITY: Ensure that the PR is safe
_ = CheckAndEnsureSafePR(allPRs[len(allPRs)-1], g.baseURL, g)
} }
return allPRs, len(prs) < perPage, nil return allPRs, len(prs) < perPage, nil

View file

@ -63,7 +63,6 @@ type GitlabDownloader struct {
base.NullDownloader base.NullDownloader
ctx context.Context ctx context.Context
client *gitlab.Client client *gitlab.Client
baseURL string
repoID int repoID int
repoName string repoName string
issueCount int64 issueCount int64
@ -125,27 +124,12 @@ func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, username, passw
return &GitlabDownloader{ return &GitlabDownloader{
ctx: ctx, ctx: ctx,
client: gitlabClient, client: gitlabClient,
baseURL: baseURL,
repoID: gr.ID, repoID: gr.ID,
repoName: gr.Name, repoName: gr.Name,
maxPerPage: 100, maxPerPage: 100,
}, nil }, nil
} }
// String implements Stringer
func (g *GitlabDownloader) String() string {
return fmt.Sprintf("migration from gitlab server %s [%d]/%s", g.baseURL, g.repoID, g.repoName)
}
// ColorFormat provides a basic color format for a GitlabDownloader
func (g *GitlabDownloader) ColorFormat(s fmt.State) {
if g == nil {
log.ColorFprintf(s, "<nil: GitlabDownloader>")
return
}
log.ColorFprintf(s, "migration from gitlab server %s [%d]/%s", g.baseURL, g.repoID, g.repoName)
}
// SetContext set context // SetContext set context
func (g *GitlabDownloader) SetContext(ctx context.Context) { func (g *GitlabDownloader) SetContext(ctx context.Context) {
g.ctx = ctx g.ctx = ctx
@ -323,11 +307,6 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
return nil, err return nil, err
} }
if !hasBaseURL(link.URL, g.baseURL) {
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, link.URL)
return io.NopCloser(strings.NewReader(link.URL)), nil
}
req, err := http.NewRequest("GET", link.URL, nil) req, err := http.NewRequest("GET", link.URL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -631,9 +610,6 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
ForeignIndex: int64(pr.IID), ForeignIndex: int64(pr.IID),
Context: gitlabIssueContext{IsMergeRequest: true}, Context: gitlabIssueContext{IsMergeRequest: true},
}) })
// SECURITY: Ensure that the PR is safe
_ = CheckAndEnsureSafePR(allPRs[len(allPRs)-1], g.baseURL, g)
} }
return allPRs, len(prs) < perPage, nil return allPRs, len(prs) < perPage, nil

View file

@ -73,20 +73,6 @@ type GogsDownloader struct {
transport http.RoundTripper transport http.RoundTripper
} }
// String implements Stringer
func (g *GogsDownloader) String() string {
return fmt.Sprintf("migration from gogs server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
// ColorFormat provides a basic color format for a GogsDownloader
func (g *GogsDownloader) ColorFormat(s fmt.State) {
if g == nil {
log.ColorFprintf(s, "<nil: GogsDownloader>")
return
}
log.ColorFprintf(s, "migration from gogs server %s %s/%s", g.baseURL, g.repoOwner, g.repoName)
}
// SetContext set context // SetContext set context
func (g *GogsDownloader) SetContext(ctx context.Context) { func (g *GogsDownloader) SetContext(ctx context.Context) {
g.ctx = ctx g.ctx = ctx

View file

@ -125,7 +125,7 @@ func MigrateRepository(ctx context.Context, doer *user_model.User, ownerName str
uploader := NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName) uploader := NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName)
uploader.gitServiceType = opts.GitServiceType uploader.gitServiceType = opts.GitServiceType
if err := migrateRepository(doer, downloader, uploader, opts, messenger); err != nil { if err := migrateRepository(downloader, uploader, opts, messenger); err != nil {
if err1 := uploader.Rollback(); err1 != nil { if err1 := uploader.Rollback(); err1 != nil {
log.Error("rollback failed: %v", err1) log.Error("rollback failed: %v", err1)
} }
@ -174,7 +174,7 @@ func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptio
// migrateRepository will download information and then upload it to Uploader, this is a simple // migrateRepository will download information and then upload it to Uploader, this is a simple
// process for small repository. For a big repository, save all the data to disk // process for small repository. For a big repository, save all the data to disk
// before upload is better // before upload is better
func migrateRepository(doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error {
if messenger == nil { if messenger == nil {
messenger = base.NilMessenger messenger = base.NilMessenger
} }
@ -195,27 +195,6 @@ func migrateRepository(doer *user_model.User, downloader base.Downloader, upload
return err return err
} }
// SECURITY: If the downloader is not a RepositoryRestorer then we need to recheck the CloneURL
if _, ok := downloader.(*RepositoryRestorer); !ok {
// Now the clone URL can be rewritten by the downloader so we must recheck
if err := IsMigrateURLAllowed(repo.CloneURL, doer); err != nil {
return err
}
// SECURITY: Ensure that we haven't been redirected from an external to a local filesystem
// Now we know all of these must parse
cloneAddrURL, _ := url.Parse(opts.CloneAddr)
cloneURL, _ := url.Parse(repo.CloneURL)
if cloneURL.Scheme == "file" || cloneURL.Scheme == "" {
if cloneAddrURL.Scheme != "file" && cloneAddrURL.Scheme != "" {
return fmt.Errorf("repo info has changed from external to local filesystem")
}
}
// We don't actually need to check the OriginalURL as it isn't used anywhere
}
log.Trace("migrating git data from %s", repo.CloneURL) log.Trace("migrating git data from %s", repo.CloneURL)
messenger("repo.migrate.migrating_git") messenger("repo.migrate.migrating_git")
if err = uploader.CreateRepo(repo, opts); err != nil { if err = uploader.CreateRepo(repo, opts); err != nil {

View file

@ -110,20 +110,6 @@ func NewOneDevDownloader(ctx context.Context, baseURL *url.URL, username, passwo
return downloader return downloader
} }
// String implements Stringer
func (d *OneDevDownloader) String() string {
return fmt.Sprintf("migration from oneDev server %s [%d]/%s", d.baseURL, d.repoID, d.repoName)
}
// ColorFormat provides a basic color format for a OneDevDownloader
func (d *OneDevDownloader) ColorFormat(s fmt.State) {
if d == nil {
log.ColorFprintf(s, "<nil: OneDevDownloader>")
return
}
log.ColorFprintf(s, "migration from oneDev server %s [%d]/%s", d.baseURL, d.repoID, d.repoName)
}
func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, result interface{}) error { func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, result interface{}) error {
u, err := d.baseURL.Parse(endpoint) u, err := d.baseURL.Parse(endpoint)
if err != nil { if err != nil {
@ -556,9 +542,6 @@ func (d *OneDevDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
ForeignIndex: pr.ID, ForeignIndex: pr.ID,
Context: onedevIssueContext{IsPullRequest: true}, Context: onedevIssueContext{IsPullRequest: true},
}) })
// SECURITY: Ensure that the PR is safe
_ = CheckAndEnsureSafePR(pullRequests[len(pullRequests)-1], d.baseURL.String(), d)
} }
return pullRequests, len(pullRequests) == 0, nil return pullRequests, len(pullRequests) == 0, nil

View file

@ -243,7 +243,6 @@ func (r *RepositoryRestorer) GetPullRequests(page, perPage int) ([]*base.PullReq
} }
for _, pr := range pulls { for _, pr := range pulls {
pr.PatchURL = "file://" + filepath.Join(r.baseDir, pr.PatchURL) pr.PatchURL = "file://" + filepath.Join(r.baseDir, pr.PatchURL)
CheckAndEnsureSafePR(pr, "", r)
} }
return pulls, true, nil return pulls, true, nil
} }

View file

@ -37,9 +37,6 @@ func createTag(gitRepo *git.Repository, rel *models.Release, msg string) (bool,
if err != nil { if err != nil {
return false, fmt.Errorf("GetProtectedTags: %v", err) return false, fmt.Errorf("GetProtectedTags: %v", err)
} }
// Trim '--' prefix to prevent command line argument vulnerability.
rel.TagName = strings.TrimPrefix(rel.TagName, "--")
isAllowed, err := git_model.IsUserAllowedToControlTag(protectedTags, rel.TagName, rel.PublisherID) isAllowed, err := git_model.IsUserAllowedToControlTag(protectedTags, rel.TagName, rel.PublisherID)
if err != nil { if err != nil {
return false, err return false, err
@ -55,6 +52,8 @@ func createTag(gitRepo *git.Repository, rel *models.Release, msg string) (bool,
return false, fmt.Errorf("createTag::GetCommit[%v]: %v", rel.Target, err) return false, fmt.Errorf("createTag::GetCommit[%v]: %v", rel.Target, err)
} }
// Trim '--' prefix to prevent command line argument vulnerability.
rel.TagName = strings.TrimPrefix(rel.TagName, "--")
if len(msg) > 0 { if len(msg) > 0 {
if err = gitRepo.CreateAnnotatedTag(rel.TagName, msg, commit.ID.String()); err != nil { if err = gitRepo.CreateAnnotatedTag(rel.TagName, msg, commit.ID.String()); err != nil {
if strings.Contains(err.Error(), "is not a valid tag name") { if strings.Contains(err.Error(), "is not a valid tag name") {
@ -309,7 +308,7 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del
} }
} }
if stdout, _, err := git.NewCommand(ctx, "tag", "-d", "--", rel.TagName). if stdout, _, err := git.NewCommand(ctx, "tag", "-d", rel.TagName).
SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)). SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)).
RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") {
log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err) log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err)

View file

@ -57,21 +57,6 @@ func (ErrUnknownArchiveFormat) Is(err error) bool {
return ok return ok
} }
// RepoRefNotFoundError is returned when a requested reference (commit, tag) was not found.
type RepoRefNotFoundError struct {
RefName string
}
// Error implements error.
func (e RepoRefNotFoundError) Error() string {
return fmt.Sprintf("unrecognized repository reference: %s", e.RefName)
}
func (e RepoRefNotFoundError) Is(err error) bool {
_, ok := err.(RepoRefNotFoundError)
return ok
}
// NewRequest creates an archival request, based on the URI. The // NewRequest creates an archival request, based on the URI. The
// resulting ArchiveRequest is suitable for being passed to ArchiveRepository() // resulting ArchiveRequest is suitable for being passed to ArchiveRepository()
// if it's determined that the request still needs to be satisfied. // if it's determined that the request still needs to be satisfied.
@ -118,7 +103,7 @@ func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest
} }
} }
} else { } else {
return nil, RepoRefNotFoundError{RefName: r.refName} return nil, fmt.Errorf("Unknow ref %s type", r.refName)
} }
return r, nil return r, nil
@ -130,49 +115,6 @@ func (aReq *ArchiveRequest) GetArchiveName() string {
return strings.ReplaceAll(aReq.refName, "/", "-") + "." + aReq.Type.String() return strings.ReplaceAll(aReq.refName, "/", "-") + "." + aReq.Type.String()
} }
// Await awaits the completion of an ArchiveRequest. If the archive has
// already been prepared the method returns immediately. Otherwise an archiver
// process will be started and its completion awaited. On success the returned
// RepoArchiver may be used to download the archive. Note that even if the
// context is cancelled/times out a started archiver will still continue to run
// in the background.
func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver, error) {
archiver, err := repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID)
if err != nil {
return nil, fmt.Errorf("models.GetRepoArchiver: %v", err)
}
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
// Archive already generated, we're done.
return archiver, nil
}
if err := StartArchive(aReq); err != nil {
return nil, fmt.Errorf("archiver.StartArchive: %v", err)
}
poll := time.NewTicker(time.Second * 1)
defer poll.Stop()
for {
select {
case <-graceful.GetManager().HammerContext().Done():
// System stopped.
return nil, graceful.GetManager().HammerContext().Err()
case <-ctx.Done():
return nil, ctx.Err()
case <-poll.C:
archiver, err = repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID)
if err != nil {
return nil, fmt.Errorf("repo_model.GetRepoArchiver: %v", err)
}
if archiver != nil && archiver.Status == repo_model.ArchiverReady {
return archiver, nil
}
}
}
}
func doArchive(r *ArchiveRequest) (*repo_model.RepoArchiver, error) { func doArchive(r *ArchiveRequest) (*repo_model.RepoArchiver, error) {
txCtx, committer, err := db.TxContext() txCtx, committer, err := db.TxContext()
if err != nil { if err != nil {
@ -205,7 +147,11 @@ func doArchive(r *ArchiveRequest) (*repo_model.RepoArchiver, error) {
} }
} }
rPath := archiver.RelativePath() rPath, err := archiver.RelativePath()
if err != nil {
return nil, err
}
_, err = storage.RepoArchives.Stat(rPath) _, err = storage.RepoArchives.Stat(rPath)
if err == nil { if err == nil {
if archiver.Status == repo_model.ArchiverGenerating { if archiver.Status == repo_model.ArchiverGenerating {
@ -338,10 +284,13 @@ func StartArchive(request *ArchiveRequest) error {
} }
func deleteOldRepoArchiver(ctx context.Context, archiver *repo_model.RepoArchiver) error { func deleteOldRepoArchiver(ctx context.Context, archiver *repo_model.RepoArchiver) error {
p, err := archiver.RelativePath()
if err != nil {
return err
}
if err := repo_model.DeleteRepoArchiver(ctx, archiver); err != nil { if err := repo_model.DeleteRepoArchiver(ctx, archiver); err != nil {
return err return err
} }
p := archiver.RelativePath()
if err := storage.RepoArchives.Delete(p); err != nil { if err := storage.RepoArchives.Delete(p); err != nil {
log.Error("delete repo archive file failed: %v", err) log.Error("delete repo archive file failed: %v", err)
} }

View file

@ -341,12 +341,12 @@
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_required_claim_name">{{.i18n.Tr "admin.auths.oauth2_required_claim_name"}}</label> <label for="oauth2_required_claim_name">{{.i18n.Tr "admin.auths.oauth2_required_claim_name"}}</label>
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" value="{{$cfg.RequiredClaimName}}"> <input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{$cfg.RequiredClaimName}}">
<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p> <p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_required_claim_value">{{.i18n.Tr "admin.auths.oauth2_required_claim_value"}}</label> <label for="oauth2_required_claim_value">{{.i18n.Tr "admin.auths.oauth2_required_claim_value"}}</label>
<input id="oauth2_required_claim_value" name="oauth2_required_claim_value" value="{{$cfg.RequiredClaimValue}}"> <input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{$cfg.RequiredClaimValue}}">
<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p> <p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">

View file

@ -22,7 +22,7 @@
<script src='https://hcaptcha.com/1/api.js' async></script> <script src='https://hcaptcha.com/1/api.js' async></script>
{{end}} {{end}}
{{end}} {{end}}
<script src="{{AssetUrlPrefix}}/js/index.js?v={{AssetVersion}}" onerror="alert('Failed to load asset files from ' + this.src + ', please make sure the asset files can be accessed and the ROOT_URL setting in app.ini is correct.')"></script> <script src="{{AssetUrlPrefix}}/js/index.js?v={{MD5 AppVer}}" onerror="alert('Failed to load asset files from ' + this.src + ', please make sure the asset files can be accessed and the ROOT_URL setting in app.ini is correct.')"></script>
{{template "custom/footer" .}} {{template "custom/footer" .}}
</body> </body>
</html> </html>

View file

@ -21,7 +21,7 @@
{{end}} {{end}}
<link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml"> <link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml">
<link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png"> <link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/index.css?v={{AssetVersion}}"> <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/index.css?v={{MD5 AppVer}}">
{{template "base/head_script" .}} {{template "base/head_script" .}}
<noscript> <noscript>
<style> <style>
@ -67,10 +67,10 @@
<meta property="og:site_name" content="{{AppName}}"> <meta property="og:site_name" content="{{AppName}}">
{{if .IsSigned }} {{if .IsSigned }}
{{ if ne .SignedUser.Theme "gitea" }} {{ if ne .SignedUser.Theme "gitea" }}
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{.SignedUser.Theme | PathEscape}}.css?v={{AssetVersion}}"> <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{.SignedUser.Theme | PathEscape}}.css?v={{MD5 AppVer}}">
{{end}} {{end}}
{{else if ne DefaultTheme "gitea"}} {{else if ne DefaultTheme "gitea"}}
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{DefaultTheme | PathEscape}}.css?v={{AssetVersion}}"> <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{DefaultTheme | PathEscape}}.css?v={{MD5 AppVer}}">
{{end}} {{end}}
{{template "custom/header" .}} {{template "custom/header" .}}
</head> </head>

View file

@ -10,7 +10,6 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
appVer: '{{AppVer}}', appVer: '{{AppVer}}',
appUrl: '{{AppUrl}}', appUrl: '{{AppUrl}}',
appSubUrl: '{{AppSubUrl}}', appSubUrl: '{{AppSubUrl}}',
assetVersionEncoded: encodeURIComponent('{{AssetVersion}}'), // will be used in URL construction directly
assetUrlPrefix: '{{AssetUrlPrefix}}', assetUrlPrefix: '{{AssetUrlPrefix}}',
runModeIsProd: {{.RunModeIsProd}}, runModeIsProd: {{.RunModeIsProd}},
customEmojis: {{CustomEmojis}}, customEmojis: {{CustomEmojis}},

View file

@ -32,7 +32,9 @@
</div> </div>
<div class="ui attached segment members"> <div class="ui attached segment members">
{{range .Members}} {{range .Members}}
{{template "shared/user/avatarlink" .}} <a href="{{.HomeLink}}" title="{{.Name}}">
{{avatar .}}
</a>
{{end}} {{end}}
</div> </div>
<div class="ui bottom attached header"> <div class="ui bottom attached header">

View file

@ -1,6 +1,5 @@
{{$release := .release}} {{$release := .release}}
{{$defaultBranch := $.root.BranchName}}{{if and .root.IsViewTag (not .noTag)}}{{$defaultBranch = .root.TagName}}{{end}}{{if eq $defaultBranch ""}}{{$defaultBranch = $.root.Repository.DefaultBranch}}{{end}} {{$defaultBranch := $.root.BranchName}}{{if and .root.IsViewTag (not .noTag)}}{{$defaultBranch = .root.TagName}}{{end}}{{if eq $defaultBranch ""}}{{$defaultBranch = $.root.Repository.DefaultBranch}}{{end}}
{{$type := ""}}{{if and .root.IsViewTag (not .noTag)}}{{$type = "tag"}}{{else if .root.IsViewBranch}}{{$type = "branch"}}{{else}}{{$type = "tree"}}{{end}}
{{$showBranchesInDropdown := not .root.HideBranchesInDropdown}} {{$showBranchesInDropdown := not .root.HideBranchesInDropdown}}
<div class="fitted item choose reference{{if not $release}} mr-1{{end}}"> <div class="fitted item choose reference{{if not $release}} mr-1{{end}}">
<div class="ui floating filter dropdown custom" <div class="ui floating filter dropdown custom"
@ -8,20 +7,20 @@
data-can-create-branch="{{if .canCreateBranch}}{{.canCreateBranch}}{{else}}{{.root.CanCreateBranch}}{{end}}" data-can-create-branch="{{if .canCreateBranch}}{{.canCreateBranch}}{{else}}{{.root.CanCreateBranch}}{{end}}"
data-no-results="{{.root.i18n.Tr "repo.pulls.no_results"}}" data-no-results="{{.root.i18n.Tr "repo.pulls.no_results"}}"
data-set-action="{{.setAction}}" data-submit-form="{{.submitForm}}" data-set-action="{{.setAction}}" data-submit-form="{{.submitForm}}"
data-view-type="{{$type}}" data-view-type="{{if and .root.IsViewTag (not .noTag)}}tag{{else if .root.IsViewBranch}}branch{{else}}tree{{end}}"
data-ref-name="{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}" data-ref-name="{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}"
data-branch-url-prefix="{{if .branchURLPrefix}}{{.branchURLPrefix}}{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{end}}" data-branch-url-prefix="{{if .branchURLPrefix}}{{.branchURLPrefix}}{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{end}}"
data-branch-url-suffix="{{if .branchURLSuffix}}{{.branchURLSuffix}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}" data-branch-url-suffix="{{if .branchURLSuffix}}{{.branchURLSuffix}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}"
data-tag-url-prefix="{{if .tagURLPrefix}}{{.tagURLPrefix}}{{else if $release}}{{$.root.RepoLink}}/compare/{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{end}}" data-tag-url-prefix="{{if .tagURLPrefix}}{{.tagURLPrefix}}{{else if $release}}{{$.root.RepoLink}}/compare/{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{end}}"
data-tag-url-suffix="{{if .tagURLSuffix}}{{.tagURLSuffix}}{{else if $release}}...{{if $release.IsDraft}}{{PathEscapeSegments $release.Target}}{{else}}{{if $release.TagName}}{{PathEscapeSegments $release.TagName}}{{else}}{{PathEscapeSegments $release.Sha1}}{{end}}{{end}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}"> data-tag-url-suffix="{{if .tagURLSuffix}}{{.tagURLSuffix}}{{else if $release}}...{{if $release.IsDraft}}{{PathEscapeSegments $release.Target}}{{else}}{{if $release.TagName}}{{PathEscapeSegments $release.TagName}}{{else}}{{PathEscapeSegments $release.Sha1}}{{end}}{{end}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}">
<div class="branch-dropdown-button ellipsis ui basic small compact button" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible"> <div class="ui basic small compact button" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible">
<span class="text"> <span class="text">
{{if $release}} {{if $release}}
{{.root.i18n.Tr "repo.release.compare"}} {{.root.i18n.Tr "repo.release.compare"}}
{{else}} {{else}}
<span :class="{visible: isViewTag}" v-if="isViewTag" {{if not (eq $type "tag")}}v-cloak{{end}}>{{svg "octicon-tag"}} {{.root.i18n.Tr "repo.tag"}}:</span> <span :class="{visible: isViewTag}" v-if="isViewTag" v-cloak>{{svg "octicon-tag"}} {{.root.i18n.Tr "repo.tag"}}:</span>
<span :class="{visible: isViewBranch}" v-if="isViewBranch" {{if not (eq $type "branch")}}v-cloak{{end}}>{{svg "octicon-git-branch"}} {{.root.i18n.Tr "repo.branch"}}:</span> <span :class="{visible: isViewBranch}" v-if="isViewBranch" v-cloak>{{svg "octicon-git-branch"}} {{.root.i18n.Tr "repo.branch"}}:</span>
<span :class="{visible: isViewTree}" v-if="isViewTree" {{if not (eq $type "tree")}}v-cloak{{end}}>{{svg "octicon-git-branch"}} {{.root.i18n.Tr "repo.tree"}}:</span> <span :class="{visible: isViewTree}" v-if="isViewTree" v-cloak>{{svg "octicon-git-branch"}} {{.root.i18n.Tr "repo.tree"}}:</span>
<strong ref="dropdownRefName">{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong> <strong ref="dropdownRefName">{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong>
{{end}} {{end}}
</span> </span>

View file

@ -1,15 +1,15 @@
<!-- there is always at least one button (by context/repo.go) --> <!-- there is always at least one button (by context/repo.go) -->
{{if $.CloneButtonShowHTTPS}} {{if $.CloneButtonShowHTTPS}}
<button class="ui basic small compact clone button no-transition" id="repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}"> <button class="ui basic clone button no-transition" id="repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">
{{if UseHTTPS}}HTTPS{{else}}HTTP{{end}} {{if UseHTTPS}}HTTPS{{else}}HTTP{{end}}
</button> </button>
{{end}} {{end}}
{{if $.CloneButtonShowSSH}} {{if $.CloneButtonShowSSH}}
<button class="ui basic small compact clone button no-transition" id="repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}"> <button class="ui basic clone button no-transition" id="repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">
SSH SSH
</button> </button>
{{end}} {{end}}
<input id="repo-clone-url" size="20" class="js-clone-url br-0" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly> <input id="repo-clone-url" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" size="1" readonly>
<button class="ui basic small compact icon button tooltip" id="clipboard-btn" data-content="{{.i18n.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{.i18n.Tr "copy_url"}}"> <button class="ui basic icon button tooltip" id="clipboard-btn" data-content="{{.i18n.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{.i18n.Tr "copy_url"}}">
{{svg "octicon-copy" 14}} {{svg "octicon-paste"}}
</button> </button>

View file

@ -222,7 +222,7 @@
{{.Verification.SigningSSHKey.Fingerprint}} {{.Verification.SigningSSHKey.Fingerprint}}
{{else}} {{else}}
<span class="ui text mr-3">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <span class="ui text mr-3">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}} {{.Verification.SigningKey.KeyID}}
{{end}} {{end}}
{{else}} {{else}}
{{svg "octicon-shield-lock" 16 "mr-3"}} {{svg "octicon-shield-lock" 16 "mr-3"}}
@ -231,7 +231,7 @@
{{.Verification.SigningSSHKey.Fingerprint}} {{.Verification.SigningSSHKey.Fingerprint}}
{{else}} {{else}}
<span class="ui text mr-3 tooltip" data-content="{{.i18n.Tr "gpg.default_key"}}">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <span class="ui text mr-3 tooltip" data-content="{{.i18n.Tr "gpg.default_key"}}">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}} {{.Verification.SigningKey.KeyID}}
{{end}} {{end}}
{{end}} {{end}}
{{else if .Verification.Warning}} {{else if .Verification.Warning}}
@ -241,14 +241,14 @@
{{.Verification.SigningSSHKey.Fingerprint}} {{.Verification.SigningSSHKey.Fingerprint}}
{{else}} {{else}}
<span class="ui text mr-3">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <span class="ui text mr-3">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}} {{.Verification.SigningKey.KeyID}}
{{end}} {{end}}
{{else}} {{else}}
{{if .Verification.SigningKey}} {{if .Verification.SigningKey}}
{{if ne .Verification.SigningKey.KeyID ""}} {{if ne .Verification.SigningKey.KeyID ""}}
{{svg "octicon-shield" 16 "mr-3"}} {{svg "octicon-shield" 16 "mr-3"}}
<span class="ui text mr-3">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> <span class="ui text mr-3">{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}} {{.Verification.SigningKey.KeyID}}
{{end}} {{end}}
{{end}} {{end}}
{{if .Verification.SigningSSHKey}} {{if .Verification.SigningSSHKey}}

View file

@ -3,9 +3,9 @@
{{template "repo/header" .}} {{template "repo/header" .}}
<div class="ui container"> <div class="ui container">
{{template "repo/sub_menu" .}} {{template "repo/sub_menu" .}}
<div class="repo-button-row df ac sb fw mb-4 mt-3"> <div class="ui secondary stackable menu mobile--margin-between-items">
<div class="df ac">
{{template "repo/branch_dropdown" dict "root" .}} {{template "repo/branch_dropdown" dict "root" .}}
<div class="fitted item">
<a href="{{.RepoLink}}/graph" class="ui basic small compact button"> <a href="{{.RepoLink}}/graph" class="ui basic small compact button">
<span class="text"> <span class="text">
{{svg "octicon-git-branch"}} {{svg "octicon-git-branch"}}

View file

@ -13,14 +13,14 @@
<h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4> <h4>{{.i18n.Tr "repo.diff.data_not_available"}}</h4>
{{else}} {{else}}
<div> <div>
<div class="diff-detail-box diff-box sticky df sb ac fw"> <div class="diff-detail-box diff-box sticky df sb ac">
<div class="diff-detail-stats df ac"> <div class="diff-detail-stats df ac">
{{svg "octicon-diff" 16 "mr-2"}}{{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}} {{svg "octicon-diff" 16 "mr-2"}}{{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2html}}
</div> </div>
<div class="diff-detail-actions df ac"> <div class="diff-detail-actions df ac">
{{if and .PageIsPullFiles $.SignedUserID (not .IsArchived)}} {{if and .PageIsPullFiles $.SignedUserID (not .IsArchived)}}
<progress id="viewed-files-summary" class="mr-2" value="{{.Diff.NumViewedFiles}}" max="{{.Diff.NumFiles}}"></progress> <progress id="viewed-files-summary" class="mr-2" value="{{.Diff.NumViewedFiles}}" max="{{.Diff.NumFiles}}"></progress>
<label for="viewed-files-summary" id="viewed-files-summary-label" class="mr-3" data-text-changed-template="{{.i18n.Tr "repo.pulls.viewed_files_label"}}"> <label for="viewed-files-summary" id="viewed-files-summary-label" class="mr-2" data-text-changed-template="{{.i18n.Tr "repo.pulls.viewed_files_label"}}">
{{.i18n.Tr "repo.pulls.viewed_files_label" .Diff.NumViewedFiles .Diff.NumFiles}} {{.i18n.Tr "repo.pulls.viewed_files_label" .Diff.NumViewedFiles .Diff.NumFiles}}
</label> </label>
{{end}} {{end}}

View file

@ -5,7 +5,9 @@
{{if .OriginalAuthor }} {{if .OriginalAuthor }}
<span class="avatar"><img src="{{AppSubUrl}}/assets/img/avatar_default.png"></span> <span class="avatar"><img src="{{AppSubUrl}}/assets/img/avatar_default.png"></span>
{{else}} {{else}}
{{template "shared/user/avatarlink" .Poster}} <a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
{{avatar .Poster}}
</a>
{{end}} {{end}}
<div class="content comment-container"> <div class="content comment-container">
<div class="ui top attached header comment-header df ac sb"> <div class="ui top attached header comment-header df ac sb">
@ -25,7 +27,9 @@
</span> </span>
{{else}} {{else}}
<span class="text grey"> <span class="text grey">
{{template "shared/user/namelink" .Poster}} <a {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
{{.Poster.GetDisplayName}}
</a>
{{$.root.i18n.Tr "repo.issues.commented_at" (.HashTag|Escape) $createdStr | Safe}} {{$.root.i18n.Tr "repo.issues.commented_at" (.HashTag|Escape) $createdStr | Safe}}
</span> </span>
{{end}} {{end}}

View file

@ -1,5 +1,5 @@
<div class="ui top right pointing dropdown custom" id="review-box"> <div class="ui top right pointing dropdown custom" id="review-box">
<div class="ui tiny green button btn-review ml-2 mr-0"> <div class="ui tiny green button btn-review">
{{.i18n.Tr "repo.diff.review"}} {{.i18n.Tr "repo.diff.review"}}
<span class="ui small label review-comments-counter" data-pending-comment-number="{{.PendingCodeCommentNumber}}">{{.PendingCodeCommentNumber}}</span> <span class="ui small label review-comments-counter" data-pending-comment-number="{{.PendingCodeCommentNumber}}">{{.PendingCodeCommentNumber}}</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}

Some files were not shown because too many files have changed in this diff Show more