diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md index a703766855f4b..745c5d37bc729 100644 --- a/docs/content/doc/features/comparison.en-us.md +++ b/docs/content/doc/features/comparison.en-us.md @@ -50,7 +50,7 @@ _Symbols used in table:_ | Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Built-in Container Registry | [✘](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | External git mirroring | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | -| FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | +| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? | | Built-in CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Subgroups: groups within groups | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | @@ -66,6 +66,7 @@ _Symbols used in table:_ | Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Verified Committer | ⁄ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | | GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | +| SSH Signed Commits | ✓ | ✘ | ✘ | ✘ | ✘ | ? | ? | | Reject unsigned commits | [✓](https://github.com/go-gitea/gitea/pull/9708) | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | diff --git a/docs/content/doc/features/comparison.zh-cn.md b/docs/content/doc/features/comparison.zh-cn.md index 8ccdebe8cd94d..98a50f5dc2abd 100644 --- a/docs/content/doc/features/comparison.zh-cn.md +++ b/docs/content/doc/features/comparison.zh-cn.md @@ -48,7 +48,7 @@ _表格中的符号含义:_ | 仓库写权限令牌 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | | 内置容器 Registry | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | 外部 Git 镜像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | -| FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | +| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? | | 内置 CI/CD | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | 子组织:组织内的组织 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | @@ -64,6 +64,7 @@ _表格中的符号含义:_ | 细粒度用户角色 (例如 Code, Issues, Wiki) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | 提交人的身份验证 | ✘ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | | GPG 签名的提交 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | +| SSH 签名的提交 | ✓ | ✘ | ✘ | ✘ | ✘ | ? | ? | | 拒绝未用通过验证的提交 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | | 仓库活跃度页面 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | 分支管理 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | diff --git a/models/unit/unit.go b/models/unit/unit.go index eb71276786d1a..a6a47eb1f3c5a 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -328,7 +328,12 @@ func AllUnitKeyNames() []string { // MinUnitAccessMode returns the minial permission of the permission map func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode { res := perm.AccessModeNone - for _, mode := range unitsMap { + for t, mode := range unitsMap { + // Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms. + if t == TypeExternalTracker || t == TypeExternalWiki { + continue + } + // get the minial permission great than AccessModeNone except all are AccessModeNone if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) { res = mode diff --git a/modules/queue/workerpool.go b/modules/queue/workerpool.go index 20108d35886ca..100197c5e1f1f 100644 --- a/modules/queue/workerpool.go +++ b/modules/queue/workerpool.go @@ -115,6 +115,9 @@ func (p *WorkerPool) hasNoWorkerScaling() bool { return p.numberOfWorkers == 0 && (p.boostTimeout == 0 || p.boostWorkers == 0 || p.maxNumberOfWorkers == 0) } +// zeroBoost will add a temporary boost worker for a no worker queue +// p.lock must be locked at the start of this function BUT it will be unlocked by the end of this function +// (This is because addWorkers has to be called whilst unlocked) func (p *WorkerPool) zeroBoost() { ctx, cancel := context.WithTimeout(p.baseCtx, p.boostTimeout) mq := GetManager().GetManagedQueue(p.qid) @@ -305,16 +308,23 @@ func (p *WorkerPool) addWorkers(ctx context.Context, cancel context.CancelFunc, p.cond.Broadcast() cancel() } - if p.hasNoWorkerScaling() { - select { - case <-p.baseCtx.Done(): - // Don't warn if the baseCtx is shutdown - default: + select { + case <-p.baseCtx.Done(): + // Don't warn or check for ongoing work if the baseCtx is shutdown + case <-p.paused: + // Don't warn or check for ongoing work if the pool is paused + default: + if p.hasNoWorkerScaling() { log.Warn( "Queue: %d is configured to be non-scaling and has no workers - this configuration is likely incorrect.\n"+ "The queue will be paused to prevent data-loss with the assumption that you will add workers and unpause as required.", p.qid) + p.pause() + } else if p.numberOfWorkers == 0 && atomic.LoadInt64(&p.numInQueue) > 0 { + // OK there are no workers but... there's still work to be done -> Reboost + p.zeroBoost() + // p.lock will be unlocked by zeroBoost + return } - p.pause() } p.lock.Unlock() }() @@ -371,14 +381,37 @@ func (p *WorkerPool) pause() { // Resume resumes the WorkerPool func (p *WorkerPool) Resume() { - p.lock.Lock() - defer p.lock.Unlock() + p.lock.Lock() // can't defer unlock because of the zeroBoost at the end select { case <-p.resumed: + // already resumed - there's nothing to do + p.lock.Unlock() + return default: - p.paused = make(chan struct{}) - close(p.resumed) } + + p.paused = make(chan struct{}) + close(p.resumed) + + // OK now we need to check if we need to add some workers... + if p.numberOfWorkers > 0 || p.hasNoWorkerScaling() || atomic.LoadInt64(&p.numInQueue) == 0 { + // We either have workers, can't scale or there's no work to be done -> so just resume + p.lock.Unlock() + return + } + + // OK we got some work but no workers we need to think about boosting + select { + case <-p.baseCtx.Done(): + // don't bother boosting if the baseCtx is done + p.lock.Unlock() + return + default: + } + + // OK we'd better add some boost workers! + p.zeroBoost() + // p.zeroBoost will unlock the lock } // CleanUp will drain the remaining contents of the channel diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 531d265c3a0d2..cdfa1130f2b69 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1061,11 +1061,14 @@ func loadFromConf(allowEmpty bool, extraConfig string) { } // FIXME: DEPRECATED to be removed in v1.18.0 + U2F.AppID = strings.TrimSuffix(AppURL, "/") if Cfg.Section("U2F").HasKey("APP_ID") { log.Error("Deprecated setting `[U2F]` `APP_ID` present. This fallback will be removed in v1.18.0") + U2F.AppID = Cfg.Section("U2F").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/")) + } else if Cfg.Section("u2f").HasKey("APP_ID") { + log.Error("Deprecated setting `[u2]` `APP_ID` present. This fallback will be removed in v1.18.0") + U2F.AppID = Cfg.Section("u2f").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/")) } - sec = Cfg.Section("U2F") - U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/")) } func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) { diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 975154597dd37..75d7edb606629 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -2436,6 +2436,7 @@ dashboard.last_gc_pause=上次 GC 暂停时间 dashboard.gc_times=GC 执行次数 dashboard.delete_old_actions=从数据库中删除所有旧操作记录 dashboard.delete_old_actions.started=已开始从数据库中删除所有旧操作记录。 +dashboard.update_checker=更新检查器 users.user_manage_panel=用户帐户管理 users.new_account=创建新帐户 diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 6f285ec467c63..5639a08f96401 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -59,11 +59,13 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { handler := func(idx int, bean interface{}, limit int) error { var item SyncRequest + var repo *repo_model.Repository if m, ok := bean.(*repo_model.Mirror); ok { if m.Repo == nil { log.Error("Disconnected mirror found: %d", m.ID) return nil } + repo = m.Repo item = SyncRequest{ Type: PullMirrorType, RepoID: m.RepoID, @@ -73,6 +75,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { log.Error("Disconnected push-mirror found: %d", m.ID) return nil } + repo = m.Repo item = SyncRequest{ Type: PushMirrorType, RepoID: m.RepoID, @@ -89,17 +92,16 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { default: } - // Check if this request is already in the queue - has, err := mirrorQueue.Has(&item) - if err != nil { - return err - } - if has { - return nil - } - // Push to the Queue if err := mirrorQueue.Push(&item); err != nil { + if err == queue.ErrAlreadyInQueue { + if item.Type == PushMirrorType { + log.Trace("PushMirrors for %-v already queued for sync", repo) + } else { + log.Trace("PullMirrors for %-v already queued for sync", repo) + } + return nil + } return err } @@ -110,23 +112,29 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { return nil } + pullMirrorsRequested := 0 if pullLimit != 0 { + requested = 0 if err := repo_model.MirrorsIterate(func(idx int, bean interface{}) error { return handler(idx, bean, pullLimit) }); err != nil && err != errLimit { log.Error("MirrorsIterate: %v", err) return err } + pullMirrorsRequested, requested = requested, 0 } + pushMirrorsRequested := 0 if pushLimit != 0 { + requested = 0 if err := repo_model.PushMirrorsIterate(func(idx int, bean interface{}) error { return handler(idx, bean, pushLimit) }); err != nil && err != errLimit { log.Error("PushMirrorsIterate: %v", err) return err } + pushMirrorsRequested, requested = requested, 0 } - log.Trace("Finished: Update") + log.Trace("Finished: Update: %d pull mirrors and %d push mirrors queued", pullMirrorsRequested, pushMirrorsRequested) return nil } diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index 4cb8c18219b80..f11a49864dedc 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -24,6 +24,19 @@ export function initUserAuthWebAuthn() { .then((credential) => { verifyAssertion(credential); }).catch((err) => { + // Try again... without the appid + if (makeAssertionOptions.publicKey.extensions && makeAssertionOptions.publicKey.extensions.appid) { + delete makeAssertionOptions.publicKey.extensions['appid']; + navigator.credentials.get({ + publicKey: makeAssertionOptions.publicKey + }) + .then((credential) => { + verifyAssertion(credential); + }).catch((err) => { + webAuthnError('general', err.message); + }); + return; + } webAuthnError('general', err.message); }); }).fail(() => {