From eadb45e8912c3cd1d3af7cdf17a3641201213a34 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sat, 11 Jan 2020 10:50:05 +0000 Subject: [PATCH 01/14] Restore IsPasswordSet previous value (#9682) --- models/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user.go b/models/user.go index f2c0a1861e5a..9ddd262aed87 100644 --- a/models/user.go +++ b/models/user.go @@ -503,7 +503,7 @@ func (u *User) ValidatePassword(passwd string) bool { // IsPasswordSet checks if the password is set or left empty func (u *User) IsPasswordSet() bool { - return len(u.Passwd) > 0 + return !u.ValidatePassword("") } // UploadAvatar saves custom avatar for user. From f2e6c4538e83f267a1b1f2abaaf38cd99c6dd0a5 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sat, 11 Jan 2020 10:51:31 +0000 Subject: [PATCH 02/14] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 1 - options/locale/locale_de-DE.ini | 1 - options/locale/locale_es-ES.ini | 1 - options/locale/locale_fa-IR.ini | 1 - options/locale/locale_fr-FR.ini | 1 - options/locale/locale_ja-JP.ini | 51 ++++++++++++++++++++++++++++++++- options/locale/locale_lv-LV.ini | 1 - options/locale/locale_pl-PL.ini | 1 - options/locale/locale_pt-BR.ini | 1 - options/locale/locale_tr-TR.ini | 1 - options/locale/locale_uk-UA.ini | 1 - options/locale/locale_zh-CN.ini | 1 - 12 files changed, 50 insertions(+), 12 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index b55c98557021..340b20e7d7e1 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1036,7 +1036,6 @@ pulls.cannot_auto_merge_helper=Pro vyřešení konfliktů proveďte ruční slou pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány. pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně. pulls.no_merge_wip=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený. -pulls.no_merge_status_check=Tento požadavek na natažení nemůže být sloučen, protože ne všechny požadované kontroly stavu byly úspěšné. pulls.merge_pull_request=Sloučit požadavek na natažení pulls.rebase_merge_pull_request=Rebase a sloučit pulls.rebase_merge_commit_pull_request=Rebase a sloučit (--no-ff) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 5661323132ce..cc4f8de22a2f 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=Bitte manuell zusammenführen, um die Konflikte z pulls.no_merge_desc=Dieser Pull-Request kann nicht gemerged werden, da keine Mergeoptionen aktiviert sind. pulls.no_merge_helper=Aktiviere Mergeoptionen in den Repositoryeinstellungen oder merge den Pull-Request manuell. pulls.no_merge_wip=Dieser Pull Request kann nicht zusammengeführt werden, da er als Work In Progress gekennzeichnet ist. -pulls.no_merge_status_check=Dieser Pull-Request kann nicht zusammengeführt werden, da nicht alle erforderlichen Statusprüfungen erfolgreich waren. pulls.merge_pull_request=Pull-Request zusammenführen pulls.rebase_merge_pull_request=Rebase und Mergen pulls.rebase_merge_commit_pull_request=Rebasen und Mergen (--no-ff) diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index f504c80f0411..7471854d4442 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -1057,7 +1057,6 @@ pulls.cannot_auto_merge_helper=Combinar manualmente para resolver los conflictos pulls.no_merge_desc=Este pull request no se puede combinar porque todas las opciones de combinación del repositorio están deshabilitadas. pulls.no_merge_helper=Habilite las opciones de combinación en la configuración del repositorio o fusione el pull request manualmente. pulls.no_merge_wip=Este pull request no se puede combinar porque está marcada como un trabajo en progreso. -pulls.no_merge_status_check=No se puede fusionar este pull request porque no todas las comprobaciones de estado requeridas resultaron exitosas. pulls.merge_pull_request=Fusionar Pull Request pulls.rebase_merge_pull_request=Hacer Rebase y Fusionar pulls.rebase_merge_commit_pull_request=Hacer Rebase y Fusionar (--no-ff) diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index e9fc99c500d9..912c9bed9162 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1062,7 +1062,6 @@ pulls.cannot_auto_merge_helper=به صورتی دستی ادغام کنید تا pulls.no_merge_desc=این تقاضای واکشی قابل ادغام نیست لذا تمامی گزینه های ادغام مخزن غیر فعال هستند. pulls.no_merge_helper=گزینه های ادغام را در تنظیمات مخزن فعال کنید یا از تقاضای واکشی به صورت دستی ادغام نمایید. pulls.no_merge_wip=این تقاضای واکشی قابل ادغام نیست لذا اکنون به این مخزن درحال پردازش علامت گذاری شده است. -pulls.no_merge_status_check=این تقاضای واکشی نمی‌تواند ادغام شود لذا تمامی حالت های ضروری با موفقیت بررسی نشدند. pulls.merge_pull_request=ادغام تقاضای واکشی pulls.rebase_merge_pull_request=بازگردانی و ادغام pulls.rebase_merge_commit_pull_request=بازگردانی و ادغام (--no-ff) diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index b9a1b579ad78..499e39aafa5a 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1054,7 +1054,6 @@ pulls.cannot_auto_merge_helper=Fusionner manuellement pour résoudre les conflit pulls.no_merge_desc=Cette demande de fusion ne peut être appliquée directement car toutes les options de fusion du dépôt sont désactivées. pulls.no_merge_helper=Activez des options de fusion dans les paramètres du dépôt ou fusionnez la demande manuellement. pulls.no_merge_wip=Cette demande d'ajout ne peut pas être fusionnée car elle est marquée comme en cours de chantier. -pulls.no_merge_status_check=Cette demande de pull ne peut pas être fusionnée car tous les contrôles de statut requis ne sont pas réussis. pulls.merge_pull_request=Fusionner la demande d'ajout pulls.rebase_merge_pull_request=Rebase et fusionner pulls.rebase_merge_commit_pull_request=Rebase et Fusion (--no-ff) diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index ababfbb58d4b..9da5a245f99f 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1061,7 +1061,8 @@ pulls.cannot_auto_merge_helper=コンフリクトを解消するため手動で pulls.no_merge_desc=リポジトリのマージオプションがすべて無効になっているため、このプルリクエストをマージすることはできせん。 pulls.no_merge_helper=リポジトリ設定でマージを有効にするか、手動でマージしてください。 pulls.no_merge_wip=このプルリクエストはWork in Progressとマークされているため、マージすることはできません。 -pulls.no_merge_status_check=すべての必要なステータスチェックが成功していないため、このプルリクエストはマージできません。 +pulls.no_merge_not_ready=このプルリクエストはマージする準備ができていません。 レビュー状況とステータスチェックを確認してください。 +pulls.no_merge_access=このプルリクエストをマージする権限がありません。 pulls.merge_pull_request=プルリクエストをマージ pulls.rebase_merge_pull_request=リベースしてマージ pulls.rebase_merge_commit_pull_request=リベースしてマージ(--no-ff) @@ -1412,6 +1413,8 @@ settings.protect_approvals_whitelist_enabled=ホワイトリストに登録し settings.protect_approvals_whitelist_enabled_desc=ホワイトリストに登録したユーザーやチームによるレビューのみを、必要な承認とみなします。 承認のホワイトリストが無い場合は、書き込み権限がある人によるレビューを必要な承認とみなします。 settings.protect_approvals_whitelist_users=ホワイトリストに含めるレビューア: settings.protect_approvals_whitelist_teams=ホワイトリストに含めるレビューチーム: +settings.dismiss_stale_approvals=古くなった承認を取り消す +settings.dismiss_stale_approvals_desc=プルリクエストの内容を変える新たなコミットがブランチにプッシュされた場合、以前の承認を取り消します。 settings.add_protected_branch=保護を有効にする settings.delete_protected_branch=保護を無効にする settings.update_protect_branch_success=ブランチ '%s' の保護を更新しました。 @@ -2025,8 +2028,54 @@ monitor.execute_time=実行時間 monitor.process.cancel=処理をキャンセル monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります monitor.process.cancel_notices=キャンセル: %s? +monitor.queues=キュー +monitor.queue=キュー: %s +monitor.queue.name=キュー名 +monitor.queue.type=種類 +monitor.queue.exemplar=要素の型 +monitor.queue.numberworkers=ワーカー数 +monitor.queue.maxnumberworkers=ワーカー数上限 +monitor.queue.review=設定確認 +monitor.queue.review_add=確認/ワーカー追加 +monitor.queue.configuration=初期設定 +monitor.queue.nopool.title=ワーカープールはありません +monitor.queue.nopool.desc=このキューは他のキューをラップし、これ自体にはワーカープールがありません。 +monitor.queue.wrapped.desc=wrappedキューは、すぐに開始されないキューをラップし、入ってきたリクエストをチャンネルにバッファリングします。 これ自体にはワーカープールがありません。 +monitor.queue.persistable-channel.desc=persistable-channelキューは二つのキューをラップします。 一つはchannelキューで、自分のワーカープールを持ちます。もう一つはlevelキューで、前回のシャットダウンからリクエストを引き継ぐためのものです。 これ自体にはワーカープールがありません。 +monitor.queue.pool.timeout=タイムアウト +monitor.queue.pool.addworkers.title=ワーカーの追加 +monitor.queue.pool.addworkers.submit=ワーカーを追加 +monitor.queue.pool.addworkers.desc=このプールに、タイムアウト付きまたはタイムアウト無しでワーカーを追加します。 タイムアウトを指定した場合は、タイムアウト後にそれらのワーカーがこのプールから取り除かれます。 +monitor.queue.pool.addworkers.numberworkers.placeholder=ワーカー数 +monitor.queue.pool.addworkers.timeout.placeholder=0でタイムアウト無し +monitor.queue.pool.addworkers.mustnumbergreaterzero=追加するワーカー数は1以上にしてください +monitor.queue.pool.addworkers.musttimeoutduration=タイムアウトは 、Go言語の時間差表記(例 5m)、または0にしてください +monitor.queue.settings.title=プール設定 +monitor.queue.settings.desc=ワーカーへのキューのブロックが発生すると、それに応じてプール数がブースト分ずつ動的に増えます。 これらの変更は現在のワーカーグループには影響しません。 +monitor.queue.settings.timeout=ブースト分のタイムアウト +monitor.queue.settings.timeout.placeholder=現在の設定 %[1]v +monitor.queue.settings.timeout.error=タイムアウトは 、Go言語の時間差表記(例 5m)、または0にしてください +monitor.queue.settings.numberworkers=ブースト分のワーカー数 +monitor.queue.settings.numberworkers.placeholder=現在の設定 %[1]d +monitor.queue.settings.numberworkers.error=追加するワーカー数はゼロ以上にしてください +monitor.queue.settings.maxnumberworkers=ワーカー数上限 +monitor.queue.settings.maxnumberworkers.placeholder=現在の設定 %[1]d +monitor.queue.settings.maxnumberworkers.error=ワーカー数上限は数値にしてください +monitor.queue.settings.submit=設定を更新 +monitor.queue.settings.changed=設定を更新しました +monitor.queue.settings.blocktimeout=現在のブロックタイムアウト +monitor.queue.settings.blocktimeout.value=%[1]v +monitor.queue.pool.none=このキューにはプールがありません +monitor.queue.pool.added=ワーカーグループを追加しました +monitor.queue.pool.max_changed=ワーカー数の上限を変更しました +monitor.queue.pool.workers.title=アクティブなワーカーグループ +monitor.queue.pool.workers.none=ワーカーグループはありません。 +monitor.queue.pool.cancel=ワーカーグループのシャットダウン +monitor.queue.pool.cancelling=ワーカーグループをシャットダウンしています +monitor.queue.pool.cancel_notices=このワーカー数 %s のグループをシャットダウンしますか? +monitor.queue.pool.cancel_desc=キューをワーカーグループ無しのままにすると、リクエストがブロックし続ける原因となります。 notices.system_notice_list=システム通知 notices.view_detail_header=通知の詳細を表示 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 6a59492de49b..c207d43b8247 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=Sapludiniet manuāli, lai atrisinātu konfliktus. pulls.no_merge_desc=Šo izmaiņu pieprasījumu nav iespējams sapludināt, jo nav atļauts neviens sapludināšanas veids. pulls.no_merge_helper=Lai sapludinātu šo izmaiņu pieprasījumu, iespējojiet vismaz vienu sapludināšanas veidu repozitorija iestatījumos vai sapludiniet to manuāli. pulls.no_merge_wip=Šo izmaiņu pieprasījumu nav iespējams sapludināt, jo tas ir atzīmēts, ka darbs pie tā vēl nav pabeigts. -pulls.no_merge_status_check=Šo izmaiņu pieprasījumu nevar saplusināt, jo nav veiksmīgi izildītas visas obligātas statusa pārbaudes. pulls.merge_pull_request=Izmaiņu pieprasījuma sapludināšana pulls.rebase_merge_pull_request=Pārbāzēt un sapludināt pulls.rebase_merge_commit_pull_request=Pārbāzēt un sapludināt (--no-ff) diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 754013212a15..67a355b2ecc1 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -1055,7 +1055,6 @@ pulls.cannot_auto_merge_helper=Scal ręcznie, aby rozwiązać konflikty. pulls.no_merge_desc=Ten Pull Request nie może zostać scalony, ponieważ wszystkie opcje scalania dla tego repozytorium są wyłączone. pulls.no_merge_helper=Włącz opcje scalania w ustawieniach repozytorium, lub scal ten Pull Request ręcznie. pulls.no_merge_wip=Ten pull request nie może być automatycznie scalony, ponieważ jest oznaczony jako praca w toku. -pulls.no_merge_status_check=Ten Pull Request nie może być scalony, bo nie wszystkie kontrole stanów były pomyślne. pulls.merge_pull_request=Scal Pull Request pulls.rebase_merge_pull_request=Zmień bazę i scal pulls.rebase_merge_commit_pull_request=Zmień bazę i scal (--no-ff) diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 78ed5a959805..9728fd58cbae 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=Faça o merge manualmente para resolver os confli pulls.no_merge_desc=O merge deste pull request não pode ser aplicado porque todas as opções de mesclagem do repositório estão desabilitadas. pulls.no_merge_helper=Habilite as opções de merge nas configurações do repositório ou faça o merge do pull request manualmente. pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque está marcado como um trabalho em andamento. -pulls.no_merge_status_check=Este pull request não pode ter seu merge aplicado porque nem todas as verificações de status necessárias foram bem sucedidas. pulls.merge_pull_request=Aplicar merge do pull request pulls.rebase_merge_pull_request=Aplicar Rebase e Merge pulls.rebase_merge_commit_pull_request=Aplicar Rebase e Merge (--no-ff) diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 5940524c71dc..019d143092f6 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1054,7 +1054,6 @@ pulls.cannot_auto_merge_helper=Çakışmaları çözmek için el ile birleştiri pulls.no_merge_desc=Tüm depo birleştirme seçenekleri devre dışı bırakıldığından, bu değişiklik isteği birleştirilemez. pulls.no_merge_helper=Depo ayarlarındaki birleştirme seçeneklerini etkinleştirin veya değişiklik isteğini el ile birleştirin. pulls.no_merge_wip=Bu değişiklik isteği birleştirilemez çünkü devam eden bir çalışma olarak işaretlendi. -pulls.no_merge_status_check=Gerekli olan tüm durum denetimleri başarılı olmadığından bu değişiklik isteği birleştirilemez. pulls.merge_pull_request=Değişiklik İsteğini Birleştir pulls.rebase_merge_pull_request=Rebase ve Merge pulls.rebase_merge_commit_pull_request=Rebase ve Merge (--no-ff) diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index e140af1e4997..ed8b6f4fa3cd 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1058,7 +1058,6 @@ pulls.cannot_auto_merge_helper=Злийте вручну для вирішенн pulls.no_merge_desc=Цей запити на злиття неможливо злити, оскільки всі параметри об'єднання репозиторія вимкнено. pulls.no_merge_helper=Увімкніть параметри злиття в налаштуваннях репозиторія або злийте запити на злиття вручну. pulls.no_merge_wip=Цей пулл-реквест не можливо об'єднати, тому-що він вже виконується. -pulls.no_merge_status_check=Не вдалося об'єднати цей запит на злиття, оскільки не всі обов'язкові перевірки були успішними. pulls.merge_pull_request=Об'єднати запит на злиття pulls.rebase_merge_pull_request=Зробити Rebase і злити pulls.rebase_merge_commit_pull_request=Rebase та злитя (--no-ff) diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 00c80787541c..ec00848dc5f2 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=手动合并解决此冲突 pulls.no_merge_desc=由于未启用合并选项,此合并请求无法被合并。 pulls.no_merge_helper=在仓库设置中启用合并选项或者手工合并请求。 pulls.no_merge_wip=这个合并请求无法合并,因为被标记为尚未完成的工作。 -pulls.no_merge_status_check=此合并请求不能合并,因为不是所有的状态检查都是成功的。 pulls.merge_pull_request=合并请求 pulls.rebase_merge_pull_request=变基并合并 pulls.rebase_merge_commit_pull_request=变基合并 (--no-ff) From f69f5a9f105f655c931b29a57f9ffb257b7edf7c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 11 Jan 2020 22:24:57 +0800 Subject: [PATCH 03/14] Add a new command doctor to check if some wrong configurations on gitea instance (#9095) * add doctor * Add a new command doctor to check if some wrong configurations on gitea instance * fix import * use regex match authorized_keys on doctor * Add documentation --- cmd/doctor.go | 130 +++++++++++++++++++ docs/content/doc/usage/command-line.en-us.md | 25 ++++ main.go | 1 + 3 files changed, 156 insertions(+) create mode 100644 cmd/doctor.go diff --git a/cmd/doctor.go b/cmd/doctor.go new file mode 100644 index 000000000000..d81ead97c72c --- /dev/null +++ b/cmd/doctor.go @@ -0,0 +1,130 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "bufio" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +// CmdDoctor represents the available doctor sub-command. +var CmdDoctor = cli.Command{ + Name: "doctor", + Usage: "Diagnose the problems", + Description: "A command to diagnose the problems of current gitea instance according the given configuration.", + Action: runDoctor, +} + +type check struct { + title string + f func(ctx *cli.Context) ([]string, error) +} + +// checklist represents list for all checks +var checklist = []check{ + { + title: "Check if OpenSSH authorized_keys file id correct", + f: runDoctorLocationMoved, + }, + // more checks please append here +} + +func runDoctor(ctx *cli.Context) error { + err := initDB() + fmt.Println("Using app.ini at", setting.CustomConf) + if err != nil { + fmt.Println(err) + fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") + return nil + } + + for i, check := range checklist { + fmt.Println("[", i+1, "]", check.title) + if messages, err := check.f(ctx); err != nil { + fmt.Println("Error:", err) + } else if len(messages) > 0 { + for _, message := range messages { + fmt.Println("-", message) + } + } else { + fmt.Println("OK.") + } + fmt.Println() + } + return nil +} + +func exePath() (string, error) { + file, err := exec.LookPath(os.Args[0]) + if err != nil { + return "", err + } + return filepath.Abs(file) +} + +func runDoctorLocationMoved(ctx *cli.Context) ([]string, error) { + if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile { + return nil, nil + } + + fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys") + f, err := os.Open(fPath) + if err != nil { + return nil, err + } + defer f.Close() + + var firstline string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + firstline = strings.TrimSpace(scanner.Text()) + if len(firstline) == 0 || firstline[0] == '#' { + continue + } + break + } + + // command="/Volumes/data/Projects/gitea/gitea/gitea --config + if len(firstline) > 0 { + exp := regexp.MustCompile(`^[ \t]*(?:command=")([^ ]+) --config='([^']+)' serv key-([^"]+)",(?:[^ ]+) ssh-rsa ([^ ]+) ([^ ]+)[ \t]*$`) + + // command="/home/user/gitea --config='/home/user/etc/app.ini' serv key-999",option-1,option-2,option-n ssh-rsa public-key-value key-name + res := exp.FindStringSubmatch(firstline) + if res == nil { + return nil, errors.New("Unknow authorized_keys format") + } + + giteaPath := res[1] // => /home/user/gitea + iniPath := res[2] // => /home/user/etc/app.ini + + p, err := exePath() + if err != nil { + return nil, err + } + p, err = filepath.Abs(p) + if err != nil { + return nil, err + } + + if len(giteaPath) > 0 && giteaPath != p { + return []string{fmt.Sprintf("Gitea exe path wants %s but %s on %s", p, giteaPath, fPath)}, nil + } + if len(iniPath) > 0 && iniPath != setting.CustomConf { + return []string{fmt.Sprintf("Gitea config path wants %s but %s on %s", setting.CustomConf, iniPath, fPath)}, nil + } + } + + return nil, nil +} diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 0f7b4f61a20d..60c2e26a7b7c 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -289,3 +289,28 @@ This command is idempotent. #### convert Converts an existing MySQL database from utf8 to utf8mb4. + +#### doctor +Diagnose the problems of current gitea instance according the given configuration. +Currently there are a check list below: + +- Check if OpenSSH authorized_keys file id correct +When your gitea instance support OpenSSH, your gitea instance binary path will be written to `authorized_keys` +when there is any public key added or changed on your gitea instance. +Sometimes if you moved or renamed your gitea binary when upgrade and you haven't run `Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)` on your Admin Panel. Then all pull/push via SSH will not be work. +This check will help you to check if it works well. + +For contributors, if you want to add more checks, you can wrie ad new function like `func(ctx *cli.Context) ([]string, error)` and +append it to `doctor.go`. + +```go +var checklist = []check{ + { + title: "Check if OpenSSH authorized_keys file id correct", + f: runDoctorLocationMoved, + }, + // more checks please append here +} +``` + +This function will receive a command line context and return a list of details about the problems or error. \ No newline at end of file diff --git a/main.go b/main.go index 30dbf2766224..c67eaf7692e1 100644 --- a/main.go +++ b/main.go @@ -68,6 +68,7 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdMigrate, cmd.CmdKeys, cmd.CmdConvert, + cmd.CmdDoctor, } // Now adjust these commands to add our global configuration options From edd31770aa10207d91405a98c9b97ce14db7b302 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sat, 11 Jan 2020 17:05:07 +0000 Subject: [PATCH 04/14] Missed q.lock.Unlock() will cause panic (#9705) --- modules/queue/queue_wrapped.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/queue/queue_wrapped.go b/modules/queue/queue_wrapped.go index d0b93b54d0c3..0e948bae280f 100644 --- a/modules/queue/queue_wrapped.go +++ b/modules/queue/queue_wrapped.go @@ -62,7 +62,6 @@ func (q *delayedStarter) setInternal(atShutdown func(context.Context, func()), h queue, err := NewQueue(q.underlying, handle, q.cfg, exemplar) if err == nil { q.internal = queue - q.lock.Unlock() break } if err.Error() != "resource temporarily unavailable" { From 960ac3609962b33c6937ef9de473122dff98a523 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sat, 11 Jan 2020 19:06:35 +0000 Subject: [PATCH 05/14] Remove unused lock (#9709) --- modules/queue/queue_disk_channel.go | 2 ++ modules/queue/queue_wrapped.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/queue/queue_disk_channel.go b/modules/queue/queue_disk_channel.go index 895c8ce918f3..6bb5a1be9798 100644 --- a/modules/queue/queue_disk_channel.go +++ b/modules/queue/queue_disk_channel.go @@ -6,6 +6,7 @@ package queue import ( "context" + "sync" "time" "code.gitea.io/gitea/modules/log" @@ -33,6 +34,7 @@ type PersistableChannelQueueConfiguration struct { type PersistableChannelQueue struct { *ChannelQueue delayedStarter + lock sync.Mutex closed chan struct{} } diff --git a/modules/queue/queue_wrapped.go b/modules/queue/queue_wrapped.go index 0e948bae280f..c52e6e467360 100644 --- a/modules/queue/queue_wrapped.go +++ b/modules/queue/queue_wrapped.go @@ -28,7 +28,6 @@ type WrappedQueueConfiguration struct { } type delayedStarter struct { - lock sync.Mutex internal Queue underlying Type cfg interface{} @@ -89,6 +88,7 @@ func (q *delayedStarter) setInternal(atShutdown func(context.Context, func()), h // WrappedQueue wraps a delayed starting queue type WrappedQueue struct { delayedStarter + lock sync.Mutex handle HandlerFunc exemplar interface{} channel chan Data From fef49f5e2ecfc189f3d1d263c4a438d71161fef0 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Sat, 11 Jan 2020 15:33:40 -0500 Subject: [PATCH 06/14] golangci-lint 1.22.2 (#9711) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8da85c7a814d..61cab8b06d98 100644 --- a/Makefile +++ b/Makefile @@ -558,6 +558,6 @@ pr: golangci-lint: @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ export BINARY="golangci-lint"; \ - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.20.0; \ + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.22.2; \ fi golangci-lint run --timeout 5m From 86464de0c150e046b4590c02851c9c1111cd2176 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 12 Jan 2020 03:57:32 +0100 Subject: [PATCH 07/14] silence fomantic error regarding tabs (#9713) Fomantic expects all tabs to have a target element with content as defined by the data-tab attribute. All our usage of the tab module seems to use element tabs that link to new pages so these content elements are never present and fomantic complains about that in the console with an "Activated tab cannot be found" error. This silences that error. --- web_src/js/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web_src/js/index.js b/web_src/js/index.js index b8145fa43991..b701da08b143 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -22,6 +22,9 @@ if (typeof (Dropzone) !== 'undefined') { Dropzone.autoDiscover = false; } +// Silence fomantic's error logging when tabs are used without a target content element +$.fn.tab.settings.silent = true; + function initCommentPreviewTab($form) { const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); From 83f9359a7545601754e775a79273a326e833b0bc Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 12 Jan 2020 07:35:11 +0100 Subject: [PATCH 08/14] =?UTF-8?q?[BugFix]=20[API]=20=E2=80=8B/repos?= =?UTF-8?q?=E2=80=8B/issues=E2=80=8B/search=20(#9698)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix * fix options * add TEST Co-authored-by: Antoine GIRARD --- integrations/api_issue_test.go | 45 ++++++++++++++++++++++++++++++++++ routers/api/v1/repo/issue.go | 28 ++++++++++++--------- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index 382fe606bf35..ce1c4b7d33fc 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -7,6 +7,7 @@ package integrations import ( "fmt" "net/http" + "net/url" "testing" "code.gitea.io/gitea/models" @@ -120,3 +121,47 @@ func TestAPIEditIssue(t *testing.T) { assert.Equal(t, body, issueAfter.Content) assert.Equal(t, title, issueAfter.Title) } + +func TestAPISearchIssue(t *testing.T) { + defer prepareTestEnv(t)() + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + + link, _ := url.Parse("/api/v1/repos/issues/search") + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiIssues []*api.Issue + DecodeJSON(t, resp, &apiIssues) + + assert.Len(t, apiIssues, 8) + + query := url.Values{} + query.Add("token", token) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 8) + + query.Add("state", "closed") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query.Set("state", "all") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 10) //there are more but 10 is page item limit + + query.Add("page", "2") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 0) +} diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index ad82d53e7a19..69b8a369957f 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -67,20 +67,24 @@ func SearchIssues(ctx *context.APIContext) { // find repos user can access (for issue search) repoIDs := make([]int64, 0) + opts := &models.SearchRepoOptions{ + PageSize: 15, + Private: false, + AllPublic: true, + TopicOnly: false, + Collaborate: util.OptionalBoolNone, + UserIsAdmin: ctx.IsUserSiteAdmin(), + OrderBy: models.SearchOrderByRecentUpdated, + } + if ctx.IsSigned { + opts.Private = true + opts.AllLimited = true + opts.UserID = ctx.User.ID + } issueCount := 0 for page := 1; ; page++ { - repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ - Page: page, - PageSize: 15, - Private: true, - Keyword: "", - OwnerID: ctx.User.ID, - TopicOnly: false, - Collaborate: util.OptionalBoolNone, - UserIsAdmin: ctx.IsUserSiteAdmin(), - UserID: ctx.User.ID, - OrderBy: models.SearchOrderByRecentUpdated, - }) + opts.Page = page + repos, count, err := models.SearchRepositoryByName(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err) return From 65baacf2273f74876b3faed93f60641389a20d39 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sun, 12 Jan 2020 08:46:03 +0000 Subject: [PATCH 09/14] Make hook status printing configurable with delay (#9641) * Delay printing hook statuses until after 1 second * Move to a 5s delay, wrapped writer structure and add config * Update cmd/hook.go * Apply suggestions from code review * Update cmd/hook.go Co-authored-by: Antoine GIRARD --- cmd/hook.go | 148 +++++++++++++++--- .../doc/advanced/config-cheat-sheet.en-us.md | 2 + modules/setting/git.go | 4 + 3 files changed, 128 insertions(+), 26 deletions(-) diff --git a/cmd/hook.go b/cmd/hook.go index aabd9637c2a0..331f6a2d2ddf 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -8,10 +8,12 @@ import ( "bufio" "bytes" "fmt" + "io" "net/http" "os" "strconv" "strings" + "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" @@ -58,6 +60,85 @@ var ( } ) +type delayWriter struct { + internal io.Writer + buf *bytes.Buffer + timer *time.Timer +} + +func newDelayWriter(internal io.Writer, delay time.Duration) *delayWriter { + timer := time.NewTimer(delay) + return &delayWriter{ + internal: internal, + buf: &bytes.Buffer{}, + timer: timer, + } +} + +func (d *delayWriter) Write(p []byte) (n int, err error) { + if d.buf != nil { + select { + case <-d.timer.C: + _, err := d.internal.Write(d.buf.Bytes()) + if err != nil { + return 0, err + } + d.buf = nil + return d.internal.Write(p) + default: + return d.buf.Write(p) + } + } + return d.internal.Write(p) +} + +func (d *delayWriter) WriteString(s string) (n int, err error) { + if d.buf != nil { + select { + case <-d.timer.C: + _, err := d.internal.Write(d.buf.Bytes()) + if err != nil { + return 0, err + } + d.buf = nil + return d.internal.Write([]byte(s)) + default: + return d.buf.WriteString(s) + } + } + return d.internal.Write([]byte(s)) +} + +func (d *delayWriter) Close() error { + if d == nil { + return nil + } + stopped := d.timer.Stop() + if stopped { + return nil + } + select { + case <-d.timer.C: + default: + } + if d.buf == nil { + return nil + } + _, err := d.internal.Write(d.buf.Bytes()) + d.buf = nil + return err +} + +type nilWriter struct{} + +func (n *nilWriter) Write(p []byte) (int, error) { + return len(p), nil +} + +func (n *nilWriter) WriteString(s string) (int, error) { + return len(s), nil +} + func runHookPreReceive(c *cli.Context) error { if os.Getenv(models.EnvIsInternal) == "true" { return nil @@ -101,6 +182,18 @@ Gitea or set your environment appropriately.`, "") total := 0 lastline := 0 + var out io.Writer + out = &nilWriter{} + if setting.Git.VerbosePush { + if setting.Git.VerbosePushDelay > 0 { + dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay) + defer dWriter.Close() + out = dWriter + } else { + out = os.Stdout + } + } + for scanner.Scan() { // TODO: support news feeds for wiki if isWiki { @@ -124,12 +217,10 @@ Gitea or set your environment appropriately.`, "") newCommitIDs[count] = newCommitID refFullNames[count] = refFullName count++ - fmt.Fprintf(os.Stdout, "*") - os.Stdout.Sync() + fmt.Fprintf(out, "*") if count >= hookBatchSize { - fmt.Fprintf(os.Stdout, " Checking %d branches\n", count) - os.Stdout.Sync() + fmt.Fprintf(out, " Checking %d branches\n", count) hookOptions.OldCommitIDs = oldCommitIDs hookOptions.NewCommitIDs = newCommitIDs @@ -147,12 +238,10 @@ Gitea or set your environment appropriately.`, "") lastline = 0 } } else { - fmt.Fprintf(os.Stdout, ".") - os.Stdout.Sync() + fmt.Fprintf(out, ".") } if lastline >= hookBatchSize { - fmt.Fprintf(os.Stdout, "\n") - os.Stdout.Sync() + fmt.Fprintf(out, "\n") lastline = 0 } } @@ -162,8 +251,7 @@ Gitea or set your environment appropriately.`, "") hookOptions.NewCommitIDs = newCommitIDs[:count] hookOptions.RefFullNames = refFullNames[:count] - fmt.Fprintf(os.Stdout, " Checking %d branches\n", count) - os.Stdout.Sync() + fmt.Fprintf(out, " Checking %d branches\n", count) statusCode, msg := private.HookPreReceive(username, reponame, hookOptions) switch statusCode { @@ -173,14 +261,11 @@ Gitea or set your environment appropriately.`, "") fail(msg, "") } } else if lastline > 0 { - fmt.Fprintf(os.Stdout, "\n") - os.Stdout.Sync() + fmt.Fprintf(out, "\n") lastline = 0 } - fmt.Fprintf(os.Stdout, "Checked %d references in total\n", total) - os.Stdout.Sync() - + fmt.Fprintf(out, "Checked %d references in total\n", total) return nil } @@ -206,6 +291,19 @@ Gitea or set your environment appropriately.`, "") } } + var out io.Writer + var dWriter *delayWriter + out = &nilWriter{} + if setting.Git.VerbosePush { + if setting.Git.VerbosePushDelay > 0 { + dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay) + defer dWriter.Close() + out = dWriter + } else { + out = os.Stdout + } + } + // the environment setted on serv command repoUser := os.Getenv(models.EnvRepoUsername) isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true") @@ -241,7 +339,7 @@ Gitea or set your environment appropriately.`, "") continue } - fmt.Fprintf(os.Stdout, ".") + fmt.Fprintf(out, ".") oldCommitIDs[count] = string(fields[0]) newCommitIDs[count] = string(fields[1]) refFullNames[count] = string(fields[2]) @@ -250,16 +348,15 @@ Gitea or set your environment appropriately.`, "") } count++ total++ - os.Stdout.Sync() if count >= hookBatchSize { - fmt.Fprintf(os.Stdout, " Processing %d references\n", count) - os.Stdout.Sync() + fmt.Fprintf(out, " Processing %d references\n", count) hookOptions.OldCommitIDs = oldCommitIDs hookOptions.NewCommitIDs = newCommitIDs hookOptions.RefFullNames = refFullNames resp, err := private.HookPostReceive(repoUser, repoName, hookOptions) if resp == nil { + _ = dWriter.Close() hookPrintResults(results) fail("Internal Server Error", err) } @@ -277,9 +374,9 @@ Gitea or set your environment appropriately.`, "") fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) } } - fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total) - os.Stdout.Sync() + fmt.Fprintf(out, "Processed %d references in total\n", total) + _ = dWriter.Close() hookPrintResults(results) return nil } @@ -288,19 +385,18 @@ Gitea or set your environment appropriately.`, "") hookOptions.NewCommitIDs = newCommitIDs[:count] hookOptions.RefFullNames = refFullNames[:count] - fmt.Fprintf(os.Stdout, " Processing %d references\n", count) - os.Stdout.Sync() + fmt.Fprintf(out, " Processing %d references\n", count) resp, err := private.HookPostReceive(repoUser, repoName, hookOptions) if resp == nil { + _ = dWriter.Close() hookPrintResults(results) fail("Internal Server Error", err) } wasEmpty = wasEmpty || resp.RepoWasEmpty results = append(results, resp.Results...) - fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total) - os.Stdout.Sync() + fmt.Fprintf(out, "Processed %d references in total\n", total) if wasEmpty && masterPushed { // We need to tell the repo to reset the default branch to master @@ -309,7 +405,7 @@ Gitea or set your environment appropriately.`, "") fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) } } - + _ = dWriter.Close() hookPrintResults(results) return nil diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index dc6a1ba34697..ea17096ea574 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -522,6 +522,8 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` - `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view. - `GC_ARGS`: **\**: Arguments for command `git gc`, e.g. `--aggressive --auto`. See more on http://git-scm.com/docs/git-gc/ - `ENABLE_AUTO_GIT_WIRE_PROTOCOL`: **true**: If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1 +- `VERBOSE_PUSH`: **true**: Print status information about pushes as they are being processed. +- `VERBOSE_PUSH_DELAY`: **5s**: Only print verbose information if push takes longer than this delay. ## Git - Timeout settings (`git.timeout`) - `DEFAUlT`: **360**: Git operations default timeout seconds. diff --git a/modules/setting/git.go b/modules/setting/git.go index 8495be8836f1..8c8179cba6b8 100644 --- a/modules/setting/git.go +++ b/modules/setting/git.go @@ -21,6 +21,8 @@ var ( MaxGitDiffLines int MaxGitDiffLineCharacters int MaxGitDiffFiles int + VerbosePush bool + VerbosePushDelay time.Duration GCArgs []string `ini:"GC_ARGS" delim:" "` EnableAutoGitWireProtocol bool Timeout struct { @@ -36,6 +38,8 @@ var ( MaxGitDiffLines: 1000, MaxGitDiffLineCharacters: 5000, MaxGitDiffFiles: 100, + VerbosePush: true, + VerbosePushDelay: 5 * time.Second, GCArgs: []string{}, EnableAutoGitWireProtocol: true, Timeout: struct { From 5765212c6dbcaeb27779707af3ca57775e535bd9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 12 Jan 2020 17:36:21 +0800 Subject: [PATCH 10/14] Add owner_name column for table repository for maintaince reason (#9717) * Add owner_name column for table repository for maintaince reason * refactor * Fix tests * fix test * fix bug when fork repository Co-authored-by: zeripath --- models/action.go | 2 +- models/fixtures/repository.yml | 48 ++++++++++++++++ models/helper_environment.go | 2 +- models/migrations/migrations.go | 2 + models/migrations/v120.go | 20 +++++++ models/pull.go | 2 +- models/repo.go | 98 ++++++++++----------------------- models/repo_generate.go | 14 ++--- models/repo_indexer.go | 6 +- models/repo_permission.go | 8 +-- models/repo_test.go | 1 + models/wiki.go | 5 +- modules/context/repo.go | 2 +- routers/admin/repos.go | 2 +- routers/repo/compare.go | 6 +- services/mailer/mail.go | 3 +- services/pull/patch.go | 8 +-- services/pull/pull.go | 2 +- 18 files changed, 129 insertions(+), 102 deletions(-) create mode 100644 models/migrations/v120.go diff --git a/models/action.go b/models/action.go index a7e04a72fd27..1754c2a353f5 100644 --- a/models/action.go +++ b/models/action.go @@ -145,7 +145,7 @@ func (a *Action) GetActAvatar() string { // GetRepoUserName returns the name of the action repository owner. func (a *Action) GetRepoUserName() string { a.loadRepo() - return a.Repo.MustOwner().Name + return a.Repo.OwnerName } // ShortRepoUserName returns the name of the action repository owner diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index c7f4d4d1096a..a68e63e309ee 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -1,6 +1,7 @@ - id: 1 owner_id: 2 + owner_name: user2 lower_name: repo1 name: repo1 is_private: false @@ -16,6 +17,7 @@ - id: 2 owner_id: 2 + owner_name: user2 lower_name: repo2 name: repo2 is_private: true @@ -30,6 +32,7 @@ - id: 3 owner_id: 3 + owner_name: user3 lower_name: repo3 name: repo3 is_private: true @@ -43,6 +46,7 @@ - id: 4 owner_id: 5 + owner_name: user5 lower_name: repo4 name: repo4 is_private: false @@ -56,6 +60,7 @@ - id: 5 owner_id: 3 + owner_name: user3 lower_name: repo5 name: repo5 is_private: true @@ -70,6 +75,7 @@ - id: 6 owner_id: 10 + owner_name: user10 lower_name: repo6 name: repo6 is_private: true @@ -83,6 +89,7 @@ - id: 7 owner_id: 10 + owner_name: user10 lower_name: repo7 name: repo7 is_private: true @@ -96,6 +103,7 @@ - id: 8 owner_id: 10 + owner_name: user10 lower_name: repo8 name: repo8 is_private: false @@ -109,6 +117,7 @@ - id: 9 owner_id: 11 + owner_name: user11 lower_name: repo9 name: repo9 is_private: false @@ -122,6 +131,7 @@ - id: 10 owner_id: 12 + owner_name: user12 lower_name: repo10 name: repo10 is_private: false @@ -137,6 +147,7 @@ id: 11 fork_id: 10 owner_id: 13 + owner_name: user13 lower_name: repo11 name: repo11 is_private: false @@ -150,6 +161,7 @@ - id: 12 owner_id: 14 + owner_name: user14 lower_name: test_repo_12 name: test_repo_12 is_private: false @@ -163,6 +175,7 @@ - id: 13 owner_id: 14 + owner_name: user14 lower_name: test_repo_13 name: test_repo_13 is_private: true @@ -176,6 +189,7 @@ - id: 14 owner_id: 14 + owner_name: user14 lower_name: test_repo_14 name: test_repo_14 description: test_description_14 @@ -190,6 +204,7 @@ - id: 15 owner_id: 2 + owner_name: user2 lower_name: repo15 name: repo15 is_empty: true @@ -198,6 +213,7 @@ - id: 16 owner_id: 2 + owner_name: user2 lower_name: repo16 name: repo16 is_private: true @@ -211,6 +227,7 @@ - id: 17 owner_id: 15 + owner_name: user15 lower_name: big_test_public_1 name: big_test_public_1 is_private: false @@ -226,6 +243,7 @@ - id: 18 owner_id: 15 + owner_name: user15 lower_name: big_test_public_2 name: big_test_public_2 is_private: false @@ -240,6 +258,7 @@ - id: 19 owner_id: 15 + owner_name: user15 lower_name: big_test_private_1 name: big_test_private_1 is_private: true @@ -254,6 +273,7 @@ - id: 20 owner_id: 15 + owner_name: user15 lower_name: big_test_private_2 name: big_test_private_2 is_private: true @@ -268,6 +288,7 @@ - id: 21 owner_id: 16 + owner_name: user16 lower_name: big_test_public_3 name: big_test_public_3 is_private: false @@ -282,6 +303,7 @@ - id: 22 owner_id: 16 + owner_name: user16 lower_name: big_test_private_3 name: big_test_private_3 is_private: true @@ -296,6 +318,7 @@ - id: 23 owner_id: 17 + owner_name: user17 lower_name: big_test_public_4 name: big_test_public_4 is_private: false @@ -310,6 +333,7 @@ - id: 24 owner_id: 17 + owner_name: user17 lower_name: big_test_private_4 name: big_test_private_4 is_private: true @@ -324,6 +348,7 @@ - id: 25 owner_id: 20 + owner_name: user20 lower_name: big_test_public_mirror_5 name: big_test_public_mirror_5 is_private: false @@ -339,6 +364,7 @@ - id: 26 owner_id: 20 + owner_name: user20 lower_name: big_test_private_mirror_5 name: big_test_private_mirror_5 is_private: true @@ -354,6 +380,7 @@ - id: 27 owner_id: 19 + owner_name: user19 lower_name: big_test_public_mirror_6 name: big_test_public_mirror_6 is_private: false @@ -370,6 +397,7 @@ - id: 28 owner_id: 19 + owner_name: user19 lower_name: big_test_private_mirror_6 name: big_test_private_mirror_6 is_private: true @@ -387,6 +415,7 @@ id: 29 fork_id: 27 owner_id: 20 + owner_name: user20 lower_name: big_test_public_fork_7 name: big_test_public_fork_7 is_private: false @@ -402,6 +431,7 @@ id: 30 fork_id: 28 owner_id: 20 + owner_name: user20 lower_name: big_test_private_fork_7 name: big_test_private_fork_7 is_private: true @@ -416,6 +446,7 @@ - id: 31 owner_id: 2 + owner_name: user2 lower_name: repo20 name: repo20 num_stars: 0 @@ -427,6 +458,7 @@ - id: 32 # org public repo owner_id: 3 + owner_name: user3 lower_name: repo21 name: repo21 is_private: false @@ -439,6 +471,7 @@ - id: 33 owner_id: 2 + owner_name: user2 lower_name: utf8 name: utf8 is_private: false @@ -447,6 +480,7 @@ - id: 34 owner_id: 21 + owner_name: user21 lower_name: golang name: golang is_private: false @@ -459,6 +493,7 @@ - id: 35 owner_id: 21 + owner_name: user21 lower_name: graphql name: graphql is_private: false @@ -471,6 +506,7 @@ - id: 36 owner_id: 2 + owner_name: user2 lower_name: commits_search_test name: commits_search_test is_private: false @@ -483,6 +519,7 @@ - id: 37 owner_id: 2 + owner_name: user2 lower_name: git_hooks_test name: git_hooks_test is_private: false @@ -495,6 +532,7 @@ - id: 38 owner_id: 22 + owner_name: limited_org lower_name: public_repo_on_limited_org name: public_repo_on_limited_org is_private: false @@ -507,6 +545,7 @@ - id: 39 owner_id: 22 + owner_name: limited_org lower_name: private_repo_on_limited_org name: private_repo_on_limited_org is_private: true @@ -519,6 +558,7 @@ - id: 40 owner_id: 23 + owner_name: limited_org lower_name: public_repo_on_private_org name: public_repo_on_private_org is_private: false @@ -531,6 +571,7 @@ - id: 41 owner_id: 23 + owner_name: limited_org lower_name: private_repo_on_private_org name: private_repo_on_private_org is_private: true @@ -542,6 +583,7 @@ - id: 42 owner_id: 2 + owner_name: user2 lower_name: glob name: glob is_private: false @@ -554,6 +596,7 @@ - id: 43 owner_id: 26 + owner_name: org26 lower_name: repo26 name: repo26 is_private: true @@ -566,6 +609,7 @@ - id: 44 owner_id: 27 + owner_name: user27 lower_name: template1 name: template1 is_private: false @@ -579,6 +623,7 @@ - id: 45 owner_id: 27 + owner_name: user27 lower_name: template2 name: template2 is_private: false @@ -592,6 +637,7 @@ - id: 46 owner_id: 26 + owner_name: org26 lower_name: repo_external_tracker name: repo_external_tracker is_private: false @@ -604,6 +650,7 @@ - id: 47 owner_id: 26 + owner_name: org26 lower_name: repo_external_tracker_numeric name: repo_external_tracker_numeric is_private: false @@ -616,6 +663,7 @@ - id: 48 owner_id: 26 + owner_name: org26 lower_name: repo_external_tracker_alpha name: repo_external_tracker_alpha is_private: false diff --git a/models/helper_environment.go b/models/helper_environment.go index 35af17adb102..112df96823a4 100644 --- a/models/helper_environment.go +++ b/models/helper_environment.go @@ -43,7 +43,7 @@ func FullPushingEnvironment(author, committer *User, repo *Repository, repoName "GIT_COMMITTER_NAME="+committerSig.Name, "GIT_COMMITTER_EMAIL="+committerSig.Email, EnvRepoName+"="+repoName, - EnvRepoUsername+"="+repo.MustOwnerName(), + EnvRepoUsername+"="+repo.OwnerName, EnvRepoIsWiki+"="+isWiki, EnvPusherName+"="+committer.Name, EnvPusherID+"="+fmt.Sprintf("%d", committer.ID), diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index dc5cc48c64e3..703c168b0087 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -294,6 +294,8 @@ var migrations = []Migration{ NewMigration("Add commit id and stale to reviews", addReviewCommitAndStale), // v119 -> v120 NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType), + // v120 -> v121 + NewMigration("Add owner_name on table repository", addOwnerNameOnRepository), } // Migrate database to current version diff --git a/models/migrations/v120.go b/models/migrations/v120.go new file mode 100644 index 000000000000..91d5b503f3f1 --- /dev/null +++ b/models/migrations/v120.go @@ -0,0 +1,20 @@ +// Copyright 2020 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 migrations + +import ( + "xorm.io/xorm" +) + +func addOwnerNameOnRepository(x *xorm.Engine) error { + type Repository struct { + OwnerName string + } + if err := x.Sync2(new(Repository)); err != nil { + return err + } + _, err := x.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)") + return err +} diff --git a/models/pull.go b/models/pull.go index bf2527679f10..0435311e4e5d 100644 --- a/models/pull.go +++ b/models/pull.go @@ -70,7 +70,7 @@ func (pr *PullRequest) MustHeadUserName() string { log.Error("LoadHeadRepo: %v", err) return "" } - return pr.HeadRepo.MustOwnerName() + return pr.HeadRepo.OwnerName } // Note: don't try to get Issue because will end up recursive querying. diff --git a/models/repo.go b/models/repo.go index 6c9623ea2c5d..e15c22e82245 100644 --- a/models/repo.go +++ b/models/repo.go @@ -149,9 +149,9 @@ const ( // Repository represents a git repository. type Repository struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"UNIQUE(s) index"` - OwnerName string `xorm:"-"` + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"UNIQUE(s) index"` + OwnerName string Owner *User `xorm:"-"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` Name string `xorm:"INDEX NOT NULL"` @@ -252,17 +252,9 @@ func (repo *Repository) MustOwner() *User { return repo.mustOwner(x) } -// MustOwnerName always returns valid owner name to avoid -// conceptually impossible error handling. -// It returns "error" and logs error details when error -// occurs. -func (repo *Repository) MustOwnerName() string { - return repo.mustOwnerName(x) -} - // FullName returns the repository full name func (repo *Repository) FullName() string { - return repo.MustOwnerName() + "/" + repo.Name + return repo.OwnerName + "/" + repo.Name } // HTMLURL returns the repository HTML URL @@ -294,7 +286,7 @@ func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) *api.Repository { var parent *api.Repository - cloneLink := repo.cloneLink(e, false) + cloneLink := repo.cloneLink(false) permission := &api.Permission{ Admin: mode >= AccessModeAdmin, Push: mode >= AccessModeWrite, @@ -356,6 +348,8 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) allowSquash = config.AllowSquash } + repo.mustOwner(e) + return &api.Repository{ ID: repo.ID, Owner: repo.Owner.APIFormat(), @@ -533,46 +527,11 @@ func (repo *Repository) mustOwner(e Engine) *User { return repo.Owner } -func (repo *Repository) getOwnerName(e Engine) error { - if len(repo.OwnerName) > 0 { - return nil - } - - if repo.Owner != nil { - repo.OwnerName = repo.Owner.Name - return nil - } - - u := new(User) - has, err := e.ID(repo.OwnerID).Cols("name").Get(u) - if err != nil { - return err - } else if !has { - return ErrUserNotExist{repo.OwnerID, "", 0} - } - repo.OwnerName = u.Name - return nil -} - -// GetOwnerName returns the repository owner name -func (repo *Repository) GetOwnerName() error { - return repo.getOwnerName(x) -} - -func (repo *Repository) mustOwnerName(e Engine) string { - if err := repo.getOwnerName(e); err != nil { - log.Error("Error loading repository owner name: %v", err) - return "error" - } - - return repo.OwnerName -} - // ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers. func (repo *Repository) ComposeMetas() map[string]string { if repo.RenderingMetas == nil { metas := map[string]string{ - "user": repo.MustOwner().Name, + "user": repo.OwnerName, "repo": repo.Name, "repoPath": repo.RepoPath(), } @@ -588,6 +547,7 @@ func (repo *Repository) ComposeMetas() map[string]string { } } + repo.MustOwner() if repo.Owner.IsOrganization() { teams := make([]string, 0, 5) _ = x.Table("team_repo"). @@ -597,7 +557,7 @@ func (repo *Repository) ComposeMetas() map[string]string { OrderBy("team.lower_name"). Find(&teams) metas["teams"] = "," + strings.Join(teams, ",") + "," - metas["org"] = repo.Owner.LowerName + metas["org"] = strings.ToLower(repo.OwnerName) } repo.RenderingMetas = metas @@ -711,13 +671,9 @@ func (repo *Repository) getTemplateRepo(e Engine) (err error) { return err } -func (repo *Repository) repoPath(e Engine) string { - return RepoPath(repo.mustOwnerName(e), repo.Name) -} - // RepoPath returns the repository path func (repo *Repository) RepoPath() string { - return repo.repoPath(x) + return RepoPath(repo.OwnerName, repo.Name) } // GitConfigPath returns the path to a repository's git config/ directory @@ -742,7 +698,7 @@ func (repo *Repository) Link() string { // ComposeCompareURL returns the repository comparison URL func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { - return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) + return fmt.Sprintf("%s/compare/%s...%s", repo.FullName(), oldCommitID, newCommitID) } // UpdateDefaultBranch updates the default branch @@ -757,7 +713,7 @@ func (repo *Repository) IsOwnedBy(userID int64) bool { } func (repo *Repository) updateSize(e Engine) error { - size, err := util.GetDirectorySize(repo.repoPath(e)) + size, err := util.GetDirectorySize(repo.RepoPath()) if err != nil { return fmt.Errorf("UpdateSize: %v", err) } @@ -912,7 +868,7 @@ func ComposeHTTPSCloneURL(owner, repo string) string { return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo)) } -func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink { +func (repo *Repository) cloneLink(isWiki bool) *CloneLink { repoName := repo.Name if isWiki { repoName += ".wiki" @@ -923,22 +879,21 @@ func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink { sshUser = setting.SSH.BuiltinServerUser } - repo.Owner = repo.mustOwner(e) cl := new(CloneLink) if setting.SSH.Port != 22 { - cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", sshUser, setting.SSH.Domain, setting.SSH.Port, repo.Owner.Name, repoName) + cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", sshUser, setting.SSH.Domain, setting.SSH.Port, repo.OwnerName, repoName) } else if setting.Repository.UseCompatSSHURI { - cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) + cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.OwnerName, repoName) } else { - cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) + cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.OwnerName, repoName) } - cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName) + cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName) return cl } // CloneLink returns clone URLs of repository. func (repo *Repository) CloneLink() (cl *CloneLink) { - return repo.cloneLink(x, false) + return repo.cloneLink(false) } // CheckCreateRepository check if could created a repository @@ -1137,7 +1092,7 @@ func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err) } - cloneLink := repo.cloneLink(e, false) + cloneLink := repo.cloneLink(false) match := map[string]string{ "Name": repo.Name, "Description": repo.Description, @@ -1210,7 +1165,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C if opts.AutoInit { tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.repoPath(e), err) + return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) } defer os.RemoveAll(tmpDir) @@ -1366,6 +1321,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err repo := &Repository{ OwnerID: u.ID, Owner: u, + OwnerName: u.Name, Name: opts.Name, LowerName: strings.ToLower(opts.Name), Description: opts.Description, @@ -1485,6 +1441,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error // new owner. repo.OwnerID = newOwner.ID repo.Owner = newOwner + repo.OwnerName = newOwner.Name // Update repository. if _, err := sess.ID(repo.ID).Update(repo); err != nil { @@ -1683,7 +1640,7 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e } // Create/Remove git-daemon-export-ok for git-daemon... - daemonExportFile := path.Join(repo.repoPath(e), `git-daemon-export-ok`) + daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`) if repo.IsPrivate && com.IsExist(daemonExportFile) { if err = os.Remove(daemonExportFile); err != nil { log.Error("Failed to remove %s: %v", daemonExportFile, err) @@ -1905,7 +1862,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { } // FIXME: Remove repository files should be executed after transaction succeed. - repoPath := repo.repoPath(sess) + repoPath := repo.RepoPath() removeAllWithNotice(sess, "Delete repository files", repoPath) err = repo.deleteWiki(sess) @@ -2290,7 +2247,7 @@ func GitGcRepos() error { SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())). RunInDirTimeout( time.Duration(setting.Git.Timeout.GC)*time.Second, - RepoPath(repo.Owner.Name, repo.Name)); err != nil { + repo.RepoPath()); err != nil { log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("Repository garbage collection failed: Error: %v", err) } @@ -2517,6 +2474,7 @@ func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) ( repo := &Repository{ OwnerID: owner.ID, Owner: owner, + OwnerName: owner.Name, Name: name, LowerName: strings.ToLower(name), Description: desc, @@ -2543,7 +2501,7 @@ func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) ( repoPath := RepoPath(owner.Name, repo.Name) if stdout, err := git.NewCommand( - "clone", "--bare", oldRepo.repoPath(sess), repoPath). + "clone", "--bare", oldRepo.RepoPath(), repoPath). SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())). RunInDirTimeout(10*time.Minute, ""); err != nil { log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err) diff --git a/models/repo_generate.go b/models/repo_generate.go index 1b0466eaa7c8..6761c1ce41d5 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -107,7 +107,7 @@ func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository, ) // Clone to temporary path and do the init commit. - templateRepoPath := templateRepo.repoPath(e) + templateRepoPath := templateRepo.RepoPath() if err := git.Clone(templateRepoPath, tmpDir, git.CloneRepoOptions{ Depth: 1, }); err != nil { @@ -168,7 +168,7 @@ func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository, return err } - repoPath := repo.repoPath(e) + repoPath := repo.RepoPath() if stdout, err := git.NewCommand("remote", "add", "origin", repoPath). SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)). RunInDirWithEnv(tmpDir, env); err != nil { @@ -183,7 +183,7 @@ func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository, func generateRepository(e Engine, repo, templateRepo, generateRepo *Repository) (err error) { tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.repoPath(e), err) + return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) } defer func() { @@ -263,13 +263,13 @@ func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error // GenerateGitHooks generates git hooks from a template repository func GenerateGitHooks(ctx DBContext, templateRepo, generateRepo *Repository) error { - generateGitRepo, err := git.OpenRepository(generateRepo.repoPath(ctx.e)) + generateGitRepo, err := git.OpenRepository(generateRepo.RepoPath()) if err != nil { return err } defer generateGitRepo.Close() - templateGitRepo, err := git.OpenRepository(templateRepo.repoPath(ctx.e)) + templateGitRepo, err := git.OpenRepository(templateRepo.RepoPath()) if err != nil { return err } @@ -365,9 +365,9 @@ func generateExpansion(src string, templateRepo, generateRepo *Repository) strin case "TEMPLATE_DESCRIPTION": return templateRepo.Description case "REPO_OWNER": - return generateRepo.MustOwnerName() + return generateRepo.OwnerName case "TEMPLATE_OWNER": - return templateRepo.MustOwnerName() + return templateRepo.OwnerName case "REPO_LINK": return generateRepo.Link() case "TEMPLATE_LINK": diff --git a/models/repo_indexer.go b/models/repo_indexer.go index aee3c74b35d4..a9a516175d9f 100644 --- a/models/repo_indexer.go +++ b/models/repo_indexer.go @@ -62,13 +62,13 @@ func (repo *Repository) GetIndexerStatus() error { // UpdateIndexerStatus updates indexer status func (repo *Repository) UpdateIndexerStatus(sha string) error { if err := repo.GetIndexerStatus(); err != nil { - return fmt.Errorf("UpdateIndexerStatus: Unable to getIndexerStatus for repo: %s/%s Error: %v", repo.MustOwnerName(), repo.Name, err) + return fmt.Errorf("UpdateIndexerStatus: Unable to getIndexerStatus for repo: %s Error: %v", repo.FullName(), err) } if len(repo.IndexerStatus.CommitSha) == 0 { repo.IndexerStatus.CommitSha = sha _, err := x.Insert(repo.IndexerStatus) if err != nil { - return fmt.Errorf("UpdateIndexerStatus: Unable to insert repoIndexerStatus for repo: %s/%s Sha: %s Error: %v", repo.MustOwnerName(), repo.Name, sha, err) + return fmt.Errorf("UpdateIndexerStatus: Unable to insert repoIndexerStatus for repo: %s Sha: %s Error: %v", repo.FullName(), sha, err) } return nil } @@ -76,7 +76,7 @@ func (repo *Repository) UpdateIndexerStatus(sha string) error { _, err := x.ID(repo.IndexerStatus.ID).Cols("commit_sha"). Update(repo.IndexerStatus) if err != nil { - return fmt.Errorf("UpdateIndexerStatus: Unable to update repoIndexerStatus for repo: %s/%s Sha: %s Error: %v", repo.MustOwnerName(), repo.Name, sha, err) + return fmt.Errorf("UpdateIndexerStatus: Unable to update repoIndexerStatus for repo: %s Sha: %s Error: %v", repo.FullName(), sha, err) } return nil } diff --git a/models/repo_permission.go b/models/repo_permission.go index 374c6f8d5618..cd2022491211 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -164,10 +164,6 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss return } - if repo.Owner == nil { - repo.mustOwner(e) - } - var isCollaborator bool if user != nil { isCollaborator, err = repo.isCollaborator(e, user.ID) @@ -176,6 +172,10 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss } } + if err = repo.getOwner(e); err != nil { + return + } + // Prevent strangers from checking out public repo of private orginization // Allow user if they are collaborator of a repo within a private orginization but not a member of the orginization itself if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) && !isCollaborator { diff --git a/models/repo_test.go b/models/repo_test.go index 7a2227c82023..3a6555c76668 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -22,6 +22,7 @@ func TestMetas(t *testing.T) { repo := &Repository{Name: "testRepo"} repo.Owner = &User{Name: "testOwner"} + repo.OwnerName = repo.Owner.Name repo.Units = nil diff --git a/models/wiki.go b/models/wiki.go index 32a0cc1627a6..223abf1edc55 100644 --- a/models/wiki.go +++ b/models/wiki.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2020 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. @@ -13,7 +14,7 @@ import ( // WikiCloneLink returns clone URLs of repository wiki. func (repo *Repository) WikiCloneLink() *CloneLink { - return repo.cloneLink(x, true) + return repo.cloneLink(true) } // WikiPath returns wiki data path by given user and repository name. @@ -23,7 +24,7 @@ func WikiPath(userName, repoName string) string { // WikiPath returns wiki data path for given repository. func (repo *Repository) WikiPath() string { - return WikiPath(repo.MustOwnerName(), repo.Name) + return WikiPath(repo.OwnerName, repo.Name) } // HasWiki returns true if repository has wiki. diff --git a/modules/context/repo.go b/modules/context/repo.go index 4c32e846eb1c..86c7df2b0543 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -256,7 +256,7 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) { redirectPath := strings.Replace( ctx.Req.URL.Path, fmt.Sprintf("%s/%s", ownerName, previousRepoName), - fmt.Sprintf("%s/%s", repo.MustOwnerName(), repo.Name), + repo.FullName(), 1, ) if ctx.Req.URL.RawQuery != "" { diff --git a/routers/admin/repos.go b/routers/admin/repos.go index 73aa66807b31..39a1d7596c3f 100644 --- a/routers/admin/repos.go +++ b/routers/admin/repos.go @@ -43,7 +43,7 @@ func DeleteRepo(ctx *context.Context) { ctx.ServerError("DeleteRepository", err) return } - log.Trace("Repository deleted: %s/%s", repo.MustOwner().Name, repo.Name) + log.Trace("Repository deleted: %s", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) ctx.JSON(200, map[string]interface{}{ diff --git a/routers/repo/compare.go b/routers/repo/compare.go index d23bccd09ad0..bb800f9ef70c 100644 --- a/routers/repo/compare.go +++ b/routers/repo/compare.go @@ -361,10 +361,8 @@ func parseBaseRepoInfo(ctx *context.Context, repo *models.Repository) error { if err := repo.GetBaseRepo(); err != nil { return err } - if err := repo.BaseRepo.GetOwnerName(); err != nil { - return err - } - baseGitRepo, err := git.OpenRepository(models.RepoPath(repo.BaseRepo.OwnerName, repo.BaseRepo.Name)) + + baseGitRepo, err := git.OpenRepository(repo.BaseRepo.RepoPath()) if err != nil { return err } diff --git a/services/mailer/mail.go b/services/mailer/mail.go index fa40170d464e..4b8e46715f3f 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -10,7 +10,6 @@ import ( "fmt" "html/template" "mime" - "path" "regexp" "strings" texttmpl "text/template" @@ -142,7 +141,7 @@ func SendRegisterNotifyMail(locale Locale, u *models.User) { // SendCollaboratorMail sends mail notification to new collaborator. func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) { - repoName := path.Join(repo.Owner.Name, repo.Name) + repoName := repo.FullName() subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName) data := map[string]interface{}{ diff --git a/services/pull/patch.go b/services/pull/patch.go index 57a2997b36ee..1dbeb81c0110 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -55,8 +55,8 @@ func DownloadDiffOrPatch(pr *models.PullRequest, w io.Writer, patch bool) error } pr.MergeBase = strings.TrimSpace(pr.MergeBase) if err := gitRepo.GetDiffOrPatch(pr.MergeBase, "tracking", w, patch); err != nil { - log.Error("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) - return fmt.Errorf("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) + log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) + return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) } return nil } @@ -108,8 +108,8 @@ func TestPatch(pr *models.PullRequest) error { if err := gitRepo.GetDiff(pr.MergeBase, "tracking", tmpPatchFile); err != nil { tmpPatchFile.Close() - log.Error("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) - return fmt.Errorf("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) + log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) + return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) } stat, err := tmpPatchFile.Stat() if err != nil { diff --git a/services/pull/pull.go b/services/pull/pull.go index b459d81cf79a..bc71e5221358 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -256,7 +256,7 @@ func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID st // Add a temporary remote. tmpRemote := "checkIfPRContentChanged-" + com.ToStr(time.Now().UnixNano()) - if err = headGitRepo.AddRemote(tmpRemote, models.RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name), true); err != nil { + if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil { return false, fmt.Errorf("AddRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err) } defer func() { From b465d0d78793da6e67890a7cb9d3ae1b807c53ca Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 12 Jan 2020 20:11:17 +0800 Subject: [PATCH 11/14] Move create/fork repository from models to modules/repository (#9489) * Move create/fork repository from models to modules/repository * fix wrong reference * fix test * fix test * fix lint * Fix DBContext * remove duplicated TestMain * fix lint * fix conflicts --- models/issue_label.go | 8 +- models/org_team_test.go | 133 ---------- models/repo.go | 400 +++--------------------------- models/repo_generate.go | 219 ---------------- models/repo_test.go | 13 - models/task.go | 53 +--- modules/migrations/gitea.go | 3 +- modules/repofiles/update.go | 4 +- modules/repository/create.go | 77 ++++++ modules/repository/create_test.go | 145 +++++++++++ modules/repository/fork.go | 87 +++++++ modules/repository/fork_test.go | 25 ++ modules/repository/generate.go | 230 +++++++++++++++++ modules/repository/init.go | 214 ++++++++++++++++ modules/repository/repo.go | 2 +- modules/task/task.go | 51 +++- routers/api/v1/repo/repo.go | 8 +- routers/repo/issue_label.go | 2 +- services/mirror/mirror.go | 2 +- services/mirror/mirror_test.go | 2 +- services/repository/generate.go | 5 +- services/repository/repository.go | 5 +- 22 files changed, 894 insertions(+), 794 deletions(-) create mode 100644 modules/repository/create.go create mode 100644 modules/repository/create_test.go create mode 100644 modules/repository/fork.go create mode 100644 modules/repository/fork_test.go create mode 100644 modules/repository/generate.go create mode 100644 modules/repository/init.go diff --git a/models/issue_label.go b/models/issue_label.go index d96152d463f4..66f93f4f48a4 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -22,9 +22,9 @@ var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})") // GetLabelTemplateFile loads the label template file by given name, // then parses and returns a list of name-color pairs and optionally description. func GetLabelTemplateFile(name string) ([][3]string, error) { - data, err := getRepoInitFile("label", name) + data, err := GetRepoInitFile("label", name) if err != nil { - return nil, fmt.Errorf("getRepoInitFile: %v", err) + return nil, fmt.Errorf("GetRepoInitFile: %v", err) } lines := strings.Split(string(data), "\n") @@ -175,8 +175,8 @@ func initalizeLabels(e Engine, repoID int64, labelTemplate string) error { } // InitalizeLabels adds a label set to a repository using a template -func InitalizeLabels(repoID int64, labelTemplate string) error { - return initalizeLabels(x, repoID, labelTemplate) +func InitalizeLabels(ctx DBContext, repoID int64, labelTemplate string) error { + return initalizeLabels(ctx.e, repoID, labelTemplate) } func newLabel(e Engine, label *Label) error { diff --git a/models/org_team_test.go b/models/org_team_test.go index b7e2ef113d3d..249e50b07214 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -5,12 +5,9 @@ package models import ( - "fmt" "strings" "testing" - "code.gitea.io/gitea/modules/structs" - "github.com/stretchr/testify/assert" ) @@ -377,133 +374,3 @@ func TestUsersInTeamsCount(t *testing.T) { test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4 test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5 } - -func TestIncludesAllRepositoriesTeams(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - testTeamRepositories := func(teamID int64, repoIds []int64) { - team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - assert.NoError(t, team.GetRepositories(), "%s: GetRepositories", team.Name) - assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) - assert.Equal(t, len(repoIds), len(team.Repos), "%s: repo count", team.Name) - for i, rid := range repoIds { - if rid > 0 { - assert.True(t, team.HasRepository(rid), "%s: HasRepository(%d) %d", rid, i) - } - } - } - - // Get an admin user. - user, err := GetUserByID(1) - assert.NoError(t, err, "GetUserByID") - - // Create org. - org := &User{ - Name: "All repo", - IsActive: true, - Type: UserTypeOrganization, - Visibility: structs.VisibleTypePublic, - } - assert.NoError(t, CreateOrganization(org, user), "CreateOrganization") - - // Check Owner team. - ownerTeam, err := org.GetOwnerTeam() - assert.NoError(t, err, "GetOwnerTeam") - assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories") - - // Create repos. - repoIds := make([]int64, 0) - for i := 0; i < 3; i++ { - r, err := CreateRepository(user, org, CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}) - assert.NoError(t, err, "CreateRepository %d", i) - if r != nil { - repoIds = append(repoIds, r.ID) - } - } - // Get fresh copy of Owner team after creating repos. - ownerTeam, err = org.GetOwnerTeam() - assert.NoError(t, err, "GetOwnerTeam") - - // Create teams and check repositories. - teams := []*Team{ - ownerTeam, - { - OrgID: org.ID, - Name: "team one", - Authorize: AccessModeRead, - IncludesAllRepositories: true, - }, - { - OrgID: org.ID, - Name: "team 2", - Authorize: AccessModeRead, - IncludesAllRepositories: false, - }, - { - OrgID: org.ID, - Name: "team three", - Authorize: AccessModeWrite, - IncludesAllRepositories: true, - }, - { - OrgID: org.ID, - Name: "team 4", - Authorize: AccessModeWrite, - IncludesAllRepositories: false, - }, - } - teamRepos := [][]int64{ - repoIds, - repoIds, - {}, - repoIds, - {}, - } - for i, team := range teams { - if i > 0 { // first team is Owner. - assert.NoError(t, NewTeam(team), "%s: NewTeam", team.Name) - } - testTeamRepositories(team.ID, teamRepos[i]) - } - - // Update teams and check repositories. - teams[3].IncludesAllRepositories = false - teams[4].IncludesAllRepositories = true - teamRepos[4] = repoIds - for i, team := range teams { - assert.NoError(t, UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name) - testTeamRepositories(team.ID, teamRepos[i]) - } - - // Create repo and check teams repositories. - org.Teams = nil // Reset teams to allow their reloading. - r, err := CreateRepository(user, org, CreateRepoOptions{Name: "repo-last"}) - assert.NoError(t, err, "CreateRepository last") - if r != nil { - repoIds = append(repoIds, r.ID) - } - teamRepos[0] = repoIds - teamRepos[1] = repoIds - teamRepos[4] = repoIds - for i, team := range teams { - testTeamRepositories(team.ID, teamRepos[i]) - } - - // Remove repo and check teams repositories. - assert.NoError(t, DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository") - teamRepos[0] = repoIds[1:] - teamRepos[1] = repoIds[1:] - teamRepos[3] = repoIds[1:3] - teamRepos[4] = repoIds[1:] - for i, team := range teams { - testTeamRepositories(team.ID, teamRepos[i]) - } - - // Wipe created items. - for i, rid := range repoIds { - if i > 0 { // first repo already deleted. - assert.NoError(t, DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i) - } - } - assert.NoError(t, DeleteOrganization(org), "DeleteOrganization") -} diff --git a/models/repo.go b/models/repo.go index e15c22e82245..ee9e6a504ba1 100644 --- a/models/repo.go +++ b/models/repo.go @@ -6,7 +6,6 @@ package models import ( - "bytes" "context" "crypto/md5" "errors" @@ -38,7 +37,6 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/mcuadros/go-version" "github.com/unknwon/com" "xorm.io/builder" ) @@ -715,7 +713,7 @@ func (repo *Repository) IsOwnedBy(userID int64) bool { func (repo *Repository) updateSize(e Engine) error { size, err := util.GetDirectorySize(repo.RepoPath()) if err != nil { - return fmt.Errorf("UpdateSize: %v", err) + return fmt.Errorf("updateSize: %v", err) } repo.Size = size @@ -724,8 +722,8 @@ func (repo *Repository) updateSize(e Engine) error { } // UpdateSize updates the repository size, calculating it using util.GetDirectorySize -func (repo *Repository) UpdateSize() error { - return repo.updateSize(x) +func (repo *Repository) UpdateSize(ctx DBContext) error { + return repo.updateSize(ctx.e) } // CanUserFork returns true if specified user can fork repository. @@ -966,64 +964,6 @@ func createDelegateHooks(repoPath string) (err error) { return nil } -// initRepoCommit temporarily changes with work directory. -func initRepoCommit(tmpPath string, repo *Repository, u *User) (err error) { - commitTimeStr := time.Now().Format(time.RFC3339) - - sig := u.NewGitSig() - // Because this may call hooks we should pass in the environment - env := append(os.Environ(), - "GIT_AUTHOR_NAME="+sig.Name, - "GIT_AUTHOR_EMAIL="+sig.Email, - "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_NAME="+sig.Name, - "GIT_COMMITTER_EMAIL="+sig.Email, - "GIT_COMMITTER_DATE="+commitTimeStr, - ) - - if stdout, err := git.NewCommand("add", "--all"). - SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). - RunInDir(tmpPath); err != nil { - log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) - return fmt.Errorf("git add --all: %v", err) - } - - binVersion, err := git.BinVersion() - if err != nil { - return fmt.Errorf("Unable to get git version: %v", err) - } - - args := []string{ - "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), - "-m", "Initial commit", - } - - if version.Compare(binVersion, "1.7.9", ">=") { - sign, keyID := SignInitialCommit(tmpPath, u) - if sign { - args = append(args, "-S"+keyID) - } else if version.Compare(binVersion, "2.0.0", ">=") { - args = append(args, "--no-gpg-sign") - } - } - - if stdout, err := git.NewCommand(args...). - SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). - RunInDirWithEnv(tmpPath, env); err != nil { - log.Error("Failed to commit: %v: Stdout: %s\nError: %v", args, stdout, err) - return fmt.Errorf("git commit: %v", err) - } - - if stdout, err := git.NewCommand("push", "origin", "master"). - SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)). - RunInDirWithEnv(tmpPath, InternalPushingEnvironment(u, repo)); err != nil { - log.Error("Failed to push back to master: Stdout: %s\nError: %v", stdout, err) - return fmt.Errorf("git push: %v", err) - } - - return nil -} - // CreateRepoOptions contains the create repository options type CreateRepoOptions struct { Name string @@ -1040,7 +980,8 @@ type CreateRepoOptions struct { Status RepositoryStatus } -func getRepoInitFile(tp, name string) ([]byte, error) { +// GetRepoInitFile returns repository init files +func GetRepoInitFile(tp, name string) ([]byte, error) { cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") relPath := path.Join("options", tp, cleanedName) @@ -1064,140 +1005,6 @@ func getRepoInitFile(tp, name string) ([]byte, error) { } } -func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { - commitTimeStr := time.Now().Format(time.RFC3339) - authorSig := repo.Owner.NewGitSig() - - // Because this may call hooks we should pass in the environment - env := append(os.Environ(), - "GIT_AUTHOR_NAME="+authorSig.Name, - "GIT_AUTHOR_EMAIL="+authorSig.Email, - "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_NAME="+authorSig.Name, - "GIT_COMMITTER_EMAIL="+authorSig.Email, - "GIT_COMMITTER_DATE="+commitTimeStr, - ) - - // Clone to temporary path and do the init commit. - if stdout, err := git.NewCommand("clone", repoPath, tmpDir). - SetDescription(fmt.Sprintf("initRepository (git clone): %s to %s", repoPath, tmpDir)). - RunInDirWithEnv("", env); err != nil { - log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) - return fmt.Errorf("git clone: %v", err) - } - - // README - data, err := getRepoInitFile("readme", opts.Readme) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err) - } - - cloneLink := repo.cloneLink(false) - match := map[string]string{ - "Name": repo.Name, - "Description": repo.Description, - "CloneURL.SSH": cloneLink.SSH, - "CloneURL.HTTPS": cloneLink.HTTPS, - } - if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), - []byte(com.Expand(string(data), match)), 0644); err != nil { - return fmt.Errorf("write README.md: %v", err) - } - - // .gitignore - if len(opts.Gitignores) > 0 { - var buf bytes.Buffer - names := strings.Split(opts.Gitignores, ",") - for _, name := range names { - data, err = getRepoInitFile("gitignore", name) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", name, err) - } - buf.WriteString("# ---> " + name + "\n") - buf.Write(data) - buf.WriteString("\n") - } - - if buf.Len() > 0 { - if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { - return fmt.Errorf("write .gitignore: %v", err) - } - } - } - - // LICENSE - if len(opts.License) > 0 { - data, err = getRepoInitFile("license", opts.License) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.License, err) - } - - if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { - return fmt.Errorf("write LICENSE: %v", err) - } - } - - return nil -} - -func checkInitRepository(repoPath string) (err error) { - // Somehow the directory could exist. - if com.IsExist(repoPath) { - return fmt.Errorf("initRepository: path already exists: %s", repoPath) - } - - // Init git bare new repository. - if err = git.InitRepository(repoPath, true); err != nil { - return fmt.Errorf("InitRepository: %v", err) - } else if err = createDelegateHooks(repoPath); err != nil { - return fmt.Errorf("createDelegateHooks: %v", err) - } - return nil -} - -// InitRepository initializes README and .gitignore if needed. -func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) { - if err = checkInitRepository(repoPath); err != nil { - return err - } - - // Initialize repository according to user's choice. - if opts.AutoInit { - tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) - if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) - } - - defer os.RemoveAll(tmpDir) - - if err = prepareRepoCommit(e, repo, tmpDir, repoPath, opts); err != nil { - return fmt.Errorf("prepareRepoCommit: %v", err) - } - - // Apply changes and commit. - if err = initRepoCommit(tmpDir, repo, u); err != nil { - return fmt.Errorf("initRepoCommit: %v", err) - } - } - - // Re-fetch the repository from database before updating it (else it would - // override changes that were done earlier with sql) - if repo, err = getRepositoryByID(e, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %v", err) - } - - if !opts.AutoInit { - repo.IsEmpty = true - } - - repo.DefaultBranch = "master" - if err = updateRepository(e, repo, false); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return nil -} - var ( reservedRepoNames = []string{".", ".."} reservedRepoPatterns = []string{"*.git", "*.wiki"} @@ -1208,22 +1015,23 @@ func IsUsableRepoName(name string) error { return isUsableName(reservedRepoNames, reservedRepoPatterns, name) } -func createRepository(e Engine, doer, u *User, repo *Repository) (err error) { +// CreateRepository creates a repository for the user/organization. +func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) { if err = IsUsableRepoName(repo.Name); err != nil { return err } - has, err := isRepositoryExist(e, u, repo.Name) + has, err := isRepositoryExist(ctx.e, u, repo.Name) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { return ErrRepoAlreadyExist{u.Name, repo.Name} } - if _, err = e.Insert(repo); err != nil { + if _, err = ctx.e.Insert(repo); err != nil { return err } - if err = deleteRepoRedirect(e, u.ID, repo.Name); err != nil { + if err = deleteRepoRedirect(ctx.e, u.ID, repo.Name); err != nil { return err } @@ -1252,20 +1060,19 @@ func createRepository(e Engine, doer, u *User, repo *Repository) (err error) { Type: tp, }) } - } - if _, err = e.Insert(&units); err != nil { + if _, err = ctx.e.Insert(&units); err != nil { return err } // Remember visibility preference. u.LastRepoVisibility = repo.IsPrivate - if err = updateUserCols(e, u, "last_repo_visibility"); err != nil { + if err = updateUserCols(ctx.e, u, "last_repo_visibility"); err != nil { return fmt.Errorf("updateUser: %v", err) } - if _, err = e.Incr("num_repos").ID(u.ID).Update(new(User)); err != nil { + if _, err = ctx.e.Incr("num_repos").ID(u.ID).Update(new(User)); err != nil { return fmt.Errorf("increment user total_repos: %v", err) } u.NumRepos++ @@ -1277,107 +1084,41 @@ func createRepository(e Engine, doer, u *User, repo *Repository) (err error) { } for _, t := range u.Teams { if t.IncludesAllRepositories { - if err := t.addRepository(e, repo); err != nil { + if err := t.addRepository(ctx.e, repo); err != nil { return fmt.Errorf("addRepository: %v", err) } } } - if isAdmin, err := isUserRepoAdmin(e, repo, doer); err != nil { + if isAdmin, err := isUserRepoAdmin(ctx.e, repo, doer); err != nil { return fmt.Errorf("isUserRepoAdmin: %v", err) } else if !isAdmin { // Make creator repo admin if it wan't assigned automatically - if err = repo.addCollaborator(e, doer); err != nil { + if err = repo.addCollaborator(ctx.e, doer); err != nil { return fmt.Errorf("AddCollaborator: %v", err) } - if err = repo.changeCollaborationAccessMode(e, doer.ID, AccessModeAdmin); err != nil { + if err = repo.changeCollaborationAccessMode(ctx.e, doer.ID, AccessModeAdmin); err != nil { return fmt.Errorf("ChangeCollaborationAccessMode: %v", err) } } - } else if err = repo.recalculateAccesses(e); err != nil { + } else if err = repo.recalculateAccesses(ctx.e); err != nil { // Organization automatically called this in addRepository method. return fmt.Errorf("recalculateAccesses: %v", err) } if setting.Service.AutoWatchNewRepos { - if err = watchRepo(e, doer.ID, repo.ID, true); err != nil { + if err = watchRepo(ctx.e, doer.ID, repo.ID, true); err != nil { return fmt.Errorf("watchRepo: %v", err) } } - if err = copyDefaultWebhooksToRepo(e, repo.ID); err != nil { + if err = copyDefaultWebhooksToRepo(ctx.e, repo.ID); err != nil { return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err) } return nil } -// CreateRepository creates a repository for the user/organization. -func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) { - if !doer.IsAdmin && !u.CanCreateRepo() { - return nil, ErrReachLimitOfRepo{u.MaxRepoCreation} - } - - repo := &Repository{ - OwnerID: u.ID, - Owner: u, - OwnerName: u.Name, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - OriginalURL: opts.OriginalURL, - OriginalServiceType: opts.GitServiceType, - IsPrivate: opts.IsPrivate, - IsFsckEnabled: !opts.IsMirror, - CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, - Status: opts.Status, - IsEmpty: !opts.AutoInit, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, u, repo); err != nil { - return nil, err - } - - // No need for init mirror. - if !opts.IsMirror { - repoPath := RepoPath(u.Name, repo.Name) - if err = initRepository(sess, repoPath, u, repo, opts); err != nil { - if err2 := os.RemoveAll(repoPath); err2 != nil { - log.Error("initRepository: %v", err) - return nil, fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) - } - return nil, fmt.Errorf("initRepository: %v", err) - } - - // Initialize Issue Labels if selected - if len(opts.IssueLabels) > 0 { - if err = initalizeLabels(sess, repo.ID, opts.IssueLabels); err != nil { - return nil, fmt.Errorf("initalizeLabels: %v", err) - } - } - - if stdout, err := git.NewCommand("update-server-info"). - SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). - RunInDir(repoPath); err != nil { - log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return nil, fmt.Errorf("CreateRepository(git update-server-info): %v", err) - } - } - - if err = sess.Commit(); err != nil { - return nil, err - } - - return repo, err -} - func countRepositories(userID int64, private bool) int64 { sess := x.Where("id > 0") @@ -1414,6 +1155,12 @@ func RepoPath(userName, repoName string) string { return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") } +// IncrementRepoForkNum increment repository fork number +func IncrementRepoForkNum(ctx DBContext, repoID int64) error { + _, err := ctx.e.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) + return err +} + // TransferOwnership transfers all corresponding setting from old user to new one. func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error { newOwner, err := GetUserByName(newOwnerName) @@ -1672,6 +1419,11 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e return nil } +// UpdateRepositoryCtx updates a repository with db context +func UpdateRepositoryCtx(ctx DBContext, repo *Repository, visibilityChanged bool) error { + return updateRepository(ctx.e, repo, visibilityChanged) +} + // UpdateRepository updates a repository func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { sess := x.NewSession() @@ -1987,6 +1739,11 @@ func GetRepositoryByID(id int64) (*Repository, error) { return getRepositoryByID(x, id) } +// GetRepositoryByIDCtx returns the repository by given id if exists. +func GetRepositoryByIDCtx(ctx DBContext, id int64) (*Repository, error) { + return getRepositoryByID(ctx.e, id) +} + // GetRepositoriesMapByIDs returns the repositories by given id slice. func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { var repos = make(map[int64]*Repository, len(ids)) @@ -2436,20 +2193,16 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { } // CopyLFS copies LFS data from one repo to another -func CopyLFS(newRepo, oldRepo *Repository) error { - return copyLFS(x, newRepo, oldRepo) -} - -func copyLFS(e Engine, newRepo, oldRepo *Repository) error { +func CopyLFS(ctx DBContext, newRepo, oldRepo *Repository) error { var lfsObjects []*LFSMetaObject - if err := e.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { + if err := ctx.e.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { return err } for _, v := range lfsObjects { v.ID = 0 v.RepositoryID = newRepo.ID - if _, err := e.Insert(v); err != nil { + if _, err := ctx.e.Insert(v); err != nil { return err } } @@ -2457,81 +2210,6 @@ func copyLFS(e Engine, newRepo, oldRepo *Repository) error { return nil } -// ForkRepository forks a repository -func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { - forkedRepo, err := oldRepo.GetUserFork(owner.ID) - if err != nil { - return nil, err - } - if forkedRepo != nil { - return nil, ErrForkAlreadyExist{ - Uname: owner.Name, - RepoName: oldRepo.FullName(), - ForkName: forkedRepo.FullName(), - } - } - - repo := &Repository{ - OwnerID: owner.ID, - Owner: owner, - OwnerName: owner.Name, - Name: name, - LowerName: strings.ToLower(name), - Description: desc, - DefaultBranch: oldRepo.DefaultBranch, - IsPrivate: oldRepo.IsPrivate, - IsEmpty: oldRepo.IsEmpty, - IsFork: true, - ForkID: oldRepo.ID, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, owner, repo); err != nil { - return nil, err - } - - if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { - return nil, err - } - - repoPath := RepoPath(owner.Name, repo.Name) - if stdout, err := git.NewCommand( - "clone", "--bare", oldRepo.RepoPath(), repoPath). - SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())). - RunInDirTimeout(10*time.Minute, ""); err != nil { - log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err) - return nil, fmt.Errorf("git clone: %v", err) - } - - if stdout, err := git.NewCommand("update-server-info"). - SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())). - RunInDir(repoPath); err != nil { - log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) - return nil, fmt.Errorf("git update-server-info: %v", err) - } - - if err = createDelegateHooks(repoPath); err != nil { - return nil, fmt.Errorf("createDelegateHooks: %v", err) - } - - //Commit repo to get Fork ID - err = sess.Commit() - if err != nil { - return nil, err - } - - if err = repo.UpdateSize(); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - - return repo, CopyLFS(repo, oldRepo) -} - // GetForks returns all the forks of the repository func (repo *Repository) GetForks() ([]*Repository, error) { forks := make([]*Repository, 0, repo.NumForks) diff --git a/models/repo_generate.go b/models/repo_generate.go index 6761c1ce41d5..b3230acd060b 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -5,14 +5,8 @@ package models import ( - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" "strconv" "strings" - "time" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -71,186 +65,6 @@ func (gt GiteaTemplate) Globs() []glob.Glob { return gt.globs } -func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { - gtPath := filepath.Join(tmpDir, ".gitea", "template") - if _, err := os.Stat(gtPath); os.IsNotExist(err) { - return nil, nil - } else if err != nil { - return nil, err - } - - content, err := ioutil.ReadFile(gtPath) - if err != nil { - return nil, err - } - - gt := &GiteaTemplate{ - Path: gtPath, - Content: content, - } - - return gt, nil -} - -func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository, tmpDir string) error { - commitTimeStr := time.Now().Format(time.RFC3339) - authorSig := repo.Owner.NewGitSig() - - // Because this may call hooks we should pass in the environment - env := append(os.Environ(), - "GIT_AUTHOR_NAME="+authorSig.Name, - "GIT_AUTHOR_EMAIL="+authorSig.Email, - "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_NAME="+authorSig.Name, - "GIT_COMMITTER_EMAIL="+authorSig.Email, - "GIT_COMMITTER_DATE="+commitTimeStr, - ) - - // Clone to temporary path and do the init commit. - templateRepoPath := templateRepo.RepoPath() - if err := git.Clone(templateRepoPath, tmpDir, git.CloneRepoOptions{ - Depth: 1, - }); err != nil { - return fmt.Errorf("git clone: %v", err) - } - - if err := os.RemoveAll(path.Join(tmpDir, ".git")); err != nil { - return fmt.Errorf("remove git dir: %v", err) - } - - // Variable expansion - gt, err := checkGiteaTemplate(tmpDir) - if err != nil { - return fmt.Errorf("checkGiteaTemplate: %v", err) - } - - if gt != nil { - if err := os.Remove(gt.Path); err != nil { - return fmt.Errorf("remove .giteatemplate: %v", err) - } - - // Avoid walking tree if there are no globs - if len(gt.Globs()) > 0 { - tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" - if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error { - if walkErr != nil { - return walkErr - } - - if info.IsDir() { - return nil - } - - base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) - for _, g := range gt.Globs() { - if g.Match(base) { - content, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - if err := ioutil.WriteFile(path, - []byte(generateExpansion(string(content), templateRepo, generateRepo)), - 0644); err != nil { - return err - } - break - } - } - return nil - }); err != nil { - return err - } - } - } - - if err := git.InitRepository(tmpDir, false); err != nil { - return err - } - - repoPath := repo.RepoPath() - if stdout, err := git.NewCommand("remote", "add", "origin", repoPath). - SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)). - RunInDirWithEnv(tmpDir, env); err != nil { - log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) - return fmt.Errorf("git remote add: %v", err) - } - - return initRepoCommit(tmpDir, repo, repo.Owner) -} - -// generateRepository initializes repository from template -func generateRepository(e Engine, repo, templateRepo, generateRepo *Repository) (err error) { - tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) - if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) - } - - defer func() { - if err := os.RemoveAll(tmpDir); err != nil { - log.Error("RemoveAll: %v", err) - } - }() - - if err = generateRepoCommit(e, repo, templateRepo, generateRepo, tmpDir); err != nil { - return fmt.Errorf("generateRepoCommit: %v", err) - } - - // re-fetch repo - if repo, err = getRepositoryByID(e, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %v", err) - } - - repo.DefaultBranch = "master" - if err = updateRepository(e, repo, false); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return nil -} - -// GenerateRepository generates a repository from a template -func GenerateRepository(ctx DBContext, doer, owner *User, templateRepo *Repository, opts GenerateRepoOptions) (_ *Repository, err error) { - generateRepo := &Repository{ - OwnerID: owner.ID, - Owner: owner, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - IsPrivate: opts.Private, - IsEmpty: !opts.GitContent || templateRepo.IsEmpty, - IsFsckEnabled: templateRepo.IsFsckEnabled, - TemplateID: templateRepo.ID, - } - - if err = createRepository(ctx.e, doer, owner, generateRepo); err != nil { - return nil, err - } - - repoPath := RepoPath(owner.Name, generateRepo.Name) - if err = checkInitRepository(repoPath); err != nil { - return generateRepo, err - } - - return generateRepo, nil -} - -// GenerateGitContent generates git content from a template repository -func GenerateGitContent(ctx DBContext, templateRepo, generateRepo *Repository) error { - if err := generateRepository(ctx.e, generateRepo, templateRepo, generateRepo); err != nil { - return err - } - - if err := generateRepo.updateSize(ctx.e); err != nil { - return fmt.Errorf("failed to update size for repository: %v", err) - } - - if err := copyLFS(ctx.e, generateRepo, templateRepo); err != nil { - return fmt.Errorf("failed to copy LFS: %v", err) - } - return nil -} - // GenerateTopics generates topics from a template repository func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error { for _, topic := range templateRepo.Topics { @@ -352,36 +166,3 @@ func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository) } return nil } - -func generateExpansion(src string, templateRepo, generateRepo *Repository) string { - return os.Expand(src, func(key string) string { - switch key { - case "REPO_NAME": - return generateRepo.Name - case "TEMPLATE_NAME": - return templateRepo.Name - case "REPO_DESCRIPTION": - return generateRepo.Description - case "TEMPLATE_DESCRIPTION": - return templateRepo.Description - case "REPO_OWNER": - return generateRepo.OwnerName - case "TEMPLATE_OWNER": - return templateRepo.OwnerName - case "REPO_LINK": - return generateRepo.Link() - case "TEMPLATE_LINK": - return templateRepo.Link() - case "REPO_HTTPS_URL": - return generateRepo.CloneLink().HTTPS - case "TEMPLATE_HTTPS_URL": - return templateRepo.CloneLink().HTTPS - case "REPO_SSH_URL": - return generateRepo.CloneLink().SSH - case "TEMPLATE_SSH_URL": - return templateRepo.CloneLink().SSH - default: - return key - } - }) -} diff --git a/models/repo_test.go b/models/repo_test.go index 3a6555c76668..20da43fbbfc9 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -133,19 +133,6 @@ func TestGetUserFork(t *testing.T) { assert.Nil(t, repo) } -func TestForkRepository(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - // user 13 has already forked repo10 - user := AssertExistsAndLoadBean(t, &User{ID: 13}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) - - fork, err := ForkRepository(user, user, repo, "test", "test") - assert.Nil(t, fork) - assert.Error(t, err) - assert.True(t, IsErrForkAlreadyExist(err)) -} - func TestRepoAPIURL(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) diff --git a/models/task.go b/models/task.go index e1d751bc3cf6..f4fce058c056 100644 --- a/models/task.go +++ b/models/task.go @@ -8,8 +8,6 @@ import ( "encoding/json" "fmt" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -169,57 +167,16 @@ func FindTasks(opts FindTaskOptions) ([]*Task, error) { return tasks, err } +// CreateTask creates a task on database +func CreateTask(task *Task) error { + return createTask(x, task) +} + func createTask(e Engine, task *Task) error { _, err := e.Insert(task) return err } -// CreateMigrateTask creates a migrate task -func CreateMigrateTask(doer, u *User, opts base.MigrateOptions) (*Task, error) { - bs, err := json.Marshal(&opts) - if err != nil { - return nil, err - } - - var task = Task{ - DoerID: doer.ID, - OwnerID: u.ID, - Type: structs.TaskTypeMigrateRepo, - Status: structs.TaskStatusQueue, - PayloadContent: string(bs), - } - - if err := createTask(x, &task); err != nil { - return nil, err - } - - repo, err := CreateRepository(doer, u, CreateRepoOptions{ - Name: opts.RepoName, - Description: opts.Description, - OriginalURL: opts.OriginalURL, - GitServiceType: opts.GitServiceType, - IsPrivate: opts.Private, - IsMirror: opts.Mirror, - Status: RepositoryBeingMigrated, - }) - if err != nil { - task.EndTime = timeutil.TimeStampNow() - task.Status = structs.TaskStatusFailed - err2 := task.UpdateCols("end_time", "status") - if err2 != nil { - log.Error("UpdateCols Failed: %v", err2.Error()) - } - return nil, err - } - - task.RepoID = repo.ID - if err = task.UpdateCols("repo_id"); err != nil { - return nil, err - } - - return &task, nil -} - // FinishMigrateTask updates database when migrate task finished func FinishMigrateTask(task *Task) error { task.Status = structs.TaskStatusFinished diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go index 94e81bd9a512..88414e6cadc9 100644 --- a/modules/migrations/gitea.go +++ b/modules/migrations/gitea.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/repository" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -100,7 +101,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate var r *models.Repository if opts.MigrateToRepoID <= 0 { - r, err = models.CreateRepository(g.doer, owner, models.CreateRepoOptions{ + r, err = repo_module.CreateRepository(g.doer, owner, models.CreateRepoOptions{ Name: g.repoName, Description: repo.Description, OriginalURL: repo.OriginalURL, diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go index 3a0ba668c1ad..812649af367c 100644 --- a/modules/repofiles/update.go +++ b/modules/repofiles/update.go @@ -458,7 +458,7 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions) } defer gitRepo.Close() - if err = repo.UpdateSize(); err != nil { + if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { log.Error("Failed to update size for repository: %v", err) } @@ -498,7 +498,7 @@ func PushUpdates(repo *models.Repository, optsList []*PushUpdateOptions) error { if err != nil { return fmt.Errorf("OpenRepository: %v", err) } - if err = repo.UpdateSize(); err != nil { + if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { log.Error("Failed to update size for repository: %v", err) } diff --git a/modules/repository/create.go b/modules/repository/create.go new file mode 100644 index 000000000000..dc96b856d962 --- /dev/null +++ b/modules/repository/create.go @@ -0,0 +1,77 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "fmt" + "os" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +// CreateRepository creates a repository for the user/organization. +func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *models.Repository, err error) { + if !doer.IsAdmin && !u.CanCreateRepo() { + return nil, models.ErrReachLimitOfRepo{ + Limit: u.MaxRepoCreation, + } + } + + repo := &models.Repository{ + OwnerID: u.ID, + Owner: u, + OwnerName: u.Name, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + OriginalURL: opts.OriginalURL, + OriginalServiceType: opts.GitServiceType, + IsPrivate: opts.IsPrivate, + IsFsckEnabled: !opts.IsMirror, + CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, + Status: opts.Status, + IsEmpty: !opts.AutoInit, + } + + err = models.WithTx(func(ctx models.DBContext) error { + if err = models.CreateRepository(ctx, doer, u, repo); err != nil { + return err + } + + // No need for init mirror. + if !opts.IsMirror { + repoPath := models.RepoPath(u.Name, repo.Name) + if err = initRepository(ctx, repoPath, u, repo, opts); err != nil { + if err2 := os.RemoveAll(repoPath); err2 != nil { + log.Error("initRepository: %v", err) + return fmt.Errorf( + "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) + } + return fmt.Errorf("initRepository: %v", err) + } + + // Initialize Issue Labels if selected + if len(opts.IssueLabels) > 0 { + if err = models.InitalizeLabels(ctx, repo.ID, opts.IssueLabels); err != nil { + return fmt.Errorf("initalizeLabels: %v", err) + } + } + + if stdout, err := git.NewCommand("update-server-info"). + SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). + RunInDir(repoPath); err != nil { + log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return fmt.Errorf("CreateRepository(git update-server-info): %v", err) + } + } + return nil + }) + + return repo, err +} diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go new file mode 100644 index 000000000000..53c0b0f30534 --- /dev/null +++ b/modules/repository/create_test.go @@ -0,0 +1,145 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "fmt" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestIncludesAllRepositoriesTeams(t *testing.T) { + assert.NoError(t, models.PrepareTestDatabase()) + + testTeamRepositories := func(teamID int64, repoIds []int64) { + team := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) + assert.NoError(t, team.GetRepositories(), "%s: GetRepositories", team.Name) + assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) + assert.Equal(t, len(repoIds), len(team.Repos), "%s: repo count", team.Name) + for i, rid := range repoIds { + if rid > 0 { + assert.True(t, team.HasRepository(rid), "%s: HasRepository(%d) %d", rid, i) + } + } + } + + // Get an admin user. + user, err := models.GetUserByID(1) + assert.NoError(t, err, "GetUserByID") + + // Create org. + org := &models.User{ + Name: "All repo", + IsActive: true, + Type: models.UserTypeOrganization, + Visibility: structs.VisibleTypePublic, + } + assert.NoError(t, models.CreateOrganization(org, user), "CreateOrganization") + + // Check Owner team. + ownerTeam, err := org.GetOwnerTeam() + assert.NoError(t, err, "GetOwnerTeam") + assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories") + + // Create repos. + repoIds := make([]int64, 0) + for i := 0; i < 3; i++ { + r, err := CreateRepository(user, org, models.CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}) + assert.NoError(t, err, "CreateRepository %d", i) + if r != nil { + repoIds = append(repoIds, r.ID) + } + } + // Get fresh copy of Owner team after creating repos. + ownerTeam, err = org.GetOwnerTeam() + assert.NoError(t, err, "GetOwnerTeam") + + // Create teams and check repositories. + teams := []*models.Team{ + ownerTeam, + { + OrgID: org.ID, + Name: "team one", + Authorize: models.AccessModeRead, + IncludesAllRepositories: true, + }, + { + OrgID: org.ID, + Name: "team 2", + Authorize: models.AccessModeRead, + IncludesAllRepositories: false, + }, + { + OrgID: org.ID, + Name: "team three", + Authorize: models.AccessModeWrite, + IncludesAllRepositories: true, + }, + { + OrgID: org.ID, + Name: "team 4", + Authorize: models.AccessModeWrite, + IncludesAllRepositories: false, + }, + } + teamRepos := [][]int64{ + repoIds, + repoIds, + {}, + repoIds, + {}, + } + for i, team := range teams { + if i > 0 { // first team is Owner. + assert.NoError(t, models.NewTeam(team), "%s: NewTeam", team.Name) + } + testTeamRepositories(team.ID, teamRepos[i]) + } + + // Update teams and check repositories. + teams[3].IncludesAllRepositories = false + teams[4].IncludesAllRepositories = true + teamRepos[4] = repoIds + for i, team := range teams { + assert.NoError(t, models.UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name) + testTeamRepositories(team.ID, teamRepos[i]) + } + + // Create repo and check teams repositories. + org.Teams = nil // Reset teams to allow their reloading. + r, err := CreateRepository(user, org, models.CreateRepoOptions{Name: "repo-last"}) + assert.NoError(t, err, "CreateRepository last") + if r != nil { + repoIds = append(repoIds, r.ID) + } + teamRepos[0] = repoIds + teamRepos[1] = repoIds + teamRepos[4] = repoIds + for i, team := range teams { + testTeamRepositories(team.ID, teamRepos[i]) + } + + // Remove repo and check teams repositories. + assert.NoError(t, models.DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository") + teamRepos[0] = repoIds[1:] + teamRepos[1] = repoIds[1:] + teamRepos[3] = repoIds[1:3] + teamRepos[4] = repoIds[1:] + for i, team := range teams { + testTeamRepositories(team.ID, teamRepos[i]) + } + + // Wipe created items. + for i, rid := range repoIds { + if i > 0 { // first repo already deleted. + assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i) + } + } + assert.NoError(t, models.DeleteOrganization(org), "DeleteOrganization") +} diff --git a/modules/repository/fork.go b/modules/repository/fork.go new file mode 100644 index 000000000000..8953ce9ba48e --- /dev/null +++ b/modules/repository/fork.go @@ -0,0 +1,87 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "fmt" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +// ForkRepository forks a repository +func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc string) (_ *models.Repository, err error) { + forkedRepo, err := oldRepo.GetUserFork(owner.ID) + if err != nil { + return nil, err + } + if forkedRepo != nil { + return nil, models.ErrForkAlreadyExist{ + Uname: owner.Name, + RepoName: oldRepo.FullName(), + ForkName: forkedRepo.FullName(), + } + } + + repo := &models.Repository{ + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, + Name: name, + LowerName: strings.ToLower(name), + Description: desc, + DefaultBranch: oldRepo.DefaultBranch, + IsPrivate: oldRepo.IsPrivate, + IsEmpty: oldRepo.IsEmpty, + IsFork: true, + ForkID: oldRepo.ID, + } + + oldRepoPath := oldRepo.RepoPath() + + err = models.WithTx(func(ctx models.DBContext) error { + if err = models.CreateRepository(ctx, doer, owner, repo); err != nil { + return err + } + + if err = models.IncrementRepoForkNum(ctx, oldRepo.ID); err != nil { + return err + } + + repoPath := models.RepoPath(owner.Name, repo.Name) + if stdout, err := git.NewCommand( + "clone", "--bare", oldRepoPath, repoPath). + SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())). + RunInDirTimeout(10*time.Minute, ""); err != nil { + log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err) + return fmt.Errorf("git clone: %v", err) + } + + if stdout, err := git.NewCommand("update-server-info"). + SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())). + RunInDir(repoPath); err != nil { + log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) + return fmt.Errorf("git update-server-info: %v", err) + } + + if err = models.CreateDelegateHooks(repoPath); err != nil { + return fmt.Errorf("createDelegateHooks: %v", err) + } + return nil + }) + if err != nil { + return nil, err + } + + ctx := models.DefaultDBContext() + if err = repo.UpdateSize(ctx); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + + return repo, models.CopyLFS(ctx, repo, oldRepo) +} diff --git a/modules/repository/fork_test.go b/modules/repository/fork_test.go new file mode 100644 index 000000000000..cb3526bccf69 --- /dev/null +++ b/modules/repository/fork_test.go @@ -0,0 +1,25 @@ +// Copyright 2017 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 repository + +import ( + "testing" + + "code.gitea.io/gitea/models" + "github.com/stretchr/testify/assert" +) + +func TestForkRepository(t *testing.T) { + assert.NoError(t, models.PrepareTestDatabase()) + + // user 13 has already forked repo10 + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) + + fork, err := ForkRepository(user, user, repo, "test", "test") + assert.Nil(t, fork) + assert.Error(t, err) + assert.True(t, models.IsErrForkAlreadyExist(err)) +} diff --git a/modules/repository/generate.go b/modules/repository/generate.go new file mode 100644 index 000000000000..96ce25e59f78 --- /dev/null +++ b/modules/repository/generate.go @@ -0,0 +1,230 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +func generateExpansion(src string, templateRepo, generateRepo *models.Repository) string { + return os.Expand(src, func(key string) string { + switch key { + case "REPO_NAME": + return generateRepo.Name + case "TEMPLATE_NAME": + return templateRepo.Name + case "REPO_DESCRIPTION": + return generateRepo.Description + case "TEMPLATE_DESCRIPTION": + return templateRepo.Description + case "REPO_OWNER": + return generateRepo.OwnerName + case "TEMPLATE_OWNER": + return templateRepo.OwnerName + case "REPO_LINK": + return generateRepo.Link() + case "TEMPLATE_LINK": + return templateRepo.Link() + case "REPO_HTTPS_URL": + return generateRepo.CloneLink().HTTPS + case "TEMPLATE_HTTPS_URL": + return templateRepo.CloneLink().HTTPS + case "REPO_SSH_URL": + return generateRepo.CloneLink().SSH + case "TEMPLATE_SSH_URL": + return templateRepo.CloneLink().SSH + default: + return key + } + }) +} + +func checkGiteaTemplate(tmpDir string) (*models.GiteaTemplate, error) { + gtPath := filepath.Join(tmpDir, ".gitea", "template") + if _, err := os.Stat(gtPath); os.IsNotExist(err) { + return nil, nil + } else if err != nil { + return nil, err + } + + content, err := ioutil.ReadFile(gtPath) + if err != nil { + return nil, err + } + + gt := &models.GiteaTemplate{ + Path: gtPath, + Content: content, + } + + return gt, nil +} + +func generateRepoCommit(repo, templateRepo, generateRepo *models.Repository, tmpDir string) error { + commitTimeStr := time.Now().Format(time.RFC3339) + authorSig := repo.Owner.NewGitSig() + + // Because this may call hooks we should pass in the environment + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+authorSig.Name, + "GIT_AUTHOR_EMAIL="+authorSig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_NAME="+authorSig.Name, + "GIT_COMMITTER_EMAIL="+authorSig.Email, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + + // Clone to temporary path and do the init commit. + templateRepoPath := templateRepo.RepoPath() + if err := git.Clone(templateRepoPath, tmpDir, git.CloneRepoOptions{ + Depth: 1, + }); err != nil { + return fmt.Errorf("git clone: %v", err) + } + + if err := os.RemoveAll(path.Join(tmpDir, ".git")); err != nil { + return fmt.Errorf("remove git dir: %v", err) + } + + // Variable expansion + gt, err := checkGiteaTemplate(tmpDir) + if err != nil { + return fmt.Errorf("checkGiteaTemplate: %v", err) + } + + if err := os.Remove(gt.Path); err != nil { + return fmt.Errorf("remove .giteatemplate: %v", err) + } + + // Avoid walking tree if there are no globs + if len(gt.Globs()) > 0 { + tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" + if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if info.IsDir() { + return nil + } + + base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) + for _, g := range gt.Globs() { + if g.Match(base) { + content, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + if err := ioutil.WriteFile(path, + []byte(generateExpansion(string(content), templateRepo, generateRepo)), + 0644); err != nil { + return err + } + break + } + } + return nil + }); err != nil { + return err + } + } + + if err := git.InitRepository(tmpDir, false); err != nil { + return err + } + + repoPath := repo.RepoPath() + if stdout, err := git.NewCommand("remote", "add", "origin", repoPath). + SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)). + RunInDirWithEnv(tmpDir, env); err != nil { + log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) + return fmt.Errorf("git remote add: %v", err) + } + + return initRepoCommit(tmpDir, repo, repo.Owner) +} + +func generateGitContent(ctx models.DBContext, repo, templateRepo, generateRepo *models.Repository) (err error) { + tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) + if err != nil { + return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) + } + + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + log.Error("RemoveAll: %v", err) + } + }() + + if err = generateRepoCommit(repo, templateRepo, generateRepo, tmpDir); err != nil { + return fmt.Errorf("generateRepoCommit: %v", err) + } + + // re-fetch repo + if repo, err = models.GetRepositoryByIDCtx(ctx, repo.ID); err != nil { + return fmt.Errorf("getRepositoryByID: %v", err) + } + + repo.DefaultBranch = "master" + if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return nil +} + +// GenerateGitContent generates git content from a template repository +func GenerateGitContent(ctx models.DBContext, templateRepo, generateRepo *models.Repository) error { + if err := generateGitContent(ctx, generateRepo, templateRepo, generateRepo); err != nil { + return err + } + + if err := generateRepo.UpdateSize(ctx); err != nil { + return fmt.Errorf("failed to update size for repository: %v", err) + } + + if err := models.CopyLFS(ctx, generateRepo, templateRepo); err != nil { + return fmt.Errorf("failed to copy LFS: %v", err) + } + return nil +} + +// GenerateRepository generates a repository from a template +func GenerateRepository(ctx models.DBContext, doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) { + generateRepo := &models.Repository{ + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + IsPrivate: opts.Private, + IsEmpty: !opts.GitContent || templateRepo.IsEmpty, + IsFsckEnabled: templateRepo.IsFsckEnabled, + TemplateID: templateRepo.ID, + } + + if err = models.CreateRepository(ctx, doer, owner, generateRepo); err != nil { + return nil, err + } + + repoPath := models.RepoPath(owner.Name, generateRepo.Name) + if err = checkInitRepository(repoPath); err != nil { + return generateRepo, err + } + + return generateRepo, nil +} diff --git a/modules/repository/init.go b/modules/repository/init.go new file mode 100644 index 000000000000..a65b33517464 --- /dev/null +++ b/modules/repository/init.go @@ -0,0 +1,214 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + + "github.com/mcuadros/go-version" + "github.com/unknwon/com" +) + +func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, repoPath string, opts models.CreateRepoOptions) error { + commitTimeStr := time.Now().Format(time.RFC3339) + authorSig := repo.Owner.NewGitSig() + + // Because this may call hooks we should pass in the environment + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+authorSig.Name, + "GIT_AUTHOR_EMAIL="+authorSig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_NAME="+authorSig.Name, + "GIT_COMMITTER_EMAIL="+authorSig.Email, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + + // Clone to temporary path and do the init commit. + if stdout, err := git.NewCommand("clone", repoPath, tmpDir). + SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)). + RunInDirWithEnv("", env); err != nil { + log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) + return fmt.Errorf("git clone: %v", err) + } + + // README + data, err := models.GetRepoInitFile("readme", opts.Readme) + if err != nil { + return fmt.Errorf("GetRepoInitFile[%s]: %v", opts.Readme, err) + } + + cloneLink := repo.CloneLink() + match := map[string]string{ + "Name": repo.Name, + "Description": repo.Description, + "CloneURL.SSH": cloneLink.SSH, + "CloneURL.HTTPS": cloneLink.HTTPS, + } + if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), + []byte(com.Expand(string(data), match)), 0644); err != nil { + return fmt.Errorf("write README.md: %v", err) + } + + // .gitignore + if len(opts.Gitignores) > 0 { + var buf bytes.Buffer + names := strings.Split(opts.Gitignores, ",") + for _, name := range names { + data, err = models.GetRepoInitFile("gitignore", name) + if err != nil { + return fmt.Errorf("GetRepoInitFile[%s]: %v", name, err) + } + buf.WriteString("# ---> " + name + "\n") + buf.Write(data) + buf.WriteString("\n") + } + + if buf.Len() > 0 { + if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { + return fmt.Errorf("write .gitignore: %v", err) + } + } + } + + // LICENSE + if len(opts.License) > 0 { + data, err = models.GetRepoInitFile("license", opts.License) + if err != nil { + return fmt.Errorf("GetRepoInitFile[%s]: %v", opts.License, err) + } + + if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { + return fmt.Errorf("write LICENSE: %v", err) + } + } + + return nil +} + +// initRepoCommit temporarily changes with work directory. +func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User) (err error) { + commitTimeStr := time.Now().Format(time.RFC3339) + + sig := u.NewGitSig() + // Because this may call hooks we should pass in the environment + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+sig.Name, + "GIT_AUTHOR_EMAIL="+sig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_NAME="+sig.Name, + "GIT_COMMITTER_EMAIL="+sig.Email, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + + if stdout, err := git.NewCommand("add", "--all"). + SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). + RunInDir(tmpPath); err != nil { + log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) + return fmt.Errorf("git add --all: %v", err) + } + + binVersion, err := git.BinVersion() + if err != nil { + return fmt.Errorf("Unable to get git version: %v", err) + } + + args := []string{ + "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), + "-m", "Initial commit", + } + + if version.Compare(binVersion, "1.7.9", ">=") { + sign, keyID := models.SignInitialCommit(tmpPath, u) + if sign { + args = append(args, "-S"+keyID) + } else if version.Compare(binVersion, "2.0.0", ">=") { + args = append(args, "--no-gpg-sign") + } + } + + if stdout, err := git.NewCommand(args...). + SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). + RunInDirWithEnv(tmpPath, env); err != nil { + log.Error("Failed to commit: %v: Stdout: %s\nError: %v", args, stdout, err) + return fmt.Errorf("git commit: %v", err) + } + + if stdout, err := git.NewCommand("push", "origin", "master"). + SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)). + RunInDirWithEnv(tmpPath, models.InternalPushingEnvironment(u, repo)); err != nil { + log.Error("Failed to push back to master: Stdout: %s\nError: %v", stdout, err) + return fmt.Errorf("git push: %v", err) + } + + return nil +} + +func checkInitRepository(repoPath string) (err error) { + // Somehow the directory could exist. + if com.IsExist(repoPath) { + return fmt.Errorf("checkInitRepository: path already exists: %s", repoPath) + } + + // Init git bare new repository. + if err = git.InitRepository(repoPath, true); err != nil { + return fmt.Errorf("git.InitRepository: %v", err) + } else if err = models.CreateDelegateHooks(repoPath); err != nil { + return fmt.Errorf("createDelegateHooks: %v", err) + } + return nil +} + +// InitRepository initializes README and .gitignore if needed. +func initRepository(ctx models.DBContext, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) { + if err = checkInitRepository(repoPath); err != nil { + return err + } + + // Initialize repository according to user's choice. + if opts.AutoInit { + tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name) + if err != nil { + return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err) + } + + defer os.RemoveAll(tmpDir) + + if err = prepareRepoCommit(ctx, repo, tmpDir, repoPath, opts); err != nil { + return fmt.Errorf("prepareRepoCommit: %v", err) + } + + // Apply changes and commit. + if err = initRepoCommit(tmpDir, repo, u); err != nil { + return fmt.Errorf("initRepoCommit: %v", err) + } + } + + // Re-fetch the repository from database before updating it (else it would + // override changes that were done earlier with sql) + if repo, err = models.GetRepositoryByIDCtx(ctx, repo.ID); err != nil { + return fmt.Errorf("getRepositoryByID: %v", err) + } + + if !opts.AutoInit { + repo.IsEmpty = true + } + + repo.DefaultBranch = "master" + if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return nil +} diff --git a/modules/repository/repo.go b/modules/repository/repo.go index b0b118e03805..bb8cceeadceb 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -116,7 +116,7 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt } } - if err = repo.UpdateSize(); err != nil { + if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { log.Error("Failed to update size for repository: %v", err) } diff --git a/modules/task/task.go b/modules/task/task.go index 416f0c696a99..72f111ecc7c5 100644 --- a/modules/task/task.go +++ b/modules/task/task.go @@ -5,6 +5,7 @@ package task import ( + "encoding/json" "fmt" "code.gitea.io/gitea/models" @@ -12,7 +13,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/queue" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" ) // taskQueue is a global queue of tasks @@ -52,10 +55,56 @@ func handle(data ...queue.Data) { // MigrateRepository add migration repository to task func MigrateRepository(doer, u *models.User, opts base.MigrateOptions) error { - task, err := models.CreateMigrateTask(doer, u, opts) + task, err := CreateMigrateTask(doer, u, opts) if err != nil { return err } return taskQueue.Push(task) } + +// CreateMigrateTask creates a migrate task +func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models.Task, error) { + bs, err := json.Marshal(&opts) + if err != nil { + return nil, err + } + + var task = models.Task{ + DoerID: doer.ID, + OwnerID: u.ID, + Type: structs.TaskTypeMigrateRepo, + Status: structs.TaskStatusQueue, + PayloadContent: string(bs), + } + + if err := models.CreateTask(&task); err != nil { + return nil, err + } + + repo, err := repo_module.CreateRepository(doer, u, models.CreateRepoOptions{ + Name: opts.RepoName, + Description: opts.Description, + OriginalURL: opts.OriginalURL, + GitServiceType: opts.GitServiceType, + IsPrivate: opts.Private, + IsMirror: opts.Mirror, + Status: models.RepositoryBeingMigrated, + }) + if err != nil { + task.EndTime = timeutil.TimeStampNow() + task.Status = structs.TaskStatusFailed + err2 := task.UpdateCols("end_time", "status") + if err2 != nil { + log.Error("UpdateCols Failed: %v", err2.Error()) + } + return nil, err + } + + task.RepoID = repo.ID + if err = task.UpdateCols("repo_id"); err != nil { + return nil, err + } + + return &task, nil +} diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 2990ccd27659..c7959c6db9ae 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -22,8 +22,8 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations" "code.gitea.io/gitea/modules/notification" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/validation" @@ -451,10 +451,10 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { return } - var gitServiceType = structs.PlainGitService + var gitServiceType = api.PlainGitService u, err := url.Parse(remoteAddr) if err == nil && strings.EqualFold(u.Host, "github.com") { - gitServiceType = structs.GithubService + gitServiceType = api.GithubService } var opts = migrations.MigrateOptions{ @@ -483,7 +483,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { opts.Releases = false } - repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + repo, err := repo_module.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, OriginalURL: form.CloneAddr, diff --git a/routers/repo/issue_label.go b/routers/repo/issue_label.go index 02568f77a6dc..98f2dded3180 100644 --- a/routers/repo/issue_label.go +++ b/routers/repo/issue_label.go @@ -35,7 +35,7 @@ func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) { return } - if err := models.InitalizeLabels(ctx.Repo.Repository.ID, form.TemplateName); err != nil { + if err := models.InitalizeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil { if models.IsErrIssueLabelTemplateLoad(err) { originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index d4f97c260064..0c7f3ae3c734 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -217,7 +217,7 @@ func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) { } gitRepo.Close() - if err := m.Repo.UpdateSize(); err != nil { + if err := m.Repo.UpdateSize(models.DefaultDBContext()); err != nil { log.Error("Failed to update size for mirror repository: %v", err) } diff --git a/services/mirror/mirror_test.go b/services/mirror/mirror_test.go index 816ef230fd07..25e499ad788a 100644 --- a/services/mirror/mirror_test.go +++ b/services/mirror/mirror_test.go @@ -38,7 +38,7 @@ func TestRelease_MirrorDelete(t *testing.T) { Releases: false, } - mirrorRepo, err := models.CreateRepository(user, user, models.CreateRepoOptions{ + mirrorRepo, err := repository.CreateRepository(user, user, models.CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, IsPrivate: opts.Private, diff --git a/services/repository/generate.go b/services/repository/generate.go index f7e8ebd8c4db..95e5cdc6c270 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -8,20 +8,21 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" + repo_module "code.gitea.io/gitea/modules/repository" ) // GenerateRepository generates a repository from a template func GenerateRepository(doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) { var generateRepo *models.Repository if err = models.WithTx(func(ctx models.DBContext) error { - generateRepo, err = models.GenerateRepository(ctx, doer, owner, templateRepo, opts) + generateRepo, err = repo_module.GenerateRepository(ctx, doer, owner, templateRepo, opts) if err != nil { return err } // Git Content if opts.GitContent && !templateRepo.IsEmpty { - if err = models.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { + if err = repo_module.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { return err } } diff --git a/services/repository/repository.go b/services/repository/repository.go index 2fb45bb6173d..eea8b352b4b5 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -10,11 +10,12 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" + repo_module "code.gitea.io/gitea/modules/repository" ) // CreateRepository creates a repository for the user/organization. func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) (*models.Repository, error) { - repo, err := models.CreateRepository(doer, owner, opts) + repo, err := repo_module.CreateRepository(doer, owner, opts) if err != nil { if repo != nil { if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil { @@ -31,7 +32,7 @@ func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) ( // ForkRepository forks a repository func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) { - repo, err := models.ForkRepository(doer, u, oldRepo, name, desc) + repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc) if err != nil { if repo != nil { if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { From 497e15fdc28518ab03e2f1114fb112b8c0630e18 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sun, 12 Jan 2020 12:12:40 +0000 Subject: [PATCH 12/14] [skip ci] Updated translations via Crowdin --- options/locale/locale_id-ID.ini | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index cd49c4133a26..ba881c1af418 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -10,6 +10,7 @@ link_account=Tautan Akun register=Daftar website=Situs Web version=Versi +powered_by=Didukung oleh %s page=Halaman template=Contoh language=Bahasa @@ -31,6 +32,10 @@ passcode=Kode Akses u2f_insert_key=Masukkan kunci keamanan anda u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda… u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda +u2f_unsupported_browser=Browser Anda tidak mendukung kunci keamanan U2F. +u2f_error_1=Terdapat kesalahan yang tidak diketahui. Mohon coba lagi. +u2f_error_2=Pastikan menggunakan URL yang benar dan terenkripsi (https://). +u2f_error_4=Kunci keamanan tidak diperbolehkan untuk permintaan ini. Pastikan bahwa kunci ini belum terdaftar sebelumnya. u2f_reload=Muat Ulang repository=Repositori @@ -60,6 +65,8 @@ pull_requests=Tarik Permintaan issues=Masalah cancel=Batal +add=Tambah +add_all=Tambah Semua [startpage] @@ -118,6 +125,7 @@ openid_signup_popup=Aktifkan pendaftaran berdasarkan OpenID. enable_captcha=Aktifkan CAPTCHA enable_captcha_popup=Membutukan CAPTCHA untuk pendaftaran. require_sign_in_view=Anda Harus Login untuk Melihat Halaman +admin_setting_desc=Akun administrator tidak wajib dibuat. Pengguna yang pertama kali mendaftar akan secara otomatis menjadi administrator. admin_title=Pengaturan Akun Admin admin_name=Nama Pengguna Admin admin_password=Kata sandi @@ -128,6 +136,7 @@ test_git_failed=Tidak dapat menguji perintah 'git': %v sqlite3_not_available=Gitea versi ini tidak mendukung SQLite3, Silahkan untuh versi biner resmi dari %s (bukan versi 'gobuild'). invalid_db_setting=Pengaturan basis data tidak valid: %v save_config_failed=Gagal menyimpan konfigurasi: %v +install_success=Selamat datang! Terimakasih telah memilih Gitea. Selamat bersenang-senang dan hati-hati! [home] uname_holder=Nama Pengguna atau Alamat Surel @@ -161,11 +170,17 @@ social_register_helper_msg=Sudah memiliki akun? Hubungkan sekarang! remember_me=Ingat Saya forgot_password_title=Lupa Kata Sandi forgot_password=Lupa kata sandi? +sign_up_now=Butuh akun? Daftar sekarang. +sign_up_successful=Akun berhasil dibuat. confirmation_mail_sent_prompt=Surel konfirmasi baru telah dikirim ke %s. Silakan periksa kotak masuk anda dalam %s ke depan untuk menyelesaikan proses pendaftaran. active_your_account=Aktifkan Akun Anda +account_activated=Akun telah diaktifkan +prohibit_login_desc=Akun Anda tidak diperbolehkan untuk masuk, silakan hubungi admin situs. has_unconfirmed_mail=Hai %s, anda memiliki sebuah alamat surel yang belum dikonfirmasi (%s). Jika anda belum menerima surel konfirmasi atau perlu untuk mengirim ulang yang baru, silakan klik pada tombol di bawah. resend_mail=Klik di sini untuk mengirim ulang surel aktivasi anda email_not_associate=Alamat surel tidak terhubung dengan akun apapun. +send_reset_mail=Kirim Surel Pemulihan Akun +reset_password=Pemulihan Akun verify=Verifikasi scratch_code=Kode coretan use_scratch_code=Gunakan kode coretan @@ -214,11 +229,30 @@ email_error=` bukan alamat surel yang valid. ` url_error=` bukan URL yang valid.` include_error=` harus mengandung substring '%s'.` unknown_error=Kesalahan yang tidak diketahui: +lang_select_error=Pilih bahasa dari daftar. +email_been_used=Alamat email sudah digunakan. +openid_been_used=Alamat OpenID '%s' sudah digunakan. +username_password_incorrect=Nama pengguna atau sandi salah. +password_complexity=Kata sandi tidak memenuhi persyaratan kerumitan: +password_lowercase_one=Sekurang-kurangnya satu karakter kecil +password_uppercase_one=Sekurang-kurangnya satu karakter besar +password_digit_one=Sekurang-kurangnya satu angka +password_special_one=Sekurang-kurangnya satu karater khusus (tanda baca, kurung, kutip, dll.) +enterred_invalid_repo_name=Nama repositori yang Anda masukkan salah. +enterred_invalid_owner_name=Nama pemilik baru salah. +enterred_invalid_password=Kata sandi yang Anda masukkan salah. user_not_exist=Pengguna tidak ada. +last_org_owner=Anda tidak dapat menghapus pengguna terakhir dari tim pemilik. Harus ada setidaknya satu pemilik dalam tim yang diberikan. +cannot_add_org_to_team=Sebuah organisasi tidak dapat ditambahkan sebagai anggota tim. +invalid_ssh_key=Tidak dapat memverifikasi kunci SSH Anda: %s +invalid_gpg_key=Tidak dapat memverifikasi kunci GPG Anda: %s auth_failed=Otentikasi gagal: %v +still_own_repo=Akun anda memiliki satu atau lebih repositori, pindahkan atau transfer terlebih dahulu. +still_has_org=Akun Anda adalah anggota dari satu atau lebih organisasi, tinggalkan terlebih dahulu. +org_still_own_repo=Organisasi ini masih memiliki satu atau lebih repositori; hapus atau transfer terlebih dahulu. target_branch_not_exist=Target cabang tidak ada. From 10055bd2b1d18d3ccbec78cbc213e459ddb75804 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 12 Jan 2020 16:43:44 +0100 Subject: [PATCH 13/14] [API] add GET /orgs endpoint (#9560) * introduce `GET /orgs` * add TEST * show also other VisibleType's * update description * refactor a lot * SearchUserOptions by default return only public --- integrations/api_org_test.go | 14 ++++++++++ models/user.go | 7 ++--- routers/admin/orgs.go | 3 ++- routers/api/v1/admin/org.go | 2 +- routers/api/v1/api.go | 1 + routers/api/v1/org/org.go | 47 ++++++++++++++++++++++++++++++++++ routers/home.go | 14 +++++++--- templates/swagger/v1_json.tmpl | 29 +++++++++++++++++++++ 8 files changed, 108 insertions(+), 9 deletions(-) diff --git a/integrations/api_org_test.go b/integrations/api_org_test.go index 34579aa1ea9c..551da3032694 100644 --- a/integrations/api_org_test.go +++ b/integrations/api_org_test.go @@ -136,3 +136,17 @@ func TestAPIOrgDeny(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) }) } + +func TestAPIGetAll(t *testing.T) { + defer prepareTestEnv(t)() + + req := NewRequestf(t, "GET", "/api/v1/orgs") + resp := MakeRequest(t, req, http.StatusOK) + + var apiOrgList []*api.Organization + DecodeJSON(t, resp, &apiOrgList) + + assert.Len(t, apiOrgList, 7) + assert.Equal(t, "org25", apiOrgList[0].FullName) + assert.Equal(t, "public", apiOrgList[0].Visibility) +} diff --git a/models/user.go b/models/user.go index 9ddd262aed87..dc8ae7e0f8cc 100644 --- a/models/user.go +++ b/models/user.go @@ -1469,7 +1469,7 @@ type SearchUserOptions struct { UID int64 OrderBy SearchOrderBy Page int - Private bool // Include private orgs in search + Visible []structs.VisibleType OwnerID int64 // id of user for visibility calculation PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum IsActive util.OptionalBool @@ -1492,8 +1492,9 @@ func (opts *SearchUserOptions) toConds() builder.Cond { cond = cond.And(keywordCond) } - if !opts.Private { - // user not logged in and so they won't be allowed to see non-public orgs + if len(opts.Visible) > 0 { + cond = cond.And(builder.In("visibility", opts.Visible)) + } else { cond = cond.And(builder.In("visibility", structs.VisibleTypePublic)) } diff --git a/routers/admin/orgs.go b/routers/admin/orgs.go index e0be99872e75..02068d6185e6 100644 --- a/routers/admin/orgs.go +++ b/routers/admin/orgs.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers" ) @@ -25,6 +26,6 @@ func Organizations(ctx *context.Context) { routers.RenderUserSearch(ctx, &models.SearchUserOptions{ Type: models.UserTypeOrganization, PageSize: setting.UI.Admin.OrgPagingNum, - Private: true, + Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, }, tplOrgs) } diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index 1db4e592ff49..ca2ef574f3aa 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -104,7 +104,7 @@ func GetAllOrgs(ctx *context.APIContext) { OrderBy: models.SearchOrderByAlphabetically, Page: ctx.QueryInt("page"), PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")), - Private: true, + Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate}, }) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index fd7f7c05cf9a..86c745017302 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -821,6 +821,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/user/orgs", reqToken(), org.ListMyOrgs) m.Get("/users/:username/orgs", org.ListUserOrgs) m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create) + m.Get("/orgs", org.GetAll) m.Group("/orgs/:orgname", func() { m.Combo("").Get(org.Get). Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit). diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 67770e70aa62..4bcd60a679df 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -66,6 +66,53 @@ func ListUserOrgs(ctx *context.APIContext) { listUserOrgs(ctx, u, ctx.User.IsAdmin) } +// GetAll return list of all public organizations +func GetAll(ctx *context.APIContext) { + // swagger:operation Get /orgs organization orgGetAll + // --- + // summary: Get list of organizations + // produces: + // - application/json + // parameters: + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results, maximum page size is 50 + // type: integer + // responses: + // "200": + // "$ref": "#/responses/OrganizationList" + + vMode := []api.VisibleType{api.VisibleTypePublic} + if ctx.IsSigned { + vMode = append(vMode, api.VisibleTypeLimited) + if ctx.User.IsAdmin { + vMode = append(vMode, api.VisibleTypePrivate) + } + } + + publicOrgs, _, err := models.SearchUsers(&models.SearchUserOptions{ + Type: models.UserTypeOrganization, + OrderBy: models.SearchOrderByAlphabetically, + Page: ctx.QueryInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")), + Visible: vMode, + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err) + return + } + orgs := make([]*api.Organization, len(publicOrgs)) + for i := range publicOrgs { + orgs[i] = convert.ToOrganization(publicOrgs[i]) + } + + ctx.JSON(http.StatusOK, &orgs) +} + // Create api for create organization func Create(ctx *context.APIContext, form api.CreateOrgOption) { // swagger:operation POST /orgs organization orgCreate diff --git a/routers/home.go b/routers/home.go index 773e0f3d6beb..0f59c95705b8 100644 --- a/routers/home.go +++ b/routers/home.go @@ -15,6 +15,7 @@ import ( code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/user" ) @@ -249,7 +250,7 @@ func ExploreUsers(ctx *context.Context) { Type: models.UserTypeIndividual, PageSize: setting.UI.ExplorePagingNum, IsActive: util.OptionalBoolTrue, - Private: true, + Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, }, tplExploreUsers) } @@ -265,12 +266,17 @@ func ExploreOrganizations(ctx *context.Context) { ownerID = ctx.User.ID } - RenderUserSearch(ctx, &models.SearchUserOptions{ + opts := models.SearchUserOptions{ Type: models.UserTypeOrganization, PageSize: setting.UI.ExplorePagingNum, - Private: ctx.User != nil, OwnerID: ownerID, - }, tplExploreOrganizations) + } + if ctx.User != nil { + opts.Visible = []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate} + } else { + opts.Visible = []structs.VisibleType{structs.VisibleTypePublic} + } + RenderUserSearch(ctx, &opts, tplExploreOrganizations) } // ExploreCode render explore code page diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index ddf144066d55..de774de9fbe0 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -606,6 +606,35 @@ } }, "/orgs": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Get list of organizations", + "operationId": "orgGetAll", + "parameters": [ + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results, maximum page size is 50", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/OrganizationList" + } + } + }, "post": { "consumes": [ "application/json" From 625057c6747e8abf50a958788f9a129ebd594176 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sun, 12 Jan 2020 16:34:48 +0000 Subject: [PATCH 14/14] [skip ci] Updated translations via Crowdin --- options/locale/locale_id-ID.ini | 54 +++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index ba881c1af418..1a0279944935 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -30,12 +30,16 @@ twofa_scratch=Kode Awal Dua Faktor passcode=Kode Akses u2f_insert_key=Masukkan kunci keamanan anda +u2f_sign_in=Tekan tombol pada kunci keamanan anda. Jika kunci keamanan anda tidak memiliki tombol, masukkan kembali. u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda… u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda +u2f_error=Tidak dapat membaca kunci keamanan Anda. u2f_unsupported_browser=Browser Anda tidak mendukung kunci keamanan U2F. u2f_error_1=Terdapat kesalahan yang tidak diketahui. Mohon coba lagi. u2f_error_2=Pastikan menggunakan URL yang benar dan terenkripsi (https://). +u2f_error_3=Server tidak bisa memproses permintaan anda. u2f_error_4=Kunci keamanan tidak diperbolehkan untuk permintaan ini. Pastikan bahwa kunci ini belum terdaftar sebelumnya. +u2f_error_5=Waktu habis sebelum kunci Anda dapat dibaca. Mohon muat ulang halaman ini dan coba lagi. u2f_reload=Muat Ulang repository=Repositori @@ -63,27 +67,48 @@ forks=Garpu activities=Aktivitas pull_requests=Tarik Permintaan issues=Masalah +milestones=Tonggak cancel=Batal add=Tambah add_all=Tambah Semua +remove=Buang +remove_all=Buang Semua +write=Tulis +preview=Pratinjau +loading=Memuat… [startpage] +app_desc=Sebuah layanan hosting Git sendiri yang tanpa kesulitan +install=Mudah dipasang +install_desc=Cukup jalankan program biner yang sesuai dengan sistem operasi Anda. Atau jalankan Gitea dengan Docker atau Vagrant, atau install dari paket. +platform=Lintas platform +platform_desc=Gitea bisa digunakan di mana Go bisa dijalankan: Windows, macOS, Linux, ARM, dll. Silahkan pilih yang Anda suka! +lightweight=Ringan +lightweight_desc=Gitea hanya membutuhkan persyaratan minimal dan bisa berjalan pada Raspberry Pi yang murah. Bisa menghemat listrik! +license=Sumber Terbuka +license_desc="Go get" (Dapatkan kode sumber dari) code.gitea.io/gitea! Mari bergabung dengan berkontribusi untuk membuat proyek ini lebih baik. Jangan malu untuk menjadi kontributor! [install] install=Pemasangan title=Konfigurasi Awal +docker_helper=Jika Anda menjalankan Gitea di dalam Docker, baca dokumentasi sebelum mengubah pengaturan. +requite_db_desc=Gitea memerlukan MySQL, PostgreSQL, MSSQL atau SQLite3. db_title=Pengaturan Basis Data db_type=Tipe Basis Data host=Host user=Nama Pengguna password=Kata Sandi db_name=Nama Basis Data +db_helper=Untuk pengguna MySQL: Mohon gunakan mesin penyimpanan InnoDB, dan jika Anda menggunakan enkoding "utf8mb4", versi InnoDB Anda harus diatas 5.6. ssl_mode=SSL +charset=Jenis karakter path=Jalur +sqlite_helper=Jalur berkas untuk basis data SQLite3 atau TiDB.
Masukkan path absolut jika anda menjalankan Gitea sebagai layanan. no_admin_and_disable_registration=Anda tidak dapat menonaktifkan pendaftaran tanpa membuat akun admin. err_empty_admin_password=Sandi administrator tidak boleh kosong. +err_empty_admin_email=Email administrator tidak boleh kosong. general_title=Pengaturan Umum app_name=Judul Situs @@ -118,13 +143,18 @@ server_service_title=Server dan Pengaturan Layanan Pihak Ketiga offline_mode=Aktifkan Mode Lokal offline_mode_popup=Non-aktifkan jaringan pengiriman konten dari pihak ketiga dan layani semua sumber daya secara lokal. disable_gravatar=Non-aktifkan Gravatar +federated_avatar_lookup=Aktifkan Avatar Terfederasi federated_avatar_lookup_popup=Mengaktifkan pencarian avatar federasi menggunakan Libravatar. +disable_registration_popup=Nonaktifkan pendaftaran oleh pengguna. Hanya admin yang dapat membuat akun pengguna baru. +allow_only_external_registration_popup=Perbolehkan Pendaftaran Hanya Melalui Layanan External openid_signin=Aktifkan Login OpenID +openid_signin_popup=Aktifkan masuk pengguna lewat OpenID. openid_signup=Aktifkan Pendaftaran OpenID openid_signup_popup=Aktifkan pendaftaran berdasarkan OpenID. enable_captcha=Aktifkan CAPTCHA enable_captcha_popup=Membutukan CAPTCHA untuk pendaftaran. require_sign_in_view=Anda Harus Login untuk Melihat Halaman +require_sign_in_view_popup=Batasi akses halaman hanya pada pengguna yang masuk. Pengunjung hanya dapat melihat halaman masuk dan pendaftaran. admin_setting_desc=Akun administrator tidak wajib dibuat. Pengguna yang pertama kali mendaftar akan secara otomatis menjadi administrator. admin_title=Pengaturan Akun Admin admin_name=Nama Pengguna Admin @@ -181,25 +211,32 @@ resend_mail=Klik di sini untuk mengirim ulang surel aktivasi anda email_not_associate=Alamat surel tidak terhubung dengan akun apapun. send_reset_mail=Kirim Surel Pemulihan Akun reset_password=Pemulihan Akun +password_too_short=Panjang kata sandi tidak boleh kurang dari %d karakter. verify=Verifikasi scratch_code=Kode coretan use_scratch_code=Gunakan kode coretan twofa_scratch_used=Anda telah menggunakan kode coretan anda. Anda telah dialihkan ke halaman pengaturan dua-faktor jadi anda boleh menghapus pendaftaran perangkat anda atau menghasilkan kode coretan yang baru. +twofa_passcode_incorrect=Kata sandi Anda salah. Jika Anda salah tempatkan perangkat Anda, gunakan kode gosok Anda untuk masuk. twofa_scratch_token_incorrect=Kode coretan anda tidak tepat. login_openid=OpenID openid_connect_submit=Sambungkan openid_connect_title=Sambungkan ke akun yang sudah ada openid_register_title=Buat akun baru +openid_signin_desc=Masukkan URI OpenID Anda. Misalnya: https://anne.me, bob.openid.org.cn, atau gnusocial.net/carry. +email_domain_blacklisted=Anda tidak dapat mendaftar dengan alamat email. +authorize_application=Izinkan aplikasi [mail] activate_account=Silakan aktifkan akun anda activate_email=Verifikasi alamat surel anda +reset_password=Pulihkan akun Anda register_success=Pendaftaran berhasil register_notify=Selamat Datang di Gitea [modal] yes=Ya no=Tidak +modify=Perbarui [form] UserName=Nama Pengguna @@ -248,6 +285,7 @@ cannot_add_org_to_team=Sebuah organisasi tidak dapat ditambahkan sebagai anggota invalid_ssh_key=Tidak dapat memverifikasi kunci SSH Anda: %s invalid_gpg_key=Tidak dapat memverifikasi kunci GPG Anda: %s +unable_verify_ssh_key=Tidak dapat memverifikasi kunci SSH; periksa kembali bila ada kesalahan. auth_failed=Otentikasi gagal: %v still_own_repo=Akun anda memiliki satu atau lebih repositori, pindahkan atau transfer terlebih dahulu. @@ -257,34 +295,48 @@ org_still_own_repo=Organisasi ini masih memiliki satu atau lebih repositori; hap target_branch_not_exist=Target cabang tidak ada. [user] +change_avatar=Ganti avatar anda… join_on=Telah bergabung di repositories=Repositori activity=Aktivitas Publik followers=Pengikut +starred=Repositori Terbintang following=Mengikuti follow=Ikuti unfollow=Berhenti Mengikuti +heatmap.loading=Memuat Peta Panas… +user_bio=Biografi form.name_reserved=Nama pengguna '%s' dicadangkan. +form.name_pattern_not_allowed=Pola '%s' tidak diperbolehkan dalam nama pengguna. [settings] profile=Profil +account=Akun password=Kata Sandi security=Keamanan avatar=Avatar ssh_gpg_keys=Kunci SSH / GPG social=Akun Sosial +applications=Aplikasi +orgs=Kelola organisasi repos=Repositori delete=Hapus Akun twofa=Otentikasi Dua-Faktor +account_link=Akun Tertaut +organization=Organisasi uid=Uid +u2f=Kunci keamanan public_profile=Profil Publik +profile_desc=Alamat email Anda akan digunakan untuk notifikasi dan operasi lainnya. +password_username_disabled=Pengguna non-lokal tidak diizinkan untuk mengubah nama pengguna mereka. Silakan hubungi administrator sistem anda untuk lebih lanjut. full_name=Nama Lengkap website=Situs Web location=Lokasi update_profile=Perbarui Profil update_profile_success=Profil anda telah diperbarui. +change_username_prompt=Catatan: Perubahan nama pengguna juga mengubah URL akun Anda. continue=Lanjutkan cancel=Batalkan @@ -352,6 +404,7 @@ confirm_delete_account=Konfirmasi Penghapusan owner=Pemilik repo_name=Nama Repositori visibility=Jarak pandang +clone_helper=Butuh bantuan kloning? Kunjungi Bantuan. fork_repo=Cabang Gudang penyimpanan fork_from=Cabang Dari repo_desc=Deskripsi @@ -542,6 +595,7 @@ pulls.new=Permintaan Tarik Baru pulls.filter_branch=Penyaringan cabang pulls.no_results=Hasil tidak ditemukan. pulls.create=Buat Permintaan Tarik +pulls.title_desc=ingin menggabungkan komit %[1]d dari %[2]s menuju %[3]s pulls.merged_title_desc=commit %[1]d telah digabungkan dari %[2]s menjadi %[3]s %[4]s pulls.tab_conversation=Percakapan pulls.tab_commits=Melakukan