mirror of
https://github.com/go-gitea/gitea
synced 2024-12-22 08:07:51 +01:00
Merge branch 'main' into upstream_sort-repo-list-on-dashboard
This commit is contained in:
commit
411bfe069f
10
cmd/hook.go
10
cmd/hook.go
@ -220,10 +220,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
supportProcReceive := false
|
supportProcReceive := git.DefaultFeatures().SupportProcReceive
|
||||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
|
||||||
supportProcReceive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// TODO: support news feeds for wiki
|
// TODO: support news feeds for wiki
|
||||||
@ -341,6 +338,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||||
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
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)
|
pusherName := os.Getenv(repo_module.EnvPusherName)
|
||||||
|
|
||||||
hookOptions := private.HookOptions{
|
hookOptions := private.HookOptions{
|
||||||
@ -350,6 +348,8 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
||||||
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
||||||
GitPushOptions: pushOptions(),
|
GitPushOptions: pushOptions(),
|
||||||
|
PullRequestID: prID,
|
||||||
|
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
|
||||||
}
|
}
|
||||||
oldCommitIDs := make([]string, hookBatchSize)
|
oldCommitIDs := make([]string, hookBatchSize)
|
||||||
newCommitIDs := make([]string, hookBatchSize)
|
newCommitIDs := make([]string, hookBatchSize)
|
||||||
@ -497,7 +497,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
return nil
|
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.")
|
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",
|
Name: "type",
|
||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
Value: "",
|
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{
|
&cli.StringFlag{
|
||||||
Name: "storage",
|
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 {
|
func runMigrateStorage(ctx *cli.Context) error {
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -223,13 +230,14 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
|
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
|
||||||
"attachments": migrateAttachments,
|
"attachments": migrateAttachments,
|
||||||
"lfs": migrateLFS,
|
"lfs": migrateLFS,
|
||||||
"avatars": migrateAvatars,
|
"avatars": migrateAvatars,
|
||||||
"repo-avatars": migrateRepoAvatars,
|
"repo-avatars": migrateRepoAvatars,
|
||||||
"repo-archivers": migrateRepoArchivers,
|
"repo-archivers": migrateRepoArchivers,
|
||||||
"packages": migratePackages,
|
"packages": migratePackages,
|
||||||
"actions-log": migrateActionsLog,
|
"actions-log": migrateActionsLog,
|
||||||
|
"actions-artifacts": migrateActionsArtifacts,
|
||||||
}
|
}
|
||||||
|
|
||||||
tp := strings.ToLower(ctx.String("type"))
|
tp := strings.ToLower(ctx.String("type"))
|
||||||
|
@ -178,7 +178,7 @@ func runServ(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(words) < 2 {
|
if len(words) < 2 {
|
||||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
if git.DefaultFeatures().SupportProcReceive {
|
||||||
// for AGit Flow
|
// for AGit Flow
|
||||||
if cmd == "ssh_info" {
|
if cmd == "ssh_info" {
|
||||||
fmt.Print(`{"type":"gitea","version":1}`)
|
fmt.Print(`{"type":"gitea","version":1}`)
|
||||||
|
@ -1456,7 +1456,7 @@ LEVEL = Info
|
|||||||
;; Batch size to send for batched queues
|
;; Batch size to send for batched queues
|
||||||
;BATCH_LENGTH = 20
|
;BATCH_LENGTH = 20
|
||||||
;;
|
;;
|
||||||
;; Connection string for redis queues this will store the redis or redis-cluster connection string.
|
;; Connection string for redis queues this will store the redis (or Redis cluster) connection string.
|
||||||
;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb
|
;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb
|
||||||
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
|
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
|
||||||
;CONN_STR = "redis://127.0.0.1:6379/0"
|
;CONN_STR = "redis://127.0.0.1:6379/0"
|
||||||
@ -1740,9 +1740,8 @@ LEVEL = Info
|
|||||||
;; For "memory" only, GC interval in seconds, default is 60
|
;; For "memory" only, GC interval in seconds, default is 60
|
||||||
;INTERVAL = 60
|
;INTERVAL = 60
|
||||||
;;
|
;;
|
||||||
;; For "redis", "redis-cluster" and "memcache", connection host address
|
;; For "redis" and "memcache", connection host address
|
||||||
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
|
||||||
;; redis-cluster: `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
|
||||||
;; memcache: `127.0.0.1:11211`
|
;; memcache: `127.0.0.1:11211`
|
||||||
;; twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000`
|
;; twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000`
|
||||||
;HOST =
|
;HOST =
|
||||||
@ -1772,15 +1771,14 @@ LEVEL = Info
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; Either "memory", "file", "redis", "redis-cluster", "db", "mysql", "couchbase", "memcache" or "postgres"
|
;; Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
|
||||||
;; Default is "memory". "db" will reuse the configuration in [database]
|
;; Default is "memory". "db" will reuse the configuration in [database]
|
||||||
;PROVIDER = memory
|
;PROVIDER = memory
|
||||||
;;
|
;;
|
||||||
;; Provider config options
|
;; Provider config options
|
||||||
;; memory: doesn't have any config yet
|
;; memory: doesn't have any config yet
|
||||||
;; file: session file path, e.g. `data/sessions`
|
;; file: session file path, e.g. `data/sessions`
|
||||||
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
|
||||||
;; redis-cluster: `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
|
||||||
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
|
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
|
||||||
;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
|
;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
|
||||||
;;
|
;;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Gitea - Docker
|
# 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).
|
||||||
|
@ -492,7 +492,7 @@ Configuration at `[queue]` will set defaults for queues with overrides for indiv
|
|||||||
- `DATADIR`: **queues/common**: Base DataDir for storing level queues. `DATADIR` for individual queues can be set in `queue.name` sections. Relative paths will be made absolute against `%(APP_DATA_PATH)s`.
|
- `DATADIR`: **queues/common**: Base DataDir for storing level queues. `DATADIR` for individual queues can be set in `queue.name` sections. Relative paths will be made absolute against `%(APP_DATA_PATH)s`.
|
||||||
- `LENGTH`: **100000**: Maximal queue size before channel queues block
|
- `LENGTH`: **100000**: Maximal queue size before channel queues block
|
||||||
- `BATCH_LENGTH`: **20**: Batch data before passing to the handler
|
- `BATCH_LENGTH`: **20**: Batch data before passing to the handler
|
||||||
- `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. For `redis-cluster` use `redis+cluster://127.0.0.1:6379/0`. Options can be set using query params. Similarly, LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value**, and will override `DATADIR`
|
- `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. If you're running a Redis cluster, use `redis+cluster://127.0.0.1:6379/0`. Options can be set using query params. Similarly, LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value**, and will override `DATADIR`
|
||||||
- `QUEUE_NAME`: **_queue**: The suffix for default redis and disk queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overridden in the specific `queue.name` section.
|
- `QUEUE_NAME`: **_queue**: The suffix for default redis and disk queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overridden in the specific `queue.name` section.
|
||||||
- `SET_NAME`: **_unique**: The suffix that will be added to the default redis and disk queue `set` name for unique queues. Individual queues will default to **`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific `queue.name` section.
|
- `SET_NAME`: **_unique**: The suffix that will be added to the default redis and disk queue `set` name for unique queues. Individual queues will default to **`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific `queue.name` section.
|
||||||
- `MAX_WORKERS`: **(dynamic)**: Maximum number of worker go-routines for the queue. Default value is "CpuNum/2" clipped to between 1 and 10.
|
- `MAX_WORKERS`: **(dynamic)**: Maximum number of worker go-routines for the queue. Default value is "CpuNum/2" clipped to between 1 and 10.
|
||||||
@ -777,11 +777,11 @@ and
|
|||||||
|
|
||||||
## Cache (`cache`)
|
## Cache (`cache`)
|
||||||
|
|
||||||
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, `redis-cluster`, `twoqueue` or `memcache`. (`twoqueue` represents a size limited LRU cache.)
|
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, `twoqueue` or `memcache`. (`twoqueue` represents a size limited LRU cache.)
|
||||||
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory and twoqueue cache only.
|
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory and twoqueue cache only.
|
||||||
- `HOST`: **_empty_**: Connection string for `redis`, `redis-cluster` and `memcache`. For `twoqueue` sets configuration for the queue.
|
- `HOST`: **_empty_**: Connection string for `redis` and `memcache`. For `twoqueue` sets configuration for the queue.
|
||||||
- Redis: `redis://:macaron@127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
- Redis: `redis://:macaron@127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
||||||
- Redis-cluster `redis+cluster://:macaron@127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
- For a Redis cluster: `redis+cluster://:macaron@127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
|
||||||
- Memcache: `127.0.0.1:9090;127.0.0.1:9091`
|
- Memcache: `127.0.0.1:9090;127.0.0.1:9091`
|
||||||
- TwoQueue LRU cache: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000` representing the maximum number of objects stored in the cache.
|
- TwoQueue LRU cache: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000` representing the maximum number of objects stored in the cache.
|
||||||
- `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to -1 disables caching.
|
- `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to -1 disables caching.
|
||||||
@ -793,7 +793,7 @@ and
|
|||||||
|
|
||||||
## Session (`session`)
|
## Session (`session`)
|
||||||
|
|
||||||
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, redis-cluster, db, mysql, couchbase, memcache, postgres\]. Setting `db` will reuse the configuration in `[database]`
|
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, db, mysql, couchbase, memcache, postgres\]. Setting `db` will reuse the configuration in `[database]`
|
||||||
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for db, empty (database config will be used); for others, the connection string. Relative paths will be made absolute against _`AppWorkPath`_.
|
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for db, empty (database config will be used); for others, the connection string. Relative paths will be made absolute against _`AppWorkPath`_.
|
||||||
- `COOKIE_SECURE`:**_empty_**: `true` or `false`. Enable this to force using HTTPS for all session access. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
- `COOKIE_SECURE`:**_empty_**: `true` or `false`. Enable this to force using HTTPS for all session access. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||||
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
|
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
|
||||||
|
4
go.mod
4
go.mod
@ -8,7 +8,7 @@ require (
|
|||||||
code.gitea.io/sdk/gitea v0.17.1
|
code.gitea.io/sdk/gitea v0.17.1
|
||||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||||
connectrpc.com/connect v1.15.0
|
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/cache v0.2.0
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||||
@ -59,6 +59,7 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/feeds v1.1.2
|
github.com/gorilla/feeds v1.1.2
|
||||||
github.com/gorilla/sessions v1.2.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/go-version v1.6.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
github.com/huandu/xstrings v1.4.0
|
github.com/huandu/xstrings v1.4.0
|
||||||
@ -209,6 +210,7 @@ require (
|
|||||||
github.com/gorilla/handlers v1.5.2 // indirect
|
github.com/gorilla/handlers v1.5.2 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // 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-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // 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=
|
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 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
||||||
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
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-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||||
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/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
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/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
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.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
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/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 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
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=
|
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/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 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
|
||||||
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
|
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 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
|
||||||
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
|
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
|
||||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
|
@ -397,36 +397,16 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co
|
|||||||
|
|
||||||
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
|
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
|
||||||
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
|
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
|
||||||
type result struct {
|
|
||||||
Index int64
|
|
||||||
SHA string
|
|
||||||
}
|
|
||||||
getBase := func() *xorm.Session {
|
|
||||||
return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID)
|
|
||||||
}
|
|
||||||
|
|
||||||
start := timeutil.TimeStampNow().AddDuration(-before)
|
start := timeutil.TimeStampNow().AddDuration(-before)
|
||||||
results := make([]result, 0, 10)
|
|
||||||
|
|
||||||
sess := getBase().And("updated_unix >= ?", start).
|
var contexts []string
|
||||||
Select("max( `index` ) as `index`, sha").
|
if err := db.GetEngine(ctx).Table("commit_status").
|
||||||
GroupBy("context_hash, sha").OrderBy("max( `index` ) desc")
|
Where("repo_id = ?", repoID).And("updated_unix >= ?", start).
|
||||||
|
Cols("context").Distinct().Find(&contexts); err != nil {
|
||||||
err := sess.Find(&results)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
contexts := make([]string, 0, len(results))
|
return contexts, nil
|
||||||
if len(results) == 0 {
|
|
||||||
return contexts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
conds := make([]builder.Cond, 0, len(results))
|
|
||||||
for _, result := range results {
|
|
||||||
conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA})
|
|
||||||
}
|
|
||||||
return contexts, getBase().And(builder.Or(conds...)).Select("context").Find(&contexts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommitStatusOptions holds options for creating a CommitStatus
|
// NewCommitStatusOptions holds options for creating a CommitStatus
|
||||||
|
@ -5,11 +5,15 @@ package git_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"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/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -175,3 +179,55 @@ func Test_CalcCommitStatus(t *testing.T) {
|
|||||||
assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses))
|
assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindRepoRecentCommitStatusContexts(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
commit, err := gitRepo.GetBranchCommit(repo2.DefaultBranch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_, err := db.DeleteByBean(db.DefaultContext, &git_model.CommitStatus{
|
||||||
|
RepoID: repo2.ID,
|
||||||
|
CreatorID: user2.ID,
|
||||||
|
SHA: commit.ID.String(),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{
|
||||||
|
Repo: repo2,
|
||||||
|
Creator: user2,
|
||||||
|
SHA: commit.ID,
|
||||||
|
CommitStatus: &git_model.CommitStatus{
|
||||||
|
State: structs.CommitStatusFailure,
|
||||||
|
TargetURL: "https://example.com/tests/",
|
||||||
|
Context: "compliance/lint-backend",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{
|
||||||
|
Repo: repo2,
|
||||||
|
Creator: user2,
|
||||||
|
SHA: commit.ID,
|
||||||
|
CommitStatus: &git_model.CommitStatus{
|
||||||
|
State: structs.CommitStatusSuccess,
|
||||||
|
TargetURL: "https://example.com/tests/",
|
||||||
|
Context: "compliance/lint-backend",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
contexts, err := git_model.FindRepoRecentCommitStatusContexts(db.DefaultContext, repo2.ID, time.Hour)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, contexts, 1) {
|
||||||
|
assert.Equal(t, "compliance/lint-backend", contexts[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -429,62 +429,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
|
|||||||
return nil
|
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.
|
// 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) {
|
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
||||||
// if the deadline hasn't changed do nothing
|
// if the deadline hasn't changed do nothing
|
||||||
|
@ -9,10 +9,10 @@ import (
|
|||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/avatar"
|
"code.gitea.io/gitea/modules/avatar"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"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)
|
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 {
|
func (repo *Repository) AvatarLink(ctx context.Context) string {
|
||||||
link := repo.relAvatarLink(ctx)
|
return httplib.MakeAbsoluteURL(ctx, 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
|
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,10 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
|||||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||||
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
||||||
if len(userIDs) > 0 {
|
if len(userIDs) > 0 {
|
||||||
if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
|
if err = e.In("id", uniqueUserIDs.Values()).
|
||||||
|
Where(builder.Eq{"`user`.is_active": true}).
|
||||||
|
OrderBy(user_model.GetOrderByName()).
|
||||||
|
Find(&users); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,7 +155,8 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cond := builder.And(builder.Neq{"`user`.id": posterID})
|
cond := builder.And(builder.Neq{"`user`.id": posterID}).
|
||||||
|
And(builder.Eq{"`user`.is_active": true})
|
||||||
|
|
||||||
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
|
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
|
||||||
// This a private repository:
|
// This a private repository:
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -25,8 +26,17 @@ func TestRepoAssignees(t *testing.T) {
|
|||||||
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
|
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
|
||||||
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
|
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, users, 4)
|
if assert.Len(t, users, 4) {
|
||||||
assert.ElementsMatch(t, []int64{10, 15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID, users[3].ID})
|
assert.ElementsMatch(t, []int64{10, 15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID, users[3].ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not return deactivated users
|
||||||
|
assert.NoError(t, user_model.UpdateUserCols(db.DefaultContext, &user_model.User{ID: 15, IsActive: false}, "is_active"))
|
||||||
|
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, users, 3) {
|
||||||
|
assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoGetReviewers(t *testing.T) {
|
func TestRepoGetReviewers(t *testing.T) {
|
||||||
@ -38,17 +48,19 @@ func TestRepoGetReviewers(t *testing.T) {
|
|||||||
ctx := db.DefaultContext
|
ctx := db.DefaultContext
|
||||||
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
|
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, reviewers, 4)
|
if assert.Len(t, reviewers, 3) {
|
||||||
|
assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
|
||||||
|
}
|
||||||
|
|
||||||
// should include doer if doer is not PR poster.
|
// should include doer if doer is not PR poster.
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
|
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, reviewers, 4)
|
assert.Len(t, reviewers, 3)
|
||||||
|
|
||||||
// should not include PR poster, if PR poster would be otherwise eligible
|
// should not include PR poster, if PR poster would be otherwise eligible
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
|
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, reviewers, 3)
|
assert.Len(t, reviewers, 2)
|
||||||
|
|
||||||
// test private user repo
|
// test private user repo
|
||||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
@ -9,11 +9,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/avatars"
|
"code.gitea.io/gitea/models/avatars"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/avatar"
|
"code.gitea.io/gitea/modules/avatar"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"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)
|
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 {
|
func (u *User) AvatarLink(ctx context.Context) string {
|
||||||
link := u.AvatarLinkWithSize(ctx, 0)
|
return httplib.MakeAbsoluteURL(ctx, u.AvatarLinkWithSize(ctx, 0))
|
||||||
if !strings.HasPrefix(link, "//") && !strings.Contains(link, "://") {
|
|
||||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL+"/")
|
|
||||||
}
|
|
||||||
return link
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||||
|
@ -4,12 +4,11 @@
|
|||||||
package pwn
|
package pwn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand/v2"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/h2non/gock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,86 +17,34 @@ var client = New(WithHTTP(&http.Client{
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
func TestPassword(t *testing.T) {
|
func TestPassword(t *testing.T) {
|
||||||
// Check input error
|
defer gock.Off()
|
||||||
_, err := client.CheckPassword("", false)
|
|
||||||
|
count, err := client.CheckPassword("", false)
|
||||||
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
|
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
|
||||||
|
assert.Equal(t, -1, count)
|
||||||
|
|
||||||
// Should fail
|
gock.New("https://api.pwnedpasswords.com").Get("/range/5c1d8").Times(1).Reply(200).BodyString("EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2")
|
||||||
fail := "password1234"
|
count, err = client.CheckPassword("pwned", false)
|
||||||
count, err := client.CheckPassword(fail, false)
|
|
||||||
assert.NotEmpty(t, count, "%s should fail as a password", fail)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
// Should fail (with padding)
|
gock.New("https://api.pwnedpasswords.com").Get("/range/ba189").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4")
|
||||||
failPad := "administrator"
|
count, err = client.CheckPassword("notpwned", false)
|
||||||
count, err = client.CheckPassword(failPad, true)
|
|
||||||
assert.NotEmpty(t, count, "%s should fail as a password", failPad)
|
|
||||||
assert.NoError(t, err)
|
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
|
gock.New("https://api.pwnedpasswords.com").Get("/range/a1733").Times(1).Reply(200).BodyString("C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0")
|
||||||
// with hopefully minimal error. Try five times?
|
count, err = client.CheckPassword("paddedpwned", true)
|
||||||
assert.Condition(t, func() bool {
|
assert.NoError(t, err)
|
||||||
for i := 0; i <= 5; i++ {
|
assert.Equal(t, 1, count)
|
||||||
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")
|
|
||||||
|
|
||||||
// Again, but with padded responses
|
gock.New("https://api.pwnedpasswords.com").Get("/range/5617b").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0")
|
||||||
assert.Condition(t, func() bool {
|
count, err = client.CheckPassword("paddednotpwned", true)
|
||||||
for i := 0; i <= 5; i++ {
|
assert.NoError(t, err)
|
||||||
count, err = client.CheckPassword(testPassword(), true)
|
assert.Equal(t, 0, count)
|
||||||
assert.NoError(t, err)
|
|
||||||
if count == 0 {
|
gock.New("https://api.pwnedpasswords.com").Get("/range/79082").Times(1).Reply(200).BodyString("FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0")
|
||||||
return true
|
count, err = client.CheckPassword("paddednotpwnedzero", true)
|
||||||
}
|
assert.NoError(t, err)
|
||||||
}
|
assert.Equal(t, 0, count)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func (r *BlameReader) Close() error {
|
|||||||
// CreateBlameReader creates reader for given repository, commit and file
|
// 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) {
|
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
||||||
var ignoreRevsFile *string
|
var ignoreRevsFile *string
|
||||||
if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore {
|
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
|
||||||
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
|
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')
|
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||||
func (c *Commit) GetBranchName() (string, error) {
|
func (c *Commit) GetBranchName() (string, error) {
|
||||||
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
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("--exclude", "refs/tags/*")
|
||||||
}
|
}
|
||||||
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
|
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
|
||||||
|
@ -22,42 +22,63 @@ import (
|
|||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequiredVersion is the minimum Git version required
|
const RequiredVersion = "2.0.0" // the minimum Git version required
|
||||||
const RequiredVersion = "2.0.0"
|
|
||||||
|
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 (
|
var (
|
||||||
// GitExecutable is the command name of git
|
GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
|
||||||
// Could be updated to an absolute path while initialization
|
DefaultContext context.Context // the default context to run git commands in, must be initialized by git.InitXxx
|
||||||
GitExecutable = "git"
|
defaultFeatures *Features
|
||||||
|
|
||||||
// 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’
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadGitVersion tries to get the current git version and stores it into a global variable
|
func (f *Features) CheckVersionAtLeast(atLeast string) bool {
|
||||||
func loadGitVersion() error {
|
return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
|
||||||
// doesn't need RWMutex because it's executed by Init()
|
}
|
||||||
if DefaultFeatures.GitVersion != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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)
|
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
return runErr
|
return nil, runErr
|
||||||
}
|
}
|
||||||
|
|
||||||
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
|
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
|
||||||
if err == nil {
|
if err != nil {
|
||||||
DefaultFeatures.GitVersion = ver
|
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) {
|
func parseGitVersionLine(s string) (*version.Version, error) {
|
||||||
@ -85,56 +106,24 @@ func SetExecutablePath(path string) error {
|
|||||||
return fmt.Errorf("git not found: %w", err)
|
return fmt.Errorf("git not found: %w", err)
|
||||||
}
|
}
|
||||||
GitExecutable = absPath
|
GitExecutable = absPath
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if err = loadGitVersion(); err != nil {
|
func ensureGitVersion() error {
|
||||||
return fmt.Errorf("unable to load git version: %w", err)
|
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
||||||
}
|
|
||||||
|
|
||||||
versionRequired, err := version.NewVersion(RequiredVersion)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures.GitVersion.LessThan(versionRequired) {
|
|
||||||
moreHint := "get git: https://git-scm.com/download/"
|
moreHint := "get git: https://git-scm.com/download/"
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
// 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
|
// 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"
|
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 {
|
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 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")
|
|
||||||
}
|
}
|
||||||
return nil
|
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.
|
// 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.
|
// 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 {
|
func InitSimple(ctx context.Context) error {
|
||||||
if err := checkInit(); err != nil {
|
if setting.Git.HomePath == "" {
|
||||||
return err
|
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
|
DefaultContext = ctx
|
||||||
@ -165,7 +158,24 @@ func InitSimple(ctx context.Context) error {
|
|||||||
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
|
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.
|
// 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
|
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
|
// 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")
|
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicitly disable credential helper, otherwise Git credentials might leak
|
// 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=")
|
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 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")
|
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=")
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckGitVersionAtLeast("2.10") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
||||||
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckGitVersionAtLeast("2.18") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||||
if err := configSet("core.commitGraph", "true"); err != nil {
|
if err := configSet("core.commitGraph", "true"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -256,7 +254,7 @@ func syncGitConfig() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultFeatures.SupportProcReceive {
|
if DefaultFeatures().SupportProcReceive {
|
||||||
// set support for AGit flow
|
// set support for AGit flow
|
||||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -294,7 +292,7 @@ func syncGitConfig() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// By default partial clones are disabled, enable them from git v2.22
|
// 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 {
|
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -309,21 +307,6 @@ func syncGitConfig() (err error) {
|
|||||||
return err
|
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 {
|
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
||||||
badVersions := []struct {
|
badVersions := []struct {
|
||||||
Version *version.Version
|
Version *version.Version
|
||||||
|
@ -29,6 +29,7 @@ type GrepOptions struct {
|
|||||||
ContextLineNumber int
|
ContextLineNumber int
|
||||||
IsFuzzy bool
|
IsFuzzy bool
|
||||||
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
|
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) {
|
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.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
||||||
}
|
}
|
||||||
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
||||||
|
cmd.AddDashesAndList(opts.PathspecList...)
|
||||||
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
||||||
stderr := bytes.Buffer{}
|
stderr := bytes.Buffer{}
|
||||||
err = cmd.Run(&RunOpts{
|
err = cmd.Run(&RunOpts{
|
||||||
|
@ -31,6 +31,26 @@ func TestGrepSearch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, res)
|
}, 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})
|
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []*GrepResult{
|
assert.Equal(t, []*GrepResult{
|
||||||
|
@ -120,12 +120,8 @@ var (
|
|||||||
Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
|
Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
|
||||||
)
|
)
|
||||||
|
|
||||||
var SupportedObjectFormats = []ObjectFormat{
|
|
||||||
Sha1ObjectFormat,
|
|
||||||
}
|
|
||||||
|
|
||||||
func ObjectFormatFromName(name string) ObjectFormat {
|
func ObjectFormatFromName(name string) ObjectFormat {
|
||||||
for _, objectFormat := range SupportedObjectFormats {
|
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||||
if name == objectFormat.Name() {
|
if name == objectFormat.Name() {
|
||||||
return objectFormat
|
return objectFormat
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
|
|||||||
|
|
||||||
func NewIDFromString(hexHash string) (ObjectID, error) {
|
func NewIDFromString(hexHash string) (ObjectID, error) {
|
||||||
var theObjectFormat ObjectFormat
|
var theObjectFormat ObjectFormat
|
||||||
for _, objectFormat := range SupportedObjectFormats {
|
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||||
if len(hexHash) == objectFormat.FullLength() {
|
if len(hexHash) == objectFormat.FullLength() {
|
||||||
theObjectFormat = objectFormat
|
theObjectFormat = objectFormat
|
||||||
break
|
break
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
||||||
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
||||||
var cmd *Command
|
var cmd *Command
|
||||||
if CheckGitVersionAtLeast("2.7") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.7") {
|
||||||
cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
|
cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
|
||||||
} else {
|
} else {
|
||||||
cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
|
cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
|
||||||
|
@ -7,7 +7,6 @@ package git
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -63,32 +62,6 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool {
|
|||||||
return err == nil
|
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.
|
// InitRepository initializes a new Git repository.
|
||||||
func InitRepository(ctx context.Context, repoPath string, bare bool, objectFormatName string) error {
|
func InitRepository(ctx context.Context, repoPath string, bare bool, objectFormatName string) error {
|
||||||
err := os.MkdirAll(repoPath, os.ModePerm)
|
err := os.MkdirAll(repoPath, os.ModePerm)
|
||||||
@ -101,7 +74,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
|
|||||||
if !IsValidObjectFormat(objectFormatName) {
|
if !IsValidObjectFormat(objectFormatName) {
|
||||||
return fmt.Errorf("invalid object format: %s", objectFormatName)
|
return fmt.Errorf("invalid object format: %s", objectFormatName)
|
||||||
}
|
}
|
||||||
if DefaultFeatures.SupportHashSha256 {
|
if DefaultFeatures().SupportHashSha256 {
|
||||||
cmd.AddOptionValues("--object-format", objectFormatName)
|
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"
|
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
const isGogit = true
|
||||||
isGogit = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repository represents a Git repository.
|
// Repository represents a Git repository.
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
|
@ -15,9 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
const isGogit = false
|
||||||
isGogit = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repository represents a Git repository.
|
// Repository represents a Git repository.
|
||||||
type Repository struct {
|
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) {
|
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)").
|
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").
|
||||||
AddOptionFormat("--count=%d", limit).
|
AddOptionFormat("--count=%d", limit).
|
||||||
AddOptionValues("--contains", commit.ID.String(), BranchPrefix).
|
AddOptionValues("--contains", commit.ID.String(), BranchPrefix).
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
// WriteCommitGraph write commit graph to speed up repo access
|
// WriteCommitGraph write commit graph to speed up repo access
|
||||||
// this requires git v2.18 to be installed
|
// this requires git v2.18 to be installed
|
||||||
func WriteCommitGraph(ctx context.Context, repoPath string) error {
|
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 {
|
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)
|
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
charsetModule "code.gitea.io/gitea/modules/charset"
|
charsetModule "code.gitea.io/gitea/modules/charset"
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/httpcache"
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/typesniffer"
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/gzhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServeHeaderOptions struct {
|
type ServeHeaderOptions struct {
|
||||||
@ -38,6 +41,11 @@ type ServeHeaderOptions struct {
|
|||||||
func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
|
func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
|
||||||
header := w.Header()
|
header := w.Header()
|
||||||
|
|
||||||
|
skipCompressionExts := container.SetOf(".gz", ".bz2", ".zip", ".xz", ".zst", ".deb", ".apk", ".jar", ".png", ".jpg", ".webp")
|
||||||
|
if skipCompressionExts.Contains(strings.ToLower(path.Ext(opts.Filename))) {
|
||||||
|
w.Header().Add(gzhttp.HeaderNoCompression, "1")
|
||||||
|
}
|
||||||
|
|
||||||
contentType := typesniffer.ApplicationOctetStream
|
contentType := typesniffer.ApplicationOctetStream
|
||||||
if opts.ContentType != "" {
|
if opts.ContentType != "" {
|
||||||
if opts.ContentTypeCharset != "" {
|
if opts.ContentTypeCharset != "" {
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
package httplib
|
package httplib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -11,6 +13,10 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RequestContextKeyStruct struct{}
|
||||||
|
|
||||||
|
var RequestContextKey = RequestContextKeyStruct{}
|
||||||
|
|
||||||
func urlIsRelative(s string, u *url.URL) bool {
|
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"
|
// 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
|
// 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)
|
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)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@ -45,5 +100,6 @@ func IsCurrentGiteaSiteURL(s string) bool {
|
|||||||
if u.Path == "" {
|
if u.Path == "" {
|
||||||
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
|
package httplib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"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) {
|
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
|
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
|
||||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||||
|
ctx := context.Background()
|
||||||
good := []string{
|
good := []string{
|
||||||
"?key=val",
|
"?key=val",
|
||||||
"/sub",
|
"/sub",
|
||||||
@ -50,7 +87,7 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
|||||||
"http://localhost:3000/sub/",
|
"http://localhost:3000/sub/",
|
||||||
}
|
}
|
||||||
for _, s := range good {
|
for _, s := range good {
|
||||||
assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s)
|
assert.True(t, IsCurrentGiteaSiteURL(ctx, s), "good = %q", s)
|
||||||
}
|
}
|
||||||
bad := []string{
|
bad := []string{
|
||||||
".",
|
".",
|
||||||
@ -64,13 +101,23 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
|||||||
"http://other/",
|
"http://other/",
|
||||||
}
|
}
|
||||||
for _, s := range bad {
|
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.AppURL = "http://localhost:3000/"
|
||||||
setting.AppSubURL = ""
|
setting.AppSubURL = ""
|
||||||
assert.False(t, IsCurrentGiteaSiteURL("//"))
|
assert.False(t, IsCurrentGiteaSiteURL(ctx, "//"))
|
||||||
assert.False(t, IsCurrentGiteaSiteURL("\\\\"))
|
assert.False(t, IsCurrentGiteaSiteURL(ctx, "\\\\"))
|
||||||
assert.False(t, IsCurrentGiteaSiteURL("http://localhost"))
|
assert.False(t, IsCurrentGiteaSiteURL(ctx, "http://localhost"))
|
||||||
assert.True(t, IsCurrentGiteaSiteURL("http://localhost:3000?key=val"))
|
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"))
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
unicodeNormalizeName = "unicodeNormalize"
|
unicodeNormalizeName = "unicodeNormalize"
|
||||||
maxBatchSize = 16
|
maxBatchSize = 16
|
||||||
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
|
|
||||||
fuzzyDenominator = 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
|
func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
|
||||||
@ -245,7 +243,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
|||||||
phraseQuery.Analyzer = repoIndexerAnalyzer
|
phraseQuery.Analyzer = repoIndexerAnalyzer
|
||||||
keywordQuery = phraseQuery
|
keywordQuery = phraseQuery
|
||||||
if opts.IsKeywordFuzzy {
|
if opts.IsKeywordFuzzy {
|
||||||
phraseQuery.Fuzziness = len(opts.Keyword) / fuzzyDenominator
|
phraseQuery.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(opts.Keyword)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.RepoIDs) > 0 {
|
if len(opts.RepoIDs) > 0 {
|
||||||
|
@ -178,12 +178,6 @@ func Init() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
rIndexer = elasticsearch.NewIndexer(setting.Indexer.RepoConnStr, setting.Indexer.RepoIndexerName)
|
rIndexer = elasticsearch.NewIndexer(setting.Indexer.RepoConnStr, setting.Indexer.RepoIndexerName)
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
(*globalIndexer.Load()).Close()
|
|
||||||
close(waitChannel)
|
|
||||||
log.Fatal("PID: %d Unable to create the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err)
|
|
||||||
}
|
|
||||||
existed, err = rIndexer.Init(ctx)
|
existed, err = rIndexer.Init(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
|
@ -47,3 +47,15 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
|
|||||||
|
|
||||||
return index, 0, nil
|
return index, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GuessFuzzinessByKeyword(s string) int {
|
||||||
|
// according to https://github.com/blevesearch/bleve/issues/1563, the supported max fuzziness is 2
|
||||||
|
// magic number 4 was chosen to determine the levenshtein distance per each character of a keyword
|
||||||
|
// BUT, when using CJK (eg: `갃갃갃` `啊啊啊`), it mismatches a lot.
|
||||||
|
for _, r := range s {
|
||||||
|
if r >= 128 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return min(2, len(s)/4)
|
||||||
|
}
|
||||||
|
@ -35,11 +35,7 @@ func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const maxBatchSize = 16
|
||||||
maxBatchSize = 16
|
|
||||||
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
|
|
||||||
fuzzyDenominator = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// IndexerData an update to the issue indexer
|
// IndexerData an update to the issue indexer
|
||||||
type IndexerData internal.IndexerData
|
type IndexerData internal.IndexerData
|
||||||
@ -162,7 +158,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||||||
if options.Keyword != "" {
|
if options.Keyword != "" {
|
||||||
fuzziness := 0
|
fuzziness := 0
|
||||||
if options.IsFuzzyKeyword {
|
if options.IsFuzzyKeyword {
|
||||||
fuzziness = len(options.Keyword) / fuzzyDenominator
|
fuzziness = inner_bleve.GuessFuzzinessByKeyword(options.Keyword)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
|
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
|
||||||
|
@ -41,7 +41,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c
|
|||||||
go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
|
go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
|
||||||
|
|
||||||
// 1. Run batch-check on all objects in the repository
|
// 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()
|
revListReader, revListWriter := io.Pipe()
|
||||||
shasToCheckReader, shasToCheckWriter := io.Pipe()
|
shasToCheckReader, shasToCheckWriter := io.Pipe()
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ var (
|
|||||||
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
||||||
|
|
||||||
// anyHashPattern splits url containing SHA into parts
|
// 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 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]+)?`)
|
comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`)
|
||||||
@ -591,17 +592,17 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) {
|
|||||||
|
|
||||||
func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
start := 0
|
start := 0
|
||||||
next := node.NextSibling
|
nodeStop := node.NextSibling
|
||||||
for node != nil && node != next && start < len(node.Data) {
|
for node != nodeStop {
|
||||||
// We replace only the first mention; other mentions will be addressed later
|
found, loc := references.FindFirstMentionBytes(util.UnsafeStringToBytes(node.Data[start:]))
|
||||||
found, loc := references.FindFirstMentionBytes([]byte(node.Data[start:]))
|
|
||||||
if !found {
|
if !found {
|
||||||
return
|
node = node.NextSibling
|
||||||
|
start = 0
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
loc.Start += start
|
loc.Start += start
|
||||||
loc.End += start
|
loc.End += start
|
||||||
mention := node.Data[loc.Start:loc.End]
|
mention := node.Data[loc.Start:loc.End]
|
||||||
var teams string
|
|
||||||
teams, ok := ctx.Metas["teams"]
|
teams, ok := ctx.Metas["teams"]
|
||||||
// FIXME: util.URLJoin may not be necessary here:
|
// FIXME: util.URLJoin may not be necessary here:
|
||||||
// - setting.AppURL is defined to have a terminal '/' so unless mention[1:]
|
// - setting.AppURL is defined to have a terminal '/' so unless mention[1:]
|
||||||
@ -623,10 +624,10 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
|
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
|
||||||
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "mention"))
|
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "mention"))
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
|
start = 0
|
||||||
} else {
|
} else {
|
||||||
node = node.NextSibling
|
start = loc.End
|
||||||
}
|
}
|
||||||
start = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -963,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
|
// fullHashPatternProcessor renders SHA containing URLs
|
||||||
func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.Metas == nil {
|
if ctx.Metas == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
nodeStop := node.NextSibling
|
||||||
next := node.NextSibling
|
for node != nodeStop {
|
||||||
for node != nil && node != next {
|
if node.Type != html.TextNode {
|
||||||
m := anyHashPattern.FindStringSubmatchIndex(node.Data)
|
node = node.NextSibling
|
||||||
if m == nil {
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
ret, ok := anyHashPatternExtract(node.Data)
|
||||||
urlFull := node.Data[m[0]:m[1]]
|
if !ok {
|
||||||
text := base.ShortSha(node.Data[m[2]:m[3]])
|
node = node.NextSibling
|
||||||
|
continue
|
||||||
// 3rd capture group matches a optional path
|
|
||||||
subpath := ""
|
|
||||||
if m[5] > 0 {
|
|
||||||
subpath = node.Data[m[4]:m[5]]
|
|
||||||
}
|
}
|
||||||
|
text := base.ShortSha(ret.CommitID)
|
||||||
// 4th capture group matches a optional url hash
|
if ret.SubPath != "" {
|
||||||
hash := ""
|
text += ret.SubPath
|
||||||
if m[7] > 0 {
|
|
||||||
hash = node.Data[m[6]:m[7]][1:]
|
|
||||||
}
|
}
|
||||||
|
if ret.QueryHash != "" {
|
||||||
start := m[0]
|
text += " (" + ret.QueryHash + ")"
|
||||||
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]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
replaceContent(node, ret.PosStart, ret.PosEnd, createCodeLink(ret.FullURL, text, "commit"))
|
||||||
if subpath != "" {
|
|
||||||
text += subpath
|
|
||||||
}
|
|
||||||
|
|
||||||
if hash != "" {
|
|
||||||
text += " (" + hash + ")"
|
|
||||||
}
|
|
||||||
replaceContent(node, start, end, createCodeLink(urlFull, text, "commit"))
|
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1022,19 +1034,16 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
if ctx.Metas == nil {
|
if ctx.Metas == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
nodeStop := node.NextSibling
|
||||||
next := node.NextSibling
|
for node != nodeStop {
|
||||||
for node != nil && node != next {
|
if node.Type != html.TextNode {
|
||||||
m := comparePattern.FindStringSubmatchIndex(node.Data)
|
node = node.NextSibling
|
||||||
if m == nil {
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
m := comparePattern.FindStringSubmatchIndex(node.Data)
|
||||||
// Ensure that every group (m[0]...m[7]) has a match
|
if m == nil || slices.Contains(m[:8], -1) { // ensure that every group (m[0]...m[7]) has a match
|
||||||
for i := 0; i < 8; i++ {
|
node = node.NextSibling
|
||||||
if m[i] == -1 {
|
continue
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
urlFull := node.Data[m[0]:m[1]]
|
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]],
|
CommitID: node.Data[m[6]:m[7]],
|
||||||
FilePath: node.Data[m[8]:m[9]],
|
FilePath: node.Data[m[8]:m[9]],
|
||||||
}
|
}
|
||||||
if !httplib.IsCurrentGiteaSiteURL(opts.FullURL) {
|
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, opts.FullURL) {
|
||||||
return 0, 0, "", nil
|
return 0, 0, "", nil
|
||||||
}
|
}
|
||||||
u, err := url.Parse(opts.FilePath)
|
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) {
|
func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
for node != nil {
|
nodeStop := node.NextSibling
|
||||||
|
for node != nodeStop {
|
||||||
if node.Type != html.TextNode {
|
if node.Type != html.TextNode {
|
||||||
node = node.NextSibling
|
node = node.NextSibling
|
||||||
continue
|
continue
|
||||||
|
@ -399,36 +399,61 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRegExp_anySHA1Pattern(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": {
|
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": {
|
||||||
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||||
"/test/unit/event.js",
|
SubPath: "/test/unit/event.js",
|
||||||
"#L2703",
|
QueryHash: "L2703",
|
||||||
},
|
},
|
||||||
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": {
|
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": {
|
||||||
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||||
"/test/unit/event.js",
|
SubPath: "/test/unit/event.js",
|
||||||
"",
|
|
||||||
},
|
},
|
||||||
"https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": {
|
"https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": {
|
||||||
"0705be475092aede1eddae01319ec931fb9c65fc",
|
CommitID: "0705be475092aede1eddae01319ec931fb9c65fc",
|
||||||
"",
|
|
||||||
"",
|
|
||||||
},
|
},
|
||||||
"https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": {
|
"https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": {
|
||||||
"0705be475092aede1eddae01319ec931fb9c65fc",
|
CommitID: "0705be475092aede1eddae01319ec931fb9c65fc",
|
||||||
"/src",
|
SubPath: "/src",
|
||||||
"",
|
|
||||||
},
|
},
|
||||||
"https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": {
|
"https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": {
|
||||||
"d8a994ef243349f321568f9e36d5c3f444b99cae",
|
CommitID: "d8a994ef243349f321568f9e36d5c3f444b99cae",
|
||||||
"",
|
QueryHash: "diff-2",
|
||||||
"#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 {
|
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(
|
test(
|
||||||
util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
|
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>`)
|
`<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) {
|
func TestMisc_IsSameDomain(t *testing.T) {
|
||||||
@ -695,7 +700,7 @@ func TestIssue18471(t *testing.T) {
|
|||||||
}, strings.NewReader(data), &res)
|
}, strings.NewReader(data), &res)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
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) {
|
func TestIsFullURL(t *testing.T) {
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ type HookOptions struct {
|
|||||||
GitQuarantinePath string
|
GitQuarantinePath string
|
||||||
GitPushOptions GitPushOptions
|
GitPushOptions GitPushOptions
|
||||||
PullRequestID int64
|
PullRequestID int64
|
||||||
|
PushTrigger repository.PushTrigger
|
||||||
DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user.
|
DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user.
|
||||||
IsWiki bool
|
IsWiki bool
|
||||||
ActionPerm int
|
ActionPerm int
|
||||||
|
@ -29,7 +29,7 @@ var (
|
|||||||
// TODO: fix invalid linking issue
|
// TODO: fix invalid linking issue
|
||||||
|
|
||||||
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
|
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
|
||||||
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`)
|
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[-\w][-.\w]*?|@[-\w][-.\w]*?/[-\w][-.\w]*?)(?:\s|$|[:,;.?!](\s|$)|'|\)|\])`)
|
||||||
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
|
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
|
||||||
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
|
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
|
||||||
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
|
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
|
||||||
|
@ -392,6 +392,7 @@ func TestRegExp_mentionPattern(t *testing.T) {
|
|||||||
{"@gitea,", "@gitea"},
|
{"@gitea,", "@gitea"},
|
||||||
{"@gitea;", "@gitea"},
|
{"@gitea;", "@gitea"},
|
||||||
{"@gitea/team1;", "@gitea/team1"},
|
{"@gitea/team1;", "@gitea/team1"},
|
||||||
|
{"@user's idea", "@user"},
|
||||||
}
|
}
|
||||||
falseTestCases := []string{
|
falseTestCases := []string{
|
||||||
"@ 0",
|
"@ 0",
|
||||||
@ -412,7 +413,6 @@ func TestRegExp_mentionPattern(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range trueTestCases {
|
for _, testCase := range trueTestCases {
|
||||||
found := mentionPattern.FindStringSubmatch(testCase.pat)
|
found := mentionPattern.FindStringSubmatch(testCase.pat)
|
||||||
assert.Len(t, found, 2)
|
|
||||||
assert.Equal(t, testCase.exp, found[1])
|
assert.Equal(t, testCase.exp, found[1])
|
||||||
}
|
}
|
||||||
for _, testCase := range falseTestCases {
|
for _, testCase := range falseTestCases {
|
||||||
|
@ -5,6 +5,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
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) {
|
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]{}
|
allBranches := container.Set[string]{}
|
||||||
{
|
{
|
||||||
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
||||||
|
31
modules/repository/branch_test.go
Normal file
31
modules/repository/branch_test.go
Normal file
@ -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
|
EnvKeyID = "GITEA_KEY_ID" // public key ID
|
||||||
EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID"
|
EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID"
|
||||||
EnvPRID = "GITEA_PR_ID"
|
EnvPRID = "GITEA_PR_ID"
|
||||||
|
EnvPushTrigger = "GITEA_PUSH_TRIGGER"
|
||||||
EnvIsInternal = "GITEA_INTERNAL_PUSH"
|
EnvIsInternal = "GITEA_INTERNAL_PUSH"
|
||||||
EnvAppURL = "GITEA_ROOT_URL"
|
EnvAppURL = "GITEA_ROOT_URL"
|
||||||
EnvActionPerm = "GITEA_ACTION_PERM"
|
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
|
// 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
|
// 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
|
// or if you absolutely are sure that post-receive and pre-receive will do nothing
|
||||||
|
32
modules/setting/glob.go
Normal file
32
modules/setting/glob.go
Normal file
@ -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"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Indexer settings
|
// Indexer settings
|
||||||
@ -30,8 +28,8 @@ var Indexer = struct {
|
|||||||
RepoConnStr string
|
RepoConnStr string
|
||||||
RepoIndexerName string
|
RepoIndexerName string
|
||||||
MaxIndexerFileSize int64
|
MaxIndexerFileSize int64
|
||||||
IncludePatterns []glob.Glob
|
IncludePatterns []*GlobMatcher
|
||||||
ExcludePatterns []glob.Glob
|
ExcludePatterns []*GlobMatcher
|
||||||
ExcludeVendored bool
|
ExcludeVendored bool
|
||||||
}{
|
}{
|
||||||
IssueType: "bleve",
|
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
|
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
|
||||||
func IndexerGlobFromString(globstr string) []glob.Glob {
|
func IndexerGlobFromString(globstr string) []*GlobMatcher {
|
||||||
extarr := make([]glob.Glob, 0, 10)
|
extarr := make([]*GlobMatcher, 0, 10)
|
||||||
for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
|
for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
|
||||||
expr = strings.TrimSpace(expr)
|
expr = strings.TrimSpace(expr)
|
||||||
if 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)
|
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
||||||
} else {
|
} else {
|
||||||
extarr = append(extarr, g)
|
extarr = append(extarr, g)
|
||||||
|
@ -85,7 +85,7 @@ type CreatePullRequestOption struct {
|
|||||||
// EditPullRequestOption options when modify pull request
|
// EditPullRequestOption options when modify pull request
|
||||||
type EditPullRequestOption struct {
|
type EditPullRequestOption struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body string `json:"body"`
|
Body *string `json:"body"`
|
||||||
Base string `json:"base"`
|
Base string `json:"base"`
|
||||||
Assignee string `json:"assignee"`
|
Assignee string `json:"assignee"`
|
||||||
Assignees []string `json:"assignees"`
|
Assignees []string `json:"assignees"`
|
||||||
|
34
modules/structs/repo_actions.go
Normal file
34
modules/structs/repo_actions.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActionTask represents a ActionTask
|
||||||
|
type ActionTask struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
HeadBranch string `json:"head_branch"`
|
||||||
|
HeadSHA string `json:"head_sha"`
|
||||||
|
RunNumber int64 `json:"run_number"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
DisplayTitle string `json:"display_title"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
WorkflowID string `json:"workflow_id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
RunStartedAt time.Time `json:"run_started_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionTaskResponse returns a ActionTask
|
||||||
|
type ActionTaskResponse struct {
|
||||||
|
Entries []*ActionTask `json:"workflow_runs"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
}
|
@ -121,29 +121,25 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
|
|||||||
// RenderLabel renders a label
|
// RenderLabel renders a label
|
||||||
// locale is needed due to an import cycle with our context providing the `Tr` function
|
// locale is needed due to an import cycle with our context providing the `Tr` function
|
||||||
func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
||||||
var (
|
var extraCSSClasses string
|
||||||
archivedCSSClass string
|
textColor := util.ContrastColor(label.Color)
|
||||||
textColor = util.ContrastColor(label.Color)
|
labelScope := label.ExclusiveScope()
|
||||||
labelScope = label.ExclusiveScope()
|
descriptionText := emoji.ReplaceAliases(label.Description)
|
||||||
)
|
|
||||||
|
|
||||||
description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description))
|
|
||||||
|
|
||||||
if label.IsArchived() {
|
if label.IsArchived() {
|
||||||
archivedCSSClass = "archived-label"
|
extraCSSClasses = "archived-label"
|
||||||
description = fmt.Sprintf("(%s) %s", locale.TrString("archived"), description)
|
descriptionText = fmt.Sprintf("(%s) %s", locale.TrString("archived"), descriptionText)
|
||||||
}
|
}
|
||||||
|
|
||||||
if labelScope == "" {
|
if labelScope == "" {
|
||||||
// Regular label
|
// Regular label
|
||||||
s := fmt.Sprintf("<div class='ui label %s' style='color: %s !important; background-color: %s !important;' data-tooltip-content title='%s'>%s</div>",
|
return HTMLFormat(`<div class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s">%s</div>`,
|
||||||
archivedCSSClass, textColor, label.Color, description, RenderEmoji(ctx, label.Name))
|
extraCSSClasses, textColor, label.Color, descriptionText, RenderEmoji(ctx, label.Name))
|
||||||
return template.HTML(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scoped label
|
// Scoped label
|
||||||
scopeText := RenderEmoji(ctx, labelScope)
|
scopeHTML := RenderEmoji(ctx, labelScope)
|
||||||
itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:])
|
itemHTML := RenderEmoji(ctx, label.Name[len(labelScope)+1:])
|
||||||
|
|
||||||
// Make scope and item background colors slightly darker and lighter respectively.
|
// Make scope and item background colors slightly darker and lighter respectively.
|
||||||
// More contrast needed with higher luminance, empirically tweaked.
|
// More contrast needed with higher luminance, empirically tweaked.
|
||||||
@ -171,14 +167,13 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m
|
|||||||
itemColor := "#" + hex.EncodeToString(itemBytes)
|
itemColor := "#" + hex.EncodeToString(itemBytes)
|
||||||
scopeColor := "#" + hex.EncodeToString(scopeBytes)
|
scopeColor := "#" + hex.EncodeToString(scopeBytes)
|
||||||
|
|
||||||
s := fmt.Sprintf("<span class='ui label %s scope-parent' data-tooltip-content title='%s'>"+
|
return HTMLFormat(`<span class="ui label %s scope-parent" data-tooltip-content title="%s">`+
|
||||||
"<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
`<div class="ui label scope-left" style="color: %s !important; background-color: %s !important">%s</div>`+
|
||||||
"<div class='ui label scope-right' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
`<div class="ui label scope-right" style="color: %s !important; background-color: %s !important">%s</div>`+
|
||||||
"</span>",
|
`</span>`,
|
||||||
archivedCSSClass, description,
|
extraCSSClasses, descriptionText,
|
||||||
textColor, scopeColor, scopeText,
|
textColor, scopeColor, scopeHTML,
|
||||||
textColor, itemColor, itemText)
|
textColor, itemColor, itemHTML)
|
||||||
return template.HTML(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderEmoji renders html text with emoji post processors
|
// RenderEmoji renders html text with emoji post processors
|
||||||
|
@ -207,3 +207,8 @@ func TestRenderLabels(t *testing.T) {
|
|||||||
expected = `/owner/repo/pulls?labels=123`
|
expected = `/owner/repo/pulls?labels=123`
|
||||||
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
|
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserMention(t *testing.T) {
|
||||||
|
rendered := RenderMarkdownToHtml(context.Background(), "@no-such-user @mention-user @mention-user")
|
||||||
|
assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" rel="nofollow">@mention-user</a> <a href="/mention-user" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
|
||||||
|
}
|
||||||
|
@ -3495,6 +3495,7 @@ npm.install=Para instalar o pacote usando o npm, execute o seguinte comando:
|
|||||||
npm.install2=ou adicione-o ao ficheiro <code>package.json</code>:
|
npm.install2=ou adicione-o ao ficheiro <code>package.json</code>:
|
||||||
npm.dependencies=Dependências
|
npm.dependencies=Dependências
|
||||||
npm.dependencies.development=Dependências de desenvolvimento
|
npm.dependencies.development=Dependências de desenvolvimento
|
||||||
|
npm.dependencies.bundle=Dependências agregadas
|
||||||
npm.dependencies.peer=Dependências de pares
|
npm.dependencies.peer=Dependências de pares
|
||||||
npm.dependencies.optional=Dependências opcionais
|
npm.dependencies.optional=Dependências opcionais
|
||||||
npm.details.tag=Etiqueta
|
npm.details.tag=Etiqueta
|
||||||
|
52
package-lock.json
generated
52
package-lock.json
generated
@ -14,6 +14,7 @@
|
|||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.9.0",
|
"@primer/octicons": "19.9.0",
|
||||||
|
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||||
"add-asset-webpack-plugin": "2.0.1",
|
"add-asset-webpack-plugin": "2.0.1",
|
||||||
"ansi_up": "6.0.2",
|
"ansi_up": "6.0.2",
|
||||||
"asciinema-player": "3.7.1",
|
"asciinema-player": "3.7.1",
|
||||||
@ -42,7 +43,6 @@
|
|||||||
"postcss": "8.4.38",
|
"postcss": "8.4.38",
|
||||||
"postcss-loader": "8.1.1",
|
"postcss-loader": "8.1.1",
|
||||||
"postcss-nesting": "12.1.2",
|
"postcss-nesting": "12.1.2",
|
||||||
"pretty-ms": "9.0.0",
|
|
||||||
"sortablejs": "1.15.2",
|
"sortablejs": "1.15.2",
|
||||||
"swagger-ui-dist": "5.17.2",
|
"swagger-ui-dist": "5.17.2",
|
||||||
"tailwindcss": "3.4.3",
|
"tailwindcss": "3.4.3",
|
||||||
@ -58,7 +58,6 @@
|
|||||||
"vue-bar-graph": "2.0.0",
|
"vue-bar-graph": "2.0.0",
|
||||||
"vue-chartjs": "5.3.1",
|
"vue-chartjs": "5.3.1",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
"vue3-calendar-heatmap": "2.0.5",
|
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.91.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"wrap-ansi": "9.0.0"
|
"wrap-ansi": "9.0.0"
|
||||||
@ -1627,6 +1626,18 @@
|
|||||||
"win32"
|
"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": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
@ -9170,17 +9181,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/parse-ms": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@ -9772,20 +9772,6 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pretty-ms": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==",
|
|
||||||
"dependencies": {
|
|
||||||
"parse-ms": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/printable-characters": {
|
"node_modules/printable-characters": {
|
||||||
"version": "1.0.42",
|
"version": "1.0.42",
|
||||||
"resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
|
"resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
|
||||||
@ -12226,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": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.9.0",
|
"@primer/octicons": "19.9.0",
|
||||||
|
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||||
"add-asset-webpack-plugin": "2.0.1",
|
"add-asset-webpack-plugin": "2.0.1",
|
||||||
"ansi_up": "6.0.2",
|
"ansi_up": "6.0.2",
|
||||||
"asciinema-player": "3.7.1",
|
"asciinema-player": "3.7.1",
|
||||||
@ -41,7 +42,6 @@
|
|||||||
"postcss": "8.4.38",
|
"postcss": "8.4.38",
|
||||||
"postcss-loader": "8.1.1",
|
"postcss-loader": "8.1.1",
|
||||||
"postcss-nesting": "12.1.2",
|
"postcss-nesting": "12.1.2",
|
||||||
"pretty-ms": "9.0.0",
|
|
||||||
"sortablejs": "1.15.2",
|
"sortablejs": "1.15.2",
|
||||||
"swagger-ui-dist": "5.17.2",
|
"swagger-ui-dist": "5.17.2",
|
||||||
"tailwindcss": "3.4.3",
|
"tailwindcss": "3.4.3",
|
||||||
@ -57,7 +57,6 @@
|
|||||||
"vue-bar-graph": "2.0.0",
|
"vue-bar-graph": "2.0.0",
|
||||||
"vue-chartjs": "5.3.1",
|
"vue-chartjs": "5.3.1",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
"vue3-calendar-heatmap": "2.0.5",
|
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.91.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"wrap-ansi": "9.0.0"
|
"wrap-ansi": "9.0.0"
|
||||||
|
@ -71,6 +71,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/actions"
|
"code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -184,8 +185,8 @@ type artifactRoutes struct {
|
|||||||
fs storage.ObjectStorage
|
fs storage.ObjectStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar artifactRoutes) buildArtifactURL(runID int64, artifactHash, suffix string) string {
|
func (ar artifactRoutes) buildArtifactURL(ctx *ArtifactContext, runID int64, artifactHash, suffix string) string {
|
||||||
uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ar.prefix, "/") +
|
uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(ar.prefix, "/") +
|
||||||
strings.ReplaceAll(artifactRouteBase, "{run_id}", strconv.FormatInt(runID, 10)) +
|
strings.ReplaceAll(artifactRouteBase, "{run_id}", strconv.FormatInt(runID, 10)) +
|
||||||
"/" + artifactHash + "/" + suffix
|
"/" + artifactHash + "/" + suffix
|
||||||
return uploadURL
|
return uploadURL
|
||||||
@ -224,7 +225,7 @@ func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
|
|||||||
// use md5(artifact_name) to create upload url
|
// use md5(artifact_name) to create upload url
|
||||||
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name)))
|
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name)))
|
||||||
resp := getUploadArtifactResponse{
|
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)
|
log.Debug("[artifact] get upload url: %s", resp.FileContainerResourceURL)
|
||||||
ctx.JSON(http.StatusOK, resp)
|
ctx.JSON(http.StatusOK, resp)
|
||||||
@ -365,7 +366,7 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
|
|||||||
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(art.ArtifactName)))
|
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(art.ArtifactName)))
|
||||||
item := listArtifactsResponseItem{
|
item := listArtifactsResponseItem{
|
||||||
Name: art.ArtifactName,
|
Name: art.ArtifactName,
|
||||||
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "download_url"),
|
FileContainerResourceURL: ar.buildArtifactURL(ctx, runID, artifactHash, "download_url"),
|
||||||
}
|
}
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
values[art.ArtifactName] = true
|
values[art.ArtifactName] = true
|
||||||
@ -437,7 +438,7 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if downloadURL == "" {
|
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{
|
item := downloadArtifactResponseItem{
|
||||||
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
|
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
|
||||||
|
@ -92,6 +92,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/actions"
|
"code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
@ -160,9 +161,9 @@ func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, tas
|
|||||||
return mac.Sum(nil)
|
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")
|
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)
|
"/" + 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
|
return uploadURL
|
||||||
}
|
}
|
||||||
@ -278,7 +279,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
|
|||||||
|
|
||||||
respData := CreateArtifactResponse{
|
respData := CreateArtifactResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
SignedUploadUrl: r.buildArtifactURL("UploadArtifact", artifactName, ctx.ActionTask.ID),
|
SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID),
|
||||||
}
|
}
|
||||||
r.sendProtbufBody(ctx, &respData)
|
r.sendProtbufBody(ctx, &respData)
|
||||||
}
|
}
|
||||||
@ -454,7 +455,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if respData.SignedUrl == "" {
|
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)
|
r.sendProtbufBody(ctx, &respData)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
container_model "code.gitea.io/gitea/models/packages/container"
|
container_model "code.gitea.io/gitea/models/packages/container"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "code.gitea.io/gitea/modules/packages"
|
||||||
@ -115,7 +116,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func apiUnauthorizedError(ctx *context.Context) {
|
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)
|
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-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
|
||||||
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
|
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
|
||||||
|
|
||||||
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
|
_, _ = ctx.Resp.Write(xmlMetadataWithHeader)
|
||||||
log.Error("write bytes failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
|
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
|
||||||
|
@ -1168,6 +1168,9 @@ func Routes() *web.Route {
|
|||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
||||||
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
||||||
|
m.Group("/actions", func() {
|
||||||
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
|
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
||||||
m.Group("/keys", func() {
|
m.Group("/keys", func() {
|
||||||
m.Combo("").Get(repo.ListDeployKeys).
|
m.Combo("").Get(repo.ListDeployKeys).
|
||||||
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
|
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
actions_service "code.gitea.io/gitea/services/actions"
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -517,3 +518,68 @@ type Action struct{}
|
|||||||
func NewAction() actions_service.API {
|
func NewAction() actions_service.API {
|
||||||
return Action{}
|
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)
|
||||||
|
}
|
||||||
|
@ -29,7 +29,6 @@ import (
|
|||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
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
|
// SearchIssues searches for issues across the repositories that the user has access to
|
||||||
@ -803,12 +802,19 @@ func EditIssue(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTitle := issue.Title
|
|
||||||
if len(form.Title) > 0 {
|
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 {
|
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 {
|
if form.Ref != nil {
|
||||||
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
|
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
|
||||||
@ -880,24 +886,14 @@ func EditIssue(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||||
}
|
if issues_model.IsErrDependenciesLeft(err) {
|
||||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||||
if err != nil {
|
return
|
||||||
if issues_model.IsErrDependenciesLeft(err) {
|
}
|
||||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||||
return
|
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
|
// Refetch from database to assign some automatic values
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/attachment"
|
"code.gitea.io/gitea/services/attachment"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
)
|
)
|
||||||
@ -153,6 +154,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
@ -185,7 +188,11 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
|||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/attachment"
|
"code.gitea.io/gitea/services/attachment"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
)
|
)
|
||||||
@ -160,6 +161,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
@ -194,9 +197,14 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
|||||||
CommentID: comment.ID,
|
CommentID: comment.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.LoadAttachments(ctx); err != nil {
|
if err := comment.LoadAttachments(ctx); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
|
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
|
||||||
return
|
return
|
||||||
|
@ -602,12 +602,19 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTitle := issue.Title
|
|
||||||
if len(form.Title) > 0 {
|
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 {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update or remove deadline if set
|
// 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")
|
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||||
}
|
if issues_model.IsErrDependenciesLeft(err) {
|
||||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||||
if err != nil {
|
return
|
||||||
if issues_model.IsErrDependenciesLeft(err) {
|
}
|
||||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||||
return
|
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
|
// change pull target branch
|
||||||
|
@ -415,6 +415,13 @@ type swaggerRepoNewIssuePinsAllowed struct {
|
|||||||
Body api.NewIssuePinsAllowed `json:"body"`
|
Body api.NewIssuePinsAllowed `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TasksList
|
||||||
|
// swagger:response TasksList
|
||||||
|
type swaggerRepoTasksList struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionTaskResponse `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
// swagger:response Compare
|
// swagger:response Compare
|
||||||
type swaggerCompare struct {
|
type swaggerCompare struct {
|
||||||
// in:body
|
// in:body
|
||||||
|
@ -6,10 +6,8 @@ package user
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"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)
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
return
|
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))
|
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,13 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
go_context "context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"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(middleware.WithContextData(req.Context()))
|
||||||
|
req = req.WithContext(go_context.WithValue(req.Context(), httplib.RequestContextKey, req))
|
||||||
next.ServeHTTP(resp, 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",
|
// 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.
|
// then frontend needs this delegate to redirect to the new location with hash correctly.
|
||||||
redirect := req.PostFormValue("redirect")
|
redirect := req.PostFormValue("redirect")
|
||||||
if !httplib.IsCurrentGiteaSiteURL(redirect) {
|
if !httplib.IsCurrentGiteaSiteURL(req.Context(), redirect) {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/system"
|
"code.gitea.io/gitea/modules/system"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/modules/web/routing"
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
actions_router "code.gitea.io/gitea/routers/api/actions"
|
actions_router "code.gitea.io/gitea/routers/api/actions"
|
||||||
@ -112,7 +113,10 @@ func InitWebInstallPage(ctx context.Context) {
|
|||||||
// InitWebInstalled is for global installed configuration.
|
// InitWebInstalled is for global installed configuration.
|
||||||
func InitWebInstalled(ctx context.Context) {
|
func InitWebInstalled(ctx context.Context) {
|
||||||
mustInitCtx(ctx, git.InitFull)
|
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
|
// Setup i18n
|
||||||
translation.InitLocales(ctx)
|
translation.InitLocales(ctx)
|
||||||
|
@ -4,20 +4,25 @@
|
|||||||
package private
|
package private
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
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"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
timeutil "code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
gitea_context "code.gitea.io/gitea/services/context"
|
gitea_context "code.gitea.io/gitea/services/context"
|
||||||
@ -117,16 +122,14 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(branchesToSync) > 0 {
|
if len(branchesToSync) > 0 {
|
||||||
if gitRepo == nil {
|
var err error
|
||||||
var err error
|
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
|
||||||
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
|
if err != nil {
|
||||||
if err != nil {
|
log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
|
||||||
log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
|
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err),
|
||||||
Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err),
|
})
|
||||||
})
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -160,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)
|
isPrivate := opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate)
|
||||||
isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate)
|
isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate)
|
||||||
// Handle Push Options
|
// Handle Push Options
|
||||||
@ -174,7 +185,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||||||
wasEmpty = repo.IsEmpty
|
wasEmpty = repo.IsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
pusher, err := user_model.GetUserByID(ctx, opts.UserID)
|
pusher, err := loadContextCacheUser(ctx, opts.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
||||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||||
@ -309,3 +320,52 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||||||
RepoWasEmpty: wasEmpty,
|
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"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
49
routers/private/hook_post_receive_test.go
Normal file
49
routers/private/hook_post_receive_test.go
Normal file
@ -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)
|
preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName)
|
||||||
case refFullName.IsTag():
|
case refFullName.IsTag():
|
||||||
preReceiveTag(ourCtx, refFullName)
|
preReceiveTag(ourCtx, refFullName)
|
||||||
case git.DefaultFeatures.SupportProcReceive && refFullName.IsFor():
|
case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor():
|
||||||
preReceiveFor(ourCtx, refFullName)
|
preReceiveFor(ourCtx, refFullName)
|
||||||
default:
|
default:
|
||||||
ourCtx.AssertCanWriteCode()
|
ourCtx.AssertCanWriteCode()
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present
|
// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present
|
||||||
func HookProcReceive(ctx *gitea_context.PrivateContext) {
|
func HookProcReceive(ctx *gitea_context.PrivateContext) {
|
||||||
opts := web.GetForm(ctx).(*private.HookOptions)
|
opts := web.GetForm(ctx).(*private.HookOptions)
|
||||||
if !git.DefaultFeatures.SupportProcReceive {
|
if !git.DefaultFeatures().SupportProcReceive {
|
||||||
ctx.Status(http.StatusNotFound)
|
ctx.Status(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -297,7 +297,7 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Because of the special ref "refs/for" we will need to delay write permission check
|
// 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
|
mode = perm.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ func Config(ctx *context.Context) {
|
|||||||
ctx.Data["OfflineMode"] = setting.OfflineMode
|
ctx.Data["OfflineMode"] = setting.OfflineMode
|
||||||
ctx.Data["RunUser"] = setting.RunUser
|
ctx.Data["RunUser"] = setting.RunUser
|
||||||
ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
|
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["AppDataPath"] = setting.AppDataPath
|
||||||
ctx.Data["RepoRootPath"] = setting.RepoRootPath
|
ctx.Data["RepoRootPath"] = setting.RepoRootPath
|
||||||
|
@ -368,7 +368,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||||||
return setting.AppSubURL + "/"
|
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)
|
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||||
if obeyRedirect {
|
if obeyRedirect {
|
||||||
ctx.RedirectToCurrentSite(redirectTo)
|
ctx.RedirectToCurrentSite(redirectTo)
|
||||||
|
@ -470,8 +470,9 @@ func AuthorizeOAuth(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect if user already granted access
|
// Redirect if user already granted access and the application is confidential.
|
||||||
if grant != nil {
|
// 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)
|
code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleServerError(ctx, form.State, form.RedirectURI)
|
handleServerError(ctx, form.State, form.RedirectURI)
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
|
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
|
||||||
if !git.DefaultFeatures.SupportProcReceive {
|
if !git.DefaultFeatures().SupportProcReceive {
|
||||||
rw.WriteHeader(http.StatusNotFound)
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
|||||||
|
|
||||||
if repoExist {
|
if repoExist {
|
||||||
// Because of special ref "refs/for" .. , need delay write permission check
|
// Because of special ref "refs/for" .. , need delay write permission check
|
||||||
if git.DefaultFeatures.SupportProcReceive {
|
if git.DefaultFeatures().SupportProcReceive {
|
||||||
accessMode = perm.AccessModeRead
|
accessMode = perm.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2177,7 +2177,10 @@ func GetIssueInfo(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToIssue(ctx, ctx.Doer, issue))
|
ctx.JSON(http.StatusOK, map[string]any{
|
||||||
|
"convertedIssue": convert.ToIssue(ctx, ctx.Doer, issue),
|
||||||
|
"renderedLabels": templates.RenderLabels(ctx, ctx.Locale, issue.Labels, ctx.Repo.RepoLink, issue),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateIssueTitle change issue's title
|
// UpdateIssueTitle change issue's title
|
||||||
|
@ -180,7 +180,7 @@ func Create(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
|
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
|
||||||
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
|
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
|
||||||
ctx.Data["SupportedObjectFormats"] = git.SupportedObjectFormats
|
ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats
|
||||||
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
|
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplCreate)
|
ctx.HTML(http.StatusOK, tplCreate)
|
||||||
|
@ -17,6 +17,16 @@ import (
|
|||||||
|
|
||||||
const tplSearch base.TplName = "repo/search"
|
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
|
// Search render repository search page
|
||||||
func Search(ctx *context.Context) {
|
func Search(ctx *context.Context) {
|
||||||
language := ctx.FormTrim("l")
|
language := ctx.FormTrim("l")
|
||||||
@ -28,6 +38,7 @@ func Search(ctx *context.Context) {
|
|||||||
ctx.Data["Language"] = language
|
ctx.Data["Language"] = language
|
||||||
ctx.Data["IsFuzzy"] = isFuzzy
|
ctx.Data["IsFuzzy"] = isFuzzy
|
||||||
ctx.Data["PageIsViewCode"] = true
|
ctx.Data["PageIsViewCode"] = true
|
||||||
|
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||||
|
|
||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
ctx.HTML(http.StatusOK, tplSearch)
|
ctx.HTML(http.StatusOK, tplSearch)
|
||||||
@ -64,8 +75,14 @@ func Search(ctx *context.Context) {
|
|||||||
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
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)
|
ctx.ServerError("GrepSearch", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -86,7 +103,6 @@ func Search(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
|
||||||
ctx.Data["Repo"] = ctx.Repo.Repository
|
ctx.Data["Repo"] = ctx.Repo.Repository
|
||||||
ctx.Data["SearchResults"] = searchResults
|
ctx.Data["SearchResults"] = searchResults
|
||||||
ctx.Data["SearchResultLanguages"] = searchResultLanguages
|
ctx.Data["SearchResultLanguages"] = searchResultLanguages
|
||||||
|
19
routers/web/repo/search_test.go
Normal file
19
routers/web/repo/search_test.go
Normal file
@ -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())
|
||||||
|
}
|
@ -54,7 +54,7 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const GzipMinSize = 1400 // min size to compress for the body size of response
|
var GzipMinSize = 1400 // min size to compress for the body size of response
|
||||||
|
|
||||||
// optionsCorsHandler return a http handler which sets CORS options if enabled by config, it blocks non-CORS OPTIONS requests.
|
// optionsCorsHandler return a http handler which sets CORS options if enabled by config, it blocks non-CORS OPTIONS requests.
|
||||||
func optionsCorsHandler() func(next http.Handler) http.Handler {
|
func optionsCorsHandler() func(next http.Handler) http.Handler {
|
||||||
|
@ -182,7 +182,7 @@ func createProvider(providerName string, source *Source) (goth.Provider, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// always set the name if provider is created so we can support multiple setups of 1 provider
|
// always set the name if provider is created so we can support multiple setups of 1 provider
|
||||||
if err == nil && provider != nil {
|
if provider != nil {
|
||||||
provider.SetName(providerName)
|
provider.SetName(providerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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("Content-Type", "text/plain;charset=utf-8")
|
||||||
b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
b.Resp.WriteHeader(status)
|
b.Resp.WriteHeader(status)
|
||||||
if _, err := b.Resp.Write(bs); err != nil {
|
_, _ = b.Resp.Write(bs)
|
||||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlainTextBytes renders bytes as plain text
|
// PlainTextBytes renders bytes as plain text
|
||||||
@ -256,7 +254,7 @@ func (b *Base) Redirect(location string, status ...int) {
|
|||||||
code = status[0]
|
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
|
// 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
|
// 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)
|
// 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking)
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -51,7 +52,7 @@ func (ctx *Context) RedirectToCurrentSite(location ...string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !httplib.IsCurrentGiteaSiteURL(loc) {
|
if !httplib.IsCurrentGiteaSiteURL(ctx, loc) {
|
||||||
continue
|
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)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +94,19 @@ func MockAPIContext(t *testing.T, reqPath string) (*context.APIContext, *httptes
|
|||||||
return ctx, resp
|
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.
|
// LoadRepo load a repo into a test context.
|
||||||
func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) {
|
func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) {
|
||||||
var doer *user_model.User
|
var doer *user_model.User
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
@ -24,6 +25,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
@ -193,6 +195,31 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToActionTask convert a actions_model.ActionTask to an api.ActionTask
|
||||||
|
func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.ActionTask, error) {
|
||||||
|
if err := t.LoadAttributes(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := strings.TrimSuffix(setting.AppURL, "/") + t.GetRunLink()
|
||||||
|
|
||||||
|
return &api.ActionTask{
|
||||||
|
ID: t.ID,
|
||||||
|
Name: t.Job.Name,
|
||||||
|
HeadBranch: t.Job.Run.PrettyRef(),
|
||||||
|
HeadSHA: t.Job.CommitSHA,
|
||||||
|
RunNumber: t.Job.Run.Index,
|
||||||
|
Event: t.Job.Run.TriggerEvent,
|
||||||
|
DisplayTitle: t.Job.Run.Title,
|
||||||
|
Status: t.Status.String(),
|
||||||
|
WorkflowID: t.Job.Run.WorkflowID,
|
||||||
|
URL: url,
|
||||||
|
CreatedAt: t.Created.AsLocalTime(),
|
||||||
|
UpdatedAt: t.Updated.AsLocalTime(),
|
||||||
|
RunStartedAt: t.Started.AsLocalTime(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
||||||
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
|
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
|
||||||
verif := asymkey_model.ParseCommitWithSignature(ctx, c)
|
verif := asymkey_model.ParseCommitWithSignature(ctx, c)
|
||||||
|
@ -211,13 +211,11 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
|
|||||||
IsArchived: label.IsArchived(),
|
IsArchived: label.IsArchived(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
labelBelongsToRepo := label.BelongsToRepo()
|
||||||
|
|
||||||
// calculate URL
|
// calculate URL
|
||||||
if label.BelongsToRepo() && repo != nil {
|
if labelBelongsToRepo && repo != nil {
|
||||||
if repo != nil {
|
result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID)
|
||||||
result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID)
|
|
||||||
} else {
|
|
||||||
log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID)
|
|
||||||
}
|
|
||||||
} else { // BelongsToOrg
|
} else { // BelongsToOrg
|
||||||
if org != nil {
|
if org != nil {
|
||||||
result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, url.PathEscape(org.Name), label.ID)
|
result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, url.PathEscape(org.Name), label.ID)
|
||||||
@ -226,6 +224,10 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if labelBelongsToRepo && repo == nil {
|
||||||
|
log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID)
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
// so if we are using at least this version of git we don't have to tell ParsePatch to do
|
||||||
// the skipping for us
|
// the skipping for us
|
||||||
parsePatchSkipToFile := opts.SkipTo
|
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)
|
cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo)
|
||||||
parsePatchSkipToFile = ""
|
parsePatchSkipToFile = ""
|
||||||
}
|
}
|
||||||
|
@ -289,8 +289,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to compose independent messages to avoid leaking user emails
|
// Make sure to compose independent messages to avoid leaking user emails
|
||||||
msgID := createReference(ctx.Issue, ctx.Comment, ctx.ActionType)
|
msgID := generateMessageIDForIssue(ctx.Issue, ctx.Comment, ctx.ActionType)
|
||||||
reference := createReference(ctx.Issue, nil, activities_model.ActionType(0))
|
reference := generateMessageIDForIssue(ctx.Issue, nil, activities_model.ActionType(0))
|
||||||
|
|
||||||
var replyPayload []byte
|
var replyPayload []byte
|
||||||
if ctx.Comment != nil {
|
if ctx.Comment != nil {
|
||||||
@ -362,7 +362,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
|||||||
return msgs, nil
|
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
|
var path string
|
||||||
if issue.IsPull {
|
if issue.IsPull {
|
||||||
path = "pulls"
|
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)
|
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 {
|
func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *user_model.User) map[string]string {
|
||||||
repo := ctx.Issue.Repo
|
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))
|
msgs := make([]*Message, 0, len(tos))
|
||||||
publisherName := rel.Publisher.DisplayName()
|
publisherName := rel.Publisher.DisplayName()
|
||||||
relURL := "<" + rel.HTMLURL() + ">"
|
msgID := generateMessageIDForRelease(rel)
|
||||||
for _, to := range tos {
|
for _, to := range tos {
|
||||||
msg := NewMessageFrom(to, publisherName, setting.MailService.FromEmail, subject, mailBody.String())
|
msg := NewMessageFrom(to, publisherName, setting.MailService.FromEmail, subject, mailBody.String())
|
||||||
msg.Info = subject
|
msg.Info = subject
|
||||||
msg.SetHeader("Message-ID", relURL)
|
msg.SetHeader("Message-ID", msgID)
|
||||||
msgs = append(msgs, msg)
|
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)
|
_, _, issue, comment := prepareMailerTest(t)
|
||||||
_, _, pullIssue, _ := prepareMailerTest(t)
|
_, _, pullIssue, _ := prepareMailerTest(t)
|
||||||
pullIssue.IsPull = true
|
pullIssue.IsPull = true
|
||||||
@ -388,10 +388,18 @@ func Test_createReference(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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) {
|
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"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
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"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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))
|
pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
|
||||||
defer pullWorkingPool.CheckOut(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)
|
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
|
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, "", "")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pr.MergedUnix = timeutil.TimeStampNow()
|
// reload pull request because it has been updated by post receive hook
|
||||||
pr.Merger = doer
|
pr, err = issues_model.GetPullRequestByID(ctx, pr.ID)
|
||||||
pr.MergerID = doer.ID
|
if err != nil {
|
||||||
|
return err
|
||||||
if _, err := pr.SetMerged(ctx); err != nil {
|
|
||||||
log.Error("SetMerged %-v: %v", pr, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pr.LoadIssue(ctx); err != nil {
|
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
|
// 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.
|
// Clone base repo.
|
||||||
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -318,11 +309,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use
|
|||||||
pr.BaseRepo.Name,
|
pr.BaseRepo.Name,
|
||||||
pr.ID,
|
pr.ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger))
|
||||||
pushCmd := git.NewCommand(ctx, "push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch)
|
pushCmd := git.NewCommand(ctx, "push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch)
|
||||||
|
|
||||||
// Push back to upstream.
|
// Push back to upstream.
|
||||||
// TODO: this cause an api call to "/api/internal/hook/post-receive/...",
|
// This cause an api call to "/api/internal/hook/post-receive/...",
|
||||||
// that prevents us from doint the whole merge in one db transaction
|
// 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 err := pushCmd.Run(mergeCtx.RunOpts()); err != nil {
|
||||||
if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") {
|
if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") {
|
||||||
return "", &git.ErrPushOutOfDate{
|
return "", &git.ErrPushOutOfDate{
|
||||||
|
@ -383,7 +383,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
|
|||||||
cmdApply.AddArguments("--ignore-whitespace")
|
cmdApply.AddArguments("--ignore-whitespace")
|
||||||
}
|
}
|
||||||
is3way := false
|
is3way := false
|
||||||
if git.CheckGitVersionAtLeast("2.32.0") == nil {
|
if git.DefaultFeatures().CheckVersionAtLeast("2.32.0") {
|
||||||
cmdApply.AddArguments("--3way")
|
cmdApply.AddArguments("--3way")
|
||||||
is3way = true
|
is3way = true
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
|
|||||||
baseBranch := "base"
|
baseBranch := "base"
|
||||||
|
|
||||||
fetchArgs := git.TrustedCmdArgs{"--no-tags"}
|
fetchArgs := git.TrustedCmdArgs{"--no-tags"}
|
||||||
if git.CheckGitVersionAtLeast("2.25.0") == nil {
|
if git.DefaultFeatures().CheckVersionAtLeast("2.25.0") {
|
||||||
// Writing the commit graph can be slow and is not needed here
|
// Writing the commit graph can be slow and is not needed here
|
||||||
fetchArgs = append(fetchArgs, "--no-write-commit-graph")
|
fetchArgs = append(fetchArgs, "--no-write-commit-graph")
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user