diff --git a/docs/content/doc/installation/with-docker.en-us.md b/docs/content/doc/installation/with-docker.en-us.md index 77825729c2df3..6322e0b953a98 100644 --- a/docs/content/doc/installation/with-docker.en-us.md +++ b/docs/content/doc/installation/with-docker.en-us.md @@ -590,7 +590,7 @@ Add the following block to `/etc/ssh/sshd_config`, on the host: ```bash Match User git AuthorizedKeysCommandUser git - AuthorizedKeysCommand ssh -p 2222 -o StrictHostKeyChecking=no git@127.0.0.1 /usr/local/bin/gitea keys -c /data/gitea/conf/app.ini -e git -u %u -t %t -k %k + AuthorizedKeysCommand /usr/bin/ssh -p 2222 -o StrictHostKeyChecking=no git@127.0.0.1 /usr/local/bin/gitea keys -c /data/gitea/conf/app.ini -e git -u %u -t %t -k %k ``` (From 1.16.0 you will not need to set the `-c /data/gitea/conf/app.ini` option.) diff --git a/models/action.go b/models/action.go index f2723a20147cc..e9f5138b40be7 100644 --- a/models/action.go +++ b/models/action.go @@ -328,7 +328,7 @@ type GetFeedsOptions struct { } // GetFeeds returns actions according to the provided options -func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { +func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) { if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil { return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") } @@ -338,7 +338,8 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { return nil, err } - sess := db.GetEngine(db.DefaultContext).Where(cond) + e := db.GetEngine(ctx) + sess := e.Where(cond) opts.SetDefaultValues() sess = db.SetSessionPagination(sess, &opts) @@ -349,7 +350,7 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { return nil, fmt.Errorf("Find: %v", err) } - if err := ActionList(actions).LoadAttributes(); err != nil { + if err := ActionList(actions).loadAttributes(e); err != nil { return nil, fmt.Errorf("LoadAttributes: %v", err) } diff --git a/models/action_list.go b/models/action_list.go index 3f52d3cd5e321..ce621753a46f7 100644 --- a/models/action_list.go +++ b/models/action_list.go @@ -25,7 +25,7 @@ func (actions ActionList) getUserIDs() []int64 { return keysInt64(userIDs) } -func (actions ActionList) loadUsers(e db.Engine) ([]*user_model.User, error) { +func (actions ActionList) loadUsers(e db.Engine) (map[int64]*user_model.User, error) { if len(actions) == 0 { return nil, nil } @@ -42,12 +42,7 @@ func (actions ActionList) loadUsers(e db.Engine) ([]*user_model.User, error) { for _, action := range actions { action.ActUser = userMaps[action.ActUserID] } - return valuesUser(userMaps), nil -} - -// LoadUsers loads actions' all users -func (actions ActionList) LoadUsers() ([]*user_model.User, error) { - return actions.loadUsers(db.GetEngine(db.DefaultContext)) + return userMaps, nil } func (actions ActionList) getRepoIDs() []int64 { @@ -60,45 +55,57 @@ func (actions ActionList) getRepoIDs() []int64 { return keysInt64(repoIDs) } -func (actions ActionList) loadRepositories(e db.Engine) ([]*repo_model.Repository, error) { +func (actions ActionList) loadRepositories(e db.Engine) error { if len(actions) == 0 { - return nil, nil + return nil } repoIDs := actions.getRepoIDs() repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs)) - err := e. - In("id", repoIDs). - Find(&repoMaps) + err := e.In("id", repoIDs).Find(&repoMaps) if err != nil { - return nil, fmt.Errorf("find repository: %v", err) + return fmt.Errorf("find repository: %v", err) } for _, action := range actions { action.Repo = repoMaps[action.RepoID] } - return valuesRepository(repoMaps), nil -} - -// LoadRepositories loads actions' all repositories -func (actions ActionList) LoadRepositories() ([]*repo_model.Repository, error) { - return actions.loadRepositories(db.GetEngine(db.DefaultContext)) + return nil } -// loadAttributes loads all attributes -func (actions ActionList) loadAttributes(e db.Engine) (err error) { - if _, err = actions.loadUsers(e); err != nil { - return +func (actions ActionList) loadRepoOwner(e db.Engine, userMap map[int64]*user_model.User) (err error) { + if userMap == nil { + userMap = make(map[int64]*user_model.User) } - if _, err = actions.loadRepositories(e); err != nil { - return + for _, action := range actions { + repoOwner, ok := userMap[action.Repo.OwnerID] + if !ok { + repoOwner, err = user_model.GetUserByID(action.Repo.OwnerID) + if err != nil { + if user_model.IsErrUserNotExist(err) { + continue + } + return err + } + userMap[repoOwner.ID] = repoOwner + } + action.Repo.Owner = repoOwner } return nil } -// LoadAttributes loads attributes of the actions -func (actions ActionList) LoadAttributes() error { - return actions.loadAttributes(db.GetEngine(db.DefaultContext)) +// loadAttributes loads all attributes +func (actions ActionList) loadAttributes(e db.Engine) error { + userMap, err := actions.loadUsers(e) + if err != nil { + return err + } + + if err := actions.loadRepositories(e); err != nil { + return err + } + + return actions.loadRepoOwner(e, userMap) } diff --git a/models/action_test.go b/models/action_test.go index 0ce9183b9658e..e247a5ec29c80 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -8,6 +8,7 @@ import ( "path" "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -39,7 +40,7 @@ func TestGetFeeds(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - actions, err := GetFeeds(GetFeedsOptions{ + actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{ RequestedUser: user, Actor: user, IncludePrivate: true, @@ -52,7 +53,7 @@ func TestGetFeeds(t *testing.T) { assert.EqualValues(t, user.ID, actions[0].UserID) } - actions, err = GetFeeds(GetFeedsOptions{ + actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{ RequestedUser: user, Actor: user, IncludePrivate: false, @@ -62,13 +63,54 @@ func TestGetFeeds(t *testing.T) { assert.Len(t, actions, 0) } +func TestGetFeedsForRepos(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + privRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) + pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8}).(*repo_model.Repository) + + // private repo & no login + actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{ + RequestedRepo: privRepo, + IncludePrivate: true, + }) + assert.NoError(t, err) + assert.Len(t, actions, 0) + + // public repo & no login + actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{ + RequestedRepo: pubRepo, + IncludePrivate: true, + }) + assert.NoError(t, err) + assert.Len(t, actions, 1) + + // private repo and login + actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{ + RequestedRepo: privRepo, + IncludePrivate: true, + Actor: user, + }) + assert.NoError(t, err) + assert.Len(t, actions, 1) + + // public repo & login + actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{ + RequestedRepo: pubRepo, + IncludePrivate: true, + Actor: user, + }) + assert.NoError(t, err) + assert.Len(t, actions, 1) +} + func TestGetFeeds2(t *testing.T) { // test with an organization user assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - actions, err := GetFeeds(GetFeedsOptions{ + actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{ RequestedUser: org, Actor: user, IncludePrivate: true, @@ -82,7 +124,7 @@ func TestGetFeeds2(t *testing.T) { assert.EqualValues(t, org.ID, actions[0].UserID) } - actions, err = GetFeeds(GetFeedsOptions{ + actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{ RequestedUser: org, Actor: user, IncludePrivate: false, diff --git a/models/fixtures/action.yml b/models/fixtures/action.yml index e3f3d2a97126a..e283b01db29fc 100644 --- a/models/fixtures/action.yml +++ b/models/fixtures/action.yml @@ -3,7 +3,7 @@ user_id: 2 op_type: 12 # close issue act_user_id: 2 - repo_id: 2 + repo_id: 2 # private is_private: true created_unix: 1603228283 @@ -12,7 +12,7 @@ user_id: 3 op_type: 2 # rename repo act_user_id: 2 - repo_id: 3 + repo_id: 3 # private is_private: true content: oldRepoName @@ -21,7 +21,7 @@ user_id: 11 op_type: 1 # create repo act_user_id: 11 - repo_id: 9 + repo_id: 9 # public is_private: false - @@ -29,7 +29,7 @@ user_id: 16 op_type: 12 # close issue act_user_id: 16 - repo_id: 22 + repo_id: 22 # private is_private: true created_unix: 1603267920 @@ -37,7 +37,7 @@ user_id: 10 op_type: 1 # create repo act_user_id: 10 - repo_id: 6 + repo_id: 6 # private is_private: true created_unix: 1603010100 @@ -45,7 +45,7 @@ user_id: 10 op_type: 1 # create repo act_user_id: 10 - repo_id: 7 + repo_id: 7 # private is_private: true created_unix: 1603011300 @@ -53,6 +53,6 @@ user_id: 10 op_type: 1 # create repo act_user_id: 10 - repo_id: 8 + repo_id: 8 # public is_private: false created_unix: 1603011540 # grouped with id:7 diff --git a/models/user_heatmap_test.go b/models/user_heatmap_test.go index 7915363d9574a..9361cb3452fa8 100644 --- a/models/user_heatmap_test.go +++ b/models/user_heatmap_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/json" @@ -72,7 +73,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { } // get the action for comparison - actions, err := GetFeeds(GetFeedsOptions{ + actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{ RequestedUser: user, Actor: doer, IncludePrivate: true, diff --git a/modules/auth/pam/pam.go b/modules/auth/pam/pam.go index 0a3d8e9f91a7d..1acd0b112c6f6 100644 --- a/modules/auth/pam/pam.go +++ b/modules/auth/pam/pam.go @@ -34,10 +34,10 @@ func Auth(serviceName, userName, passwd string) (string, error) { if err = t.Authenticate(0); err != nil { return "", err } - + if err = t.AcctMgmt(0); err != nil { - return "", err - } + return "", err + } // PAM login names might suffer transformations in the PAM stack. // We should take whatever the PAM stack returns for it. diff --git a/modules/context/repo.go b/modules/context/repo.go index 355c40af8a693..e55c13f49cce5 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -418,6 +418,8 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { userName := ctx.Params(":username") repoName := ctx.Params(":reponame") repoName = strings.TrimSuffix(repoName, ".git") + repoName = strings.TrimSuffix(repoName, ".rss") + repoName = strings.TrimSuffix(repoName, ".atom") // Check if the user is the same as the repository owner if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) { diff --git a/options/license/Baekmuk b/options/license/Baekmuk new file mode 100644 index 0000000000000..b86efc04a08c1 --- /dev/null +++ b/options/license/Baekmuk @@ -0,0 +1,9 @@ +Copyright (c) 1986-2002 Kim Jeong-Hwan All rights reserved. + +Permission to use, copy, modify and distribute this font +is hereby granted, provided that both the copyright notice +and this permission notice appear in all copies of the +font, derivative works or modified versions, and that the +following acknowledgement appear in supporting documentation: +Baekmuk Batang, Baekmuk Dotum, Baekmuk Gulim, and Baekmuk +Headline are registered trademarks owned by Kim Jeong-Hwan. diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 1441903e590e7..781d5516015ed 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -547,9 +547,17 @@ language=Sprache ui=Theme comment_type_group_label=Label comment_type_group_milestone=Meilenstein +comment_type_group_title=Titel comment_type_group_branch=Branch +comment_type_group_time_tracking=Zeiterfassung comment_type_group_deadline=Frist +comment_type_group_dependency=Abhängigkeit +comment_type_group_lock=Sperrstatus comment_type_group_review_request=Angeforderte Reviews +comment_type_group_pull_request_push=Hinzugefügte Commits +comment_type_group_project=Projekt +comment_type_group_issue_ref=Issue-Referenz +saved_successfully=Die Einstellungen wurden erfolgreich gespeichert. privacy=Datenschutz keep_activity_private=Aktivität auf der Profilseite ausblenden keep_activity_private_popup=Macht die Aktivität nur für dich und die Administratoren sichtbar @@ -804,6 +812,7 @@ clone_helper=Benötigst du Hilfe beim Klonen? Öffne die %s“ einchecken. @@ -1092,6 +1105,7 @@ editor.cannot_commit_to_protected_branch=Commit in den geschützten Branch „%s editor.no_commit_to_branch=Kann nicht direkt zum Branch committen, da: editor.user_no_push_to_branch=Benutzer kann nicht in die Branch pushen editor.require_signed_commit=Branch erfordert einen signierten Commit +editor.cherry_pick=Cherry-Picke %s von: commits.desc=Durchsuche die Quellcode-Änderungshistorie. commits.commits=Commits @@ -1112,6 +1126,8 @@ commits.signed_by_untrusted_user_unmatched=Signiert von nicht vertrauenswürdige commits.gpg_key_id=GPG-Schlüssel-ID commits.ssh_key_fingerprint=SSH-Key-Fingerabdruck +commit.actions=Aktionen +commit.revert=Zurücksetzen ext_issues=Zugriff auf Externe Issues ext_issues.desc=Link zu externem Issuetracker. @@ -2843,7 +2859,7 @@ commit_repo=hat %[3]s auf %[4]s gepusht create_issue=`hat Ticket %[3]s#%[2]s geöffnet` close_issue=`Ticket %[3]s#%[2]s geschlossen` reopen_issue=`Ticket %[3]s#%[2]s wiedereröffnet` -create_pull_request=`Pull-Request %[3]s#%[2]s wurde erstellt` +create_pull_request=`hat den Pull-Request %[3]s#%[2]s erstellt` close_pull_request=`Pull-Request %[3]s#%[2]s wurde geschlossen` reopen_pull_request=`Pull-Request %[3]s#%[2]s wurde wiedereröffnet` comment_issue=`Ticket %[3]s#%[2]s wurde kommentiert` diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index c9ad797a19130..2d80d8cb810b8 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -268,6 +268,7 @@ search=Buscar code=Código search.fuzzy=Parcial search.match=Coincidir +code_search_unavailable=Actualmente la búsqueda de código no está disponible. Póngase en contacto con el administrador de su sitio. repo_no_results=No se ha encontrado ningún repositorio coincidente. user_no_results=No se ha encontrado ningún usuario coincidente. org_no_results=No se ha encontrado ninguna organización coincidente. @@ -549,6 +550,14 @@ continue=Continuar cancel=Cancelar language=Idioma ui=Tema +hidden_comment_types=Tipos de comentarios ocultos +comment_type_group_reference=Referencia +comment_type_group_label=Etiqueta +comment_type_group_milestone=Hito +comment_type_group_assignee=Asignado +comment_type_group_title=Título +comment_type_group_branch=Rama +comment_type_group_time_tracking=Seguimiento de Tiempo privacy=Privacidad keep_activity_private=Ocultar la actividad de la página del perfil keep_activity_private_popup=Hace la actividad visible sólo para ti y los administradores @@ -2779,6 +2788,9 @@ monitor.queue.pool.flush.title=Vaciar cola monitor.queue.pool.flush.desc=Al vaciar la cola se añadirá un worker que terminará una vez que la cola esté vacía, o se agote. monitor.queue.pool.flush.submit=Añadir trabajador de vaciado monitor.queue.pool.flush.added=Trabajador de vaciado añadido por %[1]s +monitor.queue.pool.pause.title=Pausar cola +monitor.queue.pool.pause.submit=Pausar cola +monitor.queue.pool.resume.submit=Reanudar cola monitor.queue.settings.title=Ajustes del grupo monitor.queue.settings.desc=Los grupos de trabajadores se crean dinámicamente como un impulso en respuesta al bloqueo de la cola de sus trabajadores. Estos cambios no afectarán a los grupos de trabajadores actuales. diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 384d70ee2c59c..7ee30ea660086 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -57,8 +57,8 @@ new_migrate=Nova migração new_mirror=Novo espelho new_fork=Nova derivação do repositório new_org=Nova organização -new_project=Novo projecto -new_project_board=Novo painel para o projecto +new_project=Novo plano +new_project_board=Novo painel para o plano manage_org=Gerir organizações admin_panel=Administração do sítio account_settings=Configurações da conta @@ -499,7 +499,7 @@ activity=Trabalho público followers=Seguidores starred=Repositórios favoritos watched=Repositórios sob vigilância -projects=Projectos +projects=Planos following=Que segue follow=Seguir unfollow=Deixar de seguir @@ -562,7 +562,7 @@ comment_type_group_deadline=Prazo comment_type_group_dependency=Dependência comment_type_group_review_request=Pedido de revisão comment_type_group_pull_request_push=Cometimentos adicionados -comment_type_group_project=Projecto +comment_type_group_project=Plano comment_type_group_issue_ref=Referência da questão saved_successfully=As suas configurações foram guardadas com sucesso. privacy=Privacidade @@ -801,7 +801,7 @@ visibility.private=Privada visibility.private_tooltip=Visível apenas para membros da organização [repo] -new_repo_helper=Um repositório contém todos os ficheiros do projecto, incluindo o histórico das revisões. Já o tem noutro sítio? Migre o repositório. +new_repo_helper=Um repositório contém todos os ficheiros do trabalho, incluindo o histórico das revisões. Já o tem noutro sítio? Migre o repositório. owner=Proprietário(a) owner_helper=Algumas organizações podem não aparecer na lista suspensa devido a um limite máximo de contagem de repositórios. repo_name=Nome do repositório @@ -838,10 +838,10 @@ issue_labels=Rótulos para as questões issue_labels_helper=Escolha um conjunto de rótulos para as questões. license=Licença license_helper=Escolha um ficheiro de licença. -license_helper_desc=Uma licença rege o que os outros podem, ou não, fazer com o seu código fonte. Não tem a certeza sobre qual a mais indicada para o seu projecto? Veja: Escolher uma licença. +license_helper_desc=Uma licença rege o que os outros podem, ou não, fazer com o seu código fonte. Não tem a certeza sobre qual a mais indicada para o seu trabalho? Veja: Escolher uma licença. readme=README readme_helper=Escolha um modelo de ficheiro README. -readme_helper_desc=Este é o sítio onde pode escrever uma descrição completa do seu projecto. +readme_helper_desc=Este é o sítio onde pode escrever uma descrição completa do seu trabalho. auto_init=Inicializar repositório (adiciona `.gitignore`, `LICENSE` e `README.md`) trust_model_helper=Escolha o modelo de confiança para a validação das assinaturas. As opções são: trust_model_helper_collaborator=Colaborador: Confiar nas assinaturas dos colaboradores @@ -1005,7 +1005,7 @@ branches=Ramos tags=Etiquetas issues=Questões pulls=Pedidos de integração -project_board=Projectos +project_board=Planos labels=Rótulos org_labels_desc=Rótulos ao nível da organização que podem ser usados em todos os repositórios desta organização org_labels_desc_manage=gerir @@ -1146,27 +1146,27 @@ commit.cherry-pick-content=Escolha o ramo para onde vai escolher a dedo: ext_issues=Acesso a questões externas ext_issues.desc=Ligação para um rastreador de questões externo. -projects=Projectos -projects.desc=Gerir questões e integrações nos quadros do projecto. +projects=Planos +projects.desc=Gerir questões e integrações nos quadros do plano. projects.description=Descrição (opcional) projects.description_placeholder=Descrição -projects.create=Criar projecto +projects.create=Criar plano projects.title=Título -projects.new=Novo projecto -projects.new_subheader=Coordene, acompanhe e modifique o seu trabalho num só lugar, para que os projectos se mantenham transparentes e cumpram o calendário. -projects.create_success=O projecto '%s' foi criado. -projects.deletion=Eliminar projecto -projects.deletion_desc=Eliminar um projecto remove-o de todas as questões relacionadas. Continuar? -projects.deletion_success=O projecto foi eliminado. -projects.edit=Editar projectos -projects.edit_subheader=Projectos organizam questões e acompanham o progresso. -projects.modify=Modificar projecto -projects.edit_success=O projecto '%s' foi modificado. +projects.new=Novo plano +projects.new_subheader=Coordene, acompanhe e modifique o seu trabalho num só lugar, para que os planos se mantenham transparentes e cumpram o calendário. +projects.create_success=O plano '%s' foi criado. +projects.deletion=Eliminar plano +projects.deletion_desc=Eliminar um plano remove-o de todas as questões relacionadas. Continuar? +projects.deletion_success=O plano foi eliminado. +projects.edit=Editar planos +projects.edit_subheader=Planos organizam questões e acompanham o progresso. +projects.modify=Modificar plano +projects.edit_success=O plano '%s' foi modificado. projects.type.none=Nenhum projects.type.basic_kanban=Kanban básico projects.type.bug_triage=Triagem de erros -projects.template.desc=Modelo de projecto -projects.template.desc_helper=Escolha um modelo de projecto para começar +projects.template.desc=Modelo de plano +projects.template.desc_helper=Escolha um modelo de plano para começar projects.type.uncategorized=Sem categoria projects.board.edit=Editar painel projects.board.edit_title=Novo nome para o painel @@ -1176,7 +1176,7 @@ projects.board.new=Novo painel projects.board.set_default=Definir como padrão projects.board.set_default_desc=Definir este painel como padrão para questões e pedidos de integração não categorizados projects.board.delete=Eliminar painel -projects.board.deletion_desc=Eliminar um painel de projecto faz com que todas as questões nesse painel sejam movidas para o painel 'Sem categoria'. Continuar? +projects.board.deletion_desc=Eliminar um painel do plano faz com que todas as questões nesse painel sejam movidas para o painel 'Sem categoria'. Continuar? projects.board.color=Cor projects.open=Abrir projects.close=Fechar @@ -1185,7 +1185,7 @@ projects.board.assigned_to=Atribuído a issues.desc=Organize relatórios de erros, tarefas e etapas. issues.filter_assignees=Filtrar responsável issues.filter_milestones=Filtrar etapa -issues.filter_projects=Filtrar projecto +issues.filter_projects=Filtrar plano issues.filter_labels=Filtrar rótulo issues.filter_reviewers=Filtrar revisor issues.new=Questão nova @@ -1194,12 +1194,12 @@ issues.new.labels=Rótulos issues.new.add_labels_title=Aplicar rótulos issues.new.no_label=Sem rótulo issues.new.clear_labels=Retirar rótulos -issues.new.projects=Projectos -issues.new.add_project_title=Definir projecto -issues.new.clear_projects=Limpar projectos -issues.new.no_projects=Nenhum projecto -issues.new.open_projects=Projectos abertos -issues.new.closed_projects=Projectos fechados +issues.new.projects=Planos +issues.new.add_project_title=Definir plano +issues.new.clear_projects=Limpar planos +issues.new.no_projects=Nenhum plano +issues.new.open_projects=Planos abertos +issues.new.closed_projects=Planos fechados issues.new.no_items=Sem itens issues.new.milestone=Etapa issues.new.add_milestone_title=Definir etapa @@ -1233,11 +1233,11 @@ issues.remove_label=removeu o rótulo %s %s issues.remove_labels=removeu os rótulos %s %s issues.add_remove_labels=adicionou o(s) rótulo(s) %s e removeu %s %s issues.add_milestone_at=`adicionou esta questão à etapa %s %s` -issues.add_project_at=`adicionou esta questão ao projecto %s %s` +issues.add_project_at=`adicionou esta questão ao plano %s %s` issues.change_milestone_at=`modificou a etapa de %s para %s %s` -issues.change_project_at=`modificou o projecto de %s para %s %s` +issues.change_project_at=`modificou o plano de %s para %s %s` issues.remove_milestone_at=`removeu esta questão da etapa %s %s` -issues.remove_project_at=`removeu isto do projecto %s %s` +issues.remove_project_at=`removeu isto do plano %s %s` issues.deleted_milestone=`(eliminada)` issues.deleted_project=`(eliminado)` issues.self_assign_at=`atribuiu a si mesmo(a) esta questão %s` @@ -1373,6 +1373,9 @@ issues.lock.reason=Motivo do bloqueio issues.lock.title=Bloquear diálogo sobre esta questão. issues.unlock.title=Desbloquear diálogo sobre esta questão. issues.comment_on_locked=Não pode comentar numa questão bloqueada. +issues.delete=Eliminar +issues.delete.title=Pretende eliminar esta questão? +issues.delete.text=Tem a certeza que quer eliminar esta questão? Isso irá remover todo o conteúdo permanentemente. Como alternativa considere fechá-la, se pretender mantê-la em arquivo. issues.tracker=Gestor de tempo issues.start_tracking_short=Iniciar cronómetro issues.start_tracking=Iniciar contagem de tempo @@ -1576,7 +1579,7 @@ pulls.closed_at=`fechou este pedido de integração pulls.reopened_at=`reabriu este pedido de integração %[2]s` pulls.merge_instruction_hint=`Também pode ver as instruções para a linha de comandos.` -pulls.merge_instruction_step1_desc=No seu repositório do projecto, crie um novo ramo e teste as modificações. +pulls.merge_instruction_step1_desc=No seu repositório, crie um novo ramo e teste as modificações. pulls.merge_instruction_step2_desc=Integre as modificações e envie para o Gitea. milestones.new=Nova etapa @@ -1788,7 +1791,7 @@ settings.pulls.allow_manual_merge=Habilitar a marcação dos pedidos de integra settings.pulls.enable_autodetect_manual_merge=Habilitar a identificação automática de integrações manuais (obs.: nalguns casos especiais a avaliação pode ser errada) settings.pulls.allow_rebase_update=Habilitar a modificação do ramo do pedido de integração através da mudança de base settings.pulls.default_delete_branch_after_merge=Eliminar o ramo do pedido de integração depois de finalizada a integração, como predefinição -settings.projects_desc=Habilitar projectos no repositório +settings.projects_desc=Habilitar planos no repositório settings.admin_settings=Configurações do administrador settings.admin_enable_health_check=Habilitar verificações de integridade (git fsck) no repositório settings.admin_code_indexer=Indexador de código diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go index 4dbd9c9d0e71a..64801a6078bca 100644 --- a/routers/web/feed/convert.go +++ b/routers/web/feed/convert.go @@ -7,12 +7,15 @@ package feed import ( "fmt" "html" + "net/http" "net/url" "strconv" "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/util" @@ -44,8 +47,27 @@ func toReleaseLink(act *models.Action) string { return act.GetRepoLink() + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch()) } +// renderMarkdown creates a minimal markdown render context from an action. +// If rendering fails, the original markdown text is returned +func renderMarkdown(ctx *context.Context, act *models.Action, content string) string { + markdownCtx := &markup.RenderContext{ + Ctx: ctx, + URLPrefix: act.GetRepoLink(), + Type: markdown.MarkupName, + Metas: map[string]string{ + "user": act.GetRepoUserName(), + "repo": act.GetRepoName(), + }, + } + markdown, err := markdown.RenderString(markdownCtx, content) + if err != nil { + return content + } + return markdown +} + // feedActionsToFeedItems convert gitea's Action feed to feeds Item -func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) { +func feedActionsToFeedItems(ctx *context.Context, actions models.ActionList) (items []*feeds.Item, err error) { for _, act := range actions { act.LoadActUser() @@ -192,12 +214,12 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite case models.ActionCreateIssue, models.ActionCreatePullRequest: desc = strings.Join(act.GetIssueInfos(), "#") - content = act.GetIssueContent() + content = renderMarkdown(ctx, act, act.GetIssueContent()) case models.ActionCommentIssue, models.ActionApprovePullRequest, models.ActionRejectPullRequest, models.ActionCommentPull: desc = act.GetIssueTitle() comment := act.GetIssueInfos()[1] if len(comment) != 0 { - desc += "\n\n" + comment + desc += "\n\n" + renderMarkdown(ctx, act, comment) } case models.ActionMergePullRequest: desc = act.GetIssueInfos()[1] @@ -226,3 +248,18 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite } return } + +// GetFeedType return if it is a feed request and altered name and feed type. +func GetFeedType(name string, req *http.Request) (bool, string, string) { + if strings.HasSuffix(name, ".rss") || + strings.Contains(req.Header.Get("Accept"), "application/rss+xml") { + return true, strings.TrimSuffix(name, ".rss"), "rss" + } + + if strings.HasSuffix(name, ".atom") || + strings.Contains(req.Header.Get("Accept"), "application/atom+xml") { + return true, strings.TrimSuffix(name, ".atom"), "atom" + } + + return false, name, "" +} diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go index 4c1eff04a9d94..a7b8efcdbe578 100644 --- a/routers/web/feed/profile.go +++ b/routers/web/feed/profile.go @@ -15,48 +15,9 @@ import ( "github.com/gorilla/feeds" ) -// RetrieveFeeds loads feeds for the specified user -func RetrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*models.Action { - actions, err := models.GetFeeds(options) - if err != nil { - ctx.ServerError("GetFeeds", err) - return nil - } - - // TODO: move load repoOwner of act.Repo into models.GetFeeds->loadAttributes() - { - userCache := map[int64]*user_model.User{options.RequestedUser.ID: options.RequestedUser} - if ctx.User != nil { - userCache[ctx.User.ID] = ctx.User - } - for _, act := range actions { - if act.ActUser != nil { - userCache[act.ActUserID] = act.ActUser - } - } - for _, act := range actions { - repoOwner, ok := userCache[act.Repo.OwnerID] - if !ok { - repoOwner, err = user_model.GetUserByID(act.Repo.OwnerID) - if err != nil { - if user_model.IsErrUserNotExist(err) { - continue - } - ctx.ServerError("GetUserByID", err) - return nil - } - userCache[repoOwner.ID] = repoOwner - } - act.Repo.Owner = repoOwner - } - } - - return actions -} - // ShowUserFeed show user activity as RSS / Atom feed func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType string) { - actions := RetrieveFeeds(ctx, models.GetFeedsOptions{ + actions, err := models.GetFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: false, @@ -64,7 +25,8 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str IncludeDeleted: false, Date: ctx.FormString("date"), }) - if ctx.Written() { + if err != nil { + ctx.ServerError("GetFeeds", err) return } @@ -75,7 +37,6 @@ func ShowUserFeed(ctx *context.Context, ctxUser *user_model.User, formatType str Created: time.Now(), } - var err error feed.Items, err = feedActionsToFeedItems(ctx, actions) if err != nil { ctx.ServerError("convert feed", err) diff --git a/routers/web/feed/repo.go b/routers/web/feed/repo.go new file mode 100644 index 0000000000000..53fb8148e07d2 --- /dev/null +++ b/routers/web/feed/repo.go @@ -0,0 +1,44 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package feed + +import ( + "time" + + "code.gitea.io/gitea/models" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/context" + + "github.com/gorilla/feeds" +) + +// ShowRepoFeed shows user activity on the repo as RSS / Atom feed +func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType string) { + actions, err := models.GetFeeds(ctx, models.GetFeedsOptions{ + RequestedRepo: repo, + Actor: ctx.User, + IncludePrivate: true, + Date: ctx.FormString("date"), + }) + if err != nil { + ctx.ServerError("GetFeeds", err) + return + } + + feed := &feeds.Feed{ + Title: ctx.Tr("home.feed_of", repo.FullName()), + Link: &feeds.Link{Href: repo.HTMLURL()}, + Description: repo.Description, + Created: time.Now(), + } + + feed.Items, err = feedActionsToFeedItems(ctx, actions) + if err != nil { + ctx.ServerError("convert feed", err) + return + } + + writeFeed(ctx, feed, formatType) +} diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 9ff72b2102362..5293d3c6a3a90 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -38,6 +38,7 @@ import ( "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/web/feed" ) const ( @@ -691,6 +692,14 @@ func checkHomeCodeViewable(ctx *context.Context) { // Home render repository home page func Home(ctx *context.Context) { + isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req) + if isFeed { + feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType) + return + } + + ctx.Data["FeedURL"] = ctx.Repo.Repository.HTMLURL() + checkHomeCodeViewable(ctx) if ctx.Written() { return diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 379e1f8e20762..877aa452804b6 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -29,7 +29,6 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/routers/web/feed" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" @@ -131,7 +130,7 @@ func Dashboard(ctx *context.Context) { ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["Mirrors"] = mirrors - ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{ + ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, RequestedTeam: ctx.Org.Team, Actor: ctx.User, @@ -140,8 +139,8 @@ func Dashboard(ctx *context.Context) { IncludeDeleted: false, Date: ctx.FormString("date"), }) - - if ctx.Written() { + if err != nil { + ctx.ServerError("GetFeeds", err) return } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index b4198ef8fd8b8..b84cee2b3ab9d 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -74,19 +74,7 @@ func Profile(ctx *context.Context) { uname = strings.TrimSuffix(uname, ".gpg") } - showFeedType := "" - if strings.HasSuffix(uname, ".rss") { - showFeedType = "rss" - uname = strings.TrimSuffix(uname, ".rss") - } else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { - showFeedType = "rss" - } - if strings.HasSuffix(uname, ".atom") { - showFeedType = "atom" - uname = strings.TrimSuffix(uname, ".atom") - } else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") { - showFeedType = "atom" - } + isShowFeed, uname, showFeedType := feed.GetFeedType(uname, ctx.Req) ctxUser := GetUserByName(ctx, uname) if ctx.Written() { @@ -95,7 +83,7 @@ func Profile(ctx *context.Context) { if ctxUser.IsOrganization() { // Show Org RSS feed - if len(showFeedType) != 0 { + if isShowFeed { feed.ShowUserFeed(ctx, ctxUser, showFeedType) return } @@ -123,11 +111,14 @@ func Profile(ctx *context.Context) { } // Show User RSS feed - if len(showFeedType) != 0 { + if isShowFeed { feed.ShowUserFeed(ctx, ctxUser, showFeedType) return } + // advertise feed via meta tag + ctx.Data["FeedURL"] = ctxUser.HTMLURL() + // Show OpenID URIs openIDs, err := user_model.GetUserOpenIDs(ctxUser.ID) if err != nil { @@ -259,7 +250,7 @@ func Profile(ctx *context.Context) { total = ctxUser.NumFollowing case "activity": - ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{ + ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{ RequestedUser: ctxUser, Actor: ctx.User, IncludePrivate: showPrivate, @@ -267,7 +258,8 @@ func Profile(ctx *context.Context) { IncludeDeleted: false, Date: ctx.FormString("date"), }) - if ctx.Written() { + if err != nil { + ctx.ServerError("GetFeeds", err) return } case "stars": diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 32e206a95d58e..666246a18adaf 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -14,6 +14,10 @@ {{if .GoGetImport}} +{{end}} +{{if .FeedURL}} + + {{end}}