diff --git a/Makefile b/Makefile index 5ed50a67382cd..ab112584c65f4 100644 --- a/Makefile +++ b/Makefile @@ -646,7 +646,9 @@ release-sources: | $(DIST_DIRS) echo $(VERSION) > $(STORED_VERSION_FILE) # bsdtar needs a ^ to prevent matching subdirectories $(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./) - tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz . +# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s + $(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./gitea-src-$(VERSION)/'" || echo "--transform 's|^./|gitea-src-$(VERSION)/|'")) + tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz . rm -f $(STORED_VERSION_FILE) .PHONY: release-docs diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 80dd44642eb41..81459ba4460ff 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -66,6 +66,38 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex return } +// UserIDCount is a simple coalition of UserID and Count +type UserStopwatch struct { + UserID int64 + StopWatches []*Stopwatch +} + +// GetUIDsAndNotificationCounts between the two provided times +func GetUIDsAndStopwatch() ([]*UserStopwatch, error) { + sws := []*Stopwatch{} + if err := db.GetEngine(db.DefaultContext).Find(&sws); err != nil { + return nil, err + } + if len(sws) == 0 { + return []*UserStopwatch{}, nil + } + + lastUserID := int64(-1) + res := []*UserStopwatch{} + for _, sw := range sws { + if lastUserID == sw.UserID { + lastUserStopwatch := res[len(res)-1] + lastUserStopwatch.StopWatches = append(lastUserStopwatch.StopWatches, sw) + } else { + res = append(res, &UserStopwatch{ + UserID: sw.UserID, + StopWatches: []*Stopwatch{sw}, + }) + } + } + return res, nil +} + // GetUserStopwatches return list of all stopwatches of a user func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) { sws := make([]*Stopwatch, 0, 8) diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 9af5c9e78ac13..127979ad63456 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -9,7 +9,9 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" @@ -80,6 +82,31 @@ loop: }) } then = now + + if setting.Service.EnableTimetracking { + usersStopwatches, err := models.GetUIDsAndStopwatch() + if err != nil { + log.Error("Unable to get GetUIDsAndStopwatch: %v", err) + return + } + + for _, userStopwatches := range usersStopwatches { + apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches) + if err != nil { + log.Error("Unable to APIFormat stopwatches: %v", err) + continue + } + dataBs, err := json.Marshal(apiSWs) + if err != nil { + log.Error("Unable to marshal stopwatches: %v", err) + continue + } + m.SendMessage(userStopwatches.UserID, &Event{ + Name: "stopwatches", + Data: string(dataBs), + }) + } + } } } m.UnregisterAll() diff --git a/modules/git/diff.go b/modules/git/diff.go index e11f63cabd6c3..c9d68bb130fe9 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -28,8 +28,8 @@ const ( ) // GetRawDiff dumps diff results of repository in given commit ID to io.Writer. -func GetRawDiff(ctx context.Context, repoPath, commitID string, diffType RawDiffType, writer io.Writer) error { - return GetRawDiffForFile(ctx, repoPath, "", commitID, diffType, "", writer) +func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer io.Writer) error { + return GetRepoRawDiffForFile(repo, "", commitID, diffType, "", writer) } // GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer. @@ -46,17 +46,6 @@ func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io return nil } -// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer. -func GetRawDiffForFile(ctx context.Context, repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { - repo, closer, err := RepositoryFromContextOrOpen(ctx, repoPath) - if err != nil { - return fmt.Errorf("RepositoryFromContextOrOpen: %v", err) - } - defer closer.Close() - - return GetRepoRawDiffForFile(repo, startCommit, endCommit, diffType, file, writer) -} - // GetRepoRawDiffForFile dumps diff results of file in given commit ID to io.Writer according given repository func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { commit, err := repo.GetCommit(endCommit) diff --git a/modules/sync/unique_queue.go b/modules/sync/unique_queue.go deleted file mode 100644 index df115d7c96cc9..0000000000000 --- a/modules/sync/unique_queue.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package sync - -// UniqueQueue is a queue which guarantees only one instance of same -// identity is in the line. Instances with same identity will be -// discarded if there is already one in the line. -// -// This queue is particularly useful for preventing duplicated task -// of same purpose. -type UniqueQueue struct { - table *StatusTable - queue chan string - closed chan struct{} -} - -// NewUniqueQueue initializes and returns a new UniqueQueue object. -func NewUniqueQueue(queueLength int) *UniqueQueue { - if queueLength <= 0 { - queueLength = 100 - } - - return &UniqueQueue{ - table: NewStatusTable(), - queue: make(chan string, queueLength), - closed: make(chan struct{}), - } -} - -// Close closes this queue -func (q *UniqueQueue) Close() { - select { - case <-q.closed: - default: - q.table.lock.Lock() - select { - case <-q.closed: - default: - close(q.closed) - } - q.table.lock.Unlock() - } -} - -// IsClosed returns a channel that is closed when this Queue is closed -func (q *UniqueQueue) IsClosed() <-chan struct{} { - return q.closed -} - -// IDs returns the current ids in the pool -func (q *UniqueQueue) IDs() []string { - q.table.lock.Lock() - defer q.table.lock.Unlock() - ids := make([]string, 0, len(q.table.pool)) - for id := range q.table.pool { - ids = append(ids, id) - } - return ids -} - -// Queue returns channel of queue for retrieving instances. -func (q *UniqueQueue) Queue() <-chan string { - return q.queue -} - -// Exist returns true if there is an instance with given identity -// exists in the queue. -func (q *UniqueQueue) Exist(id string) bool { - return q.table.IsRunning(id) -} - -// AddFunc adds new instance to the queue with a custom runnable function, -// the queue is blocked until the function exits. -func (q *UniqueQueue) AddFunc(id string, fn func()) { - q.table.lock.Lock() - if _, ok := q.table.pool[id]; ok { - q.table.lock.Unlock() - return - } - q.table.pool[id] = struct{}{} - if fn != nil { - fn() - } - q.table.lock.Unlock() - select { - case <-q.closed: - return - case q.queue <- id: - return - } -} - -// Add adds new instance to the queue. -func (q *UniqueQueue) Add(id string) { - q.AddFunc(id, nil) -} - -// Remove removes instance from the queue. -func (q *UniqueQueue) Remove(id string) { - q.table.Stop(id) -} diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini index 2887a9b7f2639..22351c04e20e1 100644 --- a/options/locale/locale_bg-BG.ini +++ b/options/locale/locale_bg-BG.ini @@ -72,6 +72,7 @@ loading=Зареждане… error404=Страницата, която се опитвате да достъпите, не съществува или не сте оторизирани да я достъпите. + [error] [startpage] diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 778326b205a1c..f08947a40a15c 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -86,6 +86,7 @@ error404=Stránka, kterou se snažíte zobrazit, buď neexistujeexistiert entwed never=Niemals + [error] occurred=Ein Fehler ist aufgetreten report_message=Wenn du dir sicher bist, dass dies ein Fehler von Gitea ist, suche bitte auf GitHub nach diesem Fehler und erstelle gegebenenfalls ein neues Issue. diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 14d7c9fd1d06d..0119148cd52d5 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -105,6 +105,7 @@ error404=Η σελίδα που προσπαθείτε να φτάσετε εί never=Ποτέ + [error] occurred=Παρουσιάστηκε ένα σφάλμα report_message=Αν είστε σίγουροι ότι πρόκειται για ένα πρόβλημα στο Gitea, παρακαλώ αναζητήστε στα ζητήματα στο GitHub ή ανοίξτε ένα νέο ζήτημα εάν είναι απαραίτητο. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6725964f53f4e..5ec001b6d46cb 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1041,6 +1041,7 @@ line_unicode = `This line has hidden unicode characters` escape_control_characters = Escape unescape_control_characters = Unescape file_copy_permalink = Copy Permalink +view_git_blame = View Git Blame video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. stored_lfs = Stored with Git LFS @@ -3088,7 +3089,7 @@ settings.link = Link this package to a repository settings.link.description = If you link a package with a repository, the package is listed in the repository's package list. settings.link.select = Select Repository settings.link.button = Update Repository Link -settings.link.success = Repository link was successfully updated. +settings.link.success = Repository link was successfully updated. settings.link.error = Failed to update repository link. settings.delete = Delete package settings.delete.description = Deleting a package is permanent and cannot be undone. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index d187c43a3d373..5311470969dc8 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -105,6 +105,7 @@ error404=La página a la que está intentando acceder o no existeGitHub y abre un nuevo problema si es necesario. diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 38f24b1b23ea7..0216356d135f0 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -91,6 +91,7 @@ error404=صفحه موردنظر شما یا وجود نداردei löydy tai et ole oikeutettu katsomaan sitä. + [error] [startpage] diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index b83924ae7be8b..2f9ef3c1bc844 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -91,6 +91,7 @@ error404=La page que vous essayez d'atteindre n'existe pas ou < never=Jamais + [error] missing_csrf=Requête incorrecte: aucun jeton CSRF présent diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 61c21c688d6a7..b830e8baf8737 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -80,6 +80,7 @@ step2=2. lépés: error404=Az elérni kívánt oldal vagy nem létezik, vagy nincs jogosultsága a megtekintéséhez. + [error] [startpage] diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index b9f1e46e397c3..e4869c02c5202 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -73,6 +73,7 @@ loading=Memuat… + [error] [startpage] diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index 03589d3ae4f38..de89ad638582a 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -105,6 +105,7 @@ error404=Síðan sem þú ert að reyna að fá annað hvort er ekki til never=Aldrei + [error] occurred=Villa kom upp report_message=Ef þú ert viss um að þetta sé villa í Gitea þá skaltu leita að vandamálum á GitHub eða opna nýtt vandamál ef þörf krefst. diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 0550290fe34ba..7a547142bf1c4 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -83,6 +83,7 @@ step2=Passo 2: error404=La pagina che stai cercando di raggiungere non esiste oppure non sei autorizzato a visualizzarla. + [error] [startpage] diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index be82d9b46ca4a..f20994529d052 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -105,6 +105,7 @@ error404=アクセスしようとしたページは存在しないGitHubでIssueを検索して、見つからなければ新しいIssueを作成してください。 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index b9d878904fa8f..c55de5a8f9d36 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -74,6 +74,7 @@ loading=불러오는 중... + [error] [startpage] diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 16eb1866a5f17..40cb93c80a67e 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -105,6 +105,7 @@ error404=Lapa, ko vēlaties atvērt, neeksistē vai arī GitHub vai ziņojiet par jaunu kļūdu, ja nepieciešams. diff --git a/options/locale/locale_ml-IN.ini b/options/locale/locale_ml-IN.ini index 6c42aff448955..31b9ca23ebb33 100644 --- a/options/locale/locale_ml-IN.ini +++ b/options/locale/locale_ml-IN.ini @@ -65,6 +65,7 @@ loading=ലഭ്യമാക്കുന്നു… + [error] [startpage] diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 3553b11e090f3..dfc7fe13babb6 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -90,6 +90,7 @@ error404=De pagina die u probeert te bereiken bestaat niet of < never=Nooit + [error] missing_csrf=Foutief verzoek: geen CSRF-token aanwezig diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index cfdb177765b90..90c4d28027f83 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -105,6 +105,7 @@ error404=Strona, do której próbujesz dotrzeć nie istnieje lu never=Nigdy + [error] occurred=Wystąpił błąd report_message=Jeśli jesteś pewien, że jest to błąd Gitea, poszukaj już istniejącego zgłoszenia na GitHub lub w razie potrzeby otwórz nowy problem. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index f007cf0ab95a1..e11385cdfb202 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -105,6 +105,7 @@ error404=A página que você está tentando acessar não existe never=Nunca + [error] occurred=Ocorreu um erro report_message=Se você tem certeza de que é um bug do Gitea, procure por issues no GitHub ou abra uma nova issue, se necessário. @@ -2161,6 +2162,7 @@ diff.review.placeholder=Comentário da revisão diff.review.comment=Comentar diff.review.approve=Aprovar diff.review.reject=Solicitar alterações +diff.committed_by=commit de diff.protected=Protegido diff.image.side_by_side=Lado a Lado diff.image.swipe=Deslizar diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 05f102d9e35a1..1780f4fa2ec7f 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -105,6 +105,8 @@ error404=A página que pretende aceder não existe ou n never=Nunca +rss_feed=Fonte RSS + [error] occurred=Ocorreu um erro report_message=Se tiver certeza de que se trata de um erro do Gitea, procure, por favor, questões relacionadas no GitHub ou abra uma nova questão, se necessário. diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 47d85b5edf86c..20664861aac1e 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -45,6 +45,8 @@ webauthn_error_insecure=WebAuthn поддерживает только безо webauthn_error_unable_to_process=Сервер не смог обработать ваш запрос. webauthn_error_duplicated=Представленный ключ не подходит для этого запроса. Если вы пытаетесь зарегистрировать его, убедитесь, что ключ ещё не зарегистрирован. webauthn_error_empty=Вы должны указать имя для этого ключа. +webauthn_error_timeout=Тайм-аут достигнут до того, как ваш ключ был прочитан. Перезагрузите эту страницу и повторите попытку. +webauthn_reload=Обновить repository=Репозиторий organization=Организация @@ -102,8 +104,13 @@ error404=Страница, которую вы пытаетесь открыть never=Никогда +rss_feed=RSS-лента + [error] +occurred=Произошла ошибка missing_csrf=Некорректный запрос: CSRF токен отсутствует +invalid_csrf=Некорректный запрос: неверный CSRF токен +network_error=Ошибка сети [startpage] app_desc=Удобный сервис собственного хостинга репозиториев Git @@ -260,6 +267,7 @@ search=Поиск code=Код search.fuzzy=Неточный search.match=Соответствие +code_search_unavailable=В настоящее время поиск по коду недоступен. Обратитесь к администратору сайта. repo_no_results=Подходящие репозитории не найдены. user_no_results=Подходящие пользователи не найдены. org_no_results=Подходящие организации не найдены. @@ -273,6 +281,7 @@ register_helper_msg=Уже есть аккаунт? Авторизуйтесь! social_register_helper_msg=Уже есть аккаунт? Свяжите его сейчас! disable_register_prompt=Извините, возможность регистрации отключена. Пожалуйста, свяжитесь с администратором сайта. disable_register_mail=Подтверждение регистрации по электронной почте отключено. +manual_activation_only=Обратитесь к администратору сайта для завершения активации. remember_me=Запомнить это устройство forgot_password_title=Восстановить пароль forgot_password=Забыли пароль? @@ -311,6 +320,9 @@ oauth_signup_submit=Полная учётная запись oauth_signin_tab=Ссылка на существующую учётную запись oauth_signin_title=Войдите, чтобы авторизовать связанную учётную запись oauth_signin_submit=Привязать учётную запись +oauth.signin.error=Произошла ошибка при обработке запроса авторизации. Если эта ошибка повторяется, обратитесь к администратору сайта. +oauth.signin.error.access_denied=Запрос на авторизацию был отклонен. +oauth.signin.error.temporarily_unavailable=Ошибка авторизации, так как сервер временно недоступен. Пожалуйста, повторите попытку позже. openid_connect_submit=Подключить openid_connect_title=Подключение к существующей учетной записи openid_connect_desc=Выбранный OpenID URI неизвестен. Свяжите с новой учетной записью здесь. @@ -475,7 +487,9 @@ auth_failed=Ошибка аутентификации: %v still_own_repo=Ваша учётная запись владеет одним или несколькими репозиториями; сначала удалите или перенесите их. still_has_org=Ваша учётная запись является членом одной или нескольких организаций; сначала выйдите из них. +still_own_packages=Ваша учётная запись владеет одним или несколькими пакетами, сначала удалите их. org_still_own_repo=Эта организация по-прежнему владеет одним или несколькими репозиториями; сначала удалите или перенесите их. +org_still_own_packages=Эта организация всё ещё имеет пакеты, сначала удалите их. target_branch_not_exist=Целевая ветка не существует. @@ -516,6 +530,7 @@ twofa=Двухфакторная аутентификация account_link=Привязанные аккаунты organization=Организации uid=UID +webauthn=Ключи безопасности public_profile=Открытый профиль biography_placeholder=Расскажите немного о себе @@ -537,6 +552,19 @@ continue=Далее cancel=Отмена language=Язык ui=Тема +hidden_comment_types=Скрытые типы комментариев +comment_type_group_reference=Упоминания +comment_type_group_label=Операции с метками +comment_type_group_milestone=Этап +comment_type_group_assignee=Назначения +comment_type_group_title=Правки заголовков +comment_type_group_branch=Операции с ветками +comment_type_group_time_tracking=Отслеживание времени +comment_type_group_deadline=Модификации сроков выполнения +comment_type_group_dependency=Модификации зависимостей +comment_type_group_lock=Смена статуса ограничения на обсуждение +comment_type_group_review_request=Запросы на рецензию +saved_successfully=Ваши настройки успешно сохранены. privacy=Приватность keep_activity_private=Скрыть активность со страницы профиля keep_activity_private_popup=Делает активность видимой только для вас и администраторов @@ -736,6 +764,10 @@ twofa_enrolled=Для вашего аккаунта была включена д twofa_failed_get_secret=Не удалось получить ключ. webauthn_desc=Ключи безопасности - это аппаратные устройства, содержащие криптографические ключи. Они могут использоваться для двухфакторной аутентификации. Ключи безопасности должны поддерживать стандарт WebAuthn Authenticator. +webauthn_register_key=Добавить ключ безопасности +webauthn_nickname=Имя пользователя +webauthn_delete_key=Удалить ключ безопасности +webauthn_delete_key_desc=Если вы удалите ключ безопасности, вы больше не сможете войти с его помощью. Продолжить? manage_account_links=Управление привязанными аккаунтами manage_account_links_desc=Эти внешние аккаунты привязаны к вашему аккаунту Gitea. @@ -786,6 +818,7 @@ visibility_fork_helper=(Изменение этого повлияет на вс clone_helper=Нужна помощь в клонировании? Посетите страницу помощи. fork_repo=Форкнуть репозиторий fork_from=Форк от +already_forked=Вы уже форкнули %s fork_visibility_helper=Видимость форкнутого репозитория изменить нельзя. use_template=Использовать этот шаблон clone_in_vsc=Клонировать в VS Code @@ -920,6 +953,7 @@ migrate.migrating=Перенос из %s... migrate.migrating_failed=Перенос из %s не удался. migrate.migrating_failed.error=Ошибка: %s migrate.migrating_failed_no_addr=Миграция не удалась. +migrate.github.description=Переносите данные с github.com или других серверов GitHub. migrate.git.description=Перенести только репозиторий из любого Git сервиса. migrate.gitlab.description=Перенести данные с gitlab.com или других экземпляров GitLab. migrate.gitea.description=Перенести данные с gitea.com или других экземпляров Gitea. @@ -929,6 +963,7 @@ migrate.codebase.description=Перенос данных с codebasehq.com. migrate.gitbucket.description=Перенести данные из экземпляров GitBucket. migrate.migrating_git=Перенос Git данных migrate.migrating_topics=Миграция тем +migrate.migrating_milestones=Перенос этапов migrate.migrating_labels=Миграция меток migrate.migrating_issues=Миграция задач migrate.migrating_pulls=Миграция запросов на слияние @@ -1025,6 +1060,8 @@ editor.add_tmpl=Добавить '' editor.add=Создал(а) '%s' editor.update=Изменил(а) на '%s' editor.delete=Удалить '%s' +editor.patch=Применить патч +editor.new_patch=Новый патч editor.commit_message_desc=Добавьте необязательное расширенное описание… editor.signoff_desc=Добавить Signed-off-by коммитом в конце сообщения журнала коммитов. editor.commit_directly_to_this_branch=Сделайте коммит прямо в ветку %s. @@ -1077,6 +1114,12 @@ commits.signed_by_untrusted_user=Подписано ненадежным пол commits.signed_by_untrusted_user_unmatched=Подписан ненадежным пользователем, который не соответствует коммиту commits.gpg_key_id=Идентификатор GPG ключа +commit.revert=Откатить +commit.revert-header=Откат: %s +commit.revert-content=Выбрать ветку для отката: +commit.cherry-pick=Cherry-pick +commit.cherry-pick-header=Cherry-pick: %s +commit.cherry-pick-content=Выбрать ветку для cherry-pick: ext_issues.desc=Ссылка на внешнюю систему отслеживания ошибок. @@ -1302,6 +1345,9 @@ issues.lock.reason=Причина для ограничения issues.lock.title=Ограничить обсуждение данной задачи. issues.unlock.title=Снять ограничение обсуждения данной задачи. issues.comment_on_locked=Вы не можете оставить комментарий по задаче, ограниченной для обсуждения. +issues.delete=Удалить +issues.delete.title=Удалить эту задачу? +issues.delete.text=Вы действительно хотите удалить эту задачу? Это навсегда удалит всё содержимое. Возможно лучше закрыть её в архивных целях. issues.tracker=Отслеживание времени issues.start_tracking_short=Запустить таймер issues.start_tracking=Начать отслеживание времени @@ -1342,6 +1388,8 @@ issues.due_date_remove=удалён срок выполнения %s %s issues.due_date_overdue=Просроченные issues.due_date_invalid=Срок действия недействителен или находится за пределами допустимого диапазона. Пожалуйста, используйте формат 'гггг-мм-дд'. issues.dependency.title=Зависимости +issues.dependency.issue_no_dependencies=Зависимостей нет. +issues.dependency.pr_no_dependencies=Зависимостей нет. issues.dependency.add=Добавить зависимость… issues.dependency.cancel=Отменить issues.dependency.remove=Удалить @@ -1407,7 +1455,7 @@ pulls.new=Новый запрос на слияние pulls.view=Просмотр запроса на слияние pulls.compare_changes=Новый запрос на слияние pulls.compare_changes_desc=Сравнить две ветки и создать запрос на слияние для изменений. -pulls.compare_base=родительская ветка +pulls.compare_base=базовая ветка pulls.compare_compare=взять из pulls.switch_comparison_type=Переключить тип сравнения pulls.switch_head_and_base=Поменять исходную и целевую ветки местами @@ -1482,7 +1530,9 @@ pulls.rebase_conflict_summary=Сообщение об ошибке ; %[2]s
%[3]s
pulls.unrelated_histories=Слияние не удалось: У источника и цели слияния нет общей истории. Совет: попробуйте другую стратегию pulls.merge_out_of_date=Ошибка слияния: при создании слияния база данных была обновлена. Подсказка: попробуйте ещё раз. +pulls.push_rejected=Слияние не удалось: push был отклонён. Проверьте Git-хуки для этого репозитория. pulls.push_rejected_summary=Полная ошибка отклонения +pulls.push_rejected_no_message=Слияние не удалось: push был отклонён, но сервер не указал причину.
Проверьте Git-хуки для этого репозитория pulls.open_unmerged_pull_exists=`Вы не можете снова открыть, поскольку уже существует запрос на слияние (#%d) из того же репозитория с той же информацией о слиянии и ожидающий слияния.` pulls.status_checking=Выполняются некоторые проверки pulls.status_checks_success=Все проверки выполнены успешно @@ -1646,6 +1696,8 @@ search.search_repo=Поиск по репозиторию search.fuzzy=Неточный search.match=Соответствие search.results=Результаты поиска "%s" в %s +search.code_no_results=Не найдено исходного кода, соответствующего поисковому запросу. +search.code_search_unavailable=В настоящее время поиск по коду недоступен. Обратитесь к администратору сайта. settings=Настройки settings.desc=В настройках вы можете менять различные параметры этого репозитория @@ -1805,6 +1857,7 @@ settings.webhook.response=Ответ settings.webhook.headers=Заголовки settings.webhook.payload=Содержимое settings.webhook.body=Тело ответа +settings.webhook.replay.description=Повторить этот веб-хук. settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведёт к отключению хука. settings.githook_name=Название Hook'a settings.githook_content=Содержание hook'а @@ -1862,6 +1915,8 @@ settings.event_pull_request_review=Запрос на слияние рассмо settings.event_pull_request_review_desc=Запрос на слияние утвержден, отклонён или оставлен комментарий. settings.event_pull_request_sync=Синхронизация запроса на слияние settings.event_pull_request_sync_desc=Запрос на слияние синхронизирован. +settings.event_package=Пакеты +settings.event_package_desc=Пакет создан или удален в репозитории. settings.branch_filter=Фильтр веток settings.branch_filter_desc=Белый список ветвей для событий Push, создания ветвей и удаления ветвей, указанных в виде глоб-шаблона. Если пустой или *, то все событий для всех ветвей будут зарегистрированы. Перейдите по ссылке github.com/gobwas/glob на документацию по синтаксису. Примеры: master, {master,release*}. settings.active=Активный @@ -1875,6 +1930,13 @@ settings.hook_type=Тип hook'а settings.slack_token=Slack токен settings.slack_domain=Домен settings.slack_channel=Канал +settings.web_hook_name_gitea=Gitea +settings.web_hook_name_gogs=Gogs +settings.web_hook_name_slack=Slack +settings.web_hook_name_discord=Discord +settings.web_hook_name_dingtalk=DingTalk +settings.web_hook_name_msteams=Microsoft Teams +settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite settings.deploy_keys=Ключи развертывания settings.add_deploy_key=Добавить ключ развертывания settings.deploy_key_desc=Ключи развёртывания доступны только для чтения. Это не то же самое что и SSH-ключи аккаунта. @@ -2129,11 +2191,14 @@ branch.included_desc=Эта ветка является частью ветки branch.included=Включено branch.create_new_branch=Создать ветку из ветви: branch.confirm_create_branch=Создать ветку +branch.create_branch_operation=Создать ветку branch.new_branch=Создать новую ветку branch.new_branch_from=Создать новую ветку из '%s' branch.renamed=Ветка %s была переименована в %s. tag.create_tag=Создать тег %s +tag.create_tag_operation=Создать тег +tag.confirm_create_tag=Создать тег tag.create_success=Тег '%s' был создан. @@ -2221,7 +2286,9 @@ teams.leave=Выйти teams.leave.detail=Покинуть %s? teams.can_create_org_repo=Создать репозитории teams.can_create_org_repo_helper=Участники могут создавать новые репозитории в организации. Создатель получит администраторский доступ к новому репозиторию. +teams.read_access=Чтение teams.read_access_helper=Участники могут просматривать и клонировать командные репозитории. +teams.write_access=Запись teams.write_access_helper=Участники могут читать и выполнять push в командные репозитории. teams.admin_access=Доступ администратора teams.admin_access_helper=Участники могут выполнять pull, push в командные репозитории и добавлять соавторов в команду. @@ -2345,6 +2412,7 @@ dashboard.last_gc_pause=Последняя пауза сборщика мусо dashboard.gc_times=Количество сборок мусора dashboard.delete_old_actions=Удалите все старые действия из базы данных dashboard.delete_old_actions.started=Удалите все старые действия из запущенной базы данных. +dashboard.update_checker=Проверка обновлений users.user_manage_panel=Панель управления пользователями users.new_account=Создать новый аккаунт @@ -2427,6 +2495,14 @@ repos.forks=Форки repos.issues=Задачи repos.size=Размер +packages.owner=Владелец +packages.creator=Автор +packages.name=Наименование +packages.version=Версия +packages.type=Тип +packages.repository=Репозиторий +packages.size=Размер +packages.published=Опубликовано defaulthooks=Стандартные Веб-хуки defaulthooks.desc=Вебхуки автоматически делают HTTP-POST запросы на сервер, когда вызываются определенные события Gitea. Вебхуки, определённые здесь, по умолчанию и будут скопированы во все новые репозитории. Подробнее читайте в руководстве по вебхукам. @@ -2683,9 +2759,11 @@ monitor.next=Следующий раз monitor.previous=Предыдущий раз monitor.execute_times=Количество выполнений monitor.process=Запущенные процессы +monitor.goroutines=%d горутин monitor.desc=Описание monitor.start=Время начала monitor.execute_time=Время выполнения +monitor.last_execution_result=Результат monitor.process.cancel=Отменить процесс monitor.process.cancel_desc=Отмена процесса может привести к потере данных monitor.process.cancel_notices=Отменить: %s? @@ -2716,6 +2794,11 @@ monitor.queue.pool.flush.title=Очистить очередь monitor.queue.pool.flush.desc=При сбросе будет добавлен работник, который будет закрыт, когда очередь будет пустой, или истечет время время. monitor.queue.pool.flush.submit=Добавить чистящего работника monitor.queue.pool.flush.added=Добавлен чистящий рабочий на %[1]s +monitor.queue.pool.pause.desc=Приостановка очереди приостановит обработку данных +monitor.queue.pool.pause.submit=Приостановить очередь +monitor.queue.pool.resume.title=Возобновить очередь +monitor.queue.pool.resume.desc=Эта очередь возобновит работу +monitor.queue.pool.resume.submit=Возобновить очередь monitor.queue.settings.title=Настройки пула monitor.queue.settings.desc=Пулы динамично растут с ускорением в ответ на блокировку их рабочих очередей. Эти изменения не повлияют на текущие рабочие группы. @@ -2845,4 +2928,18 @@ error.no_unit_allowed_repo=У вас нет доступа ни к одному error.unit_not_allowed=У вас нет доступа к этому разделу репозитория. [packages] +title=Пакеты +desc=Управление пакетами репозитория. +empty=Пока нет пакетов. +empty.documentation=Дополнительную информацию о реестре пакетов можно найти в документации. +filter.type=Тип +filter.type.all=Все +filter.no_result=Фильтр не дал результатов. +installation=Установка +about=Об этом пакете +requirements=Требования +dependencies=Зависимости +container.multi_arch=ОС / архитектура +container.labels.key=Ключ +container.labels.value=Значение diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 115b09fb3c3eb..f64c40d2286ed 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -91,6 +91,7 @@ error404=ඔබ ළඟා වීමට උත්සාහ කරන පිටු never=කිසි විටෙකත් + [error] missing_csrf=නරක ඉල්ලීම: CSRF ටෝකන් නොමැත diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index fdca7da18d035..88d9b8bc5361c 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -79,6 +79,7 @@ loading=Laddar… error404=Sidan du försöker nå finns inte eller så har du inte behörighet att se den. + [error] [startpage] diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 9eb3022d27362..b0cea0cc08b29 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -91,6 +91,7 @@ error404=Ulaşmaya çalıştığınız sayfa mevcut değil veya never=Asla + [error] missing_csrf=Hatalı İstek: CSRF anahtarı yok diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 7c9a3b0ce6206..f9e099baa24a1 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -92,6 +92,7 @@ error404=Сторінка, до якої ви намагаєтеся зверн never=Ніколи + [error] occurred=Сталася помилка missing_csrf=Некоректний запит: токен CSRF не задано diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 0c395fec9ac83..51b8263691cff 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -105,6 +105,7 @@ error404=您正尝试访问的页面 不存在您 never=从不 + [error] occurred=发生了一个错误 report_message=如果您确定这是一个 Gitea bug,请在 GitHub 上搜索问题,或在必要时打开一个新问题。 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index c2e9cbe7c4f2f..89fc772ecbcad 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -46,6 +46,7 @@ cancel=取消 + [error] [startpage] diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 7193429309091..440bf18135bae 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -105,6 +105,7 @@ error404=您正嘗試訪問的頁面 不存在您 never=從來沒有 + [error] occurred=發生錯誤 report_message=如果您確定這是一個 Gitea 的 bug,請到 GitHub 搜尋相關的問題,如果有需要您也可以建立新問題。 diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index c79c34ec42961..b196ce97740de 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -11,7 +11,6 @@ import ( "net/http" "strconv" - repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" @@ -268,17 +267,12 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) { // "$ref": "#/responses/string" // "404": // "$ref": "#/responses/notFound" - repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) - // TODO: use gitRepo from context - if err := git.GetRawDiff( - ctx, - repoPath, - ctx.Params(":sha"), - git.RawDiffType(ctx.Params(":diffType")), - ctx.Resp, - ); err != nil { + sha := ctx.Params(":sha") + diffType := git.RawDiffType(ctx.Params(":diffType")) + + if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil { if git.IsErrNotExist(err) { - ctx.NotFound(ctx.Params(":sha")) + ctx.NotFound(sha) return } ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err) diff --git a/routers/api/v1/repo/main_test.go b/routers/api/v1/repo/main_test.go index 19e524d014f76..1f91a24937186 100644 --- a/routers/api/v1/repo/main_test.go +++ b/routers/api/v1/repo/main_test.go @@ -9,10 +9,15 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" + webhook_service "code.gitea.io/gitea/services/webhook" ) func TestMain(m *testing.M) { + setting.LoadForTest() + setting.NewQueueService() unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", "..", "..", ".."), + SetUp: webhook_service.Init, }) } diff --git a/routers/init.go b/routers/init.go index 88c393736ef48..403fab00cd3b2 100644 --- a/routers/init.go +++ b/routers/init.go @@ -145,7 +145,7 @@ func GlobalInitInstalled(ctx context.Context) { mustInit(stats_indexer.Init) mirror_service.InitSyncMirrors() - webhook.InitDeliverHooks() + mustInit(webhook.Init) mustInit(pull_service.Init) mustInit(task.Init) mustInit(repo_migrations.Init) diff --git a/routers/web/events/events.go b/routers/web/events/events.go index 02d20550afcd2..d8c6f38d02ae2 100644 --- a/routers/web/events/events.go +++ b/routers/web/events/events.go @@ -8,15 +8,10 @@ import ( "net/http" "time" - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/web/auth" ) @@ -71,8 +66,6 @@ func Events(ctx *context.Context) { timer := time.NewTicker(30 * time.Second) - stopwatchTimer := time.NewTicker(setting.UI.Notification.MinTimeout) - loop: for { select { @@ -93,32 +86,6 @@ loop: case <-shutdownCtx.Done(): go unregister() break loop - case <-stopwatchTimer.C: - sws, err := models.GetUserStopwatches(ctx.Doer.ID, db.ListOptions{}) - if err != nil { - log.Error("Unable to GetUserStopwatches: %v", err) - continue - } - apiSWs, err := convert.ToStopWatches(sws) - if err != nil { - log.Error("Unable to APIFormat stopwatches: %v", err) - continue - } - dataBs, err := json.Marshal(apiSWs) - if err != nil { - log.Error("Unable to marshal stopwatches: %v", err) - continue - } - _, err = (&eventsource.Event{ - Name: "stopwatches", - Data: string(dataBs), - }).WriteTo(ctx.Resp) - if err != nil { - log.Error("Unable to write to EventStream for user %s: %v", ctx.Doer.Name, err) - go unregister() - break loop - } - ctx.Resp.Flush() case event, ok := <-messageChan: if !ok { break loop diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index 926361ccd7686..6667d27410873 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -151,7 +151,7 @@ func CherryPickPost(ctx *context.Context) { return } } else { - if err := git.GetRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, git.RawDiffType("patch"), buf); err != nil { + if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, git.RawDiffType("patch"), buf); err != nil { if git.IsErrNotExist(err) { ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.Params(":sha")+" does not exist.")) return diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 2b1b5440d04aa..59d818672a8e1 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -7,13 +7,13 @@ package repo import ( "errors" + "fmt" "net/http" "strings" "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" @@ -381,15 +381,24 @@ func Diff(ctx *context.Context) { // RawDiff dumps diff results of repository in given commit ID to io.Writer func RawDiff(ctx *context.Context) { - var repoPath string + var gitRepo *git.Repository if ctx.Data["PageIsWiki"] != nil { - repoPath = ctx.Repo.Repository.WikiPath() + wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return + } + defer wikiRepo.Close() + gitRepo = wikiRepo } else { - repoPath = repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) + gitRepo = ctx.Repo.GitRepo + if gitRepo == nil { + ctx.ServerError("GitRepo not open", fmt.Errorf("no open git repo for '%s'", ctx.Repo.Repository.FullName())) + return + } } if err := git.GetRawDiff( - ctx, - repoPath, + gitRepo, ctx.Params(":sha"), git.RawDiffType(ctx.Params(":ext")), ctx.Resp, diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go index 7ef80e77254c8..83e4ecedbf2e2 100644 --- a/routers/web/repo/issue_stopwatch.go +++ b/routers/web/repo/issue_stopwatch.go @@ -9,7 +9,9 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/eventsource" ) // IssueStopwatch creates or stops a stopwatch for the given issue. @@ -59,6 +61,18 @@ func CancelStopwatch(c *context.Context) { return } + stopwatches, err := models.GetUserStopwatches(c.Doer.ID, db.ListOptions{}) + if err != nil { + c.ServerError("GetUserStopwatches", err) + return + } + if len(stopwatches) == 0 { + eventsource.GetManager().SendMessage(c.Doer.ID, &eventsource.Event{ + Name: "stopwatches", + Data: "{}", + }) + } + url := issue.HTMLURL() c.Redirect(url, http.StatusSeeOther) } diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 7998be53c2831..77744473f1ce3 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -15,7 +15,6 @@ import ( "io" "net/http" "net/url" - "strconv" "strings" "sync" "time" @@ -26,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/proxy" + "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "github.com/gobwas/glob" @@ -202,10 +202,8 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { return nil } -// DeliverHooks checks and delivers undelivered hooks. -// FIXME: graceful: This would likely benefit from either a worker pool with dummy queue -// or a full queue. Then more hooks could be sent at same time. -func DeliverHooks(ctx context.Context) { +// populateDeliverHooks checks and delivers undelivered hooks. +func populateDeliverHooks(ctx context.Context) { select { case <-ctx.Done(): return @@ -226,42 +224,9 @@ func DeliverHooks(ctx context.Context) { return default: } - if err = Deliver(ctx, t); err != nil { - log.Error("deliver: %v", err) - } - } - - // Start listening on new hook requests. - for { - select { - case <-ctx.Done(): - hookQueue.Close() - return - case repoIDStr := <-hookQueue.Queue(): - log.Trace("DeliverHooks [repo_id: %v]", repoIDStr) - hookQueue.Remove(repoIDStr) - - repoID, err := strconv.ParseInt(repoIDStr, 10, 64) - if err != nil { - log.Error("Invalid repo ID: %s", repoIDStr) - continue - } - tasks, err := webhook_model.FindRepoUndeliveredHookTasks(repoID) - if err != nil { - log.Error("Get repository [%d] hook tasks: %v", repoID, err) - continue - } - for _, t := range tasks { - select { - case <-ctx.Done(): - return - default: - } - if err = Deliver(ctx, t); err != nil { - log.Error("deliver: %v", err) - } - } + if err := addToTask(t.RepoID); err != nil { + log.Error("DeliverHook failed [%d]: %v", t.RepoID, err) } } } @@ -297,8 +262,8 @@ func webhookProxy() func(req *http.Request) (*url.URL, error) { } } -// InitDeliverHooks starts the hooks delivery thread -func InitDeliverHooks() { +// Init starts the hooks delivery thread +func Init() error { timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second allowedHostListValue := setting.Webhook.AllowedHostList @@ -316,5 +281,13 @@ func InitDeliverHooks() { }, } - go graceful.GetManager().RunWithShutdownContext(DeliverHooks) + hookQueue = queue.CreateUniqueQueue("webhook_sender", handle, "") + if hookQueue == nil { + return fmt.Errorf("Unable to create webhook_sender Queue") + } + go graceful.GetManager().RunWithShutdownFns(hookQueue.Run) + + populateDeliverHooks(graceful.GetManager().HammerContext()) + + return nil } diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go index 25b9df0af6688..1dc2e1bd83fb3 100644 --- a/services/webhook/main_test.go +++ b/services/webhook/main_test.go @@ -9,12 +9,16 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" _ "code.gitea.io/gitea/models" ) func TestMain(m *testing.M) { + setting.LoadForTest() + setting.NewQueueService() unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", ".."), + SetUp: Init, }) } diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index a3efc7535fc3c..b15b8173f51fe 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -12,10 +12,11 @@ import ( repo_model "code.gitea.io/gitea/models/repo" webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" @@ -80,7 +81,7 @@ func IsValidHookTaskType(name string) bool { } // hookQueue is a global queue of web hooks -var hookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength) +var hookQueue queue.UniqueQueue // getPayloadBranch returns branch for hook event, if applicable. func getPayloadBranch(p api.Payloader) string { @@ -101,14 +102,47 @@ func getPayloadBranch(p api.Payloader) string { return "" } +// handle passed PR IDs and test the PRs +func handle(data ...queue.Data) []queue.Data { + for _, datum := range data { + repoIDStr := datum.(string) + log.Trace("DeliverHooks [repo_id: %v]", repoIDStr) + + repoID, err := strconv.ParseInt(repoIDStr, 10, 64) + if err != nil { + log.Error("Invalid repo ID: %s", repoIDStr) + continue + } + + tasks, err := webhook_model.FindRepoUndeliveredHookTasks(repoID) + if err != nil { + log.Error("Get repository [%d] hook tasks: %v", repoID, err) + continue + } + for _, t := range tasks { + if err = Deliver(graceful.GetManager().HammerContext(), t); err != nil { + log.Error("deliver: %v", err) + } + } + } + return nil +} + +func addToTask(repoID int64) error { + err := hookQueue.PushFunc(strconv.FormatInt(repoID, 10), nil) + if err != nil && err != queue.ErrAlreadyInQueue { + return err + } + return nil +} + // PrepareWebhook adds special webhook to task queue for given payload. func PrepareWebhook(w *webhook_model.Webhook, repo *repo_model.Repository, event webhook_model.HookEventType, p api.Payloader) error { if err := prepareWebhook(w, repo, event, p); err != nil { return err } - go hookQueue.Add(strconv.FormatInt(repo.ID, 10)) - return nil + return addToTask(repo.ID) } func checkBranch(w *webhook_model.Webhook, branch string) bool { @@ -188,8 +222,7 @@ func PrepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventT return err } - go hookQueue.Add(strconv.FormatInt(repo.ID, 10)) - return nil + return addToTask(repo.ID) } func prepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventType, p api.Payloader) error { @@ -240,7 +273,5 @@ func ReplayHookTask(w *webhook_model.Webhook, uuid string) error { return err } - go hookQueue.Add(strconv.FormatInt(t.RepoID, 10)) - - return nil + return addToTask(t.RepoID) } diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 4517f0029c79c..743eaa2efa35c 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -191,7 +191,7 @@
{{.i18n.Tr "repo.pulls.has_pull_request" (Escape $.RepoLink) (Escape $.RepoRelPath) .PullRequest.Index | Safe}}

