mirror of https://github.com/go-gitea/gitea
Compare commits
129 Commits
6a70233228
...
6fb96e3112
Author | SHA1 | Date |
---|---|---|
yp05327 | 6fb96e3112 | |
Lunny Xiao | 67ce9ca110 | |
wxiaoguang | 67c1a07285 | |
Lunny Xiao | ebf0c96940 | |
Lunny Xiao | 6ad77125ca | |
wxiaoguang | 9c08637eae | |
wxiaoguang | 7c613f100e | |
6543 | 8e8ca6c653 | |
wxiaoguang | eda10cc2bb | |
wxiaoguang | ce8b11ae13 | |
Kemal Zebari | 22c7b3a744 | |
wxiaoguang | 982b20d259 | |
wxiaoguang | 5c236bd4c0 | |
yp05327 | ecd1d96f49 | |
Neal Caffery | bb0e4ce581 | |
wxiaoguang | c7bb3aa034 | |
wxiaoguang | 0f3e717a1a | |
Kemal Zebari | 9f0ef3621a | |
yp05327 | a50026e2f3 | |
wxiaoguang | 53b55223d1 | |
silverwind | c4e875402b | |
silverwind | b30b7df9f4 | |
silverwind | c445a85528 | |
Bo-Yi Wu | e67fbe4f15 | |
Archer | 5c542ca94c | |
Kemal Zebari | 872caa17c0 | |
wxiaoguang | 677032d36a | |
silverwind | 6f89d5e3a0 | |
silverwind | 9235442ba5 | |
Lunny Xiao | cb9e1a3ff6 | |
silverwind | b1bb3642e5 | |
wxiaoguang | eb8bb82e58 | |
wxiaoguang | 6ff2acc52c | |
wxiaoguang | ebe6f4cad7 | |
silverwind | 82eca44581 | |
yp05327 | db0bfbb3ea | |
yp05327 | f1784504da | |
yp05327 | 43495eb81a | |
yp05327 | 7031377e1e | |
yp05327 | c397f08812 | |
yp05327 | ed19b8bdd7 | |
yp05327 | 58147c14f7 | |
yp05327 | a8a5de2cdf | |
yp05327 | 263a4438e4 | |
yp05327 | eed8b07efd | |
yp05327 | c4386f0532 | |
yp05327 | 7b06c463e6 | |
yp05327 | 17ad1f6835 | |
yp05327 | 5af46f7ce9 | |
yp05327 | 66f96761e8 | |
yp05327 | 01088a9f41 | |
yp05327 | ba4406ff2c | |
yp05327 | 58dbb670b0 | |
yp05327 | 2e860edbd4 | |
yp05327 | 6f5f65ee79 | |
yp05327 | 4bd9cdba22 | |
yp05327 | b3d6bed21d | |
yp05327 | 3922011044 | |
yp05327 | e44cec43f6 | |
yp05327 | 3d98673fd6 | |
yp05327 | bff0db2601 | |
yp05327 | 2d53287c89 | |
yp05327 | 2b677fc2ae | |
yp05327 | 2549c33632 | |
yp05327 | a2425ce6ac | |
yp05327 | 7fa0362d36 | |
yp05327 | 34734c2265 | |
yp05327 | e79c8e0b58 | |
yp05327 | 8f3d5c9bc3 | |
yp05327 | 330c3f8f5d | |
yp05327 | 38f4b6680b | |
yp05327 | 60fd886265 | |
yp05327 | 44c80aed15 | |
yp05327 | bb53d5756d | |
yp05327 | 6248701203 | |
yp05327 | 2e93f87122 | |
yp05327 | a641d84bb1 | |
yp05327 | 5bfa11ba03 | |
yp05327 | aa2b8c4855 | |
yp05327 | d7cb37c8ba | |
yp05327 | 08b0159874 | |
yp05327 | 8e96323ef0 | |
yp05327 | 7559311bae | |
yp05327 | 4df1529e2e | |
yp05327 | dcc91fbb70 | |
yp05327 | 9a9dd060b0 | |
yp05327 | fb3afac3e5 | |
yp05327 | 64ece1cbe5 | |
yp05327 | 5cf3d9e505 | |
yp05327 | 3b6ff9ba0a | |
yp05327 | a583c64fd0 | |
yp05327 | b57883c31c | |
yp05327 | 76f472dba8 | |
yp05327 | d60c75883b | |
yp05327 | 66071ac97b | |
yp05327 | 86892d458d | |
yp05327 | f9b4085b57 | |
yp05327 | 274e5abdac | |
yp05327 | 94025efd44 | |
yp05327 | 4de1b0514c | |
yp05327 | 9a33437cd6 | |
yp05327 | 6517ad975f | |
yp05327 | 17876b4e89 | |
yp05327 | e263227a88 | |
yp05327 | 4d42a63805 | |
yp05327 | 63562c31b8 | |
yp05327 | 5449bc139a | |
yp05327 | d848876744 | |
yp05327 | f06e09151d | |
yp05327 | 1ecb6fbc3e | |
yp05327 | f7295177bd | |
yp05327 | 0bfec1a3e8 | |
yp05327 | 69b76246b0 | |
yp05327 | a7fc27917c | |
yp05327 | c704332d23 | |
yp05327 | 5c3f7aa231 | |
yp05327 | d93b2bcc80 | |
yp05327 | e74709ed15 | |
yp05327 | d3f72aac2f | |
yp05327 | bc176bb1bc | |
yp05327 | 172ceebf14 | |
yp05327 | 54f4fd56cd | |
yp05327 | 4c116821fa | |
yp05327 | 2b0abf9331 | |
yp05327 | 71dd740416 | |
yp05327 | 266d07fdd9 | |
yp05327 | df97530659 | |
yp05327 | b3e3b7311c | |
yp05327 | c8f707b890 |
10
cmd/hook.go
10
cmd/hook.go
|
@ -220,10 +220,7 @@ Gitea or set your environment appropriately.`, "")
|
|||
}
|
||||
}
|
||||
|
||||
supportProcReceive := false
|
||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
||||
supportProcReceive = true
|
||||
}
|
||||
supportProcReceive := git.DefaultFeatures().SupportProcReceive
|
||||
|
||||
for scanner.Scan() {
|
||||
// TODO: support news feeds for wiki
|
||||
|
@ -341,6 +338,7 @@ Gitea or set your environment appropriately.`, "")
|
|||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
||||
pusherName := os.Getenv(repo_module.EnvPusherName)
|
||||
|
||||
hookOptions := private.HookOptions{
|
||||
|
@ -350,6 +348,8 @@ Gitea or set your environment appropriately.`, "")
|
|||
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
||||
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
||||
GitPushOptions: pushOptions(),
|
||||
PullRequestID: prID,
|
||||
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
|
||||
}
|
||||
oldCommitIDs := make([]string, hookBatchSize)
|
||||
newCommitIDs := make([]string, hookBatchSize)
|
||||
|
@ -497,7 +497,7 @@ Gitea or set your environment appropriately.`, "")
|
|||
return nil
|
||||
}
|
||||
|
||||
if git.CheckGitVersionAtLeast("2.29") != nil {
|
||||
if !git.DefaultFeatures().SupportProcReceive {
|
||||
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ var CmdMigrateStorage = &cli.Command{
|
|||
Name: "type",
|
||||
Aliases: []string{"t"},
|
||||
Value: "",
|
||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'",
|
||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "storage",
|
||||
|
@ -160,6 +160,13 @@ func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) er
|
|||
})
|
||||
}
|
||||
|
||||
func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
|
||||
_, err := storage.Copy(dstStorage, artifact.ArtifactPath, storage.ActionsArtifacts, artifact.ArtifactPath)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func runMigrateStorage(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
@ -223,13 +230,14 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
|
||||
"attachments": migrateAttachments,
|
||||
"lfs": migrateLFS,
|
||||
"avatars": migrateAvatars,
|
||||
"repo-avatars": migrateRepoAvatars,
|
||||
"repo-archivers": migrateRepoArchivers,
|
||||
"packages": migratePackages,
|
||||
"actions-log": migrateActionsLog,
|
||||
"attachments": migrateAttachments,
|
||||
"lfs": migrateLFS,
|
||||
"avatars": migrateAvatars,
|
||||
"repo-avatars": migrateRepoAvatars,
|
||||
"repo-archivers": migrateRepoArchivers,
|
||||
"packages": migratePackages,
|
||||
"actions-log": migrateActionsLog,
|
||||
"actions-artifacts": migrateActionsArtifacts,
|
||||
}
|
||||
|
||||
tp := strings.ToLower(ctx.String("type"))
|
||||
|
|
|
@ -178,7 +178,7 @@ func runServ(c *cli.Context) error {
|
|||
}
|
||||
|
||||
if len(words) < 2 {
|
||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
||||
if git.DefaultFeatures().SupportProcReceive {
|
||||
// for AGit Flow
|
||||
if cmd == "ssh_info" {
|
||||
fmt.Print(`{"type":"gitea","version":1}`)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Gitea - Docker
|
||||
|
||||
Dockerfile is found in root of repository.
|
||||
Dockerfile is found in the root of the repository.
|
||||
|
||||
Docker image can be found on [docker hub](https://hub.docker.com/r/gitea/gitea)
|
||||
Docker image can be found on [docker hub](https://hub.docker.com/r/gitea/gitea).
|
||||
|
||||
Documentation on using docker image can be found on [Gitea Docs site](https://docs.gitea.com/installation/install-with-docker-rootless)
|
||||
Documentation on using docker image can be found on [Gitea Docs site](https://docs.gitea.com/installation/install-with-docker-rootless).
|
||||
|
|
4
go.mod
4
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
code.gitea.io/sdk/gitea v0.17.1
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.15.0
|
||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||
gitea.com/go-chi/cache v0.2.0
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||
|
@ -59,6 +59,7 @@ require (
|
|||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.1.2
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/h2non/gock v1.2.0
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huandu/xstrings v1.4.0
|
||||
|
@ -209,6 +210,7 @@ require (
|
|||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -20,8 +20,8 @@ git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4H
|
|||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||
gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
||||
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028 h1:6/QAx4+s0dyRwdaTFPTnhGppuiuu0OqxIH9szyTpvKw=
|
||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||
|
@ -430,6 +430,10 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw
|
|||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
|
@ -591,6 +595,8 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
|||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
|
||||
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
|
||||
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
|
|
|
@ -1,173 +1,185 @@
|
|||
-
|
||||
id: 1
|
||||
user_id: 2
|
||||
repo_id: 3
|
||||
user_id: 1
|
||||
repo_id: 63
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 2
|
||||
user_id: 2
|
||||
repo_id: 5
|
||||
repo_id: 3
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 3
|
||||
user_id: 2
|
||||
repo_id: 5
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 4
|
||||
user_id: 2
|
||||
repo_id: 24
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 4
|
||||
id: 5
|
||||
user_id: 2
|
||||
repo_id: 32
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 5
|
||||
id: 6
|
||||
user_id: 4
|
||||
repo_id: 3
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 6
|
||||
id: 7
|
||||
user_id: 4
|
||||
repo_id: 4
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 7
|
||||
id: 8
|
||||
user_id: 4
|
||||
repo_id: 40
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 8
|
||||
id: 9
|
||||
user_id: 10
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 9
|
||||
id: 10
|
||||
user_id: 10
|
||||
repo_id: 32
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 10
|
||||
id: 11
|
||||
user_id: 12
|
||||
repo_id: 62
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 12
|
||||
user_id: 15
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 11
|
||||
id: 13
|
||||
user_id: 15
|
||||
repo_id: 22
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 12
|
||||
id: 14
|
||||
user_id: 15
|
||||
repo_id: 23
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 13
|
||||
id: 15
|
||||
user_id: 15
|
||||
repo_id: 24
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 14
|
||||
id: 16
|
||||
user_id: 15
|
||||
repo_id: 32
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 15
|
||||
user_id: 18
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 16
|
||||
user_id: 18
|
||||
repo_id: 22
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 17
|
||||
user_id: 18
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 18
|
||||
user_id: 18
|
||||
repo_id: 22
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 19
|
||||
user_id: 18
|
||||
repo_id: 23
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 18
|
||||
id: 20
|
||||
user_id: 18
|
||||
repo_id: 24
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 19
|
||||
id: 21
|
||||
user_id: 20
|
||||
repo_id: 24
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 20
|
||||
id: 22
|
||||
user_id: 20
|
||||
repo_id: 27
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 21
|
||||
id: 23
|
||||
user_id: 20
|
||||
repo_id: 28
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 22
|
||||
id: 24
|
||||
user_id: 29
|
||||
repo_id: 4
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 23
|
||||
id: 25
|
||||
user_id: 29
|
||||
repo_id: 24
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 24
|
||||
id: 26
|
||||
user_id: 31
|
||||
repo_id: 27
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 25
|
||||
id: 27
|
||||
user_id: 31
|
||||
repo_id: 28
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 26
|
||||
id: 28
|
||||
user_id: 38
|
||||
repo_id: 60
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 27
|
||||
id: 29
|
||||
user_id: 38
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 28
|
||||
id: 30
|
||||
user_id: 39
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 29
|
||||
id: 31
|
||||
user_id: 40
|
||||
repo_id: 61
|
||||
mode: 4
|
||||
|
|
|
@ -45,3 +45,267 @@
|
|||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 5
|
||||
repo_id: 10
|
||||
name: 'master'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 6
|
||||
repo_id: 10
|
||||
name: 'new-commit'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 7
|
||||
repo_id: 10
|
||||
name: 'no-commit'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 8
|
||||
repo_id: 10
|
||||
name: 'opening-pr'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 9
|
||||
repo_id: 10
|
||||
name: 'closed-pr'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 10
|
||||
repo_id: 10
|
||||
name: 'merged-pr'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 11
|
||||
repo_id: 10
|
||||
name: 'closed-pr-with-deleted-branch'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 12
|
||||
is_deleted: true
|
||||
deleted_by_id: 12
|
||||
deleted_unix: 1489927700
|
||||
|
||||
-
|
||||
id: 12
|
||||
repo_id: 10
|
||||
name: 'merged-pr-with-deleted-branch'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 12
|
||||
is_deleted: true
|
||||
deleted_by_id: 12
|
||||
deleted_unix: 1489927700
|
||||
|
||||
-
|
||||
id: 13
|
||||
repo_id: 10
|
||||
name: 'deleted-branch'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 12
|
||||
is_deleted: true
|
||||
deleted_by_id: 12
|
||||
deleted_unix: 1489927700
|
||||
|
||||
-
|
||||
id: 14
|
||||
repo_id: 11
|
||||
name: 'master'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 13
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 15
|
||||
repo_id: 11
|
||||
name: 'user-fork-new-commit'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 13
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 16
|
||||
repo_id: 11
|
||||
name: 'user-fork-no-commit'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 13
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 17
|
||||
repo_id: 62
|
||||
name: 'master'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 18
|
||||
repo_id: 62
|
||||
name: 'org-fork-new-commit'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927691
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 19
|
||||
repo_id: 62
|
||||
name: 'org-fork-no-commit'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927691
|
||||
pusher_id: 12
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 20
|
||||
repo_id: 63
|
||||
name: 'master'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 1
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 21
|
||||
repo_id: 63
|
||||
name: 'private-org-fork-new-commit'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 1
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 22
|
||||
repo_id: 63
|
||||
name: 'private-org-fork-no-commit'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 1
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 23
|
||||
repo_id: 63
|
||||
name: 'private-org-fork-no-permission-new-commit'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 2
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 24
|
||||
repo_id: 63
|
||||
name: 'private-org-fork-no-permission-no-commit'
|
||||
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||
commit_message: 'Initial commit'
|
||||
commit_time: 1489927679
|
||||
pusher_id: 2
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 25
|
||||
repo_id: 10
|
||||
name: 'same-name-branch-in-pr'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 13
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
||||
-
|
||||
id: 26
|
||||
repo_id: 11
|
||||
name: 'same-name-branch-in-pr'
|
||||
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||
commit_message: 'add'
|
||||
commit_time: 1489927680
|
||||
pusher_id: 13
|
||||
is_deleted: false
|
||||
deleted_by_id: 0
|
||||
deleted_unix: 0
|
||||
|
|
|
@ -372,3 +372,105 @@
|
|||
created_unix: 1707270422
|
||||
updated_unix: 1707270422
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 23
|
||||
repo_id: 10
|
||||
index: 2
|
||||
poster_id: 12
|
||||
original_author_id: 0
|
||||
name: opening pr for recently new branch search test
|
||||
content: content
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: false
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 24
|
||||
repo_id: 10
|
||||
index: 3
|
||||
poster_id: 12
|
||||
original_author_id: 0
|
||||
name: closed pr for recently new branch search test
|
||||
content: content
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: true
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 25
|
||||
repo_id: 10
|
||||
index: 4
|
||||
poster_id: 12
|
||||
original_author_id: 0
|
||||
name: merged pr for recently new branch search test
|
||||
content: content
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: false
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 26
|
||||
repo_id: 10
|
||||
index: 5
|
||||
poster_id: 12
|
||||
original_author_id: 0
|
||||
name: closed pr with deleted branch for recently new branch search test
|
||||
content: content
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: true
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 27
|
||||
repo_id: 10
|
||||
index: 6
|
||||
poster_id: 12
|
||||
original_author_id: 0
|
||||
name: merged pr with deleted branch for recently new branch search test
|
||||
content: content
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: false
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
||||
-
|
||||
id: 28
|
||||
repo_id: 11
|
||||
index: 1
|
||||
poster_id: 13
|
||||
original_author_id: 0
|
||||
name: pr with same branch name for recently new branch search test
|
||||
content: content
|
||||
milestone_id: 0
|
||||
priority: 0
|
||||
is_closed: false
|
||||
is_pull: true
|
||||
num_comments: 0
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
|
|
@ -1,27 +1,35 @@
|
|||
-
|
||||
group_id: 1
|
||||
max_index: 5
|
||||
|
||||
-
|
||||
group_id: 2
|
||||
max_index: 2
|
||||
|
||||
-
|
||||
group_id: 3
|
||||
max_index: 2
|
||||
|
||||
-
|
||||
group_id: 10
|
||||
max_index: 1
|
||||
max_index: 6
|
||||
|
||||
-
|
||||
group_id: 32
|
||||
max_index: 2
|
||||
|
||||
-
|
||||
group_id: 48
|
||||
max_index: 1
|
||||
|
||||
-
|
||||
group_id: 42
|
||||
max_index: 1
|
||||
|
||||
-
|
||||
group_id: 50
|
||||
max_index: 1
|
||||
|
||||
-
|
||||
group_id: 51
|
||||
max_index: 1
|
||||
|
|
|
@ -117,3 +117,15 @@
|
|||
uid: 40
|
||||
org_id: 41
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 21
|
||||
uid: 12
|
||||
org_id: 25
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 22
|
||||
uid: 2
|
||||
org_id: 35
|
||||
is_public: true
|
||||
|
|
|
@ -117,3 +117,81 @@
|
|||
index: 1
|
||||
head_repo_id: 61
|
||||
base_repo_id: 61
|
||||
|
||||
-
|
||||
id: 11
|
||||
type: 0 # gitea pull request
|
||||
status: 2 # mergable
|
||||
issue_id: 23
|
||||
index: 2
|
||||
head_repo_id: 10
|
||||
base_repo_id: 10
|
||||
head_branch: opening-pr
|
||||
base_branch: master
|
||||
merge_base: cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d
|
||||
has_merged: false
|
||||
|
||||
-
|
||||
id: 12
|
||||
type: 0 # gitea pull request
|
||||
status: 2 # mergable
|
||||
issue_id: 24
|
||||
index: 3
|
||||
head_repo_id: 10
|
||||
base_repo_id: 10
|
||||
head_branch: closed-pr
|
||||
base_branch: master
|
||||
merge_base: cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d
|
||||
has_merged: false
|
||||
|
||||
-
|
||||
id: 13
|
||||
type: 0 # gitea pull request
|
||||
status: 3 # manually merged
|
||||
issue_id: 25
|
||||
index: 4
|
||||
head_repo_id: 10
|
||||
base_repo_id: 10
|
||||
head_branch: merged-pr
|
||||
base_branch: master
|
||||
merge_base: cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d
|
||||
has_merged: true
|
||||
|
||||
-
|
||||
id: 14
|
||||
type: 0 # gitea pull request
|
||||
status: 2 # mergable
|
||||
issue_id: 26
|
||||
index: 5
|
||||
head_repo_id: 10
|
||||
base_repo_id: 10
|
||||
head_branch: closed-pr-with-deleted-branch
|
||||
base_branch: master
|
||||
merge_base: cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d
|
||||
has_merged: false
|
||||
|
||||
-
|
||||
id: 15
|
||||
type: 0 # gitea pull request
|
||||
status: 3 # manually merged
|
||||
issue_id: 27
|
||||
index: 6
|
||||
head_repo_id: 10
|
||||
base_repo_id: 10
|
||||
head_branch: merged-pr-with-deleted-branch
|
||||
base_branch: master
|
||||
merge_base: cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d
|
||||
has_merged: true
|
||||
|
||||
-
|
||||
id: 16
|
||||
type: 0 # gitea pull request
|
||||
status: 2 # mergable
|
||||
issue_id: 28
|
||||
index: 1
|
||||
head_repo_id: 11
|
||||
base_repo_id: 10
|
||||
head_branch: same-name-branch-in-pr
|
||||
base_branch: master
|
||||
merge_base: cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d
|
||||
has_merged: false
|
||||
|
|
|
@ -712,3 +712,15 @@
|
|||
type: 3
|
||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 108
|
||||
repo_id: 62
|
||||
type: 1
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 109
|
||||
repo_id: 63
|
||||
type: 1
|
||||
created_unix: 946684810
|
||||
|
|
|
@ -282,11 +282,11 @@
|
|||
default_branch: master
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_forks: 1
|
||||
num_forks: 3
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 1
|
||||
num_closed_pulls: 0
|
||||
num_pulls: 6
|
||||
num_closed_pulls: 2
|
||||
num_milestones: 1
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
|
@ -316,7 +316,7 @@
|
|||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_pulls: 1
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 0
|
||||
num_closed_milestones: 0
|
||||
|
@ -327,7 +327,7 @@
|
|||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: false
|
||||
is_fork: true
|
||||
fork_id: 10
|
||||
is_template: false
|
||||
template_id: 0
|
||||
|
@ -1768,3 +1768,63 @@
|
|||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
-
|
||||
id: 62
|
||||
owner_id: 25
|
||||
owner_name: org25
|
||||
lower_name: org_fork_repo62
|
||||
name: org_fork_repo62
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 0
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
num_closed_projects: 0
|
||||
is_private: false
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: true
|
||||
fork_id: 10
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
-
|
||||
id: 63
|
||||
owner_id: 35
|
||||
owner_name: private_org35
|
||||
lower_name: private_org_fork_repo63
|
||||
name: private_org_fork_repo63
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 0
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
num_closed_projects: 0
|
||||
is_private: true
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: true
|
||||
fork_id: 10
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
|
|
@ -191,7 +191,7 @@
|
|||
lower_name: owners
|
||||
name: Owners
|
||||
authorize: 4 # owner
|
||||
num_repos: 0
|
||||
num_repos: 1
|
||||
num_members: 1
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: true
|
||||
|
@ -239,3 +239,25 @@
|
|||
num_members: 2
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: false
|
||||
|
||||
-
|
||||
id: 23
|
||||
org_id: 25
|
||||
lower_name: owners
|
||||
name: Owners
|
||||
authorize: 4 # owner
|
||||
num_repos: 0
|
||||
num_members: 1
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: true
|
||||
|
||||
-
|
||||
id: 24
|
||||
org_id: 35
|
||||
lower_name: team24noreadcode
|
||||
name: team24noreadcode
|
||||
authorize: 0 # no access
|
||||
num_repos: 1
|
||||
num_members: 1
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: false
|
||||
|
|
|
@ -75,3 +75,15 @@
|
|||
org_id: 41
|
||||
team_id: 22
|
||||
repo_id: 61
|
||||
|
||||
-
|
||||
id: 14
|
||||
org_id: 35
|
||||
team_id: 18
|
||||
repo_id: 63
|
||||
|
||||
-
|
||||
id: 15
|
||||
org_id: 35
|
||||
team_id: 24
|
||||
repo_id: 63
|
||||
|
|
|
@ -322,3 +322,21 @@
|
|||
team_id: 22
|
||||
type: 3
|
||||
access_mode: 1
|
||||
|
||||
-
|
||||
id: 55
|
||||
team_id: 18
|
||||
type: 1 # code
|
||||
access_mode: 4
|
||||
|
||||
-
|
||||
id: 56
|
||||
team_id: 23
|
||||
type: 1 # code
|
||||
access_mode: 4
|
||||
|
||||
-
|
||||
id: 57
|
||||
team_id: 24
|
||||
type: 1 # code
|
||||
access_mode: 0
|
||||
|
|
|
@ -147,3 +147,15 @@
|
|||
org_id: 41
|
||||
team_id: 22
|
||||
uid: 39
|
||||
|
||||
-
|
||||
id: 26
|
||||
org_id: 25
|
||||
team_id: 23
|
||||
uid: 12
|
||||
|
||||
-
|
||||
id: 27
|
||||
org_id: 35
|
||||
team_id: 24
|
||||
uid: 2
|
||||
|
|
|
@ -917,9 +917,9 @@
|
|||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 1
|
||||
num_members: 1
|
||||
num_repos: 1
|
||||
num_teams: 2
|
||||
num_members: 2
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
|
@ -1288,9 +1288,9 @@
|
|||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 1
|
||||
num_members: 1
|
||||
num_repos: 1
|
||||
num_teams: 2
|
||||
num_members: 2
|
||||
visibility: 2
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
|
|
|
@ -10,9 +10,11 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
|
@ -102,8 +104,9 @@ func (err ErrBranchesEqual) Unwrap() error {
|
|||
// for pagination, keyword search and filtering
|
||||
type Branch struct {
|
||||
ID int64
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
|
||||
CommitID string
|
||||
CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line)
|
||||
PusherID int64
|
||||
|
@ -139,6 +142,14 @@ func (b *Branch) LoadPusher(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
func (b *Branch) LoadRepo(ctx context.Context) (err error) {
|
||||
if b.Repo != nil || b.RepoID == 0 {
|
||||
return nil
|
||||
}
|
||||
b.Repo, err = repo_model.GetRepositoryByID(ctx, b.RepoID)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(Branch))
|
||||
db.RegisterModel(new(RenamedBranch))
|
||||
|
@ -400,24 +411,105 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
|||
return committer.Commit()
|
||||
}
|
||||
|
||||
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created
|
||||
// except the indicate branch
|
||||
func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) {
|
||||
branches := make(BranchList, 0, 2)
|
||||
subQuery := builder.Select("head_branch").From("pull_request").
|
||||
InnerJoin("issue", "issue.id = pull_request.issue_id").
|
||||
Where(builder.Eq{
|
||||
"pull_request.head_repo_id": repoID,
|
||||
"issue.is_closed": false,
|
||||
})
|
||||
err := db.GetEngine(ctx).
|
||||
Where("pusher_id=? AND is_deleted=?", userID, false).
|
||||
And("name <> ?", excludeBranchName).
|
||||
And("repo_id = ?", repoID).
|
||||
And("commit_time >= ?", time.Now().Add(-time.Hour*6).Unix()).
|
||||
NotIn("name", subQuery).
|
||||
OrderBy("branch.commit_time DESC").
|
||||
Limit(2).
|
||||
Find(&branches)
|
||||
return branches, err
|
||||
type FindRecentlyPushedNewBranchesOptions struct {
|
||||
Actor *user_model.User
|
||||
Repo *repo_model.Repository
|
||||
BaseRepo *repo_model.Repository
|
||||
CommitAfterUnix int64
|
||||
MaxCount int
|
||||
}
|
||||
|
||||
type RecentlyPushedNewBranch struct {
|
||||
BranchDisplayName string
|
||||
BranchLink string
|
||||
BranchCompareURL string
|
||||
CommitTime timeutil.TimeStamp
|
||||
}
|
||||
|
||||
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 2 hours which has no opened PRs created
|
||||
// opts.Actor should not be nil
|
||||
// if opts.CommitAfterUnix is 0, we will find the branches that were committed to in the last 2 hours
|
||||
// if opts.ListOptions is not set, we will only display top 2 latest branch
|
||||
func FindRecentlyPushedNewBranches(ctx context.Context, opts *FindRecentlyPushedNewBranchesOptions) ([]*RecentlyPushedNewBranch, error) {
|
||||
// find all related repo ids
|
||||
repoOpts := repo_model.SearchRepoOptions{
|
||||
Actor: opts.Actor,
|
||||
Private: true,
|
||||
AllPublic: false, // Include also all public repositories of users and public organisations
|
||||
AllLimited: false, // Include also all public repositories of limited organisations
|
||||
Fork: optional.Some(true),
|
||||
ForkFrom: opts.BaseRepo.ID,
|
||||
Archived: optional.Some(false),
|
||||
}
|
||||
repoCond := repo_model.SearchRepositoryCondition(&repoOpts).And(repo_model.AccessibleRepositoryCondition(opts.Actor, unit.TypeCode))
|
||||
if opts.Repo.ID == opts.BaseRepo.ID {
|
||||
// should also include the base repo's branches
|
||||
repoCond = repoCond.Or(builder.Eq{"id": opts.BaseRepo.ID})
|
||||
} else {
|
||||
// in fork repo, we only detect the fork repo's branch
|
||||
repoCond = repoCond.And(builder.Eq{"id": opts.Repo.ID})
|
||||
}
|
||||
repoIDs := builder.Select("id").From("repository").Where(repoCond)
|
||||
|
||||
if opts.CommitAfterUnix == 0 {
|
||||
opts.CommitAfterUnix = time.Now().Add(-time.Hour * 2).Unix()
|
||||
}
|
||||
|
||||
baseBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// find all related branches, these branches may already created PRs, we will check later
|
||||
branches, err := db.Find[Branch](ctx, FindBranchOptions{
|
||||
RepoCond: builder.In("branch.repo_id", repoIDs),
|
||||
CommitCond: builder.Neq{"branch.commit_id": baseBranch.CommitID}, // newly created branch have no changes, so skip them,
|
||||
PusherID: opts.Actor.ID,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
CommitAfterUnix: opts.CommitAfterUnix,
|
||||
OrderBy: "branch.updated_unix DESC",
|
||||
ListOptions: db.ListOptionsAll,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newBranches := make([]*RecentlyPushedNewBranch, 0, len(branches))
|
||||
if opts.MaxCount == 0 {
|
||||
// by default we display 2 recently pushed new branch
|
||||
opts.MaxCount = 2
|
||||
}
|
||||
for _, branch := range branches {
|
||||
// whether branch have already created PR
|
||||
count, err := db.GetEngine(ctx).Table("pull_request").
|
||||
// we should not only use branch name here, because if there are branches with same name in other repos,
|
||||
// we can not detect them correctly
|
||||
Where(builder.Eq{"head_repo_id": branch.RepoID, "head_branch": branch.Name}).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if no PR, we add to the result
|
||||
if count == 0 {
|
||||
if err := branch.LoadRepo(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
branchDisplayName := branch.Name
|
||||
if branch.Repo.ID != opts.BaseRepo.ID && branch.Repo.ID != opts.Repo.ID {
|
||||
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
||||
}
|
||||
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
||||
BranchDisplayName: branchDisplayName,
|
||||
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||
CommitTime: branch.CommitTime,
|
||||
})
|
||||
}
|
||||
if len(newBranches) == opts.MaxCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return newBranches, nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
@ -59,30 +60,74 @@ func (branches BranchList) LoadPusher(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (branches BranchList) LoadRepo(ctx context.Context) error {
|
||||
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
|
||||
return branch.RepoID, branch.RepoID > 0 && branch.Repo == nil
|
||||
})
|
||||
|
||||
reposMap := make(map[int64]*repo_model.Repository, len(ids))
|
||||
if err := db.GetEngine(ctx).In("id", ids).Find(&reposMap); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, branch := range branches {
|
||||
if branch.RepoID <= 0 || branch.Repo != nil {
|
||||
continue
|
||||
}
|
||||
branch.Repo = reposMap[branch.RepoID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindBranchOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
RepoCond builder.Cond
|
||||
ExcludeBranchNames []string
|
||||
CommitCond builder.Cond
|
||||
PusherID int64
|
||||
IsDeletedBranch optional.Option[bool]
|
||||
CommitAfterUnix int64
|
||||
CommitBeforeUnix int64
|
||||
OrderBy string
|
||||
Keyword string
|
||||
}
|
||||
|
||||
func (opts FindBranchOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.RepoCond != nil {
|
||||
cond = cond.And(opts.RepoCond)
|
||||
}
|
||||
|
||||
if len(opts.ExcludeBranchNames) > 0 {
|
||||
cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
|
||||
cond = cond.And(builder.NotIn("branch.name", opts.ExcludeBranchNames))
|
||||
}
|
||||
|
||||
if opts.CommitCond != nil {
|
||||
cond = cond.And(opts.CommitCond)
|
||||
}
|
||||
|
||||
if opts.PusherID > 0 {
|
||||
cond = cond.And(builder.Eq{"branch.pusher_id": opts.PusherID})
|
||||
}
|
||||
|
||||
if opts.IsDeletedBranch.Has() {
|
||||
cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.Value()})
|
||||
cond = cond.And(builder.Eq{"branch.is_deleted": opts.IsDeletedBranch.Value()})
|
||||
}
|
||||
if opts.Keyword != "" {
|
||||
cond = cond.And(builder.Like{"name", opts.Keyword})
|
||||
}
|
||||
|
||||
if opts.CommitAfterUnix != 0 {
|
||||
cond = cond.And(builder.Gte{"branch.commit_time": opts.CommitAfterUnix})
|
||||
}
|
||||
if opts.CommitBeforeUnix != 0 {
|
||||
cond = cond.And(builder.Lte{"branch.commit_time": opts.CommitBeforeUnix})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
|
@ -183,3 +184,90 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, deletedBranch)
|
||||
}
|
||||
|
||||
func TestFindRecentlyPushedNewBranches(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user12 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 12})
|
||||
user13 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 13})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *git_model.FindRecentlyPushedNewBranchesOptions
|
||||
count int
|
||||
want []string
|
||||
}{
|
||||
// user12 is the owner of the repo10 and the organization org25
|
||||
// in repo10, user12 has opening/closed/merged pr and closed/merged pr with deleted branch
|
||||
{
|
||||
name: "new branch of the repo, org fork repo, pr branches and deleted branch",
|
||||
opts: &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Actor: user12,
|
||||
CommitAfterUnix: 1489927670,
|
||||
MaxCount: 10,
|
||||
},
|
||||
count: 2,
|
||||
want: []string{"new-commit", "org25/org_fork_repo62:org-fork-new-commit"},
|
||||
},
|
||||
// user13 pushed 2 branches with the same name in repo10 and repo11
|
||||
// and repo11's branch has a pr, but repo10's branch doesn't
|
||||
// in this case, we should get repo10's branch but not repo11's branch
|
||||
{
|
||||
name: "new branch from user fork repo and same name branch",
|
||||
opts: &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Actor: user13,
|
||||
CommitAfterUnix: 1489927670,
|
||||
MaxCount: 10,
|
||||
},
|
||||
count: 2,
|
||||
want: []string{"user13/repo11:user-fork-new-commit", "same-name-branch-in-pr"},
|
||||
},
|
||||
// user1 is the owner of private_org35
|
||||
{
|
||||
name: "new branch from private org with code permission repo",
|
||||
opts: &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Actor: user1,
|
||||
CommitAfterUnix: 1489927670,
|
||||
},
|
||||
count: 1,
|
||||
want: []string{"private_org35/private_org_fork_repo63:private-org-fork-new-commit"},
|
||||
},
|
||||
// user2 does not have code permission in private_org35
|
||||
{
|
||||
name: "new branch from private org with no code permission repo",
|
||||
opts: &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Actor: user2,
|
||||
CommitAfterUnix: 1489927670,
|
||||
},
|
||||
count: 0,
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "test commitAfterUnix option",
|
||||
opts: &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Actor: user12,
|
||||
CommitAfterUnix: 1489927690,
|
||||
},
|
||||
count: 1,
|
||||
want: []string{"org25/org_fork_repo62:org-fork-new-commit"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.opts.Repo = repo
|
||||
tt.opts.BaseRepo = repo
|
||||
branches, err := git_model.FindRecentlyPushedNewBranches(db.DefaultContext, tt.opts)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.count, len(branches))
|
||||
|
||||
for i := 0; i < tt.count; i++ {
|
||||
assert.Equal(t, tt.want[i], branches[i].BranchDisplayName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -379,7 +379,7 @@ func TestCountIssues(t *testing.T) {
|
|||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 22, count)
|
||||
assert.EqualValues(t, 28, count)
|
||||
}
|
||||
|
||||
func TestIssueLoadAttributes(t *testing.T) {
|
||||
|
|
|
@ -429,62 +429,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpdateIssueByAPI updates all allowed fields of given issue.
|
||||
// If the issue status is changed a statusChangeComment is returned
|
||||
// similarly if the title is changed the titleChanged bool is set to true
|
||||
func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
return nil, false, fmt.Errorf("loadRepo: %w", err)
|
||||
}
|
||||
|
||||
// Reload the issue
|
||||
currentIssue, err := GetIssueByID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(
|
||||
"name", "content", "milestone_id", "priority",
|
||||
"deadline_unix", "updated_unix", "is_locked").
|
||||
Update(issue); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
titleChanged = currentIssue.Title != issue.Title
|
||||
if titleChanged {
|
||||
opts := &CreateCommentOptions{
|
||||
Type: CommentTypeChangeTitle,
|
||||
Doer: doer,
|
||||
Repo: issue.Repo,
|
||||
Issue: issue,
|
||||
OldTitle: currentIssue.Title,
|
||||
NewTitle: issue.Title,
|
||||
}
|
||||
_, err := CreateComment(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("createComment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if currentIssue.IsClosed != issue.IsClosed {
|
||||
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return statusChangeComment, titleChanged, committer.Commit()
|
||||
}
|
||||
|
||||
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
|
||||
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
||||
// if the deadline hasn't changed do nothing
|
||||
|
|
|
@ -81,7 +81,7 @@ func TestUserListIsPublicMember(t *testing.T) {
|
|||
{3, map[int64]bool{2: true, 4: false, 28: true}},
|
||||
{6, map[int64]bool{5: true, 28: true}},
|
||||
{7, map[int64]bool{5: false}},
|
||||
{25, map[int64]bool{24: true}},
|
||||
{25, map[int64]bool{12: true, 24: true}},
|
||||
{22, map[int64]bool{}},
|
||||
}
|
||||
for _, v := range tt {
|
||||
|
@ -108,8 +108,8 @@ func TestUserListIsUserOrgOwner(t *testing.T) {
|
|||
{3, map[int64]bool{2: true, 4: false, 28: false}},
|
||||
{6, map[int64]bool{5: true, 28: false}},
|
||||
{7, map[int64]bool{5: true}},
|
||||
{25, map[int64]bool{24: false}}, // ErrTeamNotExist
|
||||
{22, map[int64]bool{}}, // No member
|
||||
{25, map[int64]bool{12: true, 24: false}}, // ErrTeamNotExist
|
||||
{22, map[int64]bool{}}, // No member
|
||||
}
|
||||
for _, v := range tt {
|
||||
t.Run(fmt.Sprintf("IsUserOrgOwnerOfOrgId%d", v.orgid), func(t *testing.T) {
|
||||
|
|
|
@ -9,10 +9,10 @@ import (
|
|||
"image/png"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/avatar"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
@ -84,13 +84,7 @@ func (repo *Repository) relAvatarLink(ctx context.Context) string {
|
|||
return setting.AppSubURL + "/repo-avatars/" + url.PathEscape(repo.Avatar)
|
||||
}
|
||||
|
||||
// AvatarLink returns a link to the repository's avatar.
|
||||
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||
func (repo *Repository) AvatarLink(ctx context.Context) string {
|
||||
link := repo.relAvatarLink(ctx)
|
||||
// we only prepend our AppURL to our known (relative, internal) avatar link to get an absolute URL
|
||||
if strings.HasPrefix(link, "/") && !strings.HasPrefix(link, "//") {
|
||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
|
||||
}
|
||||
// otherwise, return the link as it is
|
||||
return link
|
||||
return httplib.MakeAbsoluteURL(ctx, repo.relAvatarLink(ctx))
|
||||
}
|
||||
|
|
|
@ -175,6 +175,8 @@ type SearchRepoOptions struct {
|
|||
// True -> include just forks
|
||||
// False -> include just non-forks
|
||||
Fork optional.Option[bool]
|
||||
// If Fork option is True, you can use this option to limit the forks of a special repo by repo id.
|
||||
ForkFrom int64
|
||||
// None -> include templates AND non-templates
|
||||
// True -> include just templates
|
||||
// False -> include just non-templates
|
||||
|
@ -514,6 +516,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
|||
cond = cond.And(builder.Eq{"is_fork": false})
|
||||
} else {
|
||||
cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()})
|
||||
|
||||
if opts.ForkFrom > 0 && opts.Fork.Value() {
|
||||
cond = cond.And(builder.Eq{"fork_id": opts.ForkFrom})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -138,12 +138,12 @@ func getTestCases() []struct {
|
|||
{
|
||||
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)},
|
||||
count: 33,
|
||||
count: 34,
|
||||
},
|
||||
{
|
||||
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)},
|
||||
count: 38,
|
||||
count: 39,
|
||||
},
|
||||
{
|
||||
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||
|
@ -158,7 +158,7 @@ func getTestCases() []struct {
|
|||
{
|
||||
name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)},
|
||||
count: 33,
|
||||
count: 34,
|
||||
},
|
||||
{
|
||||
name: "AllTemplates",
|
||||
|
|
|
@ -8,14 +8,14 @@ import "code.gitea.io/gitea/models/db"
|
|||
// SearchOrderByMap represents all possible search order
|
||||
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||
"asc": {
|
||||
"alpha": db.SearchOrderByAlphabetically,
|
||||
"alpha": "owner_name ASC, name ASC",
|
||||
"created": db.SearchOrderByOldest,
|
||||
"updated": db.SearchOrderByLeastUpdated,
|
||||
"size": db.SearchOrderBySize,
|
||||
"id": db.SearchOrderByID,
|
||||
},
|
||||
"desc": {
|
||||
"alpha": db.SearchOrderByAlphabeticallyReverse,
|
||||
"alpha": "owner_name DESC, name DESC",
|
||||
"created": db.SearchOrderByNewest,
|
||||
"updated": db.SearchOrderByRecentUpdated,
|
||||
"size": db.SearchOrderBySizeReverse,
|
||||
|
|
|
@ -9,11 +9,11 @@ import (
|
|||
"fmt"
|
||||
"image/png"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/avatars"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/avatar"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
@ -89,13 +89,9 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
|||
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
||||
}
|
||||
|
||||
// AvatarLink returns the full avatar link with http host
|
||||
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||
func (u *User) AvatarLink(ctx context.Context) string {
|
||||
link := u.AvatarLinkWithSize(ctx, 0)
|
||||
if !strings.HasPrefix(link, "//") && !strings.Contains(link, "://") {
|
||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL+"/")
|
||||
}
|
||||
return link
|
||||
return httplib.MakeAbsoluteURL(ctx, u.AvatarLinkWithSize(ctx, 0))
|
||||
}
|
||||
|
||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
package pwn
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/h2non/gock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -18,86 +17,34 @@ var client = New(WithHTTP(&http.Client{
|
|||
}))
|
||||
|
||||
func TestPassword(t *testing.T) {
|
||||
// Check input error
|
||||
_, err := client.CheckPassword("", false)
|
||||
defer gock.Off()
|
||||
|
||||
count, err := client.CheckPassword("", false)
|
||||
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
|
||||
assert.Equal(t, -1, count)
|
||||
|
||||
// Should fail
|
||||
fail := "password1234"
|
||||
count, err := client.CheckPassword(fail, false)
|
||||
assert.NotEmpty(t, count, "%s should fail as a password", fail)
|
||||
gock.New("https://api.pwnedpasswords.com").Get("/range/5c1d8").Times(1).Reply(200).BodyString("EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2")
|
||||
count, err = client.CheckPassword("pwned", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
// Should fail (with padding)
|
||||
failPad := "administrator"
|
||||
count, err = client.CheckPassword(failPad, true)
|
||||
assert.NotEmpty(t, count, "%s should fail as a password", failPad)
|
||||
gock.New("https://api.pwnedpasswords.com").Get("/range/ba189").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4")
|
||||
count, err = client.CheckPassword("notpwned", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
|
||||
// Checking for a "good" password isn't going to be perfect, but we can give it a good try
|
||||
// with hopefully minimal error. Try five times?
|
||||
assert.Condition(t, func() bool {
|
||||
for i := 0; i <= 5; i++ {
|
||||
count, err = client.CheckPassword(testPassword(), false)
|
||||
assert.NoError(t, err)
|
||||
if count == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, "no generated passwords passed. there is a chance this is a fluke")
|
||||
gock.New("https://api.pwnedpasswords.com").Get("/range/a1733").Times(1).Reply(200).BodyString("C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0")
|
||||
count, err = client.CheckPassword("paddedpwned", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
// Again, but with padded responses
|
||||
assert.Condition(t, func() bool {
|
||||
for i := 0; i <= 5; i++ {
|
||||
count, err = client.CheckPassword(testPassword(), true)
|
||||
assert.NoError(t, err)
|
||||
if count == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, "no generated passwords passed. there is a chance this is a fluke")
|
||||
}
|
||||
|
||||
// Credit to https://golangbyexample.com/generate-random-password-golang/
|
||||
// DO NOT USE THIS FOR AN ACTUAL PASSWORD GENERATOR
|
||||
var (
|
||||
lowerCharSet = "abcdedfghijklmnopqrst"
|
||||
upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
specialCharSet = "!@#$%&*"
|
||||
numberSet = "0123456789"
|
||||
allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet
|
||||
)
|
||||
|
||||
func testPassword() string {
|
||||
var password strings.Builder
|
||||
|
||||
// Set special character
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.IntN(len(specialCharSet))
|
||||
password.WriteString(string(specialCharSet[random]))
|
||||
}
|
||||
|
||||
// Set numeric
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.IntN(len(numberSet))
|
||||
password.WriteString(string(numberSet[random]))
|
||||
}
|
||||
|
||||
// Set uppercase
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.IntN(len(upperCharSet))
|
||||
password.WriteString(string(upperCharSet[random]))
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
random := rand.IntN(len(allCharSet))
|
||||
password.WriteString(string(allCharSet[random]))
|
||||
}
|
||||
inRune := []rune(password.String())
|
||||
rand.Shuffle(len(inRune), func(i, j int) {
|
||||
inRune[i], inRune[j] = inRune[j], inRune[i]
|
||||
})
|
||||
return string(inRune)
|
||||
gock.New("https://api.pwnedpasswords.com").Get("/range/5617b").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0")
|
||||
count, err = client.CheckPassword("paddednotpwned", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
|
||||
gock.New("https://api.pwnedpasswords.com").Get("/range/79082").Times(1).Reply(200).BodyString("FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0")
|
||||
count, err = client.CheckPassword("paddednotpwnedzero", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ func (r *BlameReader) Close() error {
|
|||
// CreateBlameReader creates reader for given repository, commit and file
|
||||
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
||||
var ignoreRevsFile *string
|
||||
if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
|
||||
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
|
||||
}
|
||||
|
||||
|
|
|
@ -423,7 +423,7 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
|||
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||
func (c *Commit) GetBranchName() (string, error) {
|
||||
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
||||
if CheckGitVersionAtLeast("2.13.0") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
|
||||
cmd.AddArguments("--exclude", "refs/tags/*")
|
||||
}
|
||||
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
|
||||
|
|
|
@ -22,42 +22,63 @@ import (
|
|||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// RequiredVersion is the minimum Git version required
|
||||
const RequiredVersion = "2.0.0"
|
||||
const RequiredVersion = "2.0.0" // the minimum Git version required
|
||||
|
||||
type Features struct {
|
||||
gitVersion *version.Version
|
||||
|
||||
UsingGogit bool
|
||||
SupportProcReceive bool // >= 2.29
|
||||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||||
SupportedObjectFormats []ObjectFormat // sha1, sha256
|
||||
}
|
||||
|
||||
var (
|
||||
// GitExecutable is the command name of git
|
||||
// Could be updated to an absolute path while initialization
|
||||
GitExecutable = "git"
|
||||
|
||||
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
|
||||
DefaultContext context.Context
|
||||
|
||||
DefaultFeatures struct {
|
||||
GitVersion *version.Version
|
||||
|
||||
SupportProcReceive bool // >= 2.29
|
||||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||||
}
|
||||
GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
|
||||
DefaultContext context.Context // the default context to run git commands in, must be initialized by git.InitXxx
|
||||
defaultFeatures *Features
|
||||
)
|
||||
|
||||
// loadGitVersion tries to get the current git version and stores it into a global variable
|
||||
func loadGitVersion() error {
|
||||
// doesn't need RWMutex because it's executed by Init()
|
||||
if DefaultFeatures.GitVersion != nil {
|
||||
return nil
|
||||
}
|
||||
func (f *Features) CheckVersionAtLeast(atLeast string) bool {
|
||||
return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
|
||||
}
|
||||
|
||||
// VersionInfo returns git version information
|
||||
func (f *Features) VersionInfo() string {
|
||||
return f.gitVersion.Original()
|
||||
}
|
||||
|
||||
func DefaultFeatures() *Features {
|
||||
if defaultFeatures == nil {
|
||||
if !setting.IsProd || setting.IsInTesting {
|
||||
log.Warn("git.DefaultFeatures is called before git.InitXxx, initializing with default values")
|
||||
}
|
||||
if err := InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git.InitSimple failed: %v", err)
|
||||
}
|
||||
}
|
||||
return defaultFeatures
|
||||
}
|
||||
|
||||
func loadGitVersionFeatures() (*Features, error) {
|
||||
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
|
||||
if runErr != nil {
|
||||
return runErr
|
||||
return nil, runErr
|
||||
}
|
||||
|
||||
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
|
||||
if err == nil {
|
||||
DefaultFeatures.GitVersion = ver
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return err
|
||||
|
||||
features := &Features{gitVersion: ver, UsingGogit: isGogit}
|
||||
features.SupportProcReceive = features.CheckVersionAtLeast("2.29")
|
||||
features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") && !isGogit
|
||||
features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat}
|
||||
if features.SupportHashSha256 {
|
||||
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
|
||||
}
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func parseGitVersionLine(s string) (*version.Version, error) {
|
||||
|
@ -85,56 +106,24 @@ func SetExecutablePath(path string) error {
|
|||
return fmt.Errorf("git not found: %w", err)
|
||||
}
|
||||
GitExecutable = absPath
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = loadGitVersion(); err != nil {
|
||||
return fmt.Errorf("unable to load git version: %w", err)
|
||||
}
|
||||
|
||||
versionRequired, err := version.NewVersion(RequiredVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if DefaultFeatures.GitVersion.LessThan(versionRequired) {
|
||||
func ensureGitVersion() error {
|
||||
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
||||
moreHint := "get git: https://git-scm.com/download/"
|
||||
if runtime.GOOS == "linux" {
|
||||
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
||||
if _, err = os.Stat("/etc/redhat-release"); err == nil {
|
||||
if _, err := os.Stat("/etc/redhat-release"); err == nil {
|
||||
// ius.io is the recommended official(git-scm.com) method to install git
|
||||
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures.GitVersion.Original(), RequiredVersion, moreHint)
|
||||
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)
|
||||
}
|
||||
|
||||
if err = checkGitVersionCompatibility(DefaultFeatures.GitVersion); err != nil {
|
||||
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures.GitVersion.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VersionInfo returns git version information
|
||||
func VersionInfo() string {
|
||||
if DefaultFeatures.GitVersion == nil {
|
||||
return "(git not found)"
|
||||
}
|
||||
format := "%s"
|
||||
args := []any{DefaultFeatures.GitVersion.Original()}
|
||||
// Since git wire protocol has been released from git v2.18
|
||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
||||
format += ", Wire Protocol %s Enabled"
|
||||
args = append(args, "Version 2") // for focus color
|
||||
}
|
||||
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func checkInit() error {
|
||||
if setting.Git.HomePath == "" {
|
||||
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||||
}
|
||||
if DefaultContext != nil {
|
||||
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
|
||||
if err := checkGitVersionCompatibility(DefaultFeatures().gitVersion); err != nil {
|
||||
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures().gitVersion.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -154,8 +143,12 @@ func HomeDir() string {
|
|||
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
||||
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
|
||||
func InitSimple(ctx context.Context) error {
|
||||
if err := checkInit(); err != nil {
|
||||
return err
|
||||
if setting.Git.HomePath == "" {
|
||||
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||||
}
|
||||
|
||||
if DefaultContext != nil && (!setting.IsProd || setting.IsInTesting) {
|
||||
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
|
||||
}
|
||||
|
||||
DefaultContext = ctx
|
||||
|
@ -165,7 +158,24 @@ func InitSimple(ctx context.Context) error {
|
|||
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
|
||||
}
|
||||
|
||||
return SetExecutablePath(setting.Git.Path)
|
||||
if err := SetExecutablePath(setting.Git.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
defaultFeatures, err = loadGitVersionFeatures()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ensureGitVersion(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
||||
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
||||
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitFull initializes git module with version check and change global variables, sync gitconfig.
|
||||
|
@ -175,30 +185,18 @@ func InitFull(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
||||
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
||||
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
|
||||
}
|
||||
|
||||
// Since git wire protocol has been released from git v2.18
|
||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
||||
if setting.Git.EnableAutoGitWireProtocol && DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||
}
|
||||
|
||||
// Explicitly disable credential helper, otherwise Git credentials might leak
|
||||
if CheckGitVersionAtLeast("2.9") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.9") {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
||||
}
|
||||
DefaultFeatures.SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
|
||||
DefaultFeatures.SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit
|
||||
if DefaultFeatures.SupportHashSha256 {
|
||||
SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat)
|
||||
} else {
|
||||
log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported")
|
||||
}
|
||||
|
||||
if setting.LFS.StartServer {
|
||||
if CheckGitVersionAtLeast("2.1.2") != nil {
|
||||
if !DefaultFeatures().CheckVersionAtLeast("2.1.2") {
|
||||
return errors.New("LFS server support requires Git >= 2.1.2")
|
||||
}
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
|
||||
|
@ -238,13 +236,13 @@ func syncGitConfig() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
if CheckGitVersionAtLeast("2.10") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
||||
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if CheckGitVersionAtLeast("2.18") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||
if err := configSet("core.commitGraph", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -256,7 +254,7 @@ func syncGitConfig() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if DefaultFeatures.SupportProcReceive {
|
||||
if DefaultFeatures().SupportProcReceive {
|
||||
// set support for AGit flow
|
||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||
return err
|
||||
|
@ -294,7 +292,7 @@ func syncGitConfig() (err error) {
|
|||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
|
||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -309,21 +307,6 @@ func syncGitConfig() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// CheckGitVersionAtLeast check git version is at least the constraint version
|
||||
func CheckGitVersionAtLeast(atLeast string) error {
|
||||
if DefaultFeatures.GitVersion == nil {
|
||||
panic("git module is not initialized") // it shouldn't happen
|
||||
}
|
||||
atLeastVersion, err := version.NewVersion(atLeast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if DefaultFeatures.GitVersion.Compare(atLeastVersion) < 0 {
|
||||
return fmt.Errorf("installed git binary version %s is not at least %s", DefaultFeatures.GitVersion.Original(), atLeast)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
||||
badVersions := []struct {
|
||||
Version *version.Version
|
||||
|
|
|
@ -29,6 +29,7 @@ type GrepOptions struct {
|
|||
ContextLineNumber int
|
||||
IsFuzzy bool
|
||||
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
|
||||
PathspecList []string
|
||||
}
|
||||
|
||||
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
|
||||
|
@ -62,6 +63,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
|||
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
||||
}
|
||||
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
||||
cmd.AddDashesAndList(opts.PathspecList...)
|
||||
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
||||
stderr := bytes.Buffer{}
|
||||
err = cmd.Run(&RunOpts{
|
||||
|
|
|
@ -31,6 +31,26 @@ func TestGrepSearch(t *testing.T) {
|
|||
},
|
||||
}, res)
|
||||
|
||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*GrepResult{
|
||||
{
|
||||
Filename: "java-hello/main.java",
|
||||
LineNumbers: []int{3},
|
||||
LineCodes: []string{" public static void main(String[] args)"},
|
||||
},
|
||||
}, res)
|
||||
|
||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*GrepResult{
|
||||
{
|
||||
Filename: "main.vendor.java",
|
||||
LineNumbers: []int{3},
|
||||
LineCodes: []string{" public static void main(String[] args)"},
|
||||
},
|
||||
}, res)
|
||||
|
||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*GrepResult{
|
||||
|
|
|
@ -120,12 +120,8 @@ var (
|
|||
Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
|
||||
)
|
||||
|
||||
var SupportedObjectFormats = []ObjectFormat{
|
||||
Sha1ObjectFormat,
|
||||
}
|
||||
|
||||
func ObjectFormatFromName(name string) ObjectFormat {
|
||||
for _, objectFormat := range SupportedObjectFormats {
|
||||
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||
if name == objectFormat.Name() {
|
||||
return objectFormat
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
|
|||
|
||||
func NewIDFromString(hexHash string) (ObjectID, error) {
|
||||
var theObjectFormat ObjectFormat
|
||||
for _, objectFormat := range SupportedObjectFormats {
|
||||
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||
if len(hexHash) == objectFormat.FullLength() {
|
||||
theObjectFormat = objectFormat
|
||||
break
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
||||
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
||||
var cmd *Command
|
||||
if CheckGitVersionAtLeast("2.7") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.7") {
|
||||
cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
|
||||
} else {
|
||||
cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
|
||||
|
|
|
@ -7,7 +7,6 @@ package git
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
@ -63,32 +62,6 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
// GetObjectFormatOfRepo returns the hash type of repository at a given path
|
||||
func GetObjectFormatOfRepo(ctx context.Context, repoPath string) (ObjectFormat, error) {
|
||||
var stdout, stderr strings.Builder
|
||||
|
||||
err := NewCommand(ctx, "hash-object", "--stdin").Run(&RunOpts{
|
||||
Dir: repoPath,
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Stdin: &strings.Reader{},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stderr.Len() > 0 {
|
||||
return nil, errors.New(stderr.String())
|
||||
}
|
||||
|
||||
h, err := NewIDFromString(strings.TrimRight(stdout.String(), "\n"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return h.Type(), nil
|
||||
}
|
||||
|
||||
// InitRepository initializes a new Git repository.
|
||||
func InitRepository(ctx context.Context, repoPath string, bare bool, objectFormatName string) error {
|
||||
err := os.MkdirAll(repoPath, os.ModePerm)
|
||||
|
@ -101,7 +74,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
|
|||
if !IsValidObjectFormat(objectFormatName) {
|
||||
return fmt.Errorf("invalid object format: %s", objectFormatName)
|
||||
}
|
||||
if DefaultFeatures.SupportHashSha256 {
|
||||
if DefaultFeatures().SupportHashSha256 {
|
||||
cmd.AddOptionValues("--object-format", objectFormatName)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
var isGogit bool
|
|
@ -22,9 +22,7 @@ import (
|
|||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
)
|
||||
|
||||
func init() {
|
||||
isGogit = true
|
||||
}
|
||||
const isGogit = true
|
||||
|
||||
// Repository represents a Git repository.
|
||||
type Repository struct {
|
||||
|
|
|
@ -15,9 +15,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
isGogit = false
|
||||
}
|
||||
const isGogit = false
|
||||
|
||||
// Repository represents a Git repository.
|
||||
type Repository struct {
|
||||
|
|
|
@ -438,7 +438,7 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
|
|||
}
|
||||
|
||||
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
|
||||
if CheckGitVersionAtLeast("2.7.0") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.7.0") {
|
||||
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").
|
||||
AddOptionFormat("--count=%d", limit).
|
||||
AddOptionValues("--contains", commit.ID.String(), BranchPrefix).
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
// WriteCommitGraph write commit graph to speed up repo access
|
||||
// this requires git v2.18 to be installed
|
||||
func WriteCommitGraph(ctx context.Context, repoPath string) error {
|
||||
if CheckGitVersionAtLeast("2.18") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||
if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil {
|
||||
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package httplib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
|
@ -11,6 +13,10 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type RequestContextKeyStruct struct{}
|
||||
|
||||
var RequestContextKey = RequestContextKeyStruct{}
|
||||
|
||||
func urlIsRelative(s string, u *url.URL) bool {
|
||||
// Unfortunately browsers consider a redirect Location with preceding "//", "\\", "/\" and "\/" as meaning redirect to "http(s)://REST_OF_PATH"
|
||||
// Therefore we should ignore these redirect locations to prevent open redirects
|
||||
|
@ -26,7 +32,56 @@ func IsRelativeURL(s string) bool {
|
|||
return err == nil && urlIsRelative(s, u)
|
||||
}
|
||||
|
||||
func IsCurrentGiteaSiteURL(s string) bool {
|
||||
func guessRequestScheme(req *http.Request, def string) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||
if s := req.Header.Get("X-Forwarded-Proto"); s != "" {
|
||||
return s
|
||||
}
|
||||
if s := req.Header.Get("X-Forwarded-Protocol"); s != "" {
|
||||
return s
|
||||
}
|
||||
if s := req.Header.Get("X-Url-Scheme"); s != "" {
|
||||
return s
|
||||
}
|
||||
if s := req.Header.Get("Front-End-Https"); s != "" {
|
||||
return util.Iif(s == "on", "https", "http")
|
||||
}
|
||||
if s := req.Header.Get("X-Forwarded-Ssl"); s != "" {
|
||||
return util.Iif(s == "on", "https", "http")
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func guessForwardedHost(req *http.Request) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
|
||||
return req.Header.Get("X-Forwarded-Host")
|
||||
}
|
||||
|
||||
// GuessCurrentAppURL tries to guess the current full URL by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
||||
func GuessCurrentAppURL(ctx context.Context) string {
|
||||
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
||||
if !ok {
|
||||
return setting.AppURL
|
||||
}
|
||||
if host := guessForwardedHost(req); host != "" {
|
||||
// if it is behind a reverse proxy, use "https" as default scheme in case the site admin forgets to set the correct forwarded-protocol headers
|
||||
return guessRequestScheme(req, "https") + "://" + host + setting.AppSubURL + "/"
|
||||
} else if req.Host != "" {
|
||||
// if it is not behind a reverse proxy, use the scheme from config options, meanwhile use "https" as much as possible
|
||||
defaultScheme := util.Iif(setting.Protocol == "http", "http", "https")
|
||||
return guessRequestScheme(req, defaultScheme) + "://" + req.Host + setting.AppSubURL + "/"
|
||||
}
|
||||
return setting.AppURL
|
||||
}
|
||||
|
||||
func MakeAbsoluteURL(ctx context.Context, s string) string {
|
||||
if IsRelativeURL(s) {
|
||||
return GuessCurrentAppURL(ctx) + strings.TrimPrefix(s, "/")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -45,5 +100,6 @@ func IsCurrentGiteaSiteURL(s string) bool {
|
|||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
return strings.HasPrefix(strings.ToLower(u.String()), strings.ToLower(setting.AppURL))
|
||||
urlLower := strings.ToLower(u.String())
|
||||
return strings.HasPrefix(urlLower, strings.ToLower(setting.AppURL)) || strings.HasPrefix(urlLower, strings.ToLower(GuessCurrentAppURL(ctx)))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package httplib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -37,9 +39,44 @@ func TestIsRelativeURL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMakeAbsoluteURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Protocol, "http")()
|
||||
defer test.MockVariableValue(&setting.AppURL, "http://the-host/sub/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
|
||||
ctx := context.Background()
|
||||
assert.Equal(t, "http://the-host/sub/", MakeAbsoluteURL(ctx, ""))
|
||||
assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
|
||||
assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
})
|
||||
assert.Equal(t, "http://user-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-Host": {"forwarded-host"},
|
||||
},
|
||||
})
|
||||
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-Host": {"forwarded-host"},
|
||||
"X-Forwarded-Proto": {"https"},
|
||||
},
|
||||
})
|
||||
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
}
|
||||
|
||||
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
ctx := context.Background()
|
||||
good := []string{
|
||||
"?key=val",
|
||||
"/sub",
|
||||
|
@ -50,7 +87,7 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
|||
"http://localhost:3000/sub/",
|
||||
}
|
||||
for _, s := range good {
|
||||
assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s)
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, s), "good = %q", s)
|
||||
}
|
||||
bad := []string{
|
||||
".",
|
||||
|
@ -64,13 +101,23 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
|||
"http://other/",
|
||||
}
|
||||
for _, s := range bad {
|
||||
assert.False(t, IsCurrentGiteaSiteURL(s), "bad = %q", s)
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, s), "bad = %q", s)
|
||||
}
|
||||
|
||||
setting.AppURL = "http://localhost:3000/"
|
||||
setting.AppSubURL = ""
|
||||
assert.False(t, IsCurrentGiteaSiteURL("//"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL("\\\\"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL("http://localhost"))
|
||||
assert.True(t, IsCurrentGiteaSiteURL("http://localhost:3000?key=val"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, "//"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, "\\\\"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, "http://localhost"))
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, "http://localhost:3000?key=val"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-Host": {"forwarded-host"},
|
||||
"X-Forwarded-Proto": {"https"},
|
||||
},
|
||||
})
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, "http://localhost:3000"))
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, "https://forwarded-host"))
|
||||
}
|
||||
|
|
|
@ -215,7 +215,7 @@ func searchIssueIsPull(t *testing.T) {
|
|||
SearchOptions{
|
||||
IsPull: optional.Some(true),
|
||||
},
|
||||
[]int64{22, 21, 12, 11, 20, 19, 9, 8, 3, 2},
|
||||
[]int64{22, 21, 12, 11, 20, 28, 27, 26, 25, 24, 23, 19, 9, 8, 3, 2},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -236,13 +236,13 @@ func searchIssueIsClosed(t *testing.T) {
|
|||
SearchOptions{
|
||||
IsClosed: optional.Some(false),
|
||||
},
|
||||
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1},
|
||||
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 28, 27, 25, 23, 19, 18, 10, 7, 9, 8, 3, 2, 1},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
IsClosed: optional.Some(true),
|
||||
},
|
||||
[]int64{5, 4},
|
||||
[]int64{5, 26, 24, 4},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -302,7 +302,7 @@ func searchIssueByLabelID(t *testing.T) {
|
|||
SearchOptions{
|
||||
ExcludedLabelIDs: []int64{1},
|
||||
},
|
||||
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3},
|
||||
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 28, 27, 26, 25, 24, 23, 19, 18, 10, 7, 4, 9, 8, 3},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -323,7 +323,7 @@ func searchIssueByTime(t *testing.T) {
|
|||
SearchOptions{
|
||||
UpdatedAfterUnix: optional.Some(int64(0)),
|
||||
},
|
||||
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
|
||||
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 28, 27, 26, 25, 24, 23, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -344,7 +344,7 @@ func searchIssueWithOrder(t *testing.T) {
|
|||
SearchOptions{
|
||||
SortBy: internal.SortByCreatedAsc,
|
||||
},
|
||||
[]int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17, 21, 22},
|
||||
[]int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 23, 24, 25, 26, 27, 28, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17, 21, 22},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -402,7 +402,7 @@ func searchIssueWithPaginator(t *testing.T) {
|
|||
},
|
||||
},
|
||||
[]int64{22, 21, 17, 16, 15},
|
||||
22,
|
||||
28,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -41,7 +41,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c
|
|||
go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
|
||||
|
||||
// 1. Run batch-check on all objects in the repository
|
||||
if git.CheckGitVersionAtLeast("2.6.0") != nil {
|
||||
if !git.DefaultFeatures().CheckVersionAtLeast("2.6.0") {
|
||||
revListReader, revListWriter := io.Pipe()
|
||||
shasToCheckReader, shasToCheckWriter := io.Pipe()
|
||||
wg.Add(2)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -54,7 +55,7 @@ var (
|
|||
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
||||
|
||||
// anyHashPattern splits url containing SHA into parts
|
||||
anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
|
||||
anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?`)
|
||||
|
||||
// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
|
||||
comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`)
|
||||
|
@ -591,7 +592,8 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) {
|
|||
|
||||
func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
||||
start := 0
|
||||
for node != nil {
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
found, loc := references.FindFirstMentionBytes(util.UnsafeStringToBytes(node.Data[start:]))
|
||||
if !found {
|
||||
node = node.NextSibling
|
||||
|
@ -962,57 +964,68 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
}
|
||||
}
|
||||
|
||||
type anyHashPatternResult struct {
|
||||
PosStart int
|
||||
PosEnd int
|
||||
FullURL string
|
||||
CommitID string
|
||||
SubPath string
|
||||
QueryHash string
|
||||
}
|
||||
|
||||
func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
|
||||
m := anyHashPattern.FindStringSubmatchIndex(s)
|
||||
if m == nil {
|
||||
return ret, false
|
||||
}
|
||||
|
||||
ret.PosStart, ret.PosEnd = m[0], m[1]
|
||||
ret.FullURL = s[ret.PosStart:ret.PosEnd]
|
||||
if strings.HasSuffix(ret.FullURL, ".") {
|
||||
// if url ends in '.', it's very likely that it is not part of the actual url but used to finish a sentence.
|
||||
ret.PosEnd--
|
||||
ret.FullURL = ret.FullURL[:len(ret.FullURL)-1]
|
||||
for i := 0; i < len(m); i++ {
|
||||
m[i] = min(m[i], ret.PosEnd)
|
||||
}
|
||||
}
|
||||
|
||||
ret.CommitID = s[m[2]:m[3]]
|
||||
if m[5] > 0 {
|
||||
ret.SubPath = s[m[4]:m[5]]
|
||||
}
|
||||
|
||||
lastStart, lastEnd := m[len(m)-2], m[len(m)-1]
|
||||
if lastEnd > 0 {
|
||||
ret.QueryHash = s[lastStart:lastEnd][1:]
|
||||
}
|
||||
return ret, true
|
||||
}
|
||||
|
||||
// fullHashPatternProcessor renders SHA containing URLs
|
||||
func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
if ctx.Metas == nil {
|
||||
return
|
||||
}
|
||||
|
||||
next := node.NextSibling
|
||||
for node != nil && node != next {
|
||||
m := anyHashPattern.FindStringSubmatchIndex(node.Data)
|
||||
if m == nil {
|
||||
return
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
if node.Type != html.TextNode {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
urlFull := node.Data[m[0]:m[1]]
|
||||
text := base.ShortSha(node.Data[m[2]:m[3]])
|
||||
|
||||
// 3rd capture group matches a optional path
|
||||
subpath := ""
|
||||
if m[5] > 0 {
|
||||
subpath = node.Data[m[4]:m[5]]
|
||||
ret, ok := anyHashPatternExtract(node.Data)
|
||||
if !ok {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
// 4th capture group matches a optional url hash
|
||||
hash := ""
|
||||
if m[7] > 0 {
|
||||
hash = node.Data[m[6]:m[7]][1:]
|
||||
text := base.ShortSha(ret.CommitID)
|
||||
if ret.SubPath != "" {
|
||||
text += ret.SubPath
|
||||
}
|
||||
|
||||
start := m[0]
|
||||
end := m[1]
|
||||
|
||||
// If url ends in '.', it's very likely that it is not part of the
|
||||
// actual url but used to finish a sentence.
|
||||
if strings.HasSuffix(urlFull, ".") {
|
||||
end--
|
||||
urlFull = urlFull[:len(urlFull)-1]
|
||||
if hash != "" {
|
||||
hash = hash[:len(hash)-1]
|
||||
} else if subpath != "" {
|
||||
subpath = subpath[:len(subpath)-1]
|
||||
}
|
||||
if ret.QueryHash != "" {
|
||||
text += " (" + ret.QueryHash + ")"
|
||||
}
|
||||
|
||||
if subpath != "" {
|
||||
text += subpath
|
||||
}
|
||||
|
||||
if hash != "" {
|
||||
text += " (" + hash + ")"
|
||||
}
|
||||
replaceContent(node, start, end, createCodeLink(urlFull, text, "commit"))
|
||||
replaceContent(node, ret.PosStart, ret.PosEnd, createCodeLink(ret.FullURL, text, "commit"))
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
|
@ -1021,19 +1034,16 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
if ctx.Metas == nil {
|
||||
return
|
||||
}
|
||||
|
||||
next := node.NextSibling
|
||||
for node != nil && node != next {
|
||||
m := comparePattern.FindStringSubmatchIndex(node.Data)
|
||||
if m == nil {
|
||||
return
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
if node.Type != html.TextNode {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure that every group (m[0]...m[7]) has a match
|
||||
for i := 0; i < 8; i++ {
|
||||
if m[i] == -1 {
|
||||
return
|
||||
}
|
||||
m := comparePattern.FindStringSubmatchIndex(node.Data)
|
||||
if m == nil || slices.Contains(m[:8], -1) { // ensure that every group (m[0]...m[7]) has a match
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
urlFull := node.Data[m[0]:m[1]]
|
||||
|
|
|
@ -42,7 +42,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
|
|||
CommitID: node.Data[m[6]:m[7]],
|
||||
FilePath: node.Data[m[8]:m[9]],
|
||||
}
|
||||
if !httplib.IsCurrentGiteaSiteURL(opts.FullURL) {
|
||||
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, opts.FullURL) {
|
||||
return 0, 0, "", nil
|
||||
}
|
||||
u, err := url.Parse(opts.FilePath)
|
||||
|
@ -60,7 +60,8 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
|
|||
}
|
||||
|
||||
func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
for node != nil {
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
if node.Type != html.TextNode {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
|
|
|
@ -399,36 +399,61 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRegExp_anySHA1Pattern(t *testing.T) {
|
||||
testCases := map[string][]string{
|
||||
testCases := map[string]anyHashPatternResult{
|
||||
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": {
|
||||
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
"/test/unit/event.js",
|
||||
"#L2703",
|
||||
CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
SubPath: "/test/unit/event.js",
|
||||
QueryHash: "L2703",
|
||||
},
|
||||
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": {
|
||||
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
"/test/unit/event.js",
|
||||
"",
|
||||
CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
SubPath: "/test/unit/event.js",
|
||||
},
|
||||
"https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": {
|
||||
"0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
"",
|
||||
"",
|
||||
CommitID: "0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
},
|
||||
"https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": {
|
||||
"0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
"/src",
|
||||
"",
|
||||
CommitID: "0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
SubPath: "/src",
|
||||
},
|
||||
"https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": {
|
||||
"d8a994ef243349f321568f9e36d5c3f444b99cae",
|
||||
"",
|
||||
"#diff-2",
|
||||
CommitID: "d8a994ef243349f321568f9e36d5c3f444b99cae",
|
||||
QueryHash: "diff-2",
|
||||
},
|
||||
"non-url": {},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b#L1-L2": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
QueryHash: "L1-L2",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678/sub.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
SubPath: "/sub",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b&c=d": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678#hash.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
QueryHash: "hash",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
assert.Equal(t, anyHashPattern.FindStringSubmatch(k)[1:], v)
|
||||
ret, ok := anyHashPatternExtract(k)
|
||||
if v.CommitID == "" {
|
||||
assert.False(t, ok)
|
||||
} else {
|
||||
assert.EqualValues(t, strings.TrimSuffix(k, "."), ret.FullURL)
|
||||
assert.EqualValues(t, v.CommitID, ret.CommitID)
|
||||
assert.EqualValues(t, v.SubPath, ret.SubPath)
|
||||
assert.EqualValues(t, v.QueryHash, ret.QueryHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,6 +124,11 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||
test(
|
||||
util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
|
||||
`<p><a href="`+util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
|
||||
|
||||
inputURL := "https://host/a/b/commit/0123456789012345678901234567890123456789/foo.txt?a=b#L2-L3"
|
||||
test(
|
||||
inputURL,
|
||||
`<p><a href="`+inputURL+`" rel="nofollow"><code>0123456789/foo.txt (L2-L3)</code></a></p>`)
|
||||
}
|
||||
|
||||
func TestMisc_IsSameDomain(t *testing.T) {
|
||||
|
@ -695,7 +700,7 @@ func TestIssue18471(t *testing.T) {
|
|||
}, strings.NewReader(data), &res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>", res.String())
|
||||
assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code class="nohighlight">783b039...da951ce</code></a>`, res.String())
|
||||
}
|
||||
|
||||
func TestIsFullURL(t *testing.T) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
|
@ -54,6 +55,7 @@ type HookOptions struct {
|
|||
GitQuarantinePath string
|
||||
GitPushOptions GitPushOptions
|
||||
PullRequestID int64
|
||||
PushTrigger repository.PushTrigger
|
||||
DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user.
|
||||
IsWiki bool
|
||||
ActionPerm int
|
||||
|
|
|
@ -5,6 +5,7 @@ package repository
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
|
@ -36,6 +37,15 @@ func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error)
|
|||
}
|
||||
|
||||
func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) {
|
||||
objFmt, err := gitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("GetObjectFormat: %w", err)
|
||||
}
|
||||
_, err = db.GetEngine(ctx).ID(repo.ID).Update(&repo_model.Repository{ObjectFormatName: objFmt.Name()})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("UpdateRepository: %w", err)
|
||||
}
|
||||
|
||||
allBranches := container.Set[string]{}
|
||||
{
|
||||
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSyncRepoBranches(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
_, err := db.GetEngine(db.DefaultContext).ID(1).Update(&repo_model.Repository{ObjectFormatName: "bad-fmt"})
|
||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &git_model.Branch{}))
|
||||
assert.NoError(t, err)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, "bad-fmt", repo.ObjectFormatName)
|
||||
_, err = SyncRepoBranches(db.DefaultContext, 1, 0)
|
||||
assert.NoError(t, err)
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, "sha1", repo.ObjectFormatName)
|
||||
branch, err := git_model.GetBranch(db.DefaultContext, 1, "master")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "master", branch.Name)
|
||||
}
|
|
@ -25,11 +25,19 @@ const (
|
|||
EnvKeyID = "GITEA_KEY_ID" // public key ID
|
||||
EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID"
|
||||
EnvPRID = "GITEA_PR_ID"
|
||||
EnvPushTrigger = "GITEA_PUSH_TRIGGER"
|
||||
EnvIsInternal = "GITEA_INTERNAL_PUSH"
|
||||
EnvAppURL = "GITEA_ROOT_URL"
|
||||
EnvActionPerm = "GITEA_ACTION_PERM"
|
||||
)
|
||||
|
||||
type PushTrigger string
|
||||
|
||||
const (
|
||||
PushTriggerPRMergeToBase PushTrigger = "pr-merge-to-base"
|
||||
PushTriggerPRUpdateWithBase PushTrigger = "pr-update-with-base"
|
||||
)
|
||||
|
||||
// InternalPushingEnvironment returns an os environment to switch off hooks on push
|
||||
// It is recommended to avoid using this unless you are pushing within a transaction
|
||||
// or if you absolutely are sure that post-receive and pre-receive will do nothing
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import "github.com/gobwas/glob"
|
||||
|
||||
type GlobMatcher struct {
|
||||
compiledGlob glob.Glob
|
||||
patternString string
|
||||
}
|
||||
|
||||
var _ glob.Glob = (*GlobMatcher)(nil)
|
||||
|
||||
func (g *GlobMatcher) Match(s string) bool {
|
||||
return g.compiledGlob.Match(s)
|
||||
}
|
||||
|
||||
func (g *GlobMatcher) PatternString() string {
|
||||
return g.patternString
|
||||
}
|
||||
|
||||
func GlobMatcherCompile(pattern string, separators ...rune) (*GlobMatcher, error) {
|
||||
g, err := glob.Compile(pattern, separators...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GlobMatcher{
|
||||
compiledGlob: g,
|
||||
patternString: pattern,
|
||||
}, nil
|
||||
}
|
|
@ -10,8 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// Indexer settings
|
||||
|
@ -30,8 +28,8 @@ var Indexer = struct {
|
|||
RepoConnStr string
|
||||
RepoIndexerName string
|
||||
MaxIndexerFileSize int64
|
||||
IncludePatterns []glob.Glob
|
||||
ExcludePatterns []glob.Glob
|
||||
IncludePatterns []*GlobMatcher
|
||||
ExcludePatterns []*GlobMatcher
|
||||
ExcludeVendored bool
|
||||
}{
|
||||
IssueType: "bleve",
|
||||
|
@ -93,12 +91,12 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
|
|||
}
|
||||
|
||||
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
|
||||
func IndexerGlobFromString(globstr string) []glob.Glob {
|
||||
extarr := make([]glob.Glob, 0, 10)
|
||||
func IndexerGlobFromString(globstr string) []*GlobMatcher {
|
||||
extarr := make([]*GlobMatcher, 0, 10)
|
||||
for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
|
||||
expr = strings.TrimSpace(expr)
|
||||
if expr != "" {
|
||||
if g, err := glob.Compile(expr, '.', '/'); err != nil {
|
||||
if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil {
|
||||
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
||||
} else {
|
||||
extarr = append(extarr, g)
|
||||
|
|
|
@ -85,7 +85,7 @@ type CreatePullRequestOption struct {
|
|||
// EditPullRequestOption options when modify pull request
|
||||
type EditPullRequestOption struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Body *string `json:"body"`
|
||||
Base string `json:"base"`
|
||||
Assignee string `json:"assignee"`
|
||||
Assignees []string `json:"assignees"`
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"@github/text-expander-element": "2.6.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.9.0",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||
"add-asset-webpack-plugin": "2.0.1",
|
||||
"ansi_up": "6.0.2",
|
||||
"asciinema-player": "3.7.1",
|
||||
|
@ -57,7 +58,6 @@
|
|||
"vue-bar-graph": "2.0.0",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue3-calendar-heatmap": "2.0.5",
|
||||
"webpack": "5.91.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"wrap-ansi": "9.0.0"
|
||||
|
@ -1626,6 +1626,18 @@
|
|||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@silverwind/vue3-calendar-heatmap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz",
|
||||
"integrity": "sha512-efX+nf2GR7EfA7iNgZDeM9Jue5ksglSXvN0C/ja0M1bTmkCpAxKlGJ3vki7wfTPQgX1O0nCfAM62IKqUUEM0cQ==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tippy.js": "^6.3.7",
|
||||
"vue": "^3.2.29"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
|
@ -12200,18 +12212,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue3-calendar-heatmap": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.5.tgz",
|
||||
"integrity": "sha512-qvveNQlTS5Aw7AvRLs0zOyu3uP5iGJlXJAnkrkG2ElDdyQ8H1TJhQ8rL702CROjAg16ezIveUY10nCO7lqZ25w==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tippy.js": "^6.3.7",
|
||||
"vue": "^3.2.29"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"@github/text-expander-element": "2.6.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.9.0",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||
"add-asset-webpack-plugin": "2.0.1",
|
||||
"ansi_up": "6.0.2",
|
||||
"asciinema-player": "3.7.1",
|
||||
|
@ -56,7 +57,6 @@
|
|||
"vue-bar-graph": "2.0.0",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue3-calendar-heatmap": "2.0.5",
|
||||
"webpack": "5.91.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"wrap-ansi": "9.0.0"
|
||||
|
|
|
@ -71,6 +71,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -184,8 +185,8 @@ type artifactRoutes struct {
|
|||
fs storage.ObjectStorage
|
||||
}
|
||||
|
||||
func (ar artifactRoutes) buildArtifactURL(runID int64, artifactHash, suffix string) string {
|
||||
uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ar.prefix, "/") +
|
||||
func (ar artifactRoutes) buildArtifactURL(ctx *ArtifactContext, runID int64, artifactHash, suffix string) string {
|
||||
uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(ar.prefix, "/") +
|
||||
strings.ReplaceAll(artifactRouteBase, "{run_id}", strconv.FormatInt(runID, 10)) +
|
||||
"/" + artifactHash + "/" + suffix
|
||||
return uploadURL
|
||||
|
@ -224,7 +225,7 @@ func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
|
|||
// use md5(artifact_name) to create upload url
|
||||
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name)))
|
||||
resp := getUploadArtifactResponse{
|
||||
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "upload"+retentionQuery),
|
||||
FileContainerResourceURL: ar.buildArtifactURL(ctx, runID, artifactHash, "upload"+retentionQuery),
|
||||
}
|
||||
log.Debug("[artifact] get upload url: %s", resp.FileContainerResourceURL)
|
||||
ctx.JSON(http.StatusOK, resp)
|
||||
|
@ -365,7 +366,7 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
|
|||
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(art.ArtifactName)))
|
||||
item := listArtifactsResponseItem{
|
||||
Name: art.ArtifactName,
|
||||
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "download_url"),
|
||||
FileContainerResourceURL: ar.buildArtifactURL(ctx, runID, artifactHash, "download_url"),
|
||||
}
|
||||
items = append(items, item)
|
||||
values[art.ArtifactName] = true
|
||||
|
@ -437,7 +438,7 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
|
|||
}
|
||||
}
|
||||
if downloadURL == "" {
|
||||
downloadURL = ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
|
||||
downloadURL = ar.buildArtifactURL(ctx, runID, strconv.FormatInt(artifact.ID, 10), "download")
|
||||
}
|
||||
item := downloadArtifactResponseItem{
|
||||
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
|
||||
|
|
|
@ -92,6 +92,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
@ -160,9 +161,9 @@ func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, tas
|
|||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
func (r artifactV4Routes) buildArtifactURL(endp, artifactName string, taskID int64) string {
|
||||
func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID int64) string {
|
||||
expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST")
|
||||
uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(r.prefix, "/") +
|
||||
uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") +
|
||||
"/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID)
|
||||
return uploadURL
|
||||
}
|
||||
|
@ -278,7 +279,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
|
|||
|
||||
respData := CreateArtifactResponse{
|
||||
Ok: true,
|
||||
SignedUploadUrl: r.buildArtifactURL("UploadArtifact", artifactName, ctx.ActionTask.ID),
|
||||
SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID),
|
||||
}
|
||||
r.sendProtbufBody(ctx, &respData)
|
||||
}
|
||||
|
@ -454,7 +455,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
|
|||
}
|
||||
}
|
||||
if respData.SignedUrl == "" {
|
||||
respData.SignedUrl = r.buildArtifactURL("DownloadArtifact", artifactName, ctx.ActionTask.ID)
|
||||
respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID)
|
||||
}
|
||||
r.sendProtbufBody(ctx, &respData)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
container_model "code.gitea.io/gitea/models/packages/container"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
|
@ -115,7 +116,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
|
|||
}
|
||||
|
||||
func apiUnauthorizedError(ctx *context.Context) {
|
||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
|
||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+httplib.GuessCurrentAppURL(ctx)+`v2/token",service="container_registry",scope="*"`)
|
||||
apiErrorDefined(ctx, errUnauthorized)
|
||||
}
|
||||
|
||||
|
|
|
@ -140,9 +140,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
|||
ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
|
||||
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
|
||||
|
||||
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
|
||||
log.Error("write bytes failed: %v", err)
|
||||
}
|
||||
_, _ = ctx.Resp.Write(xmlMetadataWithHeader)
|
||||
}
|
||||
|
||||
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
)
|
||||
|
||||
|
@ -517,3 +518,68 @@ type Action struct{}
|
|||
func NewAction() actions_service.API {
|
||||
return Action{}
|
||||
}
|
||||
|
||||
// ListActionTasks list all the actions of a repository
|
||||
func ListActionTasks(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/tasks repository ListActionTasks
|
||||
// ---
|
||||
// summary: List a repository's action tasks
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results, default maximum page size is 50
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/TasksList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "409":
|
||||
// "$ref": "#/responses/conflict"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ListActionTasks", err)
|
||||
return
|
||||
}
|
||||
|
||||
res := new(api.ActionTaskResponse)
|
||||
res.TotalCount = total
|
||||
|
||||
res.Entries = make([]*api.ActionTask, len(tasks))
|
||||
for i := range tasks {
|
||||
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ToActionTask", err)
|
||||
return
|
||||
}
|
||||
res.Entries[i] = convertedTask
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &res)
|
||||
}
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// ListActionTasks list all the actions of a repository
|
||||
func ListActionTasks(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/tasks repository ListActionTasks
|
||||
// ---
|
||||
// summary: List a repository's action tasks
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results, default maximum page size is 50
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/TasksList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "409":
|
||||
// "$ref": "#/responses/conflict"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ListActionTasks", err)
|
||||
return
|
||||
}
|
||||
|
||||
res := new(api.ActionTaskResponse)
|
||||
res.TotalCount = total
|
||||
|
||||
res.Entries = make([]*api.ActionTask, len(tasks))
|
||||
for i := range tasks {
|
||||
convertedTask, err := convert.ToActionTask(ctx, tasks[i])
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ToActionTask", err)
|
||||
return
|
||||
}
|
||||
res.Entries[i] = convertedTask
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &res)
|
||||
}
|
|
@ -29,7 +29,6 @@ import (
|
|||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
// SearchIssues searches for issues across the repositories that the user has access to
|
||||
|
@ -803,12 +802,19 @@ func EditIssue(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
oldTitle := issue.Title
|
||||
if len(form.Title) > 0 {
|
||||
issue.Title = form.Title
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if form.Body != nil {
|
||||
issue.Content = *form.Body
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if form.Ref != nil {
|
||||
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
|
||||
|
@ -880,24 +886,14 @@ func EditIssue(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
}
|
||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
||||
}
|
||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
|
||||
return
|
||||
}
|
||||
|
||||
if titleChanged {
|
||||
notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
|
||||
}
|
||||
|
||||
if statusChangeComment != nil {
|
||||
notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
|
||||
}
|
||||
|
||||
// Refetch from database to assign some automatic values
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/attachment"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
)
|
||||
|
@ -153,6 +154,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
|||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/error"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "423":
|
||||
// "$ref": "#/responses/repoArchivedError"
|
||||
|
||||
|
@ -185,7 +188,11 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
|||
IssueID: issue.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/attachment"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
)
|
||||
|
@ -160,6 +161,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
|||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/error"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "423":
|
||||
// "$ref": "#/responses/repoArchivedError"
|
||||
|
||||
|
@ -194,9 +197,14 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
|||
CommentID: comment.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := comment.LoadAttachments(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
|
||||
return
|
||||
|
|
|
@ -602,12 +602,19 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
oldTitle := issue.Title
|
||||
if len(form.Title) > 0 {
|
||||
issue.Title = form.Title
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(form.Body) > 0 {
|
||||
issue.Content = form.Body
|
||||
if form.Body != nil {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Update or remove deadline if set
|
||||
|
@ -686,24 +693,14 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
|
||||
return
|
||||
}
|
||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
||||
}
|
||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
|
||||
return
|
||||
}
|
||||
|
||||
if titleChanged {
|
||||
notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
|
||||
}
|
||||
|
||||
if statusChangeComment != nil {
|
||||
notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
|
||||
}
|
||||
|
||||
// change pull target branch
|
||||
|
|
|
@ -6,10 +6,8 @@ package user
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
|
@ -44,7 +42,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
|||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.UnitAccessMode(unit_model.TypeCode) >= perm.AccessModeRead {
|
||||
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.HasAnyUnitAccess() {
|
||||
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
go_context "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
@ -34,6 +36,7 @@ func ProtocolMiddlewares() (handlers []any) {
|
|||
}
|
||||
}()
|
||||
req = req.WithContext(middleware.WithContextData(req.Context()))
|
||||
req = req.WithContext(go_context.WithValue(req.Context(), httplib.RequestContextKey, req))
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -17,7 +17,7 @@ func FetchRedirectDelegate(resp http.ResponseWriter, req *http.Request) {
|
|||
// The typical page is "issue comment" page. The backend responds "/owner/repo/issues/1#comment-2",
|
||||
// then frontend needs this delegate to redirect to the new location with hash correctly.
|
||||
redirect := req.PostFormValue("redirect")
|
||||
if !httplib.IsCurrentGiteaSiteURL(redirect) {
|
||||
if !httplib.IsCurrentGiteaSiteURL(req.Context(), redirect) {
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/system"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/routing"
|
||||
actions_router "code.gitea.io/gitea/routers/api/actions"
|
||||
|
@ -112,7 +113,10 @@ func InitWebInstallPage(ctx context.Context) {
|
|||
// InitWebInstalled is for global installed configuration.
|
||||
func InitWebInstalled(ctx context.Context) {
|
||||
mustInitCtx(ctx, git.InitFull)
|
||||
log.Info("Git version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
||||
log.Info("Git version: %s (home: %s)", git.DefaultFeatures().VersionInfo(), git.HomeDir())
|
||||
if !git.DefaultFeatures().SupportHashSha256 {
|
||||
log.Warn("sha256 hash support is disabled - requires Git >= 2.42." + util.Iif(git.DefaultFeatures().UsingGogit, " Gogit is currently unsupported.", ""))
|
||||
}
|
||||
|
||||
// Setup i18n
|
||||
translation.InitLocales(ctx)
|
||||
|
|
|
@ -4,20 +4,25 @@
|
|||
package private
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
timeutil "code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
|
@ -158,6 +163,14 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||
}
|
||||
}
|
||||
|
||||
// handle pull request merging, a pull request action should push at least 1 commit
|
||||
if opts.PushTrigger == repo_module.PushTriggerPRMergeToBase {
|
||||
handlePullRequestMerging(ctx, opts, ownerName, repoName, updates)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isPrivate := opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate)
|
||||
isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate)
|
||||
// Handle Push Options
|
||||
|
@ -172,7 +185,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||
wasEmpty = repo.IsEmpty
|
||||
}
|
||||
|
||||
pusher, err := user_model.GetUserByID(ctx, opts.UserID)
|
||||
pusher, err := loadContextCacheUser(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||
|
@ -307,3 +320,52 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||
RepoWasEmpty: wasEmpty,
|
||||
})
|
||||
}
|
||||
|
||||
func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) {
|
||||
return cache.GetWithContextCache(ctx, "hook_post_receive_user", id, func() (*user_model.User, error) {
|
||||
return user_model.GetUserByID(ctx, id)
|
||||
})
|
||||
}
|
||||
|
||||
// handlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit
|
||||
func handlePullRequestMerging(ctx *gitea_context.PrivateContext, opts *private.HookOptions, ownerName, repoName string, updates []*repo_module.PushUpdateOptions) {
|
||||
if len(updates) == 0 {
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||
Err: fmt.Sprintf("Pushing a merged PR (pr:%d) no commits pushed ", opts.PullRequestID),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pr, err := issues_model.GetPullRequestByID(ctx, opts.PullRequestID)
|
||||
if err != nil {
|
||||
log.Error("GetPullRequestByID[%d]: %v", opts.PullRequestID, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "GetPullRequestByID failed"})
|
||||
return
|
||||
}
|
||||
|
||||
pusher, err := loadContextCacheUser(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Load pusher user failed"})
|
||||
return
|
||||
}
|
||||
|
||||
pr.MergedCommitID = updates[len(updates)-1].NewCommitID
|
||||
pr.MergedUnix = timeutil.TimeStampNow()
|
||||
pr.Merger = pusher
|
||||
pr.MergerID = pusher.ID
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
// Removing an auto merge pull and ignore if not exist
|
||||
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
|
||||
return fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", opts.PullRequestID, err)
|
||||
}
|
||||
if _, err := pr.SetMerged(ctx); err != nil {
|
||||
return fmt.Errorf("SetMerged failed: %s/%s Error: %v", ownerName, repoName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Failed to update PR to merged: %v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Failed to update PR to merged"})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package private
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandlePullRequestMerging(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
pr, err := issues_model.GetUnmergedPullRequest(db.DefaultContext, 1, 1, "branch2", "master", issues_model.PullRequestFlowGithub)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, pr.LoadBaseRepo(db.DefaultContext))
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
err = pull_model.ScheduleAutoMerge(db.DefaultContext, user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr")
|
||||
assert.NoError(t, err)
|
||||
|
||||
autoMerge := unittest.AssertExistsAndLoadBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||
|
||||
ctx, resp := contexttest.MockPrivateContext(t, "/")
|
||||
handlePullRequestMerging(ctx, &private.HookOptions{
|
||||
PullRequestID: pr.ID,
|
||||
UserID: 2,
|
||||
}, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, []*repo_module.PushUpdateOptions{
|
||||
{NewCommitID: "01234567"},
|
||||
})
|
||||
assert.Equal(t, 0, len(resp.Body.String()))
|
||||
pr, err = issues_model.GetPullRequestByID(db.DefaultContext, pr.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, pr.HasMerged)
|
||||
assert.EqualValues(t, "01234567", pr.MergedCommitID)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{ID: autoMerge.ID})
|
||||
}
|
|
@ -122,7 +122,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
|||
preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName)
|
||||
case refFullName.IsTag():
|
||||
preReceiveTag(ourCtx, refFullName)
|
||||
case git.DefaultFeatures.SupportProcReceive && refFullName.IsFor():
|
||||
case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor():
|
||||
preReceiveFor(ourCtx, refFullName)
|
||||
default:
|
||||
ourCtx.AssertCanWriteCode()
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present
|
||||
func HookProcReceive(ctx *gitea_context.PrivateContext) {
|
||||
opts := web.GetForm(ctx).(*private.HookOptions)
|
||||
if !git.DefaultFeatures.SupportProcReceive {
|
||||
if !git.DefaultFeatures().SupportProcReceive {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||
}
|
||||
} else {
|
||||
// Because of the special ref "refs/for" we will need to delay write permission check
|
||||
if git.DefaultFeatures.SupportProcReceive && unitType == unit.TypeCode {
|
||||
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
|
||||
mode = perm.AccessModeRead
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ func Config(ctx *context.Context) {
|
|||
ctx.Data["OfflineMode"] = setting.OfflineMode
|
||||
ctx.Data["RunUser"] = setting.RunUser
|
||||
ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
|
||||
ctx.Data["GitVersion"] = git.VersionInfo()
|
||||
ctx.Data["GitVersion"] = git.DefaultFeatures().VersionInfo()
|
||||
|
||||
ctx.Data["AppDataPath"] = setting.AppDataPath
|
||||
ctx.Data["RepoRootPath"] = setting.RepoRootPath
|
||||
|
|
|
@ -368,7 +368,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||
return setting.AppSubURL + "/"
|
||||
}
|
||||
|
||||
if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(redirectTo) {
|
||||
if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(ctx, redirectTo) {
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
if obeyRedirect {
|
||||
ctx.RedirectToCurrentSite(redirectTo)
|
||||
|
|
|
@ -470,8 +470,9 @@ func AuthorizeOAuth(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Redirect if user already granted access
|
||||
if grant != nil {
|
||||
// Redirect if user already granted access and the application is confidential.
|
||||
// I.e. always require authorization for public clients as recommended by RFC 6749 Section 10.2
|
||||
if app.ConfidentialClient && grant != nil {
|
||||
code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
|
||||
if !git.DefaultFeatures.SupportProcReceive {
|
||||
if !git.DefaultFeatures().SupportProcReceive {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
|||
|
||||
if repoExist {
|
||||
// Because of special ref "refs/for" .. , need delay write permission check
|
||||
if git.DefaultFeatures.SupportProcReceive {
|
||||
if git.DefaultFeatures().SupportProcReceive {
|
||||
accessMode = perm.AccessModeRead
|
||||
}
|
||||
|
||||
|
|
|
@ -180,7 +180,7 @@ func Create(ctx *context.Context) {
|
|||
|
||||
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
|
||||
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
|
||||
ctx.Data["SupportedObjectFormats"] = git.SupportedObjectFormats
|
||||
ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats
|
||||
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCreate)
|
||||
|
|
|
@ -17,6 +17,16 @@ import (
|
|||
|
||||
const tplSearch base.TplName = "repo/search"
|
||||
|
||||
func indexSettingToGitGrepPathspecList() (list []string) {
|
||||
for _, expr := range setting.Indexer.IncludePatterns {
|
||||
list = append(list, ":(glob)"+expr.PatternString())
|
||||
}
|
||||
for _, expr := range setting.Indexer.ExcludePatterns {
|
||||
list = append(list, ":(glob,exclude)"+expr.PatternString())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Search render repository search page
|
||||
func Search(ctx *context.Context) {
|
||||
language := ctx.FormTrim("l")
|
||||
|
@ -65,8 +75,14 @@ func Search(ctx *context.Context) {
|
|||
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
||||
}
|
||||
} else {
|
||||
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ContextLineNumber: 3, IsFuzzy: isFuzzy})
|
||||
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{
|
||||
ContextLineNumber: 1,
|
||||
IsFuzzy: isFuzzy,
|
||||
RefName: git.RefNameFromBranch(ctx.Repo.BranchName).String(), // BranchName should be default branch or the first existing branch
|
||||
PathspecList: indexSettingToGitGrepPathspecList(),
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: if no branch exists, it reports: exit status 128, fatal: this operation must be run in a work tree.
|
||||
ctx.ServerError("GrepSearch", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIndexSettingToGitGrepPathspecList(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Indexer.IncludePatterns, setting.IndexerGlobFromString("a"))()
|
||||
defer test.MockVariableValue(&setting.Indexer.ExcludePatterns, setting.IndexerGlobFromString("b"))()
|
||||
assert.Equal(t, []string{":(glob)a", ":(glob,exclude)b"}, indexSettingToGitGrepPathspecList())
|
||||
}
|
|
@ -29,6 +29,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issue_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
@ -1027,15 +1028,27 @@ func renderHomeCode(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
showRecentlyPushedNewBranches := true
|
||||
if ctx.Repo.Repository.IsMirror ||
|
||||
!ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypePullRequests) {
|
||||
showRecentlyPushedNewBranches = false
|
||||
opts := &git_model.FindRecentlyPushedNewBranchesOptions{
|
||||
Actor: ctx.Doer,
|
||||
Repo: ctx.Repo.Repository,
|
||||
BaseRepo: ctx.Repo.Repository,
|
||||
}
|
||||
if showRecentlyPushedNewBranches {
|
||||
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID, ctx.Repo.Repository.DefaultBranch)
|
||||
if ctx.Repo.Repository.IsFork {
|
||||
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
|
||||
}
|
||||
|
||||
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
|
||||
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
|
||||
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
|
||||
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRecentlyPushedBranches", err)
|
||||
ctx.ServerError("FindRecentlyPushedNewBranches", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -234,9 +234,7 @@ func (b *Base) plainTextInternal(skip, status int, bs []byte) {
|
|||
b.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
b.Resp.WriteHeader(status)
|
||||
if _, err := b.Resp.Write(bs); err != nil {
|
||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
||||
}
|
||||
_, _ = b.Resp.Write(bs)
|
||||
}
|
||||
|
||||
// PlainTextBytes renders bytes as plain text
|
||||
|
@ -256,7 +254,7 @@ func (b *Base) Redirect(location string, status ...int) {
|
|||
code = status[0]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(location, "http://") || strings.HasPrefix(location, "https://") || strings.HasPrefix(location, "//") {
|
||||
if !httplib.IsRelativeURL(location) {
|
||||
// Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path
|
||||
// 1. the first request to "/my-path" contains cookie
|
||||
// 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
@ -51,7 +52,7 @@ func (ctx *Context) RedirectToCurrentSite(location ...string) {
|
|||
continue
|
||||
}
|
||||
|
||||
if !httplib.IsCurrentGiteaSiteURL(loc) {
|
||||
if !httplib.IsCurrentGiteaSiteURL(ctx, loc) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -77,7 +78,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
|
|||
}
|
||||
|
||||
err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
|
||||
if err == nil {
|
||||
if err == nil || errors.Is(err, syscall.EPIPE) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,19 @@ func MockAPIContext(t *testing.T, reqPath string) (*context.APIContext, *httptes
|
|||
return ctx, resp
|
||||
}
|
||||
|
||||
func MockPrivateContext(t *testing.T, reqPath string) (*context.PrivateContext, *httptest.ResponseRecorder) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := mockRequest(t, reqPath)
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
base.Data = middleware.GetContextData(req.Context())
|
||||
base.Locale = &translation.MockLocale{}
|
||||
ctx := &context.PrivateContext{Base: base}
|
||||
_ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
|
||||
chiCtx := chi.NewRouteContext()
|
||||
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||
return ctx, resp
|
||||
}
|
||||
|
||||
// LoadRepo load a repo into a test context.
|
||||
func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) {
|
||||
var doer *user_model.User
|
||||
|
|
|
@ -1143,7 +1143,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
|||
// so if we are using at least this version of git we don't have to tell ParsePatch to do
|
||||
// the skipping for us
|
||||
parsePatchSkipToFile := opts.SkipTo
|
||||
if opts.SkipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil {
|
||||
if opts.SkipTo != "" && git.DefaultFeatures().CheckVersionAtLeast("2.31") {
|
||||
cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo)
|
||||
parsePatchSkipToFile = ""
|
||||
}
|
||||
|
|
|
@ -35,9 +35,10 @@ func TestGetRefEndNamesAndURLs(t *testing.T) {
|
|||
func TestIssue_DeleteIssue(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
issueCount := 5
|
||||
issueIDs, err := issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issueIDs, 5)
|
||||
assert.Len(t, issueIDs, issueCount)
|
||||
|
||||
issue := &issues_model.Issue{
|
||||
RepoID: 1,
|
||||
|
@ -48,7 +49,7 @@ func TestIssue_DeleteIssue(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
issueIDs, err = issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issueIDs, 4)
|
||||
assert.Len(t, issueIDs, issueCount-1)
|
||||
|
||||
// check attachment removal
|
||||
attachments, err := repo_model.GetAttachmentsByIssueID(db.DefaultContext, 4)
|
||||
|
|
|
@ -289,8 +289,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
|||
}
|
||||
|
||||
// Make sure to compose independent messages to avoid leaking user emails
|
||||
msgID := createReference(ctx.Issue, ctx.Comment, ctx.ActionType)
|
||||
reference := createReference(ctx.Issue, nil, activities_model.ActionType(0))
|
||||
msgID := generateMessageIDForIssue(ctx.Issue, ctx.Comment, ctx.ActionType)
|
||||
reference := generateMessageIDForIssue(ctx.Issue, nil, activities_model.ActionType(0))
|
||||
|
||||
var replyPayload []byte
|
||||
if ctx.Comment != nil {
|
||||
|
@ -362,7 +362,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
|||
return msgs, nil
|
||||
}
|
||||
|
||||
func createReference(issue *issues_model.Issue, comment *issues_model.Comment, actionType activities_model.ActionType) string {
|
||||
func generateMessageIDForIssue(issue *issues_model.Issue, comment *issues_model.Comment, actionType activities_model.ActionType) string {
|
||||
var path string
|
||||
if issue.IsPull {
|
||||
path = "pulls"
|
||||
|
@ -389,6 +389,10 @@ func createReference(issue *issues_model.Issue, comment *issues_model.Comment, a
|
|||
return fmt.Sprintf("<%s/%s/%d%s@%s>", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)
|
||||
}
|
||||
|
||||
func generateMessageIDForRelease(release *repo_model.Release) string {
|
||||
return fmt.Sprintf("<%s/releases/%d@%s>", release.Repo.FullName(), release.ID, setting.Domain)
|
||||
}
|
||||
|
||||
func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *user_model.User) map[string]string {
|
||||
repo := ctx.Issue.Repo
|
||||
|
||||
|
|
|
@ -86,11 +86,11 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo
|
|||
|
||||
msgs := make([]*Message, 0, len(tos))
|
||||
publisherName := rel.Publisher.DisplayName()
|
||||
relURL := "<" + rel.HTMLURL() + ">"
|
||||
msgID := generateMessageIDForRelease(rel)
|
||||
for _, to := range tos {
|
||||
msg := NewMessageFrom(to, publisherName, setting.MailService.FromEmail, subject, mailBody.String())
|
||||
msg.Info = subject
|
||||
msg.SetHeader("Message-ID", relURL)
|
||||
msg.SetHeader("Message-ID", msgID)
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
|
||||
|
|
|
@ -288,7 +288,7 @@ func TestGenerateAdditionalHeaders(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_createReference(t *testing.T) {
|
||||
func TestGenerateMessageIDForIssue(t *testing.T) {
|
||||
_, _, issue, comment := prepareMailerTest(t)
|
||||
_, _, pullIssue, _ := prepareMailerTest(t)
|
||||
pullIssue.IsPull = true
|
||||
|
@ -388,10 +388,18 @@ func Test_createReference(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := createReference(tt.args.issue, tt.args.comment, tt.args.actionType)
|
||||
got := generateMessageIDForIssue(tt.args.issue, tt.args.comment, tt.args.actionType)
|
||||
if !strings.HasPrefix(got, tt.prefix) {
|
||||
t.Errorf("createReference() = %v, want %v", got, tt.prefix)
|
||||
t.Errorf("generateMessageIDForIssue() = %v, want %v", got, tt.prefix)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateMessageIDForRelease(t *testing.T) {
|
||||
msgID := generateMessageIDForRelease(&repo_model.Release{
|
||||
ID: 1,
|
||||
Repo: &repo_model.Repository{OwnerName: "owner", Name: "repo"},
|
||||
})
|
||||
assert.Equal(t, "<owner/repo/releases/1@localhost>", msgID)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
@ -162,12 +161,6 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
|
||||
defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
|
||||
|
||||
// Removing an auto merge pull and ignore if not exist
|
||||
// FIXME: is this the correct point to do this? Shouldn't this be after IsMergeStyleAllowed?
|
||||
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
|
||||
|
@ -184,17 +177,15 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||
}()
|
||||
|
||||
pr.MergedCommitID, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message)
|
||||
_, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message, repo_module.PushTriggerPRMergeToBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr.MergedUnix = timeutil.TimeStampNow()
|
||||
pr.Merger = doer
|
||||
pr.MergerID = doer.ID
|
||||
|
||||
if _, err := pr.SetMerged(ctx); err != nil {
|
||||
log.Error("SetMerged %-v: %v", pr, err)
|
||||
// reload pull request because it has been updated by post receive hook
|
||||
pr, err = issues_model.GetPullRequestByID(ctx, pr.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
|
@ -245,7 +236,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
}
|
||||
|
||||
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
|
||||
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) (string, error) {
|
||||
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) {
|
||||
// Clone base repo.
|
||||
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
||||
if err != nil {
|
||||
|
@ -318,11 +309,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use
|
|||
pr.BaseRepo.Name,
|
||||
pr.ID,
|
||||
)
|
||||
|
||||
mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger))
|
||||
pushCmd := git.NewCommand(ctx, "push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch)
|
||||
|
||||
// Push back to upstream.
|
||||
// TODO: this cause an api call to "/api/internal/hook/post-receive/...",
|
||||
// that prevents us from doint the whole merge in one db transaction
|
||||
// This cause an api call to "/api/internal/hook/post-receive/...",
|
||||
// If it's merge, all db transaction and operations should be there but not here to prevent deadlock.
|
||||
if err := pushCmd.Run(mergeCtx.RunOpts()); err != nil {
|
||||
if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") {
|
||||
return "", &git.ErrPushOutOfDate{
|
||||
|
|
|
@ -383,7 +383,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
|
|||
cmdApply.AddArguments("--ignore-whitespace")
|
||||
}
|
||||
is3way := false
|
||||
if git.CheckGitVersionAtLeast("2.32.0") == nil {
|
||||
if git.DefaultFeatures().CheckVersionAtLeast("2.32.0") {
|
||||
cmdApply.AddArguments("--3way")
|
||||
is3way = true
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue