diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml index ca0f16b07b6c..c488d0677f18 100644 --- a/.stylelintrc.yaml +++ b/.stylelintrc.yaml @@ -1,12 +1,18 @@ plugins: - stylelint-declaration-strict-value +ignoreFiles: + - "**/*.go" + overrides: - files: ["**/*.less"] customSyntax: postcss-less - files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"] rules: scale-unlimited/declaration-strict-value: null + - files: ["**/chroma/*", "**/codemirror/*"] + rules: + block-no-empty: null rules: alpha-value-notation: null diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..979831eb9b8b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,96 @@ +# Gitea Community Code of Conduct + +## About + +Online communities include people from many different backgrounds. The Gitea contributors are committed to providing a friendly, safe and welcoming environment for all, regardless of gender identity and expression, sexual orientation, disabilities, neurodiversity, physical appearance, body size, ethnicity, nationality, race, age, religion, or similar personal characteristics. + +The first goal of the Code of Conduct is to specify a baseline standard of behavior so that people with different social values and communication styles can talk about Gitea effectively, productively, and respectfully. + +The second goal is to provide a mechanism for resolving conflicts in the community when they arise. + +The third goal of the Code of Conduct is to make our community welcoming to people from different backgrounds. Diversity is critical to the project; for Gitea to be successful, it needs contributors and users from all backgrounds. + +We believe that healthy debate and disagreement are essential to a healthy project and community. However, it is never ok to be disrespectful. We value diverse opinions, but we value respectful behavior more. + +## Community values + +These are the values to which people in the Gitea community should aspire. + +- **Be friendly and welcoming.** +- **Be patient.** + - Remember that people have varying communication styles and that not everyone is using their native language. (Meaning and tone can be lost in translation.) +- **Be thoughtful.** + - Productive communication requires effort. Think about how your words will be interpreted. + - Remember that sometimes it is best to refrain entirely from commenting. +- **Be respectful.** + - In particular, respect differences of opinion. +- **Be charitable.** + - Interpret the arguments of others in good faith, do not seek to disagree. + - When we do disagree, try to understand why. +- **Be constructive.** + - Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation. + - Avoid unconstructive criticism: don't merely decry the current state of affairs; offer—or at least solicit—suggestions as to how things may be improved. + - Avoid snarking (pithy, unproductive, sniping comments) + - Avoid discussing potentially offensive or sensitive issues; this all too often leads to unnecessary conflict. + - Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults to a person or group). +- **Be responsible.** + - What you say and do matters. Take responsibility for your words and actions, including their consequences, whether intended or otherwise. + +People are complicated. You should expect to be misunderstood and to misunderstand others; when this inevitably occurs, resist the urge to be defensive or assign blame. Try not to take offense where no offense was intended. Give people the benefit of the doubt. Even if the intent was to provoke, do not rise to it. It is the responsibility of all parties to de-escalate conflict when it arises. + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject: comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, as well as to ban (temporarily or permanently) any contributor for behaviors that they deem inappropriate, threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project Stewards have a reasonable belief that an individual’s behavior may have a negative impact on the project or its community. + +### Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement often yield positive results. However, it is never okay to be disrespectful or to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address the behavior directly with those involved. Many issues can be resolved quickly and easily, and this gives people more control over the outcome of their dispute. If you are unable to resolve the matter for any reason, or if the behavior is threatening or harassing, report it. We are dedicated to providing an environment where participants feel welcome and safe. + +Reports should be directed to the Gitea Project Stewards at conduct@gitea.com. It is the Project Stewards’ duty to receive and address reported violations of the code of conduct. They will then work with a committee consisting of representatives from the technical-oversight-committee. + +We will investigate every complaint, but you may not receive a direct response. We will use our discretion in determining when and how to follow up on reported incidents, which may range from not taking action to permanent expulsion from the project and project-sponsored spaces. Under normal circumstances, we will notify the accused of the report and provide them an opportunity to discuss it before any action is taken. If there is a consensus between maintainers that such an endeavor would be useless (i.e. in case of an obvious spammer), we reserve the right to take action without notifying the accused first. The identity of the reporter will be omitted from the details of the report supplied to the accused. In potentially harmful situations, such as ongoing harassment or threats to anyone’s safety, we may take action without notice. + +### Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +## Summary + +- Treat everyone with respect and kindness. +- Be thoughtful in how you communicate. +- Don’t be destructive or inflammatory. +- If you encounter an issue, please mail conduct@gitea.com. diff --git a/contrib/init/ubuntu/gitea b/contrib/init/ubuntu/gitea new file mode 100644 index 000000000000..da56b6e4a975 --- /dev/null +++ b/contrib/init/ubuntu/gitea @@ -0,0 +1,84 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: gitea +# Required-Start: $syslog $network +# Required-Stop: $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: A self-hosted Git service written in Go. +# Description: A self-hosted Git service written in Go. +### END INIT INFO + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin +DESC="Gitea - Git with a cup of tea" +NAME=gitea +SERVICEVERBOSE=yes +PIDFILE=/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME +WORKINGDIR=/var/lib/$NAME +DAEMON=/usr/local/bin/$NAME +DAEMON_ARGS="web -c /etc/$NAME/app.ini" +USER=git +STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/1/KILL/5}" + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +do_start() +{ + GITEA_ENVS="USER=$USER GITEA_WORK_DIR=$WORKINGDIR HOME=/home/$USER" + GITEA_EXEC="$DAEMON -- $DAEMON_ARGS" + sh -c "start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\ + --background --chdir $WORKINGDIR --chuid $USER \\ + --exec /bin/bash -- -c '/usr/bin/env $GITEA_ENVS $GITEA_EXEC'" +} + +do_stop() +{ + start-stop-daemon --stop --quiet --retry=$STOP_SCHEDULE --pidfile $PIDFILE --name $NAME --oknodo + rm -f $PIDFILE +} + +do_status() +{ + if [ -f $PIDFILE ]; then + if kill -0 $(cat "$PIDFILE"); then + echo "$NAME is running, PID is $(cat $PIDFILE)" + else + echo "$NAME process is dead, but pidfile exists" + fi + else + echo "$NAME is not running" + fi +} + +case "$1" in + start) + echo "Starting $DESC" "$NAME" + do_start + ;; + stop) + echo "Stopping $DESC" "$NAME" + do_stop + ;; + status) + do_status + ;; + restart) + echo "Restarting $DESC" "$NAME" + do_stop + do_start + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2 + exit 2 + ;; +esac + +exit 0 diff --git a/docs/content/doc/packages/maven.en-us.md b/docs/content/doc/packages/maven.en-us.md index 22da3c16d294..2ec5ca2ab710 100644 --- a/docs/content/doc/packages/maven.en-us.md +++ b/docs/content/doc/packages/maven.en-us.md @@ -23,7 +23,7 @@ Publish [Maven](https://maven.apache.org) packages for your user or organization ## Requirements To work with the Maven package registry, you can use [Maven](https://maven.apache.org/install.html) or [Gradle](https://gradle.org/install/). -The following examples use `Maven`. +The following examples use `Maven` and `Gradle Groovy`. ## Configuring the package registry @@ -73,6 +73,40 @@ Afterwards add the following sections to your project `pom.xml` file: | `access_token` | Your [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}). | | `owner` | The owner of the package. | +### Gradle variant + +When you plan to add some packages from Gitea instance in your project, you should add it in repositories section: + +```groovy +repositories { + // other repositories + maven { url "https://gitea.example.com/api/packages/{owner}/maven" } +} +``` + +In Groovy gradle you may include next script in your publishing part: + +```groovy +publishing { + // other settings of publication + repositories { + maven { + name = "Gitea" + url = uri("https://gitea.example.com/api/packages/{owner}/maven") + + credentials(HttpHeaderCredentials) { + name = "Authorization" + value = "token {access_token}" + } + + authentication { + header(HttpHeaderAuthentication) + } + } + } +} +``` + ## Publish a package To publish a package simply run: @@ -81,6 +115,12 @@ To publish a package simply run: mvn deploy ``` +Or call `gradle` with task `publishAllPublicationsToGiteaRepository` in case you are using gradle: + +```groovy +./gradlew publishAllPublicationsToGiteaRepository +``` + If you want to publish a prebuild package to the registry, you can use [`mvn deploy:deploy-file`](https://maven.apache.org/plugins/maven-deploy-plugin/deploy-file-mojo.html): ```shell @@ -105,6 +145,12 @@ To install a Maven package from the package registry, add a new dependency to yo ``` +And analog in gradle groovy: + +```groovy +implementation "com.test.package:test_project:1.0.0" +``` + Afterwards run: ```shell diff --git a/modules/git/command.go b/modules/git/command.go index 0bc8103116a0..9a65279a8cb5 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -179,7 +179,7 @@ func (c *Command) AddDashesAndList(list ...string) *Command { } // ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs -// In most cases, it shouldn't be used. Use AddXxx function instead +// In most cases, it shouldn't be used. Use NewCommand().AddXxx() function instead func ToTrustedCmdArgs(args []string) TrustedCmdArgs { ret := make(TrustedCmdArgs, len(args)) for i, arg := range args { diff --git a/modules/git/commit.go b/modules/git/commit.go index 4a55645d3034..6e8fcb3e0802 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -218,6 +218,19 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) { return false, err } +// IsForcePush returns true if a push from oldCommitHash to this is a force push +func (c *Commit) IsForcePush(oldCommitID string) (bool, error) { + if oldCommitID == EmptySHA { + return false, nil + } + oldCommit, err := c.repo.GetCommit(oldCommitID) + if err != nil { + return false, err + } + hasPreviousCommit, err := c.HasPreviousCommit(oldCommit.ID) + return !hasPreviousCommit, err +} + // CommitsBeforeLimit returns num commits before current revision func (c *Commit) CommitsBeforeLimit(num int) ([]*Commit, error) { return c.repo.getCommitsBeforeLimit(c.ID, num) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index a62e0670fedc..0e1b00ce082e 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -323,6 +323,27 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) } +// CommitsBetweenNotBase returns a list that contains commits between [before, last), excluding commits in baseBranch. +// If before is detached (removed by reset + push) it is not included. +func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch string) ([]*Commit, error) { + var stdout []byte + var err error + if before == nil { + stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + } else { + stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + if err != nil && strings.Contains(err.Error(), "no merge base") { + // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. + // previously it would return the results of git rev-list before last so let's try that... + stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + } + } + if err != nil { + return nil, err + } + return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) +} + // CommitsBetweenIDs return commits between twoe commits func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error) { lastCommit, err := repo.GetCommit(last) diff --git a/modules/repository/push.go b/modules/repository/push.go index 1fa711b359c3..aa1552351d51 100644 --- a/modules/repository/push.go +++ b/modules/repository/push.go @@ -4,10 +4,8 @@ package repository import ( - "context" "strings" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" ) @@ -96,19 +94,3 @@ func (opts *PushUpdateOptions) RefName() string { func (opts *PushUpdateOptions) RepoFullName() string { return opts.RepoUserName + "/" + opts.RepoName } - -// IsForcePush detect if a push is a force push -func IsForcePush(ctx context.Context, opts *PushUpdateOptions) (bool, error) { - if !opts.IsUpdateBranch() { - return false, nil - } - - output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(opts.OldCommitID, "^"+opts.NewCommitID). - RunStdString(&git.RunOpts{Dir: repo_model.RepoPath(opts.RepoUserName, opts.RepoName)}) - if err != nil { - return false, err - } else if len(output) > 0 { - return true, nil - } - return false, nil -} diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 3c8d1a79fd8c..7a42eec18182 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -57,6 +57,7 @@ new_mirror=Nové zrcadlo new_fork=Nové rozštěpení repozitáře new_org=Nová organizace new_project=Nový projekt +new_project_column=Nový sloupec manage_org=Spravovat organizace admin_panel=Administrace account_settings=Nastavení účtu @@ -90,9 +91,11 @@ disabled=Zakázané copy=Kopírovat copy_url=Kopírovat URL +copy_content=Kopírovat obsah copy_branch=Kopírovat jméno větve copy_success=Zkopírováno! copy_error=Kopírování se nezdařilo +copy_type_unsupported=Tento typ souboru nelze zkopírovat write=Zapsat preview=Náhled @@ -109,6 +112,10 @@ never=Nikdy rss_feed=RSS kanál [aria] +navbar=Navigační lišta +footer=Patička +footer.software=O softwaru +footer.links=Odkazy [filter] string.asc=A – Z @@ -292,23 +299,71 @@ code_search_results=Výsledky hledání pro „%s“ code_last_indexed_at=Naposledy indexováno %s relevant_repositories_tooltip=Repozitáře, které jsou rozštěpení nebo nemají žádné téma, ikonu a žádný popis jsou skryty. relevant_repositories=`Zobrazují se pouze relevantní repositáře, zobrazit nefiltrované výsledky. [auth] @@ -338,6 +393,7 @@ email_not_associate=Tato e-mailová adresa není spojena s žádným účtem. send_reset_mail=Zaslat e-mail pro obnovení účtu reset_password=Obnovení účtu invalid_code=Tento potvrzující kód je neplatný nebo mu vypršela platnost. +invalid_password=Vaše heslo se neshoduje s heslem, které bylo použito k vytvoření účtu. reset_password_helper=Obnovit účet reset_password_wrong_user=Jste přihlášen/a jako %s, ale odkaz pro obnovení účtu je pro %s password_too_short=Délka hesla musí být minimálně %d znaků. @@ -381,6 +437,7 @@ password_pwned_err=Nelze dokončit požadavek na HaveIBeenPwned [mail] view_it_on=Zobrazit na %s +reply=nebo přímo odpovědět na tento e-mail link_not_working_do_paste=Nefunguje? Zkuste jej zkopírovat a vložit do svého prohlížeče. hi_user_x=Ahoj %s, @@ -484,6 +541,7 @@ url_error=`„%s“ není platná adresa URL.` include_error=` musí obsahovat řetězec „%s“.` glob_pattern_error=`zástupný vzor je neplatný: %s.` regex_pattern_error=` regex vzor je neplatný: %s.` +invalid_group_team_map_error=` mapování je neplatné: %s` unknown_error=Neznámá chyba: captcha_incorrect=CAPTCHA kód není správný. password_not_match=Zadaná hesla nesouhlasí. @@ -520,10 +578,12 @@ team_not_exist=Tento tým neexistuje. last_org_owner=Nemůžete odstranit posledního uživatele z týmu „vlastníci“. Musí existovat alespoň jeden vlastník pro organizaci. cannot_add_org_to_team=Organizace nemůže být přidána jako člen týmu. duplicate_invite_to_team=Uživatel byl již pozván jako člen týmu. +organization_leave_success=Úspěšně jste opustili organizaci %s. invalid_ssh_key=Nelze ověřit váš SSH klíč: %s invalid_gpg_key=Nelze ověřit váš GPG klíč: %s invalid_ssh_principal=Neplatný SSH Principal certifikát: %s +must_use_public_key=Zadaný klíč je soukromý klíč. Nenahrávejte svůj soukromý klíč nikde. Místo toho použijte váš veřejný klíč. unable_verify_ssh_key=Nelze ověřit váš SSH klíč auth_failed=Ověření selhalo: %v @@ -1185,6 +1245,7 @@ commits.signed_by_untrusted_user_unmatched=Podepsáno nedůvěryhodným uživate commits.gpg_key_id=ID GPG klíče commits.ssh_key_fingerprint=Otisk klíče SSH +commit.operations=Operace commit.revert=Vrátit commit.revert-header=Vrátit: %s commit.revert-content=Vyberte větev pro návrat na: @@ -1217,11 +1278,21 @@ projects.type.bug_triage=Třídění chyb projects.template.desc=Šablona projektu projects.template.desc_helper=Vyberte šablonu projektu pro začátek projects.type.uncategorized=Nezařazené +projects.column.edit=Upravit sloupec projects.column.edit_title=Název projects.column.new_title=Název +projects.column.new_submit=Vytvořit sloupec +projects.column.new=Nový sloupec +projects.column.set_default=Nastavit jako výchozí +projects.column.set_default_desc=Nastavit tento sloupec jako výchozí pro nekategorizované úkoly a požadavky na natažení +projects.column.delete=Smazat sloupec +projects.column.deletion_desc=Smazání projektového sloupce přesune všechny související problémy do kategorie „Nezařazené“. Pokračovat? projects.column.color=Barva projects.open=Otevřít projects.close=Zavřít +projects.column.assigned_to=Přiřazeno k +projects.card_type.images_and_text=Obrázky a text +projects.card_type.text_only=Pouze text issues.desc=Organizování hlášení chyb, úkolů a milníků. issues.filter_assignees=Filtrovat zpracovatele @@ -1298,6 +1369,7 @@ issues.filter_label_no_select=Všechny štítky issues.filter_milestone=Milník issues.filter_milestone_no_select=Všechny milníky issues.filter_project=Projekt +issues.filter_project_all=Všechny projekty issues.filter_project_none=Žádný projekt issues.filter_assignee=Zpracovatel issues.filter_assginee_no_select=Všichni zpracovatelé @@ -1383,6 +1455,7 @@ issues.save=Uložit issues.label_title=Název štítku issues.label_description=Popis štítku issues.label_color=Barva štítku +issues.label_exclusive=Exkluzivní issues.label_count=%d štítků issues.label_open_issues=%d otevřených úkolů issues.label_edit=Upravit @@ -1828,6 +1901,7 @@ settings.mirror_sync_in_progress=Právě probíhá synchronizace zrcadla. Zkuste settings.site=Webová stránka settings.update_settings=Aktualizovat nastavení settings.branches.update_default_branch=Aktualizovat výchozí větev +settings.branches.add_new_rule=Přidat nové pravidlo settings.advanced_settings=Pokročilá nastavení settings.wiki_desc=Povolit Wiki repozitáře settings.use_internal_wiki=Používat vestavěnou Wiki @@ -2022,6 +2096,8 @@ settings.event_package=Balíček settings.event_package_desc=Balíček vytvořen nebo odstraněn v repozitáři. settings.branch_filter=Filtr větví settings.branch_filter_desc=Povolené větve pro události nahrání, vytvoření větve a smazání větve jsou určeny pomocí zástupného vzoru. Pokud je prázdný nebo *, všechny události jsou ohlášeny. Podívejte se na dokumentaci syntaxe na github.com/gobwas/glob. Příklady: master, {master,release*}. +settings.authorization_header=Autorizační hlavička +settings.authorization_header_desc=Pokud vyplněno, bude připojeno k požadavkům jako autorizační hlavička. Příklady: %s. settings.active=Aktivní settings.active_helper=Informace o spuštěných událostech budou odeslány na URL webového háčku. settings.add_hook_success=Webový háček byl přidán. @@ -2066,6 +2142,8 @@ settings.deploy_key_deletion_desc=Odstranění klíče pro nasazení zruší jeh settings.deploy_key_deletion_success=Klíč pro nasazení byl odstraněn. settings.branches=Větve settings.protected_branch=Ochrana větví +settings.protected_branch.save_rule=Uložit pravidlo +settings.protected_branch.delete_rule=Odstranit pravidlo settings.protected_branch_can_push=Povolit nahrání? settings.protected_branch_can_push_yes=Můžete nahrávat settings.protected_branch_can_push_no=Nemůžete nahrávat @@ -2543,6 +2621,9 @@ dashboard.delete_old_actions=Odstranit všechny staré akce z databáze dashboard.delete_old_actions.started=Začalo odstraňování všech starých akcí z databáze. dashboard.update_checker=Kontrola aktualizací dashboard.delete_old_system_notices=Odstranit všechna stará systémová upozornění z databáze +dashboard.stop_zombie_tasks=Zastavit zombie úkoly +dashboard.stop_endless_tasks=Zastavit nekonečné úkoly +dashboard.cancel_abandoned_jobs=Zrušit opuštěné úlohy users.user_manage_panel=Správa uživatelských účtů users.new_account=Vytvořit uživatelský účet @@ -2724,6 +2805,7 @@ auths.oauth2_required_claim_value_helper=Nastavte tuto hodnotu pro omezení při auths.oauth2_group_claim_name=Název tvrzení poskytující názvy skupin pro tento zdroj. (nepovinné) auths.oauth2_admin_group=Hodnota tvrzení pro skupinu uživatelů administrátorů. (Volitelné - vyžaduje název tvrzení výše) auths.oauth2_restricted_group=Hodnota tvrzení pro skupinu omezených uživatelů. (Volitelné - vyžaduje název tvrzení výše) +auths.oauth2_map_group_to_team_removal=Odebrat uživatele z synchronizovaných týmů, pokud uživatel nepatří do odpovídající skupiny. auths.enable_auto_register=Povolit zaregistrování se auths.sspi_auto_create_users=Automaticky vytvářet uživatele auths.sspi_auto_create_users_helper=Povolit SSPI autentizační metodě automaticky vytvářet nové účty pro uživatele, kteří se poprvé přihlásili @@ -2986,6 +3068,7 @@ monitor.queue.pool.cancel_desc=Opustit frontu bez skupin workerů může způsob notices.system_notice_list=Systémová oznámení notices.view_detail_header=Zobrazit detaily oznámení +notices.operations=Operace notices.select_all=Vybrat vše notices.deselect_all=Zrušit výběr všech notices.inverse_selection=Inverzní výběr @@ -3011,6 +3094,7 @@ reopen_pull_request=`znovuotevřel/a požadavek na natažení %[ comment_issue=`okomentoval/a problém %[3]s#%[2]s` comment_pull=`okomentoval/a požadavek na natažení %[3]s#%[2]s` merge_pull_request=`sloučil/a požadavek na natažení %[3]s#%[2]s` +auto_merge_pull_request=`automaticky sloučen požadavek na natažení %[3]s#%[2]s` transfer_repo=předal/a repozitář %s uživateli/organizaci %s push_tag=nahrál/a značku %[3]s do %[4]s delete_tag=smazal/a značku %[2]s z %[3]s @@ -3109,6 +3193,8 @@ keywords=Klíčová slova details=Podrobnosti details.author=Autor details.project_site=Stránka projektu +details.repository_site=Stránka repositáře +details.documentation_site=Stránka dokumentace details.license=Licence assets=Prostředky versions=Verze @@ -3116,7 +3202,13 @@ versions.on= versions.view_all=Zobrazit všechny dependency.id=ID dependency.version=Verze +cargo.install=Chcete-li nainstalovat balíček pomocí Cargo, spusťte následující příkaz: +cargo.documentation=Další informace o registru Cargo naleznete v dokumentaci. +cargo.details.repository_site=Stránka repositáře +cargo.details.documentation_site=Stránka dokumentace +chef.registry=Nastavit tento registr v souboru ~/.chef/config.rb: chef.install=Pro instalaci balíčku spusťte následující příkaz: +chef.documentation=Další informace o registru Chef naleznete v dokumentaci. composer.registry=Nastavit tento registr v souboru ~/.composer/config.json: composer.install=Pro instalaci balíčku pomocí Compposer spusťte následující příkaz: composer.documentation=Další informace o registru Composer naleznete v dokumentaci. @@ -3126,6 +3218,11 @@ conan.details.repository=Repozitář conan.registry=Nastavte tento registr z příkazového řádku: conan.install=Pro instalaci balíčku pomocí Conan spusťte následující příkaz: conan.documentation=Další informace o registru Conan naleznete v dokumentaci. +conda.registry=Nastavte tento registr jako Conda repozitář ve vašem .condarc: +conda.install=Pro instalaci balíčku pomocí Conda spusťte následující příkaz: +conda.documentation=Další informace o registru Conda naleznete v dokumentaci. +conda.details.repository_site=Stránka repositáře +conda.details.documentation_site=Stránka dokumentace container.details.type=Typ obrazu container.details.platform=Platforma container.pull=Stáhněte obraz z příkazové řádky: @@ -3184,26 +3281,79 @@ settings.delete.description=Smazání balíčku je trvalé a nelze ho vrátit zp settings.delete.notice=Chystáte se odstranit %s (%s). Tato operace je nevratná, jste si jisti? settings.delete.success=Balíček byl odstraněn. settings.delete.error=Nepodařilo se odstranit balíček. +owner.settings.cargo.initialize=Inicializovat index +owner.settings.cargo.initialize.description=K použití registru Cargo je zapotřebí speciální indexový repozitář git. Zde jej můžete (znovu) vytvořit s požadovanou konfigurací. +owner.settings.cargo.initialize.error=Nepodařilo se inicializovat Cargo index: %v +owner.settings.cargo.initialize.success=Index Cargo byl úspěšně vytvořen. +owner.settings.cargo.rebuild=Znovu vytvořit Index +owner.settings.cargo.rebuild.description=Pokud index není synchronizován s uloženými balíčky Cargo, můžete jej zde obnovit. +owner.settings.cargo.rebuild.error=Obnovení Cargo indexu se nezdařilo: %v +owner.settings.cargo.rebuild.success=Cargo Index byl úspěšně obnoven. +owner.settings.cleanuprules.title=Spravovat pravidla pro čištění +owner.settings.cleanuprules.add=Přidat pravidlo pro čištění +owner.settings.cleanuprules.edit=Upravit pravidlo pro čištění +owner.settings.cleanuprules.none=Nejsou k dispozici žádná pravidla čištění. Přečtěte si dokumentaci a dozvíte se více. +owner.settings.cleanuprules.preview=Náhled pravidla pro čištění +owner.settings.cleanuprules.preview.overview=%d balíčků má být odstraněno. +owner.settings.cleanuprules.preview.none=Pravidlo čištění neodpovídá žádným balíčkům. owner.settings.cleanuprules.enabled=Povolený +owner.settings.cleanuprules.pattern_full_match=Použít vzor na úplný název balíčku +owner.settings.cleanuprules.keep.title=Verze, které odpovídají těmto pravidlům, jsou zachovány, i když odpovídají níže uvedenému pravidlu pro odstranění. +owner.settings.cleanuprules.keep.count=Zachovat nejnovější +owner.settings.cleanuprules.keep.count.1=1 verze na balíček +owner.settings.cleanuprules.keep.count.n=%d verzí na balíček +owner.settings.cleanuprules.keep.pattern=Ponechat odpovídající verze +owner.settings.cleanuprules.keep.pattern.container=U balíčků Container je vždy zachována nejnovější verze. +owner.settings.cleanuprules.remove.title=Verze, které odpovídají těmto pravidlům, jsou odstraněny, pokud výše uvedené pravidlo neukládá jejich zachování. +owner.settings.cleanuprules.remove.days=Odstranit verze starší než +owner.settings.cleanuprules.remove.pattern=Odstranit odpovídající verze +owner.settings.cleanuprules.success.update=Pravidlo pro čištění bylo aktualizováno. +owner.settings.cleanuprules.success.delete=Pravidlo pro čištění bylo odstraněno. +owner.settings.chef.keypair=Generovat pár klíčů [secrets] +secrets=Tajné klíče value=Hodnota name=Název [actions] +actions=Akce +unit.desc=Spravovat akce +status.unknown=Neznámý +status.waiting=Čekání +status.running=Probíhá +status.success=Úspěch +status.failure=Chyba +status.cancelled=Zrušeno +status.skipped=Přeskočeno +status.blocked=Blokováno +runners.status=Status runners.id=ID runners.name=Název runners.owner_type=Typ runners.description=Popis runners.labels=Štítky +runners.last_online=Poslední čas online +runners.agent_labels=Štítky agenta +runners.custom_labels=Vlastní štítky +runners.custom_labels_helper=Vlastní štítky jsou štítky, které správce přidává ručně. Štítky se oddělují čárkou, bílé znaky na začátku a na konci každého štítku se ignorují. runners.task_list.run=Spustit +runners.task_list.status=Status runners.task_list.repository=Repozitář runners.task_list.commit=Commit +runners.task_list.done_at=Dokončeno v +runners.update_runner=Aktualizovat změny +runners.status.unspecified=Neznámý +runners.status.idle=Nečinný runners.status.active=Aktivní +runners.status.offline=Offline +runs.all_workflows=Všechny pracovní postupy +runs.open_tab=%d otevřeno +runs.closed_tab=%d uzavřeno runs.commit=Commit diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 4f2e0ebb13c3..51610f4e82d7 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -328,6 +328,7 @@ email_not_associate=此電子信箱未與任何帳戶連結 send_reset_mail=發送帳戶救援信 reset_password=帳戶救援 invalid_code=您的確認代碼無效或已過期。 +invalid_password=您的密碼和用來建立帳戶的不符。 reset_password_helper=帳戶救援 reset_password_wrong_user=您已經使用 %s 的帳戶登入,但帳戶救援連結是給 %s 的 password_too_short=密碼長度不能少於 %d 個字! @@ -430,6 +431,10 @@ repo.transfer.body=請造訪 %s 以接受或拒絕轉移,您也可以忽略它 repo.collaborator.added.subject=%s 把您加入到 %s repo.collaborator.added.text=您已被新增為儲存庫的協作者: +team_invite.subject=%[1]s 邀請您加入組織 %[2]s +team_invite.text_1=%[1]s 邀請您加入組織 %[3]s 中的 %[2]s 團隊 +team_invite.text_2=請點擊下方連結加入團隊: +team_invite.text_3=備註: 這是寄給 %[1]s 的邀請。若您未預期收到此邀請,請忽略此郵件。 [modal] yes=是 @@ -446,7 +451,7 @@ SSHTitle=SSH 金鑰名稱 HttpsUrl=HTTPS URL 地址 PayloadUrl=推送地址 TeamName=團隊名稱 -AuthName=認證名稱 +AuthName=授權名稱 AdminEmail=管理員電子信箱 NewBranchName=新的分支名稱 @@ -506,11 +511,13 @@ user_not_exist=該用戶名不存在 team_not_exist=團隊不存在 last_org_owner=你不能從「Owners」團隊中刪除最後一個使用者。每個組織中至少要有一個擁有者。 cannot_add_org_to_team=組織不能被新增為團隊成員。 +duplicate_invite_to_team=該使用者已經被邀請為團隊成員。 organization_leave_success=您已成功離開組織 %s。 invalid_ssh_key=無法驗證您的 SSH 密鑰:%s invalid_gpg_key=無法驗證您的 GPG 密鑰:%s invalid_ssh_principal=無效的主體: %s +must_use_public_key=您提供的金鑰是私有金鑰,請勿上傳您的私有金鑰到任何地方,請使用您的公鑰。 unable_verify_ssh_key=無法驗證 SSH 密鑰 auth_failed=授權認證失敗:%v @@ -747,6 +754,8 @@ access_token_deletion_cancel_action=取消 access_token_deletion_confirm_action=刪除 access_token_deletion_desc=刪除 Token 後,使用此 Token 的應用程式將無法再存取您的帳戶,此動作不可還原。是否繼續? delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再存取您的帳戶。 +select_scopes=選擇範圍 (Scope) +scopes_list=範圍 (Scope): manage_oauth2_applications=管理 OAuth2 應用程式 edit_oauth2_application=編輯 OAuth2 應用程式 @@ -759,6 +768,7 @@ create_oauth2_application_button=建立應用程式 create_oauth2_application_success=您已成功新增一個 OAuth2 應用程式。 update_oauth2_application_success=您已成功更新了 OAuth2 應用程式。 oauth2_application_name=應用程式名稱 +oauth2_confidential_client=機密客戶端 (Confidential Client)。請為能保持機密性的程式勾選,例如網頁應用程式。使用原生程式時不要勾選,包含桌面、行動應用程式。 oauth2_redirect_uri=重新導向 URI save_application=儲存 oauth2_client_id=客戶端 ID @@ -1203,7 +1213,7 @@ projects.edit_success=已更新專案「%s」。 projects.type.none=無 projects.type.basic_kanban=基本看板 projects.type.bug_triage=Bug 檢傷分類 -projects.template.desc=專案範本 +projects.template.desc=範本 projects.template.desc_helper=選擇專案範本以開始 projects.type.uncategorized=未分類 projects.column.edit=編輯欄位 @@ -1215,7 +1225,7 @@ projects.column.set_default=設為預設 projects.column.set_default_desc=將此欄位設定為未分類問題及合併請求的預設預設值 projects.column.delete=刪除欄位 projects.column.deletion_desc=刪除專案欄位會將所有相關的問題移動到「未分類」,是否繼續? -projects.column.color=彩色 +projects.column.color=顏色 projects.open=開啟 projects.close=關閉 projects.column.assigned_to=已指派給 @@ -1310,6 +1320,7 @@ issues.filter_type.assigned_to_you=指派給您的 issues.filter_type.created_by_you=由您建立的 issues.filter_type.mentioning_you=提及您的 issues.filter_type.review_requested=已提出審核請求 +issues.filter_type.reviewed_by_you=由您審核 issues.filter_sort=排序 issues.filter_sort.latest=最新建立 issues.filter_sort.oldest=最早建立 @@ -1383,11 +1394,14 @@ issues.sign_in_require_desc= 登入 才能加入這對話。 issues.edit=編輯 issues.cancel=取消 issues.save=儲存 -issues.label_title=標籤名稱 -issues.label_description=標籤描述 -issues.label_color=標籤顏色 +issues.label_title=名稱 +issues.label_description=描述 +issues.label_color=顏色 +issues.label_exclusive=互斥 +issues.label_exclusive_desc=請以此格式命名標籤: scope/item,使它和其他 scope/ (相同範圍) 標籤互斥。 +issues.label_exclusive_warning=在編輯問題及合併請求的標籤時,將會刪除任何有相同範圍的標籤。 issues.label_count=%d 個標籤 -issues.label_open_issues=%d 個開放中的問題 +issues.label_open_issues=%d 個開放中的問題/合併請求 issues.label_edit=編輯 issues.label_delete=刪除 issues.label_modify=編輯標籤 @@ -1640,6 +1654,7 @@ pulls.reopened_at=`重新開放了這個合併請求 命令列指南。` pulls.merge_instruction_step1_desc=在您的儲存庫中切換到新分支並測試變更。 pulls.merge_instruction_step2_desc=合併變更並更新到 Gitea。 +pulls.clear_merge_message=清除合併訊息 pulls.auto_merge_button_when_succeed=(當通過檢查後) pulls.auto_merge_when_succeed=通過所有檢查後自動合併 @@ -2423,7 +2438,7 @@ teams.leave.detail=確定要離開 %s 嗎? teams.can_create_org_repo=建立儲存庫 teams.can_create_org_repo_helper=成員可以在組織中新增儲存庫。建立者將自動取得新儲存庫的管理員權限。 teams.none_access=沒有權限 -teams.none_access_helper=成員無法檢視此單元或對其執行其他動作。 +teams.none_access_helper=成員無法檢視此單元或對其執行其他動作,這對公開儲存庫沒有影響。 teams.general_access=一般權限 teams.general_access_helper=成員權限將由下列權限表決定。 teams.read_access=讀取 @@ -2561,6 +2576,10 @@ dashboard.delete_old_actions=從資料庫刪除所有舊行為 dashboard.delete_old_actions.started=從資料庫刪除所有舊行為的任務已啟動。 dashboard.update_checker=更新檢查器 dashboard.delete_old_system_notices=從資料庫刪除所有舊系統提示 +dashboard.gc_lfs=對 LFS meta objects 進行垃圾回收 +dashboard.stop_zombie_tasks=停止殭屍任務 +dashboard.stop_endless_tasks=停止永不停止的任務 +dashboard.cancel_abandoned_jobs=取消已放棄的工作 users.user_manage_panel=使用者帳戶管理 users.new_account=建立使用者帳戶 @@ -2649,6 +2668,7 @@ repos.size=大小 packages.package_manage_panel=套件管理 packages.total_size=總大小: %s +packages.unreferenced_size=未參考大小: %s packages.owner=擁有者 packages.creator=建立者 packages.name=名稱 @@ -2742,6 +2762,7 @@ auths.oauth2_required_claim_value_helper=填寫此名稱以限制 Claim 中有 auths.oauth2_group_claim_name=Claim 名稱提供群組名稱給此來源。(選用) auths.oauth2_admin_group=管理員使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) auths.oauth2_restricted_group=受限制使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) +auths.oauth2_map_group_to_team_removal=如果使用者不屬於相對應的群組,將使用者從已同步的團隊移除。 auths.enable_auto_register=允許授權用戶自動註冊 auths.sspi_auto_create_users=自動建立使用者 auths.sspi_auto_create_users_helper=允許 SSPI 認證方法於使用者首次登入時自動建立新帳戶 @@ -3218,8 +3239,14 @@ settings.delete.description=刪除套件是永久且不可還原的。 settings.delete.notice=您正要刪除 %s (%s),此動作是無法還原的,您確定嗎? settings.delete.success=已刪除該套件。 settings.delete.error=刪除套件失敗。 +owner.settings.cargo.title=Cargo Registry 索引 owner.settings.cargo.initialize=初始化索引 +owner.settings.cargo.initialize.error=初始化 Cargo 索引失敗: %v +owner.settings.cargo.initialize.success=成功建立了 Cargo 索引。 owner.settings.cargo.rebuild=重建索引 +owner.settings.cargo.rebuild.description=如果索引和 Cargo 套件不同步,您可在此重建它。 +owner.settings.cargo.rebuild.error=重建 Cargo 索引失敗: %v +owner.settings.cargo.rebuild.success=成功重建了 Cargo 索引。 owner.settings.cleanuprules.title=管理清理規則 owner.settings.cleanuprules.add=加入清理規則 owner.settings.cleanuprules.edit=編輯清理規則 @@ -3239,32 +3266,80 @@ owner.settings.cleanuprules.remove.days=移除早於天數的版本 owner.settings.cleanuprules.remove.pattern=移除版本的比對規則 owner.settings.cleanuprules.success.update=已更新清理規則。 owner.settings.cleanuprules.success.delete=已刪除清理規則。 +owner.settings.chef.title=Chef Registry +owner.settings.chef.keypair=產生密鑰組 +owner.settings.chef.keypair.description=產生用來認證 Chef Registry 的密鑰組,之前的密鑰未來將無法使用。 [secrets] +secrets=Secret +description=Secret 會被傳給特定的 Action,其他情況無法讀取。 +none=還沒有 Secret。 value=值 name=組織名稱 +creation=加入 Secret +creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。 +creation.success=已加入 Secret「%s」。 +creation.failed=加入 Secret 失敗。 +deletion=移除 Secret +deletion.description=移除 Secret 是永久的且不可還原,是否繼續? +deletion.success=已移除此 Secret。 +deletion.failed=移除 Secret 失敗。 [actions] - - - +actions=Actions + +unit.desc=管理 Actions + +status.unknown=未知 +status.waiting=正在等候 +status.running=正在執行 +status.success=成功 +status.failure=失敗 +status.cancelled=已取消 +status.skipped=已略過 +status.blocked=已阻塞 + +runners=Runner +runners.runner_manage_panel=Runner 管理 +runners.new=建立 Runner +runners.new_notice=如何啟動 Runner runners.status=狀態 runners.id=ID runners.name=組織名稱 runners.owner_type=認證類型 runners.description=組織描述 runners.labels=標籤 +runners.last_online=最後上線時間 runners.agent_labels=代理程式標籤 runners.custom_labels=自訂標籤 +runners.custom_labels_helper=自訂標籤是由管理員手動加上的標籤。標籤之間以半形逗號「,」分隔,標籤頭尾的空白將被忽略。 +runners.runner_title=Runner +runners.task_list=最近在此 Runner 上的任務 runners.task_list.run=執行 runners.task_list.status=狀態 runners.task_list.repository=儲存庫 runners.task_list.commit=提交 runners.task_list.done_at=完成於 +runners.edit_runner=編輯 Runner +runners.update_runner=更新變更 +runners.update_runner_success=更新 Runner 成功 +runners.update_runner_failed=更新 Runner 失敗 +runners.delete_runner=刪除此 Runner +runners.delete_runner_success=刪除 Runner 成功 +runners.delete_runner_failed=刪除 Runner 失敗 +runners.delete_runner_header=確認刪除此 Runner +runners.delete_runner_notice=如果有任務正在此 Runner 上執行,它可能會被中止並標記為失敗,這可能會打斷建置工作流程。 +runners.none=沒有可用的 Runner +runners.status.unspecified=未知 +runners.status.idle=閒置 runners.status.active=啟用 +runners.status.offline=離線 +runs.all_workflows=所有工作流程 runs.open_tab=%d 開放中 runs.closed_tab=%d 已關閉 runs.commit=提交 +runs.pushed_by=推送者 +need_approval_desc=來自 Frok 儲存庫的合併請求需要核可才能執行工作流程。 diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 046100c72e2f..64ae4aa70952 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -103,7 +103,7 @@ func Projects(ctx *context.Context) { pager.AddParam(ctx, "state", "State") ctx.Data["Page"] = pager - ctx.Data["CanWriteProjects"] = canWriteUnit(ctx) + ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["IsShowClosed"] = isShowClosed ctx.Data["PageIsViewProjects"] = true ctx.Data["SortType"] = sortType @@ -111,7 +111,7 @@ func Projects(ctx *context.Context) { ctx.HTML(http.StatusOK, tplProjects) } -func canWriteUnit(ctx *context.Context) bool { +func canWriteProjects(ctx *context.Context) bool { if ctx.ContextUser.IsOrganization() { return ctx.Org.CanWriteUnit(ctx, unit.TypeProjects) } @@ -122,7 +122,7 @@ func canWriteUnit(ctx *context.Context) bool { func NewProject(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.projects.new") ctx.Data["BoardTypes"] = project_model.GetBoardConfig() - ctx.Data["CanWriteProjects"] = canWriteUnit(ctx) + ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink() shared_user.RenderUserHeader(ctx) ctx.HTML(http.StatusOK, tplProjectsNew) @@ -135,7 +135,7 @@ func NewProjectPost(ctx *context.Context) { shared_user.RenderUserHeader(ctx) if ctx.HasError() { - ctx.Data["CanWriteProjects"] = canWriteUnit(ctx) + ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["PageIsViewProjects"] = true ctx.Data["BoardTypes"] = project_model.GetBoardConfig() ctx.HTML(http.StatusOK, tplProjectsNew) @@ -193,7 +193,7 @@ func DeleteProject(ctx *context.Context) { } return } - if p.RepoID != ctx.Repo.Repository.ID { + if p.OwnerID != ctx.ContextUser.ID { ctx.NotFound("", nil) return } @@ -214,7 +214,7 @@ func EditProject(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.projects.edit") ctx.Data["PageIsEditProjects"] = true ctx.Data["PageIsViewProjects"] = true - ctx.Data["CanWriteProjects"] = canWriteUnit(ctx) + ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) shared_user.RenderUserHeader(ctx) p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) @@ -226,13 +226,14 @@ func EditProject(ctx *context.Context) { } return } - if p.RepoID != ctx.Repo.Repository.ID { + if p.OwnerID != ctx.ContextUser.ID { ctx.NotFound("", nil) return } ctx.Data["title"] = p.Title ctx.Data["content"] = p.Description + ctx.Data["redirect"] = ctx.FormString("redirect") ctx.HTML(http.StatusOK, tplProjectsNew) } @@ -243,7 +244,7 @@ func EditProjectPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.projects.edit") ctx.Data["PageIsEditProjects"] = true ctx.Data["PageIsViewProjects"] = true - ctx.Data["CanWriteProjects"] = canWriteUnit(ctx) + ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) shared_user.RenderUserHeader(ctx) if ctx.HasError() { @@ -260,7 +261,7 @@ func EditProjectPost(ctx *context.Context) { } return } - if p.RepoID != ctx.Repo.Repository.ID { + if p.OwnerID != ctx.ContextUser.ID { ctx.NotFound("", nil) return } @@ -273,7 +274,11 @@ func EditProjectPost(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.projects.edit_success", p.Title)) - ctx.Redirect(ctx.Repo.RepoLink + "/projects") + if ctx.FormString("redirect") == "project" { + ctx.Redirect(p.Link()) + } else { + ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects") + } } // ViewProject renders the project board for a project @@ -332,7 +337,7 @@ func ViewProject(ctx *context.Context) { project.RenderedContent = project.Description ctx.Data["LinkedPRs"] = linkedPrsMap ctx.Data["PageIsViewProjects"] = true - ctx.Data["CanWriteProjects"] = canWriteUnit(ctx) + ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["Project"] = project ctx.Data["IssuesMap"] = issuesMap ctx.Data["Boards"] = boards diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 967b81c60851..29bd59c7a3b3 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -235,6 +235,7 @@ func EditProject(ctx *context.Context) { ctx.Data["title"] = p.Title ctx.Data["content"] = p.Description ctx.Data["card_type"] = p.CardType + ctx.Data["redirect"] = ctx.FormString("redirect") ctx.HTML(http.StatusOK, tplProjectsNew) } @@ -275,7 +276,11 @@ func EditProjectPost(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.projects.edit_success", p.Title)) - ctx.Redirect(ctx.Repo.RepoLink + "/projects") + if ctx.FormString("redirect") == "project" { + ctx.Redirect(p.Link()) + } else { + ctx.Redirect(ctx.Repo.RepoLink + "/projects") + } } // ViewProject renders the project board for a project diff --git a/services/pull/comment.go b/services/pull/comment.go index 068aca6cd128..933ad09a85e9 100644 --- a/services/pull/comment.go +++ b/services/pull/comment.go @@ -14,58 +14,6 @@ import ( issue_service "code.gitea.io/gitea/services/issue" ) -type commitBranchCheckItem struct { - Commit *git.Commit - Checked bool -} - -func commitBranchCheck(gitRepo *git.Repository, startCommit *git.Commit, endCommitID, baseBranch string, commitList map[string]*commitBranchCheckItem) error { - if startCommit.ID.String() == endCommitID { - return nil - } - - checkStack := make([]string, 0, 10) - checkStack = append(checkStack, startCommit.ID.String()) - - for len(checkStack) > 0 { - commitID := checkStack[0] - checkStack = checkStack[1:] - - item, ok := commitList[commitID] - if !ok { - continue - } - - if item.Commit.ID.String() == endCommitID { - continue - } - - if err := item.Commit.LoadBranchName(); err != nil { - return err - } - - if item.Commit.Branch == baseBranch { - continue - } - - if item.Checked { - continue - } - - item.Checked = true - - parentNum := item.Commit.ParentCount() - for i := 0; i < parentNum; i++ { - parentCommit, err := item.Commit.Parent(i) - if err != nil { - return err - } - checkStack = append(checkStack, parentCommit.ID.String()) - } - } - return nil -} - // getCommitIDsFromRepo get commit IDs from repo in between oldCommitID and newCommitID // isForcePush will be true if oldCommit isn't on the branch // Commit on baseBranch will skip @@ -82,47 +30,33 @@ func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldC return nil, false, err } - if err = oldCommit.LoadBranchName(); err != nil { - return nil, false, err - } - - if len(oldCommit.Branch) == 0 { - commitIDs = make([]string, 2) - commitIDs[0] = oldCommitID - commitIDs[1] = newCommitID - - return commitIDs, true, err - } - newCommit, err := gitRepo.GetCommit(newCommitID) if err != nil { return nil, false, err } - commits, err := newCommit.CommitsBeforeUntil(oldCommitID) + isForcePush, err = newCommit.IsForcePush(oldCommitID) if err != nil { return nil, false, err } - commitIDs = make([]string, 0, len(commits)) - commitChecks := make(map[string]*commitBranchCheckItem) + if isForcePush { + commitIDs = make([]string, 2) + commitIDs[0] = oldCommitID + commitIDs[1] = newCommitID - for _, commit := range commits { - commitChecks[commit.ID.String()] = &commitBranchCheckItem{ - Commit: commit, - Checked: false, - } + return commitIDs, isForcePush, err } - if err = commitBranchCheck(gitRepo, newCommit, oldCommitID, baseBranch, commitChecks); err != nil { - return + // Find commits between new and old commit exclusing base branch commits + commits, err := gitRepo.CommitsBetweenNotBase(newCommit, oldCommit, baseBranch) + if err != nil { + return nil, false, err } + commitIDs = make([]string, 0, len(commits)) for i := len(commits) - 1; i >= 0; i-- { - commitID := commits[i].ID.String() - if item, ok := commitChecks[commitID]; ok && item.Checked { - commitIDs = append(commitIDs, commitID) - } + commitIDs = append(commitIDs, commits[i].ID.String()) } return commitIDs, isForcePush, err diff --git a/services/pull/merge.go b/services/pull/merge.go index afa924fc109a..12e01e8ce74a 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -332,8 +332,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use } func commitAndSignNoAuthor(ctx *mergeContext, message string) error { - if err := git.NewCommand(ctx, "commit").AddArguments(ctx.signArg...).AddOptionFormat("--message=%s", message). - Run(ctx.RunOpts()); err != nil { + cmdCommit := git.NewCommand(ctx, "commit").AddOptionFormat("--message=%s", message) + if ctx.signKeyID == "" { + cmdCommit.AddArguments("--no-gpg-sign") + } else { + cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID) + } + if err := cmdCommit.Run(ctx.RunOpts()); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git commit %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 2ba821961a2b..88f6c037ebc9 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -28,7 +28,7 @@ type mergeContext struct { doer *user_model.User sig *git.Signature committer *git.Signature - signArg git.TrustedCmdArgs + signKeyID string // empty for no-sign, non-empty to sign env []string } @@ -85,12 +85,10 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque // Determine if we should sign sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, mergeCtx.tmpBasePath, "HEAD", trackingBranch) if sign { - mergeCtx.signArg = git.ToTrustedCmdArgs([]string{"-S" + keyID}) + mergeCtx.signKeyID = keyID if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { mergeCtx.committer = signer } - } else { - mergeCtx.signArg = git.ToTrustedCmdArgs([]string{"--no-gpg-sign"}) } commitTimeStr := time.Now().Format(time.RFC3339) @@ -136,18 +134,11 @@ func prepareTemporaryRepoForMerge(ctx *mergeContext) error { return fmt.Errorf("Unable to close .git/info/sparse-checkout file in tmpBasePath: %w", err) } - gitConfigCommand := func() *git.Command { - return git.NewCommand(ctx, "config", "--local") - } - setConfig := func(key, value string) error { - if err := gitConfigCommand().AddArguments(git.ToTrustedCmdArgs([]string{key, value})...). + if err := git.NewCommand(ctx, "config", "--local").AddDynamicArguments(key, value). Run(ctx.RunOpts()); err != nil { - if value == "" { - value = "<>" - } - log.Error("git config [%s -> %s ]: %v\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) - return fmt.Errorf("git config [%s -> %s ]: %w\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) + log.Error("git config [%s -> %q]: %v\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) + return fmt.Errorf("git config [%s -> %q]: %w\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) } ctx.outbuf.Reset() ctx.errbuf.Reset() diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 0a8cc0167e40..f52a2301d906 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -7,45 +7,77 @@ import ( "fmt" repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) +// doMergeStyleSquash gets a commit author signature for squash commits +func getAuthorSignatureSquash(ctx *mergeContext) (*git.Signature, error) { + if err := ctx.pr.Issue.LoadPoster(ctx); err != nil { + log.Error("%-v Issue[%d].LoadPoster: %v", ctx.pr, ctx.pr.Issue.ID, err) + return nil, err + } + + // Try to get an signature from the same user in one of the commits, as the + // poster email might be private or commits might have a different signature + // than the primary email address of the poster. + gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, ctx.tmpBasePath) + if err != nil { + log.Error("%-v Unable to open base repository: %v", ctx.pr, err) + return nil, err + } + defer closer.Close() + + commits, err := gitRepo.CommitsBetweenIDs(trackingBranch, "HEAD") + if err != nil { + log.Error("%-v Unable to get commits between: %s %s: %v", ctx.pr, "HEAD", trackingBranch, err) + return nil, err + } + + uniqueEmails := make(container.Set[string]) + for _, commit := range commits { + if commit.Author != nil && uniqueEmails.Add(commit.Author.Email) { + commitUser, _ := user_model.GetUserByEmail(ctx, commit.Author.Email) + if commitUser != nil && commitUser.ID == ctx.pr.Issue.Poster.ID { + return commit.Author, nil + } + } + } + + return ctx.pr.Issue.Poster.NewGitSig(), nil +} + // doMergeStyleSquash squashes the tracking branch on the current HEAD (=base) func doMergeStyleSquash(ctx *mergeContext, message string) error { - cmd := git.NewCommand(ctx, "merge", "--squash").AddDynamicArguments(trackingBranch) - if err := runMergeCommand(ctx, repo_model.MergeStyleSquash, cmd); err != nil { + sig, err := getAuthorSignatureSquash(ctx) + if err != nil { + return fmt.Errorf("getAuthorSignatureSquash: %w", err) + } + + cmdMerge := git.NewCommand(ctx, "merge", "--squash").AddDynamicArguments(trackingBranch) + if err := runMergeCommand(ctx, repo_model.MergeStyleSquash, cmdMerge); err != nil { log.Error("%-v Unable to merge --squash tracking into base: %v", ctx.pr, err) return err } - if err := ctx.pr.Issue.LoadPoster(ctx); err != nil { - log.Error("%-v Issue[%d].LoadPoster: %v", ctx.pr, ctx.pr.Issue.ID, err) - return fmt.Errorf("LoadPoster: %w", err) - } - sig := ctx.pr.Issue.Poster.NewGitSig() - if len(ctx.signArg) == 0 { - if err := git.NewCommand(ctx, "commit"). - AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). - AddOptionFormat("--message=%s", message). - Run(ctx.RunOpts()); err != nil { - log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", ctx.pr.HeadRepo.FullName(), ctx.pr.HeadBranch, ctx.pr.BaseRepo.FullName(), ctx.pr.BaseBranch, err, ctx.outbuf.String(), ctx.errbuf.String()) - } + if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() { + // add trailer + message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) + } + cmdCommit := git.NewCommand(ctx, "commit"). + AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). + AddOptionFormat("--message=%s", message) + if ctx.signKeyID == "" { + cmdCommit.AddArguments("--no-gpg-sign") } else { - if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() { - // add trailer - message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) - } - if err := git.NewCommand(ctx, "commit"). - AddArguments(ctx.signArg...). - AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). - AddOptionFormat("--message=%s", message). - Run(ctx.RunOpts()); err != nil { - log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", ctx.pr.HeadRepo.FullName(), ctx.pr.HeadBranch, ctx.pr.BaseRepo.FullName(), ctx.pr.BaseBranch, err, ctx.outbuf.String(), ctx.errbuf.String()) - } + cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID) + } + if err := cmdCommit.Run(ctx.RunOpts()); err != nil { + log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) + return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", ctx.pr.HeadRepo.FullName(), ctx.pr.HeadBranch, ctx.pr.BaseRepo.FullName(), ctx.pr.BaseBranch, err, ctx.outbuf.String(), ctx.errbuf.String()) } ctx.outbuf.Reset() ctx.errbuf.Reset() diff --git a/services/pull/pull.go b/services/pull/pull.go index d8923d0d5725..e40e59a2c500 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -669,7 +669,12 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ authorString := commit.Author.String() if uniqueAuthors.Add(authorString) && authorString != posterSig { - authors = append(authors, authorString) + // Compare use account as well to avoid adding the same author multiple times + // times when email addresses are private or multiple emails are used. + commitUser, _ := user_model.GetUserByEmail(ctx, commit.Author.Email) + if commitUser == nil || commitUser.ID != pr.Issue.Poster.ID { + authors = append(authors, authorString) + } } } @@ -690,7 +695,10 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ for _, commit := range commits { authorString := commit.Author.String() if uniqueAuthors.Add(authorString) && authorString != posterSig { - authors = append(authors, authorString) + commitUser, _ := user_model.GetUserByEmail(ctx, commit.Author.Email) + if commitUser == nil || commitUser.ID != pr.Issue.Poster.ID { + authors = append(authors, authorString) + } } } skip += limit diff --git a/services/repository/push.go b/services/repository/push.go index cdf030396fcf..4b574e344067 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -206,12 +206,12 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { return fmt.Errorf("newCommit.CommitsBeforeUntil: %w", err) } - isForce, err := repo_module.IsForcePush(ctx, opts) + isForcePush, err := newCommit.IsForcePush(opts.OldCommitID) if err != nil { - log.Error("isForcePush %s:%s failed: %v", repo.FullName(), branch, err) + log.Error("IsForcePush %s:%s failed: %v", repo.FullName(), branch, err) } - if isForce { + if isForcePush { log.Trace("Push %s is a force push", opts.NewCommitID) cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true)) diff --git a/templates/projects/new.tmpl b/templates/projects/new.tmpl index c96f948dedfd..826869ac5daa 100644 --- a/templates/projects/new.tmpl +++ b/templates/projects/new.tmpl @@ -21,6 +21,7 @@
{{.CsrfTokenHtml}}
+
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 14a876d8fd94..474b22a2f14c 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -46,7 +46,7 @@ {{if or $.CanWriteIssues $.CanWritePulls}}