- {{RenderIssueTitle .PullRequest.Issue.Title $.RepoLink $.Repository.ComposeMetas}} + {{RenderIssueTitle $.Context .PullRequest.Issue.Title $.RepoLink $.Repository.ComposeMetas}} #{{.PullRequest.Issue.Index}}

diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 9e1d83b836bcb..c5efd3d2d4f66 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -128,6 +128,9 @@ {{.i18n.Tr "repo.issues.context.reference_issue"}} {{end}} + diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js index 6fd2ba6c36b78..1745331768482 100644 --- a/web_src/js/features/imagediff.js +++ b/web_src/js/features/imagediff.js @@ -4,10 +4,12 @@ function getDefaultSvgBoundsIfUndefined(svgXml, src) { const DefaultSize = 300; const MaxSize = 99999; - const svg = svgXml.rootElement; - - const width = svg.width.baseVal; - const height = svg.height.baseVal; + const svg = svgXml.documentElement; + const width = svg?.width?.baseVal; + const height = svg?.height?.baseVal; + if (width === undefined || height === undefined) { + return null; // in case some svg is invalid or doesn't have the width/height + } if (width.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE || height.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE) { const img = new Image(); img.src = src; @@ -29,6 +31,7 @@ function getDefaultSvgBoundsIfUndefined(svgXml, src) { height: DefaultSize }; } + return null; } export default function initImageDiff() { @@ -88,6 +91,10 @@ export default function initImageDiff() { info.$image.on('load', () => { info.loaded = true; setReadyIfLoaded(); + }).on('error', () => { + info.loaded = true; + setReadyIfLoaded(); + info.$boundsInfo.text('(image error)'); }); info.$image.attr('src', info.path); diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index a4b6e433a5927..d7b4baac83da6 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -15,10 +15,7 @@ function selectRange($list, $select, $from) { // add hashchange to permalink const $issue = $('a.ref-in-new-issue'); const $copyPermalink = $('a.copy-line-permalink'); - - if ($copyPermalink.length === 0) { - return; - } + const $viewGitBlame = $('a.view_git_blame'); const updateIssueHref = function (anchor) { if ($issue.length === 0) { @@ -29,7 +26,22 @@ function selectRange($list, $select, $from) { $issue.attr('href', href); }; + const updateViewGitBlameFragment = function (anchor) { + if ($viewGitBlame.length === 0) { + return; + } + let href = $viewGitBlame.attr('href'); + href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`; + if (anchor.length !== 0) { + href = `${href}#${anchor}`; + } + $viewGitBlame.attr('href', href); + }; + const updateCopyPermalinkHref = function(anchor) { + if ($copyPermalink.length === 0) { + return; + } let link = $copyPermalink.attr('data-clipboard-text'); link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`; $copyPermalink.attr('data-clipboard-text', link); @@ -53,6 +65,7 @@ function selectRange($list, $select, $from) { changeHash(`#L${a}-L${b}`); updateIssueHref(`L${a}-L${b}`); + updateViewGitBlameFragment(`L${a}-L${b}`); updateCopyPermalinkHref(`L${a}-L${b}`); return; } @@ -61,6 +74,7 @@ function selectRange($list, $select, $from) { changeHash(`#${$select.attr('rel')}`); updateIssueHref($select.attr('rel')); + updateViewGitBlameFragment($select.attr('rel')); updateCopyPermalinkHref($select.attr('rel')); } diff --git a/web_src/js/features/stopwatch.js b/web_src/js/features/stopwatch.js index f86a80103871f..c47ba221241bd 100644 --- a/web_src/js/features/stopwatch.js +++ b/web_src/js/features/stopwatch.js @@ -127,6 +127,10 @@ function updateStopwatchData(data) { const watch = data[0]; const btnEl = $('.active-stopwatch-trigger'); if (!watch) { + if (updateTimeInterval) { + clearInterval(updateTimeInterval); + updateTimeInterval = null; + } btnEl.addClass('hidden'); } else { const {repo_owner_name, repo_name, issue_index, seconds} = watch;