From 172ff44d00ede290787cc1b5058d0157f5ee52b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Thu, 8 Jun 2017 13:06:34 +0800 Subject: supplement traditional chinese in hong kong translation Fix #33442 --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 674 ++++++++++++++++++++++++++++- locale/zh_HK/gitlab.po.time_stamp | 1 + 3 files changed, 656 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index fd0bcd988c5..9d4cfeea810 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"ByAuthor|by":["作者:"],"Commit":["提交"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Deploy":["部署"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Pipeline Health":["流水線健康指標"],"ProjectLifecycle|Stage":["項目生命週期"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Showing %d event":["顯示 %d 個事件"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 17:36+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-07 10:18-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"About auto deploy":["關於自動部署"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫是只讀的"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml 模板並提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Changelog":["變更日誌"],"Charts":["圖表"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"CreateNewFork|Fork":["派生"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Deploy":["部署"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadSource|Download":["下載"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"ForkedFromProjectPath|Forked from":["離開"],"Forks":["派生"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到您的派生項目"],"GoToYourFork|Fork":["跳轉到您的"],"Home":["主頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["禁用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加一個 SSH 公鑰"],"New Issue":["新議題"],"New branch":["新分支"],"New directory":["新目錄"],"New file":["新文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["禁用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OpenedNDaysAgo|Opened":["開始於"],"Pipeline Health":["流水線健康指標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["訪問項目必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從您的項目設置中生成新的導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目主頁"],"ProjectFeature|Disabled":["禁用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["項目生命週期"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Set a password on your account to pull or push via %{protocol}":["為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n妳確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只会收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"committed":["提交"],"day":["天"],"notification emails":["通知郵件"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 6d56b2897fa..4d8ceac381e 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,30 +1,180 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the gitlab package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy +# Huang Tao , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-04 19:24-0500\n" -"PO-Revision-Date: 2017-05-04 19:24-0500\n" -"Last-Translator: HuangTao , 2017\n" -"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)\n" +"POT-Creation-Date: 2017-06-07 17:36+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_HK\n" +"PO-Revision-Date: 2017-06-07 10:18-0400\n" +"Last-Translator: Huang Tao \n" +"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" +"Language: zh-HK\n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Zanata 3.9.6\n" + +msgid "About auto deploy" +msgstr "關於自動部署" + +msgid "Activity" +msgstr "活動" + +msgid "Add Changelog" +msgstr "添加變更日誌" + +msgid "Add Contribution guide" +msgstr "添加貢獻指南" + +msgid "Add License" +msgstr "添加許可證" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。" + +msgid "Add new directory" +msgstr "添加新目錄" + +msgid "Archived project! Repository is read-only" +msgstr "歸檔項目!存儲庫是只讀的" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "分支" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml " +"模板並提交更改。%{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "分支" msgid "ByAuthor|by" msgstr "作者:" +msgid "CI configuration" +msgstr "CI 配置" + +msgid "Changelog" +msgstr "變更日誌" + +msgid "Charts" +msgstr "圖表" + +msgid "CiStatusLabel|canceled" +msgstr "已取消" + +msgid "CiStatusLabel|created" +msgstr "已創建" + +msgid "CiStatusLabel|failed" +msgstr "已失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動操作" + +msgid "CiStatusLabel|passed" +msgstr "已通過" + +msgid "CiStatusLabel|passed with warnings" +msgstr "已通過但有警告" + +msgid "CiStatusLabel|pending" +msgstr "等待中" + +msgid "CiStatusLabel|skipped" +msgstr "已跳過" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "等待手動操作" + +msgid "CiStatusText|blocked" +msgstr "已阻塞" + +msgid "CiStatusText|canceled" +msgstr "已取消" + +msgid "CiStatusText|created" +msgstr "已創建" + +msgid "CiStatusText|failed" +msgstr "已失敗" + +msgid "CiStatusText|manual" +msgstr "待手動" + +msgid "CiStatusText|passed" +msgstr "已通過" + +msgid "CiStatusText|pending" +msgstr "等待中" + +msgid "CiStatusText|skipped" +msgstr "已跳過" + +msgid "CiStatus|running" +msgstr "運行中" + msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "CommitMessage|Add %{file_name}" +msgstr "添加 %{file_name}" + +msgid "Commits" +msgstr "提交" + +msgid "Commits|History" +msgstr "歷史" + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "貢獻指南" + +msgid "Contributors" +msgstr "貢獻者" + +msgid "Copy URL to clipboard" +msgstr "複製URL到剪貼板" + +msgid "Copy commit SHA to clipboard" +msgstr "複製提交 SHA 到剪貼板" + +msgid "Create New Directory" +msgstr "創建新目錄" + +msgid "Create directory" +msgstr "創建目錄" + +msgid "Create empty bare repository" +msgstr "創建空的存儲庫" + +msgid "Create merge request" +msgstr "創建合併請求" + +msgid "CreateNewFork|Fork" +msgstr "派生" + +msgid "Custom notification events" +msgstr "自定義通知事件" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}." + +msgid "Cycle Analytics" +msgstr "週期分析" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -55,25 +205,97 @@ msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" +msgid "Directory name" +msgstr "目錄名稱" + +msgid "Don't show again" +msgstr "不再顯示" + +msgid "Download tar" +msgstr "下載 tar" + +msgid "Download tar.bz2" +msgstr "下載 tar.bz2" + +msgid "Download tar.gz" +msgstr "下載 tar.gz" + +msgid "Download zip" +msgstr "下載 zip" + +msgid "DownloadArtifacts|Download" +msgstr "下載" + +msgid "DownloadSource|Download" +msgstr "下載" + +msgid "Files" +msgstr "文件" + +msgid "Find by path" +msgstr "按路徑查找" + +msgid "Find file" +msgstr "查找文件" + msgid "FirstPushedBy|First" msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" +msgid "ForkedFromProjectPath|Forked from" +msgstr "離開" + +msgid "Forks" +msgstr "派生" + msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" +msgid "Go to your fork" +msgstr "跳轉到您的派生項目" + +msgid "GoToYourFork|Fork" +msgstr "跳轉到您的" + +msgid "Home" +msgstr "主頁" + +msgid "Housekeeping successfully started" +msgstr "已開始維護" + +msgid "Import repository" +msgstr "導入存儲庫" + msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "LFSStatus|Disabled" +msgstr "禁用" + +msgid "LFSStatus|Enabled" +msgstr "啟用" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最後 %d 天" +msgid "Last Update" +msgstr "最後更新" + +msgid "Last commit" +msgstr "最後提交" + +msgid "Leave group" +msgstr "離開群組" + +msgid "Leave project" +msgstr "離開項目" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "最多顯示 %d 個事件" @@ -81,28 +303,168 @@ msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "添加一個 SSH 公鑰" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新議題" +msgid "New branch" +msgstr "新分支" + +msgid "New directory" +msgstr "新目錄" + +msgid "New file" +msgstr "新文件" + +msgid "New issue" +msgstr "新議題" + +msgid "New merge request" +msgstr "新合併請求" + +msgid "New snippet" +msgstr "新代碼片段" + +msgid "New tag" +msgstr "新標籤" + +msgid "No repository" +msgstr "沒有存儲庫" + msgid "Not available" msgstr "不可用" msgid "Not enough data" msgstr "數據不足" +msgid "Notification events" +msgstr "通知事件" + +msgid "NotificationEvent|Close issue" +msgstr "關閉議題" + +msgid "NotificationEvent|Close merge request" +msgstr "關閉合併請求" + +msgid "NotificationEvent|Failed pipeline" +msgstr "流水線失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "合併請求被合併" + +msgid "NotificationEvent|New issue" +msgstr "新議題" + +msgid "NotificationEvent|New merge request" +msgstr "新合併請求" + +msgid "NotificationEvent|New note" +msgstr "新評論" + +msgid "NotificationEvent|Reassign issue" +msgstr "重新指派議題" + +msgid "NotificationEvent|Reassign merge request" +msgstr "重新指派合併請求" + +msgid "NotificationEvent|Reopen issue" +msgstr "重新打開議題" + +msgid "NotificationEvent|Successful pipeline" +msgstr "流水線成功完成" + +msgid "NotificationLevel|Custom" +msgstr "自定義" + +msgid "NotificationLevel|Disabled" +msgstr "禁用" + +msgid "NotificationLevel|Global" +msgstr "全局" + +msgid "NotificationLevel|On mention" +msgstr "提及" + +msgid "NotificationLevel|Participate" +msgstr "參與" + +msgid "NotificationLevel|Watch" +msgstr "關注" + msgid "OpenedNDaysAgo|Opened" msgstr "開始於" msgid "Pipeline Health" msgstr "流水線健康指標" +msgid "Project '%{project_name}' queued for deletion." +msgstr "項目 '%{project_name}' 已進入刪除隊列。" + +msgid "Project '%{project_name}' was successfully created." +msgstr "項目 '%{project_name}' 已創建完成。" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "項目 '%{project_name}' 已更新完成。" + +msgid "Project '%{project_name}' will be deleted." +msgstr "項目 '%{project_name}' 已刪除。" + +msgid "Project access must be granted explicitly to each user." +msgstr "訪問項目必須明確授權給每個用戶。" + +msgid "Project export could not be deleted." +msgstr "無法刪除項目導出。" + +msgid "Project export has been deleted." +msgstr "項目導出已刪除。" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "項目導出鏈接已過期。請從您的項目設置中生成新的導出。" + +msgid "Project export started. A download link will be sent by email." +msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。" + +msgid "Project home" +msgstr "項目主頁" + +msgid "ProjectFeature|Disabled" +msgstr "禁用" + +msgid "ProjectFeature|Everyone with access" +msgstr "任何人都可訪問" + +msgid "ProjectFeature|Only team members" +msgstr "只有團隊成員" + +msgid "ProjectFileTree|Name" +msgstr "名稱" + +msgid "ProjectLastActivity|Never" +msgstr "從未" + msgid "ProjectLifecycle|Stage" msgstr "項目生命週期" +msgid "ProjectNetworkGraph|Graph" +msgstr "分支圖" + msgid "Read more" msgstr "了解更多" +msgid "Readme" +msgstr "自述文件" + +msgid "RefSwitcher|Branches" +msgstr "分支" + +msgid "RefSwitcher|Tags" +msgstr "標籤" + msgid "Related Commits" msgstr "相關的提交" @@ -121,10 +483,56 @@ msgstr "相關的合併請求" msgid "Related Merged Requests" msgstr "相關已合併的合並請求" +msgid "Remind later" +msgstr "稍後提醒" + +msgid "Remove project" +msgstr "刪除項目" + +msgid "Request Access" +msgstr "申請訪問" + +msgid "Search branches and tags" +msgstr "搜索分支和標籤" + +msgid "Select Archive Format" +msgstr "選擇下載格式" + +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。" + +msgid "Set up CI" +msgstr "設置 CI" + +msgid "Set up Koding" +msgstr "設置 Koding" + +msgid "Set up auto deploy" +msgstr "設置自動部署" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "設置密碼" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" +msgid "Source code" +msgstr "源代碼" + +msgid "StarProject|Star" +msgstr "星標" + +msgid "Switch branch/tag" +msgstr "切換分支/標籤" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "標籤" + +msgid "Tags" +msgstr "標籤" + msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " @@ -134,6 +542,9 @@ msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。 msgid "The collection of events added to the data gathered for that stage." msgstr "與該階段相關的事件。" +msgid "The fork relationship has been removed." +msgstr "派生關係已被刪除。" + msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -145,16 +556,25 @@ msgstr "項目生命週期中的各個階段。" msgid "" "The planning stage shows the time from the previous step to pushing your " -"first commit. This time will be added automatically once you push your first" -" commit." +"first commit. This time will be added automatically once you push your first " +"commit." msgstr "計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。" msgid "" -"The production stage shows the total time it takes between creating an issue" -" and deploying the code to production. The data will be automatically added " +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " "once you have completed the full idea to production cycle." msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。" +msgid "The project can be accessed by any logged in user." +msgstr "該項目允許已登錄的用戶訪問。" + +msgid "The project can be accessed without any authentication." +msgstr "該項目允許任何人訪問。" + +msgid "The repository for this project does not exist." +msgstr "此項目的存儲庫不存在。" + msgid "" "The review stage shows the time from creating the merge request to merging " "it. The data will automatically be added after you merge your first merge " @@ -163,25 +583,30 @@ msgstr "評審階段概述了從創建合並請求到合併的時間。當創建 msgid "" "The staging stage shows the time between merging the MR and deploying code " -"to the production environment. The data will be automatically added once you" -" deploy to production for the first time." +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." msgstr "預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" msgid "" "The testing stage shows the time GitLab CI takes to run every pipeline for " "the related merge request. The data will automatically be added after your " "first pipeline finishes running." -msgstr "測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" +msgstr "測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" msgid "The time taken by each data entry gathered by that stage." msgstr "該階段每條數據所花的時間" msgid "" "The value lying at the midpoint of a series of observed values. E.g., " -"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 " -"= 6." +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。" + msgid "Time before an issue gets scheduled" msgstr "議題被列入日程表的時間" @@ -194,6 +619,129 @@ msgstr "從創建合併請求到被合並或關閉的時間" msgid "Time until first merge request" msgstr "創建第壹個合併請求之前的時間" +msgid "Timeago|%s days ago" +msgstr "%s 天前" + +msgid "Timeago|%s days remaining" +msgstr "剩餘 %s 天" + +msgid "Timeago|%s hours remaining" +msgstr "剩餘 %s 小時" + +msgid "Timeago|%s minutes ago" +msgstr "%s 分鐘前" + +msgid "Timeago|%s minutes remaining" +msgstr "剩餘 %s 分鐘" + +msgid "Timeago|%s months ago" +msgstr "%s 個月前" + +msgid "Timeago|%s months remaining" +msgstr "剩餘 %s 月" + +msgid "Timeago|%s seconds remaining" +msgstr "剩餘 %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr "%s 星期前" + +msgid "Timeago|%s weeks remaining" +msgstr "剩餘 %s 星期" + +msgid "Timeago|%s years ago" +msgstr "%s 年前" + +msgid "Timeago|%s years remaining" +msgstr "剩餘 %s 年" + +msgid "Timeago|1 day remaining" +msgstr "剩餘 1 天" + +msgid "Timeago|1 hour remaining" +msgstr "剩餘 1 小時" + +msgid "Timeago|1 minute remaining" +msgstr "剩餘 1 分鐘" + +msgid "Timeago|1 month remaining" +msgstr "剩餘 1 個月" + +msgid "Timeago|1 week remaining" +msgstr "剩餘 1 星期" + +msgid "Timeago|1 year remaining" +msgstr "剩餘 1 年" + +msgid "Timeago|Past due" +msgstr "逾期" + +msgid "Timeago|a day ago" +msgstr "1 天前" + +msgid "Timeago|a month ago" +msgstr "1 個月前" + +msgid "Timeago|a week ago" +msgstr "1 星期前" + +msgid "Timeago|a while" +msgstr "剛剛" + +msgid "Timeago|a year ago" +msgstr "1 年前" + +msgid "Timeago|about %s hours ago" +msgstr "大約 %s 小時前" + +msgid "Timeago|about a minute ago" +msgstr "大約 1 分鐘前" + +msgid "Timeago|about an hour ago" +msgstr "大約 1 小時前" + +msgid "Timeago|in %s days" +msgstr "在 %s 天" + +msgid "Timeago|in %s hours" +msgstr "在 %s 小時" + +msgid "Timeago|in %s minutes" +msgstr "在 %s 分鐘" + +msgid "Timeago|in %s months" +msgstr "在 %s 個月" + +msgid "Timeago|in %s seconds" +msgstr "在 %s 秒" + +msgid "Timeago|in %s weeks" +msgstr "在 %s 星期" + +msgid "Timeago|in %s years" +msgstr "在 %s 年" + +msgid "Timeago|in 1 day" +msgstr "在 1 天" + +msgid "Timeago|in 1 hour" +msgstr "在 1 小時" + +msgid "Timeago|in 1 minute" +msgstr "在 1 分鐘" + +msgid "Timeago|in 1 month" +msgstr "在 1 月" + +msgid "Timeago|in 1 week" +msgstr "在 1 星期" + +msgid "Timeago|in 1 year" +msgstr "在 1 年" + +msgid "Timeago|less than a minute ago" +msgstr "不到 1 分鐘前" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "小時" @@ -211,15 +759,101 @@ msgstr "總時間" msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" +msgid "Unstar" +msgstr "取消" + +msgid "Upload New File" +msgstr "上傳新文件" + +msgid "Upload file" +msgstr "上傳文件" + +msgid "Use your global notification setting" +msgstr "使用全局通知設置" + +msgid "VisibilityLevel|Internal" +msgstr "內部" + +msgid "VisibilityLevel|Private" +msgstr "私有" + +msgid "VisibilityLevel|Public" +msgstr "公開" + msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" msgid "We don't have enough data to show this stage." msgstr "該階段的數據不足,無法顯示。" +msgid "Withdraw Access Request" +msgstr "取消訪問請求" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "您將要刪除 %{project_name_with_namespace}。\n" +"已刪除的項目無法恢復!\n" +"妳確定繼續嗎?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?" + +msgid "You can only add files when you are on a branch" +msgstr "您只能在分支上添加文件" + +msgid "You must sign in to star a project" +msgstr "您必須登錄才能對項目加星標" + msgid "You need permission." msgstr "您需要相關的權限。" +msgid "You will not get any notifications via email" +msgstr "您將不會收到任何通知郵件" + +msgid "You will only receive notifications for the events you choose" +msgstr "您只會收到您選擇的事件通知" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "您只會收到您參與的主題的通知" + +msgid "You will receive notifications for any activity" +msgstr "您將不會收到任何通知" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "您只会收到評論中提及(@)您的通知" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。" + +msgid "Your name" +msgstr "您的名字" + +msgid "committed" +msgstr "提交" + msgid "day" msgid_plural "days" msgstr[0] "天" + +msgid "notification emails" +msgstr "通知郵件" + diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp index e69de29bb2d..0519ecba6ea 100644 --- a/locale/zh_HK/gitlab.po.time_stamp +++ b/locale/zh_HK/gitlab.po.time_stamp @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.1 From 6f12c372f5856390dabf178c25c417b6cb34d3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Thu, 8 Jun 2017 13:21:02 +0800 Subject: add changelog of supplement traditional chinese in hong kong translation --- ...upplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml new file mode 100644 index 00000000000..a5eb71b63c6 --- /dev/null +++ b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page +merge_request: 11995 +author:Huang Tao -- cgit v1.2.1 From 625b1303dce45108606e3cc4491344950d7318bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 09:55:50 +0800 Subject: optimize translation based on comments --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 699 ++++------------------------- locale/zh_HK/gitlab.po.time_stamp | 1 - 3 files changed, 81 insertions(+), 621 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 9d4cfeea810..fcd5795c8bb 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 17:36+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-07 10:18-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"About auto deploy":["關於自動部署"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫是只讀的"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml 模板並提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Changelog":["變更日誌"],"Charts":["圖表"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"CreateNewFork|Fork":["派生"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Deploy":["部署"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadSource|Download":["下載"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"ForkedFromProjectPath|Forked from":["離開"],"Forks":["派生"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到您的派生項目"],"GoToYourFork|Fork":["跳轉到您的"],"Home":["主頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["禁用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加一個 SSH 公鑰"],"New Issue":["新議題"],"New branch":["新分支"],"New directory":["新目錄"],"New file":["新文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["禁用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OpenedNDaysAgo|Opened":["開始於"],"Pipeline Health":["流水線健康指標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["訪問項目必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從您的項目設置中生成新的導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目主頁"],"ProjectFeature|Disabled":["禁用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["項目生命週期"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Set a password on your account to pull or push via %{protocol}":["為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n妳確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只会收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"committed":["提交"],"day":["天"],"notification emails":["通知郵件"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-09 01:09-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"ByAuthor|by":["作者:"],"Cancel":["取消"],"Commit":["提交"],"Cron Timezone":["Cron 時區"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Filter":["過濾"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":["所有者"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"ProjectLifecycle|Stage":["階段"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Showing %d event":["顯示 %d 個事件"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 4d8ceac381e..4b02ba19305 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,179 +1,39 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gitlab package. +# FIRST AUTHOR , YEAR. # Huang Tao , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 17:36+0200\n" +"POT-Creation-Date: 2017-06-07 21:22+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-07 10:18-0400\n" +"PO-Revision-Date: 2017-06-09 01:09-0400\n" "Last-Translator: Huang Tao \n" -"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" +"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/" +"teams/75177/zh_HK/)\n" "Language: zh-HK\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Zanata 3.9.6\n" -msgid "About auto deploy" -msgstr "關於自動部署" - -msgid "Activity" -msgstr "活動" - -msgid "Add Changelog" -msgstr "添加變更日誌" - -msgid "Add Contribution guide" -msgstr "添加貢獻指南" - -msgid "Add License" -msgstr "添加許可證" - -msgid "Add an SSH key to your profile to pull or push via SSH." -msgstr "添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。" - -msgid "Add new directory" -msgstr "添加新目錄" - -msgid "Archived project! Repository is read-only" -msgstr "歸檔項目!存儲庫是只讀的" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "分支" - -msgid "" -"Branch %{branch_name} was created. To set up auto deploy, " -"choose a GitLab CI Yaml template and commit your changes. " -"%{link_to_autodeploy_doc}" -msgstr "" -"分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml " -"模板並提交更改。%{link_to_autodeploy_doc}" - -msgid "Branches" -msgstr "分支" +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "您確定要刪除此流水線計劃嗎?" msgid "ByAuthor|by" msgstr "作者:" -msgid "CI configuration" -msgstr "CI 配置" - -msgid "Changelog" -msgstr "變更日誌" - -msgid "Charts" -msgstr "圖表" - -msgid "CiStatusLabel|canceled" -msgstr "已取消" - -msgid "CiStatusLabel|created" -msgstr "已創建" - -msgid "CiStatusLabel|failed" -msgstr "已失敗" - -msgid "CiStatusLabel|manual action" -msgstr "手動操作" - -msgid "CiStatusLabel|passed" -msgstr "已通過" - -msgid "CiStatusLabel|passed with warnings" -msgstr "已通過但有警告" - -msgid "CiStatusLabel|pending" -msgstr "等待中" - -msgid "CiStatusLabel|skipped" -msgstr "已跳過" - -msgid "CiStatusLabel|waiting for manual action" -msgstr "等待手動操作" - -msgid "CiStatusText|blocked" -msgstr "已阻塞" - -msgid "CiStatusText|canceled" -msgstr "已取消" - -msgid "CiStatusText|created" -msgstr "已創建" - -msgid "CiStatusText|failed" -msgstr "已失敗" - -msgid "CiStatusText|manual" -msgstr "待手動" - -msgid "CiStatusText|passed" -msgstr "已通過" - -msgid "CiStatusText|pending" -msgstr "等待中" - -msgid "CiStatusText|skipped" -msgstr "已跳過" - -msgid "CiStatus|running" -msgstr "運行中" +msgid "Cancel" +msgstr "取消" msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" -msgid "CommitMessage|Add %{file_name}" -msgstr "添加 %{file_name}" - -msgid "Commits" -msgstr "提交" - -msgid "Commits|History" -msgstr "歷史" - -msgid "Compare" -msgstr "比較" - -msgid "Contribution guide" -msgstr "貢獻指南" - -msgid "Contributors" -msgstr "貢獻者" - -msgid "Copy URL to clipboard" -msgstr "複製URL到剪貼板" - -msgid "Copy commit SHA to clipboard" -msgstr "複製提交 SHA 到剪貼板" - -msgid "Create New Directory" -msgstr "創建新目錄" - -msgid "Create directory" -msgstr "創建目錄" - -msgid "Create empty bare repository" -msgstr "創建空的存儲庫" - -msgid "Create merge request" -msgstr "創建合併請求" - -msgid "CreateNewFork|Fork" -msgstr "派生" - -msgid "Custom notification events" -msgstr "自定義通知事件" - -msgid "" -"Custom notification levels are the same as participating levels. With custom " -"notification levels you will also receive notifications for select events. " -"To find out more, check out %{notification_link}." -msgstr "" -"自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}." - -msgid "Cycle Analytics" -msgstr "週期分析" +msgid "Cron Timezone" +msgstr "Cron 時區" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " @@ -201,42 +61,30 @@ msgstr "預發布" msgid "CycleAnalyticsStage|Test" msgstr "測試" +msgid "Delete" +msgstr "刪除" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" -msgid "Directory name" -msgstr "目錄名稱" - -msgid "Don't show again" -msgstr "不再顯示" - -msgid "Download tar" -msgstr "下載 tar" - -msgid "Download tar.bz2" -msgstr "下載 tar.bz2" +msgid "Description" +msgstr "描述" -msgid "Download tar.gz" -msgstr "下載 tar.gz" +msgid "Edit" +msgstr "編輯" -msgid "Download zip" -msgstr "下載 zip" +msgid "Edit Pipeline Schedule %{id}" +msgstr "編輯 %{id} 流水線計劃" -msgid "DownloadArtifacts|Download" -msgstr "下載" +msgid "Failed to change the owner" +msgstr "無法變更所有者" -msgid "DownloadSource|Download" -msgstr "下載" +msgid "Failed to remove the pipeline schedule" +msgstr "無法刪除流水線計劃" -msgid "Files" -msgstr "文件" - -msgid "Find by path" -msgstr "按路徑查找" - -msgid "Find file" -msgstr "查找文件" +msgid "Filter" +msgstr "過濾" msgid "FirstPushedBy|First" msgstr "首次推送" @@ -244,57 +92,24 @@ msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" -msgid "ForkedFromProjectPath|Forked from" -msgstr "離開" - -msgid "Forks" -msgstr "派生" - msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" -msgid "Go to your fork" -msgstr "跳轉到您的派生項目" - -msgid "GoToYourFork|Fork" -msgstr "跳轉到您的" - -msgid "Home" -msgstr "主頁" - -msgid "Housekeeping successfully started" -msgstr "已開始維護" - -msgid "Import repository" -msgstr "導入存儲庫" +msgid "Interval Pattern" +msgstr "間隔模式" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" -msgid "LFSStatus|Disabled" -msgstr "禁用" - -msgid "LFSStatus|Enabled" -msgstr "啟用" - msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最後 %d 天" -msgid "Last Update" -msgstr "最後更新" - -msgid "Last commit" -msgstr "最後提交" - -msgid "Leave group" -msgstr "離開群組" - -msgid "Leave project" -msgstr "離開項目" +msgid "Last Pipeline" +msgstr "最新流水線" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" @@ -303,36 +118,15 @@ msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" -msgid "MissingSSHKeyWarningLink|add an SSH key" -msgstr "添加一個 SSH 公鑰" - msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新議題" -msgid "New branch" -msgstr "新分支" - -msgid "New directory" -msgstr "新目錄" - -msgid "New file" -msgstr "新文件" - -msgid "New issue" -msgstr "新議題" - -msgid "New merge request" -msgstr "新合併請求" +msgid "New Pipeline Schedule" +msgstr "創建流水線計劃" -msgid "New snippet" -msgstr "新代碼片段" - -msgid "New tag" -msgstr "新標籤" - -msgid "No repository" -msgstr "沒有存儲庫" +msgid "No schedules" +msgstr "沒有計劃" msgid "Not available" msgstr "不可用" @@ -340,131 +134,54 @@ msgstr "不可用" msgid "Not enough data" msgstr "數據不足" -msgid "Notification events" -msgstr "通知事件" - -msgid "NotificationEvent|Close issue" -msgstr "關閉議題" - -msgid "NotificationEvent|Close merge request" -msgstr "關閉合併請求" - -msgid "NotificationEvent|Failed pipeline" -msgstr "流水線失敗" - -msgid "NotificationEvent|Merge merge request" -msgstr "合併請求被合併" - -msgid "NotificationEvent|New issue" -msgstr "新議題" - -msgid "NotificationEvent|New merge request" -msgstr "新合併請求" - -msgid "NotificationEvent|New note" -msgstr "新評論" - -msgid "NotificationEvent|Reassign issue" -msgstr "重新指派議題" - -msgid "NotificationEvent|Reassign merge request" -msgstr "重新指派合併請求" - -msgid "NotificationEvent|Reopen issue" -msgstr "重新打開議題" - -msgid "NotificationEvent|Successful pipeline" -msgstr "流水線成功完成" - -msgid "NotificationLevel|Custom" -msgstr "自定義" - -msgid "NotificationLevel|Disabled" -msgstr "禁用" - -msgid "NotificationLevel|Global" -msgstr "全局" - -msgid "NotificationLevel|On mention" -msgstr "提及" - -msgid "NotificationLevel|Participate" -msgstr "參與" - -msgid "NotificationLevel|Watch" -msgstr "關注" - msgid "OpenedNDaysAgo|Opened" msgstr "開始於" +msgid "Owner" +msgstr "所有者" + msgid "Pipeline Health" msgstr "流水線健康指標" -msgid "Project '%{project_name}' queued for deletion." -msgstr "項目 '%{project_name}' 已進入刪除隊列。" - -msgid "Project '%{project_name}' was successfully created." -msgstr "項目 '%{project_name}' 已創建完成。" - -msgid "Project '%{project_name}' was successfully updated." -msgstr "項目 '%{project_name}' 已更新完成。" +msgid "Pipeline Schedule" +msgstr "流水線計劃" -msgid "Project '%{project_name}' will be deleted." -msgstr "項目 '%{project_name}' 已刪除。" +msgid "Pipeline Schedules" +msgstr "流水線計劃" -msgid "Project access must be granted explicitly to each user." -msgstr "訪問項目必須明確授權給每個用戶。" +msgid "PipelineSchedules|Activated" +msgstr "已激活" -msgid "Project export could not be deleted." -msgstr "無法刪除項目導出。" +msgid "PipelineSchedules|Active" +msgstr "激活" -msgid "Project export has been deleted." -msgstr "項目導出已刪除。" +msgid "PipelineSchedules|All" +msgstr "所有" -msgid "" -"Project export link has expired. Please generate a new export from your " -"project settings." -msgstr "項目導出鏈接已過期。請從您的項目設置中生成新的導出。" - -msgid "Project export started. A download link will be sent by email." -msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。" - -msgid "Project home" -msgstr "項目主頁" +msgid "PipelineSchedules|Inactive" +msgstr "等待" -msgid "ProjectFeature|Disabled" -msgstr "禁用" +msgid "PipelineSchedules|Next Run" +msgstr "下壹個運行" -msgid "ProjectFeature|Everyone with access" -msgstr "任何人都可訪問" +msgid "PipelineSchedules|None" +msgstr "無" -msgid "ProjectFeature|Only team members" -msgstr "只有團隊成員" +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "為此流水線提供簡短描述" -msgid "ProjectFileTree|Name" -msgstr "名稱" +msgid "PipelineSchedules|Take ownership" +msgstr "取得所有權" -msgid "ProjectLastActivity|Never" -msgstr "從未" +msgid "PipelineSchedules|Target" +msgstr "目標" msgid "ProjectLifecycle|Stage" -msgstr "項目生命週期" - -msgid "ProjectNetworkGraph|Graph" -msgstr "分支圖" +msgstr "階段" msgid "Read more" msgstr "了解更多" -msgid "Readme" -msgstr "自述文件" - -msgid "RefSwitcher|Branches" -msgstr "分支" - -msgid "RefSwitcher|Tags" -msgstr "標籤" - msgid "Related Commits" msgstr "相關的提交" @@ -481,75 +198,41 @@ msgid "Related Merge Requests" msgstr "相關的合併請求" msgid "Related Merged Requests" -msgstr "相關已合併的合並請求" - -msgid "Remind later" -msgstr "稍後提醒" - -msgid "Remove project" -msgstr "刪除項目" - -msgid "Request Access" -msgstr "申請訪問" - -msgid "Search branches and tags" -msgstr "搜索分支和標籤" - -msgid "Select Archive Format" -msgstr "選擇下載格式" +msgstr "相關已合併的合併請求" -msgid "Set a password on your account to pull or push via %{protocol}" -msgstr "為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。" +msgid "Save pipeline schedule" +msgstr "保存流水線計劃" -msgid "Set up CI" -msgstr "設置 CI" +msgid "Schedule a new pipeline" +msgstr "新增流水線計劃" -msgid "Set up Koding" -msgstr "設置 Koding" +msgid "Select a timezone" +msgstr "選擇時區" -msgid "Set up auto deploy" -msgstr "設置自動部署" - -msgid "SetPasswordToCloneLink|set a password" -msgstr "設置密碼" +msgid "Select target branch" +msgstr "選擇目標分支" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" -msgid "Source code" -msgstr "源代碼" - -msgid "StarProject|Star" -msgstr "星標" - -msgid "Switch branch/tag" -msgstr "切換分支/標籤" - -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "標籤" - -msgid "Tags" -msgstr "標籤" +msgid "Target Branch" +msgstr "目標分支" msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " "first merge request." -msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。" msgid "The collection of events added to the data gathered for that stage." msgstr "與該階段相關的事件。" -msgid "The fork relationship has been removed." -msgstr "派生關係已被刪除。" - msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." -msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。" +msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。" msgid "The phase of the development lifecycle." msgstr "項目生命週期中的各個階段。" @@ -566,26 +249,17 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。" -msgid "The project can be accessed by any logged in user." -msgstr "該項目允許已登錄的用戶訪問。" - -msgid "The project can be accessed without any authentication." -msgstr "該項目允許任何人訪問。" - -msgid "The repository for this project does not exist." -msgstr "此項目的存儲庫不存在。" - msgid "" "The review stage shows the time from creating the merge request to merging " "it. The data will automatically be added after you merge your first merge " "request." -msgstr "評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。" msgid "" "The staging stage shows the time between merging the MR and deploying code " "to the production environment. The data will be automatically added once you " "deploy to production for the first time." -msgstr "預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" +msgstr "預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" msgid "" "The testing stage shows the time GitLab CI takes to run every pipeline for " @@ -600,12 +274,7 @@ msgid "" "The value lying at the midpoint of a series of observed values. E.g., " "between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" " 6." -msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" - -msgid "" -"This means you can not push code until you create an empty repository or " -"import existing one." -msgstr "在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。" +msgstr "中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" msgstr "議題被列入日程表的時間" @@ -614,134 +283,11 @@ msgid "Time before an issue starts implementation" msgstr "開始進行編碼前的時間" msgid "Time between merge request creation and merge/close" -msgstr "從創建合併請求到被合並或關閉的時間" +msgstr "從創建合併請求到被合併或關閉的時間" msgid "Time until first merge request" msgstr "創建第壹個合併請求之前的時間" -msgid "Timeago|%s days ago" -msgstr "%s 天前" - -msgid "Timeago|%s days remaining" -msgstr "剩餘 %s 天" - -msgid "Timeago|%s hours remaining" -msgstr "剩餘 %s 小時" - -msgid "Timeago|%s minutes ago" -msgstr "%s 分鐘前" - -msgid "Timeago|%s minutes remaining" -msgstr "剩餘 %s 分鐘" - -msgid "Timeago|%s months ago" -msgstr "%s 個月前" - -msgid "Timeago|%s months remaining" -msgstr "剩餘 %s 月" - -msgid "Timeago|%s seconds remaining" -msgstr "剩餘 %s 秒" - -msgid "Timeago|%s weeks ago" -msgstr "%s 星期前" - -msgid "Timeago|%s weeks remaining" -msgstr "剩餘 %s 星期" - -msgid "Timeago|%s years ago" -msgstr "%s 年前" - -msgid "Timeago|%s years remaining" -msgstr "剩餘 %s 年" - -msgid "Timeago|1 day remaining" -msgstr "剩餘 1 天" - -msgid "Timeago|1 hour remaining" -msgstr "剩餘 1 小時" - -msgid "Timeago|1 minute remaining" -msgstr "剩餘 1 分鐘" - -msgid "Timeago|1 month remaining" -msgstr "剩餘 1 個月" - -msgid "Timeago|1 week remaining" -msgstr "剩餘 1 星期" - -msgid "Timeago|1 year remaining" -msgstr "剩餘 1 年" - -msgid "Timeago|Past due" -msgstr "逾期" - -msgid "Timeago|a day ago" -msgstr "1 天前" - -msgid "Timeago|a month ago" -msgstr "1 個月前" - -msgid "Timeago|a week ago" -msgstr "1 星期前" - -msgid "Timeago|a while" -msgstr "剛剛" - -msgid "Timeago|a year ago" -msgstr "1 年前" - -msgid "Timeago|about %s hours ago" -msgstr "大約 %s 小時前" - -msgid "Timeago|about a minute ago" -msgstr "大約 1 分鐘前" - -msgid "Timeago|about an hour ago" -msgstr "大約 1 小時前" - -msgid "Timeago|in %s days" -msgstr "在 %s 天" - -msgid "Timeago|in %s hours" -msgstr "在 %s 小時" - -msgid "Timeago|in %s minutes" -msgstr "在 %s 分鐘" - -msgid "Timeago|in %s months" -msgstr "在 %s 個月" - -msgid "Timeago|in %s seconds" -msgstr "在 %s 秒" - -msgid "Timeago|in %s weeks" -msgstr "在 %s 星期" - -msgid "Timeago|in %s years" -msgstr "在 %s 年" - -msgid "Timeago|in 1 day" -msgstr "在 1 天" - -msgid "Timeago|in 1 hour" -msgstr "在 1 小時" - -msgid "Timeago|in 1 minute" -msgstr "在 1 分鐘" - -msgid "Timeago|in 1 month" -msgstr "在 1 月" - -msgid "Timeago|in 1 week" -msgstr "在 1 星期" - -msgid "Timeago|in 1 year" -msgstr "在 1 年" - -msgid "Timeago|less than a minute ago" -msgstr "不到 1 分鐘前" - msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "小時" @@ -759,101 +305,16 @@ msgstr "總時間" msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" -msgid "Unstar" -msgstr "取消" - -msgid "Upload New File" -msgstr "上傳新文件" - -msgid "Upload file" -msgstr "上傳文件" - -msgid "Use your global notification setting" -msgstr "使用全局通知設置" - -msgid "VisibilityLevel|Internal" -msgstr "內部" - -msgid "VisibilityLevel|Private" -msgstr "私有" - -msgid "VisibilityLevel|Public" -msgstr "公開" - msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" msgid "We don't have enough data to show this stage." msgstr "該階段的數據不足,無法顯示。" -msgid "Withdraw Access Request" -msgstr "取消訪問請求" - -msgid "" -"You are going to remove %{project_name_with_namespace}.\n" -"Removed project CANNOT be restored!\n" -"Are you ABSOLUTELY sure?" -msgstr "您將要刪除 %{project_name_with_namespace}。\n" -"已刪除的項目無法恢復!\n" -"妳確定繼續嗎?" - -msgid "" -"You are going to remove the fork relationship to source project " -"%{forked_from_project}. Are you ABSOLUTELY sure?" -msgstr "將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?" - -msgid "" -"You are going to transfer %{project_name_with_namespace} to another owner. " -"Are you ABSOLUTELY sure?" -msgstr "將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?" - -msgid "You can only add files when you are on a branch" -msgstr "您只能在分支上添加文件" - -msgid "You must sign in to star a project" -msgstr "您必須登錄才能對項目加星標" - msgid "You need permission." msgstr "您需要相關的權限。" -msgid "You will not get any notifications via email" -msgstr "您將不會收到任何通知郵件" - -msgid "You will only receive notifications for the events you choose" -msgstr "您只會收到您選擇的事件通知" - -msgid "" -"You will only receive notifications for threads you have participated in" -msgstr "您只會收到您參與的主題的通知" - -msgid "You will receive notifications for any activity" -msgstr "您將不會收到任何通知" - -msgid "" -"You will receive notifications only for comments in which you were " -"@mentioned" -msgstr "您只会收到評論中提及(@)您的通知" - -msgid "" -"You won't be able to pull or push project code via %{protocol} until you " -"%{set_password_link} on your account" -msgstr "在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。" - -msgid "" -"You won't be able to pull or push project code via SSH until you " -"%{add_ssh_key_link} to your profile" -msgstr "在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。" - -msgid "Your name" -msgstr "您的名字" - -msgid "committed" -msgstr "提交" - msgid "day" msgid_plural "days" msgstr[0] "天" -msgid "notification emails" -msgstr "通知郵件" - diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp index 0519ecba6ea..e69de29bb2d 100644 --- a/locale/zh_HK/gitlab.po.time_stamp +++ b/locale/zh_HK/gitlab.po.time_stamp @@ -1 +0,0 @@ - \ No newline at end of file -- cgit v1.2.1 From 7f0221a44e2a2a34bc48a9194b969655874dcaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 13:22:33 +0800 Subject: supplement bulgarian translation Fix #33561 --- app/assets/javascripts/locale/bg/app.js | 2 +- locale/bg/gitlab.po | 103 ++++++++++++++++++++++++++++++-- locale/bg/gitlab.po.time_stamp | 1 + 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index ba56c0bea25..f1fedb546de 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-05 09:40-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"ByAuthor|by":["от"],"Commit":["Подаване","Подавания"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Deploy":["Внедряване","Внедрявания"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Introducing Cycle Analytics":["Представяме Ви анализът на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Pipeline Health":["Състояние"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнените на първата Ви такава задача."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-10 03:35-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"ByAuthor|by":["от"],"Cancel":["Отказ"],"Commit":["Подаване","Подавания"],"Cron Timezone":["Часова зона за „Cron“"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Filter":["Филтриране"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Owner":["Собственик"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index e6caf83252d..41805a7792e 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -3,25 +3,34 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-04 19:24-0500\n" +"POT-Creation-Date: 2017-06-07 21:22+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-05 09:40-0400\n" +"PO-Revision-Date: 2017-06-10 03:35-0400\n" "Last-Translator: Lyubomir Vasilev \n" "Language-Team: Bulgarian\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Наистина ли искате да изтриете този план за схема?" + msgid "ByAuthor|by" msgstr "от" +msgid "Cancel" +msgstr "Отказ" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Cron Timezone" +msgstr "Часова зона за „Cron“" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -50,11 +59,32 @@ msgstr "Подготовка за издаване" msgid "CycleAnalyticsStage|Test" msgstr "Тестване" +msgid "Delete" +msgstr "Изтриване" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "Внедряване" msgstr[1] "Внедрявания" +msgid "Description" +msgstr "Описание" + +msgid "Edit" +msgstr "Редактиране" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Редактиране на плана %{id} за схема" + +msgid "Failed to change the owner" +msgstr "Собственикът не може да бъде променен" + +msgid "Failed to remove the pipeline schedule" +msgstr "Планът за схема не може да бъде премахнат" + +msgid "Filter" +msgstr "Филтриране" + msgid "FirstPushedBy|First" msgstr "Първо" @@ -68,14 +98,20 @@ msgid "From merge request merge until deploy to production" msgstr "" "От прилагането на заявката за сливане до внедряването в крайната версия" +msgid "Interval Pattern" +msgstr "Шаблон за интервала" + msgid "Introducing Cycle Analytics" -msgstr "Представяме Ви анализът на циклите" +msgstr "Представяме Ви анализа на циклите" msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последния %d ден" msgstr[1] "Последните %d дни" +msgid "Last Pipeline" +msgstr "Последна схема" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "Ограничено до показване на последното %d събитие" @@ -89,6 +125,12 @@ msgid_plural "New Issues" msgstr[0] "Нов проблем" msgstr[1] "Нови проблема" +msgid "New Pipeline Schedule" +msgstr "Нов план за схема" + +msgid "No schedules" +msgstr "Няма планове" + msgid "Not available" msgstr "Не е налично" @@ -98,9 +140,45 @@ msgstr "Няма достатъчно данни" msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" +msgid "Owner" +msgstr "Собственик" + msgid "Pipeline Health" msgstr "Състояние" +msgid "Pipeline Schedule" +msgstr "План за схема" + +msgid "Pipeline Schedules" +msgstr "Планове за схема" + +msgid "PipelineSchedules|Activated" +msgstr "Включено" + +msgid "PipelineSchedules|Active" +msgstr "Активно" + +msgid "PipelineSchedules|All" +msgstr "Всички" + +msgid "PipelineSchedules|Inactive" +msgstr "Неактивно" + +msgid "PipelineSchedules|Next Run" +msgstr "Следващо изпълнение" + +msgid "PipelineSchedules|None" +msgstr "Нищо" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Въведете кратко описание за тази схема" + +msgid "PipelineSchedules|Take ownership" +msgstr "Поемане на собствеността" + +msgid "PipelineSchedules|Target" +msgstr "Цел" + msgid "ProjectLifecycle|Stage" msgstr "Етап" @@ -125,11 +203,26 @@ msgstr "Свързани заявки за сливане" msgid "Related Merged Requests" msgstr "Свързани приложени заявки за сливане" +msgid "Save pipeline schedule" +msgstr "Запазване на плана за схема" + +msgid "Schedule a new pipeline" +msgstr "Създаване на нов план за схема" + +msgid "Select a timezone" +msgstr "Изберете часова зона" + +msgid "Select target branch" +msgstr "Изберете целеви клон" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показване на %d събитие" msgstr[1] "Показване на %d събития" +msgid "Target Branch" +msgstr "Целеви клон" + msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " @@ -197,8 +290,8 @@ msgid "" "first pipeline finishes running." msgstr "" "Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни " -"всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени " -"автоматично след като приключи изпълнените на първата Ви такава задача." +"всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат " +"добавени автоматично след като приключи изпълнението на първата Ви схема." msgid "The time taken by each data entry gathered by that stage." msgstr "Времето, което отнема всеки запис от данни за съответния етап." diff --git a/locale/bg/gitlab.po.time_stamp b/locale/bg/gitlab.po.time_stamp index e69de29bb2d..0519ecba6ea 100644 --- a/locale/bg/gitlab.po.time_stamp +++ b/locale/bg/gitlab.po.time_stamp @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.1 From 6df36fc2adfc6dbdb16ab20c7bd2a30ae27801d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 13:29:11 +0800 Subject: add changelog of supplement bulgarian translation --- .../unreleased/33561-supplement_bulgarian_translation_of_i18n.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml diff --git a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml new file mode 100644 index 00000000000..4f2ba2e1de3 --- /dev/null +++ b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Bulgarian translation of Project Page & Repository Page +merge_request: 12083 +author: Lyubomir Vasilev -- cgit v1.2.1 From 28e5b7db62146c82f6ba4da9fca7ee0596ecedfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 11:22:51 +0800 Subject: Optimization 'bg' translation 1. Fix missing translations --- app/assets/javascripts/locale/bg/app.js | 2 +- locale/bg/gitlab.po | 794 +++++++++++++++++++++++++++++++- 2 files changed, 786 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index f1fedb546de..33a5c3c7eb9 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-10 03:35-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"ByAuthor|by":["от"],"Cancel":["Отказ"],"Commit":["Подаване","Подавания"],"Cron Timezone":["Часова зона за „Cron“"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Filter":["Филтриране"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Owner":["Собственик"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 09:36-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":[""],"About auto deploy":["Относно автоматичното внедряване"],"Active":[""],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":[""],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":[""],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":[""],"ChangeTypeActionLabel|Revert in branch":[""],"ChangeTypeAction|Cherry-pick":[""],"ChangeType|commit":[""],"ChangeType|merge request":[""],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":[""],"Cherry-pick this merge-request":[""],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":[""],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":[""],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":[""],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":[""],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":[""],"Custom":[""],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":[""],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":[""],"DownloadCommit|Plain Diff":[""],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":[""],"Every month (on the 1st at 4:00am)":[""],"Every week (Sundays at 4:00am)":[""],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Наследяване",""],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":[""],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":[""],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":[""],"OpenedNDaysAgo|Opened":["Отворен"],"Options":[""],"Owner":["Собственик"],"Pipeline":[""],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":[""],"Revert this merge-request":[""],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":[""],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":[""],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":[""],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["",""],"pipeline schedules documentation":[""],"with stage":["",""]}}}; \ No newline at end of file diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 41805a7792e..fd00796d763 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -3,34 +3,245 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 21:22+0200\n" +"POT-Creation-Date: 2017-06-12 19:29-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-10 03:35-0400\n" +"PO-Revision-Date: 2017-06-12 09:36-0400\n" "Last-Translator: Lyubomir Vasilev \n" -"Language-Team: Bulgarian\n" +"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "" + +msgid "About auto deploy" +msgstr "Относно автоматичното внедряване" + +msgid "Active" +msgstr "" + +msgid "Activity" +msgstr "Дейност" + +msgid "Add Changelog" +msgstr "Добавяне на списък с промени" + +msgid "Add Contribution guide" +msgstr "Добавяне на ръководство за сътрудничество" + +msgid "Add License" +msgstr "Добавяне на лиценз" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате " +"промени чрез SSH." + +msgid "Add new directory" +msgstr "Добавяне на нова папка" + +msgid "Archived project! Repository is read-only" +msgstr "Архивиран проект! Хранилището е само за четене" + msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Наистина ли искате да изтриете този план за схема?" +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Клон" +msgstr[1] "Клонове" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"Клонът %{branch_name} беше създаден. За да настроите " +"автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте " +"промените си. %{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "Клонове" + +msgid "Browse files" +msgstr "" + msgid "ByAuthor|by" msgstr "от" +msgid "CI configuration" +msgstr "Конфигурация на непрекъсната интеграция" + msgid "Cancel" msgstr "Отказ" +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeType|commit" +msgstr "" + +msgid "ChangeType|merge request" +msgstr "" + +msgid "Changelog" +msgstr "Списък с промени" + +msgid "Charts" +msgstr "Графики" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge-request" +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "отказано" + +msgid "CiStatusLabel|created" +msgstr "създадено" + +msgid "CiStatusLabel|failed" +msgstr "неуспешно" + +msgid "CiStatusLabel|manual action" +msgstr "ръчно действие" + +msgid "CiStatusLabel|passed" +msgstr "успешно" + +msgid "CiStatusLabel|passed with warnings" +msgstr "успешно, с предупреждения" + +msgid "CiStatusLabel|pending" +msgstr "на изчакване" + +msgid "CiStatusLabel|skipped" +msgstr "пропуснато" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "чакане за ръчно действие" + +msgid "CiStatusText|blocked" +msgstr "блокирано" + +msgid "CiStatusText|canceled" +msgstr "отказано" + +msgid "CiStatusText|created" +msgstr "създадено" + +msgid "CiStatusText|failed" +msgstr "неуспешно" + +msgid "CiStatusText|manual" +msgstr "ръчно" + +msgid "CiStatusText|passed" +msgstr "успешно" + +msgid "CiStatusText|pending" +msgstr "на изчакване" + +msgid "CiStatusText|skipped" +msgstr "пропуснато" + +msgid "CiStatus|running" +msgstr "протича в момента" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Commit message" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Добавяне на „%{file_name}“" + +msgid "Commits" +msgstr "Подавания" + +msgid "Commits|History" +msgstr "История" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "Сравнение" + +msgid "Contribution guide" +msgstr "Ръководство за сътрудничество" + +msgid "Contributors" +msgstr "Сътрудници" + +msgid "Copy URL to clipboard" +msgstr "Копиране на адреса в буфера за обмен" + +msgid "Copy commit SHA to clipboard" +msgstr "Копиране на идентификатора на подаването в буфера за обмен" + +msgid "Create New Directory" +msgstr "Създаване на нова папка" + +msgid "Create directory" +msgstr "Създаване на папка" + +msgid "Create empty bare repository" +msgstr "Създаване на празно хранилище" + +msgid "Create merge request" +msgstr "Създаване на заявка за сливане" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "Разклоняване" + +msgid "CreateTag|Tag" +msgstr "" + msgid "Cron Timezone" msgstr "Часова зона за „Cron“" +msgid "Cron syntax" +msgstr "" + +msgid "Custom" +msgstr "" + +msgid "Custom notification events" +msgstr "Персонализирани събития за известяване" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Персонализираните нива на известяване са същите като нивата за участие. С " +"персонализираните нива на известяване ще можете да получавате и известия за " +"избрани събития. За да научите повече, прегледайте %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Анализ на циклите" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -59,6 +270,9 @@ msgstr "Подготовка за издаване" msgid "CycleAnalyticsStage|Test" msgstr "Тестване" +msgid "Define a custom pattern with cron syntax" +msgstr "" + msgid "Delete" msgstr "Изтриване" @@ -70,20 +284,68 @@ msgstr[1] "Внедрявания" msgid "Description" msgstr "Описание" +msgid "Directory name" +msgstr "Име на папката" + +msgid "Don't show again" +msgstr "Да не се показва повече" + +msgid "Download" +msgstr "Сваляне" + +msgid "Download tar" +msgstr "Сваляне във формат „tar“" + +msgid "Download tar.bz2" +msgstr "Сваляне във формат „tar.bz2“" + +msgid "Download tar.gz" +msgstr "Сваляне във формат „tar.gz“" + +msgid "Download zip" +msgstr "Сваляне във формат „zip“" + +msgid "DownloadArtifacts|Download" +msgstr "Сваляне" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "Сваляне" + msgid "Edit" msgstr "Редактиране" msgid "Edit Pipeline Schedule %{id}" msgstr "Редактиране на плана %{id} за схема" +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + msgid "Failed to change the owner" msgstr "Собственикът не може да бъде променен" msgid "Failed to remove the pipeline schedule" msgstr "Планът за схема не може да бъде премахнат" -msgid "Filter" -msgstr "Филтриране" +msgid "Files" +msgstr "Файлове" + +msgid "Find by path" +msgstr "Търсене по път" + +msgid "Find file" +msgstr "Търсене на файл" msgid "FirstPushedBy|First" msgstr "Първо" @@ -91,6 +353,15 @@ msgstr "Първо" msgid "FirstPushedBy|pushed by" msgstr "изпращане на промени от" +#, fuzzy +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Наследяване" +msgstr[1] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "Разклонение на" + msgid "From issue creation until deploy to production" msgstr "От създаването на проблема до внедряването в крайната версия" @@ -98,12 +369,33 @@ msgid "From merge request merge until deploy to production" msgstr "" "От прилагането на заявката за сливане до внедряването в крайната версия" +msgid "Go to your fork" +msgstr "Към Вашето разклонение" + +msgid "GoToYourFork|Fork" +msgstr "Разклонение" + +msgid "Home" +msgstr "Начало" + +msgid "Housekeeping successfully started" +msgstr "Освежаването започна успешно" + +msgid "Import repository" +msgstr "Внасяне на хранилище" + msgid "Interval Pattern" msgstr "Шаблон за интервала" msgid "Introducing Cycle Analytics" msgstr "Представяме Ви анализа на циклите" +msgid "LFSStatus|Disabled" +msgstr "Изключено" + +msgid "LFSStatus|Enabled" +msgstr "Включено" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последния %d ден" @@ -112,14 +404,32 @@ msgstr[1] "Последните %d дни" msgid "Last Pipeline" msgstr "Последна схема" +msgid "Last Update" +msgstr "Последна промяна" + +msgid "Last commit" +msgstr "Последно подаване" + +msgid "Learn more in the" +msgstr "" + +msgid "Leave group" +msgstr "Напускане на групата" + +msgid "Leave project" +msgstr "Напускане на проекта" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "Ограничено до показване на последното %d събитие" -msgstr[1] "Ограничено до показване на последните %d събития" +msgstr[0] "Ограничено до показване на най-много %d събитие" +msgstr[1] "Ограничено до показване на най-много %d събития" msgid "Median" msgstr "Медиана" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "добавите SSH ключ" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Нов проблем" @@ -128,6 +438,33 @@ msgstr[1] "Нови проблема" msgid "New Pipeline Schedule" msgstr "Нов план за схема" +msgid "New branch" +msgstr "Нов клон" + +msgid "New directory" +msgstr "Нова папка" + +msgid "New file" +msgstr "Нов файл" + +msgid "New issue" +msgstr "Нов проблем" + +msgid "New merge request" +msgstr "Нова заявка за сливане" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "Нов отрязък" + +msgid "New tag" +msgstr "Нов етикет" + +msgid "No repository" +msgstr "Няма хранилище" + msgid "No schedules" msgstr "Няма планове" @@ -137,12 +474,75 @@ msgstr "Не е налично" msgid "Not enough data" msgstr "Няма достатъчно данни" +msgid "Notification events" +msgstr "Събития за известяване" + +msgid "NotificationEvent|Close issue" +msgstr "Затваряне на проблем" + +msgid "NotificationEvent|Close merge request" +msgstr "Затваряне на заявка за сливане" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Неуспешно изпълнение на схема" + +msgid "NotificationEvent|Merge merge request" +msgstr "Прилагане на заявка за сливане" + +msgid "NotificationEvent|New issue" +msgstr "Нов проблем" + +msgid "NotificationEvent|New merge request" +msgstr "Нова заявка за сливане" + +msgid "NotificationEvent|New note" +msgstr "Нова бележка" + +msgid "NotificationEvent|Reassign issue" +msgstr "Преназначаване на проблем" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Преназначаване на заявка за сливане" + +msgid "NotificationEvent|Reopen issue" +msgstr "Повторно отваряне на проблем" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Успешно изпълнение на схема" + +msgid "NotificationLevel|Custom" +msgstr "Персонализирани" + +msgid "NotificationLevel|Disabled" +msgstr "Изключени" + +msgid "NotificationLevel|Global" +msgstr "Глобални" + +msgid "NotificationLevel|On mention" +msgstr "При споменаване" + +msgid "NotificationLevel|Participate" +msgstr "Участие" + +msgid "NotificationLevel|Watch" +msgstr "Наблюдение" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" +msgid "Options" +msgstr "" + msgid "Owner" msgstr "Собственик" +msgid "Pipeline" +msgstr "" + msgid "Pipeline Health" msgstr "Състояние" @@ -179,17 +579,80 @@ msgstr "Поемане на собствеността" msgid "PipelineSchedules|Target" msgstr "Цел" +msgid "Project '%{project_name}' queued for deletion." +msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Проектът „%{project_name}“ беше създаден успешно." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Проектът „%{project_name}“ беше обновен успешно." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Проектът „%{project_name}“ ще бъде изтрит." + +msgid "Project access must be granted explicitly to each user." +msgstr "" +"Достъпът до проекта трябва да бъде даван поотделно на всеки потребител." + +msgid "Project export could not be deleted." +msgstr "Изнесените данни на проекта не могат да бъдат изтрити." + +msgid "Project export has been deleted." +msgstr "Изнесените данни на проекта бяха изтрити." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова " +"от настройките на проекта." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Изнасянето на проекта започна. Ще получите връзка към данните по е-поща." + +msgid "Project home" +msgstr "Начална страница на проекта" + +msgid "ProjectFeature|Disabled" +msgstr "Изключено" + +msgid "ProjectFeature|Everyone with access" +msgstr "Всеки с достъп" + +msgid "ProjectFeature|Only team members" +msgstr "Само членовете на екипа" + +msgid "ProjectFileTree|Name" +msgstr "Име" + +msgid "ProjectLastActivity|Never" +msgstr "Никога" + msgid "ProjectLifecycle|Stage" msgstr "Етап" +msgid "ProjectNetworkGraph|Graph" +msgstr "Графика" + msgid "Read more" msgstr "Прочетете повече" +msgid "Readme" +msgstr "ПрочетиМе" + +msgid "RefSwitcher|Branches" +msgstr "Клонове" + +msgid "RefSwitcher|Tags" +msgstr "Етикети" + msgid "Related Commits" msgstr "Свързани подавания" msgid "Related Deployed Jobs" -msgstr "Свързани задачи за внедряване" +msgstr "Свързани внедрени задачи" msgid "Related Issues" msgstr "Свързани проблеми" @@ -203,23 +666,84 @@ msgstr "Свързани заявки за сливане" msgid "Related Merged Requests" msgstr "Свързани приложени заявки за сливане" +msgid "Remind later" +msgstr "Напомняне по-късно" + +msgid "Remove project" +msgstr "Премахване на проекта" + +msgid "Request Access" +msgstr "Заявка за достъп" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge-request" +msgstr "" + msgid "Save pipeline schedule" msgstr "Запазване на плана за схема" msgid "Schedule a new pipeline" msgstr "Създаване на нов план за схема" +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Search branches and tags" +msgstr "Търсене в клоновете и етикетите" + +msgid "Select Archive Format" +msgstr "Изберете формата на архива" + msgid "Select a timezone" msgstr "Изберете часова зона" msgid "Select target branch" msgstr "Изберете целеви клон" +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "" +"Задайте парола на профила си, за да можете да изтегляте и изпращате промени " +"чрез %{protocol}" + +msgid "Set up CI" +msgstr "Настройка на НИ" + +msgid "Set up Koding" +msgstr "Настройка на „Koding“" + +msgid "Set up auto deploy" +msgstr "Настройка на авт. внедряване" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "зададете парола" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показване на %d събитие" msgstr[1] "Показване на %d събития" +msgid "Source code" +msgstr "Изходен код" + +msgid "StarProject|Star" +msgstr "Звезда" + +msgid "Start a new merge request with these changes" +msgstr "" + +msgid "Switch branch/tag" +msgstr "Преминаване към клон/етикет" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Етикет" +msgstr[1] "Етикети" + +msgid "Tags" +msgstr "Етикети" + msgid "Target Branch" msgstr "Целеви клон" @@ -235,6 +759,9 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "Съвкупността от събития добавени към данните събрани за този етап." +msgid "The fork relationship has been removed." +msgstr "Връзката на разклонение беше премахната." + msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -248,6 +775,12 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Етапът от цикъла на разработка" +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" + msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " @@ -263,7 +796,18 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "" "Етапът на издаване показва общото време, което е нужно от създаването на " -"проблем до внедряването на кода в крайната версия." +"проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени " +"автоматично след като завършите един пълен цикъл и превърнете първата си " +"идея в реалност." + +msgid "The project can be accessed by any logged in user." +msgstr "Всеки вписан потребител има достъп до проекта." + +msgid "The project can be accessed without any authentication." +msgstr "Всеки може да има достъп до проекта, без нужда от удостоверяване." + +msgid "The repository for this project does not exist." +msgstr "Хранилището за този проект не съществува." msgid "" "The review stage shows the time from creating the merge request to merging " @@ -305,6 +849,13 @@ msgstr "" "данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е " "(5+7)/2 = 6." +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Това означава, че няма да можете да изпращате код, докато не създадете " +"празно хранилище или не внесете съществуващо такова." + msgid "Time before an issue gets scheduled" msgstr "Време преди един проблем да бъде планиран за работа" @@ -318,6 +869,129 @@ msgstr "" msgid "Time until first merge request" msgstr "Време преди първата заявка за сливане" +msgid "Timeago|%s days ago" +msgstr "преди %s дни" + +msgid "Timeago|%s days remaining" +msgstr "остават %s дни" + +msgid "Timeago|%s hours remaining" +msgstr "остават %s часа" + +msgid "Timeago|%s minutes ago" +msgstr "преди %s минути" + +msgid "Timeago|%s minutes remaining" +msgstr "остават %s минути" + +msgid "Timeago|%s months ago" +msgstr "преди %s месеца" + +msgid "Timeago|%s months remaining" +msgstr "остават %s месеца" + +msgid "Timeago|%s seconds remaining" +msgstr "остават %s секунди" + +msgid "Timeago|%s weeks ago" +msgstr "преди %s седмици" + +msgid "Timeago|%s weeks remaining" +msgstr "остават %s седмици" + +msgid "Timeago|%s years ago" +msgstr "преди %s години" + +msgid "Timeago|%s years remaining" +msgstr "остават %s години" + +msgid "Timeago|1 day remaining" +msgstr "остава 1 ден" + +msgid "Timeago|1 hour remaining" +msgstr "остава 1 час" + +msgid "Timeago|1 minute remaining" +msgstr "остава 1 минута" + +msgid "Timeago|1 month remaining" +msgstr "остава 1 месец" + +msgid "Timeago|1 week remaining" +msgstr "остава 1 седмица" + +msgid "Timeago|1 year remaining" +msgstr "остава 1 година" + +msgid "Timeago|Past due" +msgstr "Просрочено" + +msgid "Timeago|a day ago" +msgstr "преди един ден" + +msgid "Timeago|a month ago" +msgstr "преди един месец" + +msgid "Timeago|a week ago" +msgstr "преди една седмица" + +msgid "Timeago|a while" +msgstr "преди известно време" + +msgid "Timeago|a year ago" +msgstr "преди една година" + +msgid "Timeago|about %s hours ago" +msgstr "преди около %s часа" + +msgid "Timeago|about a minute ago" +msgstr "преди около една минута" + +msgid "Timeago|about an hour ago" +msgstr "преди около един час" + +msgid "Timeago|in %s days" +msgstr "след %s дни" + +msgid "Timeago|in %s hours" +msgstr "след %s часа" + +msgid "Timeago|in %s minutes" +msgstr "след %s минути" + +msgid "Timeago|in %s months" +msgstr "след %s месеца" + +msgid "Timeago|in %s seconds" +msgstr "след %s секунди" + +msgid "Timeago|in %s weeks" +msgstr "след %s седмици" + +msgid "Timeago|in %s years" +msgstr "след %s години" + +msgid "Timeago|in 1 day" +msgstr "след 1 ден" + +msgid "Timeago|in 1 hour" +msgstr "след 1 час" + +msgid "Timeago|in 1 minute" +msgstr "след 1 минута" + +msgid "Timeago|in 1 month" +msgstr "след 1 месец" + +msgid "Timeago|in 1 week" +msgstr "след 1 седмица" + +msgid "Timeago|in 1 year" +msgstr "след 1 година" + +msgid "Timeago|less than a minute ago" +msgstr "преди по-малко от минута" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "час" @@ -337,17 +1011,119 @@ msgstr "Общо време" msgid "Total test time for all commits/merges" msgstr "Общо време за тестване на всички подавания/сливания" +msgid "Unstar" +msgstr "Без звезда" + +msgid "Upload New File" +msgstr "Качване на нов файл" + +msgid "Upload file" +msgstr "Качване на файл" + +msgid "Use your global notification setting" +msgstr "Използване на глобалната Ви настройка за известията" + +msgid "VisibilityLevel|Internal" +msgstr "Вътрешен" + +msgid "VisibilityLevel|Private" +msgstr "Частен" + +msgid "VisibilityLevel|Public" +msgstr "Публичен" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Искате ли да видите данните? Помолете администратор за достъп." msgid "We don't have enough data to show this stage." msgstr "Няма достатъчно данни за този етап." +msgid "Withdraw Access Request" +msgstr "Оттегляне на заявката за достъп" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да премахнете „%{project_name_with_namespace}“.\n" +"Ако го премахнете, той НЕ може да бъде възстановен!\n" +"НАИСТИНА ли искате това?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да прехвърлите „%{project_name_with_namespace}“ към друг " +"собственик. НАИСТИНА ли искате това?" + +msgid "You can only add files when you are on a branch" +msgstr "Можете да добавяте файлове само когато се намирате в клон" + +msgid "You must sign in to star a project" +msgstr "Трябва да се впишете, за да отбележите проект със звезда" + msgid "You need permission." msgstr "Нуждаете се от разрешение." +msgid "You will not get any notifications via email" +msgstr "Няма да получавате никакви известия по е-поща" + +msgid "You will only receive notifications for the events you choose" +msgstr "Ще получавате известия само за събитията, за които желаете" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "Ще получавате известия само за нещата, в които участвате" + +msgid "You will receive notifications for any activity" +msgstr "Ще получавате известия за всяка дейност" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "Ще получавате известия само за коментари, в които Ви @споменават" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, " +"докато не %{set_password_link} за профила си" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не " +"%{add_ssh_key_link} в профила си" + +msgid "Your name" +msgstr "Вашето име" + msgid "day" msgid_plural "days" msgstr[0] "ден" msgstr[1] "дни" +msgid "notification emails" +msgstr "известия по е-поща" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" + +msgid "pipeline schedules documentation" +msgstr "" + +msgid "with stage" +msgid_plural "with stages" +msgstr[0] "" +msgstr[1] "" + -- cgit v1.2.1 From 02d7cd41dccfa74a726f31e599dd863237320c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 11:25:16 +0800 Subject: Optimization 'zh_HK' translation 1. Fix missing translations --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 763 ++++++++++++++++++++++++++++- locale/zh_HK/gitlab.po.time_stamp | 1 + 3 files changed, 755 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index fcd5795c8bb..bfc6f7e5db4 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-09 01:09-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"ByAuthor|by":["作者:"],"Cancel":["取消"],"Commit":["提交"],"Cron Timezone":["Cron 時區"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Filter":["過濾"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":["所有者"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"ProjectLifecycle|Stage":["階段"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Showing %d event":["顯示 %d 個事件"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 11:08-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["激活"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到您的個人賬戶中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["變更日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["作者:"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標簽"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每天(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月1日(淩晨4點)"],"Every week (Sundays at 4:00am)":["每周日(淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到您的派生項目"],"GoToYourFork|Fork":["跳轉到您的派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["禁用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New schedule":["新计划"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["禁用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["選項"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["禁用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["包含這些更改到 新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n您確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只會收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 4b02ba19305..22fe5e4f807 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,40 +1,240 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the gitlab package. -# FIRST AUTHOR , YEAR. # Huang Tao , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 21:22+0200\n" +"POT-Creation-Date: 2017-06-12 19:29-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-09 01:09-0400\n" +"PO-Revision-Date: 2017-06-12 11:08-0400\n" "Last-Translator: Huang Tao \n" -"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/" -"teams/75177/zh_HK/)\n" +"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Zanata 3.9.6\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "由 %{commit_author_link} 提交於 %{commit_timeago}" + +msgid "About auto deploy" +msgstr "關於自動部署" + +msgid "Active" +msgstr "激活" + +msgid "Activity" +msgstr "活動" + +msgid "Add Changelog" +msgstr "添加變更日誌" + +msgid "Add Contribution guide" +msgstr "添加貢獻指南" + +msgid "Add License" +msgstr "添加許可證" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "新增壹個用於推送或拉取的 SSH 秘鑰到您的個人賬戶中。" + +msgid "Add new directory" +msgstr "添加新目錄" + +msgid "Archived project! Repository is read-only" +msgstr "歸檔項目!存儲庫為只讀" + msgid "Are you sure you want to delete this pipeline schedule?" msgstr "您確定要刪除此流水線計劃嗎?" +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "拖放文件到此處或者 %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "分支" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml " +"模板併提交更改。%{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "分支" + +msgid "Browse files" +msgstr "瀏覽文件" + msgid "ByAuthor|by" msgstr "作者:" +msgid "CI configuration" +msgstr "CI 配置" + msgid "Cancel" msgstr "取消" +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "挑選到分支" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "還原分支" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "優選" + +msgid "ChangeType|commit" +msgstr "提交" + +msgid "ChangeType|merge request" +msgstr "合併請求" + +msgid "Changelog" +msgstr "變更日誌" + +msgid "Charts" +msgstr "統計圖" + +msgid "Cherry-pick this commit" +msgstr "優選此提交" + +msgid "Cherry-pick this merge-request" +msgstr "優選此合併請求" + +msgid "CiStatusLabel|canceled" +msgstr "已取消" + +msgid "CiStatusLabel|created" +msgstr "已創建" + +msgid "CiStatusLabel|failed" +msgstr "已失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動操作" + +msgid "CiStatusLabel|passed" +msgstr "已通過" + +msgid "CiStatusLabel|passed with warnings" +msgstr "已通過但有警告" + +msgid "CiStatusLabel|pending" +msgstr "等待中" + +msgid "CiStatusLabel|skipped" +msgstr "已跳過" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "等待手動操作" + +msgid "CiStatusText|blocked" +msgstr "已阻塞" + +msgid "CiStatusText|canceled" +msgstr "已取消" + +msgid "CiStatusText|created" +msgstr "已創建" + +msgid "CiStatusText|failed" +msgstr "已失敗" + +msgid "CiStatusText|manual" +msgstr "待手動" + +msgid "CiStatusText|passed" +msgstr "已通過" + +msgid "CiStatusText|pending" +msgstr "等待中" + +msgid "CiStatusText|skipped" +msgstr "已跳過" + +msgid "CiStatus|running" +msgstr "運行中" + msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "Commit message" +msgstr "提交信息" + +msgid "CommitMessage|Add %{file_name}" +msgstr "添加 %{file_name}" + +msgid "Commits" +msgstr "提交" + +msgid "Commits|History" +msgstr "歷史" + +msgid "Committed by" +msgstr "作者:" + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "貢獻指南" + +msgid "Contributors" +msgstr "貢獻者" + +msgid "Copy URL to clipboard" +msgstr "複製URL到剪貼板" + +msgid "Copy commit SHA to clipboard" +msgstr "複製提交 SHA 到剪貼板" + +msgid "Create New Directory" +msgstr "創建新目錄" + +msgid "Create directory" +msgstr "創建目錄" + +msgid "Create empty bare repository" +msgstr "創建空的存儲庫" + +msgid "Create merge request" +msgstr "創建合併請求" + +msgid "Create new..." +msgstr "創建..." + +msgid "CreateNewFork|Fork" +msgstr "派生" + +msgid "CreateTag|Tag" +msgstr "標簽" + msgid "Cron Timezone" msgstr "Cron 時區" +msgid "Cron syntax" +msgstr "Cron 語法" + +msgid "Custom" +msgstr "自定義" + +msgid "Custom notification events" +msgstr "自定義通知事件" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}." + +msgid "Cycle Analytics" +msgstr "週期分析" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -61,6 +261,9 @@ msgstr "預發布" msgid "CycleAnalyticsStage|Test" msgstr "測試" +msgid "Define a custom pattern with cron syntax" +msgstr "使用 Cron 語法定義自定義模式" + msgid "Delete" msgstr "刪除" @@ -71,20 +274,68 @@ msgstr[0] "部署" msgid "Description" msgstr "描述" +msgid "Directory name" +msgstr "目錄名稱" + +msgid "Don't show again" +msgstr "不再顯示" + +msgid "Download" +msgstr "下載" + +msgid "Download tar" +msgstr "下載 tar" + +msgid "Download tar.bz2" +msgstr "下載 tar.bz2" + +msgid "Download tar.gz" +msgstr "下載 tar.gz" + +msgid "Download zip" +msgstr "下載 zip" + +msgid "DownloadArtifacts|Download" +msgstr "下載" + +msgid "DownloadCommit|Email Patches" +msgstr "電子郵件補丁" + +msgid "DownloadCommit|Plain Diff" +msgstr "Diff 文件" + +msgid "DownloadSource|Download" +msgstr "下載" + msgid "Edit" msgstr "編輯" msgid "Edit Pipeline Schedule %{id}" msgstr "編輯 %{id} 流水線計劃" +msgid "Every day (at 4:00am)" +msgstr "每天(淩晨4點)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "每月1日(淩晨4點)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "每周日(淩晨4點)" + msgid "Failed to change the owner" msgstr "無法變更所有者" msgid "Failed to remove the pipeline schedule" msgstr "無法刪除流水線計劃" -msgid "Filter" -msgstr "過濾" +msgid "Files" +msgstr "文件" + +msgid "Find by path" +msgstr "按路徑查找" + +msgid "Find file" +msgstr "查找文件" msgid "FirstPushedBy|First" msgstr "首次推送" @@ -92,18 +343,46 @@ msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "派生" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "派生自" + msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" +msgid "Go to your fork" +msgstr "跳轉到您的派生項目" + +msgid "GoToYourFork|Fork" +msgstr "跳轉到您的派生項目" + +msgid "Home" +msgstr "首頁" + +msgid "Housekeeping successfully started" +msgstr "已開始維護" + +msgid "Import repository" +msgstr "導入存儲庫" + msgid "Interval Pattern" msgstr "間隔模式" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "LFSStatus|Disabled" +msgstr "禁用" + +msgid "LFSStatus|Enabled" +msgstr "啟用" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最後 %d 天" @@ -111,6 +390,21 @@ msgstr[0] "最後 %d 天" msgid "Last Pipeline" msgstr "最新流水線" +msgid "Last Update" +msgstr "最後更新" + +msgid "Last commit" +msgstr "最後提交" + +msgid "Learn more in the" +msgstr "了解更多" + +msgid "Leave group" +msgstr "離開群組" + +msgid "Leave project" +msgstr "離開項目" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "最多顯示 %d 個事件" @@ -118,6 +412,9 @@ msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "添加壹個 SSH 公鑰" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新議題" @@ -125,6 +422,33 @@ msgstr[0] "新議題" msgid "New Pipeline Schedule" msgstr "創建流水線計劃" +msgid "New branch" +msgstr "新分支" + +msgid "New directory" +msgstr "新增目錄" + +msgid "New file" +msgstr "新增文件" + +msgid "New issue" +msgstr "新議題" + +msgid "New merge request" +msgstr "新合併請求" + +msgid "New schedule" +msgstr "新计划" + +msgid "New snippet" +msgstr "新代碼片段" + +msgid "New tag" +msgstr "新標籤" + +msgid "No repository" +msgstr "沒有存儲庫" + msgid "No schedules" msgstr "沒有計劃" @@ -134,12 +458,75 @@ msgstr "不可用" msgid "Not enough data" msgstr "數據不足" +msgid "Notification events" +msgstr "通知事件" + +msgid "NotificationEvent|Close issue" +msgstr "關閉議題" + +msgid "NotificationEvent|Close merge request" +msgstr "關閉合併請求" + +msgid "NotificationEvent|Failed pipeline" +msgstr "流水線失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "合併請求被合併" + +msgid "NotificationEvent|New issue" +msgstr "新議題" + +msgid "NotificationEvent|New merge request" +msgstr "新合併請求" + +msgid "NotificationEvent|New note" +msgstr "新評論" + +msgid "NotificationEvent|Reassign issue" +msgstr "重新指派議題" + +msgid "NotificationEvent|Reassign merge request" +msgstr "重新指派合併請求" + +msgid "NotificationEvent|Reopen issue" +msgstr "重新打開議題" + +msgid "NotificationEvent|Successful pipeline" +msgstr "流水線成功完成" + +msgid "NotificationLevel|Custom" +msgstr "自定義" + +msgid "NotificationLevel|Disabled" +msgstr "禁用" + +msgid "NotificationLevel|Global" +msgstr "全局" + +msgid "NotificationLevel|On mention" +msgstr "提及" + +msgid "NotificationLevel|Participate" +msgstr "參與" + +msgid "NotificationLevel|Watch" +msgstr "關注" + +msgid "OfSearchInADropdown|Filter" +msgstr "篩選" + msgid "OpenedNDaysAgo|Opened" msgstr "開始於" +msgid "Options" +msgstr "選項" + msgid "Owner" msgstr "所有者" +msgid "Pipeline" +msgstr "流水線" + msgid "Pipeline Health" msgstr "流水線健康指標" @@ -176,12 +563,71 @@ msgstr "取得所有權" msgid "PipelineSchedules|Target" msgstr "目標" +msgid "Project '%{project_name}' queued for deletion." +msgstr "項目 '%{project_name}' 已進入刪除隊列。" + +msgid "Project '%{project_name}' was successfully created." +msgstr "項目 '%{project_name}' 已創建完成。" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "項目 '%{project_name}' 已更新完成。" + +msgid "Project '%{project_name}' will be deleted." +msgstr "項目 '%{project_name}' 已刪除。" + +msgid "Project access must be granted explicitly to each user." +msgstr "項目訪問權限必須明確授權給每個用戶。" + +msgid "Project export could not be deleted." +msgstr "無法刪除項目導出。" + +msgid "Project export has been deleted." +msgstr "項目導出已刪除。" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "項目導出鏈接已過期。請從項目設置中重新生成項目導出。" + +msgid "Project export started. A download link will be sent by email." +msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。" + +msgid "Project home" +msgstr "項目首頁" + +msgid "ProjectFeature|Disabled" +msgstr "禁用" + +msgid "ProjectFeature|Everyone with access" +msgstr "任何人都可訪問" + +msgid "ProjectFeature|Only team members" +msgstr "只有團隊成員" + +msgid "ProjectFileTree|Name" +msgstr "名稱" + +msgid "ProjectLastActivity|Never" +msgstr "從未" + msgid "ProjectLifecycle|Stage" msgstr "階段" +msgid "ProjectNetworkGraph|Graph" +msgstr "分支圖" + msgid "Read more" msgstr "了解更多" +msgid "Readme" +msgstr "自述文件" + +msgid "RefSwitcher|Branches" +msgstr "分支" + +msgid "RefSwitcher|Tags" +msgstr "標籤" + msgid "Related Commits" msgstr "相關的提交" @@ -200,22 +646,80 @@ msgstr "相關的合併請求" msgid "Related Merged Requests" msgstr "相關已合併的合併請求" +msgid "Remind later" +msgstr "稍後提醒" + +msgid "Remove project" +msgstr "刪除項目" + +msgid "Request Access" +msgstr "申請訪問" + +msgid "Revert this commit" +msgstr "還原此提交" + +msgid "Revert this merge-request" +msgstr "還原此合併請求" + msgid "Save pipeline schedule" msgstr "保存流水線計劃" msgid "Schedule a new pipeline" msgstr "新增流水線計劃" +msgid "Scheduling Pipelines" +msgstr "流水線計劃" + +msgid "Search branches and tags" +msgstr "搜索分支和標籤" + +msgid "Select Archive Format" +msgstr "選擇下載格式" + msgid "Select a timezone" msgstr "選擇時區" msgid "Select target branch" msgstr "選擇目標分支" +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。" + +msgid "Set up CI" +msgstr "設置 CI" + +msgid "Set up Koding" +msgstr "設置 Koding" + +msgid "Set up auto deploy" +msgstr "設置自動部署" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "設置密碼" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" +msgid "Source code" +msgstr "源代碼" + +msgid "StarProject|Star" +msgstr "星標" + +msgid "Start a new merge request with these changes" +msgstr "包含這些更改到 新合併請求" + +msgid "Switch branch/tag" +msgstr "切換分支/標籤" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "標籤" + +msgid "Tags" +msgstr "標籤" + msgid "Target Branch" msgstr "目標分支" @@ -228,6 +732,9 @@ msgstr "編碼階段概述了從第壹次提交到創建合併請求的時間。 msgid "The collection of events added to the data gathered for that stage." msgstr "與該階段相關的事件。" +msgid "The fork relationship has been removed." +msgstr "派生關係已被刪除。" + msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -237,6 +744,12 @@ msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議 msgid "The phase of the development lifecycle." msgstr "項目生命週期中的各個階段。" +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。" + msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " @@ -249,6 +762,15 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。" +msgid "The project can be accessed by any logged in user." +msgstr "該項目允許已登錄的用戶訪問。" + +msgid "The project can be accessed without any authentication." +msgstr "該項目允許任何人訪問。" + +msgid "The repository for this project does not exist." +msgstr "此項目的存儲庫不存在。" + msgid "" "The review stage shows the time from creating the merge request to merging " "it. The data will automatically be added after you merge your first merge " @@ -276,6 +798,11 @@ msgid "" " 6." msgstr "中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。" + msgid "Time before an issue gets scheduled" msgstr "議題被列入日程表的時間" @@ -288,6 +815,129 @@ msgstr "從創建合併請求到被合併或關閉的時間" msgid "Time until first merge request" msgstr "創建第壹個合併請求之前的時間" +msgid "Timeago|%s days ago" +msgstr "%s 天前" + +msgid "Timeago|%s days remaining" +msgstr "剩餘 %s 天" + +msgid "Timeago|%s hours remaining" +msgstr "剩餘 %s 小時" + +msgid "Timeago|%s minutes ago" +msgstr "%s 分鐘前" + +msgid "Timeago|%s minutes remaining" +msgstr "剩餘 %s 分鐘" + +msgid "Timeago|%s months ago" +msgstr "%s 個月前" + +msgid "Timeago|%s months remaining" +msgstr "剩餘 %s 月" + +msgid "Timeago|%s seconds remaining" +msgstr "剩餘 %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr "%s 星期前" + +msgid "Timeago|%s weeks remaining" +msgstr "剩餘 %s 星期" + +msgid "Timeago|%s years ago" +msgstr "%s 年前" + +msgid "Timeago|%s years remaining" +msgstr "剩餘 %s 年" + +msgid "Timeago|1 day remaining" +msgstr "剩餘 1 天" + +msgid "Timeago|1 hour remaining" +msgstr "剩餘 1 小時" + +msgid "Timeago|1 minute remaining" +msgstr "剩餘 1 分鐘" + +msgid "Timeago|1 month remaining" +msgstr "剩餘 1 個月" + +msgid "Timeago|1 week remaining" +msgstr "剩餘 1 星期" + +msgid "Timeago|1 year remaining" +msgstr "剩餘 1 年" + +msgid "Timeago|Past due" +msgstr "逾期" + +msgid "Timeago|a day ago" +msgstr "1 天前" + +msgid "Timeago|a month ago" +msgstr "1 個月前" + +msgid "Timeago|a week ago" +msgstr "1 星期前" + +msgid "Timeago|a while" +msgstr "剛剛" + +msgid "Timeago|a year ago" +msgstr "1 年前" + +msgid "Timeago|about %s hours ago" +msgstr "大約 %s 小時前" + +msgid "Timeago|about a minute ago" +msgstr "大約 1 分鐘前" + +msgid "Timeago|about an hour ago" +msgstr "大約 1 小時前" + +msgid "Timeago|in %s days" +msgstr "在 %s 天" + +msgid "Timeago|in %s hours" +msgstr "在 %s 小時" + +msgid "Timeago|in %s minutes" +msgstr "在 %s 分鐘" + +msgid "Timeago|in %s months" +msgstr "在 %s 個月" + +msgid "Timeago|in %s seconds" +msgstr "在 %s 秒" + +msgid "Timeago|in %s weeks" +msgstr "在 %s 星期" + +msgid "Timeago|in %s years" +msgstr "在 %s 年" + +msgid "Timeago|in 1 day" +msgstr "在 1 天" + +msgid "Timeago|in 1 hour" +msgstr "在 1 小時" + +msgid "Timeago|in 1 minute" +msgstr "在 1 分鐘" + +msgid "Timeago|in 1 month" +msgstr "在 1 月" + +msgid "Timeago|in 1 week" +msgstr "在 1 星期" + +msgid "Timeago|in 1 year" +msgstr "在 1 年" + +msgid "Timeago|less than a minute ago" +msgstr "不到 1 分鐘前" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "小時" @@ -305,16 +955,109 @@ msgstr "總時間" msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" +msgid "Unstar" +msgstr "取消星標" + +msgid "Upload New File" +msgstr "上傳新文件" + +msgid "Upload file" +msgstr "上傳文件" + +msgid "Use your global notification setting" +msgstr "使用全局通知設置" + +msgid "VisibilityLevel|Internal" +msgstr "內部" + +msgid "VisibilityLevel|Private" +msgstr "私有" + +msgid "VisibilityLevel|Public" +msgstr "公開" + msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" msgid "We don't have enough data to show this stage." msgstr "該階段的數據不足,無法顯示。" +msgid "Withdraw Access Request" +msgstr "取消訪問請求" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "您將要刪除 %{project_name_with_namespace}。\n" +"已刪除的項目無法恢復!\n" +"您確定繼續嗎?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?" + +msgid "You can only add files when you are on a branch" +msgstr "您只能在分支上添加文件" + +msgid "You must sign in to star a project" +msgstr "您必須登錄才能對項目加星標" + msgid "You need permission." msgstr "您需要相關的權限。" +msgid "You will not get any notifications via email" +msgstr "您將不會收到任何通知郵件" + +msgid "You will only receive notifications for the events you choose" +msgstr "您只會收到您選擇的事件通知" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "您只會收到您參與的主題的通知" + +msgid "You will receive notifications for any activity" +msgstr "您將不會收到任何通知" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "您只會收到評論中提及(@)您的通知" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。" + +msgid "Your name" +msgstr "您的名字" + msgid "day" msgid_plural "days" msgstr[0] "天" +msgid "notification emails" +msgstr "通知郵件" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "父級" + +msgid "pipeline schedules documentation" +msgstr "流水線計劃文檔" + +msgid "with stage" +msgid_plural "with stages" +msgstr[0] "於階段" + diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp index e69de29bb2d..0519ecba6ea 100644 --- a/locale/zh_HK/gitlab.po.time_stamp +++ b/locale/zh_HK/gitlab.po.time_stamp @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.1 From b89108671b802338d87a3e701665bb4d0185968b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 11:31:37 +0800 Subject: Change the format in changelog --- ...-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml index a5eb71b63c6..e383bab23d6 100644 --- a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml +++ b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml @@ -1,4 +1,4 @@ --- title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page merge_request: 11995 -author:Huang Tao +author: Huang Tao -- cgit v1.2.1 From fbee7e8fee9c19b8ddc31aa8b23e117ae44a3d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Thu, 8 Jun 2017 13:06:34 +0800 Subject: supplement traditional chinese in hong kong translation Fix #33442 --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 732 +++++++++++++++++++++++++---- locale/zh_HK/gitlab.po.time_stamp | 1 + 3 files changed, 652 insertions(+), 83 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 30cb1e6b89e..9d4cfeea810 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["作者:"],"Cancel":[""],"Commit":["提交"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Delete":[""],"Deploy":["部署"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Interval Pattern":[""],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":[""],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":[""],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["項目生命週期"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["顯示 %d 個事件"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 17:36+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-07 10:18-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"About auto deploy":["關於自動部署"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫是只讀的"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml 模板並提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Changelog":["變更日誌"],"Charts":["圖表"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"CreateNewFork|Fork":["派生"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Deploy":["部署"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadSource|Download":["下載"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"ForkedFromProjectPath|Forked from":["離開"],"Forks":["派生"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到您的派生項目"],"GoToYourFork|Fork":["跳轉到您的"],"Home":["主頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["禁用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加一個 SSH 公鑰"],"New Issue":["新議題"],"New branch":["新分支"],"New directory":["新目錄"],"New file":["新文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["禁用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OpenedNDaysAgo|Opened":["開始於"],"Pipeline Health":["流水線健康指標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["訪問項目必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從您的項目設置中生成新的導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目主頁"],"ProjectFeature|Disabled":["禁用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["項目生命週期"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Set a password on your account to pull or push via %{protocol}":["為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n妳確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只会收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"committed":["提交"],"day":["天"],"notification emails":["通知郵件"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 81b2ff863ea..4d8ceac381e 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,39 +1,183 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the gitlab package. -# FIRST AUTHOR , YEAR. -# +# Huang Tao , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-05-04 19:24-0500\n" -"Last-Translator: HuangTao , 2017\n" -"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/" -"75177/zh_HK/)\n" +"POT-Creation-Date: 2017-06-07 17:36+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_HK\n" +"PO-Revision-Date: 2017-06-07 10:18-0400\n" +"Last-Translator: Huang Tao \n" +"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" +"Language: zh-HK\n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Zanata 3.9.6\n" -msgid "Are you sure you want to delete this pipeline schedule?" +msgid "About auto deploy" +msgstr "關於自動部署" + +msgid "Activity" +msgstr "活動" + +msgid "Add Changelog" +msgstr "添加變更日誌" + +msgid "Add Contribution guide" +msgstr "添加貢獻指南" + +msgid "Add License" +msgstr "添加許可證" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。" + +msgid "Add new directory" +msgstr "添加新目錄" + +msgid "Archived project! Repository is read-only" +msgstr "歸檔項目!存儲庫是只讀的" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "分支" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" msgstr "" +"分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml " +"模板並提交更改。%{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "分支" msgid "ByAuthor|by" msgstr "作者:" -msgid "Cancel" -msgstr "" +msgid "CI configuration" +msgstr "CI 配置" + +msgid "Changelog" +msgstr "變更日誌" + +msgid "Charts" +msgstr "圖表" + +msgid "CiStatusLabel|canceled" +msgstr "已取消" + +msgid "CiStatusLabel|created" +msgstr "已創建" + +msgid "CiStatusLabel|failed" +msgstr "已失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動操作" + +msgid "CiStatusLabel|passed" +msgstr "已通過" + +msgid "CiStatusLabel|passed with warnings" +msgstr "已通過但有警告" + +msgid "CiStatusLabel|pending" +msgstr "等待中" + +msgid "CiStatusLabel|skipped" +msgstr "已跳過" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "等待手動操作" + +msgid "CiStatusText|blocked" +msgstr "已阻塞" + +msgid "CiStatusText|canceled" +msgstr "已取消" + +msgid "CiStatusText|created" +msgstr "已創建" + +msgid "CiStatusText|failed" +msgstr "已失敗" + +msgid "CiStatusText|manual" +msgstr "待手動" + +msgid "CiStatusText|passed" +msgstr "已通過" + +msgid "CiStatusText|pending" +msgstr "等待中" + +msgid "CiStatusText|skipped" +msgstr "已跳過" + +msgid "CiStatus|running" +msgstr "運行中" msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" -msgid "Cron Timezone" +msgid "CommitMessage|Add %{file_name}" +msgstr "添加 %{file_name}" + +msgid "Commits" +msgstr "提交" + +msgid "Commits|History" +msgstr "歷史" + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "貢獻指南" + +msgid "Contributors" +msgstr "貢獻者" + +msgid "Copy URL to clipboard" +msgstr "複製URL到剪貼板" + +msgid "Copy commit SHA to clipboard" +msgstr "複製提交 SHA 到剪貼板" + +msgid "Create New Directory" +msgstr "創建新目錄" + +msgid "Create directory" +msgstr "創建目錄" + +msgid "Create empty bare repository" +msgstr "創建空的存儲庫" + +msgid "Create merge request" +msgstr "創建合併請求" + +msgid "CreateNewFork|Fork" +msgstr "派生" + +msgid "Custom notification events" +msgstr "自定義通知事件" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." msgstr "" +"自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}." -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." +msgid "Cycle Analytics" +msgstr "週期分析" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." msgstr "週期分析概述了項目從想法到產品實現的各階段所需的時間。" msgid "CycleAnalyticsStage|Code" @@ -57,30 +201,42 @@ msgstr "預發布" msgid "CycleAnalyticsStage|Test" msgstr "測試" -msgid "Delete" -msgstr "" - msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" -msgid "Description" -msgstr "" +msgid "Directory name" +msgstr "目錄名稱" -msgid "Edit" -msgstr "" +msgid "Don't show again" +msgstr "不再顯示" -msgid "Edit Pipeline Schedule %{id}" -msgstr "" +msgid "Download tar" +msgstr "下載 tar" -msgid "Failed to change the owner" -msgstr "" +msgid "Download tar.bz2" +msgstr "下載 tar.bz2" -msgid "Failed to remove the pipeline schedule" -msgstr "" +msgid "Download tar.gz" +msgstr "下載 tar.gz" -msgid "Filter" -msgstr "" +msgid "Download zip" +msgstr "下載 zip" + +msgid "DownloadArtifacts|Download" +msgstr "下載" + +msgid "DownloadSource|Download" +msgstr "下載" + +msgid "Files" +msgstr "文件" + +msgid "Find by path" +msgstr "按路徑查找" + +msgid "Find file" +msgstr "查找文件" msgid "FirstPushedBy|First" msgstr "首次推送" @@ -88,24 +244,57 @@ msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" +msgid "ForkedFromProjectPath|Forked from" +msgstr "離開" + +msgid "Forks" +msgstr "派生" + msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" -msgid "Interval Pattern" -msgstr "" +msgid "Go to your fork" +msgstr "跳轉到您的派生項目" + +msgid "GoToYourFork|Fork" +msgstr "跳轉到您的" + +msgid "Home" +msgstr "主頁" + +msgid "Housekeeping successfully started" +msgstr "已開始維護" + +msgid "Import repository" +msgstr "導入存儲庫" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "LFSStatus|Disabled" +msgstr "禁用" + +msgid "LFSStatus|Enabled" +msgstr "啟用" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最後 %d 天" -msgid "Last Pipeline" -msgstr "" +msgid "Last Update" +msgstr "最後更新" + +msgid "Last commit" +msgstr "最後提交" + +msgid "Leave group" +msgstr "離開群組" + +msgid "Leave project" +msgstr "離開項目" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" @@ -114,15 +303,36 @@ msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "添加一個 SSH 公鑰" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新議題" -msgid "New Pipeline Schedule" -msgstr "" +msgid "New branch" +msgstr "新分支" -msgid "No schedules" -msgstr "" +msgid "New directory" +msgstr "新目錄" + +msgid "New file" +msgstr "新文件" + +msgid "New issue" +msgstr "新議題" + +msgid "New merge request" +msgstr "新合併請求" + +msgid "New snippet" +msgstr "新代碼片段" + +msgid "New tag" +msgstr "新標籤" + +msgid "No repository" +msgstr "沒有存儲庫" msgid "Not available" msgstr "不可用" @@ -130,54 +340,131 @@ msgstr "不可用" msgid "Not enough data" msgstr "數據不足" +msgid "Notification events" +msgstr "通知事件" + +msgid "NotificationEvent|Close issue" +msgstr "關閉議題" + +msgid "NotificationEvent|Close merge request" +msgstr "關閉合併請求" + +msgid "NotificationEvent|Failed pipeline" +msgstr "流水線失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "合併請求被合併" + +msgid "NotificationEvent|New issue" +msgstr "新議題" + +msgid "NotificationEvent|New merge request" +msgstr "新合併請求" + +msgid "NotificationEvent|New note" +msgstr "新評論" + +msgid "NotificationEvent|Reassign issue" +msgstr "重新指派議題" + +msgid "NotificationEvent|Reassign merge request" +msgstr "重新指派合併請求" + +msgid "NotificationEvent|Reopen issue" +msgstr "重新打開議題" + +msgid "NotificationEvent|Successful pipeline" +msgstr "流水線成功完成" + +msgid "NotificationLevel|Custom" +msgstr "自定義" + +msgid "NotificationLevel|Disabled" +msgstr "禁用" + +msgid "NotificationLevel|Global" +msgstr "全局" + +msgid "NotificationLevel|On mention" +msgstr "提及" + +msgid "NotificationLevel|Participate" +msgstr "參與" + +msgid "NotificationLevel|Watch" +msgstr "關注" + msgid "OpenedNDaysAgo|Opened" msgstr "開始於" -msgid "Owner" -msgstr "" - msgid "Pipeline Health" msgstr "流水線健康指標" -msgid "Pipeline Schedule" -msgstr "" +msgid "Project '%{project_name}' queued for deletion." +msgstr "項目 '%{project_name}' 已進入刪除隊列。" -msgid "Pipeline Schedules" -msgstr "" +msgid "Project '%{project_name}' was successfully created." +msgstr "項目 '%{project_name}' 已創建完成。" -msgid "PipelineSchedules|Activated" -msgstr "" +msgid "Project '%{project_name}' was successfully updated." +msgstr "項目 '%{project_name}' 已更新完成。" -msgid "PipelineSchedules|Active" -msgstr "" +msgid "Project '%{project_name}' will be deleted." +msgstr "項目 '%{project_name}' 已刪除。" -msgid "PipelineSchedules|All" -msgstr "" +msgid "Project access must be granted explicitly to each user." +msgstr "訪問項目必須明確授權給每個用戶。" -msgid "PipelineSchedules|Inactive" -msgstr "" +msgid "Project export could not be deleted." +msgstr "無法刪除項目導出。" -msgid "PipelineSchedules|Next Run" -msgstr "" +msgid "Project export has been deleted." +msgstr "項目導出已刪除。" -msgid "PipelineSchedules|None" -msgstr "" +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "項目導出鏈接已過期。請從您的項目設置中生成新的導出。" -msgid "PipelineSchedules|Provide a short description for this pipeline" -msgstr "" +msgid "Project export started. A download link will be sent by email." +msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。" -msgid "PipelineSchedules|Take ownership" -msgstr "" +msgid "Project home" +msgstr "項目主頁" -msgid "PipelineSchedules|Target" -msgstr "" +msgid "ProjectFeature|Disabled" +msgstr "禁用" + +msgid "ProjectFeature|Everyone with access" +msgstr "任何人都可訪問" + +msgid "ProjectFeature|Only team members" +msgstr "只有團隊成員" + +msgid "ProjectFileTree|Name" +msgstr "名稱" + +msgid "ProjectLastActivity|Never" +msgstr "從未" msgid "ProjectLifecycle|Stage" msgstr "項目生命週期" +msgid "ProjectNetworkGraph|Graph" +msgstr "分支圖" + msgid "Read more" msgstr "了解更多" +msgid "Readme" +msgstr "自述文件" + +msgid "RefSwitcher|Branches" +msgstr "分支" + +msgid "RefSwitcher|Tags" +msgstr "標籤" + msgid "Related Commits" msgstr "相關的提交" @@ -196,58 +483,130 @@ msgstr "相關的合併請求" msgid "Related Merged Requests" msgstr "相關已合併的合並請求" -msgid "Save pipeline schedule" -msgstr "" +msgid "Remind later" +msgstr "稍後提醒" -msgid "Schedule a new pipeline" -msgstr "" +msgid "Remove project" +msgstr "刪除項目" -msgid "Select a timezone" -msgstr "" +msgid "Request Access" +msgstr "申請訪問" -msgid "Select target branch" -msgstr "" +msgid "Search branches and tags" +msgstr "搜索分支和標籤" + +msgid "Select Archive Format" +msgstr "選擇下載格式" + +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。" + +msgid "Set up CI" +msgstr "設置 CI" + +msgid "Set up Koding" +msgstr "設置 Koding" + +msgid "Set up auto deploy" +msgstr "設置自動部署" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "設置密碼" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" -msgid "Target Branch" -msgstr "" +msgid "Source code" +msgstr "源代碼" + +msgid "StarProject|Star" +msgstr "星標" + +msgid "Switch branch/tag" +msgstr "切換分支/標籤" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "標籤" -msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." +msgid "Tags" +msgstr "標籤" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" msgid "The collection of events added to the data gathered for that stage." msgstr "與該階段相關的事件。" -msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." +msgid "The fork relationship has been removed." +msgstr "派生關係已被刪除。" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。" msgid "The phase of the development lifecycle." msgstr "項目生命週期中的各個階段。" -msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." msgstr "計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。" -msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。" -msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." +msgid "The project can be accessed by any logged in user." +msgstr "該項目允許已登錄的用戶訪問。" + +msgid "The project can be accessed without any authentication." +msgstr "該項目允許任何人訪問。" + +msgid "The repository for this project does not exist." +msgstr "此項目的存儲庫不存在。" + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." msgstr "評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。" -msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." msgstr "預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" -msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." -msgstr "測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" msgid "The time taken by each data entry gathered by that stage." msgstr "該階段每條數據所花的時間" -msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。" + msgid "Time before an issue gets scheduled" msgstr "議題被列入日程表的時間" @@ -260,6 +619,129 @@ msgstr "從創建合併請求到被合並或關閉的時間" msgid "Time until first merge request" msgstr "創建第壹個合併請求之前的時間" +msgid "Timeago|%s days ago" +msgstr "%s 天前" + +msgid "Timeago|%s days remaining" +msgstr "剩餘 %s 天" + +msgid "Timeago|%s hours remaining" +msgstr "剩餘 %s 小時" + +msgid "Timeago|%s minutes ago" +msgstr "%s 分鐘前" + +msgid "Timeago|%s minutes remaining" +msgstr "剩餘 %s 分鐘" + +msgid "Timeago|%s months ago" +msgstr "%s 個月前" + +msgid "Timeago|%s months remaining" +msgstr "剩餘 %s 月" + +msgid "Timeago|%s seconds remaining" +msgstr "剩餘 %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr "%s 星期前" + +msgid "Timeago|%s weeks remaining" +msgstr "剩餘 %s 星期" + +msgid "Timeago|%s years ago" +msgstr "%s 年前" + +msgid "Timeago|%s years remaining" +msgstr "剩餘 %s 年" + +msgid "Timeago|1 day remaining" +msgstr "剩餘 1 天" + +msgid "Timeago|1 hour remaining" +msgstr "剩餘 1 小時" + +msgid "Timeago|1 minute remaining" +msgstr "剩餘 1 分鐘" + +msgid "Timeago|1 month remaining" +msgstr "剩餘 1 個月" + +msgid "Timeago|1 week remaining" +msgstr "剩餘 1 星期" + +msgid "Timeago|1 year remaining" +msgstr "剩餘 1 年" + +msgid "Timeago|Past due" +msgstr "逾期" + +msgid "Timeago|a day ago" +msgstr "1 天前" + +msgid "Timeago|a month ago" +msgstr "1 個月前" + +msgid "Timeago|a week ago" +msgstr "1 星期前" + +msgid "Timeago|a while" +msgstr "剛剛" + +msgid "Timeago|a year ago" +msgstr "1 年前" + +msgid "Timeago|about %s hours ago" +msgstr "大約 %s 小時前" + +msgid "Timeago|about a minute ago" +msgstr "大約 1 分鐘前" + +msgid "Timeago|about an hour ago" +msgstr "大約 1 小時前" + +msgid "Timeago|in %s days" +msgstr "在 %s 天" + +msgid "Timeago|in %s hours" +msgstr "在 %s 小時" + +msgid "Timeago|in %s minutes" +msgstr "在 %s 分鐘" + +msgid "Timeago|in %s months" +msgstr "在 %s 個月" + +msgid "Timeago|in %s seconds" +msgstr "在 %s 秒" + +msgid "Timeago|in %s weeks" +msgstr "在 %s 星期" + +msgid "Timeago|in %s years" +msgstr "在 %s 年" + +msgid "Timeago|in 1 day" +msgstr "在 1 天" + +msgid "Timeago|in 1 hour" +msgstr "在 1 小時" + +msgid "Timeago|in 1 minute" +msgstr "在 1 分鐘" + +msgid "Timeago|in 1 month" +msgstr "在 1 月" + +msgid "Timeago|in 1 week" +msgstr "在 1 星期" + +msgid "Timeago|in 1 year" +msgstr "在 1 年" + +msgid "Timeago|less than a minute ago" +msgstr "不到 1 分鐘前" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "小時" @@ -277,15 +759,101 @@ msgstr "總時間" msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" +msgid "Unstar" +msgstr "取消" + +msgid "Upload New File" +msgstr "上傳新文件" + +msgid "Upload file" +msgstr "上傳文件" + +msgid "Use your global notification setting" +msgstr "使用全局通知設置" + +msgid "VisibilityLevel|Internal" +msgstr "內部" + +msgid "VisibilityLevel|Private" +msgstr "私有" + +msgid "VisibilityLevel|Public" +msgstr "公開" + msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" msgid "We don't have enough data to show this stage." msgstr "該階段的數據不足,無法顯示。" +msgid "Withdraw Access Request" +msgstr "取消訪問請求" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "您將要刪除 %{project_name_with_namespace}。\n" +"已刪除的項目無法恢復!\n" +"妳確定繼續嗎?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?" + +msgid "You can only add files when you are on a branch" +msgstr "您只能在分支上添加文件" + +msgid "You must sign in to star a project" +msgstr "您必須登錄才能對項目加星標" + msgid "You need permission." msgstr "您需要相關的權限。" +msgid "You will not get any notifications via email" +msgstr "您將不會收到任何通知郵件" + +msgid "You will only receive notifications for the events you choose" +msgstr "您只會收到您選擇的事件通知" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "您只會收到您參與的主題的通知" + +msgid "You will receive notifications for any activity" +msgstr "您將不會收到任何通知" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "您只会收到評論中提及(@)您的通知" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。" + +msgid "Your name" +msgstr "您的名字" + +msgid "committed" +msgstr "提交" + msgid "day" msgid_plural "days" msgstr[0] "天" + +msgid "notification emails" +msgstr "通知郵件" + diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp index e69de29bb2d..0519ecba6ea 100644 --- a/locale/zh_HK/gitlab.po.time_stamp +++ b/locale/zh_HK/gitlab.po.time_stamp @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.1 From a755d7197fdb459b5d37b0b40de1723b0726fea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Thu, 8 Jun 2017 13:21:02 +0800 Subject: add changelog of supplement traditional chinese in hong kong translation --- ...upplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml new file mode 100644 index 00000000000..a5eb71b63c6 --- /dev/null +++ b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page +merge_request: 11995 +author:Huang Tao -- cgit v1.2.1 From 310be19fcfa2eed10665bbfd77ba38510f5caf64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 09:55:50 +0800 Subject: optimize translation based on comments --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 699 ++++------------------------- locale/zh_HK/gitlab.po.time_stamp | 1 - 3 files changed, 81 insertions(+), 621 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 9d4cfeea810..fcd5795c8bb 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 17:36+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-07 10:18-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"About auto deploy":["關於自動部署"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫是只讀的"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml 模板並提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Changelog":["變更日誌"],"Charts":["圖表"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"CreateNewFork|Fork":["派生"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Deploy":["部署"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadSource|Download":["下載"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"ForkedFromProjectPath|Forked from":["離開"],"Forks":["派生"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到您的派生項目"],"GoToYourFork|Fork":["跳轉到您的"],"Home":["主頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["禁用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加一個 SSH 公鑰"],"New Issue":["新議題"],"New branch":["新分支"],"New directory":["新目錄"],"New file":["新文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["禁用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OpenedNDaysAgo|Opened":["開始於"],"Pipeline Health":["流水線健康指標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["訪問項目必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從您的項目設置中生成新的導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目主頁"],"ProjectFeature|Disabled":["禁用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["項目生命週期"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Set a password on your account to pull or push via %{protocol}":["為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n妳確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只会收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"committed":["提交"],"day":["天"],"notification emails":["通知郵件"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-09 01:09-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"ByAuthor|by":["作者:"],"Cancel":["取消"],"Commit":["提交"],"Cron Timezone":["Cron 時區"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Filter":["過濾"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":["所有者"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"ProjectLifecycle|Stage":["階段"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Showing %d event":["顯示 %d 個事件"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 4d8ceac381e..4b02ba19305 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,179 +1,39 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gitlab package. +# FIRST AUTHOR , YEAR. # Huang Tao , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 17:36+0200\n" +"POT-Creation-Date: 2017-06-07 21:22+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-07 10:18-0400\n" +"PO-Revision-Date: 2017-06-09 01:09-0400\n" "Last-Translator: Huang Tao \n" -"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" +"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/" +"teams/75177/zh_HK/)\n" "Language: zh-HK\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Zanata 3.9.6\n" -msgid "About auto deploy" -msgstr "關於自動部署" - -msgid "Activity" -msgstr "活動" - -msgid "Add Changelog" -msgstr "添加變更日誌" - -msgid "Add Contribution guide" -msgstr "添加貢獻指南" - -msgid "Add License" -msgstr "添加許可證" - -msgid "Add an SSH key to your profile to pull or push via SSH." -msgstr "添加一個用於推送或拉取的 SSH 秘鑰到您的個人資料中。" - -msgid "Add new directory" -msgstr "添加新目錄" - -msgid "Archived project! Repository is read-only" -msgstr "歸檔項目!存儲庫是只讀的" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "分支" - -msgid "" -"Branch %{branch_name} was created. To set up auto deploy, " -"choose a GitLab CI Yaml template and commit your changes. " -"%{link_to_autodeploy_doc}" -msgstr "" -"分支 %{branch_name} 已創建。如要設置自動部署, 請選擇 GitLab CI Yaml " -"模板並提交更改。%{link_to_autodeploy_doc}" - -msgid "Branches" -msgstr "分支" +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "您確定要刪除此流水線計劃嗎?" msgid "ByAuthor|by" msgstr "作者:" -msgid "CI configuration" -msgstr "CI 配置" - -msgid "Changelog" -msgstr "變更日誌" - -msgid "Charts" -msgstr "圖表" - -msgid "CiStatusLabel|canceled" -msgstr "已取消" - -msgid "CiStatusLabel|created" -msgstr "已創建" - -msgid "CiStatusLabel|failed" -msgstr "已失敗" - -msgid "CiStatusLabel|manual action" -msgstr "手動操作" - -msgid "CiStatusLabel|passed" -msgstr "已通過" - -msgid "CiStatusLabel|passed with warnings" -msgstr "已通過但有警告" - -msgid "CiStatusLabel|pending" -msgstr "等待中" - -msgid "CiStatusLabel|skipped" -msgstr "已跳過" - -msgid "CiStatusLabel|waiting for manual action" -msgstr "等待手動操作" - -msgid "CiStatusText|blocked" -msgstr "已阻塞" - -msgid "CiStatusText|canceled" -msgstr "已取消" - -msgid "CiStatusText|created" -msgstr "已創建" - -msgid "CiStatusText|failed" -msgstr "已失敗" - -msgid "CiStatusText|manual" -msgstr "待手動" - -msgid "CiStatusText|passed" -msgstr "已通過" - -msgid "CiStatusText|pending" -msgstr "等待中" - -msgid "CiStatusText|skipped" -msgstr "已跳過" - -msgid "CiStatus|running" -msgstr "運行中" +msgid "Cancel" +msgstr "取消" msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" -msgid "CommitMessage|Add %{file_name}" -msgstr "添加 %{file_name}" - -msgid "Commits" -msgstr "提交" - -msgid "Commits|History" -msgstr "歷史" - -msgid "Compare" -msgstr "比較" - -msgid "Contribution guide" -msgstr "貢獻指南" - -msgid "Contributors" -msgstr "貢獻者" - -msgid "Copy URL to clipboard" -msgstr "複製URL到剪貼板" - -msgid "Copy commit SHA to clipboard" -msgstr "複製提交 SHA 到剪貼板" - -msgid "Create New Directory" -msgstr "創建新目錄" - -msgid "Create directory" -msgstr "創建目錄" - -msgid "Create empty bare repository" -msgstr "創建空的存儲庫" - -msgid "Create merge request" -msgstr "創建合併請求" - -msgid "CreateNewFork|Fork" -msgstr "派生" - -msgid "Custom notification events" -msgstr "自定義通知事件" - -msgid "" -"Custom notification levels are the same as participating levels. With custom " -"notification levels you will also receive notifications for select events. " -"To find out more, check out %{notification_link}." -msgstr "" -"自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}." - -msgid "Cycle Analytics" -msgstr "週期分析" +msgid "Cron Timezone" +msgstr "Cron 時區" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " @@ -201,42 +61,30 @@ msgstr "預發布" msgid "CycleAnalyticsStage|Test" msgstr "測試" +msgid "Delete" +msgstr "刪除" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" -msgid "Directory name" -msgstr "目錄名稱" - -msgid "Don't show again" -msgstr "不再顯示" - -msgid "Download tar" -msgstr "下載 tar" - -msgid "Download tar.bz2" -msgstr "下載 tar.bz2" +msgid "Description" +msgstr "描述" -msgid "Download tar.gz" -msgstr "下載 tar.gz" +msgid "Edit" +msgstr "編輯" -msgid "Download zip" -msgstr "下載 zip" +msgid "Edit Pipeline Schedule %{id}" +msgstr "編輯 %{id} 流水線計劃" -msgid "DownloadArtifacts|Download" -msgstr "下載" +msgid "Failed to change the owner" +msgstr "無法變更所有者" -msgid "DownloadSource|Download" -msgstr "下載" +msgid "Failed to remove the pipeline schedule" +msgstr "無法刪除流水線計劃" -msgid "Files" -msgstr "文件" - -msgid "Find by path" -msgstr "按路徑查找" - -msgid "Find file" -msgstr "查找文件" +msgid "Filter" +msgstr "過濾" msgid "FirstPushedBy|First" msgstr "首次推送" @@ -244,57 +92,24 @@ msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" -msgid "ForkedFromProjectPath|Forked from" -msgstr "離開" - -msgid "Forks" -msgstr "派生" - msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" -msgid "Go to your fork" -msgstr "跳轉到您的派生項目" - -msgid "GoToYourFork|Fork" -msgstr "跳轉到您的" - -msgid "Home" -msgstr "主頁" - -msgid "Housekeeping successfully started" -msgstr "已開始維護" - -msgid "Import repository" -msgstr "導入存儲庫" +msgid "Interval Pattern" +msgstr "間隔模式" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" -msgid "LFSStatus|Disabled" -msgstr "禁用" - -msgid "LFSStatus|Enabled" -msgstr "啟用" - msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最後 %d 天" -msgid "Last Update" -msgstr "最後更新" - -msgid "Last commit" -msgstr "最後提交" - -msgid "Leave group" -msgstr "離開群組" - -msgid "Leave project" -msgstr "離開項目" +msgid "Last Pipeline" +msgstr "最新流水線" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" @@ -303,36 +118,15 @@ msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" -msgid "MissingSSHKeyWarningLink|add an SSH key" -msgstr "添加一個 SSH 公鑰" - msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新議題" -msgid "New branch" -msgstr "新分支" - -msgid "New directory" -msgstr "新目錄" - -msgid "New file" -msgstr "新文件" - -msgid "New issue" -msgstr "新議題" - -msgid "New merge request" -msgstr "新合併請求" +msgid "New Pipeline Schedule" +msgstr "創建流水線計劃" -msgid "New snippet" -msgstr "新代碼片段" - -msgid "New tag" -msgstr "新標籤" - -msgid "No repository" -msgstr "沒有存儲庫" +msgid "No schedules" +msgstr "沒有計劃" msgid "Not available" msgstr "不可用" @@ -340,131 +134,54 @@ msgstr "不可用" msgid "Not enough data" msgstr "數據不足" -msgid "Notification events" -msgstr "通知事件" - -msgid "NotificationEvent|Close issue" -msgstr "關閉議題" - -msgid "NotificationEvent|Close merge request" -msgstr "關閉合併請求" - -msgid "NotificationEvent|Failed pipeline" -msgstr "流水線失敗" - -msgid "NotificationEvent|Merge merge request" -msgstr "合併請求被合併" - -msgid "NotificationEvent|New issue" -msgstr "新議題" - -msgid "NotificationEvent|New merge request" -msgstr "新合併請求" - -msgid "NotificationEvent|New note" -msgstr "新評論" - -msgid "NotificationEvent|Reassign issue" -msgstr "重新指派議題" - -msgid "NotificationEvent|Reassign merge request" -msgstr "重新指派合併請求" - -msgid "NotificationEvent|Reopen issue" -msgstr "重新打開議題" - -msgid "NotificationEvent|Successful pipeline" -msgstr "流水線成功完成" - -msgid "NotificationLevel|Custom" -msgstr "自定義" - -msgid "NotificationLevel|Disabled" -msgstr "禁用" - -msgid "NotificationLevel|Global" -msgstr "全局" - -msgid "NotificationLevel|On mention" -msgstr "提及" - -msgid "NotificationLevel|Participate" -msgstr "參與" - -msgid "NotificationLevel|Watch" -msgstr "關注" - msgid "OpenedNDaysAgo|Opened" msgstr "開始於" +msgid "Owner" +msgstr "所有者" + msgid "Pipeline Health" msgstr "流水線健康指標" -msgid "Project '%{project_name}' queued for deletion." -msgstr "項目 '%{project_name}' 已進入刪除隊列。" - -msgid "Project '%{project_name}' was successfully created." -msgstr "項目 '%{project_name}' 已創建完成。" - -msgid "Project '%{project_name}' was successfully updated." -msgstr "項目 '%{project_name}' 已更新完成。" +msgid "Pipeline Schedule" +msgstr "流水線計劃" -msgid "Project '%{project_name}' will be deleted." -msgstr "項目 '%{project_name}' 已刪除。" +msgid "Pipeline Schedules" +msgstr "流水線計劃" -msgid "Project access must be granted explicitly to each user." -msgstr "訪問項目必須明確授權給每個用戶。" +msgid "PipelineSchedules|Activated" +msgstr "已激活" -msgid "Project export could not be deleted." -msgstr "無法刪除項目導出。" +msgid "PipelineSchedules|Active" +msgstr "激活" -msgid "Project export has been deleted." -msgstr "項目導出已刪除。" +msgid "PipelineSchedules|All" +msgstr "所有" -msgid "" -"Project export link has expired. Please generate a new export from your " -"project settings." -msgstr "項目導出鏈接已過期。請從您的項目設置中生成新的導出。" - -msgid "Project export started. A download link will be sent by email." -msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。" - -msgid "Project home" -msgstr "項目主頁" +msgid "PipelineSchedules|Inactive" +msgstr "等待" -msgid "ProjectFeature|Disabled" -msgstr "禁用" +msgid "PipelineSchedules|Next Run" +msgstr "下壹個運行" -msgid "ProjectFeature|Everyone with access" -msgstr "任何人都可訪問" +msgid "PipelineSchedules|None" +msgstr "無" -msgid "ProjectFeature|Only team members" -msgstr "只有團隊成員" +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "為此流水線提供簡短描述" -msgid "ProjectFileTree|Name" -msgstr "名稱" +msgid "PipelineSchedules|Take ownership" +msgstr "取得所有權" -msgid "ProjectLastActivity|Never" -msgstr "從未" +msgid "PipelineSchedules|Target" +msgstr "目標" msgid "ProjectLifecycle|Stage" -msgstr "項目生命週期" - -msgid "ProjectNetworkGraph|Graph" -msgstr "分支圖" +msgstr "階段" msgid "Read more" msgstr "了解更多" -msgid "Readme" -msgstr "自述文件" - -msgid "RefSwitcher|Branches" -msgstr "分支" - -msgid "RefSwitcher|Tags" -msgstr "標籤" - msgid "Related Commits" msgstr "相關的提交" @@ -481,75 +198,41 @@ msgid "Related Merge Requests" msgstr "相關的合併請求" msgid "Related Merged Requests" -msgstr "相關已合併的合並請求" - -msgid "Remind later" -msgstr "稍後提醒" - -msgid "Remove project" -msgstr "刪除項目" - -msgid "Request Access" -msgstr "申請訪問" - -msgid "Search branches and tags" -msgstr "搜索分支和標籤" - -msgid "Select Archive Format" -msgstr "選擇下載格式" +msgstr "相關已合併的合併請求" -msgid "Set a password on your account to pull or push via %{protocol}" -msgstr "為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。" +msgid "Save pipeline schedule" +msgstr "保存流水線計劃" -msgid "Set up CI" -msgstr "設置 CI" +msgid "Schedule a new pipeline" +msgstr "新增流水線計劃" -msgid "Set up Koding" -msgstr "設置 Koding" +msgid "Select a timezone" +msgstr "選擇時區" -msgid "Set up auto deploy" -msgstr "設置自動部署" - -msgid "SetPasswordToCloneLink|set a password" -msgstr "設置密碼" +msgid "Select target branch" +msgstr "選擇目標分支" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" -msgid "Source code" -msgstr "源代碼" - -msgid "StarProject|Star" -msgstr "星標" - -msgid "Switch branch/tag" -msgstr "切換分支/標籤" - -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "標籤" - -msgid "Tags" -msgstr "標籤" +msgid "Target Branch" +msgstr "目標分支" msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " "first merge request." -msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。" msgid "The collection of events added to the data gathered for that stage." msgstr "與該階段相關的事件。" -msgid "The fork relationship has been removed." -msgstr "派生關係已被刪除。" - msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." -msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。" +msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。" msgid "The phase of the development lifecycle." msgstr "項目生命週期中的各個階段。" @@ -566,26 +249,17 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。" -msgid "The project can be accessed by any logged in user." -msgstr "該項目允許已登錄的用戶訪問。" - -msgid "The project can be accessed without any authentication." -msgstr "該項目允許任何人訪問。" - -msgid "The repository for this project does not exist." -msgstr "此項目的存儲庫不存在。" - msgid "" "The review stage shows the time from creating the merge request to merging " "it. The data will automatically be added after you merge your first merge " "request." -msgstr "評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。" +msgstr "評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。" msgid "" "The staging stage shows the time between merging the MR and deploying code " "to the production environment. The data will be automatically added once you " "deploy to production for the first time." -msgstr "預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" +msgstr "預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" msgid "" "The testing stage shows the time GitLab CI takes to run every pipeline for " @@ -600,12 +274,7 @@ msgid "" "The value lying at the midpoint of a series of observed values. E.g., " "between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" " 6." -msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" - -msgid "" -"This means you can not push code until you create an empty repository or " -"import existing one." -msgstr "在創建一個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。" +msgstr "中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" msgid "Time before an issue gets scheduled" msgstr "議題被列入日程表的時間" @@ -614,134 +283,11 @@ msgid "Time before an issue starts implementation" msgstr "開始進行編碼前的時間" msgid "Time between merge request creation and merge/close" -msgstr "從創建合併請求到被合並或關閉的時間" +msgstr "從創建合併請求到被合併或關閉的時間" msgid "Time until first merge request" msgstr "創建第壹個合併請求之前的時間" -msgid "Timeago|%s days ago" -msgstr "%s 天前" - -msgid "Timeago|%s days remaining" -msgstr "剩餘 %s 天" - -msgid "Timeago|%s hours remaining" -msgstr "剩餘 %s 小時" - -msgid "Timeago|%s minutes ago" -msgstr "%s 分鐘前" - -msgid "Timeago|%s minutes remaining" -msgstr "剩餘 %s 分鐘" - -msgid "Timeago|%s months ago" -msgstr "%s 個月前" - -msgid "Timeago|%s months remaining" -msgstr "剩餘 %s 月" - -msgid "Timeago|%s seconds remaining" -msgstr "剩餘 %s 秒" - -msgid "Timeago|%s weeks ago" -msgstr "%s 星期前" - -msgid "Timeago|%s weeks remaining" -msgstr "剩餘 %s 星期" - -msgid "Timeago|%s years ago" -msgstr "%s 年前" - -msgid "Timeago|%s years remaining" -msgstr "剩餘 %s 年" - -msgid "Timeago|1 day remaining" -msgstr "剩餘 1 天" - -msgid "Timeago|1 hour remaining" -msgstr "剩餘 1 小時" - -msgid "Timeago|1 minute remaining" -msgstr "剩餘 1 分鐘" - -msgid "Timeago|1 month remaining" -msgstr "剩餘 1 個月" - -msgid "Timeago|1 week remaining" -msgstr "剩餘 1 星期" - -msgid "Timeago|1 year remaining" -msgstr "剩餘 1 年" - -msgid "Timeago|Past due" -msgstr "逾期" - -msgid "Timeago|a day ago" -msgstr "1 天前" - -msgid "Timeago|a month ago" -msgstr "1 個月前" - -msgid "Timeago|a week ago" -msgstr "1 星期前" - -msgid "Timeago|a while" -msgstr "剛剛" - -msgid "Timeago|a year ago" -msgstr "1 年前" - -msgid "Timeago|about %s hours ago" -msgstr "大約 %s 小時前" - -msgid "Timeago|about a minute ago" -msgstr "大約 1 分鐘前" - -msgid "Timeago|about an hour ago" -msgstr "大約 1 小時前" - -msgid "Timeago|in %s days" -msgstr "在 %s 天" - -msgid "Timeago|in %s hours" -msgstr "在 %s 小時" - -msgid "Timeago|in %s minutes" -msgstr "在 %s 分鐘" - -msgid "Timeago|in %s months" -msgstr "在 %s 個月" - -msgid "Timeago|in %s seconds" -msgstr "在 %s 秒" - -msgid "Timeago|in %s weeks" -msgstr "在 %s 星期" - -msgid "Timeago|in %s years" -msgstr "在 %s 年" - -msgid "Timeago|in 1 day" -msgstr "在 1 天" - -msgid "Timeago|in 1 hour" -msgstr "在 1 小時" - -msgid "Timeago|in 1 minute" -msgstr "在 1 分鐘" - -msgid "Timeago|in 1 month" -msgstr "在 1 月" - -msgid "Timeago|in 1 week" -msgstr "在 1 星期" - -msgid "Timeago|in 1 year" -msgstr "在 1 年" - -msgid "Timeago|less than a minute ago" -msgstr "不到 1 分鐘前" - msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "小時" @@ -759,101 +305,16 @@ msgstr "總時間" msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" -msgid "Unstar" -msgstr "取消" - -msgid "Upload New File" -msgstr "上傳新文件" - -msgid "Upload file" -msgstr "上傳文件" - -msgid "Use your global notification setting" -msgstr "使用全局通知設置" - -msgid "VisibilityLevel|Internal" -msgstr "內部" - -msgid "VisibilityLevel|Private" -msgstr "私有" - -msgid "VisibilityLevel|Public" -msgstr "公開" - msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" msgid "We don't have enough data to show this stage." msgstr "該階段的數據不足,無法顯示。" -msgid "Withdraw Access Request" -msgstr "取消訪問請求" - -msgid "" -"You are going to remove %{project_name_with_namespace}.\n" -"Removed project CANNOT be restored!\n" -"Are you ABSOLUTELY sure?" -msgstr "您將要刪除 %{project_name_with_namespace}。\n" -"已刪除的項目無法恢復!\n" -"妳確定繼續嗎?" - -msgid "" -"You are going to remove the fork relationship to source project " -"%{forked_from_project}. Are you ABSOLUTELY sure?" -msgstr "將刪除與源項目 %{forked_from_project} 的派生關係。妳確定繼續嗎?" - -msgid "" -"You are going to transfer %{project_name_with_namespace} to another owner. " -"Are you ABSOLUTELY sure?" -msgstr "將 %{project_name_with_namespace} 轉義給另一個所有者。妳確定繼續嗎?" - -msgid "You can only add files when you are on a branch" -msgstr "您只能在分支上添加文件" - -msgid "You must sign in to star a project" -msgstr "您必須登錄才能對項目加星標" - msgid "You need permission." msgstr "您需要相關的權限。" -msgid "You will not get any notifications via email" -msgstr "您將不會收到任何通知郵件" - -msgid "You will only receive notifications for the events you choose" -msgstr "您只會收到您選擇的事件通知" - -msgid "" -"You will only receive notifications for threads you have participated in" -msgstr "您只會收到您參與的主題的通知" - -msgid "You will receive notifications for any activity" -msgstr "您將不會收到任何通知" - -msgid "" -"You will receive notifications only for comments in which you were " -"@mentioned" -msgstr "您只会收到評論中提及(@)您的通知" - -msgid "" -"You won't be able to pull or push project code via %{protocol} until you " -"%{set_password_link} on your account" -msgstr "在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。" - -msgid "" -"You won't be able to pull or push project code via SSH until you " -"%{add_ssh_key_link} to your profile" -msgstr "在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。" - -msgid "Your name" -msgstr "您的名字" - -msgid "committed" -msgstr "提交" - msgid "day" msgid_plural "days" msgstr[0] "天" -msgid "notification emails" -msgstr "通知郵件" - diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp index 0519ecba6ea..e69de29bb2d 100644 --- a/locale/zh_HK/gitlab.po.time_stamp +++ b/locale/zh_HK/gitlab.po.time_stamp @@ -1 +0,0 @@ - \ No newline at end of file -- cgit v1.2.1 From 9a849468716655f3656e80bd6b1948329ad08285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 11:25:16 +0800 Subject: Optimization 'zh_HK' translation 1. Fix missing translations --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 763 ++++++++++++++++++++++++++++- locale/zh_HK/gitlab.po.time_stamp | 1 + 3 files changed, 755 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index fcd5795c8bb..bfc6f7e5db4 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-09 01:09-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"ByAuthor|by":["作者:"],"Cancel":["取消"],"Commit":["提交"],"Cron Timezone":["Cron 時區"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Filter":["過濾"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":["所有者"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"ProjectLifecycle|Stage":["階段"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Showing %d event":["顯示 %d 個事件"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 11:08-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["激活"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到您的個人賬戶中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["變更日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["作者:"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標簽"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每天(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月1日(淩晨4點)"],"Every week (Sundays at 4:00am)":["每周日(淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到您的派生項目"],"GoToYourFork|Fork":["跳轉到您的派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["禁用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New schedule":["新计划"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["禁用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["選項"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["禁用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["包含這些更改到 新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n您確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只會收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 4b02ba19305..22fe5e4f807 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,40 +1,240 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the gitlab package. -# FIRST AUTHOR , YEAR. # Huang Tao , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 21:22+0200\n" +"POT-Creation-Date: 2017-06-12 19:29-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-09 01:09-0400\n" +"PO-Revision-Date: 2017-06-12 11:08-0400\n" "Last-Translator: Huang Tao \n" -"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/" -"teams/75177/zh_HK/)\n" +"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Zanata 3.9.6\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "由 %{commit_author_link} 提交於 %{commit_timeago}" + +msgid "About auto deploy" +msgstr "關於自動部署" + +msgid "Active" +msgstr "激活" + +msgid "Activity" +msgstr "活動" + +msgid "Add Changelog" +msgstr "添加變更日誌" + +msgid "Add Contribution guide" +msgstr "添加貢獻指南" + +msgid "Add License" +msgstr "添加許可證" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "新增壹個用於推送或拉取的 SSH 秘鑰到您的個人賬戶中。" + +msgid "Add new directory" +msgstr "添加新目錄" + +msgid "Archived project! Repository is read-only" +msgstr "歸檔項目!存儲庫為只讀" + msgid "Are you sure you want to delete this pipeline schedule?" msgstr "您確定要刪除此流水線計劃嗎?" +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "拖放文件到此處或者 %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "分支" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml " +"模板併提交更改。%{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "分支" + +msgid "Browse files" +msgstr "瀏覽文件" + msgid "ByAuthor|by" msgstr "作者:" +msgid "CI configuration" +msgstr "CI 配置" + msgid "Cancel" msgstr "取消" +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "挑選到分支" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "還原分支" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "優選" + +msgid "ChangeType|commit" +msgstr "提交" + +msgid "ChangeType|merge request" +msgstr "合併請求" + +msgid "Changelog" +msgstr "變更日誌" + +msgid "Charts" +msgstr "統計圖" + +msgid "Cherry-pick this commit" +msgstr "優選此提交" + +msgid "Cherry-pick this merge-request" +msgstr "優選此合併請求" + +msgid "CiStatusLabel|canceled" +msgstr "已取消" + +msgid "CiStatusLabel|created" +msgstr "已創建" + +msgid "CiStatusLabel|failed" +msgstr "已失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動操作" + +msgid "CiStatusLabel|passed" +msgstr "已通過" + +msgid "CiStatusLabel|passed with warnings" +msgstr "已通過但有警告" + +msgid "CiStatusLabel|pending" +msgstr "等待中" + +msgid "CiStatusLabel|skipped" +msgstr "已跳過" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "等待手動操作" + +msgid "CiStatusText|blocked" +msgstr "已阻塞" + +msgid "CiStatusText|canceled" +msgstr "已取消" + +msgid "CiStatusText|created" +msgstr "已創建" + +msgid "CiStatusText|failed" +msgstr "已失敗" + +msgid "CiStatusText|manual" +msgstr "待手動" + +msgid "CiStatusText|passed" +msgstr "已通過" + +msgid "CiStatusText|pending" +msgstr "等待中" + +msgid "CiStatusText|skipped" +msgstr "已跳過" + +msgid "CiStatus|running" +msgstr "運行中" + msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "Commit message" +msgstr "提交信息" + +msgid "CommitMessage|Add %{file_name}" +msgstr "添加 %{file_name}" + +msgid "Commits" +msgstr "提交" + +msgid "Commits|History" +msgstr "歷史" + +msgid "Committed by" +msgstr "作者:" + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "貢獻指南" + +msgid "Contributors" +msgstr "貢獻者" + +msgid "Copy URL to clipboard" +msgstr "複製URL到剪貼板" + +msgid "Copy commit SHA to clipboard" +msgstr "複製提交 SHA 到剪貼板" + +msgid "Create New Directory" +msgstr "創建新目錄" + +msgid "Create directory" +msgstr "創建目錄" + +msgid "Create empty bare repository" +msgstr "創建空的存儲庫" + +msgid "Create merge request" +msgstr "創建合併請求" + +msgid "Create new..." +msgstr "創建..." + +msgid "CreateNewFork|Fork" +msgstr "派生" + +msgid "CreateTag|Tag" +msgstr "標簽" + msgid "Cron Timezone" msgstr "Cron 時區" +msgid "Cron syntax" +msgstr "Cron 語法" + +msgid "Custom" +msgstr "自定義" + +msgid "Custom notification events" +msgstr "自定義通知事件" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}." + +msgid "Cycle Analytics" +msgstr "週期分析" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -61,6 +261,9 @@ msgstr "預發布" msgid "CycleAnalyticsStage|Test" msgstr "測試" +msgid "Define a custom pattern with cron syntax" +msgstr "使用 Cron 語法定義自定義模式" + msgid "Delete" msgstr "刪除" @@ -71,20 +274,68 @@ msgstr[0] "部署" msgid "Description" msgstr "描述" +msgid "Directory name" +msgstr "目錄名稱" + +msgid "Don't show again" +msgstr "不再顯示" + +msgid "Download" +msgstr "下載" + +msgid "Download tar" +msgstr "下載 tar" + +msgid "Download tar.bz2" +msgstr "下載 tar.bz2" + +msgid "Download tar.gz" +msgstr "下載 tar.gz" + +msgid "Download zip" +msgstr "下載 zip" + +msgid "DownloadArtifacts|Download" +msgstr "下載" + +msgid "DownloadCommit|Email Patches" +msgstr "電子郵件補丁" + +msgid "DownloadCommit|Plain Diff" +msgstr "Diff 文件" + +msgid "DownloadSource|Download" +msgstr "下載" + msgid "Edit" msgstr "編輯" msgid "Edit Pipeline Schedule %{id}" msgstr "編輯 %{id} 流水線計劃" +msgid "Every day (at 4:00am)" +msgstr "每天(淩晨4點)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "每月1日(淩晨4點)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "每周日(淩晨4點)" + msgid "Failed to change the owner" msgstr "無法變更所有者" msgid "Failed to remove the pipeline schedule" msgstr "無法刪除流水線計劃" -msgid "Filter" -msgstr "過濾" +msgid "Files" +msgstr "文件" + +msgid "Find by path" +msgstr "按路徑查找" + +msgid "Find file" +msgstr "查找文件" msgid "FirstPushedBy|First" msgstr "首次推送" @@ -92,18 +343,46 @@ msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "派生" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "派生自" + msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" +msgid "Go to your fork" +msgstr "跳轉到您的派生項目" + +msgid "GoToYourFork|Fork" +msgstr "跳轉到您的派生項目" + +msgid "Home" +msgstr "首頁" + +msgid "Housekeeping successfully started" +msgstr "已開始維護" + +msgid "Import repository" +msgstr "導入存儲庫" + msgid "Interval Pattern" msgstr "間隔模式" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "LFSStatus|Disabled" +msgstr "禁用" + +msgid "LFSStatus|Enabled" +msgstr "啟用" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "最後 %d 天" @@ -111,6 +390,21 @@ msgstr[0] "最後 %d 天" msgid "Last Pipeline" msgstr "最新流水線" +msgid "Last Update" +msgstr "最後更新" + +msgid "Last commit" +msgstr "最後提交" + +msgid "Learn more in the" +msgstr "了解更多" + +msgid "Leave group" +msgstr "離開群組" + +msgid "Leave project" +msgstr "離開項目" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "最多顯示 %d 個事件" @@ -118,6 +412,9 @@ msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "添加壹個 SSH 公鑰" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "新議題" @@ -125,6 +422,33 @@ msgstr[0] "新議題" msgid "New Pipeline Schedule" msgstr "創建流水線計劃" +msgid "New branch" +msgstr "新分支" + +msgid "New directory" +msgstr "新增目錄" + +msgid "New file" +msgstr "新增文件" + +msgid "New issue" +msgstr "新議題" + +msgid "New merge request" +msgstr "新合併請求" + +msgid "New schedule" +msgstr "新计划" + +msgid "New snippet" +msgstr "新代碼片段" + +msgid "New tag" +msgstr "新標籤" + +msgid "No repository" +msgstr "沒有存儲庫" + msgid "No schedules" msgstr "沒有計劃" @@ -134,12 +458,75 @@ msgstr "不可用" msgid "Not enough data" msgstr "數據不足" +msgid "Notification events" +msgstr "通知事件" + +msgid "NotificationEvent|Close issue" +msgstr "關閉議題" + +msgid "NotificationEvent|Close merge request" +msgstr "關閉合併請求" + +msgid "NotificationEvent|Failed pipeline" +msgstr "流水線失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "合併請求被合併" + +msgid "NotificationEvent|New issue" +msgstr "新議題" + +msgid "NotificationEvent|New merge request" +msgstr "新合併請求" + +msgid "NotificationEvent|New note" +msgstr "新評論" + +msgid "NotificationEvent|Reassign issue" +msgstr "重新指派議題" + +msgid "NotificationEvent|Reassign merge request" +msgstr "重新指派合併請求" + +msgid "NotificationEvent|Reopen issue" +msgstr "重新打開議題" + +msgid "NotificationEvent|Successful pipeline" +msgstr "流水線成功完成" + +msgid "NotificationLevel|Custom" +msgstr "自定義" + +msgid "NotificationLevel|Disabled" +msgstr "禁用" + +msgid "NotificationLevel|Global" +msgstr "全局" + +msgid "NotificationLevel|On mention" +msgstr "提及" + +msgid "NotificationLevel|Participate" +msgstr "參與" + +msgid "NotificationLevel|Watch" +msgstr "關注" + +msgid "OfSearchInADropdown|Filter" +msgstr "篩選" + msgid "OpenedNDaysAgo|Opened" msgstr "開始於" +msgid "Options" +msgstr "選項" + msgid "Owner" msgstr "所有者" +msgid "Pipeline" +msgstr "流水線" + msgid "Pipeline Health" msgstr "流水線健康指標" @@ -176,12 +563,71 @@ msgstr "取得所有權" msgid "PipelineSchedules|Target" msgstr "目標" +msgid "Project '%{project_name}' queued for deletion." +msgstr "項目 '%{project_name}' 已進入刪除隊列。" + +msgid "Project '%{project_name}' was successfully created." +msgstr "項目 '%{project_name}' 已創建完成。" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "項目 '%{project_name}' 已更新完成。" + +msgid "Project '%{project_name}' will be deleted." +msgstr "項目 '%{project_name}' 已刪除。" + +msgid "Project access must be granted explicitly to each user." +msgstr "項目訪問權限必須明確授權給每個用戶。" + +msgid "Project export could not be deleted." +msgstr "無法刪除項目導出。" + +msgid "Project export has been deleted." +msgstr "項目導出已刪除。" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "項目導出鏈接已過期。請從項目設置中重新生成項目導出。" + +msgid "Project export started. A download link will be sent by email." +msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。" + +msgid "Project home" +msgstr "項目首頁" + +msgid "ProjectFeature|Disabled" +msgstr "禁用" + +msgid "ProjectFeature|Everyone with access" +msgstr "任何人都可訪問" + +msgid "ProjectFeature|Only team members" +msgstr "只有團隊成員" + +msgid "ProjectFileTree|Name" +msgstr "名稱" + +msgid "ProjectLastActivity|Never" +msgstr "從未" + msgid "ProjectLifecycle|Stage" msgstr "階段" +msgid "ProjectNetworkGraph|Graph" +msgstr "分支圖" + msgid "Read more" msgstr "了解更多" +msgid "Readme" +msgstr "自述文件" + +msgid "RefSwitcher|Branches" +msgstr "分支" + +msgid "RefSwitcher|Tags" +msgstr "標籤" + msgid "Related Commits" msgstr "相關的提交" @@ -200,22 +646,80 @@ msgstr "相關的合併請求" msgid "Related Merged Requests" msgstr "相關已合併的合併請求" +msgid "Remind later" +msgstr "稍後提醒" + +msgid "Remove project" +msgstr "刪除項目" + +msgid "Request Access" +msgstr "申請訪問" + +msgid "Revert this commit" +msgstr "還原此提交" + +msgid "Revert this merge-request" +msgstr "還原此合併請求" + msgid "Save pipeline schedule" msgstr "保存流水線計劃" msgid "Schedule a new pipeline" msgstr "新增流水線計劃" +msgid "Scheduling Pipelines" +msgstr "流水線計劃" + +msgid "Search branches and tags" +msgstr "搜索分支和標籤" + +msgid "Select Archive Format" +msgstr "選擇下載格式" + msgid "Select a timezone" msgstr "選擇時區" msgid "Select target branch" msgstr "選擇目標分支" +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。" + +msgid "Set up CI" +msgstr "設置 CI" + +msgid "Set up Koding" +msgstr "設置 Koding" + +msgid "Set up auto deploy" +msgstr "設置自動部署" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "設置密碼" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" +msgid "Source code" +msgstr "源代碼" + +msgid "StarProject|Star" +msgstr "星標" + +msgid "Start a new merge request with these changes" +msgstr "包含這些更改到 新合併請求" + +msgid "Switch branch/tag" +msgstr "切換分支/標籤" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "標籤" + +msgid "Tags" +msgstr "標籤" + msgid "Target Branch" msgstr "目標分支" @@ -228,6 +732,9 @@ msgstr "編碼階段概述了從第壹次提交到創建合併請求的時間。 msgid "The collection of events added to the data gathered for that stage." msgstr "與該階段相關的事件。" +msgid "The fork relationship has been removed." +msgstr "派生關係已被刪除。" + msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -237,6 +744,12 @@ msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議 msgid "The phase of the development lifecycle." msgstr "項目生命週期中的各個階段。" +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。" + msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " @@ -249,6 +762,15 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。" +msgid "The project can be accessed by any logged in user." +msgstr "該項目允許已登錄的用戶訪問。" + +msgid "The project can be accessed without any authentication." +msgstr "該項目允許任何人訪問。" + +msgid "The repository for this project does not exist." +msgstr "此項目的存儲庫不存在。" + msgid "" "The review stage shows the time from creating the merge request to merging " "it. The data will automatically be added after you merge your first merge " @@ -276,6 +798,11 @@ msgid "" " 6." msgstr "中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。" + msgid "Time before an issue gets scheduled" msgstr "議題被列入日程表的時間" @@ -288,6 +815,129 @@ msgstr "從創建合併請求到被合併或關閉的時間" msgid "Time until first merge request" msgstr "創建第壹個合併請求之前的時間" +msgid "Timeago|%s days ago" +msgstr "%s 天前" + +msgid "Timeago|%s days remaining" +msgstr "剩餘 %s 天" + +msgid "Timeago|%s hours remaining" +msgstr "剩餘 %s 小時" + +msgid "Timeago|%s minutes ago" +msgstr "%s 分鐘前" + +msgid "Timeago|%s minutes remaining" +msgstr "剩餘 %s 分鐘" + +msgid "Timeago|%s months ago" +msgstr "%s 個月前" + +msgid "Timeago|%s months remaining" +msgstr "剩餘 %s 月" + +msgid "Timeago|%s seconds remaining" +msgstr "剩餘 %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr "%s 星期前" + +msgid "Timeago|%s weeks remaining" +msgstr "剩餘 %s 星期" + +msgid "Timeago|%s years ago" +msgstr "%s 年前" + +msgid "Timeago|%s years remaining" +msgstr "剩餘 %s 年" + +msgid "Timeago|1 day remaining" +msgstr "剩餘 1 天" + +msgid "Timeago|1 hour remaining" +msgstr "剩餘 1 小時" + +msgid "Timeago|1 minute remaining" +msgstr "剩餘 1 分鐘" + +msgid "Timeago|1 month remaining" +msgstr "剩餘 1 個月" + +msgid "Timeago|1 week remaining" +msgstr "剩餘 1 星期" + +msgid "Timeago|1 year remaining" +msgstr "剩餘 1 年" + +msgid "Timeago|Past due" +msgstr "逾期" + +msgid "Timeago|a day ago" +msgstr "1 天前" + +msgid "Timeago|a month ago" +msgstr "1 個月前" + +msgid "Timeago|a week ago" +msgstr "1 星期前" + +msgid "Timeago|a while" +msgstr "剛剛" + +msgid "Timeago|a year ago" +msgstr "1 年前" + +msgid "Timeago|about %s hours ago" +msgstr "大約 %s 小時前" + +msgid "Timeago|about a minute ago" +msgstr "大約 1 分鐘前" + +msgid "Timeago|about an hour ago" +msgstr "大約 1 小時前" + +msgid "Timeago|in %s days" +msgstr "在 %s 天" + +msgid "Timeago|in %s hours" +msgstr "在 %s 小時" + +msgid "Timeago|in %s minutes" +msgstr "在 %s 分鐘" + +msgid "Timeago|in %s months" +msgstr "在 %s 個月" + +msgid "Timeago|in %s seconds" +msgstr "在 %s 秒" + +msgid "Timeago|in %s weeks" +msgstr "在 %s 星期" + +msgid "Timeago|in %s years" +msgstr "在 %s 年" + +msgid "Timeago|in 1 day" +msgstr "在 1 天" + +msgid "Timeago|in 1 hour" +msgstr "在 1 小時" + +msgid "Timeago|in 1 minute" +msgstr "在 1 分鐘" + +msgid "Timeago|in 1 month" +msgstr "在 1 月" + +msgid "Timeago|in 1 week" +msgstr "在 1 星期" + +msgid "Timeago|in 1 year" +msgstr "在 1 年" + +msgid "Timeago|less than a minute ago" +msgstr "不到 1 分鐘前" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "小時" @@ -305,16 +955,109 @@ msgstr "總時間" msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" +msgid "Unstar" +msgstr "取消星標" + +msgid "Upload New File" +msgstr "上傳新文件" + +msgid "Upload file" +msgstr "上傳文件" + +msgid "Use your global notification setting" +msgstr "使用全局通知設置" + +msgid "VisibilityLevel|Internal" +msgstr "內部" + +msgid "VisibilityLevel|Private" +msgstr "私有" + +msgid "VisibilityLevel|Public" +msgstr "公開" + msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" msgid "We don't have enough data to show this stage." msgstr "該階段的數據不足,無法顯示。" +msgid "Withdraw Access Request" +msgstr "取消訪問請求" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "您將要刪除 %{project_name_with_namespace}。\n" +"已刪除的項目無法恢復!\n" +"您確定繼續嗎?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?" + +msgid "You can only add files when you are on a branch" +msgstr "您只能在分支上添加文件" + +msgid "You must sign in to star a project" +msgstr "您必須登錄才能對項目加星標" + msgid "You need permission." msgstr "您需要相關的權限。" +msgid "You will not get any notifications via email" +msgstr "您將不會收到任何通知郵件" + +msgid "You will only receive notifications for the events you choose" +msgstr "您只會收到您選擇的事件通知" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "您只會收到您參與的主題的通知" + +msgid "You will receive notifications for any activity" +msgstr "您將不會收到任何通知" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "您只會收到評論中提及(@)您的通知" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。" + +msgid "Your name" +msgstr "您的名字" + msgid "day" msgid_plural "days" msgstr[0] "天" +msgid "notification emails" +msgstr "通知郵件" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "父級" + +msgid "pipeline schedules documentation" +msgstr "流水線計劃文檔" + +msgid "with stage" +msgid_plural "with stages" +msgstr[0] "於階段" + diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp index e69de29bb2d..0519ecba6ea 100644 --- a/locale/zh_HK/gitlab.po.time_stamp +++ b/locale/zh_HK/gitlab.po.time_stamp @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.1 From 5414733009eb9f7be177744289224b1aedf5cbfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 11:31:37 +0800 Subject: Change the format in changelog --- ...-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml index a5eb71b63c6..e383bab23d6 100644 --- a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml +++ b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml @@ -1,4 +1,4 @@ --- title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page merge_request: 11995 -author:Huang Tao +author: Huang Tao -- cgit v1.2.1 From 0a18b5ec60635264a2234e1524d0f711111ff2ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 17:34:27 +0800 Subject: Optimization 'zh_HK' translation --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 22 +++++++++++----------- locale/zh_HK/gitlab.po.time_stamp | 1 - 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index bfc6f7e5db4..67054b3ef4a 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 11:08-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["激活"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到您的個人賬戶中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["變更日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["作者:"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標簽"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每天(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月1日(淩晨4點)"],"Every week (Sundays at 4:00am)":["每周日(淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到您的派生項目"],"GoToYourFork|Fork":["跳轉到您的派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["禁用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New schedule":["新计划"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["禁用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["選項"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["禁用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["包含這些更改到 新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n您確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只會收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-13 04:07-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["激活"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["變更日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["作者:"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標簽"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每天(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月1日(淩晨4點)"],"Every week (Sundays at 4:00am)":["每周日(淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New schedule":["新计划"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["選項"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["包含這些更改到 新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n您確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只會收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中添加 %{add_ssh_key_link} 之前您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 22fe5e4f807..8fbc38ba5af 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -7,7 +7,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-12 11:08-0400\n" +"PO-Revision-Date: 2017-06-13 04:07-0400\n" "Last-Translator: Huang Tao \n" "Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" @@ -36,7 +36,7 @@ msgid "Add License" msgstr "添加許可證" msgid "Add an SSH key to your profile to pull or push via SSH." -msgstr "新增壹個用於推送或拉取的 SSH 秘鑰到您的個人賬戶中。" +msgstr "新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。" msgid "Add new directory" msgstr "添加新目錄" @@ -230,7 +230,7 @@ msgid "" "notification levels you will also receive notifications for select events. " "To find out more, check out %{notification_link}." msgstr "" -"自定義通知級別與參與級別相同。使用自定義通知級別,您只能收到所選定事件通知。想瞭解更多信息,請查看 %{notification_link}." +"自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}." msgid "Cycle Analytics" msgstr "週期分析" @@ -357,10 +357,10 @@ msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" msgid "Go to your fork" -msgstr "跳轉到您的派生項目" +msgstr "跳轉到派生項目" msgid "GoToYourFork|Fork" -msgstr "跳轉到您的派生項目" +msgstr "跳轉到派生項目" msgid "Home" msgstr "首頁" @@ -378,7 +378,7 @@ msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" msgid "LFSStatus|Disabled" -msgstr "禁用" +msgstr "停用" msgid "LFSStatus|Enabled" msgstr "啟用" @@ -498,7 +498,7 @@ msgid "NotificationLevel|Custom" msgstr "自定義" msgid "NotificationLevel|Disabled" -msgstr "禁用" +msgstr "停用" msgid "NotificationLevel|Global" msgstr "全局" @@ -596,7 +596,7 @@ msgid "Project home" msgstr "項目首頁" msgid "ProjectFeature|Disabled" -msgstr "禁用" +msgstr "停用" msgid "ProjectFeature|Everyone with access" msgstr "任何人都可訪問" @@ -683,7 +683,7 @@ msgid "Select target branch" msgstr "選擇目標分支" msgid "Set a password on your account to pull or push via %{protocol}" -msgstr "為您的賬戶添加壹個用於推送或拉取的 %{protocol} 密碼。" +msgstr "為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。" msgid "Set up CI" msgstr "設置 CI" @@ -1033,12 +1033,12 @@ msgstr "您只會收到評論中提及(@)您的通知" msgid "" "You won't be able to pull or push project code via %{protocol} until you " "%{set_password_link} on your account" -msgstr "在您的賬戶上 %{set_password_link} 之前, 您將無法通過 %{protocol} 拉取或推送代碼。" +msgstr "在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。" msgid "" "You won't be able to pull or push project code via SSH until you " "%{add_ssh_key_link} to your profile" -msgstr "在您的個人資料中添加 %{add_ssh_key_link} 之前, 您將無法通過 SSH 拉取或推送代碼。" +msgstr "在賬號中添加 %{add_ssh_key_link} 之前您將無法通過 SSH 拉取或推送代碼。" msgid "Your name" msgstr "您的名字" diff --git a/locale/zh_HK/gitlab.po.time_stamp b/locale/zh_HK/gitlab.po.time_stamp index 0519ecba6ea..e69de29bb2d 100644 --- a/locale/zh_HK/gitlab.po.time_stamp +++ b/locale/zh_HK/gitlab.po.time_stamp @@ -1 +0,0 @@ - \ No newline at end of file -- cgit v1.2.1 From ba822380045529cdffb78f27db02c14625a4b70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 13:22:33 +0800 Subject: supplement bulgarian translation Fix #33561 --- app/assets/javascripts/locale/bg/app.js | 2 +- locale/bg/gitlab.po | 103 ++++++++++++++++++++++++++++++-- locale/bg/gitlab.po.time_stamp | 1 + 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index ba56c0bea25..f1fedb546de 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-05 09:40-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"ByAuthor|by":["от"],"Commit":["Подаване","Подавания"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Deploy":["Внедряване","Внедрявания"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Introducing Cycle Analytics":["Представяме Ви анализът на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Pipeline Health":["Състояние"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнените на първата Ви такава задача."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-10 03:35-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"ByAuthor|by":["от"],"Cancel":["Отказ"],"Commit":["Подаване","Подавания"],"Cron Timezone":["Часова зона за „Cron“"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Filter":["Филтриране"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Owner":["Собственик"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index e6caf83252d..41805a7792e 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -3,25 +3,34 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-04 19:24-0500\n" +"POT-Creation-Date: 2017-06-07 21:22+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-05 09:40-0400\n" +"PO-Revision-Date: 2017-06-10 03:35-0400\n" "Last-Translator: Lyubomir Vasilev \n" "Language-Team: Bulgarian\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Наистина ли искате да изтриете този план за схема?" + msgid "ByAuthor|by" msgstr "от" +msgid "Cancel" +msgstr "Отказ" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Cron Timezone" +msgstr "Часова зона за „Cron“" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -50,11 +59,32 @@ msgstr "Подготовка за издаване" msgid "CycleAnalyticsStage|Test" msgstr "Тестване" +msgid "Delete" +msgstr "Изтриване" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "Внедряване" msgstr[1] "Внедрявания" +msgid "Description" +msgstr "Описание" + +msgid "Edit" +msgstr "Редактиране" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Редактиране на плана %{id} за схема" + +msgid "Failed to change the owner" +msgstr "Собственикът не може да бъде променен" + +msgid "Failed to remove the pipeline schedule" +msgstr "Планът за схема не може да бъде премахнат" + +msgid "Filter" +msgstr "Филтриране" + msgid "FirstPushedBy|First" msgstr "Първо" @@ -68,14 +98,20 @@ msgid "From merge request merge until deploy to production" msgstr "" "От прилагането на заявката за сливане до внедряването в крайната версия" +msgid "Interval Pattern" +msgstr "Шаблон за интервала" + msgid "Introducing Cycle Analytics" -msgstr "Представяме Ви анализът на циклите" +msgstr "Представяме Ви анализа на циклите" msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последния %d ден" msgstr[1] "Последните %d дни" +msgid "Last Pipeline" +msgstr "Последна схема" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "Ограничено до показване на последното %d събитие" @@ -89,6 +125,12 @@ msgid_plural "New Issues" msgstr[0] "Нов проблем" msgstr[1] "Нови проблема" +msgid "New Pipeline Schedule" +msgstr "Нов план за схема" + +msgid "No schedules" +msgstr "Няма планове" + msgid "Not available" msgstr "Не е налично" @@ -98,9 +140,45 @@ msgstr "Няма достатъчно данни" msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" +msgid "Owner" +msgstr "Собственик" + msgid "Pipeline Health" msgstr "Състояние" +msgid "Pipeline Schedule" +msgstr "План за схема" + +msgid "Pipeline Schedules" +msgstr "Планове за схема" + +msgid "PipelineSchedules|Activated" +msgstr "Включено" + +msgid "PipelineSchedules|Active" +msgstr "Активно" + +msgid "PipelineSchedules|All" +msgstr "Всички" + +msgid "PipelineSchedules|Inactive" +msgstr "Неактивно" + +msgid "PipelineSchedules|Next Run" +msgstr "Следващо изпълнение" + +msgid "PipelineSchedules|None" +msgstr "Нищо" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Въведете кратко описание за тази схема" + +msgid "PipelineSchedules|Take ownership" +msgstr "Поемане на собствеността" + +msgid "PipelineSchedules|Target" +msgstr "Цел" + msgid "ProjectLifecycle|Stage" msgstr "Етап" @@ -125,11 +203,26 @@ msgstr "Свързани заявки за сливане" msgid "Related Merged Requests" msgstr "Свързани приложени заявки за сливане" +msgid "Save pipeline schedule" +msgstr "Запазване на плана за схема" + +msgid "Schedule a new pipeline" +msgstr "Създаване на нов план за схема" + +msgid "Select a timezone" +msgstr "Изберете часова зона" + +msgid "Select target branch" +msgstr "Изберете целеви клон" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показване на %d събитие" msgstr[1] "Показване на %d събития" +msgid "Target Branch" +msgstr "Целеви клон" + msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " @@ -197,8 +290,8 @@ msgid "" "first pipeline finishes running." msgstr "" "Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни " -"всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени " -"автоматично след като приключи изпълнените на първата Ви такава задача." +"всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат " +"добавени автоматично след като приключи изпълнението на първата Ви схема." msgid "The time taken by each data entry gathered by that stage." msgstr "Времето, което отнема всеки запис от данни за съответния етап." diff --git a/locale/bg/gitlab.po.time_stamp b/locale/bg/gitlab.po.time_stamp index e69de29bb2d..0519ecba6ea 100644 --- a/locale/bg/gitlab.po.time_stamp +++ b/locale/bg/gitlab.po.time_stamp @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.1 From b3c13e05fb8080ea1672caf4c0ae72ebc3b5222b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 13:29:11 +0800 Subject: add changelog of supplement bulgarian translation --- .../unreleased/33561-supplement_bulgarian_translation_of_i18n.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml diff --git a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml new file mode 100644 index 00000000000..4f2ba2e1de3 --- /dev/null +++ b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Bulgarian translation of Project Page & Repository Page +merge_request: 12083 +author: Lyubomir Vasilev -- cgit v1.2.1 From 66774c7220d6d47e71df11a028c3cd4d1e5d8d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 11:22:51 +0800 Subject: Optimization 'bg' translation 1. Fix missing translations --- app/assets/javascripts/locale/bg/app.js | 2 +- locale/bg/gitlab.po | 794 +++++++++++++++++++++++++++++++- 2 files changed, 786 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index f1fedb546de..33a5c3c7eb9 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-10 03:35-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"ByAuthor|by":["от"],"Cancel":["Отказ"],"Commit":["Подаване","Подавания"],"Cron Timezone":["Часова зона за „Cron“"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Filter":["Филтриране"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Owner":["Собственик"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 09:36-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":[""],"About auto deploy":["Относно автоматичното внедряване"],"Active":[""],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":[""],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":[""],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":[""],"ChangeTypeActionLabel|Revert in branch":[""],"ChangeTypeAction|Cherry-pick":[""],"ChangeType|commit":[""],"ChangeType|merge request":[""],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":[""],"Cherry-pick this merge-request":[""],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":[""],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":[""],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":[""],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":[""],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":[""],"Custom":[""],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":[""],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":[""],"DownloadCommit|Plain Diff":[""],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":[""],"Every month (on the 1st at 4:00am)":[""],"Every week (Sundays at 4:00am)":[""],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Наследяване",""],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":[""],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":[""],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":[""],"OpenedNDaysAgo|Opened":["Отворен"],"Options":[""],"Owner":["Собственик"],"Pipeline":[""],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":[""],"Revert this merge-request":[""],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":[""],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":[""],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":[""],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["",""],"pipeline schedules documentation":[""],"with stage":["",""]}}}; \ No newline at end of file diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 41805a7792e..fd00796d763 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -3,34 +3,245 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 21:22+0200\n" +"POT-Creation-Date: 2017-06-12 19:29-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-10 03:35-0400\n" +"PO-Revision-Date: 2017-06-12 09:36-0400\n" "Last-Translator: Lyubomir Vasilev \n" -"Language-Team: Bulgarian\n" +"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "" + +msgid "About auto deploy" +msgstr "Относно автоматичното внедряване" + +msgid "Active" +msgstr "" + +msgid "Activity" +msgstr "Дейност" + +msgid "Add Changelog" +msgstr "Добавяне на списък с промени" + +msgid "Add Contribution guide" +msgstr "Добавяне на ръководство за сътрудничество" + +msgid "Add License" +msgstr "Добавяне на лиценз" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате " +"промени чрез SSH." + +msgid "Add new directory" +msgstr "Добавяне на нова папка" + +msgid "Archived project! Repository is read-only" +msgstr "Архивиран проект! Хранилището е само за четене" + msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Наистина ли искате да изтриете този план за схема?" +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Клон" +msgstr[1] "Клонове" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"Клонът %{branch_name} беше създаден. За да настроите " +"автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте " +"промените си. %{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "Клонове" + +msgid "Browse files" +msgstr "" + msgid "ByAuthor|by" msgstr "от" +msgid "CI configuration" +msgstr "Конфигурация на непрекъсната интеграция" + msgid "Cancel" msgstr "Отказ" +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeType|commit" +msgstr "" + +msgid "ChangeType|merge request" +msgstr "" + +msgid "Changelog" +msgstr "Списък с промени" + +msgid "Charts" +msgstr "Графики" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge-request" +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "отказано" + +msgid "CiStatusLabel|created" +msgstr "създадено" + +msgid "CiStatusLabel|failed" +msgstr "неуспешно" + +msgid "CiStatusLabel|manual action" +msgstr "ръчно действие" + +msgid "CiStatusLabel|passed" +msgstr "успешно" + +msgid "CiStatusLabel|passed with warnings" +msgstr "успешно, с предупреждения" + +msgid "CiStatusLabel|pending" +msgstr "на изчакване" + +msgid "CiStatusLabel|skipped" +msgstr "пропуснато" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "чакане за ръчно действие" + +msgid "CiStatusText|blocked" +msgstr "блокирано" + +msgid "CiStatusText|canceled" +msgstr "отказано" + +msgid "CiStatusText|created" +msgstr "създадено" + +msgid "CiStatusText|failed" +msgstr "неуспешно" + +msgid "CiStatusText|manual" +msgstr "ръчно" + +msgid "CiStatusText|passed" +msgstr "успешно" + +msgid "CiStatusText|pending" +msgstr "на изчакване" + +msgid "CiStatusText|skipped" +msgstr "пропуснато" + +msgid "CiStatus|running" +msgstr "протича в момента" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Commit message" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Добавяне на „%{file_name}“" + +msgid "Commits" +msgstr "Подавания" + +msgid "Commits|History" +msgstr "История" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "Сравнение" + +msgid "Contribution guide" +msgstr "Ръководство за сътрудничество" + +msgid "Contributors" +msgstr "Сътрудници" + +msgid "Copy URL to clipboard" +msgstr "Копиране на адреса в буфера за обмен" + +msgid "Copy commit SHA to clipboard" +msgstr "Копиране на идентификатора на подаването в буфера за обмен" + +msgid "Create New Directory" +msgstr "Създаване на нова папка" + +msgid "Create directory" +msgstr "Създаване на папка" + +msgid "Create empty bare repository" +msgstr "Създаване на празно хранилище" + +msgid "Create merge request" +msgstr "Създаване на заявка за сливане" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "Разклоняване" + +msgid "CreateTag|Tag" +msgstr "" + msgid "Cron Timezone" msgstr "Часова зона за „Cron“" +msgid "Cron syntax" +msgstr "" + +msgid "Custom" +msgstr "" + +msgid "Custom notification events" +msgstr "Персонализирани събития за известяване" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Персонализираните нива на известяване са същите като нивата за участие. С " +"персонализираните нива на известяване ще можете да получавате и известия за " +"избрани събития. За да научите повече, прегледайте %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Анализ на циклите" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -59,6 +270,9 @@ msgstr "Подготовка за издаване" msgid "CycleAnalyticsStage|Test" msgstr "Тестване" +msgid "Define a custom pattern with cron syntax" +msgstr "" + msgid "Delete" msgstr "Изтриване" @@ -70,20 +284,68 @@ msgstr[1] "Внедрявания" msgid "Description" msgstr "Описание" +msgid "Directory name" +msgstr "Име на папката" + +msgid "Don't show again" +msgstr "Да не се показва повече" + +msgid "Download" +msgstr "Сваляне" + +msgid "Download tar" +msgstr "Сваляне във формат „tar“" + +msgid "Download tar.bz2" +msgstr "Сваляне във формат „tar.bz2“" + +msgid "Download tar.gz" +msgstr "Сваляне във формат „tar.gz“" + +msgid "Download zip" +msgstr "Сваляне във формат „zip“" + +msgid "DownloadArtifacts|Download" +msgstr "Сваляне" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "Сваляне" + msgid "Edit" msgstr "Редактиране" msgid "Edit Pipeline Schedule %{id}" msgstr "Редактиране на плана %{id} за схема" +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + msgid "Failed to change the owner" msgstr "Собственикът не може да бъде променен" msgid "Failed to remove the pipeline schedule" msgstr "Планът за схема не може да бъде премахнат" -msgid "Filter" -msgstr "Филтриране" +msgid "Files" +msgstr "Файлове" + +msgid "Find by path" +msgstr "Търсене по път" + +msgid "Find file" +msgstr "Търсене на файл" msgid "FirstPushedBy|First" msgstr "Първо" @@ -91,6 +353,15 @@ msgstr "Първо" msgid "FirstPushedBy|pushed by" msgstr "изпращане на промени от" +#, fuzzy +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Наследяване" +msgstr[1] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "Разклонение на" + msgid "From issue creation until deploy to production" msgstr "От създаването на проблема до внедряването в крайната версия" @@ -98,12 +369,33 @@ msgid "From merge request merge until deploy to production" msgstr "" "От прилагането на заявката за сливане до внедряването в крайната версия" +msgid "Go to your fork" +msgstr "Към Вашето разклонение" + +msgid "GoToYourFork|Fork" +msgstr "Разклонение" + +msgid "Home" +msgstr "Начало" + +msgid "Housekeeping successfully started" +msgstr "Освежаването започна успешно" + +msgid "Import repository" +msgstr "Внасяне на хранилище" + msgid "Interval Pattern" msgstr "Шаблон за интервала" msgid "Introducing Cycle Analytics" msgstr "Представяме Ви анализа на циклите" +msgid "LFSStatus|Disabled" +msgstr "Изключено" + +msgid "LFSStatus|Enabled" +msgstr "Включено" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последния %d ден" @@ -112,14 +404,32 @@ msgstr[1] "Последните %d дни" msgid "Last Pipeline" msgstr "Последна схема" +msgid "Last Update" +msgstr "Последна промяна" + +msgid "Last commit" +msgstr "Последно подаване" + +msgid "Learn more in the" +msgstr "" + +msgid "Leave group" +msgstr "Напускане на групата" + +msgid "Leave project" +msgstr "Напускане на проекта" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "Ограничено до показване на последното %d събитие" -msgstr[1] "Ограничено до показване на последните %d събития" +msgstr[0] "Ограничено до показване на най-много %d събитие" +msgstr[1] "Ограничено до показване на най-много %d събития" msgid "Median" msgstr "Медиана" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "добавите SSH ключ" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Нов проблем" @@ -128,6 +438,33 @@ msgstr[1] "Нови проблема" msgid "New Pipeline Schedule" msgstr "Нов план за схема" +msgid "New branch" +msgstr "Нов клон" + +msgid "New directory" +msgstr "Нова папка" + +msgid "New file" +msgstr "Нов файл" + +msgid "New issue" +msgstr "Нов проблем" + +msgid "New merge request" +msgstr "Нова заявка за сливане" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "Нов отрязък" + +msgid "New tag" +msgstr "Нов етикет" + +msgid "No repository" +msgstr "Няма хранилище" + msgid "No schedules" msgstr "Няма планове" @@ -137,12 +474,75 @@ msgstr "Не е налично" msgid "Not enough data" msgstr "Няма достатъчно данни" +msgid "Notification events" +msgstr "Събития за известяване" + +msgid "NotificationEvent|Close issue" +msgstr "Затваряне на проблем" + +msgid "NotificationEvent|Close merge request" +msgstr "Затваряне на заявка за сливане" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Неуспешно изпълнение на схема" + +msgid "NotificationEvent|Merge merge request" +msgstr "Прилагане на заявка за сливане" + +msgid "NotificationEvent|New issue" +msgstr "Нов проблем" + +msgid "NotificationEvent|New merge request" +msgstr "Нова заявка за сливане" + +msgid "NotificationEvent|New note" +msgstr "Нова бележка" + +msgid "NotificationEvent|Reassign issue" +msgstr "Преназначаване на проблем" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Преназначаване на заявка за сливане" + +msgid "NotificationEvent|Reopen issue" +msgstr "Повторно отваряне на проблем" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Успешно изпълнение на схема" + +msgid "NotificationLevel|Custom" +msgstr "Персонализирани" + +msgid "NotificationLevel|Disabled" +msgstr "Изключени" + +msgid "NotificationLevel|Global" +msgstr "Глобални" + +msgid "NotificationLevel|On mention" +msgstr "При споменаване" + +msgid "NotificationLevel|Participate" +msgstr "Участие" + +msgid "NotificationLevel|Watch" +msgstr "Наблюдение" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" +msgid "Options" +msgstr "" + msgid "Owner" msgstr "Собственик" +msgid "Pipeline" +msgstr "" + msgid "Pipeline Health" msgstr "Състояние" @@ -179,17 +579,80 @@ msgstr "Поемане на собствеността" msgid "PipelineSchedules|Target" msgstr "Цел" +msgid "Project '%{project_name}' queued for deletion." +msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Проектът „%{project_name}“ беше създаден успешно." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Проектът „%{project_name}“ беше обновен успешно." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Проектът „%{project_name}“ ще бъде изтрит." + +msgid "Project access must be granted explicitly to each user." +msgstr "" +"Достъпът до проекта трябва да бъде даван поотделно на всеки потребител." + +msgid "Project export could not be deleted." +msgstr "Изнесените данни на проекта не могат да бъдат изтрити." + +msgid "Project export has been deleted." +msgstr "Изнесените данни на проекта бяха изтрити." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова " +"от настройките на проекта." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Изнасянето на проекта започна. Ще получите връзка към данните по е-поща." + +msgid "Project home" +msgstr "Начална страница на проекта" + +msgid "ProjectFeature|Disabled" +msgstr "Изключено" + +msgid "ProjectFeature|Everyone with access" +msgstr "Всеки с достъп" + +msgid "ProjectFeature|Only team members" +msgstr "Само членовете на екипа" + +msgid "ProjectFileTree|Name" +msgstr "Име" + +msgid "ProjectLastActivity|Never" +msgstr "Никога" + msgid "ProjectLifecycle|Stage" msgstr "Етап" +msgid "ProjectNetworkGraph|Graph" +msgstr "Графика" + msgid "Read more" msgstr "Прочетете повече" +msgid "Readme" +msgstr "ПрочетиМе" + +msgid "RefSwitcher|Branches" +msgstr "Клонове" + +msgid "RefSwitcher|Tags" +msgstr "Етикети" + msgid "Related Commits" msgstr "Свързани подавания" msgid "Related Deployed Jobs" -msgstr "Свързани задачи за внедряване" +msgstr "Свързани внедрени задачи" msgid "Related Issues" msgstr "Свързани проблеми" @@ -203,23 +666,84 @@ msgstr "Свързани заявки за сливане" msgid "Related Merged Requests" msgstr "Свързани приложени заявки за сливане" +msgid "Remind later" +msgstr "Напомняне по-късно" + +msgid "Remove project" +msgstr "Премахване на проекта" + +msgid "Request Access" +msgstr "Заявка за достъп" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge-request" +msgstr "" + msgid "Save pipeline schedule" msgstr "Запазване на плана за схема" msgid "Schedule a new pipeline" msgstr "Създаване на нов план за схема" +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Search branches and tags" +msgstr "Търсене в клоновете и етикетите" + +msgid "Select Archive Format" +msgstr "Изберете формата на архива" + msgid "Select a timezone" msgstr "Изберете часова зона" msgid "Select target branch" msgstr "Изберете целеви клон" +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "" +"Задайте парола на профила си, за да можете да изтегляте и изпращате промени " +"чрез %{protocol}" + +msgid "Set up CI" +msgstr "Настройка на НИ" + +msgid "Set up Koding" +msgstr "Настройка на „Koding“" + +msgid "Set up auto deploy" +msgstr "Настройка на авт. внедряване" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "зададете парола" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показване на %d събитие" msgstr[1] "Показване на %d събития" +msgid "Source code" +msgstr "Изходен код" + +msgid "StarProject|Star" +msgstr "Звезда" + +msgid "Start a new merge request with these changes" +msgstr "" + +msgid "Switch branch/tag" +msgstr "Преминаване към клон/етикет" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Етикет" +msgstr[1] "Етикети" + +msgid "Tags" +msgstr "Етикети" + msgid "Target Branch" msgstr "Целеви клон" @@ -235,6 +759,9 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "Съвкупността от събития добавени към данните събрани за този етап." +msgid "The fork relationship has been removed." +msgstr "Връзката на разклонение беше премахната." + msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -248,6 +775,12 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Етапът от цикъла на разработка" +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" + msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " @@ -263,7 +796,18 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "" "Етапът на издаване показва общото време, което е нужно от създаването на " -"проблем до внедряването на кода в крайната версия." +"проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени " +"автоматично след като завършите един пълен цикъл и превърнете първата си " +"идея в реалност." + +msgid "The project can be accessed by any logged in user." +msgstr "Всеки вписан потребител има достъп до проекта." + +msgid "The project can be accessed without any authentication." +msgstr "Всеки може да има достъп до проекта, без нужда от удостоверяване." + +msgid "The repository for this project does not exist." +msgstr "Хранилището за този проект не съществува." msgid "" "The review stage shows the time from creating the merge request to merging " @@ -305,6 +849,13 @@ msgstr "" "данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е " "(5+7)/2 = 6." +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Това означава, че няма да можете да изпращате код, докато не създадете " +"празно хранилище или не внесете съществуващо такова." + msgid "Time before an issue gets scheduled" msgstr "Време преди един проблем да бъде планиран за работа" @@ -318,6 +869,129 @@ msgstr "" msgid "Time until first merge request" msgstr "Време преди първата заявка за сливане" +msgid "Timeago|%s days ago" +msgstr "преди %s дни" + +msgid "Timeago|%s days remaining" +msgstr "остават %s дни" + +msgid "Timeago|%s hours remaining" +msgstr "остават %s часа" + +msgid "Timeago|%s minutes ago" +msgstr "преди %s минути" + +msgid "Timeago|%s minutes remaining" +msgstr "остават %s минути" + +msgid "Timeago|%s months ago" +msgstr "преди %s месеца" + +msgid "Timeago|%s months remaining" +msgstr "остават %s месеца" + +msgid "Timeago|%s seconds remaining" +msgstr "остават %s секунди" + +msgid "Timeago|%s weeks ago" +msgstr "преди %s седмици" + +msgid "Timeago|%s weeks remaining" +msgstr "остават %s седмици" + +msgid "Timeago|%s years ago" +msgstr "преди %s години" + +msgid "Timeago|%s years remaining" +msgstr "остават %s години" + +msgid "Timeago|1 day remaining" +msgstr "остава 1 ден" + +msgid "Timeago|1 hour remaining" +msgstr "остава 1 час" + +msgid "Timeago|1 minute remaining" +msgstr "остава 1 минута" + +msgid "Timeago|1 month remaining" +msgstr "остава 1 месец" + +msgid "Timeago|1 week remaining" +msgstr "остава 1 седмица" + +msgid "Timeago|1 year remaining" +msgstr "остава 1 година" + +msgid "Timeago|Past due" +msgstr "Просрочено" + +msgid "Timeago|a day ago" +msgstr "преди един ден" + +msgid "Timeago|a month ago" +msgstr "преди един месец" + +msgid "Timeago|a week ago" +msgstr "преди една седмица" + +msgid "Timeago|a while" +msgstr "преди известно време" + +msgid "Timeago|a year ago" +msgstr "преди една година" + +msgid "Timeago|about %s hours ago" +msgstr "преди около %s часа" + +msgid "Timeago|about a minute ago" +msgstr "преди около една минута" + +msgid "Timeago|about an hour ago" +msgstr "преди около един час" + +msgid "Timeago|in %s days" +msgstr "след %s дни" + +msgid "Timeago|in %s hours" +msgstr "след %s часа" + +msgid "Timeago|in %s minutes" +msgstr "след %s минути" + +msgid "Timeago|in %s months" +msgstr "след %s месеца" + +msgid "Timeago|in %s seconds" +msgstr "след %s секунди" + +msgid "Timeago|in %s weeks" +msgstr "след %s седмици" + +msgid "Timeago|in %s years" +msgstr "след %s години" + +msgid "Timeago|in 1 day" +msgstr "след 1 ден" + +msgid "Timeago|in 1 hour" +msgstr "след 1 час" + +msgid "Timeago|in 1 minute" +msgstr "след 1 минута" + +msgid "Timeago|in 1 month" +msgstr "след 1 месец" + +msgid "Timeago|in 1 week" +msgstr "след 1 седмица" + +msgid "Timeago|in 1 year" +msgstr "след 1 година" + +msgid "Timeago|less than a minute ago" +msgstr "преди по-малко от минута" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "час" @@ -337,17 +1011,119 @@ msgstr "Общо време" msgid "Total test time for all commits/merges" msgstr "Общо време за тестване на всички подавания/сливания" +msgid "Unstar" +msgstr "Без звезда" + +msgid "Upload New File" +msgstr "Качване на нов файл" + +msgid "Upload file" +msgstr "Качване на файл" + +msgid "Use your global notification setting" +msgstr "Използване на глобалната Ви настройка за известията" + +msgid "VisibilityLevel|Internal" +msgstr "Вътрешен" + +msgid "VisibilityLevel|Private" +msgstr "Частен" + +msgid "VisibilityLevel|Public" +msgstr "Публичен" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Искате ли да видите данните? Помолете администратор за достъп." msgid "We don't have enough data to show this stage." msgstr "Няма достатъчно данни за този етап." +msgid "Withdraw Access Request" +msgstr "Оттегляне на заявката за достъп" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да премахнете „%{project_name_with_namespace}“.\n" +"Ако го премахнете, той НЕ може да бъде възстановен!\n" +"НАИСТИНА ли искате това?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да прехвърлите „%{project_name_with_namespace}“ към друг " +"собственик. НАИСТИНА ли искате това?" + +msgid "You can only add files when you are on a branch" +msgstr "Можете да добавяте файлове само когато се намирате в клон" + +msgid "You must sign in to star a project" +msgstr "Трябва да се впишете, за да отбележите проект със звезда" + msgid "You need permission." msgstr "Нуждаете се от разрешение." +msgid "You will not get any notifications via email" +msgstr "Няма да получавате никакви известия по е-поща" + +msgid "You will only receive notifications for the events you choose" +msgstr "Ще получавате известия само за събитията, за които желаете" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "Ще получавате известия само за нещата, в които участвате" + +msgid "You will receive notifications for any activity" +msgstr "Ще получавате известия за всяка дейност" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "Ще получавате известия само за коментари, в които Ви @споменават" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, " +"докато не %{set_password_link} за профила си" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не " +"%{add_ssh_key_link} в профила си" + +msgid "Your name" +msgstr "Вашето име" + msgid "day" msgid_plural "days" msgstr[0] "ден" msgstr[1] "дни" +msgid "notification emails" +msgstr "известия по е-поща" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" + +msgid "pipeline schedules documentation" +msgstr "" + +msgid "with stage" +msgid_plural "with stages" +msgstr[0] "" +msgstr[1] "" + -- cgit v1.2.1 From 265f76b678177dd8684d98cb5a596c1fd5c20214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 13:22:33 +0800 Subject: supplement bulgarian translation Fix #33561 --- app/assets/javascripts/locale/bg/app.js | 2 +- locale/bg/gitlab.po | 794 +------------------------------- 2 files changed, 10 insertions(+), 786 deletions(-) diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index 33a5c3c7eb9..f1fedb546de 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 09:36-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":[""],"About auto deploy":["Относно автоматичното внедряване"],"Active":[""],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":[""],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":[""],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":[""],"ChangeTypeActionLabel|Revert in branch":[""],"ChangeTypeAction|Cherry-pick":[""],"ChangeType|commit":[""],"ChangeType|merge request":[""],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":[""],"Cherry-pick this merge-request":[""],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":[""],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":[""],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":[""],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":[""],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":[""],"Custom":[""],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":[""],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":[""],"DownloadCommit|Plain Diff":[""],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":[""],"Every month (on the 1st at 4:00am)":[""],"Every week (Sundays at 4:00am)":[""],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Наследяване",""],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":[""],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":[""],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":[""],"OpenedNDaysAgo|Opened":["Отворен"],"Options":[""],"Owner":["Собственик"],"Pipeline":[""],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":[""],"Revert this merge-request":[""],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":[""],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":[""],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":[""],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["",""],"pipeline schedules documentation":[""],"with stage":["",""]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-10 03:35-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"ByAuthor|by":["от"],"Cancel":["Отказ"],"Commit":["Подаване","Подавания"],"Cron Timezone":["Часова зона за „Cron“"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Filter":["Филтриране"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Owner":["Собственик"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index fd00796d763..41805a7792e 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -3,245 +3,34 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-12 19:29-0500\n" +"POT-Creation-Date: 2017-06-07 21:22+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-12 09:36-0400\n" +"PO-Revision-Date: 2017-06-10 03:35-0400\n" "Last-Translator: Lyubomir Vasilev \n" -"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" +"Language-Team: Bulgarian\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "" - -msgid "About auto deploy" -msgstr "Относно автоматичното внедряване" - -msgid "Active" -msgstr "" - -msgid "Activity" -msgstr "Дейност" - -msgid "Add Changelog" -msgstr "Добавяне на списък с промени" - -msgid "Add Contribution guide" -msgstr "Добавяне на ръководство за сътрудничество" - -msgid "Add License" -msgstr "Добавяне на лиценз" - -msgid "Add an SSH key to your profile to pull or push via SSH." -msgstr "" -"Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате " -"промени чрез SSH." - -msgid "Add new directory" -msgstr "Добавяне на нова папка" - -msgid "Archived project! Repository is read-only" -msgstr "Архивиран проект! Хранилището е само за четене" - msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Наистина ли искате да изтриете този план за схема?" -msgid "Attach a file by drag & drop or %{upload_link}" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Клон" -msgstr[1] "Клонове" - -msgid "" -"Branch %{branch_name} was created. To set up auto deploy, " -"choose a GitLab CI Yaml template and commit your changes. " -"%{link_to_autodeploy_doc}" -msgstr "" -"Клонът %{branch_name} беше създаден. За да настроите " -"автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте " -"промените си. %{link_to_autodeploy_doc}" - -msgid "Branches" -msgstr "Клонове" - -msgid "Browse files" -msgstr "" - msgid "ByAuthor|by" msgstr "от" -msgid "CI configuration" -msgstr "Конфигурация на непрекъсната интеграция" - msgid "Cancel" msgstr "Отказ" -msgid "ChangeTypeActionLabel|Pick into branch" -msgstr "" - -msgid "ChangeTypeActionLabel|Revert in branch" -msgstr "" - -msgid "ChangeTypeAction|Cherry-pick" -msgstr "" - -msgid "ChangeType|commit" -msgstr "" - -msgid "ChangeType|merge request" -msgstr "" - -msgid "Changelog" -msgstr "Списък с промени" - -msgid "Charts" -msgstr "Графики" - -msgid "Cherry-pick this commit" -msgstr "" - -msgid "Cherry-pick this merge-request" -msgstr "" - -msgid "CiStatusLabel|canceled" -msgstr "отказано" - -msgid "CiStatusLabel|created" -msgstr "създадено" - -msgid "CiStatusLabel|failed" -msgstr "неуспешно" - -msgid "CiStatusLabel|manual action" -msgstr "ръчно действие" - -msgid "CiStatusLabel|passed" -msgstr "успешно" - -msgid "CiStatusLabel|passed with warnings" -msgstr "успешно, с предупреждения" - -msgid "CiStatusLabel|pending" -msgstr "на изчакване" - -msgid "CiStatusLabel|skipped" -msgstr "пропуснато" - -msgid "CiStatusLabel|waiting for manual action" -msgstr "чакане за ръчно действие" - -msgid "CiStatusText|blocked" -msgstr "блокирано" - -msgid "CiStatusText|canceled" -msgstr "отказано" - -msgid "CiStatusText|created" -msgstr "създадено" - -msgid "CiStatusText|failed" -msgstr "неуспешно" - -msgid "CiStatusText|manual" -msgstr "ръчно" - -msgid "CiStatusText|passed" -msgstr "успешно" - -msgid "CiStatusText|pending" -msgstr "на изчакване" - -msgid "CiStatusText|skipped" -msgstr "пропуснато" - -msgid "CiStatus|running" -msgstr "протича в момента" - msgid "Commit" msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" -msgid "Commit message" -msgstr "" - -msgid "CommitMessage|Add %{file_name}" -msgstr "Добавяне на „%{file_name}“" - -msgid "Commits" -msgstr "Подавания" - -msgid "Commits|History" -msgstr "История" - -msgid "Committed by" -msgstr "" - -msgid "Compare" -msgstr "Сравнение" - -msgid "Contribution guide" -msgstr "Ръководство за сътрудничество" - -msgid "Contributors" -msgstr "Сътрудници" - -msgid "Copy URL to clipboard" -msgstr "Копиране на адреса в буфера за обмен" - -msgid "Copy commit SHA to clipboard" -msgstr "Копиране на идентификатора на подаването в буфера за обмен" - -msgid "Create New Directory" -msgstr "Създаване на нова папка" - -msgid "Create directory" -msgstr "Създаване на папка" - -msgid "Create empty bare repository" -msgstr "Създаване на празно хранилище" - -msgid "Create merge request" -msgstr "Създаване на заявка за сливане" - -msgid "Create new..." -msgstr "" - -msgid "CreateNewFork|Fork" -msgstr "Разклоняване" - -msgid "CreateTag|Tag" -msgstr "" - msgid "Cron Timezone" msgstr "Часова зона за „Cron“" -msgid "Cron syntax" -msgstr "" - -msgid "Custom" -msgstr "" - -msgid "Custom notification events" -msgstr "Персонализирани събития за известяване" - -msgid "" -"Custom notification levels are the same as participating levels. With custom " -"notification levels you will also receive notifications for select events. " -"To find out more, check out %{notification_link}." -msgstr "" -"Персонализираните нива на известяване са същите като нивата за участие. С " -"персонализираните нива на известяване ще можете да получавате и известия за " -"избрани събития. За да научите повече, прегледайте %{notification_link}." - -msgid "Cycle Analytics" -msgstr "Анализ на циклите" - msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -270,9 +59,6 @@ msgstr "Подготовка за издаване" msgid "CycleAnalyticsStage|Test" msgstr "Тестване" -msgid "Define a custom pattern with cron syntax" -msgstr "" - msgid "Delete" msgstr "Изтриване" @@ -284,68 +70,20 @@ msgstr[1] "Внедрявания" msgid "Description" msgstr "Описание" -msgid "Directory name" -msgstr "Име на папката" - -msgid "Don't show again" -msgstr "Да не се показва повече" - -msgid "Download" -msgstr "Сваляне" - -msgid "Download tar" -msgstr "Сваляне във формат „tar“" - -msgid "Download tar.bz2" -msgstr "Сваляне във формат „tar.bz2“" - -msgid "Download tar.gz" -msgstr "Сваляне във формат „tar.gz“" - -msgid "Download zip" -msgstr "Сваляне във формат „zip“" - -msgid "DownloadArtifacts|Download" -msgstr "Сваляне" - -msgid "DownloadCommit|Email Patches" -msgstr "" - -msgid "DownloadCommit|Plain Diff" -msgstr "" - -msgid "DownloadSource|Download" -msgstr "Сваляне" - msgid "Edit" msgstr "Редактиране" msgid "Edit Pipeline Schedule %{id}" msgstr "Редактиране на плана %{id} за схема" -msgid "Every day (at 4:00am)" -msgstr "" - -msgid "Every month (on the 1st at 4:00am)" -msgstr "" - -msgid "Every week (Sundays at 4:00am)" -msgstr "" - msgid "Failed to change the owner" msgstr "Собственикът не може да бъде променен" msgid "Failed to remove the pipeline schedule" msgstr "Планът за схема не може да бъде премахнат" -msgid "Files" -msgstr "Файлове" - -msgid "Find by path" -msgstr "Търсене по път" - -msgid "Find file" -msgstr "Търсене на файл" +msgid "Filter" +msgstr "Филтриране" msgid "FirstPushedBy|First" msgstr "Първо" @@ -353,15 +91,6 @@ msgstr "Първо" msgid "FirstPushedBy|pushed by" msgstr "изпращане на промени от" -#, fuzzy -msgid "Fork" -msgid_plural "Forks" -msgstr[0] "Наследяване" -msgstr[1] "" - -msgid "ForkedFromProjectPath|Forked from" -msgstr "Разклонение на" - msgid "From issue creation until deploy to production" msgstr "От създаването на проблема до внедряването в крайната версия" @@ -369,33 +98,12 @@ msgid "From merge request merge until deploy to production" msgstr "" "От прилагането на заявката за сливане до внедряването в крайната версия" -msgid "Go to your fork" -msgstr "Към Вашето разклонение" - -msgid "GoToYourFork|Fork" -msgstr "Разклонение" - -msgid "Home" -msgstr "Начало" - -msgid "Housekeeping successfully started" -msgstr "Освежаването започна успешно" - -msgid "Import repository" -msgstr "Внасяне на хранилище" - msgid "Interval Pattern" msgstr "Шаблон за интервала" msgid "Introducing Cycle Analytics" msgstr "Представяме Ви анализа на циклите" -msgid "LFSStatus|Disabled" -msgstr "Изключено" - -msgid "LFSStatus|Enabled" -msgstr "Включено" - msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последния %d ден" @@ -404,32 +112,14 @@ msgstr[1] "Последните %d дни" msgid "Last Pipeline" msgstr "Последна схема" -msgid "Last Update" -msgstr "Последна промяна" - -msgid "Last commit" -msgstr "Последно подаване" - -msgid "Learn more in the" -msgstr "" - -msgid "Leave group" -msgstr "Напускане на групата" - -msgid "Leave project" -msgstr "Напускане на проекта" - msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "Ограничено до показване на най-много %d събитие" -msgstr[1] "Ограничено до показване на най-много %d събития" +msgstr[0] "Ограничено до показване на последното %d събитие" +msgstr[1] "Ограничено до показване на последните %d събития" msgid "Median" msgstr "Медиана" -msgid "MissingSSHKeyWarningLink|add an SSH key" -msgstr "добавите SSH ключ" - msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Нов проблем" @@ -438,33 +128,6 @@ msgstr[1] "Нови проблема" msgid "New Pipeline Schedule" msgstr "Нов план за схема" -msgid "New branch" -msgstr "Нов клон" - -msgid "New directory" -msgstr "Нова папка" - -msgid "New file" -msgstr "Нов файл" - -msgid "New issue" -msgstr "Нов проблем" - -msgid "New merge request" -msgstr "Нова заявка за сливане" - -msgid "New schedule" -msgstr "" - -msgid "New snippet" -msgstr "Нов отрязък" - -msgid "New tag" -msgstr "Нов етикет" - -msgid "No repository" -msgstr "Няма хранилище" - msgid "No schedules" msgstr "Няма планове" @@ -474,75 +137,12 @@ msgstr "Не е налично" msgid "Not enough data" msgstr "Няма достатъчно данни" -msgid "Notification events" -msgstr "Събития за известяване" - -msgid "NotificationEvent|Close issue" -msgstr "Затваряне на проблем" - -msgid "NotificationEvent|Close merge request" -msgstr "Затваряне на заявка за сливане" - -msgid "NotificationEvent|Failed pipeline" -msgstr "Неуспешно изпълнение на схема" - -msgid "NotificationEvent|Merge merge request" -msgstr "Прилагане на заявка за сливане" - -msgid "NotificationEvent|New issue" -msgstr "Нов проблем" - -msgid "NotificationEvent|New merge request" -msgstr "Нова заявка за сливане" - -msgid "NotificationEvent|New note" -msgstr "Нова бележка" - -msgid "NotificationEvent|Reassign issue" -msgstr "Преназначаване на проблем" - -msgid "NotificationEvent|Reassign merge request" -msgstr "Преназначаване на заявка за сливане" - -msgid "NotificationEvent|Reopen issue" -msgstr "Повторно отваряне на проблем" - -msgid "NotificationEvent|Successful pipeline" -msgstr "Успешно изпълнение на схема" - -msgid "NotificationLevel|Custom" -msgstr "Персонализирани" - -msgid "NotificationLevel|Disabled" -msgstr "Изключени" - -msgid "NotificationLevel|Global" -msgstr "Глобални" - -msgid "NotificationLevel|On mention" -msgstr "При споменаване" - -msgid "NotificationLevel|Participate" -msgstr "Участие" - -msgid "NotificationLevel|Watch" -msgstr "Наблюдение" - -msgid "OfSearchInADropdown|Filter" -msgstr "" - msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" -msgid "Options" -msgstr "" - msgid "Owner" msgstr "Собственик" -msgid "Pipeline" -msgstr "" - msgid "Pipeline Health" msgstr "Състояние" @@ -579,80 +179,17 @@ msgstr "Поемане на собствеността" msgid "PipelineSchedules|Target" msgstr "Цел" -msgid "Project '%{project_name}' queued for deletion." -msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване." - -msgid "Project '%{project_name}' was successfully created." -msgstr "Проектът „%{project_name}“ беше създаден успешно." - -msgid "Project '%{project_name}' was successfully updated." -msgstr "Проектът „%{project_name}“ беше обновен успешно." - -msgid "Project '%{project_name}' will be deleted." -msgstr "Проектът „%{project_name}“ ще бъде изтрит." - -msgid "Project access must be granted explicitly to each user." -msgstr "" -"Достъпът до проекта трябва да бъде даван поотделно на всеки потребител." - -msgid "Project export could not be deleted." -msgstr "Изнесените данни на проекта не могат да бъдат изтрити." - -msgid "Project export has been deleted." -msgstr "Изнесените данни на проекта бяха изтрити." - -msgid "" -"Project export link has expired. Please generate a new export from your " -"project settings." -msgstr "" -"Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова " -"от настройките на проекта." - -msgid "Project export started. A download link will be sent by email." -msgstr "" -"Изнасянето на проекта започна. Ще получите връзка към данните по е-поща." - -msgid "Project home" -msgstr "Начална страница на проекта" - -msgid "ProjectFeature|Disabled" -msgstr "Изключено" - -msgid "ProjectFeature|Everyone with access" -msgstr "Всеки с достъп" - -msgid "ProjectFeature|Only team members" -msgstr "Само членовете на екипа" - -msgid "ProjectFileTree|Name" -msgstr "Име" - -msgid "ProjectLastActivity|Never" -msgstr "Никога" - msgid "ProjectLifecycle|Stage" msgstr "Етап" -msgid "ProjectNetworkGraph|Graph" -msgstr "Графика" - msgid "Read more" msgstr "Прочетете повече" -msgid "Readme" -msgstr "ПрочетиМе" - -msgid "RefSwitcher|Branches" -msgstr "Клонове" - -msgid "RefSwitcher|Tags" -msgstr "Етикети" - msgid "Related Commits" msgstr "Свързани подавания" msgid "Related Deployed Jobs" -msgstr "Свързани внедрени задачи" +msgstr "Свързани задачи за внедряване" msgid "Related Issues" msgstr "Свързани проблеми" @@ -666,84 +203,23 @@ msgstr "Свързани заявки за сливане" msgid "Related Merged Requests" msgstr "Свързани приложени заявки за сливане" -msgid "Remind later" -msgstr "Напомняне по-късно" - -msgid "Remove project" -msgstr "Премахване на проекта" - -msgid "Request Access" -msgstr "Заявка за достъп" - -msgid "Revert this commit" -msgstr "" - -msgid "Revert this merge-request" -msgstr "" - msgid "Save pipeline schedule" msgstr "Запазване на плана за схема" msgid "Schedule a new pipeline" msgstr "Създаване на нов план за схема" -msgid "Scheduling Pipelines" -msgstr "" - -msgid "Search branches and tags" -msgstr "Търсене в клоновете и етикетите" - -msgid "Select Archive Format" -msgstr "Изберете формата на архива" - msgid "Select a timezone" msgstr "Изберете часова зона" msgid "Select target branch" msgstr "Изберете целеви клон" -msgid "Set a password on your account to pull or push via %{protocol}" -msgstr "" -"Задайте парола на профила си, за да можете да изтегляте и изпращате промени " -"чрез %{protocol}" - -msgid "Set up CI" -msgstr "Настройка на НИ" - -msgid "Set up Koding" -msgstr "Настройка на „Koding“" - -msgid "Set up auto deploy" -msgstr "Настройка на авт. внедряване" - -msgid "SetPasswordToCloneLink|set a password" -msgstr "зададете парола" - msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показване на %d събитие" msgstr[1] "Показване на %d събития" -msgid "Source code" -msgstr "Изходен код" - -msgid "StarProject|Star" -msgstr "Звезда" - -msgid "Start a new merge request with these changes" -msgstr "" - -msgid "Switch branch/tag" -msgstr "Преминаване към клон/етикет" - -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Етикет" -msgstr[1] "Етикети" - -msgid "Tags" -msgstr "Етикети" - msgid "Target Branch" msgstr "Целеви клон" @@ -759,9 +235,6 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "Съвкупността от събития добавени към данните събрани за този етап." -msgid "The fork relationship has been removed." -msgstr "Връзката на разклонение беше премахната." - msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -775,12 +248,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Етапът от цикъла на разработка" -msgid "" -"The pipelines schedule runs pipelines in the future, repeatedly, for " -"specific branches or tags. Those scheduled pipelines will inherit limited " -"project access based on their associated user." -msgstr "" - msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " @@ -796,18 +263,7 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "" "Етапът на издаване показва общото време, което е нужно от създаването на " -"проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени " -"автоматично след като завършите един пълен цикъл и превърнете първата си " -"идея в реалност." - -msgid "The project can be accessed by any logged in user." -msgstr "Всеки вписан потребител има достъп до проекта." - -msgid "The project can be accessed without any authentication." -msgstr "Всеки може да има достъп до проекта, без нужда от удостоверяване." - -msgid "The repository for this project does not exist." -msgstr "Хранилището за този проект не съществува." +"проблем до внедряването на кода в крайната версия." msgid "" "The review stage shows the time from creating the merge request to merging " @@ -849,13 +305,6 @@ msgstr "" "данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е " "(5+7)/2 = 6." -msgid "" -"This means you can not push code until you create an empty repository or " -"import existing one." -msgstr "" -"Това означава, че няма да можете да изпращате код, докато не създадете " -"празно хранилище или не внесете съществуващо такова." - msgid "Time before an issue gets scheduled" msgstr "Време преди един проблем да бъде планиран за работа" @@ -869,129 +318,6 @@ msgstr "" msgid "Time until first merge request" msgstr "Време преди първата заявка за сливане" -msgid "Timeago|%s days ago" -msgstr "преди %s дни" - -msgid "Timeago|%s days remaining" -msgstr "остават %s дни" - -msgid "Timeago|%s hours remaining" -msgstr "остават %s часа" - -msgid "Timeago|%s minutes ago" -msgstr "преди %s минути" - -msgid "Timeago|%s minutes remaining" -msgstr "остават %s минути" - -msgid "Timeago|%s months ago" -msgstr "преди %s месеца" - -msgid "Timeago|%s months remaining" -msgstr "остават %s месеца" - -msgid "Timeago|%s seconds remaining" -msgstr "остават %s секунди" - -msgid "Timeago|%s weeks ago" -msgstr "преди %s седмици" - -msgid "Timeago|%s weeks remaining" -msgstr "остават %s седмици" - -msgid "Timeago|%s years ago" -msgstr "преди %s години" - -msgid "Timeago|%s years remaining" -msgstr "остават %s години" - -msgid "Timeago|1 day remaining" -msgstr "остава 1 ден" - -msgid "Timeago|1 hour remaining" -msgstr "остава 1 час" - -msgid "Timeago|1 minute remaining" -msgstr "остава 1 минута" - -msgid "Timeago|1 month remaining" -msgstr "остава 1 месец" - -msgid "Timeago|1 week remaining" -msgstr "остава 1 седмица" - -msgid "Timeago|1 year remaining" -msgstr "остава 1 година" - -msgid "Timeago|Past due" -msgstr "Просрочено" - -msgid "Timeago|a day ago" -msgstr "преди един ден" - -msgid "Timeago|a month ago" -msgstr "преди един месец" - -msgid "Timeago|a week ago" -msgstr "преди една седмица" - -msgid "Timeago|a while" -msgstr "преди известно време" - -msgid "Timeago|a year ago" -msgstr "преди една година" - -msgid "Timeago|about %s hours ago" -msgstr "преди около %s часа" - -msgid "Timeago|about a minute ago" -msgstr "преди около една минута" - -msgid "Timeago|about an hour ago" -msgstr "преди около един час" - -msgid "Timeago|in %s days" -msgstr "след %s дни" - -msgid "Timeago|in %s hours" -msgstr "след %s часа" - -msgid "Timeago|in %s minutes" -msgstr "след %s минути" - -msgid "Timeago|in %s months" -msgstr "след %s месеца" - -msgid "Timeago|in %s seconds" -msgstr "след %s секунди" - -msgid "Timeago|in %s weeks" -msgstr "след %s седмици" - -msgid "Timeago|in %s years" -msgstr "след %s години" - -msgid "Timeago|in 1 day" -msgstr "след 1 ден" - -msgid "Timeago|in 1 hour" -msgstr "след 1 час" - -msgid "Timeago|in 1 minute" -msgstr "след 1 минута" - -msgid "Timeago|in 1 month" -msgstr "след 1 месец" - -msgid "Timeago|in 1 week" -msgstr "след 1 седмица" - -msgid "Timeago|in 1 year" -msgstr "след 1 година" - -msgid "Timeago|less than a minute ago" -msgstr "преди по-малко от минута" - msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "час" @@ -1011,119 +337,17 @@ msgstr "Общо време" msgid "Total test time for all commits/merges" msgstr "Общо време за тестване на всички подавания/сливания" -msgid "Unstar" -msgstr "Без звезда" - -msgid "Upload New File" -msgstr "Качване на нов файл" - -msgid "Upload file" -msgstr "Качване на файл" - -msgid "Use your global notification setting" -msgstr "Използване на глобалната Ви настройка за известията" - -msgid "VisibilityLevel|Internal" -msgstr "Вътрешен" - -msgid "VisibilityLevel|Private" -msgstr "Частен" - -msgid "VisibilityLevel|Public" -msgstr "Публичен" - msgid "Want to see the data? Please ask an administrator for access." msgstr "Искате ли да видите данните? Помолете администратор за достъп." msgid "We don't have enough data to show this stage." msgstr "Няма достатъчно данни за този етап." -msgid "Withdraw Access Request" -msgstr "Оттегляне на заявката за достъп" - -msgid "" -"You are going to remove %{project_name_with_namespace}.\n" -"Removed project CANNOT be restored!\n" -"Are you ABSOLUTELY sure?" -msgstr "" -"На път сте да премахнете „%{project_name_with_namespace}“.\n" -"Ако го премахнете, той НЕ може да бъде възстановен!\n" -"НАИСТИНА ли искате това?" - -msgid "" -"You are going to remove the fork relationship to source project " -"%{forked_from_project}. Are you ABSOLUTELY sure?" -msgstr "" - -msgid "" -"You are going to transfer %{project_name_with_namespace} to another owner. " -"Are you ABSOLUTELY sure?" -msgstr "" -"На път сте да прехвърлите „%{project_name_with_namespace}“ към друг " -"собственик. НАИСТИНА ли искате това?" - -msgid "You can only add files when you are on a branch" -msgstr "Можете да добавяте файлове само когато се намирате в клон" - -msgid "You must sign in to star a project" -msgstr "Трябва да се впишете, за да отбележите проект със звезда" - msgid "You need permission." msgstr "Нуждаете се от разрешение." -msgid "You will not get any notifications via email" -msgstr "Няма да получавате никакви известия по е-поща" - -msgid "You will only receive notifications for the events you choose" -msgstr "Ще получавате известия само за събитията, за които желаете" - -msgid "" -"You will only receive notifications for threads you have participated in" -msgstr "Ще получавате известия само за нещата, в които участвате" - -msgid "You will receive notifications for any activity" -msgstr "Ще получавате известия за всяка дейност" - -msgid "" -"You will receive notifications only for comments in which you were " -"@mentioned" -msgstr "Ще получавате известия само за коментари, в които Ви @споменават" - -msgid "" -"You won't be able to pull or push project code via %{protocol} until you " -"%{set_password_link} on your account" -msgstr "" -"Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, " -"докато не %{set_password_link} за профила си" - -msgid "" -"You won't be able to pull or push project code via SSH until you " -"%{add_ssh_key_link} to your profile" -msgstr "" -"Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не " -"%{add_ssh_key_link} в профила си" - -msgid "Your name" -msgstr "Вашето име" - msgid "day" msgid_plural "days" msgstr[0] "ден" msgstr[1] "дни" -msgid "notification emails" -msgstr "известия по е-поща" - -msgid "parent" -msgid_plural "parents" -msgstr[0] "" -msgstr[1] "" - -msgid "pipeline schedules documentation" -msgstr "" - -msgid "with stage" -msgid_plural "with stages" -msgstr[0] "" -msgstr[1] "" - -- cgit v1.2.1 From 66b0bce9c6f9b4c338a5cf11797190a3d6dc39d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 11:22:51 +0800 Subject: Optimization 'bg' translation 1. Fix missing translations --- app/assets/javascripts/locale/bg/app.js | 2 +- locale/bg/gitlab.po | 794 +++++++++++++++++++++++++++++++- 2 files changed, 786 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index f1fedb546de..33a5c3c7eb9 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-07 21:22+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-10 03:35-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"ByAuthor|by":["от"],"Cancel":["Отказ"],"Commit":["Подаване","Подавания"],"Cron Timezone":["Часова зона за „Cron“"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Filter":["Филтриране"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Owner":["Собственик"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 09:36-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":[""],"About auto deploy":["Относно автоматичното внедряване"],"Active":[""],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":[""],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":[""],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":[""],"ChangeTypeActionLabel|Revert in branch":[""],"ChangeTypeAction|Cherry-pick":[""],"ChangeType|commit":[""],"ChangeType|merge request":[""],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":[""],"Cherry-pick this merge-request":[""],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":[""],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":[""],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":[""],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":[""],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":[""],"Custom":[""],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":[""],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":[""],"DownloadCommit|Plain Diff":[""],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":[""],"Every month (on the 1st at 4:00am)":[""],"Every week (Sundays at 4:00am)":[""],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Наследяване",""],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":[""],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":[""],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":[""],"OpenedNDaysAgo|Opened":["Отворен"],"Options":[""],"Owner":["Собственик"],"Pipeline":[""],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":[""],"Revert this merge-request":[""],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":[""],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":[""],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":[""],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["",""],"pipeline schedules documentation":[""],"with stage":["",""]}}}; \ No newline at end of file diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 41805a7792e..fd00796d763 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -3,34 +3,245 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-07 21:22+0200\n" +"POT-Creation-Date: 2017-06-12 19:29-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-10 03:35-0400\n" +"PO-Revision-Date: 2017-06-12 09:36-0400\n" "Last-Translator: Lyubomir Vasilev \n" -"Language-Team: Bulgarian\n" +"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "" + +msgid "About auto deploy" +msgstr "Относно автоматичното внедряване" + +msgid "Active" +msgstr "" + +msgid "Activity" +msgstr "Дейност" + +msgid "Add Changelog" +msgstr "Добавяне на списък с промени" + +msgid "Add Contribution guide" +msgstr "Добавяне на ръководство за сътрудничество" + +msgid "Add License" +msgstr "Добавяне на лиценз" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате " +"промени чрез SSH." + +msgid "Add new directory" +msgstr "Добавяне на нова папка" + +msgid "Archived project! Repository is read-only" +msgstr "Архивиран проект! Хранилището е само за четене" + msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Наистина ли искате да изтриете този план за схема?" +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Клон" +msgstr[1] "Клонове" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"Клонът %{branch_name} беше създаден. За да настроите " +"автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте " +"промените си. %{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "Клонове" + +msgid "Browse files" +msgstr "" + msgid "ByAuthor|by" msgstr "от" +msgid "CI configuration" +msgstr "Конфигурация на непрекъсната интеграция" + msgid "Cancel" msgstr "Отказ" +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeType|commit" +msgstr "" + +msgid "ChangeType|merge request" +msgstr "" + +msgid "Changelog" +msgstr "Списък с промени" + +msgid "Charts" +msgstr "Графики" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge-request" +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "отказано" + +msgid "CiStatusLabel|created" +msgstr "създадено" + +msgid "CiStatusLabel|failed" +msgstr "неуспешно" + +msgid "CiStatusLabel|manual action" +msgstr "ръчно действие" + +msgid "CiStatusLabel|passed" +msgstr "успешно" + +msgid "CiStatusLabel|passed with warnings" +msgstr "успешно, с предупреждения" + +msgid "CiStatusLabel|pending" +msgstr "на изчакване" + +msgid "CiStatusLabel|skipped" +msgstr "пропуснато" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "чакане за ръчно действие" + +msgid "CiStatusText|blocked" +msgstr "блокирано" + +msgid "CiStatusText|canceled" +msgstr "отказано" + +msgid "CiStatusText|created" +msgstr "създадено" + +msgid "CiStatusText|failed" +msgstr "неуспешно" + +msgid "CiStatusText|manual" +msgstr "ръчно" + +msgid "CiStatusText|passed" +msgstr "успешно" + +msgid "CiStatusText|pending" +msgstr "на изчакване" + +msgid "CiStatusText|skipped" +msgstr "пропуснато" + +msgid "CiStatus|running" +msgstr "протича в момента" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Commit message" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Добавяне на „%{file_name}“" + +msgid "Commits" +msgstr "Подавания" + +msgid "Commits|History" +msgstr "История" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "Сравнение" + +msgid "Contribution guide" +msgstr "Ръководство за сътрудничество" + +msgid "Contributors" +msgstr "Сътрудници" + +msgid "Copy URL to clipboard" +msgstr "Копиране на адреса в буфера за обмен" + +msgid "Copy commit SHA to clipboard" +msgstr "Копиране на идентификатора на подаването в буфера за обмен" + +msgid "Create New Directory" +msgstr "Създаване на нова папка" + +msgid "Create directory" +msgstr "Създаване на папка" + +msgid "Create empty bare repository" +msgstr "Създаване на празно хранилище" + +msgid "Create merge request" +msgstr "Създаване на заявка за сливане" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "Разклоняване" + +msgid "CreateTag|Tag" +msgstr "" + msgid "Cron Timezone" msgstr "Часова зона за „Cron“" +msgid "Cron syntax" +msgstr "" + +msgid "Custom" +msgstr "" + +msgid "Custom notification events" +msgstr "Персонализирани събития за известяване" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Персонализираните нива на известяване са същите като нивата за участие. С " +"персонализираните нива на известяване ще можете да получавате и известия за " +"избрани събития. За да научите повече, прегледайте %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Анализ на циклите" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -59,6 +270,9 @@ msgstr "Подготовка за издаване" msgid "CycleAnalyticsStage|Test" msgstr "Тестване" +msgid "Define a custom pattern with cron syntax" +msgstr "" + msgid "Delete" msgstr "Изтриване" @@ -70,20 +284,68 @@ msgstr[1] "Внедрявания" msgid "Description" msgstr "Описание" +msgid "Directory name" +msgstr "Име на папката" + +msgid "Don't show again" +msgstr "Да не се показва повече" + +msgid "Download" +msgstr "Сваляне" + +msgid "Download tar" +msgstr "Сваляне във формат „tar“" + +msgid "Download tar.bz2" +msgstr "Сваляне във формат „tar.bz2“" + +msgid "Download tar.gz" +msgstr "Сваляне във формат „tar.gz“" + +msgid "Download zip" +msgstr "Сваляне във формат „zip“" + +msgid "DownloadArtifacts|Download" +msgstr "Сваляне" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "Сваляне" + msgid "Edit" msgstr "Редактиране" msgid "Edit Pipeline Schedule %{id}" msgstr "Редактиране на плана %{id} за схема" +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + msgid "Failed to change the owner" msgstr "Собственикът не може да бъде променен" msgid "Failed to remove the pipeline schedule" msgstr "Планът за схема не може да бъде премахнат" -msgid "Filter" -msgstr "Филтриране" +msgid "Files" +msgstr "Файлове" + +msgid "Find by path" +msgstr "Търсене по път" + +msgid "Find file" +msgstr "Търсене на файл" msgid "FirstPushedBy|First" msgstr "Първо" @@ -91,6 +353,15 @@ msgstr "Първо" msgid "FirstPushedBy|pushed by" msgstr "изпращане на промени от" +#, fuzzy +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Наследяване" +msgstr[1] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "Разклонение на" + msgid "From issue creation until deploy to production" msgstr "От създаването на проблема до внедряването в крайната версия" @@ -98,12 +369,33 @@ msgid "From merge request merge until deploy to production" msgstr "" "От прилагането на заявката за сливане до внедряването в крайната версия" +msgid "Go to your fork" +msgstr "Към Вашето разклонение" + +msgid "GoToYourFork|Fork" +msgstr "Разклонение" + +msgid "Home" +msgstr "Начало" + +msgid "Housekeeping successfully started" +msgstr "Освежаването започна успешно" + +msgid "Import repository" +msgstr "Внасяне на хранилище" + msgid "Interval Pattern" msgstr "Шаблон за интервала" msgid "Introducing Cycle Analytics" msgstr "Представяме Ви анализа на циклите" +msgid "LFSStatus|Disabled" +msgstr "Изключено" + +msgid "LFSStatus|Enabled" +msgstr "Включено" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последния %d ден" @@ -112,14 +404,32 @@ msgstr[1] "Последните %d дни" msgid "Last Pipeline" msgstr "Последна схема" +msgid "Last Update" +msgstr "Последна промяна" + +msgid "Last commit" +msgstr "Последно подаване" + +msgid "Learn more in the" +msgstr "" + +msgid "Leave group" +msgstr "Напускане на групата" + +msgid "Leave project" +msgstr "Напускане на проекта" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "Ограничено до показване на последното %d събитие" -msgstr[1] "Ограничено до показване на последните %d събития" +msgstr[0] "Ограничено до показване на най-много %d събитие" +msgstr[1] "Ограничено до показване на най-много %d събития" msgid "Median" msgstr "Медиана" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "добавите SSH ключ" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Нов проблем" @@ -128,6 +438,33 @@ msgstr[1] "Нови проблема" msgid "New Pipeline Schedule" msgstr "Нов план за схема" +msgid "New branch" +msgstr "Нов клон" + +msgid "New directory" +msgstr "Нова папка" + +msgid "New file" +msgstr "Нов файл" + +msgid "New issue" +msgstr "Нов проблем" + +msgid "New merge request" +msgstr "Нова заявка за сливане" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "Нов отрязък" + +msgid "New tag" +msgstr "Нов етикет" + +msgid "No repository" +msgstr "Няма хранилище" + msgid "No schedules" msgstr "Няма планове" @@ -137,12 +474,75 @@ msgstr "Не е налично" msgid "Not enough data" msgstr "Няма достатъчно данни" +msgid "Notification events" +msgstr "Събития за известяване" + +msgid "NotificationEvent|Close issue" +msgstr "Затваряне на проблем" + +msgid "NotificationEvent|Close merge request" +msgstr "Затваряне на заявка за сливане" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Неуспешно изпълнение на схема" + +msgid "NotificationEvent|Merge merge request" +msgstr "Прилагане на заявка за сливане" + +msgid "NotificationEvent|New issue" +msgstr "Нов проблем" + +msgid "NotificationEvent|New merge request" +msgstr "Нова заявка за сливане" + +msgid "NotificationEvent|New note" +msgstr "Нова бележка" + +msgid "NotificationEvent|Reassign issue" +msgstr "Преназначаване на проблем" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Преназначаване на заявка за сливане" + +msgid "NotificationEvent|Reopen issue" +msgstr "Повторно отваряне на проблем" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Успешно изпълнение на схема" + +msgid "NotificationLevel|Custom" +msgstr "Персонализирани" + +msgid "NotificationLevel|Disabled" +msgstr "Изключени" + +msgid "NotificationLevel|Global" +msgstr "Глобални" + +msgid "NotificationLevel|On mention" +msgstr "При споменаване" + +msgid "NotificationLevel|Participate" +msgstr "Участие" + +msgid "NotificationLevel|Watch" +msgstr "Наблюдение" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" +msgid "Options" +msgstr "" + msgid "Owner" msgstr "Собственик" +msgid "Pipeline" +msgstr "" + msgid "Pipeline Health" msgstr "Състояние" @@ -179,17 +579,80 @@ msgstr "Поемане на собствеността" msgid "PipelineSchedules|Target" msgstr "Цел" +msgid "Project '%{project_name}' queued for deletion." +msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Проектът „%{project_name}“ беше създаден успешно." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Проектът „%{project_name}“ беше обновен успешно." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Проектът „%{project_name}“ ще бъде изтрит." + +msgid "Project access must be granted explicitly to each user." +msgstr "" +"Достъпът до проекта трябва да бъде даван поотделно на всеки потребител." + +msgid "Project export could not be deleted." +msgstr "Изнесените данни на проекта не могат да бъдат изтрити." + +msgid "Project export has been deleted." +msgstr "Изнесените данни на проекта бяха изтрити." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова " +"от настройките на проекта." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Изнасянето на проекта започна. Ще получите връзка към данните по е-поща." + +msgid "Project home" +msgstr "Начална страница на проекта" + +msgid "ProjectFeature|Disabled" +msgstr "Изключено" + +msgid "ProjectFeature|Everyone with access" +msgstr "Всеки с достъп" + +msgid "ProjectFeature|Only team members" +msgstr "Само членовете на екипа" + +msgid "ProjectFileTree|Name" +msgstr "Име" + +msgid "ProjectLastActivity|Never" +msgstr "Никога" + msgid "ProjectLifecycle|Stage" msgstr "Етап" +msgid "ProjectNetworkGraph|Graph" +msgstr "Графика" + msgid "Read more" msgstr "Прочетете повече" +msgid "Readme" +msgstr "ПрочетиМе" + +msgid "RefSwitcher|Branches" +msgstr "Клонове" + +msgid "RefSwitcher|Tags" +msgstr "Етикети" + msgid "Related Commits" msgstr "Свързани подавания" msgid "Related Deployed Jobs" -msgstr "Свързани задачи за внедряване" +msgstr "Свързани внедрени задачи" msgid "Related Issues" msgstr "Свързани проблеми" @@ -203,23 +666,84 @@ msgstr "Свързани заявки за сливане" msgid "Related Merged Requests" msgstr "Свързани приложени заявки за сливане" +msgid "Remind later" +msgstr "Напомняне по-късно" + +msgid "Remove project" +msgstr "Премахване на проекта" + +msgid "Request Access" +msgstr "Заявка за достъп" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge-request" +msgstr "" + msgid "Save pipeline schedule" msgstr "Запазване на плана за схема" msgid "Schedule a new pipeline" msgstr "Създаване на нов план за схема" +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Search branches and tags" +msgstr "Търсене в клоновете и етикетите" + +msgid "Select Archive Format" +msgstr "Изберете формата на архива" + msgid "Select a timezone" msgstr "Изберете часова зона" msgid "Select target branch" msgstr "Изберете целеви клон" +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "" +"Задайте парола на профила си, за да можете да изтегляте и изпращате промени " +"чрез %{protocol}" + +msgid "Set up CI" +msgstr "Настройка на НИ" + +msgid "Set up Koding" +msgstr "Настройка на „Koding“" + +msgid "Set up auto deploy" +msgstr "Настройка на авт. внедряване" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "зададете парола" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показване на %d събитие" msgstr[1] "Показване на %d събития" +msgid "Source code" +msgstr "Изходен код" + +msgid "StarProject|Star" +msgstr "Звезда" + +msgid "Start a new merge request with these changes" +msgstr "" + +msgid "Switch branch/tag" +msgstr "Преминаване към клон/етикет" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Етикет" +msgstr[1] "Етикети" + +msgid "Tags" +msgstr "Етикети" + msgid "Target Branch" msgstr "Целеви клон" @@ -235,6 +759,9 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "Съвкупността от събития добавени към данните събрани за този етап." +msgid "The fork relationship has been removed." +msgstr "Връзката на разклонение беше премахната." + msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -248,6 +775,12 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Етапът от цикъла на разработка" +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" + msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " @@ -263,7 +796,18 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "" "Етапът на издаване показва общото време, което е нужно от създаването на " -"проблем до внедряването на кода в крайната версия." +"проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени " +"автоматично след като завършите един пълен цикъл и превърнете първата си " +"идея в реалност." + +msgid "The project can be accessed by any logged in user." +msgstr "Всеки вписан потребител има достъп до проекта." + +msgid "The project can be accessed without any authentication." +msgstr "Всеки може да има достъп до проекта, без нужда от удостоверяване." + +msgid "The repository for this project does not exist." +msgstr "Хранилището за този проект не съществува." msgid "" "The review stage shows the time from creating the merge request to merging " @@ -305,6 +849,13 @@ msgstr "" "данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е " "(5+7)/2 = 6." +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Това означава, че няма да можете да изпращате код, докато не създадете " +"празно хранилище или не внесете съществуващо такова." + msgid "Time before an issue gets scheduled" msgstr "Време преди един проблем да бъде планиран за работа" @@ -318,6 +869,129 @@ msgstr "" msgid "Time until first merge request" msgstr "Време преди първата заявка за сливане" +msgid "Timeago|%s days ago" +msgstr "преди %s дни" + +msgid "Timeago|%s days remaining" +msgstr "остават %s дни" + +msgid "Timeago|%s hours remaining" +msgstr "остават %s часа" + +msgid "Timeago|%s minutes ago" +msgstr "преди %s минути" + +msgid "Timeago|%s minutes remaining" +msgstr "остават %s минути" + +msgid "Timeago|%s months ago" +msgstr "преди %s месеца" + +msgid "Timeago|%s months remaining" +msgstr "остават %s месеца" + +msgid "Timeago|%s seconds remaining" +msgstr "остават %s секунди" + +msgid "Timeago|%s weeks ago" +msgstr "преди %s седмици" + +msgid "Timeago|%s weeks remaining" +msgstr "остават %s седмици" + +msgid "Timeago|%s years ago" +msgstr "преди %s години" + +msgid "Timeago|%s years remaining" +msgstr "остават %s години" + +msgid "Timeago|1 day remaining" +msgstr "остава 1 ден" + +msgid "Timeago|1 hour remaining" +msgstr "остава 1 час" + +msgid "Timeago|1 minute remaining" +msgstr "остава 1 минута" + +msgid "Timeago|1 month remaining" +msgstr "остава 1 месец" + +msgid "Timeago|1 week remaining" +msgstr "остава 1 седмица" + +msgid "Timeago|1 year remaining" +msgstr "остава 1 година" + +msgid "Timeago|Past due" +msgstr "Просрочено" + +msgid "Timeago|a day ago" +msgstr "преди един ден" + +msgid "Timeago|a month ago" +msgstr "преди един месец" + +msgid "Timeago|a week ago" +msgstr "преди една седмица" + +msgid "Timeago|a while" +msgstr "преди известно време" + +msgid "Timeago|a year ago" +msgstr "преди една година" + +msgid "Timeago|about %s hours ago" +msgstr "преди около %s часа" + +msgid "Timeago|about a minute ago" +msgstr "преди около една минута" + +msgid "Timeago|about an hour ago" +msgstr "преди около един час" + +msgid "Timeago|in %s days" +msgstr "след %s дни" + +msgid "Timeago|in %s hours" +msgstr "след %s часа" + +msgid "Timeago|in %s minutes" +msgstr "след %s минути" + +msgid "Timeago|in %s months" +msgstr "след %s месеца" + +msgid "Timeago|in %s seconds" +msgstr "след %s секунди" + +msgid "Timeago|in %s weeks" +msgstr "след %s седмици" + +msgid "Timeago|in %s years" +msgstr "след %s години" + +msgid "Timeago|in 1 day" +msgstr "след 1 ден" + +msgid "Timeago|in 1 hour" +msgstr "след 1 час" + +msgid "Timeago|in 1 minute" +msgstr "след 1 минута" + +msgid "Timeago|in 1 month" +msgstr "след 1 месец" + +msgid "Timeago|in 1 week" +msgstr "след 1 седмица" + +msgid "Timeago|in 1 year" +msgstr "след 1 година" + +msgid "Timeago|less than a minute ago" +msgstr "преди по-малко от минута" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "час" @@ -337,17 +1011,119 @@ msgstr "Общо време" msgid "Total test time for all commits/merges" msgstr "Общо време за тестване на всички подавания/сливания" +msgid "Unstar" +msgstr "Без звезда" + +msgid "Upload New File" +msgstr "Качване на нов файл" + +msgid "Upload file" +msgstr "Качване на файл" + +msgid "Use your global notification setting" +msgstr "Използване на глобалната Ви настройка за известията" + +msgid "VisibilityLevel|Internal" +msgstr "Вътрешен" + +msgid "VisibilityLevel|Private" +msgstr "Частен" + +msgid "VisibilityLevel|Public" +msgstr "Публичен" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Искате ли да видите данните? Помолете администратор за достъп." msgid "We don't have enough data to show this stage." msgstr "Няма достатъчно данни за този етап." +msgid "Withdraw Access Request" +msgstr "Оттегляне на заявката за достъп" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да премахнете „%{project_name_with_namespace}“.\n" +"Ако го премахнете, той НЕ може да бъде възстановен!\n" +"НАИСТИНА ли искате това?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да прехвърлите „%{project_name_with_namespace}“ към друг " +"собственик. НАИСТИНА ли искате това?" + +msgid "You can only add files when you are on a branch" +msgstr "Можете да добавяте файлове само когато се намирате в клон" + +msgid "You must sign in to star a project" +msgstr "Трябва да се впишете, за да отбележите проект със звезда" + msgid "You need permission." msgstr "Нуждаете се от разрешение." +msgid "You will not get any notifications via email" +msgstr "Няма да получавате никакви известия по е-поща" + +msgid "You will only receive notifications for the events you choose" +msgstr "Ще получавате известия само за събитията, за които желаете" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "Ще получавате известия само за нещата, в които участвате" + +msgid "You will receive notifications for any activity" +msgstr "Ще получавате известия за всяка дейност" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "Ще получавате известия само за коментари, в които Ви @споменават" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, " +"докато не %{set_password_link} за профила си" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не " +"%{add_ssh_key_link} в профила си" + +msgid "Your name" +msgstr "Вашето име" + msgid "day" msgid_plural "days" msgstr[0] "ден" msgstr[1] "дни" +msgid "notification emails" +msgstr "известия по е-поща" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" + +msgid "pipeline schedules documentation" +msgstr "" + +msgid "with stage" +msgid_plural "with stages" +msgstr[0] "" +msgstr[1] "" + -- cgit v1.2.1 From c065040835fb487cbf5c2a07eedfeb7b79cdbe16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 13 Jun 2017 17:32:34 +0800 Subject: Optimization 'bg' translation --- app/assets/javascripts/locale/bg/app.js | 2 +- locale/bg/gitlab.po | 86 +++++++++++++++++---------------- locale/bg/gitlab.po.time_stamp | 1 - 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index 33a5c3c7eb9..24888e33b2e 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-12 09:36-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":[""],"About auto deploy":["Относно автоматичното внедряване"],"Active":[""],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":[""],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":[""],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":[""],"ChangeTypeActionLabel|Revert in branch":[""],"ChangeTypeAction|Cherry-pick":[""],"ChangeType|commit":[""],"ChangeType|merge request":[""],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":[""],"Cherry-pick this merge-request":[""],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":[""],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":[""],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":[""],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":[""],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":[""],"Custom":[""],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":[""],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":[""],"DownloadCommit|Plain Diff":[""],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":[""],"Every month (on the 1st at 4:00am)":[""],"Every week (Sundays at 4:00am)":[""],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Наследяване",""],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":[""],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":[""],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":[""],"OpenedNDaysAgo|Opened":["Отворен"],"Options":[""],"Owner":["Собственик"],"Pipeline":[""],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":[""],"Revert this merge-request":[""],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":[""],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":[""],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":[""],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["",""],"pipeline schedules documentation":[""],"with stage":["",""]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-13 04:23-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} подаде %{commit_timeago}"],"About auto deploy":["Относно автоматичното внедряване"],"Active":["Активно"],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":["Прикачете файл чрез влачене и пускане или %{upload_link}"],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":["Разглеждане на файловете"],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":["Избиране в клона"],"ChangeTypeActionLabel|Revert in branch":["Отмяна в клона"],"ChangeTypeAction|Cherry-pick":["Подбиране"],"ChangeType|commit":["подаване"],"ChangeType|merge request":["заявка за сливане"],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":["Подбиране на това подаване"],"Cherry-pick this merge-request":["Подбиране на тази заявка за сливане"],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":["Съобщение за подаването"],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":["Подадено от"],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":["Създаване на нов…"],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":["Етикет"],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":["Синтаксис на „Cron“"],"Custom":["Персонализиран"],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":["Задайте потребителски шаблон, използвайки синтаксиса на „Cron“"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":["Изпращане на кръпките по е-поща"],"DownloadCommit|Plain Diff":["Обикновен файл с разлики"],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":["Всеки ден (в 4 ч. сутринта)"],"Every month (on the 1st at 4:00am)":["Всеки месец (на 1-во число, в 4 ч. сутринта)"],"Every week (Sundays at 4:00am)":["Всяка седмица (в неделя, в 4 ч. сутринта)"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Разклонение","Разклонения"],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":["Научете повече в"],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":["Нов план"],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":["Филтър"],"OpenedNDaysAgo|Opened":["Отворен"],"Options":["Опции"],"Owner":["Собственик"],"Pipeline":["Схема"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":["Отмяна на това подаване"],"Revert this merge-request":["Отмяна на тази заявка за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":["Планиране на схемите"],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":["Създайте нова заявка за сливане с тези промени"],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["Този план за схема ще изпълнява схемите в бъдеще, периодично, за определени клонове или етикети. Тези планирани схеми ще наследят ограниченията на достъпа до проекта на свързания с тях потребител."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["На път сте да премахнете връзката на разклонението към оригиналния проект, „%{forked_from_project}“. НАИСТИНА ли искате това?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["родител","родители"],"pipeline schedules documentation":["документацията за планирането на схеми"],"with stage":["с етап","с етапи"]}}}; \ No newline at end of file diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index fd00796d763..370aca1f1d9 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -7,7 +7,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-12 09:36-0400\n" +"PO-Revision-Date: 2017-06-13 04:23-0400\n" "Last-Translator: Lyubomir Vasilev \n" "Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" @@ -15,13 +15,13 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "" +msgstr "%{commit_author_link} подаде %{commit_timeago}" msgid "About auto deploy" msgstr "Относно автоматичното внедряване" msgid "Active" -msgstr "" +msgstr "Активно" msgid "Activity" msgstr "Дейност" @@ -50,7 +50,7 @@ msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Наистина ли искате да изтриете този план за схема?" msgid "Attach a file by drag & drop or %{upload_link}" -msgstr "" +msgstr "Прикачете файл чрез влачене и пускане или %{upload_link}" msgid "Branch" msgid_plural "Branches" @@ -70,7 +70,7 @@ msgid "Branches" msgstr "Клонове" msgid "Browse files" -msgstr "" +msgstr "Разглеждане на файловете" msgid "ByAuthor|by" msgstr "от" @@ -82,19 +82,19 @@ msgid "Cancel" msgstr "Отказ" msgid "ChangeTypeActionLabel|Pick into branch" -msgstr "" +msgstr "Избиране в клона" msgid "ChangeTypeActionLabel|Revert in branch" -msgstr "" +msgstr "Отмяна в клона" msgid "ChangeTypeAction|Cherry-pick" -msgstr "" +msgstr "Подбиране" msgid "ChangeType|commit" -msgstr "" +msgstr "подаване" msgid "ChangeType|merge request" -msgstr "" +msgstr "заявка за сливане" msgid "Changelog" msgstr "Списък с промени" @@ -103,10 +103,10 @@ msgid "Charts" msgstr "Графики" msgid "Cherry-pick this commit" -msgstr "" +msgstr "Подбиране на това подаване" msgid "Cherry-pick this merge-request" -msgstr "" +msgstr "Подбиране на тази заявка за сливане" msgid "CiStatusLabel|canceled" msgstr "отказано" @@ -168,7 +168,7 @@ msgstr[0] "Подаване" msgstr[1] "Подавания" msgid "Commit message" -msgstr "" +msgstr "Съобщение за подаването" msgid "CommitMessage|Add %{file_name}" msgstr "Добавяне на „%{file_name}“" @@ -180,7 +180,7 @@ msgid "Commits|History" msgstr "История" msgid "Committed by" -msgstr "" +msgstr "Подадено от" msgid "Compare" msgstr "Сравнение" @@ -210,22 +210,22 @@ msgid "Create merge request" msgstr "Създаване на заявка за сливане" msgid "Create new..." -msgstr "" +msgstr "Създаване на нов…" msgid "CreateNewFork|Fork" msgstr "Разклоняване" msgid "CreateTag|Tag" -msgstr "" +msgstr "Етикет" msgid "Cron Timezone" msgstr "Часова зона за „Cron“" msgid "Cron syntax" -msgstr "" +msgstr "Синтаксис на „Cron“" msgid "Custom" -msgstr "" +msgstr "Персонализиран" msgid "Custom notification events" msgstr "Персонализирани събития за известяване" @@ -271,7 +271,7 @@ msgid "CycleAnalyticsStage|Test" msgstr "Тестване" msgid "Define a custom pattern with cron syntax" -msgstr "" +msgstr "Задайте потребителски шаблон, използвайки синтаксиса на „Cron“" msgid "Delete" msgstr "Изтриване" @@ -309,10 +309,10 @@ msgid "DownloadArtifacts|Download" msgstr "Сваляне" msgid "DownloadCommit|Email Patches" -msgstr "" +msgstr "Изпращане на кръпките по е-поща" msgid "DownloadCommit|Plain Diff" -msgstr "" +msgstr "Обикновен файл с разлики" msgid "DownloadSource|Download" msgstr "Сваляне" @@ -324,13 +324,13 @@ msgid "Edit Pipeline Schedule %{id}" msgstr "Редактиране на плана %{id} за схема" msgid "Every day (at 4:00am)" -msgstr "" +msgstr "Всеки ден (в 4 ч. сутринта)" msgid "Every month (on the 1st at 4:00am)" -msgstr "" +msgstr "Всеки месец (на 1-во число, в 4 ч. сутринта)" msgid "Every week (Sundays at 4:00am)" -msgstr "" +msgstr "Всяка седмица (в неделя, в 4 ч. сутринта)" msgid "Failed to change the owner" msgstr "Собственикът не може да бъде променен" @@ -353,11 +353,10 @@ msgstr "Първо" msgid "FirstPushedBy|pushed by" msgstr "изпращане на промени от" -#, fuzzy msgid "Fork" msgid_plural "Forks" -msgstr[0] "Наследяване" -msgstr[1] "" +msgstr[0] "Разклонение" +msgstr[1] "Разклонения" msgid "ForkedFromProjectPath|Forked from" msgstr "Разклонение на" @@ -411,7 +410,7 @@ msgid "Last commit" msgstr "Последно подаване" msgid "Learn more in the" -msgstr "" +msgstr "Научете повече в" msgid "Leave group" msgstr "Напускане на групата" @@ -454,7 +453,7 @@ msgid "New merge request" msgstr "Нова заявка за сливане" msgid "New schedule" -msgstr "" +msgstr "Нов план" msgid "New snippet" msgstr "Нов отрязък" @@ -529,19 +528,19 @@ msgid "NotificationLevel|Watch" msgstr "Наблюдение" msgid "OfSearchInADropdown|Filter" -msgstr "" +msgstr "Филтър" msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" msgid "Options" -msgstr "" +msgstr "Опции" msgid "Owner" msgstr "Собственик" msgid "Pipeline" -msgstr "" +msgstr "Схема" msgid "Pipeline Health" msgstr "Състояние" @@ -676,10 +675,10 @@ msgid "Request Access" msgstr "Заявка за достъп" msgid "Revert this commit" -msgstr "" +msgstr "Отмяна на това подаване" msgid "Revert this merge-request" -msgstr "" +msgstr "Отмяна на тази заявка за сливане" msgid "Save pipeline schedule" msgstr "Запазване на плана за схема" @@ -688,7 +687,7 @@ msgid "Schedule a new pipeline" msgstr "Създаване на нов план за схема" msgid "Scheduling Pipelines" -msgstr "" +msgstr "Планиране на схемите" msgid "Search branches and tags" msgstr "Търсене в клоновете и етикетите" @@ -731,7 +730,7 @@ msgid "StarProject|Star" msgstr "Звезда" msgid "Start a new merge request with these changes" -msgstr "" +msgstr "Създайте нова заявка за сливане с тези промени" msgid "Switch branch/tag" msgstr "Преминаване към клон/етикет" @@ -780,6 +779,9 @@ msgid "" "specific branches or tags. Those scheduled pipelines will inherit limited " "project access based on their associated user." msgstr "" +"Този план за схема ще изпълнява схемите в бъдеще, периодично, за определени " +"клонове или етикети. Тези планирани схеми ще наследят ограниченията на " +"достъпа до проекта на свързания с тях потребител." msgid "" "The planning stage shows the time from the previous step to pushing your " @@ -1054,6 +1056,8 @@ msgid "" "You are going to remove the fork relationship to source project " "%{forked_from_project}. Are you ABSOLUTELY sure?" msgstr "" +"На път сте да премахнете връзката на разклонението към оригиналния проект, " +"„%{forked_from_project}“. НАИСТИНА ли искате това?" msgid "" "You are going to transfer %{project_name_with_namespace} to another owner. " @@ -1116,14 +1120,14 @@ msgstr "известия по е-поща" msgid "parent" msgid_plural "parents" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "родител" +msgstr[1] "родители" msgid "pipeline schedules documentation" -msgstr "" +msgstr "документацията за планирането на схеми" msgid "with stage" msgid_plural "with stages" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "с етап" +msgstr[1] "с етапи" diff --git a/locale/bg/gitlab.po.time_stamp b/locale/bg/gitlab.po.time_stamp index 0519ecba6ea..e69de29bb2d 100644 --- a/locale/bg/gitlab.po.time_stamp +++ b/locale/bg/gitlab.po.time_stamp @@ -1 +0,0 @@ - \ No newline at end of file -- cgit v1.2.1 From 01485316f5e0fd10730e1a6bbeb71ff5f2d45d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Wed, 14 Jun 2017 17:52:00 +0800 Subject: translation optimization in synchronous reviews --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 124 ++++++++++++++--------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 67054b3ef4a..b43910dc641 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-13 04:07-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["激活"],"Activity":["活動"],"Add Changelog":["添加變更日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["您確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["變更日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["作者:"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標簽"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每天(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月1日(淩晨4點)"],"Every week (Sundays at 4:00am)":["每周日(淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["間隔模式"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最後 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["離開群組"],"Leave project":["離開項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新合併請求"],"New schedule":["新计划"],"New snippet":["新代碼片段"],"New tag":["新標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["選項"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["已激活"],"PipelineSchedules|Active":["激活"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["等待"],"PipelineSchedules|Next Run":["下壹個運行"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有權"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建完成。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 已刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新增流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["包含這些更改到 新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["大約 %s 小時前"],"Timeago|about a minute ago":["大約 1 分鐘前"],"Timeago|about an hour ago":["大約 1 小時前"],"Timeago|in %s days":["在 %s 天"],"Timeago|in %s hours":["在 %s 小時"],"Timeago|in %s minutes":["在 %s 分鐘"],"Timeago|in %s months":["在 %s 個月"],"Timeago|in %s seconds":["在 %s 秒"],"Timeago|in %s weeks":["在 %s 星期"],"Timeago|in %s years":["在 %s 年"],"Timeago|in 1 day":["在 1 天"],"Timeago|in 1 hour":["在 1 小時"],"Timeago|in 1 minute":["在 1 分鐘"],"Timeago|in 1 month":["在 1 月"],"Timeago|in 1 week":["在 1 星期"],"Timeago|in 1 year":["在 1 年"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["您將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n您確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?"],"You can only add files when you are on a branch":["您只能在分支上添加文件"],"You must sign in to star a project":["您必須登錄才能對項目加星標"],"You need permission.":["您需要相關的權限。"],"You will not get any notifications via email":["您將不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["您只會收到您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["您只會收到您參與的主題的通知"],"You will receive notifications for any activity":["您將不會收到任何通知"],"You will receive notifications only for comments in which you were @mentioned":["您只會收到評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中添加 %{add_ssh_key_link} 之前您將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-14 05:43-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交於"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標簽"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月执行(第一天淩晨4點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["重復週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新增議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["選項"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["由此更改創建新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重復運行指定分支或標簽的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 8fbc38ba5af..0be8887cc61 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -7,7 +7,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-13 04:07-0400\n" +"PO-Revision-Date: 2017-06-14 05:43-0400\n" "Last-Translator: Huang Tao \n" "Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" @@ -21,13 +21,13 @@ msgid "About auto deploy" msgstr "關於自動部署" msgid "Active" -msgstr "激活" +msgstr "啟用" msgid "Activity" msgstr "活動" msgid "Add Changelog" -msgstr "添加變更日誌" +msgstr "添加更新日誌" msgid "Add Contribution guide" msgstr "添加貢獻指南" @@ -45,7 +45,7 @@ msgid "Archived project! Repository is read-only" msgstr "歸檔項目!存儲庫為只讀" msgid "Are you sure you want to delete this pipeline schedule?" -msgstr "您確定要刪除此流水線計劃嗎?" +msgstr "確定要刪除此流水線計劃嗎?" msgid "Attach a file by drag & drop or %{upload_link}" msgstr "拖放文件到此處或者 %{upload_link}" @@ -59,7 +59,7 @@ msgid "" "choose a GitLab CI Yaml template and commit your changes. " "%{link_to_autodeploy_doc}" msgstr "" -"分支 %{branch_name} 已創建。如要設置自動部署, 請選擇合適的 GitLab CI Yaml " +"分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml " "模板併提交更改。%{link_to_autodeploy_doc}" msgid "Branches" @@ -93,7 +93,7 @@ msgid "ChangeType|merge request" msgstr "合併請求" msgid "Changelog" -msgstr "變更日誌" +msgstr "更新日誌" msgid "Charts" msgstr "統計圖" @@ -175,7 +175,7 @@ msgid "Commits|History" msgstr "歷史" msgid "Committed by" -msgstr "作者:" +msgstr "提交於" msgid "Compare" msgstr "比較" @@ -314,13 +314,13 @@ msgid "Edit Pipeline Schedule %{id}" msgstr "編輯 %{id} 流水線計劃" msgid "Every day (at 4:00am)" -msgstr "每天(淩晨4點)" +msgstr "每日執行(淩晨4點)" msgid "Every month (on the 1st at 4:00am)" -msgstr "每月1日(淩晨4點)" +msgstr "每月执行(第一天淩晨4點)" msgid "Every week (Sundays at 4:00am)" -msgstr "每周日(淩晨4點)" +msgstr "每週執行(周日淩晨4點)" msgid "Failed to change the owner" msgstr "無法變更所有者" @@ -372,7 +372,7 @@ msgid "Import repository" msgstr "導入存儲庫" msgid "Interval Pattern" -msgstr "間隔模式" +msgstr "重復週期" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" @@ -385,7 +385,7 @@ msgstr "啟用" msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最後 %d 天" +msgstr[0] "最近 %d 天" msgid "Last Pipeline" msgstr "最新流水線" @@ -400,10 +400,10 @@ msgid "Learn more in the" msgstr "了解更多" msgid "Leave group" -msgstr "離開群組" +msgstr "退出群組" msgid "Leave project" -msgstr "離開項目" +msgstr "退出項目" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" @@ -417,13 +417,13 @@ msgstr "添加壹個 SSH 公鑰" msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "新議題" +msgstr[0] "新增議題" msgid "New Pipeline Schedule" msgstr "創建流水線計劃" msgid "New branch" -msgstr "新分支" +msgstr "新增分支" msgid "New directory" msgstr "新增目錄" @@ -435,16 +435,16 @@ msgid "New issue" msgstr "新議題" msgid "New merge request" -msgstr "新合併請求" +msgstr "新增合併請求" msgid "New schedule" -msgstr "新计划" +msgstr "新增计划" msgid "New snippet" msgstr "新代碼片段" msgid "New tag" -msgstr "新標籤" +msgstr "新增標籤" msgid "No repository" msgstr "沒有存儲庫" @@ -474,13 +474,13 @@ msgid "NotificationEvent|Merge merge request" msgstr "合併請求被合併" msgid "NotificationEvent|New issue" -msgstr "新議題" +msgstr "新增議題" msgid "NotificationEvent|New merge request" msgstr "新合併請求" msgid "NotificationEvent|New note" -msgstr "新評論" +msgstr "新增評論" msgid "NotificationEvent|Reassign issue" msgstr "重新指派議題" @@ -537,19 +537,19 @@ msgid "Pipeline Schedules" msgstr "流水線計劃" msgid "PipelineSchedules|Activated" -msgstr "已激活" +msgstr "是否啟用" msgid "PipelineSchedules|Active" -msgstr "激活" +msgstr "已啟用" msgid "PipelineSchedules|All" msgstr "所有" msgid "PipelineSchedules|Inactive" -msgstr "等待" +msgstr "未啟用" msgid "PipelineSchedules|Next Run" -msgstr "下壹個運行" +msgstr "下次運行時間" msgid "PipelineSchedules|None" msgstr "無" @@ -558,7 +558,7 @@ msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "為此流水線提供簡短描述" msgid "PipelineSchedules|Take ownership" -msgstr "取得所有權" +msgstr "取得所有者" msgid "PipelineSchedules|Target" msgstr "目標" @@ -567,13 +567,13 @@ msgid "Project '%{project_name}' queued for deletion." msgstr "項目 '%{project_name}' 已進入刪除隊列。" msgid "Project '%{project_name}' was successfully created." -msgstr "項目 '%{project_name}' 已創建完成。" +msgstr "項目 '%{project_name}' 已創建成功。" msgid "Project '%{project_name}' was successfully updated." msgstr "項目 '%{project_name}' 已更新完成。" msgid "Project '%{project_name}' will be deleted." -msgstr "項目 '%{project_name}' 已刪除。" +msgstr "項目 '%{project_name}' 將被刪除。" msgid "Project access must be granted explicitly to each user." msgstr "項目訪問權限必須明確授權給每個用戶。" @@ -665,7 +665,7 @@ msgid "Save pipeline schedule" msgstr "保存流水線計劃" msgid "Schedule a new pipeline" -msgstr "新增流水線計劃" +msgstr "新建流水線計劃" msgid "Scheduling Pipelines" msgstr "流水線計劃" @@ -708,7 +708,7 @@ msgid "StarProject|Star" msgstr "星標" msgid "Start a new merge request with these changes" -msgstr "包含這些更改到 新合併請求" +msgstr "由此更改創建新合併請求" msgid "Switch branch/tag" msgstr "切換分支/標籤" @@ -739,7 +739,7 @@ msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." -msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建壹個議題後,數據將自動添加到此處。" +msgstr "議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。" msgid "The phase of the development lifecycle." msgstr "項目生命週期中的各個階段。" @@ -748,13 +748,13 @@ msgid "" "The pipelines schedule runs pipelines in the future, repeatedly, for " "specific branches or tags. Those scheduled pipelines will inherit limited " "project access based on their associated user." -msgstr "流水線計劃會針對特定的分支或標簽在以後重復運行流水線。這些預定的流水線將根據其相關用戶繼承有限的項目訪問。" +msgstr "流水線計劃會週期性重復運行指定分支或標簽的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。" msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " "commit." -msgstr "計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。" +msgstr "計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。" msgid "" "The production stage shows the total time it takes between creating an issue " @@ -888,52 +888,52 @@ msgid "Timeago|a year ago" msgstr "1 年前" msgid "Timeago|about %s hours ago" -msgstr "大約 %s 小時前" +msgstr "約 %s 小時前" msgid "Timeago|about a minute ago" -msgstr "大約 1 分鐘前" +msgstr "約 1 分鐘前" msgid "Timeago|about an hour ago" -msgstr "大約 1 小時前" +msgstr "約 1 小時前" msgid "Timeago|in %s days" -msgstr "在 %s 天" +msgstr " %s 天後" msgid "Timeago|in %s hours" -msgstr "在 %s 小時" +msgstr " %s 小時後" msgid "Timeago|in %s minutes" -msgstr "在 %s 分鐘" +msgstr " %s 分鐘後" msgid "Timeago|in %s months" -msgstr "在 %s 個月" +msgstr " %s 個月後" msgid "Timeago|in %s seconds" -msgstr "在 %s 秒" +msgstr " %s 秒後" msgid "Timeago|in %s weeks" -msgstr "在 %s 星期" +msgstr " %s 星期後" msgid "Timeago|in %s years" -msgstr "在 %s 年" +msgstr " %s 年後" msgid "Timeago|in 1 day" -msgstr "在 1 天" +msgstr " 1 天後" msgid "Timeago|in 1 hour" -msgstr "在 1 小時" +msgstr " 1 小時後" msgid "Timeago|in 1 minute" -msgstr "在 1 分鐘" +msgstr " 1 分鐘後" msgid "Timeago|in 1 month" -msgstr "在 1 月" +msgstr " 1 月後" msgid "Timeago|in 1 week" -msgstr "在 1 星期" +msgstr " 1 星期後" msgid "Timeago|in 1 year" -msgstr "在 1 年" +msgstr " 1 年後" msgid "Timeago|less than a minute ago" msgstr "不到 1 分鐘前" @@ -989,46 +989,46 @@ msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" -msgstr "您將要刪除 %{project_name_with_namespace}。\n" +msgstr "即將要刪除 %{project_name_with_namespace}。\n" "已刪除的項目無法恢復!\n" -"您確定繼續嗎?" +"確定繼續嗎?" msgid "" "You are going to remove the fork relationship to source project " "%{forked_from_project}. Are you ABSOLUTELY sure?" -msgstr "您將刪除與源項目 %{forked_from_project} 的派生關系。您確定繼續嗎?" +msgstr "即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?" msgid "" "You are going to transfer %{project_name_with_namespace} to another owner. " "Are you ABSOLUTELY sure?" -msgstr "將 %{project_name_with_namespace} 轉義給另壹個所有者。您確定繼續嗎?" +msgstr "即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?" msgid "You can only add files when you are on a branch" -msgstr "您只能在分支上添加文件" +msgstr "只能在分支上添加文件" msgid "You must sign in to star a project" -msgstr "您必須登錄才能對項目加星標" +msgstr "必須登錄才能對項目加星標" msgid "You need permission." -msgstr "您需要相關的權限。" +msgstr "需要相關的權限。" msgid "You will not get any notifications via email" -msgstr "您將不會收到任何通知郵件" +msgstr "不會收到任何通知郵件" msgid "You will only receive notifications for the events you choose" -msgstr "您只會收到您選擇的事件通知" +msgstr "只接收您選擇的事件通知" msgid "" "You will only receive notifications for threads you have participated in" -msgstr "您只會收到您參與的主題的通知" +msgstr "只接收您參與的主題的通知" msgid "You will receive notifications for any activity" -msgstr "您將不會收到任何通知" +msgstr "接收所有活動的通知" msgid "" "You will receive notifications only for comments in which you were " "@mentioned" -msgstr "您只會收到評論中提及(@)您的通知" +msgstr "只接收評論中提及(@)您的通知" msgid "" "You won't be able to pull or push project code via %{protocol} until you " @@ -1038,7 +1038,7 @@ msgstr "在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉 msgid "" "You won't be able to pull or push project code via SSH until you " "%{add_ssh_key_link} to your profile" -msgstr "在賬號中添加 %{add_ssh_key_link} 之前您將無法通過 SSH 拉取或推送代碼。" +msgstr "在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。" msgid "Your name" msgstr "您的名字" -- cgit v1.2.1 From c883883d9192d0801a30d45da80bc30f452bfdc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Thu, 15 Jun 2017 18:42:50 +0800 Subject: translation optimization in synchronous reviews --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index b43910dc641..b22262bb24d 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-14 05:43-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交於"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標簽"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月执行(第一天淩晨4點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["重復週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新增議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["選項"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只有團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["由此更改創建新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重復運行指定分支或標簽的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢復!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-15 06:23-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交於"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月執行(每月1日淩晨4點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["重複週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新建議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已被刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只限團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["由此更改創建新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢複!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 0be8887cc61..fc5feb0ab2c 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -7,7 +7,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-14 05:43-0400\n" +"PO-Revision-Date: 2017-06-15 06:23-0400\n" "Last-Translator: Huang Tao \n" "Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" @@ -211,7 +211,7 @@ msgid "CreateNewFork|Fork" msgstr "派生" msgid "CreateTag|Tag" -msgstr "標簽" +msgstr "標籤" msgid "Cron Timezone" msgstr "Cron 時區" @@ -317,7 +317,7 @@ msgid "Every day (at 4:00am)" msgstr "每日執行(淩晨4點)" msgid "Every month (on the 1st at 4:00am)" -msgstr "每月执行(第一天淩晨4點)" +msgstr "每月執行(每月1日淩晨4點)" msgid "Every week (Sundays at 4:00am)" msgstr "每週執行(周日淩晨4點)" @@ -372,7 +372,7 @@ msgid "Import repository" msgstr "導入存儲庫" msgid "Interval Pattern" -msgstr "重復週期" +msgstr "重複週期" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" @@ -417,7 +417,7 @@ msgstr "添加壹個 SSH 公鑰" msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "新增議題" +msgstr[0] "新建議題" msgid "New Pipeline Schedule" msgstr "創建流水線計劃" @@ -519,7 +519,7 @@ msgid "OpenedNDaysAgo|Opened" msgstr "開始於" msgid "Options" -msgstr "選項" +msgstr "操作" msgid "Owner" msgstr "所有者" @@ -582,7 +582,7 @@ msgid "Project export could not be deleted." msgstr "無法刪除項目導出。" msgid "Project export has been deleted." -msgstr "項目導出已刪除。" +msgstr "項目導出已被刪除。" msgid "" "Project export link has expired. Please generate a new export from your " @@ -602,7 +602,7 @@ msgid "ProjectFeature|Everyone with access" msgstr "任何人都可訪問" msgid "ProjectFeature|Only team members" -msgstr "只有團隊成員" +msgstr "只限團隊成員" msgid "ProjectFileTree|Name" msgstr "名稱" @@ -748,7 +748,7 @@ msgid "" "The pipelines schedule runs pipelines in the future, repeatedly, for " "specific branches or tags. Those scheduled pipelines will inherit limited " "project access based on their associated user." -msgstr "流水線計劃會週期性重復運行指定分支或標簽的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。" +msgstr "流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。" msgid "" "The planning stage shows the time from the previous step to pushing your " @@ -990,7 +990,7 @@ msgid "" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" msgstr "即將要刪除 %{project_name_with_namespace}。\n" -"已刪除的項目無法恢復!\n" +"已刪除的項目無法恢複!\n" "確定繼續嗎?" msgid "" -- cgit v1.2.1 From a62bead807a0d44bcdf30324cbd293b4f5af03af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 16 Jun 2017 10:53:47 +0800 Subject: add missing translation fields 1. Follow up !12052 commits. --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 46 ++++++++++++++++++------------ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index b22262bb24d..c87cab5423f 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-15 06:23-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeType|commit":["提交"],"ChangeType|merge request":["合併請求"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge-request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交於"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom":["自定義"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月執行(每月1日淩晨4點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["重複週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新建議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已被刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只限團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge-request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a new merge request with these changes":["由此更改創建新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢複!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"notification emails":["通知郵件"],"parent":["父級"],"pipeline schedules documentation":["流水線計劃文檔"],"with stage":["於階段"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 14:57+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-15 10:34-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeTypeAction|Revert":["還原"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitBoxTitle|Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交於"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月執行(每月1日淩晨4點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["重複週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["流水線計劃文檔"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新建議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"PipelineSheduleIntervalPattern|Custom":["自定義"],"Pipeline|with stage":["於階段"],"Pipeline|with stages":["於階段"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已被刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只限團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a %{new_merge_request} with these changes":["由此更改 %{new_merge_request}"],"Start a new merge request with these changes":["由此更改創建新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢複!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"new merge request":["新建合併請求"],"notification emails":["通知郵件"],"parent":["父級"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index fc5feb0ab2c..8277c8d8e27 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -3,11 +3,11 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-12 19:29-0500\n" +"POT-Creation-Date: 2017-06-15 14:57+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-15 06:23-0400\n" +"PO-Revision-Date: 2017-06-15 10:34-0400\n" "Last-Translator: Huang Tao \n" "Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" @@ -86,11 +86,8 @@ msgstr "還原分支" msgid "ChangeTypeAction|Cherry-pick" msgstr "優選" -msgid "ChangeType|commit" -msgstr "提交" - -msgid "ChangeType|merge request" -msgstr "合併請求" +msgid "ChangeTypeAction|Revert" +msgstr "還原" msgid "Changelog" msgstr "更新日誌" @@ -101,7 +98,7 @@ msgstr "統計圖" msgid "Cherry-pick this commit" msgstr "優選此提交" -msgid "Cherry-pick this merge-request" +msgid "Cherry-pick this merge request" msgstr "優選此合併請求" msgid "CiStatusLabel|canceled" @@ -165,6 +162,9 @@ msgstr[0] "提交" msgid "Commit message" msgstr "提交信息" +msgid "CommitBoxTitle|Commit" +msgstr "提交" + msgid "CommitMessage|Add %{file_name}" msgstr "添加 %{file_name}" @@ -219,9 +219,6 @@ msgstr "Cron 時區" msgid "Cron syntax" msgstr "Cron 語法" -msgid "Custom" -msgstr "自定義" - msgid "Custom notification events" msgstr "自定義通知事件" @@ -399,6 +396,9 @@ msgstr "最後提交" msgid "Learn more in the" msgstr "了解更多" +msgid "Learn more in the|pipeline schedules documentation" +msgstr "流水線計劃文檔" + msgid "Leave group" msgstr "退出群組" @@ -563,6 +563,15 @@ msgstr "取得所有者" msgid "PipelineSchedules|Target" msgstr "目標" +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "自定義" + +msgid "Pipeline|with stage" +msgstr "於階段" + +msgid "Pipeline|with stages" +msgstr "於階段" + msgid "Project '%{project_name}' queued for deletion." msgstr "項目 '%{project_name}' 已進入刪除隊列。" @@ -658,7 +667,7 @@ msgstr "申請訪問" msgid "Revert this commit" msgstr "還原此提交" -msgid "Revert this merge-request" +msgid "Revert this merge request" msgstr "還原此合併請求" msgid "Save pipeline schedule" @@ -707,6 +716,9 @@ msgstr "源代碼" msgid "StarProject|Star" msgstr "星標" +msgid "Start a %{new_merge_request} with these changes" +msgstr "由此更改 %{new_merge_request}" + msgid "Start a new merge request with these changes" msgstr "由此更改創建新合併請求" @@ -1047,6 +1059,9 @@ msgid "day" msgid_plural "days" msgstr[0] "天" +msgid "new merge request" +msgstr "新建合併請求" + msgid "notification emails" msgstr "通知郵件" @@ -1054,10 +1069,3 @@ msgid "parent" msgid_plural "parents" msgstr[0] "父級" -msgid "pipeline schedules documentation" -msgstr "流水線計劃文檔" - -msgid "with stage" -msgid_plural "with stages" -msgstr[0] "於階段" - -- cgit v1.2.1 From b4e1529e57e28dade05e307c7a3ff508c2d54f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Mon, 19 Jun 2017 17:06:17 -0400 Subject: Reinstall shell on the test env if it's outdated --- config/initializers/5_backend.rb | 10 ++++++---- spec/support/test_env.rb | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb index 2bd159ca7f1..482613dacc9 100644 --- a/config/initializers/5_backend.rb +++ b/config/initializers/5_backend.rb @@ -1,6 +1,8 @@ -required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required) -current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version) +unless Rails.env.test? + required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required) + current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version) -unless current_version.valid? && required_version <= current_version - warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell." + unless current_version.valid? && required_version <= current_version + warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell." + end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 3f472e59c49..6d71dd476c3 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -120,18 +120,21 @@ module TestEnv end def setup_gitlab_shell - unless File.directory?(Gitlab.config.gitlab_shell.path) - unless system('rake', 'gitlab:shell:install') - raise 'Can`t clone gitlab-shell' - end + shell_needs_update = component_needs_update?(Gitlab.config.gitlab_shell.path, + Gitlab::Shell.version_required) + + unless !shell_needs_update || system('rake', 'gitlab:shell:install') + raise 'Can`t clone gitlab-shell' end end def setup_gitaly socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '') gitaly_dir = File.dirname(socket_path) + gitaly_needs_update = component_needs_update?(gitaly_dir, + Gitlab::GitalyClient.expected_server_version) - unless !gitaly_needs_update?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]") + unless !gitaly_needs_update || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]") raise "Can't clone gitaly" end @@ -261,13 +264,13 @@ module TestEnv end end - def gitaly_needs_update?(gitaly_dir) - gitaly_version = File.read(File.join(gitaly_dir, 'VERSION')).strip + def component_needs_update?(component_folder, expected_version) + version = File.read(File.join(component_folder, 'VERSION')).strip # Notice that this will always yield true when using branch versions # (`=branch_name`), but that actually makes sure the server is always based # on the latest branch revision. - gitaly_version != Gitlab::GitalyClient.expected_server_version + version != expected_version rescue Errno::ENOENT true end -- cgit v1.2.1 From 298225e2a47974eab5326f408a4d27a737321769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Thu, 8 Jun 2017 13:06:34 +0800 Subject: supplement traditional chinese in hong kong translation Fix #33442 --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- ...al_chinese_in_hong_kong_translation_of_i18n.yml | 4 + locale/zh_HK/gitlab.po | 905 +++++++++++++++++++-- 3 files changed, 846 insertions(+), 65 deletions(-) create mode 100644 changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 30cb1e6b89e..c87cab5423f 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/75177/zh_HK/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_HK","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["作者:"],"Cancel":[""],"Commit":["提交"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Delete":[""],"Deploy":["部署"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Interval Pattern":[""],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":[""],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["不可用"],"Not enough data":["數據不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":[""],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["項目生命週期"],"Read more":["了解更多"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合並請求"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["顯示 %d 個事件"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合並或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 14:57+0200","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-15 10:34-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","Plural-Forms":"nplurals=1; plural=0;","X-Generator":"Zanata 3.9.6","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0;"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeTypeAction|Revert":["還原"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitBoxTitle|Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交於"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["Diff 文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨4點)"],"Every month (on the 1st at 4:00am)":["每月執行(每月1日淩晨4點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨4點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["重複週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["流水線計劃文檔"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新建議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重新打開議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"PipelineSheduleIntervalPattern|Custom":["自定義"],"Pipeline|with stage":["於階段"],"Pipeline|with stages":["於階段"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已被刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只限團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請訪問"],"Revert this commit":["還原此提交"],"Revert this merge request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a %{new_merge_request} with these changes":["由此更改 %{new_merge_request}"],"Start a new merge request with these changes":["由此更改創建新合併請求"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":["%s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":["%s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":["%s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":["%s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":["%s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":["1 天前"],"Timeago|a month ago":["1 個月前"],"Timeago|a week ago":["1 星期前"],"Timeago|a while":["剛剛"],"Timeago|a year ago":["1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消訪問請求"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢複!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"new merge request":["新建合併請求"],"notification emails":["通知郵件"],"parent":["父級"]}}}; \ No newline at end of file diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml new file mode 100644 index 00000000000..e383bab23d6 --- /dev/null +++ b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page +merge_request: 11995 +author: Huang Tao diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index f0a9e44daf3..8277c8d8e27 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -1,39 +1,240 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the gitlab package. -# FIRST AUTHOR , YEAR. -# +# Huang Tao , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-05-04 19:24-0500\n" -"Last-Translator: HuangTao , 2017\n" -"Language-Team: Chinese (Hong Kong) (https://www.transifex.com/gitlab-zh/teams/" -"75177/zh_HK/)\n" +"POT-Creation-Date: 2017-06-15 14:57+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: zh_HK\n" +"PO-Revision-Date: 2017-06-15 10:34-0400\n" +"Last-Translator: Huang Tao \n" +"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n" +"Language: zh-HK\n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Zanata 3.9.6\n" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "由 %{commit_author_link} 提交於 %{commit_timeago}" + +msgid "About auto deploy" +msgstr "關於自動部署" + +msgid "Active" +msgstr "啟用" + +msgid "Activity" +msgstr "活動" + +msgid "Add Changelog" +msgstr "添加更新日誌" + +msgid "Add Contribution guide" +msgstr "添加貢獻指南" + +msgid "Add License" +msgstr "添加許可證" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。" + +msgid "Add new directory" +msgstr "添加新目錄" + +msgid "Archived project! Repository is read-only" +msgstr "歸檔項目!存儲庫為只讀" msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "確定要刪除此流水線計劃嗎?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "拖放文件到此處或者 %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "分支" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" msgstr "" +"分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml " +"模板併提交更改。%{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "分支" + +msgid "Browse files" +msgstr "瀏覽文件" msgid "ByAuthor|by" msgstr "作者:" +msgid "CI configuration" +msgstr "CI 配置" + msgid "Cancel" -msgstr "" +msgstr "取消" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "挑選到分支" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "還原分支" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "優選" + +msgid "ChangeTypeAction|Revert" +msgstr "還原" + +msgid "Changelog" +msgstr "更新日誌" + +msgid "Charts" +msgstr "統計圖" + +msgid "Cherry-pick this commit" +msgstr "優選此提交" + +msgid "Cherry-pick this merge request" +msgstr "優選此合併請求" + +msgid "CiStatusLabel|canceled" +msgstr "已取消" + +msgid "CiStatusLabel|created" +msgstr "已創建" + +msgid "CiStatusLabel|failed" +msgstr "已失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動操作" + +msgid "CiStatusLabel|passed" +msgstr "已通過" + +msgid "CiStatusLabel|passed with warnings" +msgstr "已通過但有警告" + +msgid "CiStatusLabel|pending" +msgstr "等待中" + +msgid "CiStatusLabel|skipped" +msgstr "已跳過" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "等待手動操作" + +msgid "CiStatusText|blocked" +msgstr "已阻塞" + +msgid "CiStatusText|canceled" +msgstr "已取消" + +msgid "CiStatusText|created" +msgstr "已創建" + +msgid "CiStatusText|failed" +msgstr "已失敗" + +msgid "CiStatusText|manual" +msgstr "待手動" + +msgid "CiStatusText|passed" +msgstr "已通過" + +msgid "CiStatusText|pending" +msgstr "等待中" + +msgid "CiStatusText|skipped" +msgstr "已跳過" + +msgid "CiStatus|running" +msgstr "運行中" msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "Commit message" +msgstr "提交信息" + +msgid "CommitBoxTitle|Commit" +msgstr "提交" + +msgid "CommitMessage|Add %{file_name}" +msgstr "添加 %{file_name}" + +msgid "Commits" +msgstr "提交" + +msgid "Commits|History" +msgstr "歷史" + +msgid "Committed by" +msgstr "提交於" + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "貢獻指南" + +msgid "Contributors" +msgstr "貢獻者" + +msgid "Copy URL to clipboard" +msgstr "複製URL到剪貼板" + +msgid "Copy commit SHA to clipboard" +msgstr "複製提交 SHA 到剪貼板" + +msgid "Create New Directory" +msgstr "創建新目錄" + +msgid "Create directory" +msgstr "創建目錄" + +msgid "Create empty bare repository" +msgstr "創建空的存儲庫" + +msgid "Create merge request" +msgstr "創建合併請求" + +msgid "Create new..." +msgstr "創建..." + +msgid "CreateNewFork|Fork" +msgstr "派生" + +msgid "CreateTag|Tag" +msgstr "標籤" + msgid "Cron Timezone" +msgstr "Cron 時區" + +msgid "Cron syntax" +msgstr "Cron 語法" + +msgid "Custom notification events" +msgstr "自定義通知事件" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." msgstr "" +"自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}." + +msgid "Cycle Analytics" +msgstr "週期分析" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." msgstr "週期分析概述了項目從想法到產品實現的各階段所需的時間。" msgid "CycleAnalyticsStage|Code" @@ -57,30 +258,81 @@ msgstr "預發布" msgid "CycleAnalyticsStage|Test" msgstr "測試" +msgid "Define a custom pattern with cron syntax" +msgstr "使用 Cron 語法定義自定義模式" + msgid "Delete" -msgstr "" +msgstr "刪除" msgid "Deploy" msgid_plural "Deploys" msgstr[0] "部署" msgid "Description" -msgstr "" +msgstr "描述" + +msgid "Directory name" +msgstr "目錄名稱" + +msgid "Don't show again" +msgstr "不再顯示" + +msgid "Download" +msgstr "下載" + +msgid "Download tar" +msgstr "下載 tar" + +msgid "Download tar.bz2" +msgstr "下載 tar.bz2" + +msgid "Download tar.gz" +msgstr "下載 tar.gz" + +msgid "Download zip" +msgstr "下載 zip" + +msgid "DownloadArtifacts|Download" +msgstr "下載" + +msgid "DownloadCommit|Email Patches" +msgstr "電子郵件補丁" + +msgid "DownloadCommit|Plain Diff" +msgstr "Diff 文件" + +msgid "DownloadSource|Download" +msgstr "下載" msgid "Edit" -msgstr "" +msgstr "編輯" msgid "Edit Pipeline Schedule %{id}" -msgstr "" +msgstr "編輯 %{id} 流水線計劃" + +msgid "Every day (at 4:00am)" +msgstr "每日執行(淩晨4點)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "每月執行(每月1日淩晨4點)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "每週執行(周日淩晨4點)" msgid "Failed to change the owner" -msgstr "" +msgstr "無法變更所有者" msgid "Failed to remove the pipeline schedule" -msgstr "" +msgstr "無法刪除流水線計劃" -msgid "Filter" -msgstr "" +msgid "Files" +msgstr "文件" + +msgid "Find by path" +msgstr "按路徑查找" + +msgid "Find file" +msgstr "查找文件" msgid "FirstPushedBy|First" msgstr "首次推送" @@ -88,24 +340,70 @@ msgstr "首次推送" msgid "FirstPushedBy|pushed by" msgstr "推送者:" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "派生" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "派生自" + msgid "From issue creation until deploy to production" msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" +msgid "Go to your fork" +msgstr "跳轉到派生項目" + +msgid "GoToYourFork|Fork" +msgstr "跳轉到派生項目" + +msgid "Home" +msgstr "首頁" + +msgid "Housekeeping successfully started" +msgstr "已開始維護" + +msgid "Import repository" +msgstr "導入存儲庫" + msgid "Interval Pattern" -msgstr "" +msgstr "重複週期" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "LFSStatus|Disabled" +msgstr "停用" + +msgid "LFSStatus|Enabled" +msgstr "啟用" + msgid "Last %d day" msgid_plural "Last %d days" -msgstr[0] "最後 %d 天" +msgstr[0] "最近 %d 天" msgid "Last Pipeline" -msgstr "" +msgstr "最新流水線" + +msgid "Last Update" +msgstr "最後更新" + +msgid "Last commit" +msgstr "最後提交" + +msgid "Learn more in the" +msgstr "了解更多" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "流水線計劃文檔" + +msgid "Leave group" +msgstr "退出群組" + +msgid "Leave project" +msgstr "退出項目" msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" @@ -114,15 +412,45 @@ msgstr[0] "最多顯示 %d 個事件" msgid "Median" msgstr "中位數" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "添加壹個 SSH 公鑰" + msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "新議題" +msgstr[0] "新建議題" msgid "New Pipeline Schedule" -msgstr "" +msgstr "創建流水線計劃" + +msgid "New branch" +msgstr "新增分支" + +msgid "New directory" +msgstr "新增目錄" + +msgid "New file" +msgstr "新增文件" + +msgid "New issue" +msgstr "新議題" + +msgid "New merge request" +msgstr "新增合併請求" + +msgid "New schedule" +msgstr "新增计划" + +msgid "New snippet" +msgstr "新代碼片段" + +msgid "New tag" +msgstr "新增標籤" + +msgid "No repository" +msgstr "沒有存儲庫" msgid "No schedules" -msgstr "" +msgstr "沒有計劃" msgid "Not available" msgstr "不可用" @@ -130,54 +458,185 @@ msgstr "不可用" msgid "Not enough data" msgstr "數據不足" +msgid "Notification events" +msgstr "通知事件" + +msgid "NotificationEvent|Close issue" +msgstr "關閉議題" + +msgid "NotificationEvent|Close merge request" +msgstr "關閉合併請求" + +msgid "NotificationEvent|Failed pipeline" +msgstr "流水線失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "合併請求被合併" + +msgid "NotificationEvent|New issue" +msgstr "新增議題" + +msgid "NotificationEvent|New merge request" +msgstr "新合併請求" + +msgid "NotificationEvent|New note" +msgstr "新增評論" + +msgid "NotificationEvent|Reassign issue" +msgstr "重新指派議題" + +msgid "NotificationEvent|Reassign merge request" +msgstr "重新指派合併請求" + +msgid "NotificationEvent|Reopen issue" +msgstr "重新打開議題" + +msgid "NotificationEvent|Successful pipeline" +msgstr "流水線成功完成" + +msgid "NotificationLevel|Custom" +msgstr "自定義" + +msgid "NotificationLevel|Disabled" +msgstr "停用" + +msgid "NotificationLevel|Global" +msgstr "全局" + +msgid "NotificationLevel|On mention" +msgstr "提及" + +msgid "NotificationLevel|Participate" +msgstr "參與" + +msgid "NotificationLevel|Watch" +msgstr "關注" + +msgid "OfSearchInADropdown|Filter" +msgstr "篩選" + msgid "OpenedNDaysAgo|Opened" msgstr "開始於" +msgid "Options" +msgstr "操作" + msgid "Owner" -msgstr "" +msgstr "所有者" + +msgid "Pipeline" +msgstr "流水線" msgid "Pipeline Health" msgstr "流水線健康指標" msgid "Pipeline Schedule" -msgstr "" +msgstr "流水線計劃" msgid "Pipeline Schedules" -msgstr "" +msgstr "流水線計劃" msgid "PipelineSchedules|Activated" -msgstr "" +msgstr "是否啟用" msgid "PipelineSchedules|Active" -msgstr "" +msgstr "已啟用" msgid "PipelineSchedules|All" -msgstr "" +msgstr "所有" msgid "PipelineSchedules|Inactive" -msgstr "" +msgstr "未啟用" msgid "PipelineSchedules|Next Run" -msgstr "" +msgstr "下次運行時間" msgid "PipelineSchedules|None" -msgstr "" +msgstr "無" msgid "PipelineSchedules|Provide a short description for this pipeline" -msgstr "" +msgstr "為此流水線提供簡短描述" msgid "PipelineSchedules|Take ownership" -msgstr "" +msgstr "取得所有者" msgid "PipelineSchedules|Target" -msgstr "" +msgstr "目標" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "自定義" + +msgid "Pipeline|with stage" +msgstr "於階段" + +msgid "Pipeline|with stages" +msgstr "於階段" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "項目 '%{project_name}' 已進入刪除隊列。" + +msgid "Project '%{project_name}' was successfully created." +msgstr "項目 '%{project_name}' 已創建成功。" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "項目 '%{project_name}' 已更新完成。" + +msgid "Project '%{project_name}' will be deleted." +msgstr "項目 '%{project_name}' 將被刪除。" + +msgid "Project access must be granted explicitly to each user." +msgstr "項目訪問權限必須明確授權給每個用戶。" + +msgid "Project export could not be deleted." +msgstr "無法刪除項目導出。" + +msgid "Project export has been deleted." +msgstr "項目導出已被刪除。" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "項目導出鏈接已過期。請從項目設置中重新生成項目導出。" + +msgid "Project export started. A download link will be sent by email." +msgstr "項目導出已開始。下載鏈接將通過電子郵件發送。" + +msgid "Project home" +msgstr "項目首頁" + +msgid "ProjectFeature|Disabled" +msgstr "停用" + +msgid "ProjectFeature|Everyone with access" +msgstr "任何人都可訪問" + +msgid "ProjectFeature|Only team members" +msgstr "只限團隊成員" + +msgid "ProjectFileTree|Name" +msgstr "名稱" + +msgid "ProjectLastActivity|Never" +msgstr "從未" msgid "ProjectLifecycle|Stage" -msgstr "項目生命週期" +msgstr "階段" + +msgid "ProjectNetworkGraph|Graph" +msgstr "分支圖" msgid "Read more" msgstr "了解更多" +msgid "Readme" +msgstr "自述文件" + +msgid "RefSwitcher|Branches" +msgstr "分支" + +msgid "RefSwitcher|Tags" +msgstr "標籤" + msgid "Related Commits" msgstr "相關的提交" @@ -194,59 +653,167 @@ msgid "Related Merge Requests" msgstr "相關的合併請求" msgid "Related Merged Requests" -msgstr "相關已合併的合並請求" +msgstr "相關已合併的合併請求" + +msgid "Remind later" +msgstr "稍後提醒" + +msgid "Remove project" +msgstr "刪除項目" + +msgid "Request Access" +msgstr "申請訪問" + +msgid "Revert this commit" +msgstr "還原此提交" + +msgid "Revert this merge request" +msgstr "還原此合併請求" msgid "Save pipeline schedule" -msgstr "" +msgstr "保存流水線計劃" msgid "Schedule a new pipeline" -msgstr "" +msgstr "新建流水線計劃" + +msgid "Scheduling Pipelines" +msgstr "流水線計劃" + +msgid "Search branches and tags" +msgstr "搜索分支和標籤" + +msgid "Select Archive Format" +msgstr "選擇下載格式" msgid "Select a timezone" -msgstr "" +msgstr "選擇時區" msgid "Select target branch" -msgstr "" +msgstr "選擇目標分支" + +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。" + +msgid "Set up CI" +msgstr "設置 CI" + +msgid "Set up Koding" +msgstr "設置 Koding" + +msgid "Set up auto deploy" +msgstr "設置自動部署" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "設置密碼" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "顯示 %d 個事件" +msgid "Source code" +msgstr "源代碼" + +msgid "StarProject|Star" +msgstr "星標" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "由此更改 %{new_merge_request}" + +msgid "Start a new merge request with these changes" +msgstr "由此更改創建新合併請求" + +msgid "Switch branch/tag" +msgstr "切換分支/標籤" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "標籤" + +msgid "Tags" +msgstr "標籤" + msgid "Target Branch" -msgstr "" +msgstr "目標分支" -msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." -msgstr "編碼階段概述了從第一次提交到創建合併請求的時間。創建第壹個合並請求後,數據將自動添加到此處。" +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。" msgid "The collection of events added to the data gathered for that stage." msgstr "與該階段相關的事件。" -msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." -msgstr "議題階段概述了從創建議題到將議題設置裏程碑或將議題添加到議題看板的時間。創建一個議題後,數據將自動添加到此處。" +msgid "The fork relationship has been removed." +msgstr "派生關係已被刪除。" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "議題階段概述了從創建議題到將議題添加到裏程碑或議題看板的時間。創建第壹個議題後,數據將自動添加到此處.。" msgid "The phase of the development lifecycle." msgstr "項目生命週期中的各個階段。" -msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." -msgstr "計劃階段概述了從議題添加到日程後到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。" +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。" + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。" -msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." msgstr "生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。" -msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." -msgstr "評審階段概述了從創建合並請求到合併的時間。當創建第壹個合並請求後,數據將自動添加到此處。" +msgid "The project can be accessed by any logged in user." +msgstr "該項目允許已登錄的用戶訪問。" -msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." -msgstr "預發布階段概述了合並請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" +msgid "The project can be accessed without any authentication." +msgstr "該項目允許任何人訪問。" -msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." -msgstr "測試階段概述了GitLab CI為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" +msgid "The repository for this project does not exist." +msgstr "此項目的存儲庫不存在。" + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。" msgid "The time taken by each data entry gathered by that stage." msgstr "該階段每條數據所花的時間" -msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." -msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。" + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。" msgid "Time before an issue gets scheduled" msgstr "議題被列入日程表的時間" @@ -255,11 +822,134 @@ msgid "Time before an issue starts implementation" msgstr "開始進行編碼前的時間" msgid "Time between merge request creation and merge/close" -msgstr "從創建合併請求到被合並或關閉的時間" +msgstr "從創建合併請求到被合併或關閉的時間" msgid "Time until first merge request" msgstr "創建第壹個合併請求之前的時間" +msgid "Timeago|%s days ago" +msgstr "%s 天前" + +msgid "Timeago|%s days remaining" +msgstr "剩餘 %s 天" + +msgid "Timeago|%s hours remaining" +msgstr "剩餘 %s 小時" + +msgid "Timeago|%s minutes ago" +msgstr "%s 分鐘前" + +msgid "Timeago|%s minutes remaining" +msgstr "剩餘 %s 分鐘" + +msgid "Timeago|%s months ago" +msgstr "%s 個月前" + +msgid "Timeago|%s months remaining" +msgstr "剩餘 %s 月" + +msgid "Timeago|%s seconds remaining" +msgstr "剩餘 %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr "%s 星期前" + +msgid "Timeago|%s weeks remaining" +msgstr "剩餘 %s 星期" + +msgid "Timeago|%s years ago" +msgstr "%s 年前" + +msgid "Timeago|%s years remaining" +msgstr "剩餘 %s 年" + +msgid "Timeago|1 day remaining" +msgstr "剩餘 1 天" + +msgid "Timeago|1 hour remaining" +msgstr "剩餘 1 小時" + +msgid "Timeago|1 minute remaining" +msgstr "剩餘 1 分鐘" + +msgid "Timeago|1 month remaining" +msgstr "剩餘 1 個月" + +msgid "Timeago|1 week remaining" +msgstr "剩餘 1 星期" + +msgid "Timeago|1 year remaining" +msgstr "剩餘 1 年" + +msgid "Timeago|Past due" +msgstr "逾期" + +msgid "Timeago|a day ago" +msgstr "1 天前" + +msgid "Timeago|a month ago" +msgstr "1 個月前" + +msgid "Timeago|a week ago" +msgstr "1 星期前" + +msgid "Timeago|a while" +msgstr "剛剛" + +msgid "Timeago|a year ago" +msgstr "1 年前" + +msgid "Timeago|about %s hours ago" +msgstr "約 %s 小時前" + +msgid "Timeago|about a minute ago" +msgstr "約 1 分鐘前" + +msgid "Timeago|about an hour ago" +msgstr "約 1 小時前" + +msgid "Timeago|in %s days" +msgstr " %s 天後" + +msgid "Timeago|in %s hours" +msgstr " %s 小時後" + +msgid "Timeago|in %s minutes" +msgstr " %s 分鐘後" + +msgid "Timeago|in %s months" +msgstr " %s 個月後" + +msgid "Timeago|in %s seconds" +msgstr " %s 秒後" + +msgid "Timeago|in %s weeks" +msgstr " %s 星期後" + +msgid "Timeago|in %s years" +msgstr " %s 年後" + +msgid "Timeago|in 1 day" +msgstr " 1 天後" + +msgid "Timeago|in 1 hour" +msgstr " 1 小時後" + +msgid "Timeago|in 1 minute" +msgstr " 1 分鐘後" + +msgid "Timeago|in 1 month" +msgstr " 1 月後" + +msgid "Timeago|in 1 week" +msgstr " 1 星期後" + +msgid "Timeago|in 1 year" +msgstr " 1 年後" + +msgid "Timeago|less than a minute ago" +msgstr "不到 1 分鐘前" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "小時" @@ -277,18 +967,105 @@ msgstr "總時間" msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" +msgid "Unstar" +msgstr "取消星標" + +msgid "Upload New File" +msgstr "上傳新文件" + +msgid "Upload file" +msgstr "上傳文件" + +msgid "Use your global notification setting" +msgstr "使用全局通知設置" + +msgid "VisibilityLevel|Internal" +msgstr "內部" + +msgid "VisibilityLevel|Private" +msgstr "私有" + +msgid "VisibilityLevel|Public" +msgstr "公開" + msgid "Want to see the data? Please ask an administrator for access." msgstr "權限不足。如需查看相關數據,請向管理員申請權限。" msgid "We don't have enough data to show this stage." msgstr "該階段的數據不足,無法顯示。" -msgid "You have reached your project limit" -msgstr "" +msgid "Withdraw Access Request" +msgstr "取消訪問請求" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "即將要刪除 %{project_name_with_namespace}。\n" +"已刪除的項目無法恢複!\n" +"確定繼續嗎?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?" + +msgid "You can only add files when you are on a branch" +msgstr "只能在分支上添加文件" + +msgid "You must sign in to star a project" +msgstr "必須登錄才能對項目加星標" msgid "You need permission." -msgstr "您需要相關的權限。" +msgstr "需要相關的權限。" + +msgid "You will not get any notifications via email" +msgstr "不會收到任何通知郵件" + +msgid "You will only receive notifications for the events you choose" +msgstr "只接收您選擇的事件通知" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "只接收您參與的主題的通知" + +msgid "You will receive notifications for any activity" +msgstr "接收所有活動的通知" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "只接收評論中提及(@)您的通知" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。" + +msgid "Your name" +msgstr "您的名字" msgid "day" msgid_plural "days" msgstr[0] "天" + +msgid "new merge request" +msgstr "新建合併請求" + +msgid "notification emails" +msgstr "通知郵件" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "父級" + -- cgit v1.2.1 From 79b033df9b54acf04b62b1847f6be354def66834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 20 Jun 2017 14:04:43 +0800 Subject: Change 'Committed by' Translate --- app/assets/javascripts/locale/zh_HK/app.js | 2 +- locale/zh_HK/gitlab.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js index 5b6e56fea4a..21227e79efa 100644 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ b/app/assets/javascripts/locale/zh_HK/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 21:59-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-19 09:57-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=1; plural=0","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeTypeAction|Revert":["還原"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitBoxTitle|Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交於"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["差異文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨 4 點)"],"Every month (on the 1st at 4:00am)":["每月執行(每月 1 日淩晨 4 點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨 4 點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["循環週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["流水線計劃文檔"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新建議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重啟議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"PipelineSheduleIntervalPattern|Custom":["自定義"],"Pipeline|with stage":["於階段"],"Pipeline|with stages":["於階段"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已被刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只限團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請權限"],"Revert this commit":["還原此提交"],"Revert this merge request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a %{new_merge_request} with these changes":["由此更改 %{new_merge_request}"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板所花費的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":[" %s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":[" %s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":[" %s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":[" %s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":[" %s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":[" 1 天前"],"Timeago|a month ago":[" 1 個月前"],"Timeago|a week ago":[" 1 星期前"],"Timeago|a while":[" 剛剛"],"Timeago|a year ago":[" 1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消權限申请"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢複!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You have reached your project limit":["您已達到項目數量限制"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"new merge request":["新建合併請求"],"notification emails":["通知郵件"],"parent":["父級"]}}}; \ No newline at end of file +var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 21:59-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-19 09:57-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=1; plural=0","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeTypeAction|Revert":["還原"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitBoxTitle|Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交者:"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["差異文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨 4 點)"],"Every month (on the 1st at 4:00am)":["每月執行(每月 1 日淩晨 4 點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨 4 點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["循環週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["流水線計劃文檔"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新建議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重啟議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"PipelineSheduleIntervalPattern|Custom":["自定義"],"Pipeline|with stage":["於階段"],"Pipeline|with stages":["於階段"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已被刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只限團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請權限"],"Revert this commit":["還原此提交"],"Revert this merge request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a %{new_merge_request} with these changes":["由此更改 %{new_merge_request}"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板所花費的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":[" %s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":[" %s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":[" %s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":[" %s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":[" %s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":[" 1 天前"],"Timeago|a month ago":[" 1 個月前"],"Timeago|a week ago":[" 1 星期前"],"Timeago|a while":[" 剛剛"],"Timeago|a year ago":[" 1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消權限申请"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢複!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You have reached your project limit":["您已達到項目數量限制"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"new merge request":["新建合併請求"],"notification emails":["通知郵件"],"parent":["父級"]}}}; \ No newline at end of file diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index f4f70d80d34..4d545d27185 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -177,7 +177,7 @@ msgid "Commits|History" msgstr "歷史" msgid "Committed by" -msgstr "提交於" +msgstr "提交者:" msgid "Compare" msgstr "比較" -- cgit v1.2.1 From cc2f1df49b570119ae8a9f0bef9954dda8aff2e9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 21 Jun 2017 11:09:58 +0200 Subject: Clarify the expiration date of PATs https://gitlab.com/gitlab-org/gitlab-ce/issues/22566#note_32336497 [ci skip] --- doc/user/profile/personal_access_tokens.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 9488ce1ef30..f28c034e74c 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -14,6 +14,9 @@ accepted method of authentication when you have Once you have your token, [pass it to the API][usage] using either the `private_token` parameter or the `PRIVATE-TOKEN` header. +The expiration of personal access tokens happens on the date you define, +at midnight UTC. + ## Creating a personal access token You can create as many personal access tokens as you like from your GitLab -- cgit v1.2.1 From eb59ea890074e0b319e07204ae33865c35a1c763 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 21 Jun 2017 23:40:18 +0300 Subject: Jobs: Fix job sidebar overflow issue. --- app/assets/stylesheets/pages/builds.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 7eee0a71c66..cdb0b1bc59e 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -399,6 +399,7 @@ .build-light-text { color: $gl-text-color-secondary; + word-wrap: break-word; } .build-gutter-toggle { -- cgit v1.2.1 From 12e939672cf5a64cb5d7185775cec7de65236ab2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 22 Jun 2017 13:42:34 -0500 Subject: Adjust commit partial cache key to fix browse buttons --- app/views/projects/commits/_commit.html.haml | 2 +- changelogs/unreleased/dm-commit-row-browse-button.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/dm-commit-row-browse-button.yml diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 11de6915961..8a4ef5a45b3 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -5,7 +5,7 @@ - notes = commit.notes - note_count = notes.user.count -- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] +- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)] - cache_key.push(commit.status(ref)) if commit.status(ref) = cache(cache_key, expires_in: 1.day) do diff --git a/changelogs/unreleased/dm-commit-row-browse-button.yml b/changelogs/unreleased/dm-commit-row-browse-button.yml new file mode 100644 index 00000000000..4689dc3b0e2 --- /dev/null +++ b/changelogs/unreleased/dm-commit-row-browse-button.yml @@ -0,0 +1,4 @@ +--- +title: Adjust commit partial cache key to fix browse buttons +merge_request: +author: -- cgit v1.2.1 From 974f9fb135dd5ce9897991157f8f8c779043baf6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 22 Jun 2017 15:47:50 +0100 Subject: Added limited width to profile settings Closes #20918 --- app/views/doorkeeper/applications/edit.html.haml | 1 + app/views/doorkeeper/applications/index.html.haml | 5 +++-- app/views/doorkeeper/applications/show.html.haml | 2 ++ app/views/profiles/accounts/show.html.haml | 21 +++++++++++---------- app/views/profiles/audit_log.html.haml | 5 +++-- app/views/profiles/chat_names/index.html.haml | 5 +++-- app/views/profiles/emails/index.html.haml | 5 +++-- app/views/profiles/keys/index.html.haml | 5 +++-- app/views/profiles/keys/show.html.haml | 1 + app/views/profiles/notifications/show.html.haml | 5 +++-- app/views/profiles/passwords/edit.html.haml | 5 +++-- .../profiles/personal_access_tokens/index.html.haml | 5 +++-- app/views/profiles/preferences/show.html.haml | 9 +++++---- app/views/profiles/show.html.haml | 9 +++++---- app/views/profiles/two_factor_auths/show.html.haml | 13 +++++++------ 15 files changed, 56 insertions(+), 40 deletions(-) diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml index fb6aa30acee..49f90298a50 100644 --- a/app/views/doorkeeper/applications/edit.html.haml +++ b/app/views/doorkeeper/applications/edit.html.haml @@ -1,3 +1,4 @@ - page_title "Edit", @application.name, "Applications" +- @content_class = "limit-container-width" unless fluid_layout %h3.page-title Edit application = render 'form', application: @application diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index aa271150b07..d1237d7bf6f 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -1,7 +1,8 @@ - page_title "Applications" +- @content_class = "limit-container-width" unless fluid_layout .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p @@ -10,7 +11,7 @@ and applications that you've authorized to use your account. - else Manage applications that you've authorized to use your account. - .col-lg-9 + .col-lg-8 - if user_oauth_applications? %h5.prepend-top-0 Add new application diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml index 559de63d96d..72eab964766 100644 --- a/app/views/doorkeeper/applications/show.html.haml +++ b/app/views/doorkeeper/applications/show.html.haml @@ -1,4 +1,6 @@ - page_title @application.name, "Applications" +- @content_class = "limit-container-width" unless fluid_layout + %h3.page-title Application: #{@application.name} diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index a319b18e507..ed079ed7dfb 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,4 +1,5 @@ - page_title "Account" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' - if current_user.ldap_user? @@ -6,13 +7,13 @@ Some options are unavailable for LDAP accounts .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Private Tokens %p Keep these tokens secret, anyone with access to them can interact with GitLab as if they were you. - .col-lg-9.private-tokens-reset + .col-lg-8.private-tokens-reset = render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' } = render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' } @@ -22,12 +23,12 @@ %hr .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Two-Factor Authentication %p Increase your account's security by enabling Two-Factor Authentication (2FA). - .col-lg-9 + .col-lg-8 %p Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'} - if current_user.two_factor_enabled? @@ -43,12 +44,12 @@ %hr - if button_based_providers.any? .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Social sign-in %p Activate signin with one of the following services - .col-lg-9 + .col-lg-8 %label.label-light Connected Accounts %p Click on icon to activate signin with one of the following services @@ -69,12 +70,12 @@ %hr - if current_user.can_change_username? .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0.warning-title Change username %p Changing your username will change path to all personal projects! - .col-lg-9 + .col-lg-8 = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f| .form-group = f.label :username, "Path", class: "label-light" @@ -93,10 +94,10 @@ - if signup_enabled? .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0.danger-title Remove account - .col-lg-9 + .col-lg-8 - if @user.can_be_removed? && can?(current_user, :destroy_user, @user) %p Deleting an account has the following effects: diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index a24b7fd101d..1a392e29e2a 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,11 +1,12 @@ - page_title "Authentication log" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h3.prepend-top-0 = page_title %p This is a security log of important events involving your account. - .col-lg-9 + .col-lg-8 = render 'event_table', events: @events diff --git a/app/views/profiles/chat_names/index.html.haml b/app/views/profiles/chat_names/index.html.haml index 20cc636b2da..8f7121afe02 100644 --- a/app/views/profiles/chat_names/index.html.haml +++ b/app/views/profiles/chat_names/index.html.haml @@ -1,14 +1,15 @@ - page_title 'Chat' +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p You can see your Chat accounts. - .col-lg-9 + .col-lg-8 %h5 Active chat names (#{@chat_names.size}) - if @chat_names.present? diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index f5a323dbaf8..612ecbbb96a 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,13 +1,14 @@ - page_title "Emails" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p Control emails linked to your account - .col-lg-9 + .col-lg-8 %h4.prepend-top-0 Add email address = form_for 'email', url: profile_emails_path do |f| diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 71b224a413b..5f7b41cf30e 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,13 +1,14 @@ - page_title "SSH Keys" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p SSH keys allow you to establish a secure connection between your computer and GitLab. - .col-lg-9 + .col-lg-8 %h5.prepend-top-0 Add an SSH key %p.profile-settings-content diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml index 6283ceebf10..172c0450381 100644 --- a/app/views/profiles/keys/show.html.haml +++ b/app/views/profiles/keys/show.html.haml @@ -1,3 +1,4 @@ - page_title @key.title, "SSH Keys" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' = render "key_details" diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 51c4e8e5a73..e98fdfc7a3d 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,4 +1,5 @@ - page_title "Notifications" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' %div @@ -10,14 +11,14 @@ = hidden_field_tag :notification_type, 'global' .row - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4 = page_title %p You can specify notification level per group or per project. %p By default, all projects and groups will use the global notifications setting. - .col-lg-9 + .col-lg-8 %h5 Global notification settings diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 243428b690e..985bb79508f 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,12 +1,13 @@ - page_title "Password" +- @content_class = "limit-container-width" unless fluid_layout .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p After a successful password update, you will be redirected to the login page where you can log in with your new password. - .col-lg-9 + .col-lg-8 %h5.prepend-top-0 Change your password - unless @user.password_automatically_set? diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index c852107e69a..cf750378e25 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -1,8 +1,9 @@ - page_title "Personal Access Tokens" +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p @@ -11,7 +12,7 @@ You can also use personal access tokens to authenticate against Git over HTTP. They are the only accepted password when you have Two-Factor Authentication (2FA) enabled. - .col-lg-9 + .col-lg-8 - if flash[:personal_access_token] .created-personal-access-token-container diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 0ff19b3eab1..9cbc24eb883 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,15 +1,16 @@ - page_title 'Preferences' +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Syntax highlighting theme %p This setting allows you to customize the appearance of the syntax. = succeed '.' do = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank' - .col-lg-9.syntax-theme + .col-lg-8.syntax-theme - Gitlab::ColorSchemes.each do |scheme| = label_tag do .preview= image_tag "#{scheme.css_class}-scheme-preview.png" @@ -17,14 +18,14 @@ = scheme.name .col-sm-12 %hr - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Behavior %p This setting allows you to customize the behavior of the system layout and default views. = succeed '.' do = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank' - .col-lg-9 + .col-lg-8 .form-group = f.label :layout, class: 'label-light' do Layout width diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 087ae778b0f..016dcde288d 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,10 +1,11 @@ +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' = bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default' }, authenticity_token: true do |f| = form_errors(@user) .row - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Public Avatar %p @@ -16,7 +17,7 @@ You can upload an avatar here - if gravatar_enabled? or change it at #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host} - .col-lg-9 + .col-lg-8 .clearfix.avatar-image.append-bottom-default = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' @@ -34,14 +35,14 @@ = link_to 'Remove avatar', profile_avatar_path, data: { confirm: 'Avatar will be removed. Are you sure?' }, method: :delete, class: 'btn btn-gray' %hr .row - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Main settings %p This information will appear on your profile. - if current_user.ldap_user? Some options are unavailable for LDAP accounts - .col-lg-9 + .col-lg-8 .row = f.text_field :name, required: true, wrapper: { class: 'col-md-9' }, help: 'Enter your name, so people you know can recognize you.' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 0ff05098cd7..67792de3870 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,5 +1,6 @@ - page_title 'Two-Factor Authentication', 'Account' - header_title "Two-Factor Authentication", profile_two_factor_auth_path +- @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' - if inject_u2f_api? @@ -7,12 +8,12 @@ = page_specific_javascript_bundle_tag('u2f') .row.prepend-top-default - .col-lg-3 + .col-lg-4 %h4.prepend-top-0 Register Two-Factor Authentication App %p Use an app on your mobile device to enable two-factor authentication (2FA). - .col-lg-9 + .col-lg-8 - if current_user.two_factor_otp_enabled? = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page." - else @@ -20,9 +21,9 @@ Download the Google Authenticator application from App Store or Google Play Store and scan this code. More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}. .row.append-bottom-10 - .col-md-3 + .col-md-4 = raw @qr_code - .col-md-9 + .col-md-8 .account-well %p.prepend-top-0.append-bottom-0 Can't scan the code? @@ -50,7 +51,7 @@ .row.prepend-top-default - .col-lg-3 + .col-lg-4 %h4.prepend-top-0 Register Universal Two-Factor (U2F) Device %p @@ -59,7 +60,7 @@ As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser. - .col-lg-9 + .col-lg-8 - if @u2f_registration.errors.present? = form_errors(@u2f_registration) = render "u2f/register" -- cgit v1.2.1 From 5a044dc25b1c04da6253577a3934e857a0c0bd0d Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 23 Jun 2017 13:26:59 -0500 Subject: refactor option defaults --- app/assets/javascripts/behaviors/gl_emoji.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 36ce4fddb72..ca6117c4fd6 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -25,12 +25,9 @@ function assembleFallbackImageSrc(inputName) { return fallbackImageSrc; } -const glEmojiTagDefaults = { - sprite: false, - forceFallback: false, -}; + function glEmojiTag(inputName, options) { - const opts = Object.assign({}, glEmojiTagDefaults, options); + const opts = { sprite: false, forceFallback: false, ...options }; let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? emojiAliases[inputName] : inputName; let emojiInfo = emojiMap[name]; -- cgit v1.2.1 From d099744dd41af983e5a02f64375b60a8cf6c539f Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 23 Jun 2017 14:08:06 -0500 Subject: centralize emoji helper methods --- app/assets/javascripts/awards_handler.js | 5 +- app/assets/javascripts/behaviors/gl_emoji.js | 5 +- .../behaviors/gl_emoji/is_emoji_name_valid.js | 11 -- .../gl_emoji/is_emoji_unicode_supported.js | 120 -------------- .../behaviors/gl_emoji/unicode_support_map.js | 172 --------------------- app/assets/javascripts/emoji/index.js | 13 ++ .../javascripts/emoji/is_emoji_name_valid.js | 11 ++ .../emoji/is_emoji_unicode_supported.js | 120 ++++++++++++++ .../javascripts/emoji/unicode_support_map.js | 167 ++++++++++++++++++++ app/assets/javascripts/gfm_auto_complete.js | 9 +- 10 files changed, 317 insertions(+), 316 deletions(-) delete mode 100644 app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js delete mode 100644 app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js delete mode 100644 app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js create mode 100644 app/assets/javascripts/emoji/index.js create mode 100644 app/assets/javascripts/emoji/is_emoji_name_valid.js create mode 100644 app/assets/javascripts/emoji/is_emoji_unicode_supported.js create mode 100644 app/assets/javascripts/emoji/unicode_support_map.js diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index ebe722061d7..b6a12d09042 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -2,11 +2,8 @@ /* global Flash */ import Cookies from 'js-cookie'; - -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; import { glEmojiTag } from './behaviors/gl_emoji'; -import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid'; +import { emojiMap, emojiAliases, isEmojiNameValid } from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index ca6117c4fd6..06eb698378c 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,8 +1,5 @@ import installCustomElements from 'document-register-element'; -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; -import { getUnicodeSupportMap } from './gl_emoji/unicode_support_map'; -import { isEmojiUnicodeSupported } from './gl_emoji/is_emoji_unicode_supported'; +import { emojiMap, emojiAliases, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; installCustomElements(window); diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js deleted file mode 100644 index be4aeb32c46..00000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js +++ /dev/null @@ -1,11 +0,0 @@ -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; - -function isEmojiNameValid(inputName) { - const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; - - return name && emojiMap[name]; -} - -export default isEmojiNameValid; diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js deleted file mode 100644 index 4f8884d05ac..00000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_unicode_supported.js +++ /dev/null @@ -1,120 +0,0 @@ -// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/ -const flagACodePoint = 127462; // parseInt('1F1E6', 16) -const flagZCodePoint = 127487; // parseInt('1F1FF', 16) -function isFlagEmoji(emojiUnicode) { - const cp = emojiUnicode.codePointAt(0); - // Length 4 because flags are made of 2 characters which are surrogate pairs - return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint; -} - -// Chrome <57 renders keycaps oddly -// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294 -// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png -function isKeycapEmoji(emojiUnicode) { - return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3'; -} - -// Check for a skin tone variation emoji which aren't always supported -const tone1 = 127995;// parseInt('1F3FB', 16) -const tone5 = 127999;// parseInt('1F3FF', 16) -function isSkinToneComboEmoji(emojiUnicode) { - return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { - const cp = char.codePointAt(0); - return cp >= tone1 && cp <= tone5; - }); -} - -// macOS supports most skin tone emoji's but -// doesn't support the skin tone versions of horse racing -const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) -function isHorceRacingSkinToneComboEmoji(emojiUnicode) { - const firstCharacter = Array.from(emojiUnicode)[0]; - return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint && - isSkinToneComboEmoji(emojiUnicode); -} - -// Check for `family_*`, `kiss_*`, `couple_*` -// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these -const zwj = 8205; // parseInt('200D', 16) -const personStartCodePoint = 128102; // parseInt('1F466', 16) -const personEndCodePoint = 128105; // parseInt('1F469', 16) -function isPersonZwjEmoji(emojiUnicode) { - let hasPersonEmoji = false; - let hasZwj = false; - Array.from(emojiUnicode).forEach((character) => { - const cp = character.codePointAt(0); - if (cp === zwj) { - hasZwj = true; - } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) { - hasPersonEmoji = true; - } - }); - - return hasPersonEmoji && hasZwj; -} - -// Helper so we don't have to run `isFlagEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isFlagResult = isFlagEmoji(emojiUnicode); - return ( - (unicodeSupportMap.flag && isFlagResult) || - !isFlagResult - ); -} - -// Helper so we don't have to run `isSkinToneComboEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { - const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode); - return ( - (unicodeSupportMap.skinToneModifier && isSkinToneResult) || - !isSkinToneResult - ); -} - -// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode); - return ( - (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || - !isHorseRacingSkinToneResult - ); -} - -// Helper so we don't have to run `isPersonZwjEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode); - return ( - (unicodeSupportMap.personZwj && isPersonZwjResult) || - !isPersonZwjResult - ); -} - -// Takes in a support map and determines whether -// the given unicode emoji is supported on the platform. -// -// Combines all the edge case tests into a one-stop shop method -function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) { - const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome && - unicodeSupportMap.meta.chromeVersion < 57; - - // For comments about each scenario, see the comments above each individual respective function - return unicodeSupportMap[unicodeVersion] && - !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) && - checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) && - checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) && - checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) && - checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode); -} - -export { - isEmojiUnicodeSupported, - isFlagEmoji, - isKeycapEmoji, - isSkinToneComboEmoji, - isHorceRacingSkinToneComboEmoji, - isPersonZwjEmoji, -}; diff --git a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js b/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js deleted file mode 100644 index 257df55e54f..00000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/unicode_support_map.js +++ /dev/null @@ -1,172 +0,0 @@ -import AccessorUtilities from '../../lib/utils/accessor'; - -const unicodeSupportTestMap = { - // man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ - // occupationZwj: '\u{1F468}\u{200D}\u{1F393}', - // woman, biking (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ - // sexZwj: '\u{1F6B4}\u{200D}\u{2640}', - // family_mwgb - // Windows 8.1, Firefox 51.0.1 does not support `family_`, `kiss_`, `couple_` - personZwj: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', - // horse_racing_tone5 - // Special case that is not supported on macOS 10.12 even though `skinToneModifier` succeeds - horseRacing: '\u{1F3C7}\u{1F3FF}', - // US flag, http://emojipedia.org/flags/ - flag: '\u{1F1FA}\u{1F1F8}', - // http://emojipedia.org/modifiers/ - skinToneModifier: [ - // spy_tone5 - '\u{1F575}\u{1F3FF}', - // person_with_ball_tone5 - '\u{26F9}\u{1F3FF}', - // angel_tone5 - '\u{1F47C}\u{1F3FF}', - ], - // rofl, http://emojipedia.org/unicode-9.0/ - '9.0': '\u{1F923}', - // metal, http://emojipedia.org/unicode-8.0/ - '8.0': '\u{1F918}', - // spy, http://emojipedia.org/unicode-7.0/ - '7.0': '\u{1F575}', - // expressionless, http://emojipedia.org/unicode-6.1/ - 6.1: '\u{1F611}', - // japanese_goblin, http://emojipedia.org/unicode-6.0/ - '6.0': '\u{1F47A}', - // sailboat, http://emojipedia.org/unicode-5.2/ - 5.2: '\u{26F5}', - // mahjong, http://emojipedia.org/unicode-5.1/ - 5.1: '\u{1F004}', - // gear, http://emojipedia.org/unicode-4.1/ - 4.1: '\u{2699}', - // zap, http://emojipedia.org/unicode-4.0/ - '4.0': '\u{26A1}', - // recycle, http://emojipedia.org/unicode-3.2/ - 3.2: '\u{267B}', - // information_source, http://emojipedia.org/unicode-3.0/ - '3.0': '\u{2139}', - // heart, http://emojipedia.org/unicode-1.1/ - 1.1: '\u{2764}', -}; - -function checkPixelInImageDataArray(pixelOffset, imageDataArray) { - // `4 *` because RGBA - const indexOffset = 4 * pixelOffset; - const hasColor = imageDataArray[indexOffset + 0] || - imageDataArray[indexOffset + 1] || - imageDataArray[indexOffset + 2]; - const isVisible = imageDataArray[indexOffset + 3]; - // Check for some sort of color other than black - if (hasColor && isVisible) { - return true; - } - return false; -} - -const chromeMatches = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./); -const isChrome = chromeMatches && chromeMatches.length > 0; -const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatches[1], 10); - -// We use 16px because mobile Safari (iOS 9.3) doesn't properly scale emojis :/ -// See 32px, https://i.imgur.com/htY6Zym.png -// See 16px, https://i.imgur.com/FPPsIF8.png -const fontSize = 16; -function generateUnicodeSupportMap(testMap) { - const testMapKeys = Object.keys(testMap); - const numTestEntries = testMapKeys - .reduce((list, testKey) => list.concat(testMap[testKey]), []).length; - - const canvas = document.createElement('canvas'); - (window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas; - const ctx = canvas.getContext('2d'); - canvas.width = (2 * fontSize); - canvas.height = (numTestEntries * fontSize); - ctx.fillStyle = '#000000'; - ctx.textBaseline = 'middle'; - ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`; - // Write each emoji to the canvas vertically - let writeIndex = 0; - testMapKeys.forEach((testKey) => { - const testEntry = testMap[testKey]; - [].concat(testEntry).forEach((emojiUnicode) => { - ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2)); - writeIndex += 1; - }); - }); - - // Read from the canvas - const resultMap = {}; - let readIndex = 0; - testMapKeys.forEach((testKey) => { - const testEntry = testMap[testKey]; - // This needs to be a `reduce` instead of `every` because we need to - // keep the `readIndex` in sync from the writes by running all entries - const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => { - // Sample along the vertical-middle for a couple of characters - const imageData = ctx.getImageData( - 0, - (readIndex * fontSize) + (fontSize / 2), - 2 * fontSize, - 1, - ).data; - - let isValidEmoji = false; - for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) { - const isLookingAtFirstChar = currentPixel < fontSize; - const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2)); - // Check for the emoji somewhere along the row - if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) { - isValidEmoji = true; - - // Check to see that nothing is rendered next to the first character - // to ensure that the ZWJ sequence rendered as one piece - } else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) { - isValidEmoji = false; - break; - } - } - - readIndex += 1; - return isSatisfied && isValidEmoji; - }, true); - - resultMap[testKey] = isTestSatisfied; - }); - - resultMap.meta = { - isChrome, - chromeVersion, - }; - - return resultMap; -} - -function getUnicodeSupportMap() { - let unicodeSupportMap; - let userAgentFromCache; - - const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); - - if (isLocalStorageAvailable) userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent'); - - try { - unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map')); - } catch (err) { - // swallow - } - - if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) { - unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap); - - if (isLocalStorageAvailable) { - window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent); - window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap)); - } - } - - return unicodeSupportMap; -} - -export { - getUnicodeSupportMap, - generateUnicodeSupportMap, -}; diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js new file mode 100644 index 00000000000..1e86ce822d2 --- /dev/null +++ b/app/assets/javascripts/emoji/index.js @@ -0,0 +1,13 @@ +import emojiMap from 'emojis/digests.json'; +import emojiAliases from 'emojis/aliases.json'; +import getUnicodeSupportMap from './unicode_support_map'; +import isEmojiNameValid from './is_emoji_name_valid'; +import isEmojiUnicodeSupported from './is_emoji_unicode_supported'; + +export { + emojiMap, + emojiAliases, + getUnicodeSupportMap, + isEmojiNameValid, + isEmojiUnicodeSupported, +}; diff --git a/app/assets/javascripts/emoji/is_emoji_name_valid.js b/app/assets/javascripts/emoji/is_emoji_name_valid.js new file mode 100644 index 00000000000..be4aeb32c46 --- /dev/null +++ b/app/assets/javascripts/emoji/is_emoji_name_valid.js @@ -0,0 +1,11 @@ +import emojiMap from 'emojis/digests.json'; +import emojiAliases from 'emojis/aliases.json'; + +function isEmojiNameValid(inputName) { + const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? + emojiAliases[inputName] : inputName; + + return name && emojiMap[name]; +} + +export default isEmojiNameValid; diff --git a/app/assets/javascripts/emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/is_emoji_unicode_supported.js new file mode 100644 index 00000000000..3fd23efa9f8 --- /dev/null +++ b/app/assets/javascripts/emoji/is_emoji_unicode_supported.js @@ -0,0 +1,120 @@ +// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/ +const flagACodePoint = 127462; // parseInt('1F1E6', 16) +const flagZCodePoint = 127487; // parseInt('1F1FF', 16) +function isFlagEmoji(emojiUnicode) { + const cp = emojiUnicode.codePointAt(0); + // Length 4 because flags are made of 2 characters which are surrogate pairs + return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint; +} + +// Chrome <57 renders keycaps oddly +// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294 +// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png +function isKeycapEmoji(emojiUnicode) { + return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3'; +} + +// Check for a skin tone variation emoji which aren't always supported +const tone1 = 127995;// parseInt('1F3FB', 16) +const tone5 = 127999;// parseInt('1F3FF', 16) +function isSkinToneComboEmoji(emojiUnicode) { + return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { + const cp = char.codePointAt(0); + return cp >= tone1 && cp <= tone5; + }); +} + +// macOS supports most skin tone emoji's but +// doesn't support the skin tone versions of horse racing +const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) +function isHorceRacingSkinToneComboEmoji(emojiUnicode) { + const firstCharacter = Array.from(emojiUnicode)[0]; + return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint && + isSkinToneComboEmoji(emojiUnicode); +} + +// Check for `family_*`, `kiss_*`, `couple_*` +// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these +const zwj = 8205; // parseInt('200D', 16) +const personStartCodePoint = 128102; // parseInt('1F466', 16) +const personEndCodePoint = 128105; // parseInt('1F469', 16) +function isPersonZwjEmoji(emojiUnicode) { + let hasPersonEmoji = false; + let hasZwj = false; + Array.from(emojiUnicode).forEach((character) => { + const cp = character.codePointAt(0); + if (cp === zwj) { + hasZwj = true; + } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) { + hasPersonEmoji = true; + } + }); + + return hasPersonEmoji && hasZwj; +} + +// Helper so we don't have to run `isFlagEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) { + const isFlagResult = isFlagEmoji(emojiUnicode); + return ( + (unicodeSupportMap.flag && isFlagResult) || + !isFlagResult + ); +} + +// Helper so we don't have to run `isSkinToneComboEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { + const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode); + return ( + (unicodeSupportMap.skinToneModifier && isSkinToneResult) || + !isSkinToneResult + ); +} + +// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) { + const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode); + return ( + (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || + !isHorseRacingSkinToneResult + ); +} + +// Helper so we don't have to run `isPersonZwjEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { + const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode); + return ( + (unicodeSupportMap.personZwj && isPersonZwjResult) || + !isPersonZwjResult + ); +} + +// Takes in a support map and determines whether +// the given unicode emoji is supported on the platform. +// +// Combines all the edge case tests into a one-stop shop method +function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) { + const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome && + unicodeSupportMap.meta.chromeVersion < 57; + + // For comments about each scenario, see the comments above each individual respective function + return unicodeSupportMap[unicodeVersion] && + !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) && + checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) && + checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) && + checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) && + checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode); +} + +export { + isEmojiUnicodeSupported as default, + isFlagEmoji, + isKeycapEmoji, + isSkinToneComboEmoji, + isHorceRacingSkinToneComboEmoji, + isPersonZwjEmoji, +}; diff --git a/app/assets/javascripts/emoji/unicode_support_map.js b/app/assets/javascripts/emoji/unicode_support_map.js new file mode 100644 index 00000000000..2a0c013a70c --- /dev/null +++ b/app/assets/javascripts/emoji/unicode_support_map.js @@ -0,0 +1,167 @@ +import AccessorUtilities from '../lib/utils/accessor'; + +const unicodeSupportTestMap = { + // man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ + // occupationZwj: '\u{1F468}\u{200D}\u{1F393}', + // woman, biking (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ + // sexZwj: '\u{1F6B4}\u{200D}\u{2640}', + // family_mwgb + // Windows 8.1, Firefox 51.0.1 does not support `family_`, `kiss_`, `couple_` + personZwj: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', + // horse_racing_tone5 + // Special case that is not supported on macOS 10.12 even though `skinToneModifier` succeeds + horseRacing: '\u{1F3C7}\u{1F3FF}', + // US flag, http://emojipedia.org/flags/ + flag: '\u{1F1FA}\u{1F1F8}', + // http://emojipedia.org/modifiers/ + skinToneModifier: [ + // spy_tone5 + '\u{1F575}\u{1F3FF}', + // person_with_ball_tone5 + '\u{26F9}\u{1F3FF}', + // angel_tone5 + '\u{1F47C}\u{1F3FF}', + ], + // rofl, http://emojipedia.org/unicode-9.0/ + '9.0': '\u{1F923}', + // metal, http://emojipedia.org/unicode-8.0/ + '8.0': '\u{1F918}', + // spy, http://emojipedia.org/unicode-7.0/ + '7.0': '\u{1F575}', + // expressionless, http://emojipedia.org/unicode-6.1/ + 6.1: '\u{1F611}', + // japanese_goblin, http://emojipedia.org/unicode-6.0/ + '6.0': '\u{1F47A}', + // sailboat, http://emojipedia.org/unicode-5.2/ + 5.2: '\u{26F5}', + // mahjong, http://emojipedia.org/unicode-5.1/ + 5.1: '\u{1F004}', + // gear, http://emojipedia.org/unicode-4.1/ + 4.1: '\u{2699}', + // zap, http://emojipedia.org/unicode-4.0/ + '4.0': '\u{26A1}', + // recycle, http://emojipedia.org/unicode-3.2/ + 3.2: '\u{267B}', + // information_source, http://emojipedia.org/unicode-3.0/ + '3.0': '\u{2139}', + // heart, http://emojipedia.org/unicode-1.1/ + 1.1: '\u{2764}', +}; + +function checkPixelInImageDataArray(pixelOffset, imageDataArray) { + // `4 *` because RGBA + const indexOffset = 4 * pixelOffset; + const hasColor = imageDataArray[indexOffset + 0] || + imageDataArray[indexOffset + 1] || + imageDataArray[indexOffset + 2]; + const isVisible = imageDataArray[indexOffset + 3]; + // Check for some sort of color other than black + if (hasColor && isVisible) { + return true; + } + return false; +} + +const chromeMatches = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./); +const isChrome = chromeMatches && chromeMatches.length > 0; +const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatches[1], 10); + +// We use 16px because mobile Safari (iOS 9.3) doesn't properly scale emojis :/ +// See 32px, https://i.imgur.com/htY6Zym.png +// See 16px, https://i.imgur.com/FPPsIF8.png +const fontSize = 16; +function generateUnicodeSupportMap(testMap) { + const testMapKeys = Object.keys(testMap); + const numTestEntries = testMapKeys + .reduce((list, testKey) => list.concat(testMap[testKey]), []).length; + + const canvas = document.createElement('canvas'); + (window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas; + const ctx = canvas.getContext('2d'); + canvas.width = (2 * fontSize); + canvas.height = (numTestEntries * fontSize); + ctx.fillStyle = '#000000'; + ctx.textBaseline = 'middle'; + ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`; + // Write each emoji to the canvas vertically + let writeIndex = 0; + testMapKeys.forEach((testKey) => { + const testEntry = testMap[testKey]; + [].concat(testEntry).forEach((emojiUnicode) => { + ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2)); + writeIndex += 1; + }); + }); + + // Read from the canvas + const resultMap = {}; + let readIndex = 0; + testMapKeys.forEach((testKey) => { + const testEntry = testMap[testKey]; + // This needs to be a `reduce` instead of `every` because we need to + // keep the `readIndex` in sync from the writes by running all entries + const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => { + // Sample along the vertical-middle for a couple of characters + const imageData = ctx.getImageData( + 0, + (readIndex * fontSize) + (fontSize / 2), + 2 * fontSize, + 1, + ).data; + + let isValidEmoji = false; + for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) { + const isLookingAtFirstChar = currentPixel < fontSize; + const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2)); + // Check for the emoji somewhere along the row + if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) { + isValidEmoji = true; + + // Check to see that nothing is rendered next to the first character + // to ensure that the ZWJ sequence rendered as one piece + } else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) { + isValidEmoji = false; + break; + } + } + + readIndex += 1; + return isSatisfied && isValidEmoji; + }, true); + + resultMap[testKey] = isTestSatisfied; + }); + + resultMap.meta = { + isChrome, + chromeVersion, + }; + + return resultMap; +} + +export default function getUnicodeSupportMap() { + let unicodeSupportMap; + let userAgentFromCache; + + const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); + + if (isLocalStorageAvailable) userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent'); + + try { + unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map')); + } catch (err) { + // swallow + } + + if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) { + unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap); + + if (isLocalStorageAvailable) { + window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent); + window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap)); + } + } + + return unicodeSupportMap; +} diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 105762cb1ba..64d47a0d75e 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,8 +1,7 @@ -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; -import { glEmojiTag } from '~/behaviors/gl_emoji'; -import glRegexp from '~/lib/utils/regexp'; -import AjaxCache from '~/lib/utils/ajax_cache'; +import { glEmojiTag } from './behaviors/gl_emoji'; +import { emojiMap, emojiAliases } from './emoji'; +import glRegexp from './lib/utils/regexp'; +import AjaxCache from './lib/utils/ajax_cache'; function sanitize(str) { return str.replace(/<(?:.|\n)*?>/gm, ''); -- cgit v1.2.1 From f0a4b4875ff5c558c8420dd75c7130e1510e07f8 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 23 Jun 2017 15:25:21 -0500 Subject: update import locations for karma specs --- spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js | 2 +- spec/javascripts/gl_emoji_spec.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js index 1ed96a67478..1c6b6ffe435 100644 --- a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js +++ b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js @@ -1,4 +1,4 @@ -import { getUnicodeSupportMap } from '~/behaviors/gl_emoji/unicode_support_map'; +import getUnicodeSupportMap from '~/emoji/unicode_support_map'; import AccessorUtilities from '~/lib/utils/accessor'; describe('Unicode Support Map', () => { diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/gl_emoji_spec.js index a09e0072fa8..17c8981971c 100644 --- a/spec/javascripts/gl_emoji_spec.js +++ b/spec/javascripts/gl_emoji_spec.js @@ -1,12 +1,11 @@ import { glEmojiTag } from '~/behaviors/gl_emoji'; -import { - isEmojiUnicodeSupported, +import isEmojiUnicodeSupported, { isFlagEmoji, isKeycapEmoji, isSkinToneComboEmoji, isHorceRacingSkinToneComboEmoji, isPersonZwjEmoji, -} from '~/behaviors/gl_emoji/is_emoji_unicode_supported'; +} from '~/emoji/is_emoji_unicode_supported'; const emptySupportMap = { personZwj: false, -- cgit v1.2.1 From a89c1bf645e4ca0ccc5df9d26c196652a247e82a Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Mon, 26 Jun 2017 00:42:44 -0500 Subject: Fix application error when Project#last_activity_at is nil --- app/models/project.rb | 2 +- app/views/shared/projects/_project.html.haml | 2 +- spec/features/dashboard/projects_spec.rb | 22 +++++++++++++++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 2c2685875f8..11038c6d0d6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -689,7 +689,7 @@ class Project < ActiveRecord::Base end def last_activity_date - last_activity_at || updated_at + last_repository_updated_at || last_activity_at || updated_at end def project_id diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index fbc335f6176..8c3d6351ac2 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -7,7 +7,7 @@ - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - cache_key = project_list_cache_key(project) -- updated_tooltip = time_ago_with_tooltip(project.last_activity_at) +- updated_tooltip = time_ago_with_tooltip(project.last_activity_date) %li.project-row{ class: css_class } = cache(cache_key) do diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 2a8185ca669..f29186f368d 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -15,13 +15,25 @@ RSpec.describe 'Dashboard Projects', feature: true do expect(page).to have_content('awesome stuff') end - it 'shows the last_activity_at attribute as the update date' do - now = Time.now - project.update_column(:last_activity_at, now) + context 'when last_repository_updated_at, last_activity_at and update_at are present' do + it 'shows the last_repository_updated_at attribute as the update date' do + project.update_attributes!(last_repository_updated_at: Time.now, last_activity_at: 1.hour.ago) - visit dashboard_projects_path + visit dashboard_projects_path + + expect(page).to have_xpath("//time[@datetime='#{project.last_repository_updated_at.getutc.iso8601}']") + end + end - expect(page).to have_xpath("//time[@datetime='#{now.getutc.iso8601}']") + context 'when last_repository_updated_at and last_activity_at are missing' do + it 'shows the updated_at attribute as the update date' do + project.update_attributes!(last_repository_updated_at: nil, last_activity_at: nil) + project.touch + + visit dashboard_projects_path + + expect(page).to have_xpath("//time[@datetime='#{project.updated_at.getutc.iso8601}']") + end end context 'when on Starred projects tab' do -- cgit v1.2.1 From 9f87b34f667582c0df82b0a79b07ad2a0c1931bb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 16:23:46 +0200 Subject: Combine group leave feature specs in one file Signed-off-by: Dmitriy Zaporozhets --- .../members/last_owner_cannot_leave_group_spec.rb | 16 ------ spec/features/groups/members/leave_group_spec.rb | 62 ++++++++++++++++++++++ .../groups/members/member_leaves_group_spec.rb | 21 -------- 3 files changed, 62 insertions(+), 37 deletions(-) delete mode 100644 spec/features/groups/members/last_owner_cannot_leave_group_spec.rb create mode 100644 spec/features/groups/members/leave_group_spec.rb delete mode 100644 spec/features/groups/members/member_leaves_group_spec.rb diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb deleted file mode 100644 index 5af94e4069b..00000000000 --- a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Last owner cannot leave group', feature: true do - let(:owner) { create(:user) } - let(:group) { create(:group) } - - background do - group.add_owner(owner) - gitlab_sign_in(owner) - visit group_path(group) - end - - scenario 'user does not see a "Leave group" link' do - expect(page).not_to have_content 'Leave group' - end -end diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb new file mode 100644 index 00000000000..5b812891281 --- /dev/null +++ b/spec/features/groups/members/leave_group_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +feature 'Groups > Members > Leave group', feature: true do + let(:user) { create(:user) } + let(:other_user) { create(:user) } + let(:group) { create(:group) } + + background do + gitlab_sign_in(user) + end + + scenario 'guest leaves the group' do + group.add_guest(user) + group.add_owner(other_user) + + visit group_path(group) + click_link 'Leave group' + + expect(current_path).to eq(dashboard_groups_path) + expect(page).to have_content left_group_message(group) + expect(group.members).not_to include(user) + end + + scenario 'guest leaves the group as last member' do + group.add_guest(user) + + visit group_path(group) + click_link 'Leave group' + + expect(current_path).to eq(dashboard_groups_path) + expect(page).to have_content left_group_message(group) + expect(group.members).not_to include(user) + end + + scenario 'owner leaves the group if they is not the last owner' do + group.add_owner(user) + group.add_owner(other_user) + + visit group_path(group) + click_link 'Leave group' + + expect(current_path).to eq(dashboard_groups_path) + expect(page).to have_content left_group_message(group) + expect(group.members).not_to include(user) + end + + scenario 'owner can not leave the group if they is a last owner' do + group.add_owner(user) + + visit group_path(group) + + expect(page).not_to have_content 'Leave group' + + visit group_group_members_path(group) + + expect(find(:css, '.project-members-page li', text: user.name)).not_to have_selector(:css, 'a.btn-remove') + end + + def left_group_message(group) + "You left the \"#{group.name}\"" + end +end diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb deleted file mode 100644 index 40f3b166e74..00000000000 --- a/spec/features/groups/members/member_leaves_group_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Member leaves group', feature: true do - let(:user) { create(:user) } - let(:owner) { create(:user) } - let(:group) { create(:group, :public) } - - background do - group.add_owner(owner) - group.add_developer(user) - gitlab_sign_in(user) - visit group_path(group) - end - - scenario 'user leaves group' do - click_link 'Leave group' - - expect(current_path).to eq(dashboard_groups_path) - expect(group.users.exists?(user.id)).to be_falsey - end -end -- cgit v1.2.1 From f1b5d2c788e234428edd8857783f05613f885477 Mon Sep 17 00:00:00 2001 From: Alexander Randa Date: Mon, 26 Jun 2017 14:21:31 +0000 Subject: Replaces 'dashboard/todos' spinach with rspec --- .../23036-replace-dashboard-todo-spinach.yml | 4 + features/dashboard/todos.feature | 28 -- features/steps/dashboard/todos.rb | 191 ----------- features/steps/shared/paths.rb | 4 - spec/features/dashboard/todos/target_state_spec.rb | 65 ++++ .../dashboard/todos/todos_filtering_spec.rb | 153 +++++++++ .../features/dashboard/todos/todos_sorting_spec.rb | 99 ++++++ spec/features/dashboard/todos/todos_spec.rb | 355 +++++++++++++++++++++ spec/features/todos/target_state_spec.rb | 65 ---- spec/features/todos/todos_filtering_spec.rb | 153 --------- spec/features/todos/todos_sorting_spec.rb | 99 ------ spec/features/todos/todos_spec.rb | 355 --------------------- 12 files changed, 676 insertions(+), 895 deletions(-) create mode 100644 changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml delete mode 100644 features/dashboard/todos.feature delete mode 100644 features/steps/dashboard/todos.rb create mode 100644 spec/features/dashboard/todos/target_state_spec.rb create mode 100644 spec/features/dashboard/todos/todos_filtering_spec.rb create mode 100644 spec/features/dashboard/todos/todos_sorting_spec.rb create mode 100644 spec/features/dashboard/todos/todos_spec.rb delete mode 100644 spec/features/todos/target_state_spec.rb delete mode 100644 spec/features/todos/todos_filtering_spec.rb delete mode 100644 spec/features/todos/todos_sorting_spec.rb delete mode 100644 spec/features/todos/todos_spec.rb diff --git a/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml new file mode 100644 index 00000000000..65df9a836a5 --- /dev/null +++ b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml @@ -0,0 +1,4 @@ +--- +title: Replace 'dashboard/todos' spinach with rspec +merge_request: 12453 +author: Alexander Randa (@randaalex) diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature deleted file mode 100644 index 0b23bbb7951..00000000000 --- a/features/dashboard/todos.feature +++ /dev/null @@ -1,28 +0,0 @@ -@dashboard -Feature: Dashboard Todos - Background: - Given I sign in as a user - And I own project "Shop" - And "John Doe" is a developer of project "Shop" - And "Mary Jane" is a developer of project "Shop" - And "Mary Jane" owns private project "Enterprise" - And I am a developer of project "Enterprise" - And I have todos - And I visit dashboard todos page - - @javascript - Scenario: I mark todos as done - Then I should see todos assigned to me - And I mark the todo as done - Then I should see the todo marked as done - - @javascript - Scenario: I mark all todos as done - Then I should see todos assigned to me - And I mark all todos as done - Then I should see all todos marked as done - - @javascript - Scenario: I click on a todo row - Given I click on the todo - Then I should be directed to the corresponding page diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb deleted file mode 100644 index 4a33babe3bd..00000000000 --- a/features/steps/dashboard/todos.rb +++ /dev/null @@ -1,191 +0,0 @@ -class Spinach::Features::DashboardTodos < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - include SharedUser - include WaitForRequests - - step '"John Doe" is a developer of project "Shop"' do - project.team << [john_doe, :developer] - end - - step 'I am a developer of project "Enterprise"' do - enterprise.team << [current_user, :developer] - end - - step '"Mary Jane" is a developer of project "Shop"' do - project.team << [john_doe, :developer] - end - - step 'I have todos' do - create(:todo, user: current_user, project: project, author: mary_jane, target: issue, action: Todo::MENTIONED) - create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::ASSIGNED) - note = create(:note, author: john_doe, noteable: issue, note: "#{current_user.to_reference} Wdyt?", project: project) - create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::MENTIONED, note: note) - create(:todo, user: current_user, project: project, author: john_doe, target: merge_request, action: Todo::ASSIGNED) - end - - step 'I should see todos assigned to me' do - merge_request_reference = merge_request.to_reference(full: true) - issue_reference = issue.to_reference(full: true) - - page.within('.todos-count') { expect(page).to have_content '4' } - expect(page).to have_content 'To do 4' - expect(page).to have_content 'Done 0' - - expect(page).to have_link project.name_with_namespace - should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title) - should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?") - should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title) - should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title) - end - - step 'I mark the todo as done' do - page.within('.todo:nth-child(1)') do - click_link 'Done' - end - - page.within('.todos-count') { expect(page).to have_content '3' } - expect(page).to have_content 'To do 3' - expect(page).to have_content 'Done 1' - should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_reversible) - end - - step 'I mark all todos as done' do - merge_request_reference = merge_request.to_reference(full: true) - issue_reference = issue.to_reference(full: true) - - find('.js-todos-mark-all').trigger('click') - - page.within('.todos-count') { expect(page).to have_content '0' } - expect(page).to have_content 'To do 0' - expect(page).to have_content 'Done 4' - expect(page).to have_content "You're all done!" - expect('.prepend-top-default').not_to have_link project.name_with_namespace - should_not_see_todo "John Doe assigned you merge request #{merge_request_reference}" - should_not_see_todo "John Doe mentioned you on issue #{issue_reference}" - should_not_see_todo "John Doe assigned you issue #{issue_reference}" - should_not_see_todo "Mary Jane mentioned you on issue #{issue_reference}" - end - - step 'I should see the todo marked as done' do - find('.todos-done a').trigger('click') - - expect(page).to have_link project.name_with_namespace - should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_irreversible) - end - - step 'I should see all todos marked as done' do - merge_request_reference = merge_request.to_reference(full: true) - issue_reference = issue.to_reference(full: true) - - find('.todos-done a').trigger('click') - - expect(page).to have_link project.name_with_namespace - should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, state: :done_irreversible) - should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", state: :done_irreversible) - should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, state: :done_irreversible) - should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, state: :done_irreversible) - end - - step 'I filter by "Enterprise"' do - click_button 'Project' - page.within '.dropdown-menu-project' do - click_link enterprise.name_with_namespace - end - end - - step 'I filter by "John Doe"' do - click_button 'Author' - page.within '.dropdown-menu-author' do - click_link john_doe.username - end - end - - step 'I filter by "Issue"' do - click_button 'Type' - page.within '.dropdown-menu-type' do - click_link 'Issue' - end - end - - step 'I filter by "Mentioned"' do - click_button 'Action' - page.within '.dropdown-menu-action' do - click_link 'Mentioned' - end - end - - step 'I should not see todos' do - expect(page).to have_content "You're all done!" - end - - step 'I should not see todos related to "Mary Jane" in the list' do - should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference(full: true)}" - end - - step 'I should not see todos related to "Merge Requests" in the list' do - should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}" - end - - step 'I should not see todos related to "Assignments" in the list' do - should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}" - should_not_see_todo "John Doe assigned you issue #{issue.to_reference(full: true)}" - end - - step 'I click on the todo' do - find('.todo:nth-child(1)').click - end - - step 'I should be directed to the corresponding page' do - page.should have_css('.identifier', text: 'Merge request !1') - # Merge request page loads and issues a number of Ajax requests - wait_for_requests - end - - def should_see_todo(position, title, body, state: :pending) - page.within(".todo:nth-child(#{position})") do - expect(page).to have_content title - expect(page).to have_content body - - if state == :pending - expect(page).to have_link 'Done' - elsif state == :done_reversible - expect(page).to have_link 'Undo' - elsif state == :done_irreversible - expect(page).not_to have_link 'Undo' - expect(page).not_to have_link 'Done' - else - raise 'Invalid state given, valid states: :pending, :done_reversible, :done_irreversible' - end - end - end - - def should_not_see_todo(title) - expect(page).not_to have_visible_content title - end - - def have_visible_content(text) - have_css('*', text: text, visible: true) - end - - def john_doe - @john_doe ||= user_exists("John Doe", { username: "john_doe" }) - end - - def mary_jane - @mary_jane ||= user_exists("Mary Jane", { username: "mary_jane" }) - end - - def enterprise - @enterprise ||= Project.find_by(name: 'Enterprise') - end - - def issue - @issue ||= create(:issue, assignees: [current_user], project: project) - end - - def merge_request - @merge_request ||= create(:merge_request, assignee: current_user, source_project: project) - end -end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index f0e751b820a..8a5b4112ffe 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -112,10 +112,6 @@ module SharedPaths visit dashboard_groups_path end - step 'I visit dashboard todos page' do - visit dashboard_todos_path - end - step 'I should be redirected to the dashboard groups page' do expect(current_path).to eq dashboard_groups_path end diff --git a/spec/features/dashboard/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb new file mode 100644 index 00000000000..030a86d1c01 --- /dev/null +++ b/spec/features/dashboard/todos/target_state_spec.rb @@ -0,0 +1,65 @@ +require 'rails_helper' + +feature 'Dashboard > Todo target states' do + let(:user) { create(:user) } + let(:author) { create(:user) } + let(:project) { create(:project, :public) } + + before do + sign_in(user) + end + + scenario 'on a closed issue todo has closed label' do + issue_closed = create(:issue, state: 'closed') + create_todo issue_closed + visit dashboard_todos_path + + page.within '.todos-list' do + expect(page).to have_content('Closed') + end + end + + scenario 'on an open issue todo does not have an open label' do + issue_open = create(:issue) + create_todo issue_open + visit dashboard_todos_path + + page.within '.todos-list' do + expect(page).not_to have_content('Open') + end + end + + scenario 'on a merged merge request todo has merged label' do + mr_merged = create(:merge_request, :simple, :merged, author: user) + create_todo mr_merged + visit dashboard_todos_path + + page.within '.todos-list' do + expect(page).to have_content('Merged') + end + end + + scenario 'on a closed merge request todo has closed label' do + mr_closed = create(:merge_request, :simple, :closed, author: user) + create_todo mr_closed + visit dashboard_todos_path + + page.within '.todos-list' do + expect(page).to have_content('Closed') + end + end + + scenario 'on an open merge request todo does not have an open label' do + mr_open = create(:merge_request, :simple, author: user) + create_todo mr_open + visit dashboard_todos_path + + page.within '.todos-list' do + expect(page).not_to have_content('Open') + end + end + + def create_todo(target) + create(:todo, :mentioned, user: user, project: project, target: target, author: author) + end +end diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb new file mode 100644 index 00000000000..0a363259fe7 --- /dev/null +++ b/spec/features/dashboard/todos/todos_filtering_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +feature 'Dashboard > User filters todos', js: true do + let(:user_1) { create(:user, username: 'user_1', name: 'user_1') } + let(:user_2) { create(:user, username: 'user_2', name: 'user_2') } + + let(:project_1) { create(:empty_project, name: 'project_1') } + let(:project_2) { create(:empty_project, name: 'project_2') } + + let(:issue) { create(:issue, title: 'issue', project: project_1) } + + let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') } + + before do + create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1) + create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2) + + project_1.team << [user_1, :developer] + project_2.team << [user_1, :developer] + sign_in(user_1) + visit dashboard_todos_path + end + + it 'filters by project' do + click_button 'Project' + within '.dropdown-menu-project' do + fill_in 'Search projects', with: project_1.name_with_namespace + click_link project_1.name_with_namespace + end + + wait_for_requests + + expect(page).to have_content project_1.name_with_namespace + expect(page).not_to have_content project_2.name_with_namespace + end + + context 'Author filter' do + it 'filters by author' do + click_button 'Author' + + within '.dropdown-menu-author' do + fill_in 'Search authors', with: user_1.name + click_link user_1.name + end + + wait_for_requests + + expect(find('.todos-list')).to have_content 'merge request' + expect(find('.todos-list')).not_to have_content 'issue' + end + + it 'shows only authors of existing todos' do + click_button 'Author' + + within '.dropdown-menu-author' do + # It should contain two users + 'Any Author' + expect(page).to have_selector('.dropdown-menu-user-link', count: 3) + expect(page).to have_content(user_1.name) + expect(page).to have_content(user_2.name) + end + end + + it 'shows only authors of existing done todos' do + user_3 = create :user + user_4 = create :user + create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done) + create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done) + + project_1.team << [user_3, :developer] + project_2.team << [user_4, :developer] + + visit dashboard_todos_path(state: 'done') + + click_button 'Author' + + within '.dropdown-menu-author' do + # It should contain two users + 'Any Author' + expect(page).to have_selector('.dropdown-menu-user-link', count: 3) + expect(page).to have_content(user_3.name) + expect(page).to have_content(user_4.name) + expect(page).not_to have_content(user_1.name) + expect(page).not_to have_content(user_2.name) + end + end + end + + it 'filters by type' do + click_button 'Type' + within '.dropdown-menu-type' do + click_link 'Issue' + end + + wait_for_requests + + expect(find('.todos-list')).to have_content issue.to_reference + expect(find('.todos-list')).not_to have_content merge_request.to_reference + end + + describe 'filter by action' do + before do + create(:todo, :build_failed, user: user_1, author: user_2, project: project_1) + create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue) + end + + it 'filters by Assigned' do + filter_action('Assigned') + + expect_to_see_action(:assigned) + end + + it 'filters by Mentioned' do + filter_action('Mentioned') + + expect_to_see_action(:mentioned) + end + + it 'filters by Added' do + filter_action('Added') + + expect_to_see_action(:marked) + end + + it 'filters by Pipelines' do + filter_action('Pipelines') + + expect_to_see_action(:build_failed) + end + + def filter_action(name) + click_button 'Action' + within '.dropdown-menu-action' do + click_link name + end + + wait_for_requests + end + + def expect_to_see_action(action_name) + action_names = { + assigned: ' assigned you ', + mentioned: ' mentioned ', + marked: ' added a todo for ', + build_failed: ' build failed for ' + } + + action_name_text = action_names.delete(action_name) + expect(find('.todos-list')).to have_content action_name_text + action_names.each_value do |other_action_text| + expect(find('.todos-list')).not_to have_content other_action_text + end + end + end +end diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb new file mode 100644 index 00000000000..5858f4aa101 --- /dev/null +++ b/spec/features/dashboard/todos/todos_sorting_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +feature 'Dashboard > User sorts todos' do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + + let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } + let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } + let(:label_3) { create(:label, title: 'label_3', project: project, priority: 3) } + + before do + project.team << [user, :developer] + end + + context 'sort options' do + let(:issue_1) { create(:issue, title: 'issue_1', project: project) } + let(:issue_2) { create(:issue, title: 'issue_2', project: project) } + let(:issue_3) { create(:issue, title: 'issue_3', project: project) } + let(:issue_4) { create(:issue, title: 'issue_4', project: project) } + + let!(:merge_request_1) { create(:merge_request, source_project: project, title: 'merge_request_1') } + + before do + create(:todo, user: user, project: project, target: issue_4, created_at: 5.hours.ago) + create(:todo, user: user, project: project, target: issue_2, created_at: 4.hours.ago) + create(:todo, user: user, project: project, target: issue_3, created_at: 3.hours.ago) + create(:todo, user: user, project: project, target: issue_1, created_at: 2.hours.ago) + create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago) + + merge_request_1.labels << label_1 + issue_3.labels << label_1 + issue_2.labels << label_3 + issue_1.labels << label_2 + + sign_in(user) + visit dashboard_todos_path + end + + it 'sorts with oldest created todos first' do + click_link 'Last created' + + results_list = page.find('.todos-list') + expect(results_list.all('p')[0]).to have_content('merge_request_1') + expect(results_list.all('p')[1]).to have_content('issue_1') + expect(results_list.all('p')[2]).to have_content('issue_3') + expect(results_list.all('p')[3]).to have_content('issue_2') + expect(results_list.all('p')[4]).to have_content('issue_4') + end + + it 'sorts with newest created todos first' do + click_link 'Oldest created' + + results_list = page.find('.todos-list') + expect(results_list.all('p')[0]).to have_content('issue_4') + expect(results_list.all('p')[1]).to have_content('issue_2') + expect(results_list.all('p')[2]).to have_content('issue_3') + expect(results_list.all('p')[3]).to have_content('issue_1') + expect(results_list.all('p')[4]).to have_content('merge_request_1') + end + + it 'sorts by label priority' do + click_link 'Label priority' + + results_list = page.find('.todos-list') + expect(results_list.all('p')[0]).to have_content('issue_3') + expect(results_list.all('p')[1]).to have_content('merge_request_1') + expect(results_list.all('p')[2]).to have_content('issue_1') + expect(results_list.all('p')[3]).to have_content('issue_2') + expect(results_list.all('p')[4]).to have_content('issue_4') + end + end + + context 'issues and merge requests' do + let(:issue_1) { create(:issue, id: 10000, title: 'issue_1', project: project) } + let(:issue_2) { create(:issue, id: 10001, title: 'issue_2', project: project) } + let(:merge_request_1) { create(:merge_request, id: 10000, title: 'merge_request_1', source_project: project) } + + before do + issue_1.labels << label_1 + issue_2.labels << label_2 + + create(:todo, user: user, project: project, target: issue_1) + create(:todo, user: user, project: project, target: issue_2) + create(:todo, user: user, project: project, target: merge_request_1) + + gitlab_sign_in(user) + visit dashboard_todos_path + end + + it "doesn't mix issues and merge requests label priorities" do + click_link 'Label priority' + + results_list = page.find('.todos-list') + expect(results_list.all('p')[0]).to have_content('issue_1') + expect(results_list.all('p')[1]).to have_content('issue_2') + expect(results_list.all('p')[2]).to have_content('merge_request_1') + end + end +end diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb new file mode 100644 index 00000000000..24da5db305f --- /dev/null +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -0,0 +1,355 @@ +require 'spec_helper' + +feature 'Dashboard Todos' do + let(:user) { create(:user) } + let(:author) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, due_date: Date.today) } + + context 'User does not have todos' do + before do + sign_in(user) + visit dashboard_todos_path + end + + it 'shows "All done" message' do + expect(page).to have_content 'Todos let you see what you should do next.' + end + end + + context 'User has a todo', js: true do + before do + create(:todo, :mentioned, user: user, project: project, target: issue, author: author) + sign_in(user) + + visit dashboard_todos_path + end + + it 'has todo present' do + expect(page).to have_selector('.todos-list .todo', count: 1) + end + + it 'shows due date as today' do + within first('.todo') do + expect(page).to have_content 'Due today' + end + end + + shared_examples 'deleting the todo' do + before do + within first('.todo') do + click_link 'Done' + end + end + + it 'is marked as done-reversible in the list' do + expect(page).to have_selector('.todos-list .todo.todo-pending.done-reversible') + end + + it 'shows Undo button' do + expect(page).to have_selector('.js-undo-todo', visible: true) + expect(page).to have_selector('.js-done-todo', visible: false) + end + + it 'updates todo count' do + expect(page).to have_content 'To do 0' + expect(page).to have_content 'Done 1' + end + + it 'has not "All done" message' do + expect(page).not_to have_selector('.todos-all-done') + end + end + + shared_examples 'deleting and restoring the todo' do + before do + within first('.todo') do + click_link 'Done' + wait_for_requests + click_link 'Undo' + end + end + + it 'is marked back as pending in the list' do + expect(page).not_to have_selector('.todos-list .todo.todo-pending.done-reversible') + expect(page).to have_selector('.todos-list .todo.todo-pending') + end + + it 'shows Done button' do + expect(page).to have_selector('.js-undo-todo', visible: false) + expect(page).to have_selector('.js-done-todo', visible: true) + end + + it 'updates todo count' do + expect(page).to have_content 'To do 1' + expect(page).to have_content 'Done 0' + end + end + + it_behaves_like 'deleting the todo' + it_behaves_like 'deleting and restoring the todo' + + context 'todo is stale on the page' do + before do + todos = TodosFinder.new(user, state: :pending).execute + TodoService.new.mark_todos_as_done(todos, user) + end + + it_behaves_like 'deleting the todo' + it_behaves_like 'deleting and restoring the todo' + end + end + + context 'User created todos for themself' do + before do + sign_in(user) + end + + context 'issue assigned todo' do + before do + create(:todo, :assigned, user: user, project: project, target: issue, author: user) + visit dashboard_todos_path + end + + it 'shows issue assigned to yourself message' do + page.within('.js-todos-all') do + expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself") + end + end + end + + context 'marked todo' do + before do + create(:todo, :marked, user: user, project: project, target: issue, author: user) + visit dashboard_todos_path + end + + it 'shows you added a todo message' do + page.within('.js-todos-all') do + expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}") + expect(page).not_to have_content('to yourself') + end + end + end + + context 'mentioned todo' do + before do + create(:todo, :mentioned, user: user, project: project, target: issue, author: user) + visit dashboard_todos_path + end + + it 'shows you mentioned yourself message' do + page.within('.js-todos-all') do + expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}") + expect(page).not_to have_content('to yourself') + end + end + end + + context 'directly_addressed todo' do + before do + create(:todo, :directly_addressed, user: user, project: project, target: issue, author: user) + visit dashboard_todos_path + end + + it 'shows you directly addressed yourself message' do + page.within('.js-todos-all') do + expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}") + expect(page).not_to have_content('to yourself') + end + end + end + + context 'approval todo' do + let(:merge_request) { create(:merge_request) } + + before do + create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user) + visit dashboard_todos_path + end + + it 'shows you set yourself as an approver message' do + page.within('.js-todos-all') do + expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}") + expect(page).not_to have_content('to yourself') + end + end + end + end + + context 'User has done todos', js: true do + before do + create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author) + sign_in(user) + visit dashboard_todos_path(state: :done) + end + + it 'has the done todo present' do + expect(page).to have_selector('.todos-list .todo.todo-done', count: 1) + end + + describe 'restoring the todo' do + before do + within first('.todo') do + click_link 'Add todo' + end + end + + it 'is removed from the list' do + expect(page).not_to have_selector('.todos-list .todo.todo-done') + end + + it 'updates todo count' do + expect(page).to have_content 'To do 1' + expect(page).to have_content 'Done 0' + end + end + end + + context 'User has Todos with labels spanning multiple projects' do + before do + label1 = create(:label, project: project) + note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project) + create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id) + + project2 = create(:project, :public) + label2 = create(:label, project: project2) + issue2 = create(:issue, project: project2) + note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2) + create(:todo, :mentioned, project: project2, target: issue2, user: user, note_id: note2.id) + + gitlab_sign_in(user) + visit dashboard_todos_path + end + + it 'shows page with two Todos' do + expect(page).to have_selector('.todos-list .todo', count: 2) + end + end + + context 'User has multiple pages of Todos' do + before do + allow(Todo).to receive(:default_per_page).and_return(1) + + # Create just enough records to cause us to paginate + create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author) + + sign_in(user) + end + + it 'is paginated' do + visit dashboard_todos_path + + expect(page).to have_selector('.gl-pagination') + end + + it 'is has the right number of pages' do + visit dashboard_todos_path + + expect(page).to have_selector('.gl-pagination .page', count: 2) + end + + describe 'mark all as done', js: true do + before do + visit dashboard_todos_path + find('.js-todos-mark-all').trigger('click') + end + + it 'shows "All done" message!' do + expect(page).to have_content 'To do 0' + expect(page).to have_content "You're all done!" + expect(page).not_to have_selector('.gl-pagination') + end + + it 'shows "Undo mark all as done" button' do + expect(page).to have_selector('.js-todos-mark-all', visible: false) + expect(page).to have_selector('.js-todos-undo-all', visible: true) + end + end + + describe 'undo mark all as done', js: true do + before do + visit dashboard_todos_path + end + + it 'shows the restored todo list' do + mark_all_and_undo + + expect(page).to have_selector('.todos-list .todo', count: 1) + expect(page).to have_selector('.gl-pagination') + expect(page).not_to have_content "You're all done!" + end + + it 'updates todo count' do + mark_all_and_undo + + expect(page).to have_content 'To do 2' + expect(page).to have_content 'Done 0' + end + + it 'shows "Mark all as done" button' do + mark_all_and_undo + + expect(page).to have_selector('.js-todos-mark-all', visible: true) + expect(page).to have_selector('.js-todos-undo-all', visible: false) + end + + context 'User has deleted a todo' do + before do + within first('.todo') do + click_link 'Done' + end + end + + it 'shows the restored todo list with the deleted todo' do + mark_all_and_undo + + expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1) + end + end + + def mark_all_and_undo + find('.js-todos-mark-all').trigger('click') + wait_for_requests + find('.js-todos-undo-all').trigger('click') + wait_for_requests + end + end + end + + context 'User has a Todo in a project pending deletion' do + before do + deleted_project = create(:project, :public, pending_delete: true) + create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author) + create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author, state: :done) + sign_in(user) + visit dashboard_todos_path + end + + it 'shows "All done" message' do + within('.todos-count') { expect(page).to have_content '0' } + expect(page).to have_content 'To do 0' + expect(page).to have_content 'Done 0' + expect(page).to have_selector('.todos-all-done', count: 1) + end + end + + context 'User has a Build Failed todo' do + let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } + + before do + sign_in(user) + visit dashboard_todos_path + end + + it 'shows the todo' do + expect(page).to have_content 'The build failed for merge request' + end + + it 'links to the pipelines for the merge request' do + href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target) + + expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href + end + end +end diff --git a/spec/features/todos/target_state_spec.rb b/spec/features/todos/target_state_spec.rb deleted file mode 100644 index 99b70b3d3a1..00000000000 --- a/spec/features/todos/target_state_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'rails_helper' - -feature 'Todo target states', feature: true do - let(:user) { create(:user) } - let(:author) { create(:user) } - let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } - - before do - gitlab_sign_in user - end - - scenario 'on a closed issue todo has closed label' do - issue_closed = create(:issue, state: 'closed') - create_todo issue_closed - visit dashboard_todos_path - - page.within '.todos-list' do - expect(page).to have_content('Closed') - end - end - - scenario 'on an open issue todo does not have an open label' do - issue_open = create(:issue) - create_todo issue_open - visit dashboard_todos_path - - page.within '.todos-list' do - expect(page).not_to have_content('Open') - end - end - - scenario 'on a merged merge request todo has merged label' do - mr_merged = create(:merge_request, :simple, author: user, state: 'merged') - create_todo mr_merged - visit dashboard_todos_path - - page.within '.todos-list' do - expect(page).to have_content('Merged') - end - end - - scenario 'on a closed merge request todo has closed label' do - mr_closed = create(:merge_request, :simple, author: user, state: 'closed') - create_todo mr_closed - visit dashboard_todos_path - - page.within '.todos-list' do - expect(page).to have_content('Closed') - end - end - - scenario 'on an open merge request todo does not have an open label' do - mr_open = create(:merge_request, :simple, author: user) - create_todo mr_open - visit dashboard_todos_path - - page.within '.todos-list' do - expect(page).not_to have_content('Open') - end - end - - def create_todo(target) - create(:todo, :mentioned, user: user, project: project, target: target, author: author) - end -end diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb deleted file mode 100644 index 032fb479076..00000000000 --- a/spec/features/todos/todos_filtering_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'spec_helper' - -describe 'Dashboard > User filters todos', feature: true, js: true do - let(:user_1) { create(:user, username: 'user_1', name: 'user_1') } - let(:user_2) { create(:user, username: 'user_2', name: 'user_2') } - - let(:project_1) { create(:empty_project, name: 'project_1') } - let(:project_2) { create(:empty_project, name: 'project_2') } - - let(:issue) { create(:issue, title: 'issue', project: project_1) } - - let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') } - - before do - create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1) - create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2) - - project_1.team << [user_1, :developer] - project_2.team << [user_1, :developer] - gitlab_sign_in(user_1) - visit dashboard_todos_path - end - - it 'filters by project' do - click_button 'Project' - within '.dropdown-menu-project' do - fill_in 'Search projects', with: project_1.name_with_namespace - click_link project_1.name_with_namespace - end - - wait_for_requests - - expect(page).to have_content project_1.name_with_namespace - expect(page).not_to have_content project_2.name_with_namespace - end - - context "Author filter" do - it 'filters by author' do - click_button 'Author' - - within '.dropdown-menu-author' do - fill_in 'Search authors', with: user_1.name - click_link user_1.name - end - - wait_for_requests - - expect(find('.todos-list')).to have_content 'merge request' - expect(find('.todos-list')).not_to have_content 'issue' - end - - it "shows only authors of existing todos" do - click_button 'Author' - - within '.dropdown-menu-author' do - # It should contain two users + "Any Author" - expect(page).to have_selector('.dropdown-menu-user-link', count: 3) - expect(page).to have_content(user_1.name) - expect(page).to have_content(user_2.name) - end - end - - it "shows only authors of existing done todos" do - user_3 = create :user - user_4 = create :user - create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done) - create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done) - - project_1.team << [user_3, :developer] - project_2.team << [user_4, :developer] - - visit dashboard_todos_path(state: 'done') - - click_button 'Author' - - within '.dropdown-menu-author' do - # It should contain two users + "Any Author" - expect(page).to have_selector('.dropdown-menu-user-link', count: 3) - expect(page).to have_content(user_3.name) - expect(page).to have_content(user_4.name) - expect(page).not_to have_content(user_1.name) - expect(page).not_to have_content(user_2.name) - end - end - end - - it 'filters by type' do - click_button 'Type' - within '.dropdown-menu-type' do - click_link 'Issue' - end - - wait_for_requests - - expect(find('.todos-list')).to have_content issue.to_reference - expect(find('.todos-list')).not_to have_content merge_request.to_reference - end - - describe 'filter by action' do - before do - create(:todo, :build_failed, user: user_1, author: user_2, project: project_1) - create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue) - end - - it 'filters by Assigned' do - filter_action('Assigned') - - expect_to_see_action(:assigned) - end - - it 'filters by Mentioned' do - filter_action('Mentioned') - - expect_to_see_action(:mentioned) - end - - it 'filters by Added' do - filter_action('Added') - - expect_to_see_action(:marked) - end - - it 'filters by Pipelines' do - filter_action('Pipelines') - - expect_to_see_action(:build_failed) - end - - def filter_action(name) - click_button 'Action' - within '.dropdown-menu-action' do - click_link name - end - - wait_for_requests - end - - def expect_to_see_action(action_name) - action_names = { - assigned: ' assigned you ', - mentioned: ' mentioned ', - marked: ' added a todo for ', - build_failed: ' build failed for ' - } - - action_name_text = action_names.delete(action_name) - expect(find('.todos-list')).to have_content action_name_text - action_names.each_value do |other_action_text| - expect(find('.todos-list')).not_to have_content other_action_text - end - end - end -end diff --git a/spec/features/todos/todos_sorting_spec.rb b/spec/features/todos/todos_sorting_spec.rb deleted file mode 100644 index 498bbac6d14..00000000000 --- a/spec/features/todos/todos_sorting_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'spec_helper' - -describe "Dashboard > User sorts todos", feature: true do - let(:user) { create(:user) } - let(:project) { create(:empty_project) } - - let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } - let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } - let(:label_3) { create(:label, title: 'label_3', project: project, priority: 3) } - - before do - project.team << [user, :developer] - end - - context 'sort options' do - let(:issue_1) { create(:issue, title: 'issue_1', project: project) } - let(:issue_2) { create(:issue, title: 'issue_2', project: project) } - let(:issue_3) { create(:issue, title: 'issue_3', project: project) } - let(:issue_4) { create(:issue, title: 'issue_4', project: project) } - - let!(:merge_request_1) { create(:merge_request, source_project: project, title: "merge_request_1") } - - before do - create(:todo, user: user, project: project, target: issue_4, created_at: 5.hours.ago) - create(:todo, user: user, project: project, target: issue_2, created_at: 4.hours.ago) - create(:todo, user: user, project: project, target: issue_3, created_at: 3.hours.ago) - create(:todo, user: user, project: project, target: issue_1, created_at: 2.hours.ago) - create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago) - - merge_request_1.labels << label_1 - issue_3.labels << label_1 - issue_2.labels << label_3 - issue_1.labels << label_2 - - gitlab_sign_in(user) - visit dashboard_todos_path - end - - it "sorts with oldest created todos first" do - click_link "Last created" - - results_list = page.find('.todos-list') - expect(results_list.all('p')[0]).to have_content("merge_request_1") - expect(results_list.all('p')[1]).to have_content("issue_1") - expect(results_list.all('p')[2]).to have_content("issue_3") - expect(results_list.all('p')[3]).to have_content("issue_2") - expect(results_list.all('p')[4]).to have_content("issue_4") - end - - it "sorts with newest created todos first" do - click_link "Oldest created" - - results_list = page.find('.todos-list') - expect(results_list.all('p')[0]).to have_content("issue_4") - expect(results_list.all('p')[1]).to have_content("issue_2") - expect(results_list.all('p')[2]).to have_content("issue_3") - expect(results_list.all('p')[3]).to have_content("issue_1") - expect(results_list.all('p')[4]).to have_content("merge_request_1") - end - - it "sorts by label priority" do - click_link "Label priority" - - results_list = page.find('.todos-list') - expect(results_list.all('p')[0]).to have_content("issue_3") - expect(results_list.all('p')[1]).to have_content("merge_request_1") - expect(results_list.all('p')[2]).to have_content("issue_1") - expect(results_list.all('p')[3]).to have_content("issue_2") - expect(results_list.all('p')[4]).to have_content("issue_4") - end - end - - context 'issues and merge requests' do - let(:issue_1) { create(:issue, id: 10000, title: 'issue_1', project: project) } - let(:issue_2) { create(:issue, id: 10001, title: 'issue_2', project: project) } - let(:merge_request_1) { create(:merge_request, id: 10000, title: 'merge_request_1', source_project: project) } - - before do - issue_1.labels << label_1 - issue_2.labels << label_2 - - create(:todo, user: user, project: project, target: issue_1) - create(:todo, user: user, project: project, target: issue_2) - create(:todo, user: user, project: project, target: merge_request_1) - - gitlab_sign_in(user) - visit dashboard_todos_path - end - - it "doesn't mix issues and merge requests label priorities" do - click_link "Label priority" - - results_list = page.find('.todos-list') - expect(results_list.all('p')[0]).to have_content("issue_1") - expect(results_list.all('p')[1]).to have_content("issue_2") - expect(results_list.all('p')[2]).to have_content("merge_request_1") - end - end -end diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb deleted file mode 100644 index 41b32bdedc3..00000000000 --- a/spec/features/todos/todos_spec.rb +++ /dev/null @@ -1,355 +0,0 @@ -require 'spec_helper' - -describe 'Dashboard Todos', feature: true do - let(:user) { create(:user) } - let(:author) { create(:user) } - let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } - let(:issue) { create(:issue, due_date: Date.today) } - - describe 'GET /dashboard/todos' do - context 'User does not have todos' do - before do - gitlab_sign_in(user) - visit dashboard_todos_path - end - it 'shows "All done" message' do - expect(page).to have_content "Todos let you see what you should do next." - end - end - - context 'User has a todo', js: true do - before do - create(:todo, :mentioned, user: user, project: project, target: issue, author: author) - gitlab_sign_in(user) - visit dashboard_todos_path - end - - it 'has todo present' do - expect(page).to have_selector('.todos-list .todo', count: 1) - end - - it 'shows due date as today' do - within first('.todo') do - expect(page).to have_content 'Due today' - end - end - - shared_examples 'deleting the todo' do - before do - within first('.todo') do - click_link 'Done' - end - end - - it 'is marked as done-reversible in the list' do - expect(page).to have_selector('.todos-list .todo.todo-pending.done-reversible') - end - - it 'shows Undo button' do - expect(page).to have_selector('.js-undo-todo', visible: true) - expect(page).to have_selector('.js-done-todo', visible: false) - end - - it 'updates todo count' do - expect(page).to have_content 'To do 0' - expect(page).to have_content 'Done 1' - end - - it 'has not "All done" message' do - expect(page).not_to have_selector('.todos-all-done') - end - end - - shared_examples 'deleting and restoring the todo' do - before do - within first('.todo') do - click_link 'Done' - wait_for_requests - click_link 'Undo' - end - end - - it 'is marked back as pending in the list' do - expect(page).not_to have_selector('.todos-list .todo.todo-pending.done-reversible') - expect(page).to have_selector('.todos-list .todo.todo-pending') - end - - it 'shows Done button' do - expect(page).to have_selector('.js-undo-todo', visible: false) - expect(page).to have_selector('.js-done-todo', visible: true) - end - - it 'updates todo count' do - expect(page).to have_content 'To do 1' - expect(page).to have_content 'Done 0' - end - end - - it_behaves_like 'deleting the todo' - it_behaves_like 'deleting and restoring the todo' - - context 'todo is stale on the page' do - before do - todos = TodosFinder.new(user, state: :pending).execute - TodoService.new.mark_todos_as_done(todos, user) - end - - it_behaves_like 'deleting the todo' - it_behaves_like 'deleting and restoring the todo' - end - end - - context 'User created todos for themself' do - before do - gitlab_sign_in(user) - end - - context 'issue assigned todo' do - before do - create(:todo, :assigned, user: user, project: project, target: issue, author: user) - visit dashboard_todos_path - end - - it 'shows issue assigned to yourself message' do - page.within('.js-todos-all') do - expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself") - end - end - end - - context 'marked todo' do - before do - create(:todo, :marked, user: user, project: project, target: issue, author: user) - visit dashboard_todos_path - end - - it 'shows you added a todo message' do - page.within('.js-todos-all') do - expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}") - expect(page).not_to have_content('to yourself') - end - end - end - - context 'mentioned todo' do - before do - create(:todo, :mentioned, user: user, project: project, target: issue, author: user) - visit dashboard_todos_path - end - - it 'shows you mentioned yourself message' do - page.within('.js-todos-all') do - expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}") - expect(page).not_to have_content('to yourself') - end - end - end - - context 'directly_addressed todo' do - before do - create(:todo, :directly_addressed, user: user, project: project, target: issue, author: user) - visit dashboard_todos_path - end - - it 'shows you directly addressed yourself message' do - page.within('.js-todos-all') do - expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}") - expect(page).not_to have_content('to yourself') - end - end - end - - context 'approval todo' do - let(:merge_request) { create(:merge_request) } - - before do - create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user) - visit dashboard_todos_path - end - - it 'shows you set yourself as an approver message' do - page.within('.js-todos-all') do - expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}") - expect(page).not_to have_content('to yourself') - end - end - end - end - - context 'User has done todos', js: true do - before do - create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author) - gitlab_sign_in(user) - visit dashboard_todos_path(state: :done) - end - - it 'has the done todo present' do - expect(page).to have_selector('.todos-list .todo.todo-done', count: 1) - end - - describe 'restoring the todo' do - before do - within first('.todo') do - click_link 'Add todo' - end - end - - it 'is removed from the list' do - expect(page).not_to have_selector('.todos-list .todo.todo-done') - end - - it 'updates todo count' do - expect(page).to have_content 'To do 1' - expect(page).to have_content 'Done 0' - end - end - end - - context 'User has Todos with labels spanning multiple projects' do - before do - label1 = create(:label, project: project) - note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project) - create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id) - - project2 = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) - label2 = create(:label, project: project2) - issue2 = create(:issue, project: project2) - note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2) - create(:todo, :mentioned, project: project2, target: issue2, user: user, note_id: note2.id) - - gitlab_sign_in(user) - visit dashboard_todos_path - end - - it 'shows page with two Todos' do - expect(page).to have_selector('.todos-list .todo', count: 2) - end - end - - context 'User has multiple pages of Todos' do - before do - allow(Todo).to receive(:default_per_page).and_return(1) - - # Create just enough records to cause us to paginate - create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author) - - gitlab_sign_in(user) - end - - it 'is paginated' do - visit dashboard_todos_path - - expect(page).to have_selector('.gl-pagination') - end - - it 'is has the right number of pages' do - visit dashboard_todos_path - - expect(page).to have_selector('.gl-pagination .page', count: 2) - end - - describe 'mark all as done', js: true do - before do - visit dashboard_todos_path - find('.js-todos-mark-all').trigger('click') - end - - it 'shows "All done" message!' do - expect(page).to have_content 'To do 0' - expect(page).to have_content "You're all done!" - expect(page).not_to have_selector('.gl-pagination') - end - - it 'shows "Undo mark all as done" button' do - expect(page).to have_selector('.js-todos-mark-all', visible: false) - expect(page).to have_selector('.js-todos-undo-all', visible: true) - end - end - - describe 'undo mark all as done', js: true do - before do - visit dashboard_todos_path - end - - it 'shows the restored todo list' do - mark_all_and_undo - - expect(page).to have_selector('.todos-list .todo', count: 1) - expect(page).to have_selector('.gl-pagination') - expect(page).not_to have_content "You're all done!" - end - - it 'updates todo count' do - mark_all_and_undo - - expect(page).to have_content 'To do 2' - expect(page).to have_content 'Done 0' - end - - it 'shows "Mark all as done" button' do - mark_all_and_undo - - expect(page).to have_selector('.js-todos-mark-all', visible: true) - expect(page).to have_selector('.js-todos-undo-all', visible: false) - end - - context 'User has deleted a todo' do - before do - within first('.todo') do - click_link 'Done' - end - end - - it 'shows the restored todo list with the deleted todo' do - mark_all_and_undo - - expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1) - end - end - - def mark_all_and_undo - find('.js-todos-mark-all').trigger('click') - wait_for_requests - find('.js-todos-undo-all').trigger('click') - wait_for_requests - end - end - end - - context 'User has a Todo in a project pending deletion' do - before do - deleted_project = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC, pending_delete: true) - create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author) - create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author, state: :done) - gitlab_sign_in(user) - visit dashboard_todos_path - end - - it 'shows "All done" message' do - within('.todos-count') { expect(page).to have_content '0' } - expect(page).to have_content 'To do 0' - expect(page).to have_content 'Done 0' - expect(page).to have_selector('.todos-all-done', count: 1) - end - end - - context 'User has a Build Failed todo' do - let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } - - before do - gitlab_sign_in user - visit dashboard_todos_path - end - - it 'shows the todo' do - expect(page).to have_content 'The build failed for merge request' - end - - it 'links to the pipelines for the merge request' do - href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target) - - expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href - end - end - end -end -- cgit v1.2.1 From 5300a8944e850a2aa75ba051ef12adcd176c3dcc Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 16:26:55 +0200 Subject: Remove group members spinach tests that we already covered with rspec Signed-off-by: Dmitriy Zaporozhets --- features/group/members.feature | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/features/group/members.feature b/features/group/members.feature index e539f6a1273..5ff75ab3a5b 100644 --- a/features/group/members.feature +++ b/features/group/members.feature @@ -4,46 +4,6 @@ Feature: Group Members And "John Doe" is owner of group "Owned" And "John Doe" is guest of group "Guest" - # Leave - - @javascript - Scenario: Owner should be able to remove himself from group if he is not the last owner - Given "Mary Jane" is owner of group "Owned" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - When I click on the "Remove User From Group" button for "John Doe" - And I visit group "Owned" members page - Then I should not see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - - @javascript - Scenario: Owner should not be able to remove himself from group if he is the last owner - Given "Mary Jane" is guest of group "Owned" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - Then I should not see the "Remove User From Group" button for "John Doe" - - @javascript - Scenario: Guest should be able to remove himself from group - Given "Mary Jane" is guest of group "Guest" - When I visit group "Guest" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - When I click on the "Remove User From Group" button for "John Doe" - When I visit group "Guest" members page - Then I should not see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - - @javascript - Scenario: Guest should be able to remove himself from group even if he is the only user in the group - When I visit group "Guest" members page - Then I should see user "John Doe" in team list - When I click on the "Remove User From Group" button for "John Doe" - When I visit group "Guest" members page - Then I should not see user "John Doe" in team list - # Remove others Scenario: Owner should be able to remove other users from group -- cgit v1.2.1 From f2174f2dd07e83a02fd4bf8bb86deae8ce8fe122 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 16:33:49 +0200 Subject: Merge group request access specs under one file Signed-off-by: Dmitriy Zaporozhets --- ...er_cannot_request_access_to_his_project_spec.rb | 16 ----- .../groups/members/requests_access_spec.rb | 78 ++++++++++++++++++++++ .../groups/members/user_requests_access_spec.rb | 71 -------------------- 3 files changed, 78 insertions(+), 87 deletions(-) delete mode 100644 spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb create mode 100644 spec/features/groups/members/requests_access_spec.rb delete mode 100644 spec/features/groups/members/user_requests_access_spec.rb diff --git a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb deleted file mode 100644 index 135bb3572bc..00000000000 --- a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Member cannot request access to his project', feature: true do - let(:member) { create(:user) } - let(:group) { create(:group) } - - background do - group.add_developer(member) - gitlab_sign_in(member) - visit group_path(group) - end - - scenario 'member does not see the request access button' do - expect(page).not_to have_content 'Request Access' - end -end diff --git a/spec/features/groups/members/requests_access_spec.rb b/spec/features/groups/members/requests_access_spec.rb new file mode 100644 index 00000000000..eaf9d163896 --- /dev/null +++ b/spec/features/groups/members/requests_access_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +feature 'Groups > Members > Requests access', feature: true do + let(:user) { create(:user) } + let(:owner) { create(:user) } + let(:group) { create(:group, :public, :access_requestable) } + let!(:project) { create(:project, :private, namespace: group) } + + background do + group.add_owner(owner) + gitlab_sign_in(user) + visit group_path(group) + end + + scenario 'request access feature is disabled' do + group.update_attributes(request_access_enabled: false) + visit group_path(group) + + expect(page).not_to have_content 'Request Access' + end + + scenario 'user can request access to a group' do + perform_enqueued_jobs { click_link 'Request Access' } + + expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email] + expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group" + + expect(group.requesters.exists?(user_id: user)).to be_truthy + expect(page).to have_content 'Your request for access has been queued for review.' + + expect(page).to have_content 'Withdraw Access Request' + expect(page).not_to have_content 'Leave group' + end + + scenario 'user does not see private projects' do + perform_enqueued_jobs { click_link 'Request Access' } + + expect(page).not_to have_content project.name + end + + scenario 'user does not see group in the Dashboard > Groups page' do + perform_enqueued_jobs { click_link 'Request Access' } + + visit dashboard_groups_path + + expect(page).not_to have_content group.name + end + + scenario 'user is not listed in the group members page' do + click_link 'Request Access' + + expect(group.requesters.exists?(user_id: user)).to be_truthy + + click_link 'Members' + + page.within('.content') do + expect(page).not_to have_content(user.name) + end + end + + scenario 'user can withdraw its request for access' do + click_link 'Request Access' + + expect(group.requesters.exists?(user_id: user)).to be_truthy + + click_link 'Withdraw Access Request' + + expect(group.requesters.exists?(user_id: user)).to be_falsey + expect(page).to have_content 'Your access request to the group has been withdrawn.' + end + + scenario 'member does not see the request access button' do + group.add_owner(user) + visit group_path(group) + + expect(page).not_to have_content 'Request Access' + end +end diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb deleted file mode 100644 index 3813308c237..00000000000 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > User requests access', feature: true do - let(:user) { create(:user) } - let(:owner) { create(:user) } - let(:group) { create(:group, :public, :access_requestable) } - let!(:project) { create(:project, :private, namespace: group) } - - background do - group.add_owner(owner) - gitlab_sign_in(user) - visit group_path(group) - end - - scenario 'request access feature is disabled' do - group.update_attributes(request_access_enabled: false) - visit group_path(group) - - expect(page).not_to have_content 'Request Access' - end - - scenario 'user can request access to a group' do - perform_enqueued_jobs { click_link 'Request Access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group" - - expect(group.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content 'Your request for access has been queued for review.' - - expect(page).to have_content 'Withdraw Access Request' - expect(page).not_to have_content 'Leave group' - end - - scenario 'user does not see private projects' do - perform_enqueued_jobs { click_link 'Request Access' } - - expect(page).not_to have_content project.name - end - - scenario 'user does not see group in the Dashboard > Groups page' do - perform_enqueued_jobs { click_link 'Request Access' } - - visit dashboard_groups_path - - expect(page).not_to have_content group.name - end - - scenario 'user is not listed in the group members page' do - click_link 'Request Access' - - expect(group.requesters.exists?(user_id: user)).to be_truthy - - click_link 'Members' - - page.within('.content') do - expect(page).not_to have_content(user.name) - end - end - - scenario 'user can withdraw its request for access' do - click_link 'Request Access' - - expect(group.requesters.exists?(user_id: user)).to be_truthy - - click_link 'Withdraw Access Request' - - expect(group.requesters.exists?(user_id: user)).to be_falsey - expect(page).to have_content 'Your access request to the group has been withdrawn.' - end -end -- cgit v1.2.1 From 195cf2a7125d82edb5f82ae73d269d885aae6c04 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 16:34:35 +0200 Subject: Fix wrong scenario title to owner_manages_access_requests_spec.rb Signed-off-by: Dmitriy Zaporozhets --- spec/features/groups/members/owner_manages_access_requests_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 4e4cf12e8af..db656d244c0 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -17,7 +17,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do expect_visible_access_request(group, user) end - scenario 'master can grant access' do + scenario 'owner can grant access' do visit group_group_members_path(group) expect_visible_access_request(group, user) @@ -28,7 +28,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was granted" end - scenario 'master can deny access' do + scenario 'owner can deny access' do visit group_group_members_path(group) expect_visible_access_request(group, user) -- cgit v1.2.1 From c56f7876028e7eb3b9a116d1dc26ae229f8b2ca0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 16:43:14 +0200 Subject: Rename group member specs for consistent naming Signed-off-by: Dmitriy Zaporozhets --- spec/features/groups/members/list_members_spec.rb | 105 +++++++++++++++++++++ spec/features/groups/members/list_spec.rb | 105 --------------------- .../groups/members/manage_access_requests_spec.rb | 47 +++++++++ spec/features/groups/members/manage_members.rb | 4 + .../members/owner_manages_access_requests_spec.rb | 47 --------- .../features/groups/members/request_access_spec.rb | 78 +++++++++++++++ .../groups/members/requests_access_spec.rb | 78 --------------- spec/features/groups/members/sort_members_spec.rb | 98 +++++++++++++++++++ spec/features/groups/members/sorting_spec.rb | 98 ------------------- 9 files changed, 332 insertions(+), 328 deletions(-) create mode 100644 spec/features/groups/members/list_members_spec.rb delete mode 100644 spec/features/groups/members/list_spec.rb create mode 100644 spec/features/groups/members/manage_access_requests_spec.rb create mode 100644 spec/features/groups/members/manage_members.rb delete mode 100644 spec/features/groups/members/owner_manages_access_requests_spec.rb create mode 100644 spec/features/groups/members/request_access_spec.rb delete mode 100644 spec/features/groups/members/requests_access_spec.rb create mode 100644 spec/features/groups/members/sort_members_spec.rb delete mode 100644 spec/features/groups/members/sorting_spec.rb diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb new file mode 100644 index 00000000000..69cbeb4a35a --- /dev/null +++ b/spec/features/groups/members/list_members_spec.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +feature 'Groups > Members > List members', feature: true do + include Select2Helper + + let(:user1) { create(:user, name: 'John Doe') } + let(:user2) { create(:user, name: 'Mary Jane') } + let(:group) { create(:group) } + let(:nested_group) { create(:group, parent: group) } + + background do + gitlab_sign_in(user1) + end + + scenario 'show members from current group and parent', :nested_groups do + group.add_developer(user1) + nested_group.add_developer(user2) + + visit group_group_members_path(nested_group) + + expect(first_row.text).to include(user1.name) + expect(second_row.text).to include(user2.name) + end + + scenario 'show user once if member of both current group and parent', :nested_groups do + group.add_developer(user1) + nested_group.add_developer(user1) + + visit group_group_members_path(nested_group) + + expect(first_row.text).to include(user1.name) + expect(second_row).to be_blank + end + + scenario 'update user to owner level', :js do + group.add_owner(user1) + group.add_developer(user2) + + visit group_group_members_path(group) + + page.within(second_row) do + click_button('Developer') + click_link('Owner') + + expect(page).to have_button('Owner') + end + end + + scenario 'add user to group', :js do + group.add_owner(user1) + + visit group_group_members_path(group) + + add_user(user2.id, 'Reporter') + + page.within(second_row) do + expect(page).to have_content(user2.name) + expect(page).to have_button('Reporter') + end + end + + scenario 'add yourself to group when already an owner', :js do + group.add_owner(user1) + + visit group_group_members_path(group) + + add_user(user1.id, 'Reporter') + + page.within(first_row) do + expect(page).to have_content(user1.name) + expect(page).to have_content('Owner') + end + end + + scenario 'invite user to group', :js do + group.add_owner(user1) + + visit group_group_members_path(group) + + add_user('test@example.com', 'Reporter') + + page.within(second_row) do + expect(page).to have_content('test@example.com') + expect(page).to have_content('Invited') + expect(page).to have_button('Reporter') + end + end + + def first_row + page.all('ul.content-list > li')[0] + end + + def second_row + page.all('ul.content-list > li')[1] + end + + def add_user(id, role) + page.within ".users-group-form" do + select2(id, from: "#user_ids", multiple: true) + select(role, from: "access_level") + end + + click_button "Add to group" + end +end diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/list_spec.rb deleted file mode 100644 index 5d00ed30c83..00000000000 --- a/spec/features/groups/members/list_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'spec_helper' - -feature 'Groups members list', feature: true do - include Select2Helper - - let(:user1) { create(:user, name: 'John Doe') } - let(:user2) { create(:user, name: 'Mary Jane') } - let(:group) { create(:group) } - let(:nested_group) { create(:group, parent: group) } - - background do - gitlab_sign_in(user1) - end - - scenario 'show members from current group and parent', :nested_groups do - group.add_developer(user1) - nested_group.add_developer(user2) - - visit group_group_members_path(nested_group) - - expect(first_row.text).to include(user1.name) - expect(second_row.text).to include(user2.name) - end - - scenario 'show user once if member of both current group and parent', :nested_groups do - group.add_developer(user1) - nested_group.add_developer(user1) - - visit group_group_members_path(nested_group) - - expect(first_row.text).to include(user1.name) - expect(second_row).to be_blank - end - - scenario 'update user to owner level', :js do - group.add_owner(user1) - group.add_developer(user2) - - visit group_group_members_path(group) - - page.within(second_row) do - click_button('Developer') - click_link('Owner') - - expect(page).to have_button('Owner') - end - end - - scenario 'add user to group', :js do - group.add_owner(user1) - - visit group_group_members_path(group) - - add_user(user2.id, 'Reporter') - - page.within(second_row) do - expect(page).to have_content(user2.name) - expect(page).to have_button('Reporter') - end - end - - scenario 'add yourself to group when already an owner', :js do - group.add_owner(user1) - - visit group_group_members_path(group) - - add_user(user1.id, 'Reporter') - - page.within(first_row) do - expect(page).to have_content(user1.name) - expect(page).to have_content('Owner') - end - end - - scenario 'invite user to group', :js do - group.add_owner(user1) - - visit group_group_members_path(group) - - add_user('test@example.com', 'Reporter') - - page.within(second_row) do - expect(page).to have_content('test@example.com') - expect(page).to have_content('Invited') - expect(page).to have_button('Reporter') - end - end - - def first_row - page.all('ul.content-list > li')[0] - end - - def second_row - page.all('ul.content-list > li')[1] - end - - def add_user(id, role) - page.within ".users-group-form" do - select2(id, from: "#user_ids", multiple: true) - select(role, from: "access_level") - end - - click_button "Add to group" - end -end diff --git a/spec/features/groups/members/manage_access_requests_spec.rb b/spec/features/groups/members/manage_access_requests_spec.rb new file mode 100644 index 00000000000..f84d8594c65 --- /dev/null +++ b/spec/features/groups/members/manage_access_requests_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +feature 'Groups > Members > Manage access requests', feature: true do + let(:user) { create(:user) } + let(:owner) { create(:user) } + let(:group) { create(:group, :public, :access_requestable) } + + background do + group.request_access(user) + group.add_owner(owner) + gitlab_sign_in(owner) + end + + scenario 'owner can see access requests' do + visit group_group_members_path(group) + + expect_visible_access_request(group, user) + end + + scenario 'owner can grant access' do + visit group_group_members_path(group) + + expect_visible_access_request(group, user) + + perform_enqueued_jobs { click_on 'Grant access' } + + expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] + expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was granted" + end + + scenario 'owner can deny access' do + visit group_group_members_path(group) + + expect_visible_access_request(group, user) + + perform_enqueued_jobs { click_on 'Deny access' } + + expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] + expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was denied" + end + + def expect_visible_access_request(group, user) + expect(group.requesters.exists?(user_id: user)).to be_truthy + expect(page).to have_content "Users requesting access to #{group.name} 1" + expect(page).to have_content user.name + end +end diff --git a/spec/features/groups/members/manage_members.rb b/spec/features/groups/members/manage_members.rb new file mode 100644 index 00000000000..ff0a09709e5 --- /dev/null +++ b/spec/features/groups/members/manage_members.rb @@ -0,0 +1,4 @@ +require 'spec_helper' + +feature 'Groups > Members > Manage members', feature: true do +end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb deleted file mode 100644 index db656d244c0..00000000000 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Owner manages access requests', feature: true do - let(:user) { create(:user) } - let(:owner) { create(:user) } - let(:group) { create(:group, :public, :access_requestable) } - - background do - group.request_access(user) - group.add_owner(owner) - gitlab_sign_in(owner) - end - - scenario 'owner can see access requests' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - end - - scenario 'owner can grant access' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - - perform_enqueued_jobs { click_on 'Grant access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was granted" - end - - scenario 'owner can deny access' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - - perform_enqueued_jobs { click_on 'Deny access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was denied" - end - - def expect_visible_access_request(group, user) - expect(group.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content "Users requesting access to #{group.name} 1" - expect(page).to have_content user.name - end -end diff --git a/spec/features/groups/members/request_access_spec.rb b/spec/features/groups/members/request_access_spec.rb new file mode 100644 index 00000000000..41c31b62e18 --- /dev/null +++ b/spec/features/groups/members/request_access_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +feature 'Groups > Members > Request access', feature: true do + let(:user) { create(:user) } + let(:owner) { create(:user) } + let(:group) { create(:group, :public, :access_requestable) } + let!(:project) { create(:project, :private, namespace: group) } + + background do + group.add_owner(owner) + gitlab_sign_in(user) + visit group_path(group) + end + + scenario 'request access feature is disabled' do + group.update_attributes(request_access_enabled: false) + visit group_path(group) + + expect(page).not_to have_content 'Request Access' + end + + scenario 'user can request access to a group' do + perform_enqueued_jobs { click_link 'Request Access' } + + expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email] + expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group" + + expect(group.requesters.exists?(user_id: user)).to be_truthy + expect(page).to have_content 'Your request for access has been queued for review.' + + expect(page).to have_content 'Withdraw Access Request' + expect(page).not_to have_content 'Leave group' + end + + scenario 'user does not see private projects' do + perform_enqueued_jobs { click_link 'Request Access' } + + expect(page).not_to have_content project.name + end + + scenario 'user does not see group in the Dashboard > Groups page' do + perform_enqueued_jobs { click_link 'Request Access' } + + visit dashboard_groups_path + + expect(page).not_to have_content group.name + end + + scenario 'user is not listed in the group members page' do + click_link 'Request Access' + + expect(group.requesters.exists?(user_id: user)).to be_truthy + + click_link 'Members' + + page.within('.content') do + expect(page).not_to have_content(user.name) + end + end + + scenario 'user can withdraw its request for access' do + click_link 'Request Access' + + expect(group.requesters.exists?(user_id: user)).to be_truthy + + click_link 'Withdraw Access Request' + + expect(group.requesters.exists?(user_id: user)).to be_falsey + expect(page).to have_content 'Your access request to the group has been withdrawn.' + end + + scenario 'member does not see the request access button' do + group.add_owner(user) + visit group_path(group) + + expect(page).not_to have_content 'Request Access' + end +end diff --git a/spec/features/groups/members/requests_access_spec.rb b/spec/features/groups/members/requests_access_spec.rb deleted file mode 100644 index eaf9d163896..00000000000 --- a/spec/features/groups/members/requests_access_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Requests access', feature: true do - let(:user) { create(:user) } - let(:owner) { create(:user) } - let(:group) { create(:group, :public, :access_requestable) } - let!(:project) { create(:project, :private, namespace: group) } - - background do - group.add_owner(owner) - gitlab_sign_in(user) - visit group_path(group) - end - - scenario 'request access feature is disabled' do - group.update_attributes(request_access_enabled: false) - visit group_path(group) - - expect(page).not_to have_content 'Request Access' - end - - scenario 'user can request access to a group' do - perform_enqueued_jobs { click_link 'Request Access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group" - - expect(group.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content 'Your request for access has been queued for review.' - - expect(page).to have_content 'Withdraw Access Request' - expect(page).not_to have_content 'Leave group' - end - - scenario 'user does not see private projects' do - perform_enqueued_jobs { click_link 'Request Access' } - - expect(page).not_to have_content project.name - end - - scenario 'user does not see group in the Dashboard > Groups page' do - perform_enqueued_jobs { click_link 'Request Access' } - - visit dashboard_groups_path - - expect(page).not_to have_content group.name - end - - scenario 'user is not listed in the group members page' do - click_link 'Request Access' - - expect(group.requesters.exists?(user_id: user)).to be_truthy - - click_link 'Members' - - page.within('.content') do - expect(page).not_to have_content(user.name) - end - end - - scenario 'user can withdraw its request for access' do - click_link 'Request Access' - - expect(group.requesters.exists?(user_id: user)).to be_truthy - - click_link 'Withdraw Access Request' - - expect(group.requesters.exists?(user_id: user)).to be_falsey - expect(page).to have_content 'Your access request to the group has been withdrawn.' - end - - scenario 'member does not see the request access button' do - group.add_owner(user) - visit group_path(group) - - expect(page).not_to have_content 'Request Access' - end -end diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb new file mode 100644 index 00000000000..8ee61953844 --- /dev/null +++ b/spec/features/groups/members/sort_members_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +feature 'Groups > Members > Sort members', feature: true do + let(:owner) { create(:user, name: 'John Doe') } + let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) } + let(:group) { create(:group) } + + background do + create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago) + create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago) + + gitlab_sign_in(owner) + end + + scenario 'sorts alphabetically by default' do + visit_members_list(sort: nil) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + scenario 'sorts by access level ascending' do + visit_members_list(sort: :access_level_asc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + end + + scenario 'sorts by access level descending' do + visit_members_list(sort: :access_level_desc) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + end + + scenario 'sorts by last joined' do + visit_members_list(sort: :last_joined) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + end + + scenario 'sorts by oldest joined' do + visit_members_list(sort: :oldest_joined) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + end + + scenario 'sorts by name ascending' do + visit_members_list(sort: :name_asc) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + scenario 'sorts by name descending' do + visit_members_list(sort: :name_desc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + end + + scenario 'sorts by recent sign in', :redis do + visit_members_list(sort: :recent_sign_in) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + end + + scenario 'sorts by oldest sign in', :redis do + visit_members_list(sort: :oldest_sign_in) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + end + + def visit_members_list(sort:) + visit group_group_members_path(group.to_param, sort: sort) + end + + def first_member + page.all('ul.content-list > li').first.text + end + + def second_member + page.all('ul.content-list > li').last.text + end +end diff --git a/spec/features/groups/members/sorting_spec.rb b/spec/features/groups/members/sorting_spec.rb deleted file mode 100644 index 719fa0b40b8..00000000000 --- a/spec/features/groups/members/sorting_spec.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Sorting', feature: true do - let(:owner) { create(:user, name: 'John Doe') } - let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) } - let(:group) { create(:group) } - - background do - create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago) - create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago) - - gitlab_sign_in(owner) - end - - scenario 'sorts alphabetically by default' do - visit_members_list(sort: nil) - - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') - end - - scenario 'sorts by access level ascending' do - visit_members_list(sort: :access_level_asc) - - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') - end - - scenario 'sorts by access level descending' do - visit_members_list(sort: :access_level_desc) - - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') - end - - scenario 'sorts by last joined' do - visit_members_list(sort: :last_joined) - - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') - end - - scenario 'sorts by oldest joined' do - visit_members_list(sort: :oldest_joined) - - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') - end - - scenario 'sorts by name ascending' do - visit_members_list(sort: :name_asc) - - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') - end - - scenario 'sorts by name descending' do - visit_members_list(sort: :name_desc) - - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') - end - - scenario 'sorts by recent sign in', :redis do - visit_members_list(sort: :recent_sign_in) - - expect(first_member).to include(owner.name) - expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') - end - - scenario 'sorts by oldest sign in', :redis do - visit_members_list(sort: :oldest_sign_in) - - expect(first_member).to include(developer.name) - expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') - end - - def visit_members_list(sort:) - visit group_group_members_path(group.to_param, sort: sort) - end - - def first_member - page.all('ul.content-list > li').first.text - end - - def second_member - page.all('ul.content-list > li').last.text - end -end -- cgit v1.2.1 From 871bf96c9b231b5c2281a021599959c4f164b479 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 20 Jun 2017 08:50:31 +0000 Subject: Merge branch 'additional-metrics-dashboard' into '28717-additional-metrics-review-branch' Additional metrics dashboard See merge request !11740 --- .../monitoring/components/monitoring.vue | 157 + .../monitoring/components/monitoring_column.vue | 291 ++ .../components/monitoring_deployment.vue | 136 + .../monitoring/components/monitoring_flag.vue | 104 + .../monitoring/components/monitoring_legends.vue | 144 + .../monitoring/components/monitoring_row.vue | 41 + .../monitoring/components/monitoring_state.vue | 112 + app/assets/javascripts/monitoring/deployments.js | 211 - app/assets/javascripts/monitoring/event_hub.js | 3 + .../monitoring/mixins/monitoring_mixins.js | 46 + .../javascripts/monitoring/monitoring_bundle.js | 14 +- .../javascripts/monitoring/prometheus_graph.js | 433 -- .../monitoring/services/monitoring_service.js | 19 + .../monitoring/stores/monitoring_store.js | 61 + .../javascripts/monitoring/utils/measurements.js | 39 + app/assets/stylesheets/pages/environments.scss | 81 +- app/views/projects/environments/metrics.html.haml | 75 +- config/webpack.config.js | 1 + spec/javascripts/monitoring/deployments_spec.js | 133 - spec/javascripts/monitoring/mock_data.js | 4229 ++++++++++++++++++++ .../monitoring/monitoring_column_spec.js | 97 + .../monitoring/monitoring_deployment_spec.js | 137 + .../javascripts/monitoring/monitoring_flag_spec.js | 76 + .../monitoring/monitoring_legends_spec.js | 111 + spec/javascripts/monitoring/monitoring_row_spec.js | 57 + spec/javascripts/monitoring/monitoring_spec.js | 49 + .../monitoring/monitoring_state_spec.js | 110 + .../monitoring/monitoring_store_spec.js | 24 + .../monitoring/prometheus_graph_spec.js | 98 - .../javascripts/monitoring/prometheus_mock_data.js | 1014 ----- 30 files changed, 6119 insertions(+), 1984 deletions(-) create mode 100644 app/assets/javascripts/monitoring/components/monitoring.vue create mode 100644 app/assets/javascripts/monitoring/components/monitoring_column.vue create mode 100644 app/assets/javascripts/monitoring/components/monitoring_deployment.vue create mode 100644 app/assets/javascripts/monitoring/components/monitoring_flag.vue create mode 100644 app/assets/javascripts/monitoring/components/monitoring_legends.vue create mode 100644 app/assets/javascripts/monitoring/components/monitoring_row.vue create mode 100644 app/assets/javascripts/monitoring/components/monitoring_state.vue delete mode 100644 app/assets/javascripts/monitoring/deployments.js create mode 100644 app/assets/javascripts/monitoring/event_hub.js create mode 100644 app/assets/javascripts/monitoring/mixins/monitoring_mixins.js delete mode 100644 app/assets/javascripts/monitoring/prometheus_graph.js create mode 100644 app/assets/javascripts/monitoring/services/monitoring_service.js create mode 100644 app/assets/javascripts/monitoring/stores/monitoring_store.js create mode 100644 app/assets/javascripts/monitoring/utils/measurements.js delete mode 100644 spec/javascripts/monitoring/deployments_spec.js create mode 100644 spec/javascripts/monitoring/mock_data.js create mode 100644 spec/javascripts/monitoring/monitoring_column_spec.js create mode 100644 spec/javascripts/monitoring/monitoring_deployment_spec.js create mode 100644 spec/javascripts/monitoring/monitoring_flag_spec.js create mode 100644 spec/javascripts/monitoring/monitoring_legends_spec.js create mode 100644 spec/javascripts/monitoring/monitoring_row_spec.js create mode 100644 spec/javascripts/monitoring/monitoring_spec.js create mode 100644 spec/javascripts/monitoring/monitoring_state_spec.js create mode 100644 spec/javascripts/monitoring/monitoring_store_spec.js delete mode 100644 spec/javascripts/monitoring/prometheus_graph_spec.js delete mode 100644 spec/javascripts/monitoring/prometheus_mock_data.js diff --git a/app/assets/javascripts/monitoring/components/monitoring.vue b/app/assets/javascripts/monitoring/components/monitoring.vue new file mode 100644 index 00000000000..a6a2d3119e3 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/monitoring.vue @@ -0,0 +1,157 @@ + + diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue new file mode 100644 index 00000000000..4f4792877ee --- /dev/null +++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue @@ -0,0 +1,291 @@ + + diff --git a/app/assets/javascripts/monitoring/components/monitoring_deployment.vue b/app/assets/javascripts/monitoring/components/monitoring_deployment.vue new file mode 100644 index 00000000000..e6432ba3191 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/monitoring_deployment.vue @@ -0,0 +1,136 @@ + + diff --git a/app/assets/javascripts/monitoring/components/monitoring_flag.vue b/app/assets/javascripts/monitoring/components/monitoring_flag.vue new file mode 100644 index 00000000000..180a771415b --- /dev/null +++ b/app/assets/javascripts/monitoring/components/monitoring_flag.vue @@ -0,0 +1,104 @@ + + diff --git a/app/assets/javascripts/monitoring/components/monitoring_legends.vue b/app/assets/javascripts/monitoring/components/monitoring_legends.vue new file mode 100644 index 00000000000..b30ed3cc889 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/monitoring_legends.vue @@ -0,0 +1,144 @@ + + diff --git a/app/assets/javascripts/monitoring/components/monitoring_row.vue b/app/assets/javascripts/monitoring/components/monitoring_row.vue new file mode 100644 index 00000000000..e5528f17880 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/monitoring_row.vue @@ -0,0 +1,41 @@ + + diff --git a/app/assets/javascripts/monitoring/components/monitoring_state.vue b/app/assets/javascripts/monitoring/components/monitoring_state.vue new file mode 100644 index 00000000000..598021aa4df --- /dev/null +++ b/app/assets/javascripts/monitoring/components/monitoring_state.vue @@ -0,0 +1,112 @@ + + diff --git a/app/assets/javascripts/monitoring/deployments.js b/app/assets/javascripts/monitoring/deployments.js deleted file mode 100644 index fc92ab61b31..00000000000 --- a/app/assets/javascripts/monitoring/deployments.js +++ /dev/null @@ -1,211 +0,0 @@ -/* global Flash */ -import d3 from 'd3'; -import { - dateFormat, - timeFormat, -} from './constants'; - -export default class Deployments { - constructor(width, height) { - this.width = width; - this.height = height; - - this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint; - - this.createGradientDef(); - } - - init(chartData) { - this.chartData = chartData; - - this.x = d3.time.scale().range([0, this.width]); - this.x.domain(d3.extent(this.chartData, d => d.time)); - - this.charts = d3.selectAll('.prometheus-graph'); - - this.getData(); - } - - getData() { - $.ajax({ - url: this.endpoint, - dataType: 'JSON', - }) - .fail(() => new Flash('Error getting deployment information.')) - .done((data) => { - this.data = data.deployments.reduce((deploymentDataArray, deployment) => { - const time = new Date(deployment.created_at); - const xPos = Math.floor(this.x(time)); - - time.setSeconds(this.chartData[0].time.getSeconds()); - - if (xPos >= 0) { - deploymentDataArray.push({ - id: deployment.id, - time, - sha: deployment.sha, - tag: deployment.tag, - ref: deployment.ref.name, - xPos, - }); - } - - return deploymentDataArray; - }, []); - - this.plotData(); - }); - } - - plotData() { - this.charts.each((d, i) => { - const svg = d3.select(this.charts[0][i]); - const chart = svg.select('.graph-container'); - const key = svg.node().getAttribute('graph-type'); - - this.createLine(chart, key); - this.createDeployInfoBox(chart, key); - }); - } - - createGradientDef() { - const defs = d3.select('body') - .append('svg') - .attr({ - height: 0, - width: 0, - }) - .append('defs'); - - defs.append('linearGradient') - .attr({ - id: 'shadow-gradient', - }) - .append('stop') - .attr({ - offset: '0%', - 'stop-color': '#000', - 'stop-opacity': 0.4, - }) - .select(this.selectParentNode) - .append('stop') - .attr({ - offset: '100%', - 'stop-color': '#000', - 'stop-opacity': 0, - }); - } - - createLine(chart, key) { - chart.append('g') - .attr({ - class: 'deploy-info', - }) - .selectAll('.deploy-info') - .data(this.data) - .enter() - .append('g') - .attr({ - class: d => `deploy-info-${d.id}-${key}`, - transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`, - }) - .append('rect') - .attr({ - x: 1, - y: 0, - height: this.height + 1, - width: 3, - fill: 'url(#shadow-gradient)', - }) - .select(this.selectParentNode) - .append('line') - .attr({ - class: 'deployment-line', - x1: 0, - x2: 0, - y1: 0, - y2: this.height + 1, - }); - } - - createDeployInfoBox(chart, key) { - chart.selectAll('.deploy-info') - .selectAll('.js-deploy-info-box') - .data(this.data) - .enter() - .select(d => document.querySelector(`.deploy-info-${d.id}-${key}`)) - .append('svg') - .attr({ - class: 'js-deploy-info-box hidden', - x: 3, - y: 0, - width: 92, - height: 60, - }) - .append('rect') - .attr({ - class: 'rect-text-metric deploy-info-rect rect-metric', - x: 1, - y: 1, - rx: 2, - width: 90, - height: 58, - }) - .select(this.selectParentNode) - .append('g') - .attr({ - transform: 'translate(5, 2)', - }) - .append('text') - .attr({ - class: 'deploy-info-text text-metric-bold', - }) - .text(Deployments.refText) - .select(this.selectParentNode) - .append('text') - .attr({ - class: 'deploy-info-text', - y: 18, - }) - .text(d => dateFormat(d.time)) - .select(this.selectParentNode) - .append('text') - .attr({ - class: 'deploy-info-text text-metric-bold', - y: 38, - }) - .text(d => timeFormat(d.time)); - } - - static toggleDeployTextbox(deploy, key, showInfoBox) { - d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`) - .classed('hidden', !showInfoBox); - } - - mouseOverDeployInfo(mouseXPos, key) { - if (!this.data) return false; - - let dataFound = false; - - this.data.forEach((d) => { - if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) { - dataFound = d.xPos + 1; - - Deployments.toggleDeployTextbox(d, key, true); - } else { - Deployments.toggleDeployTextbox(d, key, false); - } - }); - - return dataFound; - } - - /* `this` is bound to the D3 node */ - selectParentNode() { - return this.parentNode; - } - - static refText(d) { - return d.tag ? d.ref : d.sha.slice(0, 6); - } -} diff --git a/app/assets/javascripts/monitoring/event_hub.js b/app/assets/javascripts/monitoring/event_hub.js new file mode 100644 index 00000000000..0948c2e5352 --- /dev/null +++ b/app/assets/javascripts/monitoring/event_hub.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js new file mode 100644 index 00000000000..8e62fa63f13 --- /dev/null +++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js @@ -0,0 +1,46 @@ +const mixins = { + methods: { + mouseOverDeployInfo(mouseXPos) { + if (!this.reducedDeploymentData) return false; + + let dataFound = false; + this.reducedDeploymentData = this.reducedDeploymentData.map((d) => { + const deployment = d; + if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) { + dataFound = d.xPos + 1; + + deployment.showDeploymentFlag = true; + } else { + deployment.showDeploymentFlag = false; + } + return deployment; + }); + + return dataFound; + }, + formatDeployments() { + this.reducedDeploymentData = this.deploymentData.reduce((deploymentDataArray, deployment) => { + const time = new Date(deployment.created_at); + const xPos = Math.floor(this.xScale(time)); + + time.setSeconds(this.data[0].time.getSeconds()); + + if (xPos >= 0) { + deploymentDataArray.push({ + id: deployment.id, + time, + sha: deployment.sha, + tag: deployment.tag, + ref: deployment.ref.name, + xPos, + showDeploymentFlag: false, + }); + } + + return deploymentDataArray; + }, []); + }, + }, +}; + +export default mixins; diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index b3ce9310417..5d5cb56af72 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -1,6 +1,10 @@ -import PrometheusGraph from './prometheus_graph'; +import Vue from 'vue'; +import Monitoring from './components/monitoring.vue'; -document.addEventListener('DOMContentLoaded', function onLoad() { - document.removeEventListener('DOMContentLoaded', onLoad, false); - return new PrometheusGraph(); -}, false); +document.addEventListener('DOMContentLoaded', () => new Vue({ + el: '#prometheus-graphs', + components: { + 'monitoring-dashboard': Monitoring, + }, + render: createElement => createElement('monitoring-dashboard'), +})); diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js deleted file mode 100644 index 6af88769129..00000000000 --- a/app/assets/javascripts/monitoring/prometheus_graph.js +++ /dev/null @@ -1,433 +0,0 @@ -/* eslint-disable no-new */ -/* global Flash */ - -import d3 from 'd3'; -import statusCodes from '~/lib/utils/http_status'; -import Deployments from './deployments'; -import '../lib/utils/common_utils'; -import { formatRelevantDigits } from '../lib/utils/number_utils'; -import '../flash'; -import { - dateFormat, - timeFormat, -} from './constants'; - -const prometheusContainer = '.prometheus-container'; -const prometheusParentGraphContainer = '.prometheus-graphs'; -const prometheusGraphsContainer = '.prometheus-graph'; -const prometheusStatesContainer = '.prometheus-state'; -const metricsEndpoint = 'metrics.json'; -const bisectDate = d3.bisector(d => d.time).left; -const extraAddedWidthParent = 100; - -class PrometheusGraph { - constructor() { - const $prometheusContainer = $(prometheusContainer); - const hasMetrics = $prometheusContainer.data('has-metrics'); - this.docLink = $prometheusContainer.data('doc-link'); - this.integrationLink = $prometheusContainer.data('prometheus-integration'); - this.state = ''; - - $(document).ajaxError(() => {}); - - if (hasMetrics) { - this.margin = { top: 80, right: 180, bottom: 80, left: 100 }; - this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 }; - const parentContainerWidth = $(prometheusGraphsContainer).parent().width() + - extraAddedWidthParent; - this.originalWidth = parentContainerWidth; - this.originalHeight = 330; - this.width = parentContainerWidth - this.margin.left - this.margin.right; - this.height = this.originalHeight - this.margin.top - this.margin.bottom; - this.backOffRequestCounter = 0; - this.deployments = new Deployments(this.width, this.height); - this.configureGraph(); - this.init(); - } else { - const prevState = this.state; - this.state = '.js-getting-started'; - this.updateState(prevState); - } - } - - createGraph() { - Object.keys(this.graphSpecificProperties).forEach((key) => { - const value = this.graphSpecificProperties[key]; - if (value.data.length > 0) { - this.plotValues(key); - } - }); - } - - init() { - return this.getData().then((metricsResponse) => { - let enoughData = true; - if (typeof metricsResponse === 'undefined') { - enoughData = false; - } else { - Object.keys(metricsResponse.metrics).forEach((key) => { - if (key === 'cpu_values' || key === 'memory_values') { - const currentData = (metricsResponse.metrics[key])[0]; - if (currentData.values.length <= 2) { - enoughData = false; - } - } - }); - } - if (enoughData) { - $(prometheusStatesContainer).hide(); - $(prometheusParentGraphContainer).show(); - this.transformData(metricsResponse); - this.createGraph(); - - const firstMetricData = this.graphSpecificProperties[ - Object.keys(this.graphSpecificProperties)[0] - ].data; - - this.deployments.init(firstMetricData); - } - }); - } - - plotValues(key) { - const graphSpecifics = this.graphSpecificProperties[key]; - - const x = d3.time.scale() - .range([0, this.width]); - - const y = d3.scale.linear() - .range([this.height, 0]); - - graphSpecifics.xScale = x; - graphSpecifics.yScale = y; - - const prometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`; - - const chart = d3.select(prometheusGraphContainer) - .attr('width', this.width + this.margin.left + this.margin.right) - .attr('height', this.height + this.margin.bottom + this.margin.top) - .append('g') - .attr('class', 'graph-container') - .attr('transform', `translate(${this.margin.left},${this.margin.top})`); - - const axisLabelContainer = d3.select(prometheusGraphContainer) - .attr('width', this.originalWidth) - .attr('height', this.originalHeight) - .append('g') - .attr('transform', `translate(${this.marginLabelContainer.left},${this.marginLabelContainer.top})`); - - x.domain(d3.extent(graphSpecifics.data, d => d.time)); - y.domain([0, d3.max(graphSpecifics.data.map(metricValue => metricValue.value))]); - - const xAxis = d3.svg.axis() - .scale(x) - .ticks(this.commonGraphProperties.axis_no_ticks) - .orient('bottom'); - - const yAxis = d3.svg.axis() - .scale(y) - .ticks(this.commonGraphProperties.axis_no_ticks) - .tickSize(-this.width) - .outerTickSize(0) - .orient('left'); - - this.createAxisLabelContainers(axisLabelContainer, key); - - chart.append('g') - .attr('class', 'x-axis') - .attr('transform', `translate(0,${this.height})`) - .call(xAxis); - - chart.append('g') - .attr('class', 'y-axis') - .call(yAxis); - - const area = d3.svg.area() - .x(d => x(d.time)) - .y0(this.height) - .y1(d => y(d.value)) - .interpolate('linear'); - - const line = d3.svg.line() - .x(d => x(d.time)) - .y(d => y(d.value)); - - chart.append('path') - .datum(graphSpecifics.data) - .attr('d', area) - .attr('class', 'metric-area') - .attr('fill', graphSpecifics.area_fill_color); - - chart.append('path') - .datum(graphSpecifics.data) - .attr('class', 'metric-line') - .attr('stroke', graphSpecifics.line_color) - .attr('fill', 'none') - .attr('stroke-width', this.commonGraphProperties.area_stroke_width) - .attr('d', line); - - // Overlay area for the mouseover events - chart.append('rect') - .attr('class', 'prometheus-graph-overlay') - .attr('width', this.width) - .attr('height', this.height) - .on('mousemove', this.handleMouseOverGraph.bind(this, prometheusGraphContainer)); - } - - // The legends from the metric - createAxisLabelContainers(axisLabelContainer, key) { - const graphSpecifics = this.graphSpecificProperties[key]; - - axisLabelContainer.append('line') - .attr('class', 'label-x-axis-line') - .attr('stroke', '#000000') - .attr('stroke-width', '1') - .attr({ - x1: 10, - y1: this.originalHeight - this.margin.top, - x2: (this.originalWidth - this.margin.right) + 10, - y2: this.originalHeight - this.margin.top, - }); - - axisLabelContainer.append('line') - .attr('class', 'label-y-axis-line') - .attr('stroke', '#000000') - .attr('stroke-width', '1') - .attr({ - x1: 10, - y1: 0, - x2: 10, - y2: this.originalHeight - this.margin.top, - }); - - axisLabelContainer.append('rect') - .attr('class', 'rect-axis-text') - .attr('x', 0) - .attr('y', 50) - .attr('width', 30) - .attr('height', 150); - - axisLabelContainer.append('text') - .attr('class', 'label-axis-text') - .attr('text-anchor', 'middle') - .attr('transform', `translate(15, ${(this.originalHeight - this.margin.top) / 2}) rotate(-90)`) - .text(graphSpecifics.graph_legend_title); - - axisLabelContainer.append('rect') - .attr('class', 'rect-axis-text') - .attr('x', (this.originalWidth / 2) - this.margin.right) - .attr('y', this.originalHeight - 100) - .attr('width', 30) - .attr('height', 80); - - axisLabelContainer.append('text') - .attr('class', 'label-axis-text') - .attr('x', (this.originalWidth / 2) - this.margin.right) - .attr('y', this.originalHeight - this.margin.top) - .attr('dy', '.35em') - .text('Time'); - - // Legends - - // Metric Usage - axisLabelContainer.append('rect') - .attr('x', this.originalWidth - 170) - .attr('y', (this.originalHeight / 2) - 60) - .style('fill', graphSpecifics.area_fill_color) - .attr('width', 20) - .attr('height', 35); - - axisLabelContainer.append('text') - .attr('class', 'text-metric-title') - .attr('x', this.originalWidth - 140) - .attr('y', (this.originalHeight / 2) - 50) - .text('Average'); - - axisLabelContainer.append('text') - .attr('class', 'text-metric-usage') - .attr('x', this.originalWidth - 140) - .attr('y', (this.originalHeight / 2) - 25); - } - - handleMouseOverGraph(prometheusGraphContainer) { - const rectOverlay = document.querySelector(`${prometheusGraphContainer} .prometheus-graph-overlay`); - const currentXCoordinate = d3.mouse(rectOverlay)[0]; - - Object.keys(this.graphSpecificProperties).forEach((key) => { - const currentGraphProps = this.graphSpecificProperties[key]; - const timeValueOverlay = currentGraphProps.xScale.invert(currentXCoordinate); - const overlayIndex = bisectDate(currentGraphProps.data, timeValueOverlay, 1); - const d0 = currentGraphProps.data[overlayIndex - 1]; - const d1 = currentGraphProps.data[overlayIndex]; - const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay; - const currentData = evalTime ? d1 : d0; - const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time)); - const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key); - const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`; - const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value)); - const maxMetricValue = currentGraphProps.yScale(maxValueFromData); - - // Clear up all the pieces of the flag - d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove(); - d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove(); - d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove(); - - const currentChart = d3.select(currentPrometheusGraphContainer).select('g'); - currentChart.append('line') - .attr({ - class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`, - x1: currentTimeCoordinate, - y1: currentGraphProps.yScale(0), - x2: currentTimeCoordinate, - y2: maxMetricValue, - }); - - currentChart.append('circle') - .attr('class', 'circle-metric') - .attr('fill', currentGraphProps.line_color) - .attr('cx', currentDeployXPos || currentTimeCoordinate) - .attr('cy', currentGraphProps.yScale(currentData.value)) - .attr('r', this.commonGraphProperties.circle_radius_metric); - - if (currentDeployXPos) return; - - // The little box with text - const rectTextMetric = currentChart.append('svg') - .attr({ - class: 'rect-text-metric', - x: currentTimeCoordinate, - y: 0, - }); - - rectTextMetric.append('rect') - .attr({ - class: 'rect-metric', - x: 4, - y: 1, - rx: 2, - width: this.commonGraphProperties.rect_text_width, - height: this.commonGraphProperties.rect_text_height, - }); - - rectTextMetric.append('text') - .attr({ - class: 'text-metric text-metric-bold', - x: 8, - y: 35, - }) - .text(timeFormat(currentData.time)); - - rectTextMetric.append('text') - .attr({ - class: 'text-metric-date', - x: 8, - y: 15, - }) - .text(dateFormat(currentData.time)); - - let currentMetricValue = formatRelevantDigits(currentData.value); - if (key === 'cpu_values') { - currentMetricValue = `${currentMetricValue}%`; - } else { - currentMetricValue = `${currentMetricValue} MB`; - } - - d3.select(`${currentPrometheusGraphContainer} .text-metric-usage`) - .text(currentMetricValue); - }); - } - - configureGraph() { - this.graphSpecificProperties = { - cpu_values: { - area_fill_color: '#edf3fc', - line_color: '#5b99f7', - graph_legend_title: 'CPU Usage (Cores)', - data: [], - xScale: {}, - yScale: {}, - }, - memory_values: { - area_fill_color: '#fca326', - line_color: '#fc6d26', - graph_legend_title: 'Memory Usage (MB)', - data: [], - xScale: {}, - yScale: {}, - }, - }; - - this.commonGraphProperties = { - area_stroke_width: 2, - median_total_characters: 8, - circle_radius_metric: 5, - rect_text_width: 90, - rect_text_height: 40, - axis_no_ticks: 3, - }; - } - - getData() { - const maxNumberOfRequests = 3; - this.state = '.js-loading'; - this.updateState(); - return gl.utils.backOff((next, stop) => { - $.ajax({ - url: metricsEndpoint, - dataType: 'json', - }) - .done((data, statusText, resp) => { - if (resp.status === statusCodes.NO_CONTENT) { - this.backOffRequestCounter = this.backOffRequestCounter += 1; - if (this.backOffRequestCounter < maxNumberOfRequests) { - next(); - } else if (this.backOffRequestCounter >= maxNumberOfRequests) { - stop(new Error('loading')); - } - } else if (!data.success) { - stop(new Error('loading')); - } else { - stop({ - status: resp.status, - metrics: data, - }); - } - }).fail(stop); - }) - .then((resp) => { - if (resp.status === statusCodes.NO_CONTENT) { - return {}; - } - return resp.metrics; - }) - .catch(() => { - const prevState = this.state; - this.state = '.js-unable-to-connect'; - this.updateState(prevState); - }); - } - - transformData(metricsResponse) { - Object.keys(metricsResponse.metrics).forEach((key) => { - if (key === 'cpu_values' || key === 'memory_values') { - const metricValues = (metricsResponse.metrics[key])[0]; - this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({ - time: new Date(metric[0] * 1000), - value: metric[1], - })); - } - }); - } - - updateState(prevState) { - const $statesContainer = $(prometheusStatesContainer); - $(prometheusParentGraphContainer).hide(); - if (prevState) { - $(`${prevState}`, $statesContainer).addClass('hidden'); - } - $(`${this.state}`, $statesContainer).removeClass('hidden'); - $(prometheusStatesContainer).show(); - } -} - -export default PrometheusGraph; diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js new file mode 100644 index 00000000000..1e9ae934853 --- /dev/null +++ b/app/assets/javascripts/monitoring/services/monitoring_service.js @@ -0,0 +1,19 @@ +import Vue from 'vue'; +import VueResource from 'vue-resource'; + +Vue.use(VueResource); + +export default class MonitoringService { + constructor(endpoint) { + this.graphs = Vue.resource(endpoint); + } + + get() { + return this.graphs.get(); + } + + // eslint-disable-next-line class-methods-use-this + getDeploymentData(endpoint) { + return Vue.http.get(endpoint); + } +} diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js new file mode 100644 index 00000000000..737c964f12e --- /dev/null +++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js @@ -0,0 +1,61 @@ +import _ from 'underscore'; + +class MonitoringStore { + constructor() { + this.groups = []; + this.deploymentData = []; + } + + // eslint-disable-next-line class-methods-use-this + createArrayRows(metrics = []) { + const currentMetrics = metrics; + const availableMetrics = []; + let metricsRow = []; + let index = 1; + Object.keys(currentMetrics).forEach((key) => { + const metricValues = currentMetrics[key].queries[0].result[0].values; + if (metricValues != null) { + const literalMetrics = metricValues.map(metric => ({ + time: new Date(metric[0] * 1000), + value: metric[1], + })); + currentMetrics[key].queries[0].result[0].values = literalMetrics; + metricsRow.push(currentMetrics[key]); + if (index % 2 === 0) { + availableMetrics.push(metricsRow); + metricsRow = []; + } + index = index += 1; + } + }); + if (metricsRow.length > 0) { + availableMetrics.push(metricsRow); + } + return availableMetrics; + } + + storeMetrics(groups = []) { + this.groups = groups.map((group) => { + const currentGroup = group; + currentGroup.metrics = _.chain(group.metrics).sortBy('weight').sortBy('title').value(); + currentGroup.metrics = this.createArrayRows(currentGroup.metrics); + return currentGroup; + }); + } + + storeDeploymentData(deploymentData = []) { + this.deploymentData = deploymentData; + } + + getMetricsCount() { + let metricsCount = 0; + this.groups.forEach((group) => { + group.metrics.forEach((metric) => { + metricsCount = metricsCount += metric.length; + }); + }); + return metricsCount; + } +} + +export default MonitoringStore; diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js new file mode 100644 index 00000000000..a60d2522f49 --- /dev/null +++ b/app/assets/javascripts/monitoring/utils/measurements.js @@ -0,0 +1,39 @@ +export default { + small: { // Covers both xs and sm screen sizes + margin: { + top: 40, + right: 40, + bottom: 50, + left: 40, + }, + legends: { + width: 15, + height: 30, + }, + backgroundLegend: { + width: 30, + height: 50, + }, + axisLabelLineOffset: -20, + legendOffset: 52, + }, + large: { // This covers both md and lg screen sizes + margin: { + top: 80, + right: 80, + bottom: 100, + left: 80, + }, + legends: { + width: 20, + height: 35, + }, + backgroundLegend: { + width: 30, + height: 150, + }, + axisLabelLineOffset: 20, + legendOffset: 55, + }, + ticks: 3, +}; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 1046ebfa2e2..a2be957655f 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -140,23 +140,6 @@ } } -.prometheus-graph { - text { - fill: $gl-text-color; - stroke-width: 0; - } - - .label-axis-text, - .text-metric-usage { - fill: $black; - font-weight: 500; - } - - .legend-axis-text { - fill: $black; - } -} - .x-axis path, .y-axis path, .label-x-axis-line, @@ -205,6 +188,7 @@ .text-metric { font-weight: 600; + font-size: 14px; } .selected-metric-line { @@ -214,20 +198,15 @@ .deployment-line { stroke: $black; - stroke-width: 2; + stroke-width: 1; } .deploy-info-text { dominant-baseline: text-before-edge; } -.text-metric-bold { - font-weight: 600; -} - .prometheus-state { margin-top: 10px; - display: none; .state-button-section { margin-top: 10px; @@ -242,3 +221,59 @@ width: 38px; } } + +.prometheus-panel { + margin-top: 20px; +} + +.prometheus-svg-container { + position: relative; + height: 0; + width: 100%; + padding: 0; + padding-bottom: 100%; + + .text-metric-bold { + font-weight: 600; + } +} + +.prometheus-svg-container > svg { + position: absolute; + height: 100%; + width: 100%; + left: 0; + top: 0; + + text { + fill: $gl-text-color; + stroke-width: 0; + } + + .label-axis-text, + .text-metric-usage { + fill: $black; + font-weight: 500; + font-size: 14px; + } + + .legend-axis-text { + fill: $black; + } + + .tick > text { + font-size: 14px; + } + + @media (max-width: $screen-sm-max) { + .label-axis-text, + .text-metric-usage, + .legend-axis-text { + font-size: 8px; + } + + .tick > text { + font-size: 8px; + } + } +} diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index e8f8fbbcf09..c5722cf5997 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -1,11 +1,12 @@ - @no_container = true - page_title "Metrics for environment", @environment.name - content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('common_d3') - = page_specific_javascript_bundle_tag('monitoring') + = webpack_bundle_tag 'common_vue' + = webpack_bundle_tag 'common_d3' + = webpack_bundle_tag 'monitoring' = render "projects/pipelines/head" -#js-metrics.prometheus-container{ class: container_class, data: { has_metrics: "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } } +.prometheus-container{ class: container_class } .top-area .row .col-sm-6 @@ -13,68 +14,8 @@ Environment: = link_to @environment.name, environment_path(@environment) - .prometheus-state - .js-getting-started.hidden - .row - .col-md-4.col-md-offset-4.state-svg - = render "shared/empty_states/monitoring/getting_started.svg" - .row - .col-md-6.col-md-offset-3 - %h4.text-center.state-title - Get started with performance monitoring - .row - .col-md-6.col-md-offset-3 - .description-text.text-center.state-description - Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments. - = link_to help_page_path('administration/monitoring/prometheus/index.md') do - Learn more about performance monitoring - .row.state-button-section - .col-md-4.col-md-offset-4.text-center.state-button - = link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'), class: 'btn btn-success' do - Configure Prometheus - .js-loading.hidden - .row - .col-md-4.col-md-offset-4.state-svg - = render "shared/empty_states/monitoring/loading.svg" - .row - .col-md-6.col-md-offset-3 - %h4.text-center.state-title - Waiting for performance data - .row - .col-md-6.col-md-offset-3 - .description-text.text-center.state-description - Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available. - .row.state-button-section - .col-md-4.col-md-offset-4.text-center.state-button - = link_to help_page_path('administration/monitoring/prometheus/index.md'), class: 'btn btn-success' do - View documentation - .js-unable-to-connect.hidden - .row - .col-md-4.col-md-offset-4.state-svg - = render "shared/empty_states/monitoring/unable_to_connect.svg" - .row - .col-md-6.col-md-offset-3 - %h4.text-center.state-title - Unable to connect to Prometheus server - .row - .col-md-6.col-md-offset-3 - .description-text.text-center.state-description - Ensure connectivity is available from the GitLab server to the - = link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus') do - Prometheus server - .row.state-button-section - .col-md-4.col-md-offset-4.text-center.state-button - = link_to help_page_path('administration/monitoring/prometheus/index.md'), class:'btn btn-success' do - View documentation + #prometheus-graphs{ data: { "settings-path": edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'), + "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'), + "additional-metrics": additional_metrics_namespace_project_environment_path(@project.namespace, @project, @environment, format: :json), + "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } } - .prometheus-graphs - .row - .col-sm-12 - %h4 - CPU utilization - %svg.prometheus-graph{ 'graph-type' => 'cpu_values' } - .row - .col-sm-12 - %h4 - Memory usage - %svg.prometheus-graph{ 'graph-type' => 'memory_values' } diff --git a/config/webpack.config.js b/config/webpack.config.js index 2e8c94655c1..90ef6a5448b 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -163,6 +163,7 @@ var config = { 'issue_show', 'job_details', 'merge_conflicts', + 'monitoring', 'notebook_viewer', 'pdf_viewer', 'pipelines', diff --git a/spec/javascripts/monitoring/deployments_spec.js b/spec/javascripts/monitoring/deployments_spec.js deleted file mode 100644 index 19bc11d0f24..00000000000 --- a/spec/javascripts/monitoring/deployments_spec.js +++ /dev/null @@ -1,133 +0,0 @@ -import d3 from 'd3'; -import PrometheusGraph from '~/monitoring/prometheus_graph'; -import Deployments from '~/monitoring/deployments'; -import { prometheusMockData } from './prometheus_mock_data'; - -describe('Metrics deployments', () => { - const fixtureName = 'environments/metrics/metrics.html.raw'; - let deployment; - let prometheusGraph; - - const graphElement = () => document.querySelector('.prometheus-graph'); - - preloadFixtures(fixtureName); - - beforeEach((done) => { - // Setup the view - loadFixtures(fixtureName); - - d3.selectAll('.prometheus-graph') - .append('g') - .attr('class', 'graph-container'); - - prometheusGraph = new PrometheusGraph(); - deployment = new Deployments(1000, 500); - - spyOn(prometheusGraph, 'init'); - spyOn($, 'ajax').and.callFake(() => { - const d = $.Deferred(); - d.resolve({ - deployments: [{ - id: 1, - created_at: deployment.chartData[10].time, - sha: 'testing', - tag: false, - ref: { - name: 'testing', - }, - }, { - id: 2, - created_at: deployment.chartData[15].time, - sha: '', - tag: true, - ref: { - name: 'tag', - }, - }], - }); - - setTimeout(done); - - return d.promise(); - }); - - prometheusGraph.configureGraph(); - prometheusGraph.transformData(prometheusMockData.metrics); - - deployment.init(prometheusGraph.graphSpecificProperties.memory_values.data); - }); - - it('creates line on graph for deploment', () => { - expect( - graphElement().querySelectorAll('.deployment-line').length, - ).toBe(2); - }); - - it('creates hidden deploy boxes', () => { - expect( - graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length, - ).toBe(2); - }); - - it('hides the info boxes by default', () => { - expect( - graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length, - ).toBe(2); - }); - - it('shows sha short code when tag is false', () => { - expect( - graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box').textContent.trim(), - ).toContain('testin'); - }); - - it('shows ref name when tag is true', () => { - expect( - graphElement().querySelector('.deploy-info-2-cpu_values .js-deploy-info-box').textContent.trim(), - ).toContain('tag'); - }); - - it('shows info box when moving mouse over line', () => { - deployment.mouseOverDeployInfo(deployment.data[0].xPos, 'cpu_values'); - - expect( - graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length, - ).toBe(1); - - expect( - graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'), - ).toBeNull(); - }); - - it('hides previously visible info box when moving mouse away', () => { - deployment.mouseOverDeployInfo(500, 'cpu_values'); - - expect( - graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length, - ).toBe(2); - - expect( - graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'), - ).not.toBeNull(); - }); - - describe('refText', () => { - it('returns shortened SHA', () => { - expect( - Deployments.refText({ - tag: false, - sha: '123456789', - }), - ).toBe('123456'); - }); - - it('returns tag name', () => { - expect( - Deployments.refText({ - tag: true, - ref: 'v1.0', - }), - ).toBe('v1.0'); - }); - }); -}); diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js new file mode 100644 index 00000000000..6f4cb989847 --- /dev/null +++ b/spec/javascripts/monitoring/mock_data.js @@ -0,0 +1,4229 @@ +/* eslint-disable quote-props, indent, comma-dangle */ + +const metricsGroupsAPIResponse = { + 'success': true, + 'data': [ + { + 'group': 'Kubernetes', + 'priority': 1, + 'metrics': [ + { + 'title': 'Memory usage', + 'weight': 1, + 'queries': [ + { + 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20', + 'label': 'Container memory', + 'unit': 'MiB', + 'result': [ + { + 'metric': {}, + 'values': [ + [ + 1495700554.925, + '8.0390625' + ], + [ + 1495700614.925, + '8.0390625' + ], + [ + 1495700674.925, + '8.0390625' + ], + [ + 1495700734.925, + '8.0390625' + ], + [ + 1495700794.925, + '8.0390625' + ], + [ + 1495700854.925, + '8.0390625' + ], + [ + 1495700914.925, + '8.0390625' + ], + [ + 1495700974.925, + '8.0390625' + ], + [ + 1495701034.925, + '8.0390625' + ], + [ + 1495701094.925, + '8.0390625' + ], + [ + 1495701154.925, + '8.0390625' + ], + [ + 1495701214.925, + '8.0390625' + ], + [ + 1495701274.925, + '8.0390625' + ], + [ + 1495701334.925, + '8.0390625' + ], + [ + 1495701394.925, + '8.0390625' + ], + [ + 1495701454.925, + '8.0390625' + ], + [ + 1495701514.925, + '8.0390625' + ], + [ + 1495701574.925, + '8.0390625' + ], + [ + 1495701634.925, + '8.0390625' + ], + [ + 1495701694.925, + '8.0390625' + ], + [ + 1495701754.925, + '8.0390625' + ], + [ + 1495701814.925, + '8.0390625' + ], + [ + 1495701874.925, + '8.0390625' + ], + [ + 1495701934.925, + '8.0390625' + ], + [ + 1495701994.925, + '8.0390625' + ], + [ + 1495702054.925, + '8.0390625' + ], + [ + 1495702114.925, + '8.0390625' + ], + [ + 1495702174.925, + '8.0390625' + ], + [ + 1495702234.925, + '8.0390625' + ], + [ + 1495702294.925, + '8.0390625' + ], + [ + 1495702354.925, + '8.0390625' + ], + [ + 1495702414.925, + '8.0390625' + ], + [ + 1495702474.925, + '8.0390625' + ], + [ + 1495702534.925, + '8.0390625' + ], + [ + 1495702594.925, + '8.0390625' + ], + [ + 1495702654.925, + '8.0390625' + ], + [ + 1495702714.925, + '8.0390625' + ], + [ + 1495702774.925, + '8.0390625' + ], + [ + 1495702834.925, + '8.0390625' + ], + [ + 1495702894.925, + '8.0390625' + ], + [ + 1495702954.925, + '8.0390625' + ], + [ + 1495703014.925, + '8.0390625' + ], + [ + 1495703074.925, + '8.0390625' + ], + [ + 1495703134.925, + '8.0390625' + ], + [ + 1495703194.925, + '8.0390625' + ], + [ + 1495703254.925, + '8.03515625' + ], + [ + 1495703314.925, + '8.03515625' + ], + [ + 1495703374.925, + '8.03515625' + ], + [ + 1495703434.925, + '8.03515625' + ], + [ + 1495703494.925, + '8.03515625' + ], + [ + 1495703554.925, + '8.03515625' + ], + [ + 1495703614.925, + '8.03515625' + ], + [ + 1495703674.925, + '8.03515625' + ], + [ + 1495703734.925, + '8.03515625' + ], + [ + 1495703794.925, + '8.03515625' + ], + [ + 1495703854.925, + '8.03515625' + ], + [ + 1495703914.925, + '8.03515625' + ], + [ + 1495703974.925, + '8.03515625' + ], + [ + 1495704034.925, + '8.03515625' + ], + [ + 1495704094.925, + '8.03515625' + ], + [ + 1495704154.925, + '8.03515625' + ], + [ + 1495704214.925, + '7.9296875' + ], + [ + 1495704274.925, + '7.9296875' + ], + [ + 1495704334.925, + '7.9296875' + ], + [ + 1495704394.925, + '7.9296875' + ], + [ + 1495704454.925, + '7.9296875' + ], + [ + 1495704514.925, + '7.9296875' + ], + [ + 1495704574.925, + '7.9296875' + ], + [ + 1495704634.925, + '7.9296875' + ], + [ + 1495704694.925, + '7.9296875' + ], + [ + 1495704754.925, + '7.9296875' + ], + [ + 1495704814.925, + '7.9296875' + ], + [ + 1495704874.925, + '7.9296875' + ], + [ + 1495704934.925, + '7.9296875' + ], + [ + 1495704994.925, + '7.9296875' + ], + [ + 1495705054.925, + '7.9296875' + ], + [ + 1495705114.925, + '7.9296875' + ], + [ + 1495705174.925, + '7.9296875' + ], + [ + 1495705234.925, + '7.9296875' + ], + [ + 1495705294.925, + '7.9296875' + ], + [ + 1495705354.925, + '7.9296875' + ], + [ + 1495705414.925, + '7.9296875' + ], + [ + 1495705474.925, + '7.9296875' + ], + [ + 1495705534.925, + '7.9296875' + ], + [ + 1495705594.925, + '7.9296875' + ], + [ + 1495705654.925, + '7.9296875' + ], + [ + 1495705714.925, + '7.9296875' + ], + [ + 1495705774.925, + '7.9296875' + ], + [ + 1495705834.925, + '7.9296875' + ], + [ + 1495705894.925, + '7.9296875' + ], + [ + 1495705954.925, + '7.9296875' + ], + [ + 1495706014.925, + '7.9296875' + ], + [ + 1495706074.925, + '7.9296875' + ], + [ + 1495706134.925, + '7.9296875' + ], + [ + 1495706194.925, + '7.9296875' + ], + [ + 1495706254.925, + '7.9296875' + ], + [ + 1495706314.925, + '7.9296875' + ], + [ + 1495706374.925, + '7.9296875' + ], + [ + 1495706434.925, + '7.9296875' + ], + [ + 1495706494.925, + '7.9296875' + ], + [ + 1495706554.925, + '7.9296875' + ], + [ + 1495706614.925, + '7.9296875' + ], + [ + 1495706674.925, + '7.9296875' + ], + [ + 1495706734.925, + '7.9296875' + ], + [ + 1495706794.925, + '7.9296875' + ], + [ + 1495706854.925, + '7.9296875' + ], + [ + 1495706914.925, + '7.9296875' + ], + [ + 1495706974.925, + '7.9296875' + ], + [ + 1495707034.925, + '7.9296875' + ], + [ + 1495707094.925, + '7.9296875' + ], + [ + 1495707154.925, + '7.9296875' + ], + [ + 1495707214.925, + '7.9296875' + ], + [ + 1495707274.925, + '7.9296875' + ], + [ + 1495707334.925, + '7.9296875' + ], + [ + 1495707394.925, + '7.9296875' + ], + [ + 1495707454.925, + '7.9296875' + ], + [ + 1495707514.925, + '7.9296875' + ], + [ + 1495707574.925, + '7.9296875' + ], + [ + 1495707634.925, + '7.9296875' + ], + [ + 1495707694.925, + '7.9296875' + ], + [ + 1495707754.925, + '7.9296875' + ], + [ + 1495707814.925, + '7.9296875' + ], + [ + 1495707874.925, + '7.9296875' + ], + [ + 1495707934.925, + '7.9296875' + ], + [ + 1495707994.925, + '7.9296875' + ], + [ + 1495708054.925, + '7.9296875' + ], + [ + 1495708114.925, + '7.9296875' + ], + [ + 1495708174.925, + '7.9296875' + ], + [ + 1495708234.925, + '7.9296875' + ], + [ + 1495708294.925, + '7.9296875' + ], + [ + 1495708354.925, + '7.9296875' + ], + [ + 1495708414.925, + '7.9296875' + ], + [ + 1495708474.925, + '7.9296875' + ], + [ + 1495708534.925, + '7.9296875' + ], + [ + 1495708594.925, + '7.9296875' + ], + [ + 1495708654.925, + '7.9296875' + ], + [ + 1495708714.925, + '7.9296875' + ], + [ + 1495708774.925, + '7.9296875' + ], + [ + 1495708834.925, + '7.9296875' + ], + [ + 1495708894.925, + '7.9296875' + ], + [ + 1495708954.925, + '7.8984375' + ], + [ + 1495709014.925, + '7.8984375' + ], + [ + 1495709074.925, + '7.8984375' + ], + [ + 1495709134.925, + '7.8984375' + ], + [ + 1495709194.925, + '7.8984375' + ], + [ + 1495709254.925, + '7.89453125' + ], + [ + 1495709314.925, + '7.89453125' + ], + [ + 1495709374.925, + '7.89453125' + ], + [ + 1495709434.925, + '7.89453125' + ], + [ + 1495709494.925, + '7.89453125' + ], + [ + 1495709554.925, + '7.89453125' + ], + [ + 1495709614.925, + '7.89453125' + ], + [ + 1495709674.925, + '7.89453125' + ], + [ + 1495709734.925, + '7.89453125' + ], + [ + 1495709794.925, + '7.89453125' + ], + [ + 1495709854.925, + '7.89453125' + ], + [ + 1495709914.925, + '7.89453125' + ], + [ + 1495709974.925, + '7.89453125' + ], + [ + 1495710034.925, + '7.89453125' + ], + [ + 1495710094.925, + '7.89453125' + ], + [ + 1495710154.925, + '7.89453125' + ], + [ + 1495710214.925, + '7.89453125' + ], + [ + 1495710274.925, + '7.89453125' + ], + [ + 1495710334.925, + '7.89453125' + ], + [ + 1495710394.925, + '7.89453125' + ], + [ + 1495710454.925, + '7.89453125' + ], + [ + 1495710514.925, + '7.89453125' + ], + [ + 1495710574.925, + '7.89453125' + ], + [ + 1495710634.925, + '7.89453125' + ], + [ + 1495710694.925, + '7.89453125' + ], + [ + 1495710754.925, + '7.89453125' + ], + [ + 1495710814.925, + '7.89453125' + ], + [ + 1495710874.925, + '7.89453125' + ], + [ + 1495710934.925, + '7.89453125' + ], + [ + 1495710994.925, + '7.89453125' + ], + [ + 1495711054.925, + '7.89453125' + ], + [ + 1495711114.925, + '7.89453125' + ], + [ + 1495711174.925, + '7.8515625' + ], + [ + 1495711234.925, + '7.8515625' + ], + [ + 1495711294.925, + '7.8515625' + ], + [ + 1495711354.925, + '7.8515625' + ], + [ + 1495711414.925, + '7.8515625' + ], + [ + 1495711474.925, + '7.8515625' + ], + [ + 1495711534.925, + '7.8515625' + ], + [ + 1495711594.925, + '7.8515625' + ], + [ + 1495711654.925, + '7.8515625' + ], + [ + 1495711714.925, + '7.8515625' + ], + [ + 1495711774.925, + '7.8515625' + ], + [ + 1495711834.925, + '7.8515625' + ], + [ + 1495711894.925, + '7.8515625' + ], + [ + 1495711954.925, + '7.8515625' + ], + [ + 1495712014.925, + '7.8515625' + ], + [ + 1495712074.925, + '7.8515625' + ], + [ + 1495712134.925, + '7.8515625' + ], + [ + 1495712194.925, + '7.8515625' + ], + [ + 1495712254.925, + '7.8515625' + ], + [ + 1495712314.925, + '7.8515625' + ], + [ + 1495712374.925, + '7.8515625' + ], + [ + 1495712434.925, + '7.83203125' + ], + [ + 1495712494.925, + '7.83203125' + ], + [ + 1495712554.925, + '7.83203125' + ], + [ + 1495712614.925, + '7.83203125' + ], + [ + 1495712674.925, + '7.83203125' + ], + [ + 1495712734.925, + '7.83203125' + ], + [ + 1495712794.925, + '7.83203125' + ], + [ + 1495712854.925, + '7.83203125' + ], + [ + 1495712914.925, + '7.83203125' + ], + [ + 1495712974.925, + '7.83203125' + ], + [ + 1495713034.925, + '7.83203125' + ], + [ + 1495713094.925, + '7.83203125' + ], + [ + 1495713154.925, + '7.83203125' + ], + [ + 1495713214.925, + '7.83203125' + ], + [ + 1495713274.925, + '7.83203125' + ], + [ + 1495713334.925, + '7.83203125' + ], + [ + 1495713394.925, + '7.8125' + ], + [ + 1495713454.925, + '7.8125' + ], + [ + 1495713514.925, + '7.8125' + ], + [ + 1495713574.925, + '7.8125' + ], + [ + 1495713634.925, + '7.8125' + ], + [ + 1495713694.925, + '7.8125' + ], + [ + 1495713754.925, + '7.8125' + ], + [ + 1495713814.925, + '7.8125' + ], + [ + 1495713874.925, + '7.8125' + ], + [ + 1495713934.925, + '7.8125' + ], + [ + 1495713994.925, + '7.8125' + ], + [ + 1495714054.925, + '7.8125' + ], + [ + 1495714114.925, + '7.8125' + ], + [ + 1495714174.925, + '7.8125' + ], + [ + 1495714234.925, + '7.8125' + ], + [ + 1495714294.925, + '7.8125' + ], + [ + 1495714354.925, + '7.80859375' + ], + [ + 1495714414.925, + '7.80859375' + ], + [ + 1495714474.925, + '7.80859375' + ], + [ + 1495714534.925, + '7.80859375' + ], + [ + 1495714594.925, + '7.80859375' + ], + [ + 1495714654.925, + '7.80859375' + ], + [ + 1495714714.925, + '7.80859375' + ], + [ + 1495714774.925, + '7.80859375' + ], + [ + 1495714834.925, + '7.80859375' + ], + [ + 1495714894.925, + '7.80859375' + ], + [ + 1495714954.925, + '7.80859375' + ], + [ + 1495715014.925, + '7.80859375' + ], + [ + 1495715074.925, + '7.80859375' + ], + [ + 1495715134.925, + '7.80859375' + ], + [ + 1495715194.925, + '7.80859375' + ], + [ + 1495715254.925, + '7.80859375' + ], + [ + 1495715314.925, + '7.80859375' + ], + [ + 1495715374.925, + '7.80859375' + ], + [ + 1495715434.925, + '7.80859375' + ], + [ + 1495715494.925, + '7.80859375' + ], + [ + 1495715554.925, + '7.80859375' + ], + [ + 1495715614.925, + '7.80859375' + ], + [ + 1495715674.925, + '7.80859375' + ], + [ + 1495715734.925, + '7.80859375' + ], + [ + 1495715794.925, + '7.80859375' + ], + [ + 1495715854.925, + '7.80859375' + ], + [ + 1495715914.925, + '7.80078125' + ], + [ + 1495715974.925, + '7.80078125' + ], + [ + 1495716034.925, + '7.80078125' + ], + [ + 1495716094.925, + '7.80078125' + ], + [ + 1495716154.925, + '7.80078125' + ], + [ + 1495716214.925, + '7.796875' + ], + [ + 1495716274.925, + '7.796875' + ], + [ + 1495716334.925, + '7.796875' + ], + [ + 1495716394.925, + '7.796875' + ], + [ + 1495716454.925, + '7.796875' + ], + [ + 1495716514.925, + '7.796875' + ], + [ + 1495716574.925, + '7.796875' + ], + [ + 1495716634.925, + '7.796875' + ], + [ + 1495716694.925, + '7.796875' + ], + [ + 1495716754.925, + '7.796875' + ], + [ + 1495716814.925, + '7.796875' + ], + [ + 1495716874.925, + '7.79296875' + ], + [ + 1495716934.925, + '7.79296875' + ], + [ + 1495716994.925, + '7.79296875' + ], + [ + 1495717054.925, + '7.79296875' + ], + [ + 1495717114.925, + '7.79296875' + ], + [ + 1495717174.925, + '7.7890625' + ], + [ + 1495717234.925, + '7.7890625' + ], + [ + 1495717294.925, + '7.7890625' + ], + [ + 1495717354.925, + '7.7890625' + ], + [ + 1495717414.925, + '7.7890625' + ], + [ + 1495717474.925, + '7.7890625' + ], + [ + 1495717534.925, + '7.7890625' + ], + [ + 1495717594.925, + '7.7890625' + ], + [ + 1495717654.925, + '7.7890625' + ], + [ + 1495717714.925, + '7.7890625' + ], + [ + 1495717774.925, + '7.7890625' + ], + [ + 1495717834.925, + '7.77734375' + ], + [ + 1495717894.925, + '7.77734375' + ], + [ + 1495717954.925, + '7.77734375' + ], + [ + 1495718014.925, + '7.77734375' + ], + [ + 1495718074.925, + '7.77734375' + ], + [ + 1495718134.925, + '7.7421875' + ], + [ + 1495718194.925, + '7.7421875' + ], + [ + 1495718254.925, + '7.7421875' + ], + [ + 1495718314.925, + '7.7421875' + ] + ] + } + ] + } + ] + }, + { + 'title': 'CPU usage', + 'weight': 1, + 'queries': [ + { + 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100', + 'result': [ + { + 'metric': {}, + 'values': [ + [ + 1495700554.925, + '0.0010794445585559514' + ], + [ + 1495700614.925, + '0.003927214935433527' + ], + [ + 1495700674.925, + '0.0053045219047619975' + ], + [ + 1495700734.925, + '0.0048892095238097155' + ], + [ + 1495700794.925, + '0.005827140952381137' + ], + [ + 1495700854.925, + '0.00569846906219937' + ], + [ + 1495700914.925, + '0.004972616802849382' + ], + [ + 1495700974.925, + '0.005117509523809902' + ], + [ + 1495701034.925, + '0.00512389061919564' + ], + [ + 1495701094.925, + '0.005199100501890691' + ], + [ + 1495701154.925, + '0.005415746394885837' + ], + [ + 1495701214.925, + '0.005607682788146286' + ], + [ + 1495701274.925, + '0.005641300000000118' + ], + [ + 1495701334.925, + '0.0071166279368766495' + ], + [ + 1495701394.925, + '0.0063242138095234044' + ], + [ + 1495701454.925, + '0.005793314698235304' + ], + [ + 1495701514.925, + '0.00703934942237556' + ], + [ + 1495701574.925, + '0.006357007076123191' + ], + [ + 1495701634.925, + '0.003753167300126738' + ], + [ + 1495701694.925, + '0.005018469678430698' + ], + [ + 1495701754.925, + '0.0045217153371887' + ], + [ + 1495701814.925, + '0.006140104285714119' + ], + [ + 1495701874.925, + '0.004818684285714102' + ], + [ + 1495701934.925, + '0.005079509718955242' + ], + [ + 1495701994.925, + '0.005059981142498263' + ], + [ + 1495702054.925, + '0.005269098389538773' + ], + [ + 1495702114.925, + '0.005269954285714175' + ], + [ + 1495702174.925, + '0.014199241435795856' + ], + [ + 1495702234.925, + '0.01511936843111017' + ], + [ + 1495702294.925, + '0.0060933692920682875' + ], + [ + 1495702354.925, + '0.004945682380952493' + ], + [ + 1495702414.925, + '0.005641266666666565' + ], + [ + 1495702474.925, + '0.005223752857142996' + ], + [ + 1495702534.925, + '0.005743098505699831' + ], + [ + 1495702594.925, + '0.00538493380952391' + ], + [ + 1495702654.925, + '0.005507793883751339' + ], + [ + 1495702714.925, + '0.005666705714285466' + ], + [ + 1495702774.925, + '0.006231530000000112' + ], + [ + 1495702834.925, + '0.006570768635394899' + ], + [ + 1495702894.925, + '0.005551146666666895' + ], + [ + 1495702954.925, + '0.005602604737098058' + ], + [ + 1495703014.925, + '0.00613993580402159' + ], + [ + 1495703074.925, + '0.004770258764368832' + ], + [ + 1495703134.925, + '0.005512376671364914' + ], + [ + 1495703194.925, + '0.005254436666666674' + ], + [ + 1495703254.925, + '0.0050109839141320505' + ], + [ + 1495703314.925, + '0.0049478019256960016' + ], + [ + 1495703374.925, + '0.0037666860965123463' + ], + [ + 1495703434.925, + '0.004813526061656314' + ], + [ + 1495703494.925, + '0.005047748095238278' + ], + [ + 1495703554.925, + '0.00386494081008772' + ], + [ + 1495703614.925, + '0.004304037408111405' + ], + [ + 1495703674.925, + '0.004999466661587168' + ], + [ + 1495703734.925, + '0.004689140476190834' + ], + [ + 1495703794.925, + '0.004746126153582475' + ], + [ + 1495703854.925, + '0.004482706382572302' + ], + [ + 1495703914.925, + '0.004032808931864524' + ], + [ + 1495703974.925, + '0.005728319047618988' + ], + [ + 1495704034.925, + '0.004436139179627006' + ], + [ + 1495704094.925, + '0.004553455714285617' + ], + [ + 1495704154.925, + '0.003455244285714341' + ], + [ + 1495704214.925, + '0.004742244761904621' + ], + [ + 1495704274.925, + '0.005366978571428422' + ], + [ + 1495704334.925, + '0.004257954837665058' + ], + [ + 1495704394.925, + '0.005431603259831257' + ], + [ + 1495704454.925, + '0.0052009214498621986' + ], + [ + 1495704514.925, + '0.004317201904761618' + ], + [ + 1495704574.925, + '0.004307384285714157' + ], + [ + 1495704634.925, + '0.004789801146644822' + ], + [ + 1495704694.925, + '0.0051429795906706485' + ], + [ + 1495704754.925, + '0.005322495714285479' + ], + [ + 1495704814.925, + '0.004512809333244233' + ], + [ + 1495704874.925, + '0.004953843582568726' + ], + [ + 1495704934.925, + '0.005812690120858119' + ], + [ + 1495704994.925, + '0.004997024285714838' + ], + [ + 1495705054.925, + '0.005246216154439592' + ], + [ + 1495705114.925, + '0.0063494966618726795' + ], + [ + 1495705174.925, + '0.005306004342898225' + ], + [ + 1495705234.925, + '0.005081412857142978' + ], + [ + 1495705294.925, + '0.00511409523809522' + ], + [ + 1495705354.925, + '0.0047861001481192' + ], + [ + 1495705414.925, + '0.005107688228042962' + ], + [ + 1495705474.925, + '0.005271929582294012' + ], + [ + 1495705534.925, + '0.004453254502681249' + ], + [ + 1495705594.925, + '0.005799134293959226' + ], + [ + 1495705654.925, + '0.005340865929502478' + ], + [ + 1495705714.925, + '0.004911654761904942' + ], + [ + 1495705774.925, + '0.005888234873953261' + ], + [ + 1495705834.925, + '0.005565283333332954' + ], + [ + 1495705894.925, + '0.005522869047618869' + ], + [ + 1495705954.925, + '0.005177549737621646' + ], + [ + 1495706014.925, + '0.0053145810232096465' + ], + [ + 1495706074.925, + '0.004751095238095275' + ], + [ + 1495706134.925, + '0.006242077142856976' + ], + [ + 1495706194.925, + '0.00621034406957871' + ], + [ + 1495706254.925, + '0.006887592738978596' + ], + [ + 1495706314.925, + '0.006328128779726213' + ], + [ + 1495706374.925, + '0.007488363809523927' + ], + [ + 1495706434.925, + '0.006193758571428157' + ], + [ + 1495706494.925, + '0.0068798371839706935' + ], + [ + 1495706554.925, + '0.005757034340423128' + ], + [ + 1495706614.925, + '0.004571388497294698' + ], + [ + 1495706674.925, + '0.00620283044923395' + ], + [ + 1495706734.925, + '0.005607562380952455' + ], + [ + 1495706794.925, + '0.005506969933620308' + ], + [ + 1495706854.925, + '0.005621118095238131' + ], + [ + 1495706914.925, + '0.004876606098698849' + ], + [ + 1495706974.925, + '0.0047871205988517206' + ], + [ + 1495707034.925, + '0.00526405939458784' + ], + [ + 1495707094.925, + '0.005716323800605852' + ], + [ + 1495707154.925, + '0.005301459523809575' + ], + [ + 1495707214.925, + '0.0051613042857144905' + ], + [ + 1495707274.925, + '0.005384792857142714' + ], + [ + 1495707334.925, + '0.005259719047619222' + ], + [ + 1495707394.925, + '0.00584101142857182' + ], + [ + 1495707454.925, + '0.0060066121920326326' + ], + [ + 1495707514.925, + '0.006359978571428453' + ], + [ + 1495707574.925, + '0.006315876322151109' + ], + [ + 1495707634.925, + '0.005590012517198831' + ], + [ + 1495707694.925, + '0.005517419877137072' + ], + [ + 1495707754.925, + '0.006089813430348506' + ], + [ + 1495707814.925, + '0.00466754476190479' + ], + [ + 1495707874.925, + '0.006059954380517721' + ], + [ + 1495707934.925, + '0.005085657142856972' + ], + [ + 1495707994.925, + '0.005897665238095296' + ], + [ + 1495708054.925, + '0.0062282023199555885' + ], + [ + 1495708114.925, + '0.00526214553236979' + ], + [ + 1495708174.925, + '0.0044803300000000644' + ], + [ + 1495708234.925, + '0.005421443333333592' + ], + [ + 1495708294.925, + '0.005694326244512144' + ], + [ + 1495708354.925, + '0.005527721904761457' + ], + [ + 1495708414.925, + '0.005988819523809819' + ], + [ + 1495708474.925, + '0.005484704285714448' + ], + [ + 1495708534.925, + '0.005041123649230085' + ], + [ + 1495708594.925, + '0.005717767639612059' + ], + [ + 1495708654.925, + '0.005412954417342863' + ], + [ + 1495708714.925, + '0.005833343333333254' + ], + [ + 1495708774.925, + '0.005448135238094969' + ], + [ + 1495708834.925, + '0.005117341428571432' + ], + [ + 1495708894.925, + '0.005888345825277833' + ], + [ + 1495708954.925, + '0.005398543809524135' + ], + [ + 1495709014.925, + '0.005325611428571416' + ], + [ + 1495709074.925, + '0.005848668571428527' + ], + [ + 1495709134.925, + '0.005135003105145044' + ], + [ + 1495709194.925, + '0.0054551400000003' + ], + [ + 1495709254.925, + '0.005319472937322171' + ], + [ + 1495709314.925, + '0.00585677857142792' + ], + [ + 1495709374.925, + '0.0062146261904759215' + ], + [ + 1495709434.925, + '0.0067105060904182265' + ], + [ + 1495709494.925, + '0.005829691904762108' + ], + [ + 1495709554.925, + '0.005719280952381261' + ], + [ + 1495709614.925, + '0.005682603793416407' + ], + [ + 1495709674.925, + '0.0055272846277326934' + ], + [ + 1495709734.925, + '0.0057123680952386735' + ], + [ + 1495709794.925, + '0.00520597958075818' + ], + [ + 1495709854.925, + '0.005584358957263837' + ], + [ + 1495709914.925, + '0.005601104275197466' + ], + [ + 1495709974.925, + '0.005991657142857066' + ], + [ + 1495710034.925, + '0.00553722238095218' + ], + [ + 1495710094.925, + '0.005127883122696293' + ], + [ + 1495710154.925, + '0.005498111927534584' + ], + [ + 1495710214.925, + '0.005609934069084202' + ], + [ + 1495710274.925, + '0.00459206285714307' + ], + [ + 1495710334.925, + '0.0047910828571428084' + ], + [ + 1495710394.925, + '0.0056014671288845685' + ], + [ + 1495710454.925, + '0.005686936791078528' + ], + [ + 1495710514.925, + '0.00444480476190448' + ], + [ + 1495710574.925, + '0.005780394696738921' + ], + [ + 1495710634.925, + '0.0053107227550210365' + ], + [ + 1495710694.925, + '0.005096031495761817' + ], + [ + 1495710754.925, + '0.005451377979091524' + ], + [ + 1495710814.925, + '0.005328136666667083' + ], + [ + 1495710874.925, + '0.006020612857143043' + ], + [ + 1495710934.925, + '0.0061063585714285365' + ], + [ + 1495710994.925, + '0.006018346015752312' + ], + [ + 1495711054.925, + '0.005069130952381193' + ], + [ + 1495711114.925, + '0.005458406190476052' + ], + [ + 1495711174.925, + '0.00577219190476179' + ], + [ + 1495711234.925, + '0.005760814645658314' + ], + [ + 1495711294.925, + '0.005371875716579101' + ], + [ + 1495711354.925, + '0.0064232666666665834' + ], + [ + 1495711414.925, + '0.009369806836906667' + ], + [ + 1495711474.925, + '0.008956864761904692' + ], + [ + 1495711534.925, + '0.005266849368559271' + ], + [ + 1495711594.925, + '0.005335111364934262' + ], + [ + 1495711654.925, + '0.006461778319586945' + ], + [ + 1495711714.925, + '0.004687939890762393' + ], + [ + 1495711774.925, + '0.004438831245760684' + ], + [ + 1495711834.925, + '0.005142786666666613' + ], + [ + 1495711894.925, + '0.007257734212054963' + ], + [ + 1495711954.925, + '0.005621991904761494' + ], + [ + 1495712014.925, + '0.007868689999999862' + ], + [ + 1495712074.925, + '0.00910970215275738' + ], + [ + 1495712134.925, + '0.006151004285714278' + ], + [ + 1495712194.925, + '0.005447120924961522' + ], + [ + 1495712254.925, + '0.005150705153929503' + ], + [ + 1495712314.925, + '0.006358108714969314' + ], + [ + 1495712374.925, + '0.0057725354795696475' + ], + [ + 1495712434.925, + '0.005232139047619015' + ], + [ + 1495712494.925, + '0.004932809617949037' + ], + [ + 1495712554.925, + '0.004511607508499662' + ], + [ + 1495712614.925, + '0.00440487701522666' + ], + [ + 1495712674.925, + '0.005479113333333174' + ], + [ + 1495712734.925, + '0.004726317619047547' + ], + [ + 1495712794.925, + '0.005582041102958029' + ], + [ + 1495712854.925, + '0.006381481216082099' + ], + [ + 1495712914.925, + '0.005474260014095208' + ], + [ + 1495712974.925, + '0.00567597142857188' + ], + [ + 1495713034.925, + '0.0064741233333332985' + ], + [ + 1495713094.925, + '0.005467475714285271' + ], + [ + 1495713154.925, + '0.004868648393824457' + ], + [ + 1495713214.925, + '0.005254923286444893' + ], + [ + 1495713274.925, + '0.005599217150312865' + ], + [ + 1495713334.925, + '0.005105413720618919' + ], + [ + 1495713394.925, + '0.007246073333333279' + ], + [ + 1495713454.925, + '0.005990312380952272' + ], + [ + 1495713514.925, + '0.005594601853351101' + ], + [ + 1495713574.925, + '0.004739258673727054' + ], + [ + 1495713634.925, + '0.003932121428571783' + ], + [ + 1495713694.925, + '0.005018188268459395' + ], + [ + 1495713754.925, + '0.004538238095237985' + ], + [ + 1495713814.925, + '0.00561816643265435' + ], + [ + 1495713874.925, + '0.0063132584495033586' + ], + [ + 1495713934.925, + '0.00442385238095213' + ], + [ + 1495713994.925, + '0.004181795887658453' + ], + [ + 1495714054.925, + '0.004437759047619037' + ], + [ + 1495714114.925, + '0.006421748157178241' + ], + [ + 1495714174.925, + '0.006525143809523842' + ], + [ + 1495714234.925, + '0.004715904935144247' + ], + [ + 1495714294.925, + '0.005966040152763461' + ], + [ + 1495714354.925, + '0.005614535466921674' + ], + [ + 1495714414.925, + '0.004934375119415906' + ], + [ + 1495714474.925, + '0.0054122933333327385' + ], + [ + 1495714534.925, + '0.004926540699612279' + ], + [ + 1495714594.925, + '0.006124649517134237' + ], + [ + 1495714654.925, + '0.004629427092013995' + ], + [ + 1495714714.925, + '0.005117951257607005' + ], + [ + 1495714774.925, + '0.004868774512685422' + ], + [ + 1495714834.925, + '0.005310093333333399' + ], + [ + 1495714894.925, + '0.0054907752286127345' + ], + [ + 1495714954.925, + '0.004597678117351089' + ], + [ + 1495715014.925, + '0.0059622552380952' + ], + [ + 1495715074.925, + '0.005352457072655368' + ], + [ + 1495715134.925, + '0.005491630952381143' + ], + [ + 1495715194.925, + '0.006391770078379791' + ], + [ + 1495715254.925, + '0.005933472857142518' + ], + [ + 1495715314.925, + '0.005301314285714163' + ], + [ + 1495715374.925, + '0.0058352959724814165' + ], + [ + 1495715434.925, + '0.006154755147867044' + ], + [ + 1495715494.925, + '0.009391935637482038' + ], + [ + 1495715554.925, + '0.007846462857142592' + ], + [ + 1495715614.925, + '0.00477608215316353' + ], + [ + 1495715674.925, + '0.006132865238094998' + ], + [ + 1495715734.925, + '0.006159762457649516' + ], + [ + 1495715794.925, + '0.005957307073265968' + ], + [ + 1495715854.925, + '0.006652319091792501' + ], + [ + 1495715914.925, + '0.005493557402895287' + ], + [ + 1495715974.925, + '0.0058652434829145166' + ], + [ + 1495716034.925, + '0.005627400430468021' + ], + [ + 1495716094.925, + '0.006240656190475609' + ], + [ + 1495716154.925, + '0.006305997676168624' + ], + [ + 1495716214.925, + '0.005388057732783248' + ], + [ + 1495716274.925, + '0.0052814916048421244' + ], + [ + 1495716334.925, + '0.00699498614272497' + ], + [ + 1495716394.925, + '0.00627768693035141' + ], + [ + 1495716454.925, + '0.0042411487048161145' + ], + [ + 1495716514.925, + '0.005348647473627653' + ], + [ + 1495716574.925, + '0.0047176657142853975' + ], + [ + 1495716634.925, + '0.004437898571428686' + ], + [ + 1495716694.925, + '0.004923527366927261' + ], + [ + 1495716754.925, + '0.005131935066048421' + ], + [ + 1495716814.925, + '0.005046949523809611' + ], + [ + 1495716874.925, + '0.00547184095238092' + ], + [ + 1495716934.925, + '0.005224140016380444' + ], + [ + 1495716994.925, + '0.005297991171665292' + ], + [ + 1495717054.925, + '0.005492965995623498' + ], + [ + 1495717114.925, + '0.005754660000000403' + ], + [ + 1495717174.925, + '0.005949557138639285' + ], + [ + 1495717234.925, + '0.006091816112534666' + ], + [ + 1495717294.925, + '0.005554210080192063' + ], + [ + 1495717354.925, + '0.006411504395279871' + ], + [ + 1495717414.925, + '0.006319643996609606' + ], + [ + 1495717474.925, + '0.005539174405717675' + ], + [ + 1495717534.925, + '0.0053157078842772255' + ], + [ + 1495717594.925, + '0.005247480952381066' + ], + [ + 1495717654.925, + '0.004820141620396252' + ], + [ + 1495717714.925, + '0.005906173868322844' + ], + [ + 1495717774.925, + '0.006173117219570961' + ], + [ + 1495717834.925, + '0.005963340952380661' + ], + [ + 1495717894.925, + '0.005698976627681527' + ], + [ + 1495717954.925, + '0.004751279096346378' + ], + [ + 1495718014.925, + '0.005733142379359711' + ], + [ + 1495718074.925, + '0.004831689010348035' + ], + [ + 1495718134.925, + '0.005188370476191092' + ], + [ + 1495718194.925, + '0.004793227554547938' + ], + [ + 1495718254.925, + '0.003997442857142731' + ], + [ + 1495718314.925, + '0.004386040132951264' + ] + ] + } + ] + } + ] + } + ] + } + ], + 'last_update': '2017-05-25T13:18:34.949Z' +}; + +export default metricsGroupsAPIResponse; + +const responseMockData = { + 'GET': { + '/root/hello-prometheus/environments/30/additional_metrics.json': metricsGroupsAPIResponse, + 'http://test.host/frontend-fixtures/environments-project/environments/1/additional_metrics.json': metricsGroupsAPIResponse, // TODO: MAke sure this works in the monitoring_bundle_spec + }, +}; + +export const deploymentData = [ + { + id: 111, + iid: 3, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + ref: { + name: 'master' + }, + created_at: '2017-05-31T21:23:37.881Z', + tag: false, + 'last?': true + }, + { + id: 110, + iid: 2, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + ref: { + name: 'master' + }, + created_at: '2017-05-30T20:08:04.629Z', + tag: false, + 'last?': false + }, + { + id: 109, + iid: 1, + sha: '6511e58faafaa7ad2228990ec57f19d66f7db7c2', + ref: { + name: 'update2-readme' + }, + created_at: '2017-05-30T17:42:38.409Z', + tag: false, + 'last?': false + } +]; + +export const statePaths = { + settingsPath: '/root/hello-prometheus/services/prometheus/edit', + documentationPath: '/help/administration/monitoring/prometheus/index.md', +}; + +export const singleRowMetrics = [ + { + 'title': 'CPU usage', + 'weight': 1, + 'y_label': 'Values', + 'queries': [ + { + 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100', + 'result': [ + { + 'metric': { + + }, + 'values': [ + { + 'time': '2017-06-04T21:22:59.508Z', + 'value': '0.06335544298150002' + }, + { + 'time': '2017-06-04T21:23:59.508Z', + 'value': '0.0420347312480917' + }, + { + 'time': '2017-06-04T21:24:59.508Z', + 'value': '0.0023175131665412706' + }, + { + 'time': '2017-06-04T21:25:59.508Z', + 'value': '0.002315870476190476' + }, + { + 'time': '2017-06-04T21:26:59.508Z', + 'value': '0.0025005961904761894' + }, + { + 'time': '2017-06-04T21:27:59.508Z', + 'value': '0.0024612605834341264' + }, + { + 'time': '2017-06-04T21:28:59.508Z', + 'value': '0.002313129398767631' + }, + { + 'time': '2017-06-04T21:29:59.508Z', + 'value': '0.002411067353663882' + }, + { + 'time': '2017-06-04T21:30:59.508Z', + 'value': '0.002577309263721303' + }, + { + 'time': '2017-06-04T21:31:59.508Z', + 'value': '0.00242688307730403' + }, + { + 'time': '2017-06-04T21:32:59.508Z', + 'value': '0.0024168360301330457' + }, + { + 'time': '2017-06-04T21:33:59.508Z', + 'value': '0.0020449528090743714' + }, + { + 'time': '2017-06-04T21:34:59.508Z', + 'value': '0.0019149619047619036' + }, + { + 'time': '2017-06-04T21:35:59.508Z', + 'value': '0.0024491714364625094' + }, + { + 'time': '2017-06-04T21:36:59.508Z', + 'value': '0.002728773131172677' + }, + { + 'time': '2017-06-04T21:37:59.508Z', + 'value': '0.0028439119047618997' + }, + { + 'time': '2017-06-04T21:38:59.508Z', + 'value': '0.0026307480952380917' + }, + { + 'time': '2017-06-04T21:39:59.508Z', + 'value': '0.0025024842620546446' + }, + { + 'time': '2017-06-04T21:40:59.508Z', + 'value': '0.002300662387260825' + }, + { + 'time': '2017-06-04T21:41:59.508Z', + 'value': '0.002052890924848337' + }, + { + 'time': '2017-06-04T21:42:59.508Z', + 'value': '0.0023711195238095275' + }, + { + 'time': '2017-06-04T21:43:59.508Z', + 'value': '0.002513477619047618' + }, + { + 'time': '2017-06-04T21:44:59.508Z', + 'value': '0.0023489776287844897' + }, + { + 'time': '2017-06-04T21:45:59.508Z', + 'value': '0.002542572310212481' + }, + { + 'time': '2017-06-04T21:46:59.508Z', + 'value': '0.0024579470671707952' + }, + { + 'time': '2017-06-04T21:47:59.508Z', + 'value': '0.0028725150236664403' + }, + { + 'time': '2017-06-04T21:48:59.508Z', + 'value': '0.0024356089105610525' + }, + { + 'time': '2017-06-04T21:49:59.508Z', + 'value': '0.002544015828269929' + }, + { + 'time': '2017-06-04T21:50:59.508Z', + 'value': '0.0029595013380824906' + }, + { + 'time': '2017-06-04T21:51:59.508Z', + 'value': '0.0023084015085858' + }, + { + 'time': '2017-06-04T21:52:59.508Z', + 'value': '0.0021070500000000083' + }, + { + 'time': '2017-06-04T21:53:59.508Z', + 'value': '0.0022950066191106617' + }, + { + 'time': '2017-06-04T21:54:59.508Z', + 'value': '0.002492719454470995' + }, + { + 'time': '2017-06-04T21:55:59.508Z', + 'value': '0.00244312761904762' + }, + { + 'time': '2017-06-04T21:56:59.508Z', + 'value': '0.0023495500000000028' + }, + { + 'time': '2017-06-04T21:57:59.508Z', + 'value': '0.0020597072353070005' + }, + { + 'time': '2017-06-04T21:58:59.508Z', + 'value': '0.0021482352044800866' + }, + { + 'time': '2017-06-04T21:59:59.508Z', + 'value': '0.002333490000000004' + }, + { + 'time': '2017-06-04T22:00:59.508Z', + 'value': '0.0025899442857142815' + }, + { + 'time': '2017-06-04T22:01:59.508Z', + 'value': '0.002430299999999999' + }, + { + 'time': '2017-06-04T22:02:59.508Z', + 'value': '0.0023550328092113476' + }, + { + 'time': '2017-06-04T22:03:59.508Z', + 'value': '0.0026521871636872793' + }, + { + 'time': '2017-06-04T22:04:59.508Z', + 'value': '0.0023080671428571398' + }, + { + 'time': '2017-06-04T22:05:59.508Z', + 'value': '0.0024108401032390896' + }, + { + 'time': '2017-06-04T22:06:59.508Z', + 'value': '0.002433249366678738' + }, + { + 'time': '2017-06-04T22:07:59.508Z', + 'value': '0.0023242202306688682' + }, + { + 'time': '2017-06-04T22:08:59.508Z', + 'value': '0.002388222857142859' + }, + { + 'time': '2017-06-04T22:09:59.508Z', + 'value': '0.002115974914046794' + }, + { + 'time': '2017-06-04T22:10:59.508Z', + 'value': '0.0025090043331269917' + }, + { + 'time': '2017-06-04T22:11:59.508Z', + 'value': '0.002445507057277277' + }, + { + 'time': '2017-06-04T22:12:59.508Z', + 'value': '0.0026348773751130976' + }, + { + 'time': '2017-06-04T22:13:59.508Z', + 'value': '0.0025616258583088104' + }, + { + 'time': '2017-06-04T22:14:59.508Z', + 'value': '0.0021544093415751505' + }, + { + 'time': '2017-06-04T22:15:59.508Z', + 'value': '0.002649394767668881' + }, + { + 'time': '2017-06-04T22:16:59.508Z', + 'value': '0.0024023332666685705' + }, + { + 'time': '2017-06-04T22:17:59.508Z', + 'value': '0.0025444105294235306' + }, + { + 'time': '2017-06-04T22:18:59.508Z', + 'value': '0.0027298872305772806' + }, + { + 'time': '2017-06-04T22:19:59.508Z', + 'value': '0.0022880104956379287' + }, + { + 'time': '2017-06-04T22:20:59.508Z', + 'value': '0.002473246666666661' + }, + { + 'time': '2017-06-04T22:21:59.508Z', + 'value': '0.002259948381935587' + }, + { + 'time': '2017-06-04T22:22:59.508Z', + 'value': '0.0025778470886268835' + }, + { + 'time': '2017-06-04T22:23:59.508Z', + 'value': '0.002246127910852894' + }, + { + 'time': '2017-06-04T22:24:59.508Z', + 'value': '0.0020697466666666758' + }, + { + 'time': '2017-06-04T22:25:59.508Z', + 'value': '0.00225859722473547' + }, + { + 'time': '2017-06-04T22:26:59.508Z', + 'value': '0.0026466728254554814' + }, + { + 'time': '2017-06-04T22:27:59.508Z', + 'value': '0.002151247619047619' + }, + { + 'time': '2017-06-04T22:28:59.508Z', + 'value': '0.002324161444543914' + }, + { + 'time': '2017-06-04T22:29:59.508Z', + 'value': '0.002476474313796452' + }, + { + 'time': '2017-06-04T22:30:59.508Z', + 'value': '0.0023922184232080517' + }, + { + 'time': '2017-06-04T22:31:59.508Z', + 'value': '0.0025094934237468933' + }, + { + 'time': '2017-06-04T22:32:59.508Z', + 'value': '0.0025665311098200883' + }, + { + 'time': '2017-06-04T22:33:59.508Z', + 'value': '0.0024154900681661374' + }, + { + 'time': '2017-06-04T22:34:59.508Z', + 'value': '0.0023267450166192037' + }, + { + 'time': '2017-06-04T22:35:59.508Z', + 'value': '0.002156521904761904' + }, + { + 'time': '2017-06-04T22:36:59.508Z', + 'value': '0.0025474356898637007' + }, + { + 'time': '2017-06-04T22:37:59.508Z', + 'value': '0.0025989409624670233' + }, + { + 'time': '2017-06-04T22:38:59.508Z', + 'value': '0.002348336664762987' + }, + { + 'time': '2017-06-04T22:39:59.508Z', + 'value': '0.002665888246554726' + }, + { + 'time': '2017-06-04T22:40:59.508Z', + 'value': '0.002652684787474174' + }, + { + 'time': '2017-06-04T22:41:59.508Z', + 'value': '0.002472620430865355' + }, + { + 'time': '2017-06-04T22:42:59.508Z', + 'value': '0.0020616469210110247' + }, + { + 'time': '2017-06-04T22:43:59.508Z', + 'value': '0.0022434546372311934' + }, + { + 'time': '2017-06-04T22:44:59.508Z', + 'value': '0.0024469386784827982' + }, + { + 'time': '2017-06-04T22:45:59.508Z', + 'value': '0.0026192823809523787' + }, + { + 'time': '2017-06-04T22:46:59.508Z', + 'value': '0.003451999542852798' + }, + { + 'time': '2017-06-04T22:47:59.508Z', + 'value': '0.0031780314285714288' + }, + { + 'time': '2017-06-04T22:48:59.508Z', + 'value': '0.0024403352380952415' + }, + { + 'time': '2017-06-04T22:49:59.508Z', + 'value': '0.001998824761904764' + }, + { + 'time': '2017-06-04T22:50:59.508Z', + 'value': '0.0023792404761904806' + }, + { + 'time': '2017-06-04T22:51:59.508Z', + 'value': '0.002725906190476185' + }, + { + 'time': '2017-06-04T22:52:59.508Z', + 'value': '0.0020989528671155624' + }, + { + 'time': '2017-06-04T22:53:59.508Z', + 'value': '0.00228808226745016' + }, + { + 'time': '2017-06-04T22:54:59.508Z', + 'value': '0.0019860807413192147' + }, + { + 'time': '2017-06-04T22:55:59.508Z', + 'value': '0.0022698085714285897' + }, + { + 'time': '2017-06-04T22:56:59.508Z', + 'value': '0.0022839098467604415' + }, + { + 'time': '2017-06-04T22:57:59.508Z', + 'value': '0.002531114761904749' + }, + { + 'time': '2017-06-04T22:58:59.508Z', + 'value': '0.0028941072550999016' + }, + { + 'time': '2017-06-04T22:59:59.508Z', + 'value': '0.002547169523809506' + }, + { + 'time': '2017-06-04T23:00:59.508Z', + 'value': '0.0024062999999999958' + }, + { + 'time': '2017-06-04T23:01:59.508Z', + 'value': '0.0026939518471604386' + }, + { + 'time': '2017-06-04T23:02:59.508Z', + 'value': '0.002362901428571429' + }, + { + 'time': '2017-06-04T23:03:59.508Z', + 'value': '0.002663927142857154' + }, + { + 'time': '2017-06-04T23:04:59.508Z', + 'value': '0.0026173314285714354' + }, + { + 'time': '2017-06-04T23:05:59.508Z', + 'value': '0.002326527366406044' + }, + { + 'time': '2017-06-04T23:06:59.508Z', + 'value': '0.002035313809523809' + }, + { + 'time': '2017-06-04T23:07:59.508Z', + 'value': '0.002421447414786533' + }, + { + 'time': '2017-06-04T23:08:59.508Z', + 'value': '0.002898313809523804' + }, + { + 'time': '2017-06-04T23:09:59.508Z', + 'value': '0.002544891856112907' + }, + { + 'time': '2017-06-04T23:10:59.508Z', + 'value': '0.002290625356938882' + }, + { + 'time': '2017-06-04T23:11:59.508Z', + 'value': '0.002483028095238096' + }, + { + 'time': '2017-06-04T23:12:59.508Z', + 'value': '0.0023396832350784237' + }, + { + 'time': '2017-06-04T23:13:59.508Z', + 'value': '0.002085529248176153' + }, + { + 'time': '2017-06-04T23:14:59.508Z', + 'value': '0.0022417815068428012' + }, + { + 'time': '2017-06-04T23:15:59.508Z', + 'value': '0.002660293333333341' + }, + { + 'time': '2017-06-04T23:16:59.508Z', + 'value': '0.0029845149093818226' + }, + { + 'time': '2017-06-04T23:17:59.508Z', + 'value': '0.0027716655079475464' + }, + { + 'time': '2017-06-04T23:18:59.508Z', + 'value': '0.0025217708908741128' + }, + { + 'time': '2017-06-04T23:19:59.508Z', + 'value': '0.0025811235131094055' + }, + { + 'time': '2017-06-04T23:20:59.508Z', + 'value': '0.002209904761904762' + }, + { + 'time': '2017-06-04T23:21:59.508Z', + 'value': '0.0025053322926383344' + }, + { + 'time': '2017-06-04T23:22:59.508Z', + 'value': '0.002350917636526411' + }, + { + 'time': '2017-06-04T23:23:59.508Z', + 'value': '0.0018477500000000078' + }, + { + 'time': '2017-06-04T23:24:59.508Z', + 'value': '0.002427629523809527' + }, + { + 'time': '2017-06-04T23:25:59.508Z', + 'value': '0.0019305498147601655' + }, + { + 'time': '2017-06-04T23:26:59.508Z', + 'value': '0.002097250000000006' + }, + { + 'time': '2017-06-04T23:27:59.508Z', + 'value': '0.002675020952780041' + }, + { + 'time': '2017-06-04T23:28:59.508Z', + 'value': '0.0023142214285714374' + }, + { + 'time': '2017-06-04T23:29:59.508Z', + 'value': '0.0023644723809523737' + }, + { + 'time': '2017-06-04T23:30:59.508Z', + 'value': '0.002108696190476198' + }, + { + 'time': '2017-06-04T23:31:59.508Z', + 'value': '0.0019918289697997194' + }, + { + 'time': '2017-06-04T23:32:59.508Z', + 'value': '0.001583584285714283' + }, + { + 'time': '2017-06-04T23:33:59.508Z', + 'value': '0.002073770226383112' + }, + { + 'time': '2017-06-04T23:34:59.508Z', + 'value': '0.0025877664234966818' + }, + { + 'time': '2017-06-04T23:35:59.508Z', + 'value': '0.0021138238095238147' + }, + { + 'time': '2017-06-04T23:36:59.508Z', + 'value': '0.0022140838095238303' + }, + { + 'time': '2017-06-04T23:37:59.508Z', + 'value': '0.0018592674425248847' + }, + { + 'time': '2017-06-04T23:38:59.508Z', + 'value': '0.0020461969533657016' + }, + { + 'time': '2017-06-04T23:39:59.508Z', + 'value': '0.0021593628571428543' + }, + { + 'time': '2017-06-04T23:40:59.508Z', + 'value': '0.0024330682564928188' + }, + { + 'time': '2017-06-04T23:41:59.508Z', + 'value': '0.0021501804779093174' + }, + { + 'time': '2017-06-04T23:42:59.508Z', + 'value': '0.0025787493928397945' + }, + { + 'time': '2017-06-04T23:43:59.508Z', + 'value': '0.002593657082448396' + }, + { + 'time': '2017-06-04T23:44:59.508Z', + 'value': '0.0021316752380952306' + }, + { + 'time': '2017-06-04T23:45:59.508Z', + 'value': '0.0026972905019952086' + }, + { + 'time': '2017-06-04T23:46:59.508Z', + 'value': '0.002580250764292983' + }, + { + 'time': '2017-06-04T23:47:59.508Z', + 'value': '0.00227103000000001' + }, + { + 'time': '2017-06-04T23:48:59.508Z', + 'value': '0.0023678515647321146' + }, + { + 'time': '2017-06-04T23:49:59.508Z', + 'value': '0.002371472857142866' + }, + { + 'time': '2017-06-04T23:50:59.508Z', + 'value': '0.0026181353688500978' + }, + { + 'time': '2017-06-04T23:51:59.508Z', + 'value': '0.0025609667711121217' + }, + { + 'time': '2017-06-04T23:52:59.508Z', + 'value': '0.0027145308139922557' + }, + { + 'time': '2017-06-04T23:53:59.508Z', + 'value': '0.0024249397613310512' + }, + { + 'time': '2017-06-04T23:54:59.508Z', + 'value': '0.002399907142857147' + }, + { + 'time': '2017-06-04T23:55:59.508Z', + 'value': '0.0024753357142857195' + }, + { + 'time': '2017-06-04T23:56:59.508Z', + 'value': '0.0026179149325231575' + }, + { + 'time': '2017-06-04T23:57:59.508Z', + 'value': '0.0024261340368186956' + }, + { + 'time': '2017-06-04T23:58:59.508Z', + 'value': '0.0021061071428571517' + }, + { + 'time': '2017-06-04T23:59:59.508Z', + 'value': '0.0024033971105037015' + }, + { + 'time': '2017-06-05T00:00:59.508Z', + 'value': '0.0028287676190475956' + }, + { + 'time': '2017-06-05T00:01:59.508Z', + 'value': '0.002499719050294778' + }, + { + 'time': '2017-06-05T00:02:59.508Z', + 'value': '0.0026726102153353856' + }, + { + 'time': '2017-06-05T00:03:59.508Z', + 'value': '0.00262582619047618' + }, + { + 'time': '2017-06-05T00:04:59.508Z', + 'value': '0.002280473147363316' + }, + { + 'time': '2017-06-05T00:05:59.508Z', + 'value': '0.002095581470652675' + }, + { + 'time': '2017-06-05T00:06:59.508Z', + 'value': '0.002270768490828408' + }, + { + 'time': '2017-06-05T00:07:59.508Z', + 'value': '0.002728577415023017' + }, + { + 'time': '2017-06-05T00:08:59.508Z', + 'value': '0.002652512857142863' + }, + { + 'time': '2017-06-05T00:09:59.508Z', + 'value': '0.0022781033924455674' + }, + { + 'time': '2017-06-05T00:10:59.508Z', + 'value': '0.0025345038095238234' + }, + { + 'time': '2017-06-05T00:11:59.508Z', + 'value': '0.002376050020000397' + }, + { + 'time': '2017-06-05T00:12:59.508Z', + 'value': '0.002455068143506122' + }, + { + 'time': '2017-06-05T00:13:59.508Z', + 'value': '0.002826705714285719' + }, + { + 'time': '2017-06-05T00:14:59.508Z', + 'value': '0.002343833692070314' + }, + { + 'time': '2017-06-05T00:15:59.508Z', + 'value': '0.00264853297122164' + }, + { + 'time': '2017-06-05T00:16:59.508Z', + 'value': '0.0027656335117426257' + }, + { + 'time': '2017-06-05T00:17:59.508Z', + 'value': '0.0025896543842439564' + }, + { + 'time': '2017-06-05T00:18:59.508Z', + 'value': '0.002180053237081201' + }, + { + 'time': '2017-06-05T00:19:59.508Z', + 'value': '0.002475245002333342' + }, + { + 'time': '2017-06-05T00:20:59.508Z', + 'value': '0.0027559767805101065' + }, + { + 'time': '2017-06-05T00:21:59.508Z', + 'value': '0.0022294836141296607' + }, + { + 'time': '2017-06-05T00:22:59.508Z', + 'value': '0.0021383590476190643' + }, + { + 'time': '2017-06-05T00:23:59.508Z', + 'value': '0.002085417956361494' + }, + { + 'time': '2017-06-05T00:24:59.508Z', + 'value': '0.0024140319047619013' + }, + { + 'time': '2017-06-05T00:25:59.508Z', + 'value': '0.0024513114285714304' + }, + { + 'time': '2017-06-05T00:26:59.508Z', + 'value': '0.0026932152380952446' + }, + { + 'time': '2017-06-05T00:27:59.508Z', + 'value': '0.0022656844350898517' + }, + { + 'time': '2017-06-05T00:28:59.508Z', + 'value': '0.0024483785714285704' + }, + { + 'time': '2017-06-05T00:29:59.508Z', + 'value': '0.002559505804817207' + }, + { + 'time': '2017-06-05T00:30:59.508Z', + 'value': '0.0019485681088751649' + }, + { + 'time': '2017-06-05T00:31:59.508Z', + 'value': '0.00228367984456996' + }, + { + 'time': '2017-06-05T00:32:59.508Z', + 'value': '0.002522149047619049' + }, + { + 'time': '2017-06-05T00:33:59.508Z', + 'value': '0.0026860117715406737' + }, + { + 'time': '2017-06-05T00:34:59.508Z', + 'value': '0.002679669523809523' + }, + { + 'time': '2017-06-05T00:35:59.508Z', + 'value': '0.0022201920970675937' + }, + { + 'time': '2017-06-05T00:36:59.508Z', + 'value': '0.0022917647619047615' + }, + { + 'time': '2017-06-05T00:37:59.508Z', + 'value': '0.0021774059294673576' + }, + { + 'time': '2017-06-05T00:38:59.508Z', + 'value': '0.0024637766666666763' + }, + { + 'time': '2017-06-05T00:39:59.508Z', + 'value': '0.002470468290174195' + }, + { + 'time': '2017-06-05T00:40:59.508Z', + 'value': '0.0022188616082057812' + }, + { + 'time': '2017-06-05T00:41:59.508Z', + 'value': '0.002421840744373875' + }, + { + 'time': '2017-06-05T00:42:59.508Z', + 'value': '0.0023918266666666547' + }, + { + 'time': '2017-06-05T00:43:59.508Z', + 'value': '0.002195743809523809' + }, + { + 'time': '2017-06-05T00:44:59.508Z', + 'value': '0.0025514828571428687' + }, + { + 'time': '2017-06-05T00:45:59.508Z', + 'value': '0.0027981709349612694' + }, + { + 'time': '2017-06-05T00:46:59.508Z', + 'value': '0.002557977142857146' + }, + { + 'time': '2017-06-05T00:47:59.508Z', + 'value': '0.002213244285714286' + }, + { + 'time': '2017-06-05T00:48:59.508Z', + 'value': '0.0025706738095238046' + }, + { + 'time': '2017-06-05T00:49:59.508Z', + 'value': '0.002210976666666671' + }, + { + 'time': '2017-06-05T00:50:59.508Z', + 'value': '0.002055377091646749' + }, + { + 'time': '2017-06-05T00:51:59.508Z', + 'value': '0.002308368095238119' + }, + { + 'time': '2017-06-05T00:52:59.508Z', + 'value': '0.0024687939885141615' + }, + { + 'time': '2017-06-05T00:53:59.508Z', + 'value': '0.002563018571428578' + }, + { + 'time': '2017-06-05T00:54:59.508Z', + 'value': '0.00240563291078959' + } + ] + } + ] + } + ] + }, + { + 'title': 'Memory usage', + 'weight': 1, + 'y_label': 'Values', + 'queries': [ + { + 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20', + 'label': 'Container memory', + 'unit': 'MiB', + 'result': [ + { + 'metric': { + + }, + 'values': [ + { + 'time': '2017-06-04T21:22:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:23:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:24:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:25:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:26:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:27:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:28:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:29:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:30:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:31:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:32:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:33:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:34:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:35:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:36:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:37:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:38:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:39:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:40:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:41:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:42:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:43:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:44:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:45:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:46:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:47:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:48:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:49:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:50:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:51:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:52:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:53:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:54:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:55:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:56:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:57:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:58:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T21:59:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:00:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:01:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:02:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:03:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:04:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:05:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:06:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:07:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:08:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:09:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:10:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:11:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:12:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:13:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:14:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:15:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:16:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:17:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:18:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:19:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:20:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:21:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:22:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:23:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:24:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:25:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:26:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:27:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:28:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:29:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:30:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:31:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:32:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:33:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:34:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:35:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:36:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:37:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:38:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:39:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:40:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:41:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:42:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:43:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:44:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:45:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:46:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:47:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:48:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:49:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:50:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:51:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:52:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:53:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:54:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:55:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:56:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:57:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:58:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T22:59:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:00:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:01:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:02:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:03:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:04:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:05:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:06:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:07:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:08:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:09:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:10:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:11:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:12:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:13:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:14:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:15:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:16:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:17:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:18:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:19:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:20:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:21:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:22:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:23:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:24:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:25:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:26:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:27:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:28:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:29:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:30:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:31:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:32:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:33:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:34:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:35:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:36:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:37:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:38:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:39:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:40:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:41:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:42:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:43:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:44:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:45:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:46:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:47:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:48:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:49:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:50:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:51:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:52:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:53:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:54:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:55:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:56:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:57:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:58:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-04T23:59:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:00:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:01:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:02:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:03:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:04:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:05:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:06:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:07:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:08:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:09:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:10:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:11:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:12:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:13:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:14:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:15:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:16:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:17:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:18:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:19:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:20:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:21:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:22:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:23:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:24:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:25:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:26:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:27:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:28:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:29:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:30:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:31:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:32:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:33:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:34:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:35:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:36:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:37:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:38:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:39:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:40:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:41:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:42:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:43:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:44:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:45:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:46:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:47:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:48:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:49:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:50:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:51:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:52:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:53:59.508Z', + 'value': '15.0859375' + }, + { + 'time': '2017-06-05T00:54:59.508Z', + 'value': '15.0859375' + } + ] + } + ] + } + ] + } +]; + +export function MonitorMockInterceptor(request, next) { + const body = responseMockData[request.method.toUpperCase()][request.url]; + + next(request.respondWith(JSON.stringify(body), { + status: 200, + })); +} diff --git a/spec/javascripts/monitoring/monitoring_column_spec.js b/spec/javascripts/monitoring/monitoring_column_spec.js new file mode 100644 index 00000000000..c8787f9708c --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_column_spec.js @@ -0,0 +1,97 @@ +import Vue from 'vue'; +import _ from 'underscore'; +import MonitoringColumn from '~/monitoring/components/monitoring_column.vue'; +import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins'; +import eventHub from '~/monitoring/event_hub'; +import { deploymentData, singleRowMetrics } from './mock_data'; + +const createComponent = (propsData) => { + const Component = Vue.extend(MonitoringColumn); + + return new Component({ + propsData, + }).$mount(); +}; + +describe('MonitoringColumn', () => { + beforeEach(() => { + spyOn(MonitoringMixins.methods, 'formatDeployments').and.callFake(function fakeFormat() { + return {}; + }); + }); + + it('has a title', () => { + const component = createComponent({ + columnData: singleRowMetrics[0], + classType: 'col-md-6', + updateAspectRatio: false, + deploymentData, + }); + + expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.columnData.title); + }); + + it('creates a path for the line and area of the graph', (done) => { + const component = createComponent({ + columnData: singleRowMetrics[0], + classType: 'col-md-6', + updateAspectRatio: false, + deploymentData, + }); + + Vue.nextTick(() => { + expect(component.area).toBeDefined(); + expect(component.line).toBeDefined(); + expect(typeof component.area).toEqual('string'); + expect(typeof component.line).toEqual('string'); + expect(_.isFunction(component.xScale)).toBe(true); + expect(_.isFunction(component.yScale)).toBe(true); + done(); + }); + }); + + describe('Computed props', () => { + it('axisTransform translates an element Y position depending of its height', () => { + const component = createComponent({ + columnData: singleRowMetrics[0], + classType: 'col-md-6', + updateAspectRatio: false, + deploymentData, + }); + + const transformedHeight = `${component.graphHeight - 100}`; + expect(component.axisTransform.indexOf(transformedHeight)) + .not.toEqual(-1); + }); + + it('outterViewBox gets a width and height property based on the DOM size of the element', () => { + const component = createComponent({ + columnData: singleRowMetrics[0], + classType: 'col-md-6', + updateAspectRatio: false, + deploymentData, + }); + + const viewBoxArray = component.outterViewBox.split(' '); + expect(typeof component.outterViewBox).toEqual('string'); + expect(viewBoxArray[2]).toEqual(component.graphWidth.toString()); + expect(viewBoxArray[3]).toEqual(component.graphHeight.toString()); + }); + }); + + it('sends an event to the eventhub when it has finished resizing', (done) => { + const component = createComponent({ + columnData: singleRowMetrics[0], + classType: 'col-md-6', + updateAspectRatio: false, + deploymentData, + }); + spyOn(eventHub, '$emit'); + + component.updateAspectRatio = true; + Vue.nextTick(() => { + expect(eventHub.$emit).toHaveBeenCalled(); + done(); + }); + }); +}); diff --git a/spec/javascripts/monitoring/monitoring_deployment_spec.js b/spec/javascripts/monitoring/monitoring_deployment_spec.js new file mode 100644 index 00000000000..5cc5b514824 --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_deployment_spec.js @@ -0,0 +1,137 @@ +import Vue from 'vue'; +import MonitoringState from '~/monitoring/components/monitoring_deployment.vue'; +import { deploymentData } from './mock_data'; + +const createComponent = (propsData) => { + const Component = Vue.extend(MonitoringState); + + return new Component({ + propsData, + }).$mount(); +}; + +describe('MonitoringDeployment', () => { + const reducedDeploymentData = [deploymentData[0]]; + reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name; + reducedDeploymentData[0].xPos = 10; + reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at); + describe('Methods', () => { + it('refText shows the ref when a tag is available', () => { + reducedDeploymentData[0].tag = '1.0'; + const component = createComponent({ + showDeployInfo: false, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect( + component.refText(reducedDeploymentData[0]), + ).toEqual(reducedDeploymentData[0].ref); + }); + + it('refText shows the sha when no tag is available', () => { + reducedDeploymentData[0].tag = null; + const component = createComponent({ + showDeployInfo: false, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect( + component.refText(reducedDeploymentData[0]), + ).toContain('f5bcd1'); + }); + + it('nameDeploymentClass creates a class with the prefix deploy-info-', () => { + const component = createComponent({ + showDeployInfo: false, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect( + component.nameDeploymentClass(reducedDeploymentData[0]), + ).toContain('deploy-info'); + }); + + it('transformDeploymentGroup translates an available deployment', () => { + const component = createComponent({ + showDeployInfo: false, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect( + component.transformDeploymentGroup(reducedDeploymentData[0]), + ).toContain('translate(11, 20)'); + }); + + it('hides the deployment flag', () => { + reducedDeploymentData[0].showDeploymentFlag = false; + const component = createComponent({ + showDeployInfo: true, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull(); + }); + + it('shows the deployment flag', () => { + reducedDeploymentData[0].showDeploymentFlag = true; + const component = createComponent({ + showDeployInfo: true, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect( + component.$el.querySelector('.js-deploy-info-box').style.display, + ).not.toEqual('display: none;'); + }); + + it('shows the refText inside a text element with the deploy-info-text class', () => { + reducedDeploymentData[0].showDeploymentFlag = true; + const component = createComponent({ + showDeployInfo: true, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect( + component.$el.querySelector('.deploy-info-text').firstChild.nodeValue.trim(), + ).toEqual(component.refText(reducedDeploymentData[0])); + }); + + it('should contain a hidden gradient', () => { + const component = createComponent({ + showDeployInfo: true, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull(); + }); + + describe('Computed props', () => { + it('calculatedHeight', () => { + const component = createComponent({ + showDeployInfo: true, + deploymentData: reducedDeploymentData, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect(component.calculatedHeight).toEqual(180); + }); + }); + }); +}); diff --git a/spec/javascripts/monitoring/monitoring_flag_spec.js b/spec/javascripts/monitoring/monitoring_flag_spec.js new file mode 100644 index 00000000000..3861a95ff07 --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_flag_spec.js @@ -0,0 +1,76 @@ +import Vue from 'vue'; +import MonitoringFlag from '~/monitoring/components/monitoring_flag.vue'; + +const createComponent = (propsData) => { + const Component = Vue.extend(MonitoringFlag); + + return new Component({ + propsData, + }).$mount(); +}; + +function getCoordinate(component, selector, coordinate) { + const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate); + return parseInt(coordinateVal, 10); +} + +describe('MonitoringFlag', () => { + it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => { + const component = createComponent({ + currentXCoordinate: 200, + currentYCoordinate: 100, + currentFlagPosition: 100, + currentData: { + time: new Date('2017-06-04T18:17:33.501Z'), + value: '1.49609375', + }, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect(getCoordinate(component, '.selected-metric-line', 'x1')) + .toEqual(component.currentXCoordinate); + expect(getCoordinate(component, '.selected-metric-line', 'x2')) + .toEqual(component.currentXCoordinate); + expect(getCoordinate(component, '.circle-metric', 'cx')) + .toEqual(component.currentXCoordinate); + expect(getCoordinate(component, '.circle-metric', 'cy')) + .toEqual(component.currentYCoordinate); + }); + + it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => { + const component = createComponent({ + currentXCoordinate: 200, + currentYCoordinate: 100, + currentFlagPosition: 100, + currentData: { + time: new Date('2017-06-04T18:17:33.501Z'), + value: '1.49609375', + }, + graphHeight: 300, + graphHeightOffset: 120, + }); + + const svg = component.$el.querySelector('.rect-text-metric'); + expect(svg.tagName).toEqual('svg'); + expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition); + }); + + describe('Computed props', () => { + it('calculatedHeight', () => { + const component = createComponent({ + currentXCoordinate: 200, + currentYCoordinate: 100, + currentFlagPosition: 100, + currentData: { + time: new Date('2017-06-04T18:17:33.501Z'), + value: '1.49609375', + }, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect(component.calculatedHeight).toEqual(180); + }); + }); +}); diff --git a/spec/javascripts/monitoring/monitoring_legends_spec.js b/spec/javascripts/monitoring/monitoring_legends_spec.js new file mode 100644 index 00000000000..4c69b81e650 --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_legends_spec.js @@ -0,0 +1,111 @@ +import Vue from 'vue'; +import MonitoringLegends from '~/monitoring/components/monitoring_legends.vue'; +import measurements from '~/monitoring/utils/measurements'; + +const createComponent = (propsData) => { + const Component = Vue.extend(MonitoringLegends); + + return new Component({ + propsData, + }).$mount(); +}; + +function getTextFromNode(component, selector) { + return component.$el.querySelector(selector).firstChild.nodeValue.trim(); +} + +describe('MonitoringLegends', () => { + describe('Computed props', () => { + it('textTransform', () => { + const component = createComponent({ + graphWidth: 500, + graphHeight: 300, + margin: measurements.large.margin, + measurements: measurements.large, + areaColorRgb: '#f0f0f0', + legendTitle: 'Title', + yAxisLabel: 'Values', + metricUsage: 'Value', + }); + + expect(component.textTransform).toContain('translate(15, 120) rotate(-90)'); + }); + + it('xPosition', () => { + const component = createComponent({ + graphWidth: 500, + graphHeight: 300, + margin: measurements.large.margin, + measurements: measurements.large, + areaColorRgb: '#f0f0f0', + legendTitle: 'Title', + yAxisLabel: 'Values', + metricUsage: 'Value', + }); + + expect(component.xPosition).toEqual(180); + }); + + it('yPosition', () => { + const component = createComponent({ + graphWidth: 500, + graphHeight: 300, + margin: measurements.large.margin, + measurements: measurements.large, + areaColorRgb: '#f0f0f0', + legendTitle: 'Title', + yAxisLabel: 'Values', + metricUsage: 'Value', + }); + + expect(component.yPosition).toEqual(240); + }); + + it('rectTransform', () => { + const component = createComponent({ + graphWidth: 500, + graphHeight: 300, + margin: measurements.large.margin, + measurements: measurements.large, + areaColorRgb: '#f0f0f0', + legendTitle: 'Title', + yAxisLabel: 'Values', + metricUsage: 'Value', + }); + + expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)'); + }); + }); + + it('has 2 rect-axis-text rect svg elements', () => { + const component = createComponent({ + graphWidth: 500, + graphHeight: 300, + margin: measurements.large.margin, + measurements: measurements.large, + areaColorRgb: '#f0f0f0', + legendTitle: 'Title', + yAxisLabel: 'Values', + metricUsage: 'Value', + }); + + expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2); + }); + + it('contains text to signal the usage, title and time', () => { + const component = createComponent({ + graphWidth: 500, + graphHeight: 300, + margin: measurements.large.margin, + measurements: measurements.large, + areaColorRgb: '#f0f0f0', + legendTitle: 'Title', + yAxisLabel: 'Values', + metricUsage: 'Value', + }); + + expect(getTextFromNode(component, '.text-metric-title')).toEqual(component.legendTitle); + expect(getTextFromNode(component, '.text-metric-usage')).toEqual(component.metricUsage); + expect(getTextFromNode(component, '.label-axis-text')).toEqual(component.yAxisLabel); + }); +}); diff --git a/spec/javascripts/monitoring/monitoring_row_spec.js b/spec/javascripts/monitoring/monitoring_row_spec.js new file mode 100644 index 00000000000..a82480e8342 --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_row_spec.js @@ -0,0 +1,57 @@ +import Vue from 'vue'; +import MonitoringRow from '~/monitoring/components/monitoring_row.vue'; +import { deploymentData, singleRowMetrics } from './mock_data'; + +const createComponent = (propsData) => { + const Component = Vue.extend(MonitoringRow); + + return new Component({ + propsData, + }).$mount(); +}; + +describe('MonitoringRow', () => { + describe('Computed props', () => { + it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => { + const component = createComponent({ + rowData: singleRowMetrics, + updateAspectRatio: false, + deploymentData, + }); + + expect(component.bootstrapClass).toEqual('col-md-6'); + }); + + it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => { + const component = createComponent({ + rowData: [singleRowMetrics[0]], + updateAspectRatio: false, + deploymentData, + }); + + expect(component.bootstrapClass).toEqual('col-md-12'); + }); + }); + + it('has one column', () => { + const component = createComponent({ + rowData: singleRowMetrics, + updateAspectRatio: false, + deploymentData, + }); + + expect(component.$el.querySelectorAll('.prometheus-svg-container').length) + .toEqual(component.rowData.length); + }); + + it('has two columns', () => { + const component = createComponent({ + rowData: singleRowMetrics, + updateAspectRatio: false, + deploymentData, + }); + + expect(component.$el.querySelectorAll('.col-md-6').length) + .toEqual(component.rowData.length); + }); +}); diff --git a/spec/javascripts/monitoring/monitoring_spec.js b/spec/javascripts/monitoring/monitoring_spec.js new file mode 100644 index 00000000000..6c7b691baa4 --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_spec.js @@ -0,0 +1,49 @@ +import Vue from 'vue'; +import Monitoring from '~/monitoring/components/monitoring.vue'; +import { MonitorMockInterceptor } from './mock_data'; + +describe('Monitoring', () => { + const fixtureName = 'environments/metrics/metrics.html.raw'; + let MonitoringComponent; + let component; + preloadFixtures(fixtureName); + + beforeEach(() => { + loadFixtures(fixtureName); + MonitoringComponent = Vue.extend(Monitoring); + }); + + describe('no metrics are available yet', () => { + it('shows a getting started empty state when no metrics are present', () => { + component = new MonitoringComponent({ + el: document.querySelector('#prometheus-graphs'), + }); + + component.$mount(); + expect(component.$el.querySelector('#prometheus-graphs')).toBe(null); + expect(component.state).toEqual('gettingStarted'); + }); + }); + + describe('requests information to the server', () => { + beforeEach(() => { + document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true'); + Vue.http.interceptors.push(MonitorMockInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, MonitorMockInterceptor); + }); + + it('shows up a loading state', (done) => { + component = new MonitoringComponent({ + el: document.querySelector('#prometheus-graphs'), + }); + component.$mount(); + Vue.nextTick(() => { + expect(component.state).toEqual('loading'); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/monitoring/monitoring_state_spec.js b/spec/javascripts/monitoring/monitoring_state_spec.js new file mode 100644 index 00000000000..4c0c558502f --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_state_spec.js @@ -0,0 +1,110 @@ +import Vue from 'vue'; +import MonitoringState from '~/monitoring/components/monitoring_state.vue'; +import { statePaths } from './mock_data'; + +const createComponent = (propsData) => { + const Component = Vue.extend(MonitoringState); + + return new Component({ + propsData, + }).$mount(); +}; + +function getTextFromNode(component, selector) { + return component.$el.querySelector(selector).firstChild.nodeValue.trim(); +} + +describe('MonitoringState', () => { + describe('Computed props', () => { + it('currentState', () => { + const component = createComponent({ + selectedState: 'gettingStarted', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.currentState).toBe(component.states.gettingStarted); + }); + + it('buttonPath returns settings path for the state "gettingStarted"', () => { + const component = createComponent({ + selectedState: 'gettingStarted', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.buttonPath).toEqual(statePaths.settingsPath); + expect(component.buttonPath).not.toEqual(statePaths.documentationPath); + }); + + it('buttonPath returns documentation path for any of the other states', () => { + const component = createComponent({ + selectedState: 'loading', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.buttonPath).toEqual(statePaths.documentationPath); + expect(component.buttonPath).not.toEqual(statePaths.settingsPath); + }); + + it('showButtonDescription returns a description with a link for the unableToConnect state', () => { + const component = createComponent({ + selectedState: 'unableToConnect', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.showButtonDescription).toEqual(true); + }); + + it('showButtonDescription returns the description without a link for any other state', () => { + const component = createComponent({ + selectedState: 'loading', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.showButtonDescription).toEqual(false); + }); + }); + + it('should show the gettingStarted state', () => { + const component = createComponent({ + selectedState: 'gettingStarted', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.$el.querySelector('svg')).toBeDefined(); + expect(getTextFromNode(component, '.state-title')).toEqual(component.states.gettingStarted.title); + expect(getTextFromNode(component, '.state-description')).toEqual(component.states.gettingStarted.description); + expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.gettingStarted.buttonText); + }); + + it('should show the loading state', () => { + const component = createComponent({ + selectedState: 'loading', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.$el.querySelector('svg')).toBeDefined(); + expect(getTextFromNode(component, '.state-title')).toEqual(component.states.loading.title); + expect(getTextFromNode(component, '.state-description')).toEqual(component.states.loading.description); + expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.loading.buttonText); + }); + + it('should show the unableToConnect state', () => { + const component = createComponent({ + selectedState: 'unableToConnect', + settingsPath: statePaths.settingsPath, + documentationPath: statePaths.documentationPath, + }); + + expect(component.$el.querySelector('svg')).toBeDefined(); + expect(getTextFromNode(component, '.state-title')).toEqual(component.states.unableToConnect.title); + expect(component.$el.querySelector('.state-description a')).toBeDefined(); + expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.unableToConnect.buttonText); + }); +}); diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js new file mode 100644 index 00000000000..20c1e6a0005 --- /dev/null +++ b/spec/javascripts/monitoring/monitoring_store_spec.js @@ -0,0 +1,24 @@ +import MonitoringStore from '~/monitoring/stores/monitoring_store'; +import MonitoringMock, { deploymentData } from './mock_data'; + +describe('MonitoringStore', () => { + this.store = new MonitoringStore(); + this.store.storeMetrics(MonitoringMock.data); + + it('contains one group that contains two queries sorted by priority in one row', () => { + expect(this.store.groups).toBeDefined(); + expect(this.store.groups.length).toEqual(1); + expect(this.store.groups[0].metrics.length).toEqual(1); + }); + + it('gets the metrics count for every group', () => { + expect(this.store.getMetricsCount()).toEqual(2); + }); + + it('contains deployment data', () => { + this.store.storeDeploymentData(deploymentData); + expect(this.store.deploymentData).toBeDefined(); + expect(this.store.deploymentData.length).toEqual(3); + expect(typeof this.store.deploymentData[0]).toEqual('object'); + }); +}); diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js deleted file mode 100644 index 25578bf1c6e..00000000000 --- a/spec/javascripts/monitoring/prometheus_graph_spec.js +++ /dev/null @@ -1,98 +0,0 @@ -import 'jquery'; -import PrometheusGraph from '~/monitoring/prometheus_graph'; -import { prometheusMockData } from './prometheus_mock_data'; - -describe('PrometheusGraph', () => { - const fixtureName = 'environments/metrics/metrics.html.raw'; - const prometheusGraphContainer = '.prometheus-graph'; - const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`; - - preloadFixtures(fixtureName); - - beforeEach(() => { - loadFixtures(fixtureName); - $('.prometheus-container').data('has-metrics', 'true'); - this.prometheusGraph = new PrometheusGraph(); - const self = this; - const fakeInit = (metricsResponse) => { - self.prometheusGraph.transformData(metricsResponse); - self.prometheusGraph.createGraph(); - }; - spyOn(this.prometheusGraph, 'init').and.callFake(fakeInit); - }); - - it('initializes graph properties', () => { - // Test for the measurements - expect(this.prometheusGraph.margin).toBeDefined(); - expect(this.prometheusGraph.marginLabelContainer).toBeDefined(); - expect(this.prometheusGraph.originalWidth).toBeDefined(); - expect(this.prometheusGraph.originalHeight).toBeDefined(); - expect(this.prometheusGraph.height).toBeDefined(); - expect(this.prometheusGraph.width).toBeDefined(); - expect(this.prometheusGraph.backOffRequestCounter).toBeDefined(); - // Test for the graph properties (colors, radius, etc.) - expect(this.prometheusGraph.graphSpecificProperties).toBeDefined(); - expect(this.prometheusGraph.commonGraphProperties).toBeDefined(); - }); - - it('transforms the data', () => { - this.prometheusGraph.init(prometheusMockData.metrics); - Object.keys(this.prometheusGraph.graphSpecificProperties, (key) => { - const graphProps = this.prometheusGraph.graphSpecificProperties[key]; - expect(graphProps.data).toBeDefined(); - expect(graphProps.data.length).toBe(121); - }); - }); - - it('creates two graphs', () => { - this.prometheusGraph.init(prometheusMockData.metrics); - expect($(prometheusGraphContainer).length).toBe(2); - }); - - describe('Graph contents', () => { - beforeEach(() => { - this.prometheusGraph.init(prometheusMockData.metrics); - }); - - it('has axis, an area, a line and a overlay', () => { - const $graphContainer = $(prometheusGraphContents).find('.x-axis').parent(); - expect($graphContainer.find('.x-axis')).toBeDefined(); - expect($graphContainer.find('.y-axis')).toBeDefined(); - expect($graphContainer.find('.prometheus-graph-overlay')).toBeDefined(); - expect($graphContainer.find('.metric-line')).toBeDefined(); - expect($graphContainer.find('.metric-area')).toBeDefined(); - }); - - it('has legends, labels and an extra axis that labels the metrics', () => { - const $prometheusGraphContents = $(prometheusGraphContents); - const $axisLabelContainer = $(prometheusGraphContents).find('.label-x-axis-line').parent(); - expect($prometheusGraphContents.find('.label-x-axis-line')).toBeDefined(); - expect($prometheusGraphContents.find('.label-y-axis-line')).toBeDefined(); - expect($prometheusGraphContents.find('.label-axis-text')).toBeDefined(); - expect($prometheusGraphContents.find('.rect-axis-text')).toBeDefined(); - expect($axisLabelContainer.find('rect').length).toBe(3); - expect($axisLabelContainer.find('text').length).toBe(4); - }); - }); -}); - -describe('PrometheusGraphs UX states', () => { - const fixtureName = 'environments/metrics/metrics.html.raw'; - preloadFixtures(fixtureName); - - beforeEach(() => { - loadFixtures(fixtureName); - this.prometheusGraph = new PrometheusGraph(); - }); - - it('shows a specified state', () => { - this.prometheusGraph.state = '.js-getting-started'; - this.prometheusGraph.updateState(); - const $state = $('.js-getting-started'); - expect($state).toBeDefined(); - expect($('.state-title', $state)).toBeDefined(); - expect($('.state-svg', $state)).toBeDefined(); - expect($('.state-description', $state)).toBeDefined(); - expect($('.state-button', $state)).toBeDefined(); - }); -}); diff --git a/spec/javascripts/monitoring/prometheus_mock_data.js b/spec/javascripts/monitoring/prometheus_mock_data.js deleted file mode 100644 index 1cdc14faaa8..00000000000 --- a/spec/javascripts/monitoring/prometheus_mock_data.js +++ /dev/null @@ -1,1014 +0,0 @@ -/* eslint-disable import/prefer-default-export*/ -export const prometheusMockData = { - status: 200, - metrics: { - success: true, - metrics: { - memory_values: [ - { - metric: { - }, - values: [ - [ - 1488462917.256, - '10.12890625', - ], - [ - 1488462977.256, - '10.140625', - ], - [ - 1488463037.256, - '10.140625', - ], - [ - 1488463097.256, - '10.14453125', - ], - [ - 1488463157.256, - '10.1484375', - ], - [ - 1488463217.256, - '10.15625', - ], - [ - 1488463277.256, - '10.15625', - ], - [ - 1488463337.256, - '10.15625', - ], - [ - 1488463397.256, - '10.1640625', - ], - [ - 1488463457.256, - '10.171875', - ], - [ - 1488463517.256, - '10.171875', - ], - [ - 1488463577.256, - '10.171875', - ], - [ - 1488463637.256, - '10.18359375', - ], - [ - 1488463697.256, - '10.1953125', - ], - [ - 1488463757.256, - '10.203125', - ], - [ - 1488463817.256, - '10.20703125', - ], - [ - 1488463877.256, - '10.20703125', - ], - [ - 1488463937.256, - '10.20703125', - ], - [ - 1488463997.256, - '10.20703125', - ], - [ - 1488464057.256, - '10.2109375', - ], - [ - 1488464117.256, - '10.2109375', - ], - [ - 1488464177.256, - '10.2109375', - ], - [ - 1488464237.256, - '10.2109375', - ], - [ - 1488464297.256, - '10.21484375', - ], - [ - 1488464357.256, - '10.22265625', - ], - [ - 1488464417.256, - '10.22265625', - ], - [ - 1488464477.256, - '10.2265625', - ], - [ - 1488464537.256, - '10.23046875', - ], - [ - 1488464597.256, - '10.23046875', - ], - [ - 1488464657.256, - '10.234375', - ], - [ - 1488464717.256, - '10.234375', - ], - [ - 1488464777.256, - '10.234375', - ], - [ - 1488464837.256, - '10.234375', - ], - [ - 1488464897.256, - '10.234375', - ], - [ - 1488464957.256, - '10.234375', - ], - [ - 1488465017.256, - '10.23828125', - ], - [ - 1488465077.256, - '10.23828125', - ], - [ - 1488465137.256, - '10.2421875', - ], - [ - 1488465197.256, - '10.2421875', - ], - [ - 1488465257.256, - '10.2421875', - ], - [ - 1488465317.256, - '10.2421875', - ], - [ - 1488465377.256, - '10.2421875', - ], - [ - 1488465437.256, - '10.2421875', - ], - [ - 1488465497.256, - '10.2421875', - ], - [ - 1488465557.256, - '10.2421875', - ], - [ - 1488465617.256, - '10.2421875', - ], - [ - 1488465677.256, - '10.2421875', - ], - [ - 1488465737.256, - '10.2421875', - ], - [ - 1488465797.256, - '10.24609375', - ], - [ - 1488465857.256, - '10.25', - ], - [ - 1488465917.256, - '10.25390625', - ], - [ - 1488465977.256, - '9.98828125', - ], - [ - 1488466037.256, - '9.9921875', - ], - [ - 1488466097.256, - '9.9921875', - ], - [ - 1488466157.256, - '9.99609375', - ], - [ - 1488466217.256, - '10', - ], - [ - 1488466277.256, - '10.00390625', - ], - [ - 1488466337.256, - '10.0078125', - ], - [ - 1488466397.256, - '10.01171875', - ], - [ - 1488466457.256, - '10.0234375', - ], - [ - 1488466517.256, - '10.02734375', - ], - [ - 1488466577.256, - '10.02734375', - ], - [ - 1488466637.256, - '10.03125', - ], - [ - 1488466697.256, - '10.03125', - ], - [ - 1488466757.256, - '10.03125', - ], - [ - 1488466817.256, - '10.03125', - ], - [ - 1488466877.256, - '10.03125', - ], - [ - 1488466937.256, - '10.03125', - ], - [ - 1488466997.256, - '10.03125', - ], - [ - 1488467057.256, - '10.0390625', - ], - [ - 1488467117.256, - '10.0390625', - ], - [ - 1488467177.256, - '10.04296875', - ], - [ - 1488467237.256, - '10.05078125', - ], - [ - 1488467297.256, - '10.05859375', - ], - [ - 1488467357.256, - '10.06640625', - ], - [ - 1488467417.256, - '10.06640625', - ], - [ - 1488467477.256, - '10.0703125', - ], - [ - 1488467537.256, - '10.07421875', - ], - [ - 1488467597.256, - '10.0859375', - ], - [ - 1488467657.256, - '10.0859375', - ], - [ - 1488467717.256, - '10.09765625', - ], - [ - 1488467777.256, - '10.1015625', - ], - [ - 1488467837.256, - '10.10546875', - ], - [ - 1488467897.256, - '10.10546875', - ], - [ - 1488467957.256, - '10.125', - ], - [ - 1488468017.256, - '10.13671875', - ], - [ - 1488468077.256, - '10.1484375', - ], - [ - 1488468137.256, - '10.15625', - ], - [ - 1488468197.256, - '10.16796875', - ], - [ - 1488468257.256, - '10.171875', - ], - [ - 1488468317.256, - '10.171875', - ], - [ - 1488468377.256, - '10.171875', - ], - [ - 1488468437.256, - '10.171875', - ], - [ - 1488468497.256, - '10.171875', - ], - [ - 1488468557.256, - '10.171875', - ], - [ - 1488468617.256, - '10.171875', - ], - [ - 1488468677.256, - '10.17578125', - ], - [ - 1488468737.256, - '10.17578125', - ], - [ - 1488468797.256, - '10.265625', - ], - [ - 1488468857.256, - '10.19921875', - ], - [ - 1488468917.256, - '10.19921875', - ], - [ - 1488468977.256, - '10.19921875', - ], - [ - 1488469037.256, - '10.19921875', - ], - [ - 1488469097.256, - '10.19921875', - ], - [ - 1488469157.256, - '10.203125', - ], - [ - 1488469217.256, - '10.43359375', - ], - [ - 1488469277.256, - '10.20703125', - ], - [ - 1488469337.256, - '10.2109375', - ], - [ - 1488469397.256, - '10.22265625', - ], - [ - 1488469457.256, - '10.21484375', - ], - [ - 1488469517.256, - '10.21484375', - ], - [ - 1488469577.256, - '10.21484375', - ], - [ - 1488469637.256, - '10.22265625', - ], - [ - 1488469697.256, - '10.234375', - ], - [ - 1488469757.256, - '10.234375', - ], - [ - 1488469817.256, - '10.234375', - ], - [ - 1488469877.256, - '10.2421875', - ], - [ - 1488469937.256, - '10.25', - ], - [ - 1488469997.256, - '10.25390625', - ], - [ - 1488470057.256, - '10.26171875', - ], - [ - 1488470117.256, - '10.2734375', - ], - ], - }, - ], - memory_current: [ - { - metric: { - }, - value: [ - 1488470117.737, - '10.2734375', - ], - }, - ], - cpu_values: [ - { - metric: { - }, - values: [ - [ - 1488462918.15, - '0.0002996458625058103', - ], - [ - 1488462978.15, - '0.0002652382333333314', - ], - [ - 1488463038.15, - '0.0003485461333333421', - ], - [ - 1488463098.15, - '0.0003420421999999886', - ], - [ - 1488463158.15, - '0.00023107150000001297', - ], - [ - 1488463218.15, - '0.00030463981666664826', - ], - [ - 1488463278.15, - '0.0002477177833333677', - ], - [ - 1488463338.15, - '0.00026936656666665115', - ], - [ - 1488463398.15, - '0.000406264750000022', - ], - [ - 1488463458.15, - '0.00029592802026561453', - ], - [ - 1488463518.15, - '0.00023426999683316343', - ], - [ - 1488463578.15, - '0.0003057080666666915', - ], - [ - 1488463638.15, - '0.0003408470500000149', - ], - [ - 1488463698.15, - '0.00025497336666665166', - ], - [ - 1488463758.15, - '0.0003009282833333534', - ], - [ - 1488463818.15, - '0.0003119383499999924', - ], - [ - 1488463878.15, - '0.00028719019999998705', - ], - [ - 1488463938.15, - '0.000327864749999988', - ], - [ - 1488463998.15, - '0.0002514917333333422', - ], - [ - 1488464058.15, - '0.0003614651166666742', - ], - [ - 1488464118.15, - '0.0003221668000000122', - ], - [ - 1488464178.15, - '0.00023323083333330884', - ], - [ - 1488464238.15, - '0.00028531499475009274', - ], - [ - 1488464298.15, - '0.0002627695294921391', - ], - [ - 1488464358.15, - '0.00027145463333333453', - ], - [ - 1488464418.15, - '0.00025669488333335266', - ], - [ - 1488464478.15, - '0.00022307761666665965', - ], - [ - 1488464538.15, - '0.0003307265833333517', - ], - [ - 1488464598.15, - '0.0002817050666666709', - ], - [ - 1488464658.15, - '0.00022357458333332285', - ], - [ - 1488464718.15, - '0.00032648590000000275', - ], - [ - 1488464778.15, - '0.00028410750000000816', - ], - [ - 1488464838.15, - '0.0003038076999999954', - ], - [ - 1488464898.15, - '0.00037568226666667335', - ], - [ - 1488464958.15, - '0.00020160354999999202', - ], - [ - 1488465018.15, - '0.0003229403333333399', - ], - [ - 1488465078.15, - '0.00033516069999999236', - ], - [ - 1488465138.15, - '0.0003365978333333371', - ], - [ - 1488465198.15, - '0.00020262178333331585', - ], - [ - 1488465258.15, - '0.00040567498333331876', - ], - [ - 1488465318.15, - '0.00029114155000001436', - ], - [ - 1488465378.15, - '0.0002498841000000122', - ], - [ - 1488465438.15, - '0.00027296763333331715', - ], - [ - 1488465498.15, - '0.0002958794000000135', - ], - [ - 1488465558.15, - '0.0002922354666666867', - ], - [ - 1488465618.15, - '0.00034186624999999653', - ], - [ - 1488465678.15, - '0.0003397984166666627', - ], - [ - 1488465738.15, - '0.0002658284166666469', - ], - [ - 1488465798.15, - '0.00026221139999999346', - ], - [ - 1488465858.15, - '0.00029467960000001034', - ], - [ - 1488465918.15, - '0.0002634141333333358', - ], - [ - 1488465978.15, - '0.0003202958333333209', - ], - [ - 1488466038.15, - '0.00037890760000000394', - ], - [ - 1488466098.15, - '0.00023453356666666518', - ], - [ - 1488466158.15, - '0.0002866827333333433', - ], - [ - 1488466218.15, - '0.0003335935499999998', - ], - [ - 1488466278.15, - '0.00022787131666666125', - ], - [ - 1488466338.15, - '0.00033821938333333064', - ], - [ - 1488466398.15, - '0.00029233375000001043', - ], - [ - 1488466458.15, - '0.00026562758333333514', - ], - [ - 1488466518.15, - '0.0003142600999999819', - ], - [ - 1488466578.15, - '0.00027392178333333444', - ], - [ - 1488466638.15, - '0.00028178598333334173', - ], - [ - 1488466698.15, - '0.0002463400666666911', - ], - [ - 1488466758.15, - '0.00040234373333332125', - ], - [ - 1488466818.15, - '0.00023677453333332822', - ], - [ - 1488466878.15, - '0.00030852703333333523', - ], - [ - 1488466938.15, - '0.0003582272833333455', - ], - [ - 1488466998.15, - '0.0002176380833332973', - ], - [ - 1488467058.15, - '0.00026180203333335447', - ], - [ - 1488467118.15, - '0.00027862966666667436', - ], - [ - 1488467178.15, - '0.0002769731166666567', - ], - [ - 1488467238.15, - '0.0002832899166666477', - ], - [ - 1488467298.15, - '0.0003446533500000311', - ], - [ - 1488467358.15, - '0.0002691345999999761', - ], - [ - 1488467418.15, - '0.000284919933333357', - ], - [ - 1488467478.15, - '0.0002396026166666528', - ], - [ - 1488467538.15, - '0.00035625295000002075', - ], - [ - 1488467598.15, - '0.00036759816666664946', - ], - [ - 1488467658.15, - '0.00030326608333333855', - ], - [ - 1488467718.15, - '0.00023584972418043393', - ], - [ - 1488467778.15, - '0.00025744508892115107', - ], - [ - 1488467838.15, - '0.00036737541666663395', - ], - [ - 1488467898.15, - '0.00034325741666666094', - ], - [ - 1488467958.15, - '0.00026390046666667407', - ], - [ - 1488468018.15, - '0.0003302534500000102', - ], - [ - 1488468078.15, - '0.00035243794999999527', - ], - [ - 1488468138.15, - '0.00020149738333333407', - ], - [ - 1488468198.15, - '0.0003183469666666679', - ], - [ - 1488468258.15, - '0.0003835329166666845', - ], - [ - 1488468318.15, - '0.0002485075333333124', - ], - [ - 1488468378.15, - '0.0003011457166666768', - ], - [ - 1488468438.15, - '0.00032242785497684965', - ], - [ - 1488468498.15, - '0.0002659713747457531', - ], - [ - 1488468558.15, - '0.0003476860333333202', - ], - [ - 1488468618.15, - '0.00028336403333334794', - ], - [ - 1488468678.15, - '0.00017132354999998728', - ], - [ - 1488468738.15, - '0.0003001915833333276', - ], - [ - 1488468798.15, - '0.0003025715666666725', - ], - [ - 1488468858.15, - '0.0003012370166666815', - ], - [ - 1488468918.15, - '0.00030203619999997025', - ], - [ - 1488468978.15, - '0.0002804355000000314', - ], - [ - 1488469038.15, - '0.00033194884999998564', - ], - [ - 1488469098.15, - '0.00025201496666665455', - ], - [ - 1488469158.15, - '0.0002777531500000189', - ], - [ - 1488469218.15, - '0.0003314885833333392', - ], - [ - 1488469278.15, - '0.0002234891422095589', - ], - [ - 1488469338.15, - '0.000349117355867791', - ], - [ - 1488469398.15, - '0.0004036731333333303', - ], - [ - 1488469458.15, - '0.00024553911666667835', - ], - [ - 1488469518.15, - '0.0003056456833333184', - ], - [ - 1488469578.15, - '0.0002618737166666681', - ], - [ - 1488469638.15, - '0.00022972643333331414', - ], - [ - 1488469698.15, - '0.0003713522500000307', - ], - [ - 1488469758.15, - '0.00018322576666666515', - ], - [ - 1488469818.15, - '0.00034534762753952466', - ], - [ - 1488469878.15, - '0.00028200510008501677', - ], - [ - 1488469938.15, - '0.0002773708499999768', - ], - [ - 1488469998.15, - '0.00027547160000001013', - ], - [ - 1488470058.15, - '0.00031713610000000023', - ], - [ - 1488470118.15, - '0.00035276853333332525', - ], - ], - }, - ], - cpu_current: [ - { - metric: { - }, - value: [ - 1488470118.566, - '0.00035276853333332525', - ], - }, - ], - last_update: '2017-03-02T15:55:18.981Z', - }, - }, -}; -- cgit v1.2.1 From a67ff8e8835e3594783de0d198f5dcce24145c6d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 16:50:16 +0200 Subject: Move "remove group member" spec from spinach to rspec Signed-off-by: Dmitriy Zaporozhets --- features/group/members.feature | 12 --- spec/features/groups/members/list_members_spec.rb | 63 ---------------- spec/features/groups/members/manage_members.rb | 92 +++++++++++++++++++++++ 3 files changed, 92 insertions(+), 75 deletions(-) diff --git a/features/group/members.feature b/features/group/members.feature index 5ff75ab3a5b..a7e51339e12 100644 --- a/features/group/members.feature +++ b/features/group/members.feature @@ -4,18 +4,6 @@ Feature: Group Members And "John Doe" is owner of group "Owned" And "John Doe" is guest of group "Guest" - # Remove others - - Scenario: Owner should be able to remove other users from group - Given "Mary Jane" is owner of group "Owned" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - When I click on the "Remove User From Group" button for "Mary Jane" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should not see user "Mary Jane" in team list - Scenario: Guest should not be able to remove other users from group Given "Mary Jane" is guest of group "Guest" When I visit group "Guest" members page diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb index 69cbeb4a35a..f6493c4c50e 100644 --- a/spec/features/groups/members/list_members_spec.rb +++ b/spec/features/groups/members/list_members_spec.rb @@ -32,60 +32,6 @@ feature 'Groups > Members > List members', feature: true do expect(second_row).to be_blank end - scenario 'update user to owner level', :js do - group.add_owner(user1) - group.add_developer(user2) - - visit group_group_members_path(group) - - page.within(second_row) do - click_button('Developer') - click_link('Owner') - - expect(page).to have_button('Owner') - end - end - - scenario 'add user to group', :js do - group.add_owner(user1) - - visit group_group_members_path(group) - - add_user(user2.id, 'Reporter') - - page.within(second_row) do - expect(page).to have_content(user2.name) - expect(page).to have_button('Reporter') - end - end - - scenario 'add yourself to group when already an owner', :js do - group.add_owner(user1) - - visit group_group_members_path(group) - - add_user(user1.id, 'Reporter') - - page.within(first_row) do - expect(page).to have_content(user1.name) - expect(page).to have_content('Owner') - end - end - - scenario 'invite user to group', :js do - group.add_owner(user1) - - visit group_group_members_path(group) - - add_user('test@example.com', 'Reporter') - - page.within(second_row) do - expect(page).to have_content('test@example.com') - expect(page).to have_content('Invited') - expect(page).to have_button('Reporter') - end - end - def first_row page.all('ul.content-list > li')[0] end @@ -93,13 +39,4 @@ feature 'Groups > Members > List members', feature: true do def second_row page.all('ul.content-list > li')[1] end - - def add_user(id, role) - page.within ".users-group-form" do - select2(id, from: "#user_ids", multiple: true) - select(role, from: "access_level") - end - - click_button "Add to group" - end end diff --git a/spec/features/groups/members/manage_members.rb b/spec/features/groups/members/manage_members.rb index ff0a09709e5..d5925a13401 100644 --- a/spec/features/groups/members/manage_members.rb +++ b/spec/features/groups/members/manage_members.rb @@ -1,4 +1,96 @@ require 'spec_helper' feature 'Groups > Members > Manage members', feature: true do + include Select2Helper + + let(:user1) { create(:user, name: 'John Doe') } + let(:user2) { create(:user, name: 'Mary Jane') } + let(:group) { create(:group) } + + background do + gitlab_sign_in(user1) + end + + scenario 'update user to owner level', :js do + group.add_owner(user1) + group.add_developer(user2) + + visit group_group_members_path(group) + + page.within(second_row) do + click_button('Developer') + click_link('Owner') + + expect(page).to have_button('Owner') + end + end + + scenario 'add user to group', :js do + group.add_owner(user1) + + visit group_group_members_path(group) + + add_user(user2.id, 'Reporter') + + page.within(second_row) do + expect(page).to have_content(user2.name) + expect(page).to have_button('Reporter') + end + end + + scenario 'remove user from group', :js do + group.add_owner(user1) + group.add_developer(user2) + + visit group_group_members_path(group) + + find(:css, '.project-members-page li', text: user2.name).find(:css, 'a.btn-remove').click + + expect(page).not_to have_content(user2.name) + expect(group.users).not_to include(user2) + end + + scenario 'add yourself to group when already an owner', :js do + group.add_owner(user1) + + visit group_group_members_path(group) + + add_user(user1.id, 'Reporter') + + page.within(first_row) do + expect(page).to have_content(user1.name) + expect(page).to have_content('Owner') + end + end + + scenario 'invite user to group', :js do + group.add_owner(user1) + + visit group_group_members_path(group) + + add_user('test@example.com', 'Reporter') + + page.within(second_row) do + expect(page).to have_content('test@example.com') + expect(page).to have_content('Invited') + expect(page).to have_button('Reporter') + end + end + + def first_row + page.all('ul.content-list > li')[0] + end + + def second_row + page.all('ul.content-list > li')[1] + end + + def add_user(id, role) + page.within ".users-group-form" do + select2(id, from: "#user_ids", multiple: true) + select(role, from: "access_level") + end + + click_button "Add to group" + end end -- cgit v1.2.1 From 69043814b40fb1203e77c75b4bb082dd67501222 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 16:51:05 +0200 Subject: Fix leave_group_spec.rb Signed-off-by: Dmitriy Zaporozhets --- spec/features/groups/members/leave_group_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index 5b812891281..b438f57753c 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -18,7 +18,7 @@ feature 'Groups > Members > Leave group', feature: true do expect(current_path).to eq(dashboard_groups_path) expect(page).to have_content left_group_message(group) - expect(group.members).not_to include(user) + expect(group.users).not_to include(user) end scenario 'guest leaves the group as last member' do @@ -29,7 +29,7 @@ feature 'Groups > Members > Leave group', feature: true do expect(current_path).to eq(dashboard_groups_path) expect(page).to have_content left_group_message(group) - expect(group.members).not_to include(user) + expect(group.users).not_to include(user) end scenario 'owner leaves the group if they is not the last owner' do @@ -41,7 +41,7 @@ feature 'Groups > Members > Leave group', feature: true do expect(current_path).to eq(dashboard_groups_path) expect(page).to have_content left_group_message(group) - expect(group.members).not_to include(user) + expect(group.users).not_to include(user) end scenario 'owner can not leave the group if they is a last owner' do -- cgit v1.2.1 From 0aa5f08988547678c2a32a5ad85332bbc165d3bf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 26 Jun 2017 17:00:07 +0200 Subject: Move another group member spec from spinach Signed-off-by: Dmitriy Zaporozhets --- features/group/members.feature | 7 ------- spec/features/groups/members/manage_members.rb | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/features/group/members.feature b/features/group/members.feature index a7e51339e12..49a44f57cbb 100644 --- a/features/group/members.feature +++ b/features/group/members.feature @@ -4,13 +4,6 @@ Feature: Group Members And "John Doe" is owner of group "Owned" And "John Doe" is guest of group "Guest" - Scenario: Guest should not be able to remove other users from group - Given "Mary Jane" is guest of group "Guest" - When I visit group "Guest" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - Then I should not see the "Remove User From Group" button for "Mary Jane" - Scenario: Search member by name Given "Mary Jane" is guest of group "Guest" And I visit group "Guest" members page diff --git a/spec/features/groups/members/manage_members.rb b/spec/features/groups/members/manage_members.rb index d5925a13401..a9a654b20e2 100644 --- a/spec/features/groups/members/manage_members.rb +++ b/spec/features/groups/members/manage_members.rb @@ -77,6 +77,23 @@ feature 'Groups > Members > Manage members', feature: true do end end + scenario 'guest can not manage other users' do + group.add_guest(user1) + group.add_developer(user2) + + visit group_group_members_path(group) + + expect(page).not_to have_button 'Add to group' + + page.within(second_row) do + # Can not modify user2 role + expect(page).not_to have_button 'Developer' + + # Can not remove user2 + expect(page).not_to have_css('a.btn-remove') + end + end + def first_row page.all('ul.content-list > li')[0] end -- cgit v1.2.1 From 1a47bd37a1bc4b33980ba0c97bae9ba54b8d2cfb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 23 Jun 2017 21:01:32 -0700 Subject: Reset the state of StubENV's @env_already_stubbed after each spec run In certain combination of tests, @env_already_stubbed could be set to `true` even though it never ran for that specific test. --- spec/support/stub_env.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb index 18597b5c71f..2999bcd9fb1 100644 --- a/spec/support/stub_env.rb +++ b/spec/support/stub_env.rb @@ -5,3 +5,11 @@ module StubENV allow(ENV).to receive(:[]).with(key).and_return(value) end end + +# It's possible that the state of the class variables are not reset across +# test runs. +RSpec.configure do |config| + config.after(:each) do + @env_already_stubbed = nil + end +end -- cgit v1.2.1 From 2c954117376f3eace3288041bdd1bcc9bd2432b0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 26 Jun 2017 16:15:51 +0100 Subject: Fixed multi-line markdown in issue edit form Closes #34318 --- app/assets/javascripts/lib/utils/text_utility.js | 4 +- .../issue-form-multiple-line-markdown.yml | 4 + .../vue_shared/components/markdown/field_spec.js | 105 ++++++++++++++------- 3 files changed, 75 insertions(+), 38 deletions(-) create mode 100644 changelogs/unreleased/issue-form-multiple-line-markdown.yml diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 601d01e1be1..021f936a4fa 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -94,8 +94,8 @@ gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) { startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : ''; - if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) { - if (blockTag != null) { + if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) { + if (blockTag != null && blockTag !== '') { insertText = this.blockTagText(text, textArea, blockTag, selected); } else { insertText = selectedSplit.map(function(val) { diff --git a/changelogs/unreleased/issue-form-multiple-line-markdown.yml b/changelogs/unreleased/issue-form-multiple-line-markdown.yml new file mode 100644 index 00000000000..23128f346bc --- /dev/null +++ b/changelogs/unreleased/issue-form-multiple-line-markdown.yml @@ -0,0 +1,4 @@ +--- +title: Fixed multi-line markdown tooltip buttons in issue edit form +merge_request: +author: diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js index 4bbaff561fc..291e19c9f3c 100644 --- a/spec/javascripts/vue_shared/components/markdown/field_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js @@ -4,47 +4,33 @@ import fieldComponent from '~/vue_shared/components/markdown/field.vue'; describe('Markdown field component', () => { let vm; - beforeEach(() => { + beforeEach((done) => { vm = new Vue({ - render(createElement) { - return createElement( - fieldComponent, - { - props: { - markdownPreviewUrl: '/preview', - markdownDocs: '/docs', - }, - }, - [ - createElement('textarea', { - slot: 'textarea', - }), - ], - ); + data() { + return { + text: 'testing\n123', + }; }, - }); - }); - - it('creates a new instance of GL form', (done) => { - spyOn(gl, 'GLForm'); - vm.$mount(); - - Vue.nextTick(() => { - expect( - gl.GLForm, - ).toHaveBeenCalled(); - - done(); - }); + components: { + fieldComponent, + }, + template: ` + + + + `, + }).$mount(); + + Vue.nextTick(done); }); describe('mounted', () => { - beforeEach((done) => { - vm.$mount(); - - Vue.nextTick(done); - }); - it('renders textarea inside backdrop', () => { expect( vm.$el.querySelector('.zen-backdrop textarea'), @@ -117,5 +103,52 @@ describe('Markdown field component', () => { }); }); }); + + describe('markdown buttons', () => { + it('converts single words', (done) => { + const textarea = vm.$el.querySelector('textarea'); + + textarea.setSelectionRange(0, 7); + vm.$el.querySelector('.js-md').click(); + + Vue.nextTick(() => { + expect( + textarea.value, + ).toContain('**testing**'); + + done(); + }); + }); + + it('converts a line', (done) => { + const textarea = vm.$el.querySelector('textarea'); + + textarea.setSelectionRange(0, 0); + vm.$el.querySelectorAll('.js-md')[4].click(); + + Vue.nextTick(() => { + expect( + textarea.value, + ).toContain('* testing'); + + done(); + }); + }); + + it('converts multiple lines', (done) => { + const textarea = vm.$el.querySelector('textarea'); + + textarea.setSelectionRange(0, 50); + vm.$el.querySelectorAll('.js-md')[4].click(); + + Vue.nextTick(() => { + expect( + textarea.value, + ).toContain('* testing\n* 123'); + + done(); + }); + }); + }); }); }); -- cgit v1.2.1 From dbec39ab863367479984858a0383197f36d7a416 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Mon, 26 Jun 2017 10:40:20 -0500 Subject: Fix environment_metrics spec --- spec/features/projects/environments/environment_metrics_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb index b48dcf6c774..a98a69a0fd6 100644 --- a/spec/features/projects/environments/environment_metrics_spec.rb +++ b/spec/features/projects/environments/environment_metrics_spec.rb @@ -27,7 +27,7 @@ feature 'Environment > Metrics', :feature do scenario 'shows metrics' do click_link('See metrics') - expect(page).to have_css('svg.prometheus-graph') + expect(page).to have_css('div#prometheus-graphs') end end -- cgit v1.2.1 From 5461623dca6cfbae5794052772f58bd657d21ea4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 26 Jun 2017 15:36:29 +0000 Subject: Merge branch 'update-9-3-templates' into '9-3-stable' Update templates for 9.3 See merge request !12041 --- vendor/Dockerfile/Binary-alpine.Dockerfile | 14 +++++++++++++ vendor/Dockerfile/Binary-scratch.Dockerfile | 17 ++++++++++++++++ vendor/Dockerfile/Binary.Dockerfile | 11 +++++++++++ vendor/Dockerfile/Golang-alpine.Dockerfile | 17 ++++++++++++++++ vendor/Dockerfile/Golang-scratch.Dockerfile | 20 +++++++++++++++++++ vendor/Dockerfile/Golang.Dockerfile | 14 +++++++++++++ vendor/Dockerfile/Node-alpine.Dockerfile | 14 +++++++++++++ vendor/Dockerfile/Node.Dockerfile | 14 +++++++++++++ vendor/Dockerfile/Ruby-alpine.Dockerfile | 24 +++++++++++++++++++++++ vendor/Dockerfile/Ruby.Dockerfile | 27 ++++++++++++++++++++++++++ vendor/gitignore/Global/Archives.gitignore | 4 ++-- vendor/gitignore/Global/JEnv.gitignore | 5 +++++ vendor/gitignore/Global/SublimeText.gitignore | 10 +++++----- vendor/gitignore/Global/Vagrant.gitignore | 4 ++++ vendor/gitignore/Global/Vim.gitignore | 10 ++++++---- vendor/gitignore/Global/Windows.gitignore | 3 +++ vendor/gitignore/Global/macOS.gitignore | 1 + vendor/gitignore/Python.gitignore | 8 +++----- vendor/gitignore/Qt.gitignore | 8 ++++++++ vendor/gitignore/SugarCRM.gitignore | 4 ++-- vendor/gitignore/VisualStudio.gitignore | 7 +++++++ vendor/gitlab-ci-yml/.gitlab-ci.yml | 4 ++-- vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml | 2 +- vendor/gitlab-ci-yml/Django.gitlab-ci.yml | 2 +- vendor/gitlab-ci-yml/Docker.gitlab-ci.yml | 8 ++++---- vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml | 2 +- vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml | 2 +- vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml | 2 +- vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml | 6 +++--- vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml | 2 +- vendor/gitlab-ci-yml/Rust.gitlab-ci.yml | 2 +- 31 files changed, 234 insertions(+), 34 deletions(-) create mode 100644 vendor/Dockerfile/Binary-alpine.Dockerfile create mode 100644 vendor/Dockerfile/Binary-scratch.Dockerfile create mode 100644 vendor/Dockerfile/Binary.Dockerfile create mode 100644 vendor/Dockerfile/Golang-alpine.Dockerfile create mode 100644 vendor/Dockerfile/Golang-scratch.Dockerfile create mode 100644 vendor/Dockerfile/Golang.Dockerfile create mode 100644 vendor/Dockerfile/Node-alpine.Dockerfile create mode 100644 vendor/Dockerfile/Node.Dockerfile create mode 100644 vendor/Dockerfile/Ruby-alpine.Dockerfile create mode 100644 vendor/Dockerfile/Ruby.Dockerfile create mode 100644 vendor/gitignore/Global/JEnv.gitignore diff --git a/vendor/Dockerfile/Binary-alpine.Dockerfile b/vendor/Dockerfile/Binary-alpine.Dockerfile new file mode 100644 index 00000000000..5a9eb2b4716 --- /dev/null +++ b/vendor/Dockerfile/Binary-alpine.Dockerfile @@ -0,0 +1,14 @@ +# This Dockerfile installs a compiled binary into a bare system. +# You must either commit your compiled binary into source control (not recommended) +# or build the binary first as part of a CI/CD pipeline. + +FROM alpine:3.5 + +# We'll likely need to add SSL root certificates +RUN apk --no-cache add ca-certificates + +WORKDIR /usr/local/bin + +# Change `app` to whatever your binary is called +Add app . +CMD ["./app"] diff --git a/vendor/Dockerfile/Binary-scratch.Dockerfile b/vendor/Dockerfile/Binary-scratch.Dockerfile new file mode 100644 index 00000000000..5e2de2ead61 --- /dev/null +++ b/vendor/Dockerfile/Binary-scratch.Dockerfile @@ -0,0 +1,17 @@ +# This Dockerfile installs a compiled binary into an image with no system at all. +# You must either commit your compiled binary into source control (not recommended) +# or build the binary first as part of a CI/CD pipeline. +# Your binary must be statically compiled with no dynamic dependencies on system libraries. +# e.g. for Docker: +# CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . + +FROM scratch + +# Since we started from scratch, we'll likely need to add SSL root certificates +ADD /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +WORKDIR /usr/local/bin + +# Change `app` to whatever your binary is called +Add app . +CMD ["./app"] diff --git a/vendor/Dockerfile/Binary.Dockerfile b/vendor/Dockerfile/Binary.Dockerfile new file mode 100644 index 00000000000..e7d560da9ac --- /dev/null +++ b/vendor/Dockerfile/Binary.Dockerfile @@ -0,0 +1,11 @@ +# This Dockerfile installs a compiled binary into a bare system. +# You must either commit your compiled binary into source control (not recommended) +# or build the binary first as part of a CI/CD pipeline. + +FROM buildpack-deps:jessie + +WORKDIR /usr/local/bin + +# Change `app` to whatever your binary is called +Add app . +CMD ["./app"] diff --git a/vendor/Dockerfile/Golang-alpine.Dockerfile b/vendor/Dockerfile/Golang-alpine.Dockerfile new file mode 100644 index 00000000000..0287315219b --- /dev/null +++ b/vendor/Dockerfile/Golang-alpine.Dockerfile @@ -0,0 +1,17 @@ +FROM golang:1.8-alpine AS builder + +WORKDIR /usr/src/app + +COPY . . +RUN go-wrapper download +RUN go build -v + +FROM alpine:3.5 + +# We'll likely need to add SSL root certificates +RUN apk --no-cache add ca-certificates + +WORKDIR /usr/local/bin + +COPY --from=builder /usr/src/app/app . +CMD ["./app"] diff --git a/vendor/Dockerfile/Golang-scratch.Dockerfile b/vendor/Dockerfile/Golang-scratch.Dockerfile new file mode 100644 index 00000000000..9057a2d0e51 --- /dev/null +++ b/vendor/Dockerfile/Golang-scratch.Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.8-alpine AS builder + +# We'll likely need to add SSL root certificates +RUN apk --no-cache add ca-certificates + +WORKDIR /usr/src/app + +COPY . . +RUN go-wrapper download +RUN CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o app . + +FROM scratch + +# Since we started from scratch, we'll copy the SSL root certificates from the builder +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +WORKDIR /usr/local/bin + +COPY --from=builder /usr/src/app/app . +CMD ["./app"] diff --git a/vendor/Dockerfile/Golang.Dockerfile b/vendor/Dockerfile/Golang.Dockerfile new file mode 100644 index 00000000000..ec94914be19 --- /dev/null +++ b/vendor/Dockerfile/Golang.Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.8 AS builder + +WORKDIR /usr/src/app + +COPY . . +RUN go-wrapper download +RUN go build -v + +FROM buildpack-deps:jessie + +WORKDIR /usr/local/bin + +COPY --from=builder /usr/src/app/app . +CMD ["./app"] diff --git a/vendor/Dockerfile/Node-alpine.Dockerfile b/vendor/Dockerfile/Node-alpine.Dockerfile new file mode 100644 index 00000000000..9776b1336b5 --- /dev/null +++ b/vendor/Dockerfile/Node-alpine.Dockerfile @@ -0,0 +1,14 @@ +FROM node:7.9-alpine + +WORKDIR /usr/src/app + +ARG NODE_ENV +ENV NODE_ENV $NODE_ENV +COPY package.json /usr/src/app/ +RUN npm install && npm cache clean +COPY . /usr/src/app + +CMD [ "npm", "start" ] + +# replace this with your application's default port +EXPOSE 8888 diff --git a/vendor/Dockerfile/Node.Dockerfile b/vendor/Dockerfile/Node.Dockerfile new file mode 100644 index 00000000000..7e936d5e887 --- /dev/null +++ b/vendor/Dockerfile/Node.Dockerfile @@ -0,0 +1,14 @@ +FROM node:7.9 + +WORKDIR /usr/src/app + +ARG NODE_ENV +ENV NODE_ENV $NODE_ENV +COPY package.json /usr/src/app/ +RUN npm install && npm cache clean +COPY . /usr/src/app + +CMD [ "npm", "start" ] + +# replace this with your application's default port +EXPOSE 8888 diff --git a/vendor/Dockerfile/Ruby-alpine.Dockerfile b/vendor/Dockerfile/Ruby-alpine.Dockerfile new file mode 100644 index 00000000000..9db4e2130f2 --- /dev/null +++ b/vendor/Dockerfile/Ruby-alpine.Dockerfile @@ -0,0 +1,24 @@ +FROM ruby:2.4-alpine + +# Edit with nodejs, mysql-client, postgresql-client, sqlite3, etc. for your needs. +# Or delete entirely if not needed. +RUN apk --no-cache add nodejs postgresql-client + +# throw errors if Gemfile has been modified since Gemfile.lock +RUN bundle config --global frozen 1 + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY Gemfile Gemfile.lock /usr/src/app/ +RUN bundle install + +COPY . /usr/src/app + +# For Sinatra +#EXPOSE 4567 +#CMD ["ruby", "./config.rb"] + +# For Rails +EXPOSE 3000 +CMD ["rails", "server"] diff --git a/vendor/Dockerfile/Ruby.Dockerfile b/vendor/Dockerfile/Ruby.Dockerfile new file mode 100644 index 00000000000..feb880ee4b2 --- /dev/null +++ b/vendor/Dockerfile/Ruby.Dockerfile @@ -0,0 +1,27 @@ +FROM ruby:2.4 + +# Edit with nodejs, mysql-client, postgresql-client, sqlite3, etc. for your needs. +# Or delete entirely if not needed. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + nodejs \ + postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +# throw errors if Gemfile has been modified since Gemfile.lock +RUN bundle config --global frozen 1 + +WORKDIR /usr/src/app + +COPY Gemfile Gemfile.lock /usr/src/app/ +RUN bundle install -j $(nproc) + +COPY . /usr/src/app + +# For Sinatra +#EXPOSE 4567 +#CMD ["ruby", "./config.rb"] + +# For Rails +EXPOSE 3000 +CMD ["rails", "server", "-b", "0.0.0.0"] diff --git a/vendor/gitignore/Global/Archives.gitignore b/vendor/gitignore/Global/Archives.gitignore index f440b808d98..43fd5582f91 100644 --- a/vendor/gitignore/Global/Archives.gitignore +++ b/vendor/gitignore/Global/Archives.gitignore @@ -12,11 +12,11 @@ *.lzma *.cab -#packing-only formats +# Packing-only formats *.iso *.tar -#package management formats +# Package management formats *.dmg *.xpi *.gem diff --git a/vendor/gitignore/Global/JEnv.gitignore b/vendor/gitignore/Global/JEnv.gitignore new file mode 100644 index 00000000000..d838300ad5e --- /dev/null +++ b/vendor/gitignore/Global/JEnv.gitignore @@ -0,0 +1,5 @@ +# JEnv local Java version configuration file +.java-version + +# Used by previous versions of JEnv +.jenv-version diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore index 95ff2244c99..86c3fa455aa 100644 --- a/vendor/gitignore/Global/SublimeText.gitignore +++ b/vendor/gitignore/Global/SublimeText.gitignore @@ -1,16 +1,16 @@ -# cache files for sublime text +# Cache files for Sublime Text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache -# workspace files are user-specific +# Workspace files are user-specific *.sublime-workspace -# project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using SublimeText +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text # *.sublime-project -# sftp configuration file +# SFTP configuration file sftp-config.json # Package control specific files diff --git a/vendor/gitignore/Global/Vagrant.gitignore b/vendor/gitignore/Global/Vagrant.gitignore index a977916f658..93987ca00ec 100644 --- a/vendor/gitignore/Global/Vagrant.gitignore +++ b/vendor/gitignore/Global/Vagrant.gitignore @@ -1 +1,5 @@ +# General .vagrant/ + +# Log files (if you are creating logs in debug mode, uncomment this) +# *.logs diff --git a/vendor/gitignore/Global/Vim.gitignore b/vendor/gitignore/Global/Vim.gitignore index 42e7afc1005..6d21783d471 100644 --- a/vendor/gitignore/Global/Vim.gitignore +++ b/vendor/gitignore/Global/Vim.gitignore @@ -1,12 +1,14 @@ -# swap +# Swap [._]*.s[a-v][a-z] [._]*.sw[a-p] [._]s[a-v][a-z] [._]sw[a-p] -# session + +# Session Session.vim -# temporary + +# Temporary .netrwhist *~ -# auto-generated tag files +# Auto-generated tag files tags diff --git a/vendor/gitignore/Global/Windows.gitignore b/vendor/gitignore/Global/Windows.gitignore index ba26afd9653..dff26a9ab70 100644 --- a/vendor/gitignore/Global/Windows.gitignore +++ b/vendor/gitignore/Global/Windows.gitignore @@ -3,6 +3,9 @@ Thumbs.db ehthumbs.db ehthumbs_vista.db +# Dump file +*.stackdump + # Folder config file Desktop.ini diff --git a/vendor/gitignore/Global/macOS.gitignore b/vendor/gitignore/Global/macOS.gitignore index 5972fe50f66..9d1061e8bc4 100644 --- a/vendor/gitignore/Global/macOS.gitignore +++ b/vendor/gitignore/Global/macOS.gitignore @@ -1,3 +1,4 @@ +# General *.DS_Store .AppleDouble .LSOverride diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore index 768d5f400bb..113294a5f18 100644 --- a/vendor/gitignore/Python.gitignore +++ b/vendor/gitignore/Python.gitignore @@ -8,7 +8,6 @@ __pycache__/ # Distribution / packaging .Python -env/ build/ develop-eggs/ dist/ @@ -43,7 +42,7 @@ htmlcov/ .cache nosetests.xml coverage.xml -*,cover +*.cover .hypothesis/ # Translations @@ -79,11 +78,10 @@ celerybeat-schedule # SageMath parsed files *.sage.py -# dotenv +# Environments .env - -# virtualenv .venv +env/ venv/ ENV/ diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore index 6732e72091c..5fa47c5a1f2 100644 --- a/vendor/gitignore/Qt.gitignore +++ b/vendor/gitignore/Qt.gitignore @@ -12,6 +12,9 @@ # Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp /.qmake.cache /.qmake.stash *.pro.user @@ -26,6 +29,11 @@ ui_*.h Makefile* *build-* + +# Qt unit tests +target_wrapper.* + + # QtCreator *.autosave diff --git a/vendor/gitignore/SugarCRM.gitignore b/vendor/gitignore/SugarCRM.gitignore index e9270205fd5..6a183d1c748 100644 --- a/vendor/gitignore/SugarCRM.gitignore +++ b/vendor/gitignore/SugarCRM.gitignore @@ -6,7 +6,7 @@ # the misuse of the repository as backup replacement. # For development the cache directory can be safely ignored and # therefore it is ignored. -/cache/ +/cache/* !/cache/index.html # Ignore some files and directories from the custom directory. /custom/history/ @@ -22,6 +22,6 @@ # Logs files can safely be ignored. *.log # Ignore the new upload directories. -/upload/ +/upload/* !/upload/index.html /upload_backup/ diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index 940794e60f2..22fd88a55a3 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -42,6 +42,9 @@ TestResult.xml [Rr]eleasePS/ dlldata.c +# Benchmark Results +BenchmarkDotNet.Artifacts/ + # .NET Core project.lock.json project.fragment.lock.json @@ -183,6 +186,7 @@ AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt +*.appx # Visual Studio cache files # files ending in .cache can be ignored @@ -278,6 +282,9 @@ __pycache__/ # tools/** # !tools/packages.config +# Tabs Studio +*.tss + # Telerik's JustMock configuration file *.jmconfig diff --git a/vendor/gitlab-ci-yml/.gitlab-ci.yml b/vendor/gitlab-ci-yml/.gitlab-ci.yml index 18b14554887..e2a55163682 100644 --- a/vendor/gitlab-ci-yml/.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: ruby:2.3-alpine +image: ruby:2.4-alpine test: - script: ruby verify_templates.rb + script: ./verify_templates.rb diff --git a/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml b/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml index 37e44735f7c..02cfab3a5b2 100644 --- a/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml @@ -4,7 +4,7 @@ image: "crystallang/crystal:latest" # Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. -# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service # services: # - mysql:latest # - redis:latest diff --git a/vendor/gitlab-ci-yml/Django.gitlab-ci.yml b/vendor/gitlab-ci-yml/Django.gitlab-ci.yml index 5ded2f5ce76..57afcbbe8b5 100644 --- a/vendor/gitlab-ci-yml/Django.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Django.gitlab-ci.yml @@ -4,7 +4,7 @@ image: python:latest # Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. -# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service services: - mysql:latest - postgres:latest diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml index 40648bcd3de..5b6af7be8c4 100644 --- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml @@ -6,8 +6,8 @@ services: build: stage: build + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "CI_REGISTRY_PASSWORD" $CI_REGISTRY script: - - export IMAGE_TAG=$(echo -en $CI_COMMIT_REF_NAME | tr -c '[:alnum:]_.-' '-') - - docker login -u "gitlab-ci-token" -p "$CI_JOB_TOKEN" $CI_REGISTRY - - docker build --pull -t "$CI_REGISTRY_IMAGE:$IMAGE_TAG" . - - docker push "$CI_REGISTRY_IMAGE:$IMAGE_TAG" + - docker build --pull -t "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG" . + - docker push "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG" diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml index 981a77497e2..cf9c731637c 100644 --- a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml @@ -2,7 +2,7 @@ image: elixir:latest # Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. -# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service services: - mysql:latest - redis:latest diff --git a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml index 0d6a6eddc97..434de4f055a 100644 --- a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml @@ -4,7 +4,7 @@ image: php:latest # Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. -# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service services: - mysql:latest diff --git a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml index e5bce3503f3..41de1458582 100644 --- a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml @@ -4,7 +4,7 @@ image: node:latest # Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. -# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service services: - mysql:latest - redis:latest diff --git a/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml index bc36a4e6966..7abfaf53e8e 100644 --- a/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml @@ -3,7 +3,7 @@ # # JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers # -# This yml works with jBake 2.4.0 +# This yml works with jBake 2.5.1 # Feel free to change JBAKE_VERSION version # # HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/ @@ -11,12 +11,12 @@ image: java:8 variables: - JBAKE_VERSION: 2.4.0 + JBAKE_VERSION: 2.5.1 # We use SDKMan as tool for managing versions before_script: - - apt-get update -qq && apt-get install -y -qq unzip + - apt-get update -qq && apt-get install -y -qq unzip zip - curl -sSL https://get.sdkman.io | bash - echo sdkman_auto_answer=true > /root/.sdkman/etc/config - source /root/.sdkman/bin/sdkman-init.sh diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml index 08b57c8c0ac..4e181e85451 100644 --- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -4,7 +4,7 @@ image: "ruby:2.3" # Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. -# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service services: - mysql:latest - redis:latest diff --git a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml index ae3f7405ea3..7810121c350 100644 --- a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml @@ -4,7 +4,7 @@ image: "scorpil/rust:stable" # Optional: Pick zero or more services to be used on all builds. # Only needed when using a docker container to run your tests in. -# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service #services: # - mysql:latest # - redis:latest -- cgit v1.2.1 From d981f9aa8a05e72264fcb1eec4119ac75429a3a0 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Thu, 25 May 2017 14:43:51 +0200 Subject: Rename all forbidden paths again --- ...170525140254_rename_all_reserved_paths_again.rb | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb diff --git a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb new file mode 100644 index 00000000000..47c6acc2bff --- /dev/null +++ b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb @@ -0,0 +1,108 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RenameAllReservedPathsAgain < ActiveRecord::Migration + include Gitlab::Database::RenameReservedPathsMigration::V1 + + DOWNTIME = false + + disable_ddl_transaction! + + TOP_LEVEL_ROUTES = %w[ + - + .well-known + abuse_reports + admin + all + api + assets + autocomplete + ci + dashboard + explore + files + groups + health_check + help + hooks + import + invites + issues + jwt + koding + member + merge_requests + new + notes + notification_settings + oauth + profile + projects + public + repository + robots.txt + s + search + sent_notifications + services + snippets + teams + u + unicorn_test + unsubscribes + uploads + users + ].freeze + + PROJECT_WILDCARD_ROUTES = %w[ + badges + blame + blob + builds + commits + create + create_dir + edit + environments/folders + files + find_file + gitlab-lfs/objects + info/lfs/objects + new + preview + raw + refs + tree + update + wikis + ].freeze + + GROUP_ROUTES = %w[ + activity + analytics + audit_events + avatar + edit + group_members + hooks + issues + labels + ldap + ldap_group_links + merge_requests + milestones + notification_setting + pipeline_quota + projects + subgroups + ].freeze + + def up + rename_root_paths(TOP_LEVEL_ROUTES) + rename_wildcard_paths(PROJECT_WILDCARD_ROUTES) + rename_child_paths(GROUP_ROUTES) + end + + def down + end +end -- cgit v1.2.1 From 36ecbb6934c73dc7a446c51766b8d43d98be7c12 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 30 May 2017 12:04:03 +0200 Subject: Rename each route individually --- db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb index 47c6acc2bff..fd0b68a4154 100644 --- a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb +++ b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb @@ -98,9 +98,9 @@ class RenameAllReservedPathsAgain < ActiveRecord::Migration ].freeze def up - rename_root_paths(TOP_LEVEL_ROUTES) - rename_wildcard_paths(PROJECT_WILDCARD_ROUTES) - rename_child_paths(GROUP_ROUTES) + TOP_LEVEL_ROUTES.each { |route| rename_root_paths(route) } + PROJECT_WILDCARD_ROUTES.each { |route| rename_wildcard_paths(route) } + GROUP_ROUTES.each { |route| rename_child_paths(route) } end def down -- cgit v1.2.1 From 3e84b6336f3d61ff56e3e8ef5cc5d8ca08ef0432 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 6 Jun 2017 12:28:28 +0200 Subject: Track all renames in redis --- .../rename_reserved_paths_migration/v1/rename_base.rb | 5 +++++ .../v1/rename_namespaces.rb | 2 ++ .../v1/rename_projects.rb | 2 ++ .../v1/rename_base_spec.rb | 17 +++++++++++++++++ .../v1/rename_namespaces_spec.rb | 7 +++++++ .../v1/rename_projects_spec.rb | 7 +++++++ spec/support/fake_migration_classes.rb | 4 ++++ 7 files changed, 44 insertions(+) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index d8163d7da11..a8febf4ec62 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -114,6 +114,11 @@ module Gitlab end end + def track_rename(type, old_path, new_path) + key = "rename:#{migration.version}:#{type}" + Gitlab::Redis.with { |redis| redis.lpush(key, [old_path, new_path].to_json) } + end + def file_storage? CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb index da7e2cb2e85..a84af02e67b 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb @@ -26,6 +26,8 @@ module Gitlab def rename_namespace(namespace) old_full_path, new_full_path = rename_path_for_routable(namespace) + track_rename('namespace', old_full_path, new_full_path) + move_repositories(namespace, old_full_path, new_full_path) move_uploads(old_full_path, new_full_path) move_pages(old_full_path, new_full_path) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb index 448717eb744..59e47120159 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb @@ -16,6 +16,8 @@ module Gitlab def rename_project(project) old_full_path, new_full_path = rename_path_for_routable(project) + track_rename('project', old_full_path, new_full_path) + move_repository(project, old_full_path, new_full_path) move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki") move_uploads(old_full_path, new_full_path) diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 5653cfee686..fa3c86bbb4f 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -203,4 +203,21 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca expect(File.exist?(expected_file)).to be(true) end end + + describe '#track_rename', redis: true do + it 'tracks a rename in redis' do + key = 'rename:20170316163845:namespace' + + subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed') + + old_path, new_path = [nil, nil] + Gitlab::Redis.with do |redis| + rename_info = redis.lpop(key) + old_path, new_path = JSON.parse(rename_info) + end + + expect(old_path).to eq('path/to/namespace') + expect(new_path).to eq('path/to/renamed') + end + end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 8125dedd3fc..142ebfaf232 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -191,6 +191,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : subject.rename_namespace(user.namespace) end + + it 'tracks the rename' do + expect(subject).to receive(:track_rename) + .with('namespace', 'the-path', 'the-path0') + + subject.rename_namespace(namespace) + end end describe '#rename_user' do diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index 802f77ad430..ed2f2241ab6 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -85,6 +85,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr subject.rename_project(project) end + + it 'tracks the rename' do + expect(subject).to receive(:track_rename) + .with('project', 'known-parent/the-path', 'known-parent/the-path0') + + subject.rename_project(project) + end end describe '#move_repository' do diff --git a/spec/support/fake_migration_classes.rb b/spec/support/fake_migration_classes.rb index 3de0460c3ca..33cd1043132 100644 --- a/spec/support/fake_migration_classes.rb +++ b/spec/support/fake_migration_classes.rb @@ -1,3 +1,7 @@ class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration include Gitlab::Database::RenameReservedPathsMigration::V1 + + def version + '20170316163845' + end end -- cgit v1.2.1 From 0faff42d7cc62dba50ca99ab1bc034c285311409 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 6 Jun 2017 17:24:32 +0200 Subject: Add methods to revert project renames --- .../v1/rename_base.rb | 36 ++++++++-- .../v1/rename_projects.rb | 19 ++++++ .../v1/rename_base_spec.rb | 37 +++++++++++ .../v1/rename_projects_spec.rb | 76 +++++++++++++++++----- 4 files changed, 146 insertions(+), 22 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index a8febf4ec62..29f21b08e6c 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -7,6 +7,7 @@ module Gitlab delegate :update_column_in_batches, :replace_sql, + :say, to: :migration def initialize(paths, migration) @@ -26,13 +27,18 @@ module Gitlab new_path = rename_path(namespace_path, old_path) new_full_path = join_routable_path(namespace_path, new_path) + perform_rename(routable, old_full_path, new_full_path) + + [old_full_path, new_full_path] + end + + def perform_rename(routable, old_full_path, new_full_path) # skips callbacks & validations - routable.class.where(id: routable) - .update_all(path: new_path) + new_path = new_full_path.split('/').last + routable.class.where(id: routable). + update_all(path: new_path) rename_routes(old_full_path, new_full_path) - - [old_full_path, new_full_path] end def rename_routes(old_full_path, new_full_path) @@ -86,7 +92,10 @@ module Gitlab def move_folders(directory, old_relative_path, new_relative_path) old_path = File.join(directory, old_relative_path) - return unless File.directory?(old_path) + unless File.directory?(old_path) + say "#{old_path} doesn't exist, skipping" + return + end new_path = File.join(directory, new_relative_path) FileUtils.mv(old_path, new_path) @@ -115,10 +124,25 @@ module Gitlab end def track_rename(type, old_path, new_path) - key = "rename:#{migration.version}:#{type}" + key = redis_key_for_type(type) Gitlab::Redis.with { |redis| redis.lpush(key, [old_path, new_path].to_json) } end + def reverts_for_type(type) + key = redis_key_for_type(type) + Gitlab::Redis.with do |redis| + while rename_info = redis.lpop(key) + path_before_rename, path_after_rename = JSON.parse(rename_info) + say "renaming #{type} from #{path_after_rename} back to #{path_before_rename}" + yield(path_before_rename, path_after_rename) + end + end + end + + def redis_key_for_type(type) + "rename:#{migration.version}:#{type}" + end + def file_storage? CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb index 59e47120159..1d2a41d91bb 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb @@ -18,12 +18,31 @@ module Gitlab track_rename('project', old_full_path, new_full_path) + move_project_folders(project, old_full_path, new_full_path) + end + + def move_project_folders(project, old_full_path, new_full_path) move_repository(project, old_full_path, new_full_path) move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki") move_uploads(old_full_path, new_full_path) move_pages(old_full_path, new_full_path) end + def revert_renames + reverts_for_type('project') do |path_before_rename, current_path| + matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path) + project = MigrationClasses::Project.joins(:route) + .where(matches_path).first + if project + perform_rename(project, current_path, path_before_rename) + + move_project_folders(project, current_path, path_before_rename) + else + say "Couldn't rename project##{project.id} from #{current_path} back to #{path_before_rename}, project no longer exists" + end + end + end + def move_repository(project, old_path, new_path) unless gitlab_shell.mv_repository(project.repository_storage_path, old_path, diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index fa3c86bbb4f..4f6f06f0d07 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -153,6 +153,30 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca end end + describe '#perform_rename' do + describe 'for namespaces' do + let(:namespace) { create(:namespace, path: 'the-path') } + it 'renames the path' do + subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed') + + expect(namespace.reload.path).to eq('renamed') + end + + it 'renames all the routes for the namespace' do + child = create(:group, path: 'child', parent: namespace) + project = create(:project, namespace: child, path: 'the-project') + other_one = create(:namespace, path: 'the-path-is-similar') + + subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed') + + expect(namespace.reload.route.path).to eq('renamed') + expect(child.reload.route.path).to eq('renamed/child') + expect(project.reload.route.path).to eq('renamed/child/the-project') + expect(other_one.reload.route.path).to eq('the-path-is-similar') + end + end + end + describe '#move_pages' do it 'moves the pages directory' do expect(subject).to receive(:move_folders) @@ -220,4 +244,17 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca expect(new_path).to eq('path/to/renamed') end end + + describe '#reverts_for_type', redis: true do + it 'yields for each tracked rename' do + subject.track_rename('project', 'old_path', 'new_path') + subject.track_rename('project', 'old_path2', 'new_path2') + subject.track_rename('namespace', 'namespace_path', 'new_namespace_path') + + expect { |b| subject.reverts_for_type('project', &b) } + .to yield_successive_args(%w(old_path2 new_path2), %w(old_path new_path)) + expect { |b| subject.reverts_for_type('namespace', &b) } + .to yield_with_args('namespace_path', 'new_namespace_path') + end + end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index ed2f2241ab6..f87f4371353 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -3,6 +3,12 @@ require 'spec_helper' describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :truncate do let(:migration) { FakeRenameReservedPathMigrationV1.new } let(:subject) { described_class.new(['the-path'], migration) } + let(:project) do + create(:empty_project, + path: 'the-path', + namespace: create(:namespace, path: 'known-parent' )) + end + before do allow(migration).to receive(:say) @@ -47,12 +53,6 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr end describe '#rename_project' do - let(:project) do - create(:empty_project, - path: 'the-path', - namespace: create(:namespace, path: 'known-parent' )) - end - it 'renames path & route for the project' do expect(subject).to receive(:rename_path_for_routable) .with(project) @@ -63,34 +63,42 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr expect(project.reload.path).to eq('the-path0') end + it 'tracks the rename' do + expect(subject).to receive(:track_rename) + .with('project', 'known-parent/the-path', 'known-parent/the-path0') + + subject.rename_project(project) + end + + it 'renames the folders for the project' do + expect(subject).to receive(:move_project_folders).with(project, 'known-parent/the-path', 'known-parent/the-path0') + + subject.rename_project(project) + end + end + + describe '#move_project_folders' do it 'moves the wiki & the repo' do expect(subject).to receive(:move_repository) .with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki') expect(subject).to receive(:move_repository) .with(project, 'known-parent/the-path', 'known-parent/the-path0') - subject.rename_project(project) + subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0') end it 'moves uploads' do expect(subject).to receive(:move_uploads) .with('known-parent/the-path', 'known-parent/the-path0') - subject.rename_project(project) + subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0') end it 'moves pages' do expect(subject).to receive(:move_pages) .with('known-parent/the-path', 'known-parent/the-path0') - subject.rename_project(project) - end - - it 'tracks the rename' do - expect(subject).to receive(:track_rename) - .with('project', 'known-parent/the-path', 'known-parent/the-path0') - - subject.rename_project(project) + subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0') end end @@ -106,4 +114,40 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr expect(File.directory?(expected_path)).to be(true) end end + + describe '#revert_renames', redis: true do + it 'renames the routes back to the previous values' do + subject.rename_project(project) + + expect(subject).to receive(:perform_rename) + .with( + kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project), + 'known-parent/the-path0', + 'known-parent/the-path' + ).and_call_original + + subject.revert_renames + + expect(project.reload.path).to eq('the-path') + expect(project.route.path).to eq('known-parent/the-path') + end + + it 'moves the repositories back to their original place' do + project.create_repository + subject.rename_project(project) + + expected_path = File.join(TestEnv.repos_path, 'known-parent', 'the-path.git') + + expect(subject).to receive(:move_project_folders) + .with( + kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project), + 'known-parent/the-path0', + 'known-parent/the-path' + ).and_call_original + + subject.revert_renames + + expect(File.directory?(expected_path)).to be_truthy + end + end end -- cgit v1.2.1 From 152cba56e4004de1b1c2accee77e40a019d8c667 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 6 Jun 2017 18:40:11 +0200 Subject: Revert namespace renames --- .../v1/rename_namespaces.rb | 21 ++++++ .../v1/rename_projects.rb | 3 +- .../v1/rename_base_spec.rb | 1 + .../v1/rename_namespaces_spec.rb | 78 +++++++++++++++++----- .../v1/rename_projects_spec.rb | 2 +- 5 files changed, 88 insertions(+), 17 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb index a84af02e67b..434167b0f88 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb @@ -28,6 +28,10 @@ module Gitlab track_rename('namespace', old_full_path, new_full_path) + rename_namespace_dependencies(namespace, old_full_path, new_full_path) + end + + def rename_namespace_dependencies(namespace, old_full_path, new_full_path) move_repositories(namespace, old_full_path, new_full_path) move_uploads(old_full_path, new_full_path) move_pages(old_full_path, new_full_path) @@ -35,6 +39,23 @@ module Gitlab remove_cached_html_for_projects(projects_for_namespace(namespace).map(&:id)) end + def revert_renames + reverts_for_type('namespace') do |path_before_rename, current_path| + matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path) + namespace = MigrationClasses::Namespace.joins(:route) + .where(matches_path).first.becomes(MigrationClasses::Namespace) + + if namespace + perform_rename(namespace, current_path, path_before_rename) + + rename_namespace_dependencies(namespace, current_path, path_before_rename) + else + say "Couldn't rename namespace##{namespace.id} from #{current_path} "\ + " back to #{path_before_rename}, namespace no longer exists" + end + end + end + def rename_user(old_username, new_username) MigrationClasses::User.where(username: old_username) .update_all(username: new_username) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb index 1d2a41d91bb..061f531f1d5 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb @@ -38,7 +38,8 @@ module Gitlab move_project_folders(project, current_path, path_before_rename) else - say "Couldn't rename project##{project.id} from #{current_path} back to #{path_before_rename}, project no longer exists" + say "Couldn't rename project##{project.id} from #{current_path} "\ + "back to #{path_before_rename}, project no longer exists" end end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 4f6f06f0d07..f4e19f09419 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -6,6 +6,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca before do allow(migration).to receive(:say) + TestEnv.clean_test_path end def migration_namespace(namespace) diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 142ebfaf232..5fa3d809c7a 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -3,9 +3,11 @@ require 'spec_helper' describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :truncate do let(:migration) { FakeRenameReservedPathMigrationV1.new } let(:subject) { described_class.new(['the-path'], migration) } + let(:namespace) { create(:group, name: 'the-path') } before do allow(migration).to receive(:say) + TestEnv.clean_test_path end def migration_namespace(namespace) @@ -137,8 +139,6 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : end describe "#rename_namespace" do - let(:namespace) { create(:group, name: 'the-path') } - it 'renames paths & routes for the namespace' do expect(subject).to receive(:rename_path_for_routable) .with(namespace) @@ -149,11 +149,27 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : expect(namespace.reload.path).to eq('the-path0') end + it 'tracks the rename' do + expect(subject).to receive(:track_rename) + .with('namespace', 'the-path', 'the-path0') + + subject.rename_namespace(namespace) + end + + it 'renames things related to the namespace' do + expect(subject).to receive(:rename_namespace_dependencies) + .with(namespace, 'the-path', 'the-path0') + + subject.rename_namespace(namespace) + end + end + + describe '#rename_namespace_dependencies' do it "moves the the repository for a project in the namespace" do create(:project, namespace: namespace, path: "the-path-project") expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git") - subject.rename_namespace(namespace) + subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0') expect(File.directory?(expected_repo)).to be(true) end @@ -161,13 +177,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : it "moves the uploads for the namespace" do expect(subject).to receive(:move_uploads).with("the-path", "the-path0") - subject.rename_namespace(namespace) + subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0') end it "moves the pages for the namespace" do expect(subject).to receive(:move_pages).with("the-path", "the-path0") - subject.rename_namespace(namespace) + subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0') end it 'invalidates the markdown cache of related projects' do @@ -175,13 +191,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : expect(subject).to receive(:remove_cached_html_for_projects).with([project.id]) - subject.rename_namespace(namespace) + subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0') end it "doesn't rename users for other namespaces" do expect(subject).not_to receive(:rename_user) - subject.rename_namespace(namespace) + subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0') end it 'renames the username of a namespace for a user' do @@ -189,14 +205,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : expect(subject).to receive(:rename_user).with('the-path', 'the-path0') - subject.rename_namespace(user.namespace) - end - - it 'tracks the rename' do - expect(subject).to receive(:track_rename) - .with('namespace', 'the-path', 'the-path0') - - subject.rename_namespace(namespace) + subject.rename_namespace_dependencies(user.namespace, 'the-path', 'the-path0') end end @@ -231,4 +240,43 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : subject.rename_namespaces(type: :child) end end + + describe '#revert_renames', redis: true do + it 'renames the routes back to the previous values' do + project = create(:project, path: 'a-project', namespace: namespace) + subject.rename_namespace(namespace) + + expect(subject).to receive(:perform_rename) + .with( + kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace), + 'the-path0', + 'the-path' + ).and_call_original + + subject.revert_renames + + expect(namespace.reload.path).to eq('the-path') + expect(namespace.reload.route.path).to eq('the-path') + expect(project.reload.route.path).to eq('the-path/a-project') + end + + it 'moves the repositories back to their original place' do + project = create(:project, path: 'a-project', namespace: namespace) + project.create_repository + subject.rename_namespace(namespace) + + expected_path = File.join(TestEnv.repos_path, 'the-path', 'a-project.git') + + expect(subject).to receive(:rename_namespace_dependencies) + .with( + kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace), + 'the-path0', + 'the-path' + ).and_call_original + + subject.revert_renames + + expect(File.directory?(expected_path)).to be_truthy + end + end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index f87f4371353..ca741a4989b 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -9,9 +9,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr namespace: create(:namespace, path: 'known-parent' )) end - before do allow(migration).to receive(:say) + TestEnv.clean_test_path end describe '#projects_for_paths' do -- cgit v1.2.1 From c98ed42d01d4a15ce2a588079601b7d620b029ff Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 12 Jun 2017 11:03:28 +0200 Subject: Revert renames from a migration --- ...170525140254_rename_all_reserved_paths_again.rb | 1 + .../database/rename_reserved_paths_migration/v1.rb | 5 +++++ .../rename_reserved_paths_migration/v1_spec.rb | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb index fd0b68a4154..9ec59913b66 100644 --- a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb +++ b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb @@ -104,5 +104,6 @@ class RenameAllReservedPathsAgain < ActiveRecord::Migration end def down + revert_renames end end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb index 89530082cd2..f333ff22300 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb @@ -29,6 +29,11 @@ module Gitlab paths = Array(paths) RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level) end + + def revert_renames + RenameProjects.new([], self).revert_renames + RenameNamespaces.new([], self).revert_renames + end end end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb index 1d5e58855c1..9a48bc3a048 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb @@ -51,4 +51,26 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do subject.rename_root_paths('the-path') end end + + describe '#revert_renames' do + it 'renames namespaces' do + rename_namespaces = double + expect(described_class::RenameNamespaces). + to receive(:new).with([], subject). + and_return(rename_namespaces) + expect(rename_namespaces).to receive(:revert_renames) + + subject.revert_renames + end + + it 'renames projects' do + rename_projects = double + expect(described_class::RenameProjects). + to receive(:new).with([], subject). + and_return(rename_projects) + expect(rename_projects).to receive(:revert_renames) + + subject.revert_renames + end + end end -- cgit v1.2.1 From 1ebb2255362788b8c34fb4120ed9e2ba478ee53b Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 12 Jun 2017 14:16:03 +0200 Subject: More logging so we know we have the rename in redis --- .../database/rename_reserved_paths_migration/v1/rename_base.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index 29f21b08e6c..3bf549a56eb 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -125,7 +125,11 @@ module Gitlab def track_rename(type, old_path, new_path) key = redis_key_for_type(type) - Gitlab::Redis.with { |redis| redis.lpush(key, [old_path, new_path].to_json) } + Gitlab::Redis.with do |redis| + redis.lpush(key, [old_path, new_path].to_json) + redis.expire(key, 2.weeks.to_i) + end + say "tracked rename: #{key}: #{old_path} -> #{new_path}" end def reverts_for_type(type) -- cgit v1.2.1 From 229ac39a4c7f7cc4fa207ffa1c826e114df2906a Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 12 Jun 2017 19:10:00 +0200 Subject: Don't break rolling back when a namespace or project was renamed --- .../rename_reserved_paths_migration/v1/rename_namespaces.rb | 6 +++--- .../database/rename_reserved_paths_migration/v1/rename_projects.rb | 6 ++++-- .../rename_reserved_paths_migration/v1/rename_namespaces_spec.rb | 7 +++++++ .../rename_reserved_paths_migration/v1/rename_projects_spec.rb | 7 +++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb index 434167b0f88..f85f2d90f02 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb @@ -43,15 +43,15 @@ module Gitlab reverts_for_type('namespace') do |path_before_rename, current_path| matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path) namespace = MigrationClasses::Namespace.joins(:route) - .where(matches_path).first.becomes(MigrationClasses::Namespace) + .where(matches_path).first&.becomes(MigrationClasses::Namespace) if namespace perform_rename(namespace, current_path, path_before_rename) rename_namespace_dependencies(namespace, current_path, path_before_rename) else - say "Couldn't rename namespace##{namespace.id} from #{current_path} "\ - " back to #{path_before_rename}, namespace no longer exists" + say "Couldn't rename namespace from #{current_path} back to #{path_before_rename} "\ + "namespace was renamed, or no longer exists at the expected path" end end end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb index 061f531f1d5..8f00e957f85 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb @@ -38,8 +38,10 @@ module Gitlab move_project_folders(project, current_path, path_before_rename) else - say "Couldn't rename project##{project.id} from #{current_path} "\ - "back to #{path_before_rename}, project no longer exists" + say "Couldn't rename Project from #{current_path} back to "\ + "#{path_before_rename}, project was renamed or no longer "\ + "exists at the expected path." + end end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 5fa3d809c7a..803e923b4a5 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -278,5 +278,12 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : expect(File.directory?(expected_path)).to be_truthy end + + it "doesn't break when the namespace was renamed" do + subject.rename_namespace(namespace) + namespace.update_attributes!(path: 'renamed-afterwards') + + expect { subject.revert_renames }.not_to raise_error + end end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index ca741a4989b..0e240a5ccf1 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -149,5 +149,12 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr expect(File.directory?(expected_path)).to be_truthy end + + it "doesn't break when the project was renamed" do + subject.rename_project(project) + project.update_attributes!(path: 'renamed-afterwards') + + expect { subject.revert_renames }.not_to raise_error + end end end -- cgit v1.2.1 From d6a0c288c89765fa8f0e96aedefc608dd7025491 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 12 Jun 2017 19:19:00 +0200 Subject: Use the migration name as a key in redis --- lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb | 2 +- .../database/rename_reserved_paths_migration/v1/rename_base_spec.rb | 2 +- spec/support/fake_migration_classes.rb | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index 3bf549a56eb..ce82a57fe7e 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -144,7 +144,7 @@ module Gitlab end def redis_key_for_type(type) - "rename:#{migration.version}:#{type}" + "rename:#{migration.name}:#{type}" end def file_storage? diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index f4e19f09419..2ce3fc0b497 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -231,7 +231,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca describe '#track_rename', redis: true do it 'tracks a rename in redis' do - key = 'rename:20170316163845:namespace' + key = 'rename:FakeRenameReservedPathMigrationV1:namespace' subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed') diff --git a/spec/support/fake_migration_classes.rb b/spec/support/fake_migration_classes.rb index 33cd1043132..b0fc8422857 100644 --- a/spec/support/fake_migration_classes.rb +++ b/spec/support/fake_migration_classes.rb @@ -4,4 +4,8 @@ class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration def version '20170316163845' end + + def name + "FakeRenameReservedPathMigrationV1" + end end -- cgit v1.2.1 From 171f2d97dcc508ff6031248c6f78fc8ba75e939c Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 14 Jun 2017 15:20:03 +0200 Subject: Keep failed renames in redis --- .../rename_reserved_paths_migration/v1/rename_base.rb | 14 +++++++++++++- .../v1/rename_base_spec.rb | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index ce82a57fe7e..060203fda12 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -134,12 +134,24 @@ module Gitlab def reverts_for_type(type) key = redis_key_for_type(type) + Gitlab::Redis.with do |redis| + failed_reverts = [] + while rename_info = redis.lpop(key) path_before_rename, path_after_rename = JSON.parse(rename_info) say "renaming #{type} from #{path_after_rename} back to #{path_before_rename}" - yield(path_before_rename, path_after_rename) + begin + yield(path_before_rename, path_after_rename) + rescue StandardError => e + failed_reverts << rename_info + say "Renaming #{type} from back to #{path_before_rename} failed. "\ + "Review the error and try again by running the `down` action. \n"\ + "#{e.message}: \n #{e.backtrace.join("\n")}" + end end + + failed_reverts.each { |rename_info| redis.lpush(key, rename_info) } end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 2ce3fc0b497..8813f129ef5 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -257,5 +257,24 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca expect { |b| subject.reverts_for_type('namespace', &b) } .to yield_with_args('namespace_path', 'new_namespace_path') end + + it 'keeps the revert in redis if it failed' do + subject.track_rename('project', 'old_path', 'new_path') + + subject.reverts_for_type('project') do + raise 'whatever happens, keep going!' + end + + key = 'rename:FakeRenameReservedPathMigrationV1:project' + stored_renames = nil + rename_count = 0 + Gitlab::Redis.with do |redis| + stored_renames = redis.lrange(key, 0, 1) + rename_count = redis.llen(key) + end + + expect(rename_count).to eq(1) + expect(JSON.parse(stored_renames.first)).to eq(%w(old_path new_path)) + end end end -- cgit v1.2.1 From 8c850380a9908542c50c7dc1226bf99274eb48e6 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 14 Jun 2017 15:21:51 +0200 Subject: Add punctuation to log messages --- .../database/rename_reserved_paths_migration/v1/rename_base.rb | 5 +++-- .../database/rename_reserved_paths_migration/v1/rename_namespaces.rb | 2 +- .../database/rename_reserved_paths_migration/v1/rename_projects.rb | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index 060203fda12..68dd6f9a5b3 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -145,8 +145,9 @@ module Gitlab yield(path_before_rename, path_after_rename) rescue StandardError => e failed_reverts << rename_info - say "Renaming #{type} from back to #{path_before_rename} failed. "\ - "Review the error and try again by running the `down` action. \n"\ + say "Renaming #{type} from #{path_after_rename} back to "\ + "#{path_before_rename} failed. Review the error and try "\ + "again by running the `down` action. \n"\ "#{e.message}: \n #{e.backtrace.join("\n")}" end end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb index f85f2d90f02..05b86f32ce2 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb @@ -50,7 +50,7 @@ module Gitlab rename_namespace_dependencies(namespace, current_path, path_before_rename) else - say "Couldn't rename namespace from #{current_path} back to #{path_before_rename} "\ + say "Couldn't rename namespace from #{current_path} back to #{path_before_rename}, "\ "namespace was renamed, or no longer exists at the expected path" end end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb index 8f00e957f85..75a75f61953 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb @@ -33,12 +33,13 @@ module Gitlab matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path) project = MigrationClasses::Project.joins(:route) .where(matches_path).first + if project perform_rename(project, current_path, path_before_rename) move_project_folders(project, current_path, path_before_rename) else - say "Couldn't rename Project from #{current_path} back to "\ + say "Couldn't rename project from #{current_path} back to "\ "#{path_before_rename}, project was renamed or no longer "\ "exists at the expected path." -- cgit v1.2.1 From 589b0da7c84280fc24a3b0f1773d0de8022c291e Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 16 Jun 2017 13:39:23 +0200 Subject: Add changelog --- changelogs/unreleased/bvl-rename-all-reserved-paths.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/bvl-rename-all-reserved-paths.yml diff --git a/changelogs/unreleased/bvl-rename-all-reserved-paths.yml b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml new file mode 100644 index 00000000000..f37f2fa94ae --- /dev/null +++ b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml @@ -0,0 +1,4 @@ +--- +title: Rename all reserved paths that could have been created +merge_request: 11713 +author: -- cgit v1.2.1 From 66ba0b0a3444eb5063803df316be1cd14e3ef605 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 21 Jun 2017 15:27:25 +0200 Subject: Clear the cache for projects one-by-one --- .../v1/rename_base.rb | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index 68dd6f9a5b3..74f6984a6bb 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -102,24 +102,26 @@ module Gitlab end def remove_cached_html_for_projects(project_ids) - update_column_in_batches(:projects, :description_html, nil) do |table, query| - query.where(table[:id].in(project_ids)) - end + project_ids.each do |project_id| + update_column_in_batches(:projects, :description_html, nil) do |table, query| + query.where(table[:id].eq(project_id)) + end - update_column_in_batches(:issues, :description_html, nil) do |table, query| - query.where(table[:project_id].in(project_ids)) - end + update_column_in_batches(:issues, :description_html, nil) do |table, query| + query.where(table[:project_id].eq(project_id)) + end - update_column_in_batches(:merge_requests, :description_html, nil) do |table, query| - query.where(table[:target_project_id].in(project_ids)) - end + update_column_in_batches(:merge_requests, :description_html, nil) do |table, query| + query.where(table[:target_project_id].eq(project_id)) + end - update_column_in_batches(:notes, :note_html, nil) do |table, query| - query.where(table[:project_id].in(project_ids)) - end + update_column_in_batches(:notes, :note_html, nil) do |table, query| + query.where(table[:project_id].eq(project_id)) + end - update_column_in_batches(:milestones, :description_html, nil) do |table, query| - query.where(table[:project_id].in(project_ids)) + update_column_in_batches(:milestones, :description_html, nil) do |table, query| + query.where(table[:project_id].eq(project_id)) + end end end -- cgit v1.2.1 From 79cdacc5d464cfb47076efe27e3d6ffe32ff1dad Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 21 Jun 2017 15:37:21 +0200 Subject: Disable statement timeout --- db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb index 9ec59913b66..9441b236c8d 100644 --- a/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb +++ b/db/post_migrate/20170525140254_rename_all_reserved_paths_again.rb @@ -98,12 +98,16 @@ class RenameAllReservedPathsAgain < ActiveRecord::Migration ].freeze def up + disable_statement_timeout + TOP_LEVEL_ROUTES.each { |route| rename_root_paths(route) } PROJECT_WILDCARD_ROUTES.each { |route| rename_wildcard_paths(route) } GROUP_ROUTES.each { |route| rename_child_paths(route) } end def down + disable_statement_timeout + revert_renames end end -- cgit v1.2.1 From 60561aca22f7abbace1b46bc8312cd2192c02344 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 23 Jun 2017 20:59:38 +0200 Subject: Update routes directly by ID instead of filtering by path --- .../rename_reserved_paths_migration/v1/rename_base.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index 74f6984a6bb..c7ce9749eba 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -6,6 +6,7 @@ module Gitlab attr_reader :paths, :migration delegate :update_column_in_batches, + :execute, :replace_sql, :say, to: :migration @@ -42,14 +43,21 @@ module Gitlab end def rename_routes(old_full_path, new_full_path) + routes = Route.arel_table + main_route_ids = routes.project(routes[:id]).where(routes[:path].matches(old_full_path)) + child_route_ids = routes.project(routes[:id]).where(routes[:path].matches("#{old_full_path}/%")) + matching_ids = main_route_ids.union(child_route_ids) + ids = execute(matching_ids.to_sql).map { |entry| entry['id'] } + replace_statement = replace_sql(Route.arel_table[:path], old_full_path, new_full_path) - update_column_in_batches(:routes, :path, replace_statement) do |table, query| - path_or_children = table[:path].matches_any([old_full_path, "#{old_full_path}/%"]) - query.where(path_or_children) - end + update = Arel::UpdateManager.new(ActiveRecord::Base) + .table(routes) + .set([[routes[:path], replace_statement]]) + .where(routes[:id].in(ids)) + execute(update.to_sql) end def rename_path(namespace_path, path_was) -- cgit v1.2.1 From 259f36c04d5b967aad4cd9c1743ad03b124a638e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 26 Jun 2017 15:35:42 +0000 Subject: Merge branch 'update-9-3-dependencies-license-list' into '9-3-stable' Update dependencies list for 9.3 See merge request !12042 --- vendor/licenses.csv | 108 ++++++++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/vendor/licenses.csv b/vendor/licenses.csv index a8e7f5e3ea9..5dcac5947a1 100644 --- a/vendor/licenses.csv +++ b/vendor/licenses.csv @@ -2,7 +2,7 @@ RedCloth,4.3.2,MIT abbrev,1.0.9,ISC accepts,1.3.3,MIT ace-rails-ap,4.1.2,MIT -acorn,4.0.11,MIT +acorn,5.0.3,MIT acorn-dynamic-import,2.0.1,MIT acorn-jsx,3.0.1,MIT actionmailer,4.2.8,MIT @@ -53,6 +53,7 @@ assert-plus,0.2.0,MIT async,0.2.10,MIT async-each,1.0.1,MIT asynckit,0.4.0,MIT +atomic,1.1.99,Apache 2.0 attr_encrypted,3.0.3,MIT attr_required,1.0.0,MIT autoparse,0.3.3,Apache 2.0 @@ -151,8 +152,9 @@ blob,0.0.4,unknown block-stream,0.0.9,ISC bluebird,3.4.7,MIT bn.js,4.11.6,MIT -body-parser,1.16.0,MIT +body-parser,1.17.2,MIT boom,2.10.1,New BSD +bootsnap,1.0.0,MIT bootstrap-sass,3.3.6,MIT brace-expansion,1.1.6,MIT braces,1.8.5,MIT @@ -222,9 +224,10 @@ compression,1.6.2,MIT compression-webpack-plugin,0.3.2,MIT concat-map,0.0.1,MIT concat-stream,1.6.0,MIT +concurrent-ruby-ext,1.0.5,MIT config-chain,1.1.11,MIT configstore,1.4.0,Simplified BSD -connect,3.5.0,MIT +connect,3.6.2,MIT connect-history-api-fallback,1.3.0,MIT connection_pool,2.2.1,MIT console-browserify,1.1.0,MIT @@ -262,8 +265,9 @@ dashdash,1.14.1,MIT date-now,0.1.4,MIT de-indent,1.0.2,MIT debug,2.6.0,MIT +debugger-ruby_core_source,1.3.8,MIT decamelize,1.2.0,MIT -deckar01-task_list,1.0.6,MIT +deckar01-task_list,2.0.0,MIT deep-extend,0.4.1,MIT deep-is,0.1.3,MIT default-require-extensions,1.0.0,MIT @@ -312,8 +316,8 @@ emojis-list,2.1.0,MIT encodeurl,1.0.1,MIT encryptor,3.0.0,MIT end-of-stream,1.0.0,MIT -engine.io,1.8.2,MIT -engine.io-client,1.8.2,MIT +engine.io,1.8.3,MIT +engine.io-client,1.8.3,MIT engine.io-parser,1.3.2,MIT enhanced-resolve,3.1.0,MIT ent,2.2.0,MIT @@ -349,7 +353,8 @@ esprima,3.1.3,Simplified BSD esrecurse,4.1.0,Simplified BSD estraverse,4.1.1,Simplified BSD esutils,2.0.2,BSD -etag,1.7.0,MIT +et-orbi,1.0.3,MIT +etag,1.8.0,MIT eve-raphael,0.5.0,Apache 2.0 event-emitter,0.3.4,MIT event-stream,3.3.4,MIT @@ -364,12 +369,11 @@ expand-braces,0.1.2,MIT expand-brackets,0.1.5,MIT expand-range,1.8.2,MIT exports-loader,0.6.4,MIT -express,4.14.1,MIT +express,4.15.3,MIT expression_parser,0.9.0,MIT extend,3.0.0,MIT extglob,0.3.2,MIT extlib,0.9.16,MIT -extract-zip,1.5.0,Simplified BSD extsprintf,1.0.2,MIT faraday,0.11.0,MIT faraday_middleware,0.11.0.1,MIT @@ -378,7 +382,6 @@ fast-levenshtein,2.0.6,MIT fast_gettext,1.4.0,"MIT,ruby" fastparse,1.1.1,MIT faye-websocket,0.7.3,MIT -fd-slicer,1.0.1,MIT ffi,1.9.10,BSD figures,1.7.0,MIT file-entry-cache,2.0.0,MIT @@ -387,13 +390,16 @@ filename-regex,2.0.0,MIT fileset,2.0.3,MIT filesize,3.3.0,New BSD fill-range,2.2.3,MIT -finalhandler,0.5.1,MIT +finalhandler,1.0.3,MIT find-cache-dir,0.1.1,MIT find-root,0.1.2,MIT find-up,2.1.0,MIT flat-cache,1.2.2,MIT flatten,1.0.2,MIT +flipper,0.10.2,MIT +flipper-active_record,0.10.2,MIT flowdock,0.7.1,MIT +fog-aliyun,0.1.0,MIT fog-aws,0.13.0,MIT fog-core,1.44.1,MIT fog-google,0.5.0,MIT @@ -409,9 +415,8 @@ forever-agent,0.6.1,Apache 2.0 form-data,2.1.2,MIT formatador,0.2.5,MIT forwarded,0.1.0,MIT -fresh,0.3.0,MIT +fresh,0.5.0,MIT from,0.1.7,MIT -fs-extra,1.0.0,MIT fs.realpath,1.0.0,ISC fsevents,,unknown fstream,1.0.10,ISC @@ -427,7 +432,7 @@ get_process_mem,0.2.0,MIT getpass,0.1.6,MIT gettext_i18n_rails,1.8.0,MIT gettext_i18n_rails_js,1.2.0,MIT -gitaly,0.6.0,MIT +gitaly,0.8.0,MIT github-linguist,4.7.6,MIT github-markup,1.4.0,MIT gitlab-flowdock-git-hook,1.0.1,MIT @@ -467,7 +472,6 @@ has-flag,1.0.0,MIT has-unicode,2.0.1,ISC hash-sum,1.0.2,MIT hash.js,1.0.3,MIT -hasha,2.2.0,MIT hashie,3.5.5,MIT hashie-forbidden_attributes,0.1.1,MIT hawk,3.1.3,New BSD @@ -487,7 +491,7 @@ htmlparser2,3.9.2,MIT http,0.9.8,MIT http-cookie,1.0.3,MIT http-deceiver,1.2.7,MIT -http-errors,1.5.1,MIT +http-errors,1.6.1,MIT http-form_data,1.0.1,MIT http-proxy,1.16.2,MIT http-proxy-middleware,0.17.4,MIT @@ -516,7 +520,7 @@ inquirer,0.12.0,MIT interpret,1.0.1,MIT invariant,2.2.2,New BSD invert-kv,1.0.0,MIT -ipaddr.js,1.2.0,MIT +ipaddr.js,1.3.0,MIT ipaddress,0.8.3,MIT is-absolute,0.2.6,MIT is-absolute-url,2.1.0,MIT @@ -563,7 +567,7 @@ istanbul-lib-instrument,1.4.2,New BSD istanbul-lib-report,1.0.0-alpha.3,New BSD istanbul-lib-source-maps,1.1.0,New BSD istanbul-reports,1.0.1,New BSD -jasmine-core,2.5.2,MIT +jasmine-core,2.6.3,MIT jasmine-jquery,2.1.1,MIT jed,1.1.1,MIT jira-ruby,1.1.2,MIT @@ -587,7 +591,6 @@ json-stable-stringify,1.0.1,MIT json-stringify-safe,5.0.1,ISC json3,3.3.2,MIT json5,0.5.1,MIT -jsonfile,2.4.0,MIT jsonify,0.0.0,Public Domain jsonpointer,4.0.1,MIT jsprim,1.3.1,MIT @@ -595,17 +598,14 @@ jszip,3.1.3,(MIT OR GPL-3.0) jszip-utils,0.0.2,MIT or GPLv3 jwt,1.5.6,MIT kaminari,0.17.0,MIT -karma,1.4.1,MIT +karma,1.7.0,MIT karma-coverage-istanbul-reporter,0.2.0,MIT karma-jasmine,1.1.0,MIT karma-mocha-reporter,2.2.2,MIT -karma-phantomjs-launcher,1.0.2,MIT karma-sourcemap-loader,0.3.7,MIT karma-webpack,2.0.2,MIT -kew,0.7.0,Apache 2.0 kgio,2.10.0,LGPL-2.1+ kind-of,3.1.0,MIT -klaw,1.3.1,MIT kubeclient,2.2.0,MIT latest-version,1.0.1,MIT launchy,2.4.3,ISC @@ -667,7 +667,7 @@ methods,1.1.2,MIT micromatch,2.3.11,MIT miller-rabin,4.0.0,MIT mime,1.3.4,MIT -mime-db,1.26.0,MIT +mime-db,1.27.0,MIT mime-types,2.99.3,"MIT,Artistic-2.0,GPL-2.0" mimemagic,0.3.0,MIT mini_portile2,2.1.0,MIT @@ -675,16 +675,20 @@ minimalistic-assert,1.0.0,ISC minimatch,3.0.3,ISC minimist,0.0.8,MIT mkdirp,0.5.1,MIT +mmap2,2.2.6,ruby moment,2.17.1,MIT mousetrap,1.4.6,Apache 2.0 mousetrap-rails,1.4.6,"MIT,Apache" ms,0.7.2,MIT +msgpack,1.1.0,Apache 2.0 multi_json,1.12.1,MIT multi_xml,0.6.0,MIT multipart-post,2.0.0,MIT mustermann,0.4.0,MIT mustermann-grape,0.4.0,MIT mute-stream,0.0.5,ISC +mysql2,0.3.20,MIT +name-all-modules-plugin,1.0.1,MIT nan,2.5.1,MIT natural-compare,1.4.0,MIT negotiator,0.6.1,MIT @@ -774,9 +778,16 @@ path-type,1.1.0,MIT pause-stream,0.0.11,"MIT,Apache2" pbkdf2,3.0.9,MIT pdfjs-dist,1.8.252,Apache 2.0 -pend,1.2.0,MIT +peek,1.0.1,MIT +peek-gc,0.0.2,MIT +peek-host,1.0.0,MIT +peek-mysql2,1.1.0,MIT +peek-performance_bar,1.2.1,MIT +peek-pg,1.3.0,MIT +peek-rblineprof,0.2.0,MIT +peek-redis,1.2.0,MIT +peek-sidekiq,1.0.3,MIT pg,0.18.4,"BSD,ruby,GPL" -phantomjs-prebuilt,2.1.14,Apache 2.0 pify,2.3.0,MIT pikaday,1.5.1,"BSD,MIT" pinkie,2.0.4,MIT @@ -833,8 +844,9 @@ private,0.1.7,MIT process,0.11.9,MIT process-nextick-args,1.0.7,MIT progress,1.1.8,MIT +prometheus-client-mmap,0.7.0.beta5,Apache 2.0 proto-list,1.2.4,ISC -proxy-addr,1.1.3,MIT +proxy-addr,1.1.4,MIT prr,0.0.0,MIT ps-tree,1.1.0,MIT pseudomap,1.0.2,ISC @@ -843,7 +855,7 @@ punycode,1.4.1,MIT pyu-ruby-sasl,0.0.3.3,MIT q,1.5.0,MIT qjobs,1.1.5,MIT -qs,6.2.0,New BSD +qs,6.3.0,New BSD query-string,4.3.2,MIT querystring,0.2.0,MIT querystring-es3,0.2.1,MIT @@ -868,7 +880,7 @@ randomatic,1.1.6,MIT randombytes,2.0.3,MIT range-parser,1.2.0,MIT raphael,2.2.7,MIT -raven-js,3.15.0,Simplified BSD +raven-js,3.14.0,Simplified BSD raw-body,2.2.0,MIT raw-loader,0.5.1,MIT rc,1.1.6,(BSD-2-Clause OR MIT OR Apache-2.0) @@ -906,7 +918,6 @@ repeat-element,1.1.2,MIT repeat-string,1.6.1,MIT repeating,2.0.1,MIT request,2.79.0,Apache 2.0 -request-progress,2.0.1,MIT request_store,1.3.1,MIT require-directory,2.1.1,MIT require-from-string,1.2.1,MIT @@ -924,7 +935,7 @@ rimraf,2.5.4,ISC rinku,2.0.0,ISC ripemd160,1.0.1,New BSD rotp,2.1.2,MIT -rouge,2.0.7,MIT +rouge,2.1.0,MIT rqrcode,0.7.0,MIT rqrcode-rails3,0.1.7,MIT ruby-fogbugz,0.2.1,MIT @@ -933,7 +944,7 @@ ruby-saml,1.4.1,MIT ruby_parser,3.8.4,MIT rubyntlm,0.5.2,MIT rubypants,0.2.0,BSD -rufus-scheduler,3.1.10,MIT +rufus-scheduler,3.4.0,MIT rugged,0.25.1.1,MIT run-async,0.1.0,MIT rx-lite,3.1.2,Apache 2.0 @@ -952,20 +963,20 @@ select2,3.5.2-browserify,unknown select2-rails,3.5.9.3,MIT semver,5.3.0,ISC semver-diff,2.1.0,MIT -send,0.14.2,MIT +send,0.15.3,MIT sentry-raven,2.4.0,Apache 2.0 serve-index,1.8.0,MIT -serve-static,1.11.2,MIT +serve-static,1.12.3,MIT set-blocking,2.0.0,ISC set-immediate-shim,1.0.1,MIT setimmediate,1.0.5,MIT -setprototypeof,1.0.2,ISC +setprototypeof,1.0.3,ISC settingslogic,2.0.9,MIT sexp_processor,4.8.0,MIT sha.js,2.4.8,MIT shelljs,0.7.6,New BSD sidekiq,5.0.0,LGPL -sidekiq-cron,0.4.4,MIT +sidekiq-cron,0.6.0,MIT sidekiq-limit_fetch,3.4.0,MIT sigmund,1.0.1,ISC signal-exit,3.0.2,ISC @@ -975,9 +986,9 @@ slash,1.0.0,MIT slice-ansi,0.0.4,MIT slide,1.1.6,ISC sntp,1.0.9,BSD -socket.io,1.7.2,MIT +socket.io,1.7.3,MIT socket.io-adapter,0.5.0,MIT -socket.io-client,1.7.2,MIT +socket.io-client,1.7.3,MIT socket.io-parser,2.3.1,MIT sockjs,0.3.18,MIT sockjs-client,1.0.1,MIT @@ -1030,7 +1041,6 @@ thread_safe,0.3.6,Apache 2.0 three,0.84.0,MIT three-orbit-controls,82.1.0,MIT three-stl-loader,1.0.4,MIT -throttleit,1.0.0,MIT through,2.3.8,MIT tilt,2.0.6,MIT timeago.js,2.0.5,MIT @@ -1038,7 +1048,7 @@ timed-out,2.0.0,MIT timers-browserify,2.0.2,MIT timfel-krb5-auth,0.8.3,LGPL tiny-emitter,1.1.0,MIT -tmp,0.0.28,MIT +tmp,0.0.31,MIT to-array,0.1.4,MIT to-arraybuffer,1.0.1,MIT to-fast-properties,1.0.2,MIT @@ -1054,15 +1064,15 @@ tty-browserify,0.0.0,MIT tunnel-agent,0.4.3,Apache 2.0 tweetnacl,0.14.5,Unlicense type-check,0.3.2,MIT -type-is,1.6.14,MIT +type-is,1.6.15,MIT typedarray,0.0.6,MIT tzinfo,1.2.2,MIT u2f,0.2.1,MIT uglifier,2.7.2,MIT -uglify-js,2.8.21,Simplified BSD +uglify-js,2.8.27,Simplified BSD uglify-to-browserify,1.0.2,MIT uid-number,0.0.6,ISC -ultron,1.0.2,MIT +ultron,1.1.0,MIT unc-path-regex,0.1.2,MIT undefsafe,0.0.3,MIT / http://rem.mit-license.org underscore,1.8.3,MIT @@ -1081,14 +1091,14 @@ url-loader,0.5.8,MIT url-parse,1.0.5,MIT url_safe_base64,0.2.2,MIT user-home,2.0.0,MIT -useragent,2.1.12,MIT +useragent,2.1.13,MIT util,0.10.3,MIT util-deprecate,1.0.2,MIT utils-merge,1.0.0,MIT uuid,3.0.1,MIT validate-npm-package-license,3.0.1,Apache 2.0 validates_hostname,1.0.6,MIT -vary,1.1.0,MIT +vary,1.1.1,MIT vendors,1.0.1,MIT verror,1.3.6,MIT version_sorter,2.1.0,MIT @@ -1107,8 +1117,8 @@ vue-template-es2015-compiler,1.5.1,MIT warden,1.2.6,MIT watchpack,1.3.1,MIT wbuf,1.7.2,MIT -webpack,2.3.3,MIT -webpack-bundle-analyzer,2.3.0,MIT +webpack,2.6.1,MIT +webpack-bundle-analyzer,2.8.2,MIT webpack-dev-middleware,1.10.0,MIT webpack-dev-server,2.4.2,MIT webpack-rails,0.9.10,MIT @@ -1127,14 +1137,14 @@ wrap-ansi,2.1.0,MIT wrappy,1.0.2,ISC write,0.2.1,MIT write-file-atomic,1.3.1,ISC -ws,1.1.1,MIT +ws,2.3.1,MIT wtf-8,1.0.0,MIT xdg-basedir,2.0.0,MIT +xml-simple,1.1.5,ruby xmlhttprequest-ssl,1.5.3,MIT xtend,4.0.1,MIT y18n,3.2.1,ISC yallist,2.1.2,ISC yargs,3.10.0,MIT yargs-parser,4.2.1,ISC -yauzl,2.4.1,MIT yeast,0.1.2,MIT -- cgit v1.2.1 From d4a3474b9b7321898a00ddcbaf651099ab96b2ba Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 26 Jun 2017 17:21:40 +0100 Subject: Fix Gitlab::Database.bulk_insert for non-UTF-8 data --- lib/gitlab/database.rb | 2 +- spec/lib/gitlab/database_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 0d5a7cf0694..d7dab584a44 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -93,7 +93,7 @@ module Gitlab row.values_at(*keys).map { |value| connection.quote(value) } end - connection.execute <<-EOF.strip_heredoc + connection.execute <<-EOF INSERT INTO #{table} (#{columns.join(', ')}) VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')} EOF diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 5e6206b96c7..cbf6c35356e 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -176,6 +176,10 @@ describe Gitlab::Database, lib: true do described_class.bulk_insert('test', rows) end + + it 'handles non-UTF-8 data' do + expect { described_class.bulk_insert('test', [{ a: "\255" }]) }.not_to raise_error + end end describe '.create_connection_pool' do -- cgit v1.2.1 From 7eb26c7ff7a78df9cb8fe5b30d48c80ce4eb8a99 Mon Sep 17 00:00:00 2001 From: Robin Bobbitt Date: Mon, 12 Jun 2017 12:13:22 -0400 Subject: Provide hint to create a personal access token for Git over HTTP If internal auth is disabled and user is not an LDAP user, present the user with an alert to create a personal access token if he does not have one already. --- app/helpers/button_helper.rb | 11 +++- app/helpers/projects_helper.rb | 17 +++++ app/models/user.rb | 8 ++- app/views/shared/_no_password.html.haml | 7 +- app/views/shared/_no_ssh.html.haml | 4 +- .../unreleased/pat-alert-when-signin-disabled.yml | 4 ++ spec/features/login_spec.rb | 23 +------ spec/features/projects/no_password_spec.rb | 69 ++++++++++++++++++++ spec/helpers/button_helper_spec.rb | 65 ++++++++++++++++++ spec/helpers/projects_helper_spec.rb | 76 ++++++++++++++++++++++ spec/support/login_helpers.rb | 21 ++++++ 11 files changed, 274 insertions(+), 31 deletions(-) create mode 100644 changelogs/unreleased/pat-alert-when-signin-disabled.yml create mode 100644 spec/features/projects/no_password_spec.rb create mode 100644 spec/helpers/button_helper_spec.rb diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 00464810054..ba84dbe4a7a 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -50,10 +50,17 @@ module ButtonHelper def http_clone_button(project, placement = 'right', append_link: true) klass = 'http-selector' - klass << ' has-tooltip' if current_user.try(:require_password?) + klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?) protocol = gitlab_config.protocol.upcase + tooltip_title = + if current_user.try(:require_password?) + _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol } + else + _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol } + end + content_tag (append_link ? :a : :span), protocol, class: klass, href: (project.http_url_to_repo if append_link), @@ -61,7 +68,7 @@ module ButtonHelper html: true, placement: placement, container: 'body', - title: _("Set a password on your account to pull or push via %{protocol}") % { protocol: protocol } + title: tooltip_title } end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d10e0bd45b0..c04b1419a19 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -198,6 +198,23 @@ module ProjectsHelper .load_in_batch_for_projects(projects) end + def show_no_ssh_key_message? + cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? + end + + def show_no_password_message? + cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && + ( current_user.require_password? || current_user.require_personal_access_token? ) + end + + def link_to_set_password + if current_user.require_password? + link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path + else + link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path + end + end + private def repo_children_classes(field) diff --git a/app/models/user.rb b/app/models/user.rb index 650b64e7551..6dd1b1415d6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -570,7 +570,13 @@ class User < ActiveRecord::Base end def require_password? - password_automatically_set? && !ldap_user? + password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled? + end + + def require_personal_access_token? + return false if current_application_settings.signin_enabled? || ldap_user? + + PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none? end def can_change_username? diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml index b561e6dc248..9b1a467df6b 100644 --- a/app/views/shared/_no_password.html.haml +++ b/app/views/shared/_no_password.html.haml @@ -1,9 +1,8 @@ -- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password? +- if show_no_password_message? .no-password-message.alert.alert-warning - - set_password_link = link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path - - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: set_password_link } + - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password } - set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params - + = set_password_message.html_safe .alert-link-group = link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put | diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index e7815e28017..17ef5327341 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,8 +1,8 @@ -- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? +- if show_no_ssh_key_message? .no-ssh-key-message.alert.alert-warning - add_ssh_key_link = link_to s_('MissingSSHKeyWarningLink|add an SSH key'), profile_keys_path, class: 'alert-link' - ssh_message = _("You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile") % { add_ssh_key_link: add_ssh_key_link } - #{ ssh_message.html_safe } + = ssh_message.html_safe .alert-link-group = link_to _("Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link' | diff --git a/changelogs/unreleased/pat-alert-when-signin-disabled.yml b/changelogs/unreleased/pat-alert-when-signin-disabled.yml new file mode 100644 index 00000000000..dca3670aeb7 --- /dev/null +++ b/changelogs/unreleased/pat-alert-when-signin-disabled.yml @@ -0,0 +1,4 @@ +--- +title: Provide hint to create a personal access token for Git over HTTP +merge_request: 12105 +author: Robin Bobbitt diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 53b8ba5b0f7..a8055b21cee 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -143,29 +143,8 @@ feature 'Login', feature: true do end context 'logging in via OAuth' do - def saml_config - OpenStruct.new(name: 'saml', label: 'saml', args: { - assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback', - idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52', - idp_sso_target_url: 'https://idp.example.com/sso/saml', - issuer: 'https://localhost:3443/', - name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' - }) - end - - def stub_omniauth_config(messages) - Rails.application.env_config['devise.mapping'] = Devise.mappings[:user] - Rails.application.routes.disable_clear_and_finalize = true - Rails.application.routes.draw do - post '/users/auth/saml' => 'omniauth_callbacks#saml' - end - allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config) - allow(Gitlab.config.omniauth).to receive_messages(messages) - expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') - end - it 'shows 2FA prompt after OAuth login' do - stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config]) + stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config]) user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') gitlab_sign_in_via('saml', user, 'my-uid') diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/no_password_spec.rb new file mode 100644 index 00000000000..30a16e38e3c --- /dev/null +++ b/spec/features/projects/no_password_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +feature 'No Password Alert' do + let(:project) { create(:project, namespace: user.namespace) } + + context 'with internal auth enabled' do + before do + sign_in(user) + visit namespace_project_path(project.namespace, project) + end + + context 'when user has a password' do + let(:user) { create(:user) } + + it 'shows no alert' do + expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account" + end + end + + context 'when user has password automatically set' do + let(:user) { create(:user, password_automatically_set: true) } + + it 'shows a password alert' do + expect(page).to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account" + end + end + end + + context 'with internal auth disabled' do + let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') } + + before do + stub_application_setting(signin_enabled?: false) + stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config]) + end + + context 'when user has no personal access tokens' do + it 'has a personal access token alert' do + gitlab_sign_in_via('saml', user, 'my-uid') + visit namespace_project_path(project.namespace, project) + + expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account" + end + end + + context 'when user has a personal access token' do + it 'shows no alert' do + create(:personal_access_token, user: user) + gitlab_sign_in_via('saml', user, 'my-uid') + visit namespace_project_path(project.namespace, project) + + expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account" + end + end + end + + context 'when user is ldap user' do + let(:user) { create(:omniauth_user, password_automatically_set: true) } + + before do + sign_in(user) + visit namespace_project_path(project.namespace, project) + end + + it 'shows no alert' do + expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you" + end + end +end diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb new file mode 100644 index 00000000000..661327d4432 --- /dev/null +++ b/spec/helpers/button_helper_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe ButtonHelper do + describe 'http_clone_button' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:has_tooltip_class) { 'has-tooltip' } + + def element + element = helper.http_clone_button(project) + + Nokogiri::HTML::DocumentFragment.parse(element).first_element_child + end + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + context 'with internal auth enabled' do + context 'when user has a password' do + it 'shows no tooltip' do + expect(element.attr('class')).not_to include(has_tooltip_class) + end + end + + context 'when user has password automatically set' do + let(:user) { create(:user, password_automatically_set: true) } + + it 'shows a password tooltip' do + expect(element.attr('class')).to include(has_tooltip_class) + expect(element.attr('data-title')).to eq('Set a password on your account to pull or push via HTTP.') + end + end + end + + context 'with internal auth disabled' do + before do + stub_application_setting(signin_enabled?: false) + end + + context 'when user has no personal access tokens' do + it 'has a personal access token tooltip ' do + expect(element.attr('class')).to include(has_tooltip_class) + expect(element.attr('data-title')).to eq('Create a personal access token on your account to pull or push via HTTP.') + end + end + + context 'when user has a personal access token' do + it 'shows no tooltip' do + create(:personal_access_token, user: user) + + expect(element.attr('class')).not_to include(has_tooltip_class) + end + end + end + + context 'when user is ldap user' do + let(:user) { create(:omniauth_user, password_automatically_set: true) } + + it 'shows no tooltip' do + expect(element.attr('class')).not_to include(has_tooltip_class) + end + end + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 9a4086725d2..487d9800707 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -115,6 +115,82 @@ describe ProjectsHelper do end end + describe '#show_no_ssh_key_message?' do + let(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + context 'user has no keys' do + it 'returns true' do + expect(helper.show_no_ssh_key_message?).to be_truthy + end + end + + context 'user has an ssh key' do + it 'returns false' do + create(:personal_key, user: user) + + expect(helper.show_no_ssh_key_message?).to be_falsey + end + end + end + + describe '#show_no_password_message?' do + let(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + context 'user has password set' do + it 'returns false' do + expect(helper.show_no_password_message?).to be_falsey + end + end + + context 'user requires a password' do + let(:user) { create(:user, password_automatically_set: true) } + + it 'returns true' do + expect(helper.show_no_password_message?).to be_truthy + end + end + + context 'user requires a personal access token' do + it 'returns true' do + stub_application_setting(signin_enabled?: false) + + expect(helper.show_no_password_message?).to be_truthy + end + end + end + + describe '#link_to_set_password' do + before do + allow(helper).to receive(:current_user).and_return(user) + end + + context 'user requires a password' do + let(:user) { create(:user, password_automatically_set: true) } + + it 'returns link to set a password' do + expect(helper.link_to_set_password).to match %r{set a password} + end + end + + context 'user requires a personal access token' do + let(:user) { create(:user) } + + it 'returns link to create a personal access token' do + stub_application_setting(signin_enabled?: false) + + expect(helper.link_to_set_password).to match %r{create a personal access token} + end + end + end + describe 'link_to_member' do let(:group) { create(:group) } let(:project) { create(:empty_project, group: group) } diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 879386b5437..4c88958264b 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -89,4 +89,25 @@ module LoginHelpers }) Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml] end + + def mock_saml_config + OpenStruct.new(name: 'saml', label: 'saml', args: { + assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback', + idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52', + idp_sso_target_url: 'https://idp.example.com/sso/saml', + issuer: 'https://localhost:3443/', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + }) + end + + def stub_omniauth_saml_config(messages) + Rails.application.env_config['devise.mapping'] = Devise.mappings[:user] + Rails.application.routes.disable_clear_and_finalize = true + Rails.application.routes.draw do + post '/users/auth/saml' => 'omniauth_callbacks#saml' + end + allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config) + stub_omniauth_setting(messages) + expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') + end end -- cgit v1.2.1 From 35ee21d992a8d2fa65ee7601c8948dbf79b54c53 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 23 Jun 2017 18:52:30 -0500 Subject: remove problematic use of requestAnimationFrame --- app/assets/javascripts/awards_handler.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index b6a12d09042..2bae4f4d955 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -7,10 +7,6 @@ import { emojiMap, emojiAliases, isEmojiNameValid } from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; -const requestAnimationFrame = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.setTimeout; const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence @@ -74,9 +70,7 @@ export default class AwardsHandler { this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => { const $menu = $('.emoji-menu'); if ($menu.length === 0) { - requestAnimationFrame(() => { - this.createEmojiMenu(); - }); + setTimeout(() => this.createEmojiMenu()); } // Prebuild the categoryMap categoryMap = categoryMap || buildCategoryMap(); @@ -221,7 +215,7 @@ export default class AwardsHandler { categoryLabelMap[categoryNameKey], emojisInCategory, ); - requestAnimationFrame(() => { + setTimeout(() => { emojiContentElement.insertAdjacentHTML('beforeend', categoryMarkup); resolve(); }); -- cgit v1.2.1 From b7a0044a70020bf97d9f1d604f126ab23db3dc93 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 24 Jun 2017 01:41:49 -0500 Subject: move normalizeEmojiName method to emoji helper module --- app/assets/javascripts/awards_handler.js | 11 +++-------- app/assets/javascripts/emoji/index.js | 12 +++++++++++- app/assets/javascripts/emoji/is_emoji_name_valid.js | 11 ----------- 3 files changed, 14 insertions(+), 20 deletions(-) delete mode 100644 app/assets/javascripts/emoji/is_emoji_name_valid.js diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 2bae4f4d955..27b61a0c61f 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -3,7 +3,7 @@ import Cookies from 'js-cookie'; import { glEmojiTag } from './behaviors/gl_emoji'; -import { emojiMap, emojiAliases, isEmojiNameValid } from './emoji'; +import { emojiMap, emojiAliases, isEmojiNameValid, normalizeEmojiName } from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; @@ -65,7 +65,6 @@ function renderCategory(name, emojiList, opts = {}) { export default class AwardsHandler { constructor() { this.eventListeners = []; - this.aliases = emojiAliases; // If the user shows intent let's pre-build the menu this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => { const $menu = $('.emoji-menu'); @@ -260,7 +259,7 @@ export default class AwardsHandler { checkMutuality, callback, ) { - const normalizedEmoji = this.normalizeEmojiName(emoji); + const normalizedEmoji = normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); @@ -279,7 +278,7 @@ export default class AwardsHandler { this.checkMutuality(votesBlock, emoji); } this.addEmojiToFrequentlyUsedList(emoji); - const normalizedEmoji = this.normalizeEmojiName(emoji); + const normalizedEmoji = normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); if ($emojiButton.length > 0) { if (this.isActive($emojiButton)) { @@ -469,10 +468,6 @@ export default class AwardsHandler { return $('body, html').animate(options, 200); } - normalizeEmojiName(emoji) { - return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji; - } - addEmojiToFrequentlyUsedList(emoji) { if (isEmojiNameValid(emoji)) { this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji)); diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 1e86ce822d2..ad4cdfb4355 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -1,12 +1,22 @@ import emojiMap from 'emojis/digests.json'; import emojiAliases from 'emojis/aliases.json'; import getUnicodeSupportMap from './unicode_support_map'; -import isEmojiNameValid from './is_emoji_name_valid'; import isEmojiUnicodeSupported from './is_emoji_unicode_supported'; +const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; + +function normalizeEmojiName(name) { + return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name; +} + +function isEmojiNameValid(name) { + return validEmojiNames.indexOf(name) >= 0; +} + export { emojiMap, emojiAliases, + normalizeEmojiName, getUnicodeSupportMap, isEmojiNameValid, isEmojiUnicodeSupported, diff --git a/app/assets/javascripts/emoji/is_emoji_name_valid.js b/app/assets/javascripts/emoji/is_emoji_name_valid.js deleted file mode 100644 index be4aeb32c46..00000000000 --- a/app/assets/javascripts/emoji/is_emoji_name_valid.js +++ /dev/null @@ -1,11 +0,0 @@ -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; - -function isEmojiNameValid(inputName) { - const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; - - return name && emojiMap[name]; -} - -export default isEmojiNameValid; -- cgit v1.2.1 From a7b603a2ac28a5c4c3e175cf4013c12d241b5452 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 24 Jun 2017 01:44:16 -0500 Subject: fix method definition styling --- app/assets/javascripts/awards_handler.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 27b61a0c61f..c91c56ff0a5 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -252,13 +252,7 @@ export default class AwardsHandler { return $menu.css(css); } - addAward( - votesBlock, - awardUrl, - emoji, - checkMutuality, - callback, - ) { + addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { const normalizedEmoji = normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { @@ -269,11 +263,7 @@ export default class AwardsHandler { $('.js-add-award.is-active').removeClass('is-active'); } - addAwardToEmojiBar( - votesBlock, - emoji, - checkForMutuality, - ) { + addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) { if (checkForMutuality || checkForMutuality === null) { this.checkMutuality(votesBlock, emoji); } -- cgit v1.2.1 From 73c5c0013313ba184837da178bf3fe36d7dfecc8 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 24 Jun 2017 01:44:57 -0500 Subject: move emojiAlias logic into helper module --- app/assets/javascripts/awards_handler.js | 22 ++++++---------------- app/assets/javascripts/emoji/index.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index c91c56ff0a5..677f997895a 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -3,7 +3,7 @@ import Cookies from 'js-cookie'; import { glEmojiTag } from './behaviors/gl_emoji'; -import { emojiMap, emojiAliases, isEmojiNameValid, normalizeEmojiName } from './emoji'; +import { emojiMap, filterEmojiNamesByAlias, isEmojiNameValid, normalizeEmojiName } from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; @@ -511,21 +511,11 @@ export default class AwardsHandler { } } - findMatchingEmojiElements(term) { - const safeTerm = term.toLowerCase(); - - const namesMatchingAlias = []; - Object.keys(emojiAliases).forEach((alias) => { - if (alias.indexOf(safeTerm) >= 0) { - namesMatchingAlias.push(emojiAliases[alias]); - } - }); - const $matchingElements = namesMatchingAlias.concat(safeTerm) - .reduce( - ($result, searchTerm) => - $result.add($(`.emoji-menu-list:not(.frequent-emojis) [data-name*="${searchTerm}"]`)), - $([]), - ); + findMatchingEmojiElements(query) { + const emojiMatches = filterEmojiNamesByAlias(query); + const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]'); + const $matchingElements = $emojiElements + .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0); return $matchingElements.closest('li').clone(); } diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index ad4cdfb4355..e1edf376936 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -13,10 +13,21 @@ function isEmojiNameValid(name) { return validEmojiNames.indexOf(name) >= 0; } +function filterEmojiNames(filter) { + const match = filter.toLowerCase(); + return validEmojiNames.filter(name => name.indexOf(match) >= 0); +} + +function filterEmojiNamesByAlias(filter) { + return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name))); +} + export { emojiMap, emojiAliases, normalizeEmojiName, + filterEmojiNames, + filterEmojiNamesByAlias, getUnicodeSupportMap, isEmojiNameValid, isEmojiUnicodeSupported, -- cgit v1.2.1 From 8455e8c481425ffa661c82fb9cffe3c053035fb5 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 24 Jun 2017 02:25:04 -0500 Subject: move emoji category index to helper method --- app/assets/javascripts/awards_handler.js | 35 ++++++++------------------------ app/assets/javascripts/emoji/index.js | 24 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 677f997895a..c363afc09b6 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -3,15 +3,18 @@ import Cookies from 'js-cookie'; import { glEmojiTag } from './behaviors/gl_emoji'; -import { emojiMap, filterEmojiNamesByAlias, isEmojiNameValid, normalizeEmojiName } from './emoji'; +import { + filterEmojiNamesByAlias, + getEmojiByCategory, + isEmojiNameValid, + normalizeEmojiName, +} from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence -let categoryMap = null; - const categoryLabelMap = { activity: 'Activity', people: 'People', @@ -23,26 +26,6 @@ const categoryLabelMap = { flags: 'Flags', }; -function buildCategoryMap() { - return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => { - const emojiInfo = emojiMap[emojiNameKey]; - if (currentCategoryMap[emojiInfo.category]) { - currentCategoryMap[emojiInfo.category].push(emojiNameKey); - } - - return currentCategoryMap; - }, { - activity: [], - people: [], - nature: [], - food: [], - travel: [], - objects: [], - symbols: [], - flags: [], - }); -} - function renderCategory(name, emojiList, opts = {}) { return `
@@ -71,8 +54,6 @@ export default class AwardsHandler { if ($menu.length === 0) { setTimeout(() => this.createEmojiMenu()); } - // Prebuild the categoryMap - categoryMap = categoryMap || buildCategoryMap(); }); this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => { e.stopPropagation(); @@ -158,7 +139,7 @@ export default class AwardsHandler { this.isCreatingEmojiMenu = true; // Render the first category - categoryMap = categoryMap || buildCategoryMap(); + const categoryMap = getEmojiByCategory(); const categoryNameKey = Object.keys(categoryMap)[0]; const emojisInCategory = categoryMap[categoryNameKey]; const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); @@ -198,7 +179,7 @@ export default class AwardsHandler { } this.isAddingRemainingEmojiMenuCategories = true; - categoryMap = categoryMap || buildCategoryMap(); + const categoryMap = getEmojiByCategory(); // Avoid the jank and render the remaining categories separately // This will take more time, but makes UI more responsive diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index e1edf376936..09ceca58e9c 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -22,12 +22,36 @@ function filterEmojiNamesByAlias(filter) { return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name))); } +let emojiByCategory; +function getEmojiByCategory(category = null) { + if (!emojiByCategory) { + emojiByCategory = { + activity: [], + people: [], + nature: [], + food: [], + travel: [], + objects: [], + symbols: [], + flags: [], + }; + Object.keys(emojiMap).forEach((name) => { + const emoji = emojiMap[name]; + if (emojiByCategory[emoji.category]) { + emojiByCategory[emoji.category].push(name); + } + }); + } + return category ? emojiByCategory[category] : emojiByCategory; +} + export { emojiMap, emojiAliases, normalizeEmojiName, filterEmojiNames, filterEmojiNamesByAlias, + getEmojiByCategory, getUnicodeSupportMap, isEmojiNameValid, isEmojiUnicodeSupported, -- cgit v1.2.1 From af6c01fb6f94f1e95972f928edcdce7518d3a4a0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 22 Jun 2017 23:11:14 -0300 Subject: Allow users to subscribe to group labels on group labels page --- app/helpers/labels_helper.rb | 13 +++--- app/views/shared/_label.html.haml | 20 +++++----- spec/features/groups/labels/subscription_spec.rb | 51 ++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 spec/features/groups/labels/subscription_spec.rb diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 4e6e6805920..6c99d993a5f 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -134,20 +134,21 @@ module LabelsHelper end def label_subscription_status(label, project) - return 'project-level' if label.subscribed?(current_user, project) return 'group-level' if label.subscribed?(current_user) + return 'project-level' if label.subscribed?(current_user, project) 'unsubscribed' end - def group_label_unsubscribe_path(label, project) - case label_subscription_status(label, project) - when 'project-level' then toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) - when 'group-level' then toggle_subscription_group_label_path(label.group, label) + def toggle_subscription_label_path(label, project) + if label.is_a?(GroupLabel) + toggle_subscription_group_label_path(label.group, label) + else + toggle_subscription_namespace_project_label_path(project.namespace, project, label) end end - def label_subscription_toggle_button_text(label, project) + def label_subscription_toggle_button_text(label, project = nil) label.subscribed?(current_user, project) ? 'Unsubscribe' : 'Subscribe' end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index c185e9b73ee..5cb12eb598d 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -17,13 +17,13 @@ %li = link_to_label(label, subject: subject) do view open issues - - if current_user && defined?(@project) + - if current_user %li.label-subscription - - if label.is_a?(ProjectLabel) - %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } + - if defined?(@group) || label.is_a?(ProjectLabel) + %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_label_path(label, @project) } } %span= label_subscription_toggle_button_text(label, @project) - else - %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: group_label_unsubscribe_path(label, @project) } } + %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_label_path(label, @project) } } %span Unsubscribe %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } %span Subscribe at project level @@ -42,14 +42,14 @@ = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do view open issues - - if current_user && defined?(@project) + - if current_user .label-subscription.inline - - if label.is_a?(ProjectLabel) - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } + - if defined?(@group) || label.is_a?(ProjectLabel) + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_label_path(label, @project) } } %span= label_subscription_toggle_button_text(label, @project) = icon('spinner spin', class: 'label-subscribe-button-loading') - else - %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: group_label_unsubscribe_path(label, @project) } } + %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_label_path(label, @project) } } %span Unsubscribe = icon('spinner spin', class: 'label-subscribe-button-loading') @@ -76,8 +76,8 @@ %span.sr-only Delete = icon('trash-o') - - if current_user && defined?(@project) - - if label.is_a?(ProjectLabel) + - if current_user + - if defined?(@group) || label.is_a?(ProjectLabel) :javascript new gl.ProjectLabelSubscription('##{dom_id(label)} .label-subscription'); - else diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb new file mode 100644 index 00000000000..1e19f781644 --- /dev/null +++ b/spec/features/groups/labels/subscription_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +feature 'Labels subscription', feature: true do + let(:user) { create(:user) } + let(:group) { create(:group) } + let!(:feature) { create(:group_label, group: group, title: 'feature') } + + context 'when signed in' do + before do + group.add_developer(user) + gitlab_sign_in user + end + + scenario 'users can subscribe/unsubscribe to labels', js: true do + visit group_labels_path(group) + + expect(page).to have_content('feature') + + within "#group_label_#{feature.id}" do + expect(page).not_to have_button 'Unsubscribe' + + click_button 'Subscribe' + + expect(page).not_to have_button 'Subscribe' + expect(page).to have_button 'Unsubscribe' + + click_button 'Unsubscribe' + + expect(page).to have_button 'Subscribe' + expect(page).not_to have_button 'Unsubscribe' + end + end + end + + context 'when not signed in' do + it 'users can not subscribe/unsubscribe to labels' do + visit group_labels_path(group) + + expect(page).to have_content 'feature' + expect(page).not_to have_button('Subscribe') + end + end + + def click_link_on_dropdown(text) + find('.dropdown-group-label').click + + page.within('.dropdown-group-label') do + find('a.js-subscribe-button', text: text).click + end + end +end -- cgit v1.2.1 From 1cd76c393137318c65d048571d7d3b7c830167e2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 22 Jun 2017 23:11:48 -0300 Subject: Add CHANGELOG --- changelogs/unreleased/fix-33991.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-33991.yml diff --git a/changelogs/unreleased/fix-33991.yml b/changelogs/unreleased/fix-33991.yml new file mode 100644 index 00000000000..39732611b6e --- /dev/null +++ b/changelogs/unreleased/fix-33991.yml @@ -0,0 +1,4 @@ +--- +title: Users can subscribe to group labels on the group labels page +merge_request: +author: -- cgit v1.2.1 From fe37ca87599865081a8e94fd778b89134fc0c088 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 23 Jun 2017 15:17:51 -0300 Subject: Extract common paths to a variable --- app/views/shared/_label.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 5cb12eb598d..8b5bd3d49ed 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -1,6 +1,7 @@ - label_css_id = dom_id(label) - status = label_subscription_status(label, @project).inquiry if current_user - subject = local_assigns[:subject] +- toggle_subscription_path = toggle_subscription_label_path(label, @project) %li{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label @@ -20,10 +21,10 @@ - if current_user %li.label-subscription - if defined?(@group) || label.is_a?(ProjectLabel) - %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_label_path(label, @project) } } + %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_path } } %span= label_subscription_toggle_button_text(label, @project) - else - %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_label_path(label, @project) } } + %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } %span Subscribe at project level @@ -45,11 +46,11 @@ - if current_user .label-subscription.inline - if defined?(@group) || label.is_a?(ProjectLabel) - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_label_path(label, @project) } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_path } } %span= label_subscription_toggle_button_text(label, @project) = icon('spinner spin', class: 'label-subscribe-button-loading') - else - %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_label_path(label, @project) } } + %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe = icon('spinner spin', class: 'label-subscribe-button-loading') -- cgit v1.2.1 From 46e4c966168f212eb647aa0d2622a025bcd2bd75 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 23 Jun 2017 15:30:13 -0300 Subject: Add can_subscribe_to_label_in_different_levels helper method --- app/helpers/labels_helper.rb | 4 ++++ app/views/shared/_label.html.haml | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 6c99d993a5f..8a2987dca42 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -133,6 +133,10 @@ module LabelsHelper end end + def can_subscribe_to_label_in_different_levels?(label) + defined?(@project) || label.is_a?(GroupLabel) + end + def label_subscription_status(label, project) return 'group-level' if label.subscribed?(current_user) return 'project-level' if label.subscribed?(current_user, project) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 8b5bd3d49ed..4794541c89e 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -20,16 +20,16 @@ view open issues - if current_user %li.label-subscription - - if defined?(@group) || label.is_a?(ProjectLabel) - %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_path } } - %span= label_subscription_toggle_button_text(label, @project) - - else + - if can_subscribe_to_label_in_different_levels?(label) %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } %span Subscribe at project level %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } %span Subscribe at group level + - else + %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_path } } + %span= label_subscription_toggle_button_text(label, @project) - if can?(current_user, :admin_label, label) %li @@ -45,11 +45,7 @@ - if current_user .label-subscription.inline - - if defined?(@group) || label.is_a?(ProjectLabel) - %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_path } } - %span= label_subscription_toggle_button_text(label, @project) - = icon('spinner spin', class: 'label-subscribe-button-loading') - - else + - if can_subscribe_to_label_in_different_levels?(label) %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } } %span Unsubscribe = icon('spinner spin', class: 'label-subscribe-button-loading') @@ -64,6 +60,10 @@ Project level %a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } } Group level + - else + %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_path } } + %span= label_subscription_toggle_button_text(label, @project) + = icon('spinner spin', class: 'label-subscribe-button-loading') - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do @@ -78,9 +78,9 @@ = icon('trash-o') - if current_user - - if defined?(@group) || label.is_a?(ProjectLabel) + - if can_subscribe_to_label_in_different_levels?(label) :javascript - new gl.ProjectLabelSubscription('##{dom_id(label)} .label-subscription'); + new gl.GroupLabelSubscription('##{dom_id(label)} .label-subscription'); - else :javascript - new gl.GroupLabelSubscription('##{dom_id(label)} .label-subscription'); + new gl.ProjectLabelSubscription('##{dom_id(label)} .label-subscription'); -- cgit v1.2.1 From fe17b11736592c3b25b3a07a0ec9494f98e8bd4e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 23 Jun 2017 15:31:02 -0300 Subject: Improve scenario description for the group labels subscription spec --- spec/features/groups/labels/subscription_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb index 1e19f781644..8b891c52d08 100644 --- a/spec/features/groups/labels/subscription_spec.rb +++ b/spec/features/groups/labels/subscription_spec.rb @@ -11,7 +11,7 @@ feature 'Labels subscription', feature: true do gitlab_sign_in user end - scenario 'users can subscribe/unsubscribe to labels', js: true do + scenario 'users can subscribe/unsubscribe to group labels', js: true do visit group_labels_path(group) expect(page).to have_content('feature') -- cgit v1.2.1 From e51953bea753ccb209d2d6382e9fc24f294b7dae Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Jun 2017 17:52:01 -0300 Subject: Fix LabelsHelper#can_subscribe_to_label_in_different_levels --- app/helpers/labels_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 8a2987dca42..b2ddcd76b08 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -134,7 +134,7 @@ module LabelsHelper end def can_subscribe_to_label_in_different_levels?(label) - defined?(@project) || label.is_a?(GroupLabel) + defined?(@project) && label.is_a?(GroupLabel) end def label_subscription_status(label, project) -- cgit v1.2.1 From 33b61858a0564d0825bf5fc2e709f806c3282e4f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Jun 2017 17:52:14 -0300 Subject: Fix LabelsHelper#toggle_subscription_label_path --- app/helpers/labels_helper.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index b2ddcd76b08..6baf6f31d8f 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -145,10 +145,12 @@ module LabelsHelper end def toggle_subscription_label_path(label, project) - if label.is_a?(GroupLabel) - toggle_subscription_group_label_path(label.group, label) - else - toggle_subscription_namespace_project_label_path(project.namespace, project, label) + return toggle_subscription_group_label_path(label.group, label) unless project + + case label_subscription_status(label, project) + when 'group-level' then toggle_subscription_group_label_path(label.group, label) + when 'project-level' then toggle_subscription_namespace_project_label_path(project.namespace, project, label) + when 'unsubscribed' then toggle_subscription_namespace_project_label_path(project.namespace, project, label) end end -- cgit v1.2.1 From 28785488e8586f6fa4bf22b4773f7fb0ad663815 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Jun 2017 17:52:53 -0300 Subject: Fix missing hidden class when subscribed to a group label --- app/views/shared/_label.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 4794541c89e..b8a2c5cb87d 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -1,7 +1,7 @@ - label_css_id = dom_id(label) - status = label_subscription_status(label, @project).inquiry if current_user - subject = local_assigns[:subject] -- toggle_subscription_path = toggle_subscription_label_path(label, @project) +- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user %li{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label @@ -56,9 +56,9 @@ = icon('chevron-down') %ul.dropdown-menu %li - %a.js-subscribe-button{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } + %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } Project level - %a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } } + %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } } Group level - else %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_path } } -- cgit v1.2.1 From 82d67f700e13dc1958c63ab71b319df0dbea47d8 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 26 Jun 2017 16:03:36 -0500 Subject: namespace emoji helper methods within AwardsHandler class --- app/assets/javascripts/awards_handler.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index c363afc09b6..0950b52caf5 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -3,12 +3,7 @@ import Cookies from 'js-cookie'; import { glEmojiTag } from './behaviors/gl_emoji'; -import { - filterEmojiNamesByAlias, - getEmojiByCategory, - isEmojiNameValid, - normalizeEmojiName, -} from './emoji'; +import * as Emoji from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; @@ -139,7 +134,7 @@ export default class AwardsHandler { this.isCreatingEmojiMenu = true; // Render the first category - const categoryMap = getEmojiByCategory(); + const categoryMap = Emoji.getEmojiByCategory(); const categoryNameKey = Object.keys(categoryMap)[0]; const emojisInCategory = categoryMap[categoryNameKey]; const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); @@ -179,7 +174,7 @@ export default class AwardsHandler { } this.isAddingRemainingEmojiMenuCategories = true; - const categoryMap = getEmojiByCategory(); + const categoryMap = Emoji.getEmojiByCategory(); // Avoid the jank and render the remaining categories separately // This will take more time, but makes UI more responsive @@ -234,7 +229,7 @@ export default class AwardsHandler { } addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { - const normalizedEmoji = normalizeEmojiName(emoji); + const normalizedEmoji = Emoji.normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); @@ -249,7 +244,7 @@ export default class AwardsHandler { this.checkMutuality(votesBlock, emoji); } this.addEmojiToFrequentlyUsedList(emoji); - const normalizedEmoji = normalizeEmojiName(emoji); + const normalizedEmoji = Emoji.normalizeEmojiName(emoji); const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); if ($emojiButton.length > 0) { if (this.isActive($emojiButton)) { @@ -440,7 +435,7 @@ export default class AwardsHandler { } addEmojiToFrequentlyUsedList(emoji) { - if (isEmojiNameValid(emoji)) { + if (Emoji.isEmojiNameValid(emoji)) { this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji)); Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 }); } @@ -450,7 +445,7 @@ export default class AwardsHandler { return this.frequentlyUsedEmojis || (() => { const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(',')); this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter( - inputName => isEmojiNameValid(inputName), + inputName => Emoji.isEmojiNameValid(inputName), ); return this.frequentlyUsedEmojis; @@ -493,7 +488,7 @@ export default class AwardsHandler { } findMatchingEmojiElements(query) { - const emojiMatches = filterEmojiNamesByAlias(query); + const emojiMatches = Emoji.filterEmojiNamesByAlias(query); const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]'); const $matchingElements = $emojiElements .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0); -- cgit v1.2.1 From c8dfdfcda353d438f5cbdef0acc2e7898ef9a136 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Wed, 14 Jun 2017 19:58:02 -0700 Subject: ruby 2.4 compat: update to webmock 2.3.2 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 3cc40eb26a4..1664895e859 100644 --- a/Gemfile +++ b/Gemfile @@ -354,7 +354,7 @@ group :test do gem 'shoulda-matchers', '~> 2.8.0', require: false gem 'email_spec', '~> 1.6.0' gem 'json-schema', '~> 2.6.2' - gem 'webmock', '~> 1.24.0' + gem 'webmock', '~> 2.3.2' gem 'test_after_commit', '~> 1.1' gem 'sham_rack', '~> 1.3.6' gem 'timecop', '~> 0.8.0' diff --git a/Gemfile.lock b/Gemfile.lock index 817a1fd6aa3..f6966457271 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -366,7 +366,7 @@ GEM temple (~> 0.7.6) thor tilt - hashdiff (0.3.2) + hashdiff (0.3.4) hashie (3.5.5) hashie-forbidden_attributes (0.1.1) hashie (>= 3.0) @@ -888,7 +888,7 @@ GEM vmstat (2.3.0) warden (1.2.6) rack (>= 1.0) - webmock (1.24.6) + webmock (2.3.2) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff @@ -1118,7 +1118,7 @@ DEPENDENCIES version_sorter (~> 2.1.0) virtus (~> 1.0.1) vmstat (~> 2.3.0) - webmock (~> 1.24.0) + webmock (~> 2.3.2) webpack-rails (~> 0.9.10) wikicloth (= 0.8.1) -- cgit v1.2.1 From ff72de88bbc0d3c2681620fb576bf0e8379bdb42 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 26 Jun 2017 17:43:26 -0700 Subject: remove use of deprecated webmock feature in jira specs --- spec/features/projects/services/jira_service_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb index 2ea50e8f672..bf2ee2a10b1 100644 --- a/spec/features/projects/services/jira_service_spec.rb +++ b/spec/features/projects/services/jira_service_spec.rb @@ -6,7 +6,11 @@ feature 'Setup Jira service', :feature, :js do let(:service) { project.create_jira_service } let(:url) { 'http://jira.example.com' } - let(:project_url) { 'http://username:password@jira.example.com/rest/api/2/project/GitLabProject' } + + def stub_project_url + WebMock.stub_request(:get, 'http://jira.example.com/rest/api/2/project/GitLabProject') + .with(basic_auth: %w(username password)) + end def fill_form(active = true) check 'Active' if active @@ -27,9 +31,7 @@ feature 'Setup Jira service', :feature, :js do describe 'user sets and activates Jira Service' do context 'when Jira connection test succeeds' do - before do - WebMock.stub_request(:get, project_url) - end + before { stub_project_url } it 'activates the JIRA service' do click_link('JIRA') @@ -43,9 +45,7 @@ feature 'Setup Jira service', :feature, :js do end context 'when Jira connection test fails' do - before do - WebMock.stub_request(:get, project_url).to_return(status: 401) - end + before { stub_project_url.to_return(status: 401) } it 'shows errors when some required fields are not filled in' do click_link('JIRA') -- cgit v1.2.1 From 9ca4a3988b494e667d1e8cd71a7ac2fb598f618d Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 26 Jun 2017 17:47:35 -0700 Subject: rm use of deprecated webmock api in bamboo_service_spec --- spec/models/project_services/bamboo_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index e62fd69e567..7b1a554d1fb 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -217,13 +217,13 @@ describe BambooService, models: true, caching: true do end def stub_request(status: 200, body: nil) - bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic' + bamboo_full_url = 'http://gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic' WebMock.stub_request(:get, bamboo_full_url).to_return( status: status, headers: { 'Content-Type' => 'application/json' }, body: body - ) + ).with(basic_auth: %w(mic password)) end def bamboo_response(result_key: 42, build_state: 'success', size: 1) -- cgit v1.2.1 From e57d1bbf138aadd9c54a3e886d66d8b02e2497ae Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 26 Jun 2017 17:53:39 -0700 Subject: rm use of deprecated webmock api in jira model spec --- spec/models/project_services/jira_service_spec.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index e2b8226124f..c86f56c55eb 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -106,15 +106,15 @@ describe JiraService, models: true do @jira_service.save - project_issues_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123' - @transitions_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions' - @comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment' - @remote_link_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/remotelink' - - WebMock.stub_request(:get, project_issues_url) - WebMock.stub_request(:post, @transitions_url) - WebMock.stub_request(:post, @comment_url) - WebMock.stub_request(:post, @remote_link_url) + project_issues_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123' + @transitions_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123/transitions' + @comment_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123/comment' + @remote_link_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123/remotelink' + + WebMock.stub_request(:get, project_issues_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)) + WebMock.stub_request(:post, @transitions_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)) + WebMock.stub_request(:post, @comment_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)) + WebMock.stub_request(:post, @remote_link_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)) end it "calls JIRA API" do @@ -202,9 +202,9 @@ describe JiraService, models: true do end def test_settings(api_url) - project_url = "http://jira_username:jira_password@#{api_url}/rest/api/2/project/GitLabProject" + project_url = "http://#{api_url}/rest/api/2/project/GitLabProject" - WebMock.stub_request(:get, project_url) + WebMock.stub_request(:get, project_url).with(basic_auth: %w(jira_username jira_password)) jira_service.test_settings end -- cgit v1.2.1 From 7ef3cf64d6d6af9941401af748274677ff3af15d Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 26 Jun 2017 17:59:32 -0700 Subject: rm use of deprecated webmock api in campfire_service_spec --- spec/models/project_services/campfire_service_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index de55627dd27..56ff3596190 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -39,21 +39,22 @@ describe CampfireService, models: true do room: 'test-room' ) @sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) - @rooms_url = 'https://verySecret:X@project-name.campfirenow.com/rooms.json' + @rooms_url = 'https://project-name.campfirenow.com/rooms.json' + @auth = %w(verySecret X) @headers = { 'Content-Type' => 'application/json; charset=utf-8' } end it "calls Campfire API to get a list of rooms and speak in a room" do # make sure a valid list of rooms is returned body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json') - WebMock.stub_request(:get, @rooms_url).to_return( + WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return( body: body, status: 200, headers: @headers ) # stub the speak request with the room id found in the previous request's response - speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/123/speak.json' - WebMock.stub_request(:post, speak_url) + speak_url = 'https://project-name.campfirenow.com/room/123/speak.json' + WebMock.stub_request(:post, speak_url).with(basic_auth: @auth) @campfire_service.execute(@sample_data) @@ -66,7 +67,7 @@ describe CampfireService, models: true do it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do # return a list of rooms that do not contain a room named 'test-room' body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json') - WebMock.stub_request(:get, @rooms_url).to_return( + WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return( body: body, status: 200, headers: @headers -- cgit v1.2.1 From 2f0a7088d33a1630c3e54812d10219b75cdf7bc4 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 26 Jun 2017 17:59:45 -0700 Subject: rm use of deprecated webmock api in teamcity_service_spec --- spec/models/project_services/teamcity_service_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 7349eb4149a..6b004098510 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -205,10 +205,12 @@ describe TeamcityService, models: true, caching: true do end def stub_request(status: 200, body: nil, build_status: 'success') - teamcity_full_url = 'http://mic:password@gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,number:123' + teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,number:123' + auth = %w(mic password) + body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) - WebMock.stub_request(:get, teamcity_full_url).to_return( + WebMock.stub_request(:get, teamcity_full_url).with(basic_auth: auth).to_return( status: status, headers: { 'Content-Type' => 'application/json' }, body: body -- cgit v1.2.1 From a274359ecd485218ed7c42b750a43992ff3142ef Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 26 Jun 2017 19:24:12 -0700 Subject: rubocop fix: don't use single line blocks --- spec/features/projects/services/jira_service_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb index bf2ee2a10b1..8cd216c8fdb 100644 --- a/spec/features/projects/services/jira_service_spec.rb +++ b/spec/features/projects/services/jira_service_spec.rb @@ -31,7 +31,9 @@ feature 'Setup Jira service', :feature, :js do describe 'user sets and activates Jira Service' do context 'when Jira connection test succeeds' do - before { stub_project_url } + before do + stub_project_url + end it 'activates the JIRA service' do click_link('JIRA') @@ -45,7 +47,9 @@ feature 'Setup Jira service', :feature, :js do end context 'when Jira connection test fails' do - before { stub_project_url.to_return(status: 401) } + before do + stub_project_url.to_return(status: 401) + end it 'shows errors when some required fields are not filled in' do click_link('JIRA') -- cgit v1.2.1 From 527e7edbc4251644867ef02a2e055815d3f28a82 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 26 Jun 2017 17:05:43 -0500 Subject: replace emojiAlias references with normalizeEmojiName helper --- app/assets/javascripts/behaviors/gl_emoji.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 06eb698378c..3a29254cf99 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,5 +1,5 @@ import installCustomElements from 'document-register-element'; -import { emojiMap, emojiAliases, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; +import { emojiMap, normalizeEmojiName, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; installCustomElements(window); @@ -10,8 +10,7 @@ function emojiImageTag(name, src) { } function assembleFallbackImageSrc(inputName) { - let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; + let name = normalizeEmojiName(inputName); let emojiInfo = emojiMap[name]; // Fallback to question mark for unknown emojis if (!emojiInfo) { @@ -25,8 +24,7 @@ function assembleFallbackImageSrc(inputName) { function glEmojiTag(inputName, options) { const opts = { sprite: false, forceFallback: false, ...options }; - let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; + let name = normalizeEmojiName(inputName); let emojiInfo = emojiMap[name]; // Fallback to question mark for unknown emojis if (!emojiInfo) { -- cgit v1.2.1 From 88114c41fb4cb1c281df613ee8647c63848e37ea Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 26 Jun 2017 22:48:41 -0500 Subject: export validEmojiNames for autocomplete --- app/assets/javascripts/emoji/index.js | 1 + app/assets/javascripts/gfm_auto_complete.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 09ceca58e9c..70346c985e9 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -55,4 +55,5 @@ export { getUnicodeSupportMap, isEmojiNameValid, isEmojiUnicodeSupported, + validEmojiNames, }; diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 64d47a0d75e..9d057fd22a8 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,5 +1,5 @@ import { glEmojiTag } from './behaviors/gl_emoji'; -import { emojiMap, emojiAliases } from './emoji'; +import { validEmojiNames } from './emoji'; import glRegexp from './lib/utils/regexp'; import AjaxCache from './lib/utils/ajax_cache'; @@ -374,7 +374,7 @@ class GfmAutoComplete { if (this.cachedData[at]) { this.loadData($input, at, this.cachedData[at]); } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { - this.loadData($input, at, Object.keys(emojiMap).concat(Object.keys(emojiAliases))); + this.loadData($input, at, validEmojiNames); } else { AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true) .then((data) => { -- cgit v1.2.1 From 27a1348f20dee09758e053b465a2d2ff37ffb649 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Jun 2017 00:10:17 -0500 Subject: split emoji support methods from the async module --- app/assets/javascripts/behaviors/gl_emoji.js | 8 +- app/assets/javascripts/emoji/index.js | 4 - .../emoji/is_emoji_unicode_supported.js | 120 --------------- app/assets/javascripts/emoji/support/index.js | 10 ++ .../emoji/support/is_emoji_unicode_supported.js | 120 +++++++++++++++ .../emoji/support/unicode_support_map.js | 167 +++++++++++++++++++++ .../javascripts/emoji/unicode_support_map.js | 167 --------------------- .../behaviors/gl_emoji/unicode_support_map_spec.js | 2 +- spec/javascripts/gl_emoji_spec.js | 2 +- 9 files changed, 302 insertions(+), 298 deletions(-) delete mode 100644 app/assets/javascripts/emoji/is_emoji_unicode_supported.js create mode 100644 app/assets/javascripts/emoji/support/index.js create mode 100644 app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js create mode 100644 app/assets/javascripts/emoji/support/unicode_support_map.js delete mode 100644 app/assets/javascripts/emoji/unicode_support_map.js diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 3a29254cf99..17422f5cece 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,10 +1,9 @@ import installCustomElements from 'document-register-element'; -import { emojiMap, normalizeEmojiName, isEmojiUnicodeSupported, getUnicodeSupportMap } from '../emoji'; +import { emojiMap, normalizeEmojiName } from '../emoji'; +import isEmojiUnicodeSupported from '../emoji/support'; installCustomElements(window); -const generatedUnicodeSupportMap = getUnicodeSupportMap(); - function emojiImageTag(name, src) { return `:${name}:`; } @@ -82,7 +81,7 @@ function installGlEmojiElement() { if ( emojiUnicode && isEmojiUnicode && - !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion) + !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion) ) { // CSS sprite fallback takes precedence over image fallback if (hasCssSpriteFalback) { @@ -106,5 +105,4 @@ function installGlEmojiElement() { export { installGlEmojiElement, glEmojiTag, - emojiImageTag, }; diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 70346c985e9..7c3bab1e4a9 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -1,7 +1,5 @@ import emojiMap from 'emojis/digests.json'; import emojiAliases from 'emojis/aliases.json'; -import getUnicodeSupportMap from './unicode_support_map'; -import isEmojiUnicodeSupported from './is_emoji_unicode_supported'; const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; @@ -52,8 +50,6 @@ export { filterEmojiNames, filterEmojiNamesByAlias, getEmojiByCategory, - getUnicodeSupportMap, isEmojiNameValid, - isEmojiUnicodeSupported, validEmojiNames, }; diff --git a/app/assets/javascripts/emoji/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/is_emoji_unicode_supported.js deleted file mode 100644 index 3fd23efa9f8..00000000000 --- a/app/assets/javascripts/emoji/is_emoji_unicode_supported.js +++ /dev/null @@ -1,120 +0,0 @@ -// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/ -const flagACodePoint = 127462; // parseInt('1F1E6', 16) -const flagZCodePoint = 127487; // parseInt('1F1FF', 16) -function isFlagEmoji(emojiUnicode) { - const cp = emojiUnicode.codePointAt(0); - // Length 4 because flags are made of 2 characters which are surrogate pairs - return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint; -} - -// Chrome <57 renders keycaps oddly -// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294 -// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png -function isKeycapEmoji(emojiUnicode) { - return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3'; -} - -// Check for a skin tone variation emoji which aren't always supported -const tone1 = 127995;// parseInt('1F3FB', 16) -const tone5 = 127999;// parseInt('1F3FF', 16) -function isSkinToneComboEmoji(emojiUnicode) { - return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { - const cp = char.codePointAt(0); - return cp >= tone1 && cp <= tone5; - }); -} - -// macOS supports most skin tone emoji's but -// doesn't support the skin tone versions of horse racing -const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) -function isHorceRacingSkinToneComboEmoji(emojiUnicode) { - const firstCharacter = Array.from(emojiUnicode)[0]; - return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint && - isSkinToneComboEmoji(emojiUnicode); -} - -// Check for `family_*`, `kiss_*`, `couple_*` -// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these -const zwj = 8205; // parseInt('200D', 16) -const personStartCodePoint = 128102; // parseInt('1F466', 16) -const personEndCodePoint = 128105; // parseInt('1F469', 16) -function isPersonZwjEmoji(emojiUnicode) { - let hasPersonEmoji = false; - let hasZwj = false; - Array.from(emojiUnicode).forEach((character) => { - const cp = character.codePointAt(0); - if (cp === zwj) { - hasZwj = true; - } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) { - hasPersonEmoji = true; - } - }); - - return hasPersonEmoji && hasZwj; -} - -// Helper so we don't have to run `isFlagEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isFlagResult = isFlagEmoji(emojiUnicode); - return ( - (unicodeSupportMap.flag && isFlagResult) || - !isFlagResult - ); -} - -// Helper so we don't have to run `isSkinToneComboEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { - const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode); - return ( - (unicodeSupportMap.skinToneModifier && isSkinToneResult) || - !isSkinToneResult - ); -} - -// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode); - return ( - (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || - !isHorseRacingSkinToneResult - ); -} - -// Helper so we don't have to run `isPersonZwjEmoji` twice -// in `isEmojiUnicodeSupported` logic -function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { - const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode); - return ( - (unicodeSupportMap.personZwj && isPersonZwjResult) || - !isPersonZwjResult - ); -} - -// Takes in a support map and determines whether -// the given unicode emoji is supported on the platform. -// -// Combines all the edge case tests into a one-stop shop method -function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) { - const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome && - unicodeSupportMap.meta.chromeVersion < 57; - - // For comments about each scenario, see the comments above each individual respective function - return unicodeSupportMap[unicodeVersion] && - !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) && - checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) && - checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) && - checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) && - checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode); -} - -export { - isEmojiUnicodeSupported as default, - isFlagEmoji, - isKeycapEmoji, - isSkinToneComboEmoji, - isHorceRacingSkinToneComboEmoji, - isPersonZwjEmoji, -}; diff --git a/app/assets/javascripts/emoji/support/index.js b/app/assets/javascripts/emoji/support/index.js new file mode 100644 index 00000000000..1f7852dd487 --- /dev/null +++ b/app/assets/javascripts/emoji/support/index.js @@ -0,0 +1,10 @@ +import isEmojiUnicodeSupported from './is_emoji_unicode_supported'; +import getUnicodeSupportMap from './unicode_support_map'; + +// cache browser support map between calls +let browserUnicodeSupportMap; + +export default function isEmojiUnicodeSupportedByBrowser(emojiUnicode, unicodeVersion) { + browserUnicodeSupportMap = browserUnicodeSupportMap || getUnicodeSupportMap(); + return isEmojiUnicodeSupported(browserUnicodeSupportMap, emojiUnicode, unicodeVersion); +} diff --git a/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js new file mode 100644 index 00000000000..3fd23efa9f8 --- /dev/null +++ b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js @@ -0,0 +1,120 @@ +// On Windows, flags render as two-letter country codes, see http://emojipedia.org/flags/ +const flagACodePoint = 127462; // parseInt('1F1E6', 16) +const flagZCodePoint = 127487; // parseInt('1F1FF', 16) +function isFlagEmoji(emojiUnicode) { + const cp = emojiUnicode.codePointAt(0); + // Length 4 because flags are made of 2 characters which are surrogate pairs + return emojiUnicode.length === 4 && cp >= flagACodePoint && cp <= flagZCodePoint; +} + +// Chrome <57 renders keycaps oddly +// See https://bugs.chromium.org/p/chromium/issues/detail?id=632294 +// Same issue on Windows also fixed in Chrome 57, http://i.imgur.com/rQF7woO.png +function isKeycapEmoji(emojiUnicode) { + return emojiUnicode.length === 3 && emojiUnicode[2] === '\u20E3'; +} + +// Check for a skin tone variation emoji which aren't always supported +const tone1 = 127995;// parseInt('1F3FB', 16) +const tone5 = 127999;// parseInt('1F3FF', 16) +function isSkinToneComboEmoji(emojiUnicode) { + return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { + const cp = char.codePointAt(0); + return cp >= tone1 && cp <= tone5; + }); +} + +// macOS supports most skin tone emoji's but +// doesn't support the skin tone versions of horse racing +const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) +function isHorceRacingSkinToneComboEmoji(emojiUnicode) { + const firstCharacter = Array.from(emojiUnicode)[0]; + return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint && + isSkinToneComboEmoji(emojiUnicode); +} + +// Check for `family_*`, `kiss_*`, `couple_*` +// For ex. Windows 8.1 Firefox 51.0.1, doesn't support these +const zwj = 8205; // parseInt('200D', 16) +const personStartCodePoint = 128102; // parseInt('1F466', 16) +const personEndCodePoint = 128105; // parseInt('1F469', 16) +function isPersonZwjEmoji(emojiUnicode) { + let hasPersonEmoji = false; + let hasZwj = false; + Array.from(emojiUnicode).forEach((character) => { + const cp = character.codePointAt(0); + if (cp === zwj) { + hasZwj = true; + } else if (cp >= personStartCodePoint && cp <= personEndCodePoint) { + hasPersonEmoji = true; + } + }); + + return hasPersonEmoji && hasZwj; +} + +// Helper so we don't have to run `isFlagEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) { + const isFlagResult = isFlagEmoji(emojiUnicode); + return ( + (unicodeSupportMap.flag && isFlagResult) || + !isFlagResult + ); +} + +// Helper so we don't have to run `isSkinToneComboEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { + const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode); + return ( + (unicodeSupportMap.skinToneModifier && isSkinToneResult) || + !isSkinToneResult + ); +} + +// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) { + const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode); + return ( + (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || + !isHorseRacingSkinToneResult + ); +} + +// Helper so we don't have to run `isPersonZwjEmoji` twice +// in `isEmojiUnicodeSupported` logic +function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { + const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode); + return ( + (unicodeSupportMap.personZwj && isPersonZwjResult) || + !isPersonZwjResult + ); +} + +// Takes in a support map and determines whether +// the given unicode emoji is supported on the platform. +// +// Combines all the edge case tests into a one-stop shop method +function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) { + const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome && + unicodeSupportMap.meta.chromeVersion < 57; + + // For comments about each scenario, see the comments above each individual respective function + return unicodeSupportMap[unicodeVersion] && + !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) && + checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) && + checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) && + checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) && + checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode); +} + +export { + isEmojiUnicodeSupported as default, + isFlagEmoji, + isKeycapEmoji, + isSkinToneComboEmoji, + isHorceRacingSkinToneComboEmoji, + isPersonZwjEmoji, +}; diff --git a/app/assets/javascripts/emoji/support/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js new file mode 100644 index 00000000000..755381c2f95 --- /dev/null +++ b/app/assets/javascripts/emoji/support/unicode_support_map.js @@ -0,0 +1,167 @@ +import AccessorUtilities from '../../lib/utils/accessor'; + +const unicodeSupportTestMap = { + // man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ + // occupationZwj: '\u{1F468}\u{200D}\u{1F393}', + // woman, biking (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ + // sexZwj: '\u{1F6B4}\u{200D}\u{2640}', + // family_mwgb + // Windows 8.1, Firefox 51.0.1 does not support `family_`, `kiss_`, `couple_` + personZwj: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', + // horse_racing_tone5 + // Special case that is not supported on macOS 10.12 even though `skinToneModifier` succeeds + horseRacing: '\u{1F3C7}\u{1F3FF}', + // US flag, http://emojipedia.org/flags/ + flag: '\u{1F1FA}\u{1F1F8}', + // http://emojipedia.org/modifiers/ + skinToneModifier: [ + // spy_tone5 + '\u{1F575}\u{1F3FF}', + // person_with_ball_tone5 + '\u{26F9}\u{1F3FF}', + // angel_tone5 + '\u{1F47C}\u{1F3FF}', + ], + // rofl, http://emojipedia.org/unicode-9.0/ + '9.0': '\u{1F923}', + // metal, http://emojipedia.org/unicode-8.0/ + '8.0': '\u{1F918}', + // spy, http://emojipedia.org/unicode-7.0/ + '7.0': '\u{1F575}', + // expressionless, http://emojipedia.org/unicode-6.1/ + 6.1: '\u{1F611}', + // japanese_goblin, http://emojipedia.org/unicode-6.0/ + '6.0': '\u{1F47A}', + // sailboat, http://emojipedia.org/unicode-5.2/ + 5.2: '\u{26F5}', + // mahjong, http://emojipedia.org/unicode-5.1/ + 5.1: '\u{1F004}', + // gear, http://emojipedia.org/unicode-4.1/ + 4.1: '\u{2699}', + // zap, http://emojipedia.org/unicode-4.0/ + '4.0': '\u{26A1}', + // recycle, http://emojipedia.org/unicode-3.2/ + 3.2: '\u{267B}', + // information_source, http://emojipedia.org/unicode-3.0/ + '3.0': '\u{2139}', + // heart, http://emojipedia.org/unicode-1.1/ + 1.1: '\u{2764}', +}; + +function checkPixelInImageDataArray(pixelOffset, imageDataArray) { + // `4 *` because RGBA + const indexOffset = 4 * pixelOffset; + const hasColor = imageDataArray[indexOffset + 0] || + imageDataArray[indexOffset + 1] || + imageDataArray[indexOffset + 2]; + const isVisible = imageDataArray[indexOffset + 3]; + // Check for some sort of color other than black + if (hasColor && isVisible) { + return true; + } + return false; +} + +const chromeMatches = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./); +const isChrome = chromeMatches && chromeMatches.length > 0; +const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatches[1], 10); + +// We use 16px because mobile Safari (iOS 9.3) doesn't properly scale emojis :/ +// See 32px, https://i.imgur.com/htY6Zym.png +// See 16px, https://i.imgur.com/FPPsIF8.png +const fontSize = 16; +function generateUnicodeSupportMap(testMap) { + const testMapKeys = Object.keys(testMap); + const numTestEntries = testMapKeys + .reduce((list, testKey) => list.concat(testMap[testKey]), []).length; + + const canvas = document.createElement('canvas'); + (window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas; + const ctx = canvas.getContext('2d'); + canvas.width = (2 * fontSize); + canvas.height = (numTestEntries * fontSize); + ctx.fillStyle = '#000000'; + ctx.textBaseline = 'middle'; + ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`; + // Write each emoji to the canvas vertically + let writeIndex = 0; + testMapKeys.forEach((testKey) => { + const testEntry = testMap[testKey]; + [].concat(testEntry).forEach((emojiUnicode) => { + ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2)); + writeIndex += 1; + }); + }); + + // Read from the canvas + const resultMap = {}; + let readIndex = 0; + testMapKeys.forEach((testKey) => { + const testEntry = testMap[testKey]; + // This needs to be a `reduce` instead of `every` because we need to + // keep the `readIndex` in sync from the writes by running all entries + const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => { + // Sample along the vertical-middle for a couple of characters + const imageData = ctx.getImageData( + 0, + (readIndex * fontSize) + (fontSize / 2), + 2 * fontSize, + 1, + ).data; + + let isValidEmoji = false; + for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) { + const isLookingAtFirstChar = currentPixel < fontSize; + const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2)); + // Check for the emoji somewhere along the row + if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) { + isValidEmoji = true; + + // Check to see that nothing is rendered next to the first character + // to ensure that the ZWJ sequence rendered as one piece + } else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) { + isValidEmoji = false; + break; + } + } + + readIndex += 1; + return isSatisfied && isValidEmoji; + }, true); + + resultMap[testKey] = isTestSatisfied; + }); + + resultMap.meta = { + isChrome, + chromeVersion, + }; + + return resultMap; +} + +export default function getUnicodeSupportMap() { + let unicodeSupportMap; + let userAgentFromCache; + + const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); + + if (isLocalStorageAvailable) userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent'); + + try { + unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map')); + } catch (err) { + // swallow + } + + if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) { + unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap); + + if (isLocalStorageAvailable) { + window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent); + window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap)); + } + } + + return unicodeSupportMap; +} diff --git a/app/assets/javascripts/emoji/unicode_support_map.js b/app/assets/javascripts/emoji/unicode_support_map.js deleted file mode 100644 index 2a0c013a70c..00000000000 --- a/app/assets/javascripts/emoji/unicode_support_map.js +++ /dev/null @@ -1,167 +0,0 @@ -import AccessorUtilities from '../lib/utils/accessor'; - -const unicodeSupportTestMap = { - // man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ - // occupationZwj: '\u{1F468}\u{200D}\u{1F393}', - // woman, biking (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/ - // sexZwj: '\u{1F6B4}\u{200D}\u{2640}', - // family_mwgb - // Windows 8.1, Firefox 51.0.1 does not support `family_`, `kiss_`, `couple_` - personZwj: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}', - // horse_racing_tone5 - // Special case that is not supported on macOS 10.12 even though `skinToneModifier` succeeds - horseRacing: '\u{1F3C7}\u{1F3FF}', - // US flag, http://emojipedia.org/flags/ - flag: '\u{1F1FA}\u{1F1F8}', - // http://emojipedia.org/modifiers/ - skinToneModifier: [ - // spy_tone5 - '\u{1F575}\u{1F3FF}', - // person_with_ball_tone5 - '\u{26F9}\u{1F3FF}', - // angel_tone5 - '\u{1F47C}\u{1F3FF}', - ], - // rofl, http://emojipedia.org/unicode-9.0/ - '9.0': '\u{1F923}', - // metal, http://emojipedia.org/unicode-8.0/ - '8.0': '\u{1F918}', - // spy, http://emojipedia.org/unicode-7.0/ - '7.0': '\u{1F575}', - // expressionless, http://emojipedia.org/unicode-6.1/ - 6.1: '\u{1F611}', - // japanese_goblin, http://emojipedia.org/unicode-6.0/ - '6.0': '\u{1F47A}', - // sailboat, http://emojipedia.org/unicode-5.2/ - 5.2: '\u{26F5}', - // mahjong, http://emojipedia.org/unicode-5.1/ - 5.1: '\u{1F004}', - // gear, http://emojipedia.org/unicode-4.1/ - 4.1: '\u{2699}', - // zap, http://emojipedia.org/unicode-4.0/ - '4.0': '\u{26A1}', - // recycle, http://emojipedia.org/unicode-3.2/ - 3.2: '\u{267B}', - // information_source, http://emojipedia.org/unicode-3.0/ - '3.0': '\u{2139}', - // heart, http://emojipedia.org/unicode-1.1/ - 1.1: '\u{2764}', -}; - -function checkPixelInImageDataArray(pixelOffset, imageDataArray) { - // `4 *` because RGBA - const indexOffset = 4 * pixelOffset; - const hasColor = imageDataArray[indexOffset + 0] || - imageDataArray[indexOffset + 1] || - imageDataArray[indexOffset + 2]; - const isVisible = imageDataArray[indexOffset + 3]; - // Check for some sort of color other than black - if (hasColor && isVisible) { - return true; - } - return false; -} - -const chromeMatches = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./); -const isChrome = chromeMatches && chromeMatches.length > 0; -const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatches[1], 10); - -// We use 16px because mobile Safari (iOS 9.3) doesn't properly scale emojis :/ -// See 32px, https://i.imgur.com/htY6Zym.png -// See 16px, https://i.imgur.com/FPPsIF8.png -const fontSize = 16; -function generateUnicodeSupportMap(testMap) { - const testMapKeys = Object.keys(testMap); - const numTestEntries = testMapKeys - .reduce((list, testKey) => list.concat(testMap[testKey]), []).length; - - const canvas = document.createElement('canvas'); - (window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas; - const ctx = canvas.getContext('2d'); - canvas.width = (2 * fontSize); - canvas.height = (numTestEntries * fontSize); - ctx.fillStyle = '#000000'; - ctx.textBaseline = 'middle'; - ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`; - // Write each emoji to the canvas vertically - let writeIndex = 0; - testMapKeys.forEach((testKey) => { - const testEntry = testMap[testKey]; - [].concat(testEntry).forEach((emojiUnicode) => { - ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2)); - writeIndex += 1; - }); - }); - - // Read from the canvas - const resultMap = {}; - let readIndex = 0; - testMapKeys.forEach((testKey) => { - const testEntry = testMap[testKey]; - // This needs to be a `reduce` instead of `every` because we need to - // keep the `readIndex` in sync from the writes by running all entries - const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => { - // Sample along the vertical-middle for a couple of characters - const imageData = ctx.getImageData( - 0, - (readIndex * fontSize) + (fontSize / 2), - 2 * fontSize, - 1, - ).data; - - let isValidEmoji = false; - for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) { - const isLookingAtFirstChar = currentPixel < fontSize; - const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2)); - // Check for the emoji somewhere along the row - if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) { - isValidEmoji = true; - - // Check to see that nothing is rendered next to the first character - // to ensure that the ZWJ sequence rendered as one piece - } else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) { - isValidEmoji = false; - break; - } - } - - readIndex += 1; - return isSatisfied && isValidEmoji; - }, true); - - resultMap[testKey] = isTestSatisfied; - }); - - resultMap.meta = { - isChrome, - chromeVersion, - }; - - return resultMap; -} - -export default function getUnicodeSupportMap() { - let unicodeSupportMap; - let userAgentFromCache; - - const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); - - if (isLocalStorageAvailable) userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent'); - - try { - unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map')); - } catch (err) { - // swallow - } - - if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) { - unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap); - - if (isLocalStorageAvailable) { - window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent); - window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap)); - } - } - - return unicodeSupportMap; -} diff --git a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js index 1c6b6ffe435..ec2c549e032 100644 --- a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js +++ b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js @@ -1,4 +1,4 @@ -import getUnicodeSupportMap from '~/emoji/unicode_support_map'; +import getUnicodeSupportMap from '~/emoji/support/unicode_support_map'; import AccessorUtilities from '~/lib/utils/accessor'; describe('Unicode Support Map', () => { diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/gl_emoji_spec.js index 17c8981971c..be1ced4aaf9 100644 --- a/spec/javascripts/gl_emoji_spec.js +++ b/spec/javascripts/gl_emoji_spec.js @@ -5,7 +5,7 @@ import isEmojiUnicodeSupported, { isSkinToneComboEmoji, isHorceRacingSkinToneComboEmoji, isPersonZwjEmoji, -} from '~/emoji/is_emoji_unicode_supported'; +} from '~/emoji/support/is_emoji_unicode_supported'; const emptySupportMap = { personZwj: false, -- cgit v1.2.1 From 9d6bbc92c978beeec70bec1f2d4a65791fa3f985 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Jun 2017 00:54:34 -0500 Subject: move glEmojiTag method to emoji helper --- app/assets/javascripts/awards_handler.js | 5 +- app/assets/javascripts/behaviors/gl_emoji.js | 67 +---- app/assets/javascripts/behaviors/index.js | 2 +- app/assets/javascripts/emoji/index.js | 76 ++++- app/assets/javascripts/gfm_auto_complete.js | 3 +- spec/javascripts/emoji_spec.js | 429 +++++++++++++++++++++++++++ spec/javascripts/gl_emoji_spec.js | 429 --------------------------- 7 files changed, 496 insertions(+), 515 deletions(-) create mode 100644 spec/javascripts/emoji_spec.js delete mode 100644 spec/javascripts/gl_emoji_spec.js diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 0950b52caf5..6f0384ebebe 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -2,7 +2,6 @@ /* global Flash */ import Cookies from 'js-cookie'; -import { glEmojiTag } from './behaviors/gl_emoji'; import * as Emoji from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; @@ -30,7 +29,7 @@ function renderCategory(name, emojiList, opts = {}) { ${emojiList.map(emojiName => `
  • @@ -369,7 +368,7 @@ export default class AwardsHandler { createAwardButtonForVotesBlock(votesBlock, emojiName) { const buttonHtml = ` `; diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 17422f5cece..8156e491a42 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,66 +1,10 @@ import installCustomElements from 'document-register-element'; -import { emojiMap, normalizeEmojiName } from '../emoji'; +import { emojiImageTag, emojiFallbackImageSrc } from '../emoji'; import isEmojiUnicodeSupported from '../emoji/support'; installCustomElements(window); -function emojiImageTag(name, src) { - return `:${name}:`; -} - -function assembleFallbackImageSrc(inputName) { - let name = normalizeEmojiName(inputName); - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`; - - return fallbackImageSrc; -} - -function glEmojiTag(inputName, options) { - const opts = { sprite: false, forceFallback: false, ...options }; - let name = normalizeEmojiName(inputName); - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - - const fallbackImageSrc = assembleFallbackImageSrc(name); - const fallbackSpriteClass = `emoji-${name}`; - - const classList = []; - if (opts.forceFallback && opts.sprite) { - classList.push('emoji-icon'); - classList.push(fallbackSpriteClass); - } - const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : ''; - const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : ''; - let contents = emojiInfo.moji; - if (opts.forceFallback && !opts.sprite) { - contents = emojiImageTag(name, fallbackImageSrc); - } - - return ` - - ${contents} - - `; -} - -function installGlEmojiElement() { +export default function installGlEmojiElement() { const GlEmojiElementProto = Object.create(HTMLElement.prototype); GlEmojiElementProto.createdCallback = function createdCallback() { const emojiUnicode = this.textContent.trim(); @@ -91,7 +35,7 @@ function installGlEmojiElement() { } else if (hasImageFallback) { this.innerHTML = emojiImageTag(name, fallbackSrc); } else { - const src = assembleFallbackImageSrc(name); + const src = emojiFallbackImageSrc(name); this.innerHTML = emojiImageTag(name, src); } } @@ -101,8 +45,3 @@ function installGlEmojiElement() { prototype: GlEmojiElementProto, }); } - -export { - installGlEmojiElement, - glEmojiTag, -}; diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js index 5b931e6cfa6..44b2c974b9e 100644 --- a/app/assets/javascripts/behaviors/index.js +++ b/app/assets/javascripts/behaviors/index.js @@ -1,7 +1,7 @@ import './autosize'; import './bind_in_out'; import './details_behavior'; -import { installGlEmojiElement } from './gl_emoji'; +import installGlEmojiElement from './gl_emoji'; import './quick_submit'; import './requires_input'; import './toggler_behavior'; diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 7c3bab1e4a9..bbecd1e41d1 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -1,27 +1,27 @@ import emojiMap from 'emojis/digests.json'; import emojiAliases from 'emojis/aliases.json'; -const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; +export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)]; -function normalizeEmojiName(name) { +export function normalizeEmojiName(name) { return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name; } -function isEmojiNameValid(name) { +export function isEmojiNameValid(name) { return validEmojiNames.indexOf(name) >= 0; } -function filterEmojiNames(filter) { +export function filterEmojiNames(filter) { const match = filter.toLowerCase(); return validEmojiNames.filter(name => name.indexOf(match) >= 0); } -function filterEmojiNamesByAlias(filter) { +export function filterEmojiNamesByAlias(filter) { return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name))); } let emojiByCategory; -function getEmojiByCategory(category = null) { +export function getEmojiByCategory(category = null) { if (!emojiByCategory) { emojiByCategory = { activity: [], @@ -43,13 +43,57 @@ function getEmojiByCategory(category = null) { return category ? emojiByCategory[category] : emojiByCategory; } -export { - emojiMap, - emojiAliases, - normalizeEmojiName, - filterEmojiNames, - filterEmojiNamesByAlias, - getEmojiByCategory, - isEmojiNameValid, - validEmojiNames, -}; +export function getEmojiInfo(query) { + let name = normalizeEmojiName(query); + let emojiInfo = emojiMap[name]; + + // Fallback to question mark for unknown emojis + if (!emojiInfo) { + name = 'grey_question'; + emojiInfo = emojiMap[name]; + } + + return { ...emojiInfo, name }; +} + +export function emojiFallbackImageSrc(inputName) { + const { name, digest } = getEmojiInfo(inputName); + return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`; +} + +export function emojiImageTag(name, src) { + return `:${name}:`; +} + +export function glEmojiTag(inputName, options) { + const opts = { sprite: false, forceFallback: false, ...options }; + const { name, ...emojiInfo } = getEmojiInfo(inputName); + + const fallbackImageSrc = emojiFallbackImageSrc(name); + const fallbackSpriteClass = `emoji-${name}`; + + const classList = []; + if (opts.forceFallback && opts.sprite) { + classList.push('emoji-icon'); + classList.push(fallbackSpriteClass); + } + const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : ''; + const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : ''; + let contents = emojiInfo.moji; + if (opts.forceFallback && !opts.sprite) { + contents = emojiImageTag(name, fallbackImageSrc); + } + + return ` + + ${contents} + + `; +} diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 9d057fd22a8..f99bac7da1a 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -1,5 +1,4 @@ -import { glEmojiTag } from './behaviors/gl_emoji'; -import { validEmojiNames } from './emoji'; +import { validEmojiNames, glEmojiTag } from './emoji'; import glRegexp from './lib/utils/regexp'; import AjaxCache from './lib/utils/ajax_cache'; diff --git a/spec/javascripts/emoji_spec.js b/spec/javascripts/emoji_spec.js new file mode 100644 index 00000000000..fa11c602ec3 --- /dev/null +++ b/spec/javascripts/emoji_spec.js @@ -0,0 +1,429 @@ +import { glEmojiTag } from '~/emoji'; +import isEmojiUnicodeSupported, { + isFlagEmoji, + isKeycapEmoji, + isSkinToneComboEmoji, + isHorceRacingSkinToneComboEmoji, + isPersonZwjEmoji, +} from '~/emoji/support/is_emoji_unicode_supported'; + +const emptySupportMap = { + personZwj: false, + horseRacing: false, + flag: false, + skinToneModifier: false, + '9.0': false, + '8.0': false, + '7.0': false, + 6.1: false, + '6.0': false, + 5.2: false, + 5.1: false, + 4.1: false, + '4.0': false, + 3.2: false, + '3.0': false, + 1.1: false, +}; + +const emojiFixtureMap = { + bomb: { + name: 'bomb', + moji: '💣', + unicodeVersion: '6.0', + }, + construction_worker_tone5: { + name: 'construction_worker_tone5', + moji: '👷🏿', + unicodeVersion: '8.0', + }, + five: { + name: 'five', + moji: '5️⃣', + unicodeVersion: '3.0', + }, + grey_question: { + name: 'grey_question', + moji: '❔', + unicodeVersion: '6.0', + }, +}; + +function markupToDomElement(markup) { + const div = document.createElement('div'); + div.innerHTML = markup; + return div.firstElementChild; +} + +function testGlEmojiImageFallback(element, name, src) { + expect(element.tagName.toLowerCase()).toBe('img'); + expect(element.getAttribute('src')).toBe(src); + expect(element.getAttribute('title')).toBe(`:${name}:`); + expect(element.getAttribute('alt')).toBe(`:${name}:`); +} + +const defaults = { + forceFallback: false, + sprite: false, +}; + +function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) { + const opts = Object.assign({}, defaults, options); + expect(element.tagName.toLowerCase()).toBe('gl-emoji'); + expect(element.dataset.name).toBe(name); + expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0); + expect(element.dataset.unicodeVersion).toBe(unicodeVersion); + + const fallbackSpriteClass = `emoji-${name}`; + if (opts.sprite) { + expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass); + } + + if (opts.forceFallback && opts.sprite) { + expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`); + } + + if (opts.forceFallback && !opts.sprite) { + // Check for image fallback + testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc); + } else { + // Otherwise make sure things are still unicode text + expect(element.textContent.trim()).toBe(unicodeMoji); + } +} + +describe('gl_emoji', () => { + describe('glEmojiTag', () => { + it('bomb emoji', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + ); + }); + + it('bomb emoji with image fallback', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { + forceFallback: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + }, + ); + }); + + it('bomb emoji with sprite fallback readiness', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { + sprite: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + sprite: true, + }, + ); + }); + it('bomb emoji with sprite fallback', () => { + const emojiKey = 'bomb'; + const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { + forceFallback: true, + sprite: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + sprite: true, + }, + ); + }); + + it('question mark when invalid emoji name given', () => { + const name = 'invalid_emoji'; + const emojiKey = 'grey_question'; + const markup = glEmojiTag(name); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + ); + }); + + it('question mark with image fallback when invalid emoji name given', () => { + const name = 'invalid_emoji'; + const emojiKey = 'grey_question'; + const markup = glEmojiTag(name, { + forceFallback: true, + }); + const glEmojiElement = markupToDomElement(markup); + testGlEmojiElement( + glEmojiElement, + emojiFixtureMap[emojiKey].name, + emojiFixtureMap[emojiKey].unicodeVersion, + emojiFixtureMap[emojiKey].moji, + { + forceFallback: true, + }, + ); + }); + }); + + describe('isFlagEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isFlagEmoji('')).toBeFalsy(); + }); + it('should detect flag_ac', () => { + expect(isFlagEmoji('🇦🇨')).toBeTruthy(); + }); + it('should detect flag_us', () => { + expect(isFlagEmoji('🇺🇸')).toBeTruthy(); + }); + it('should detect flag_zw', () => { + expect(isFlagEmoji('🇿🇼')).toBeTruthy(); + }); + it('should not detect flags', () => { + expect(isFlagEmoji('🎏')).toBeFalsy(); + }); + it('should not detect triangular_flag_on_post', () => { + expect(isFlagEmoji('🚩')).toBeFalsy(); + }); + it('should not detect single letter', () => { + expect(isFlagEmoji('🇦')).toBeFalsy(); + }); + it('should not detect >2 letters', () => { + expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy(); + }); + }); + + describe('isKeycapEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isKeycapEmoji('')).toBeFalsy(); + }); + it('should detect one(keycap)', () => { + expect(isKeycapEmoji('1️⃣')).toBeTruthy(); + }); + it('should detect nine(keycap)', () => { + expect(isKeycapEmoji('9️⃣')).toBeTruthy(); + }); + it('should not detect ten(keycap)', () => { + expect(isKeycapEmoji('🔟')).toBeFalsy(); + }); + it('should not detect hash(keycap)', () => { + expect(isKeycapEmoji('#⃣')).toBeFalsy(); + }); + }); + + describe('isSkinToneComboEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isSkinToneComboEmoji('')).toBeFalsy(); + }); + it('should detect hand_splayed_tone5', () => { + expect(isSkinToneComboEmoji('🖐🏿')).toBeTruthy(); + }); + it('should not detect hand_splayed', () => { + expect(isSkinToneComboEmoji('🖐')).toBeFalsy(); + }); + it('should detect lifter_tone1', () => { + expect(isSkinToneComboEmoji('🏋🏻')).toBeTruthy(); + }); + it('should not detect lifter', () => { + expect(isSkinToneComboEmoji('🏋')).toBeFalsy(); + }); + it('should detect rowboat_tone4', () => { + expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy(); + }); + it('should not detect rowboat', () => { + expect(isSkinToneComboEmoji('🚣')).toBeFalsy(); + }); + it('should not detect individual tone emoji', () => { + expect(isSkinToneComboEmoji('🏻')).toBeFalsy(); + }); + }); + + describe('isHorceRacingSkinToneComboEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy(); + }); + it('should detect horse_racing_tone2', () => { + expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBeTruthy(); + }); + it('should not detect horse_racing', () => { + expect(isHorceRacingSkinToneComboEmoji('🏇')).toBeFalsy(); + }); + }); + + describe('isPersonZwjEmoji', () => { + it('should gracefully handle empty string', () => { + expect(isPersonZwjEmoji('')).toBeFalsy(); + }); + it('should detect couple_mm', () => { + expect(isPersonZwjEmoji('👨‍❤️‍👨')).toBeTruthy(); + }); + it('should not detect couple_with_heart', () => { + expect(isPersonZwjEmoji('💑')).toBeFalsy(); + }); + it('should not detect couplekiss', () => { + expect(isPersonZwjEmoji('💏')).toBeFalsy(); + }); + it('should detect family_mmb', () => { + expect(isPersonZwjEmoji('👨‍👨‍👦')).toBeTruthy(); + }); + it('should detect family_mwgb', () => { + expect(isPersonZwjEmoji('👨‍👩‍👧‍👦')).toBeTruthy(); + }); + it('should not detect family', () => { + expect(isPersonZwjEmoji('👪')).toBeFalsy(); + }); + it('should detect kiss_ww', () => { + expect(isPersonZwjEmoji('👩‍❤️‍💋‍👩')).toBeTruthy(); + }); + it('should not detect girl', () => { + expect(isPersonZwjEmoji('👧')).toBeFalsy(); + }); + it('should not detect girl_tone5', () => { + expect(isPersonZwjEmoji('👧🏿')).toBeFalsy(); + }); + it('should not detect man', () => { + expect(isPersonZwjEmoji('👨')).toBeFalsy(); + }); + it('should not detect woman', () => { + expect(isPersonZwjEmoji('👩')).toBeFalsy(); + }); + }); + + describe('isEmojiUnicodeSupported', () => { + it('should gracefully handle empty string with unicode support', () => { + const isSupported = isEmojiUnicodeSupported( + { '1.0': true }, + '', + '1.0', + ); + expect(isSupported).toBeTruthy(); + }); + it('should gracefully handle empty string without unicode support', () => { + const isSupported = isEmojiUnicodeSupported( + {}, + '', + '1.0', + ); + expect(isSupported).toBeFalsy(); + }); + it('bomb(6.0) with 6.0 support', () => { + const emojiKey = 'bomb'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '6.0': true, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeTruthy(); + }); + + it('bomb(6.0) without 6.0 support', () => { + const emojiKey = 'bomb'; + const unicodeSupportMap = emptySupportMap; + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + + it('bomb(6.0) without 6.0 but with 9.0 support', () => { + const emojiKey = 'bomb'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '9.0': true, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + + it('construction_worker_tone5(8.0) without skin tone modifier support', () => { + const emojiKey = 'construction_worker_tone5'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + skinToneModifier: false, + '9.0': true, + '8.0': true, + '7.0': true, + 6.1: true, + '6.0': true, + 5.2: true, + 5.1: true, + 4.1: true, + '4.0': true, + 3.2: true, + '3.0': true, + 1.1: true, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + + it('use native keycap on >=57 chrome', () => { + const emojiKey = 'five'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '3.0': true, + meta: { + isChrome: true, + chromeVersion: 57, + }, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeTruthy(); + }); + + it('fallback keycap on <57 chrome', () => { + const emojiKey = 'five'; + const unicodeSupportMap = Object.assign({}, emptySupportMap, { + '3.0': true, + meta: { + isChrome: true, + chromeVersion: 50, + }, + }); + const isSupported = isEmojiUnicodeSupported( + unicodeSupportMap, + emojiFixtureMap[emojiKey].moji, + emojiFixtureMap[emojiKey].unicodeVersion, + ); + expect(isSupported).toBeFalsy(); + }); + }); +}); diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/gl_emoji_spec.js deleted file mode 100644 index be1ced4aaf9..00000000000 --- a/spec/javascripts/gl_emoji_spec.js +++ /dev/null @@ -1,429 +0,0 @@ -import { glEmojiTag } from '~/behaviors/gl_emoji'; -import isEmojiUnicodeSupported, { - isFlagEmoji, - isKeycapEmoji, - isSkinToneComboEmoji, - isHorceRacingSkinToneComboEmoji, - isPersonZwjEmoji, -} from '~/emoji/support/is_emoji_unicode_supported'; - -const emptySupportMap = { - personZwj: false, - horseRacing: false, - flag: false, - skinToneModifier: false, - '9.0': false, - '8.0': false, - '7.0': false, - 6.1: false, - '6.0': false, - 5.2: false, - 5.1: false, - 4.1: false, - '4.0': false, - 3.2: false, - '3.0': false, - 1.1: false, -}; - -const emojiFixtureMap = { - bomb: { - name: 'bomb', - moji: '💣', - unicodeVersion: '6.0', - }, - construction_worker_tone5: { - name: 'construction_worker_tone5', - moji: '👷🏿', - unicodeVersion: '8.0', - }, - five: { - name: 'five', - moji: '5️⃣', - unicodeVersion: '3.0', - }, - grey_question: { - name: 'grey_question', - moji: '❔', - unicodeVersion: '6.0', - }, -}; - -function markupToDomElement(markup) { - const div = document.createElement('div'); - div.innerHTML = markup; - return div.firstElementChild; -} - -function testGlEmojiImageFallback(element, name, src) { - expect(element.tagName.toLowerCase()).toBe('img'); - expect(element.getAttribute('src')).toBe(src); - expect(element.getAttribute('title')).toBe(`:${name}:`); - expect(element.getAttribute('alt')).toBe(`:${name}:`); -} - -const defaults = { - forceFallback: false, - sprite: false, -}; - -function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) { - const opts = Object.assign({}, defaults, options); - expect(element.tagName.toLowerCase()).toBe('gl-emoji'); - expect(element.dataset.name).toBe(name); - expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0); - expect(element.dataset.unicodeVersion).toBe(unicodeVersion); - - const fallbackSpriteClass = `emoji-${name}`; - if (opts.sprite) { - expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass); - } - - if (opts.forceFallback && opts.sprite) { - expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`); - } - - if (opts.forceFallback && !opts.sprite) { - // Check for image fallback - testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc); - } else { - // Otherwise make sure things are still unicode text - expect(element.textContent.trim()).toBe(unicodeMoji); - } -} - -describe('gl_emoji', () => { - describe('glEmojiTag', () => { - it('bomb emoji', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - ); - }); - - it('bomb emoji with image fallback', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { - forceFallback: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - }, - ); - }); - - it('bomb emoji with sprite fallback readiness', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { - sprite: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - sprite: true, - }, - ); - }); - it('bomb emoji with sprite fallback', () => { - const emojiKey = 'bomb'; - const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, { - forceFallback: true, - sprite: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - sprite: true, - }, - ); - }); - - it('question mark when invalid emoji name given', () => { - const name = 'invalid_emoji'; - const emojiKey = 'grey_question'; - const markup = glEmojiTag(name); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - ); - }); - - it('question mark with image fallback when invalid emoji name given', () => { - const name = 'invalid_emoji'; - const emojiKey = 'grey_question'; - const markup = glEmojiTag(name, { - forceFallback: true, - }); - const glEmojiElement = markupToDomElement(markup); - testGlEmojiElement( - glEmojiElement, - emojiFixtureMap[emojiKey].name, - emojiFixtureMap[emojiKey].unicodeVersion, - emojiFixtureMap[emojiKey].moji, - { - forceFallback: true, - }, - ); - }); - }); - - describe('isFlagEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isFlagEmoji('')).toBeFalsy(); - }); - it('should detect flag_ac', () => { - expect(isFlagEmoji('🇦🇨')).toBeTruthy(); - }); - it('should detect flag_us', () => { - expect(isFlagEmoji('🇺🇸')).toBeTruthy(); - }); - it('should detect flag_zw', () => { - expect(isFlagEmoji('🇿🇼')).toBeTruthy(); - }); - it('should not detect flags', () => { - expect(isFlagEmoji('🎏')).toBeFalsy(); - }); - it('should not detect triangular_flag_on_post', () => { - expect(isFlagEmoji('🚩')).toBeFalsy(); - }); - it('should not detect single letter', () => { - expect(isFlagEmoji('🇦')).toBeFalsy(); - }); - it('should not detect >2 letters', () => { - expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy(); - }); - }); - - describe('isKeycapEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isKeycapEmoji('')).toBeFalsy(); - }); - it('should detect one(keycap)', () => { - expect(isKeycapEmoji('1️⃣')).toBeTruthy(); - }); - it('should detect nine(keycap)', () => { - expect(isKeycapEmoji('9️⃣')).toBeTruthy(); - }); - it('should not detect ten(keycap)', () => { - expect(isKeycapEmoji('🔟')).toBeFalsy(); - }); - it('should not detect hash(keycap)', () => { - expect(isKeycapEmoji('#⃣')).toBeFalsy(); - }); - }); - - describe('isSkinToneComboEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isSkinToneComboEmoji('')).toBeFalsy(); - }); - it('should detect hand_splayed_tone5', () => { - expect(isSkinToneComboEmoji('🖐🏿')).toBeTruthy(); - }); - it('should not detect hand_splayed', () => { - expect(isSkinToneComboEmoji('🖐')).toBeFalsy(); - }); - it('should detect lifter_tone1', () => { - expect(isSkinToneComboEmoji('🏋🏻')).toBeTruthy(); - }); - it('should not detect lifter', () => { - expect(isSkinToneComboEmoji('🏋')).toBeFalsy(); - }); - it('should detect rowboat_tone4', () => { - expect(isSkinToneComboEmoji('🚣🏾')).toBeTruthy(); - }); - it('should not detect rowboat', () => { - expect(isSkinToneComboEmoji('🚣')).toBeFalsy(); - }); - it('should not detect individual tone emoji', () => { - expect(isSkinToneComboEmoji('🏻')).toBeFalsy(); - }); - }); - - describe('isHorceRacingSkinToneComboEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy(); - }); - it('should detect horse_racing_tone2', () => { - expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBeTruthy(); - }); - it('should not detect horse_racing', () => { - expect(isHorceRacingSkinToneComboEmoji('🏇')).toBeFalsy(); - }); - }); - - describe('isPersonZwjEmoji', () => { - it('should gracefully handle empty string', () => { - expect(isPersonZwjEmoji('')).toBeFalsy(); - }); - it('should detect couple_mm', () => { - expect(isPersonZwjEmoji('👨‍❤️‍👨')).toBeTruthy(); - }); - it('should not detect couple_with_heart', () => { - expect(isPersonZwjEmoji('💑')).toBeFalsy(); - }); - it('should not detect couplekiss', () => { - expect(isPersonZwjEmoji('💏')).toBeFalsy(); - }); - it('should detect family_mmb', () => { - expect(isPersonZwjEmoji('👨‍👨‍👦')).toBeTruthy(); - }); - it('should detect family_mwgb', () => { - expect(isPersonZwjEmoji('👨‍👩‍👧‍👦')).toBeTruthy(); - }); - it('should not detect family', () => { - expect(isPersonZwjEmoji('👪')).toBeFalsy(); - }); - it('should detect kiss_ww', () => { - expect(isPersonZwjEmoji('👩‍❤️‍💋‍👩')).toBeTruthy(); - }); - it('should not detect girl', () => { - expect(isPersonZwjEmoji('👧')).toBeFalsy(); - }); - it('should not detect girl_tone5', () => { - expect(isPersonZwjEmoji('👧🏿')).toBeFalsy(); - }); - it('should not detect man', () => { - expect(isPersonZwjEmoji('👨')).toBeFalsy(); - }); - it('should not detect woman', () => { - expect(isPersonZwjEmoji('👩')).toBeFalsy(); - }); - }); - - describe('isEmojiUnicodeSupported', () => { - it('should gracefully handle empty string with unicode support', () => { - const isSupported = isEmojiUnicodeSupported( - { '1.0': true }, - '', - '1.0', - ); - expect(isSupported).toBeTruthy(); - }); - it('should gracefully handle empty string without unicode support', () => { - const isSupported = isEmojiUnicodeSupported( - {}, - '', - '1.0', - ); - expect(isSupported).toBeFalsy(); - }); - it('bomb(6.0) with 6.0 support', () => { - const emojiKey = 'bomb'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '6.0': true, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeTruthy(); - }); - - it('bomb(6.0) without 6.0 support', () => { - const emojiKey = 'bomb'; - const unicodeSupportMap = emptySupportMap; - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - - it('bomb(6.0) without 6.0 but with 9.0 support', () => { - const emojiKey = 'bomb'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '9.0': true, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - - it('construction_worker_tone5(8.0) without skin tone modifier support', () => { - const emojiKey = 'construction_worker_tone5'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - skinToneModifier: false, - '9.0': true, - '8.0': true, - '7.0': true, - 6.1: true, - '6.0': true, - 5.2: true, - 5.1: true, - 4.1: true, - '4.0': true, - 3.2: true, - '3.0': true, - 1.1: true, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - - it('use native keycap on >=57 chrome', () => { - const emojiKey = 'five'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '3.0': true, - meta: { - isChrome: true, - chromeVersion: 57, - }, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeTruthy(); - }); - - it('fallback keycap on <57 chrome', () => { - const emojiKey = 'five'; - const unicodeSupportMap = Object.assign({}, emptySupportMap, { - '3.0': true, - meta: { - isChrome: true, - chromeVersion: 50, - }, - }); - const isSupported = isEmojiUnicodeSupported( - unicodeSupportMap, - emojiFixtureMap[emojiKey].moji, - emojiFixtureMap[emojiKey].unicodeVersion, - ); - expect(isSupported).toBeFalsy(); - }); - }); -}); -- cgit v1.2.1 From 6e3a7c3ac46c5638cf3597e44611e746ebbcb1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 12 Jun 2017 13:22:33 +0800 Subject: supplement bulgarian translation Fix #33561 --- app/assets/javascripts/locale/bg/app.js | 2 +- ...61-supplement_bulgarian_translation_of_i18n.yml | 4 + locale/bg/gitlab.po | 892 ++++++++++++++++++++- 3 files changed, 886 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js index ba56c0bea25..24888e33b2e 100644 --- a/app/assets/javascripts/locale/bg/app.js +++ b/app/assets/javascripts/locale/bg/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-05 09:40-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"ByAuthor|by":["от"],"Commit":["Подаване","Подавания"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Deploy":["Внедряване","Внедрявания"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Introducing Cycle Analytics":["Представяме Ви анализът на циклите"],"Last %d day":["Последния %d ден","Последните %d дни"],"Limited to showing %d event at most":["Ограничено до показване на последното %d събитие","Ограничено до показване на последните %d събития"],"Median":["Медиана"],"New Issue":["Нов проблем","Нови проблема"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"OpenedNDaysAgo|Opened":["Отворен"],"Pipeline Health":["Състояние"],"ProjectLifecycle|Stage":["Етап"],"Read more":["Прочетете повече"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани задачи за внедряване"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнените на първата Ви такава задача."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"You need permission.":["Нуждаете се от разрешение."],"day":["ден","дни"]}}}; \ No newline at end of file +var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-13 04:23-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} подаде %{commit_timeago}"],"About auto deploy":["Относно автоматичното внедряване"],"Active":["Активно"],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":["Прикачете файл чрез влачене и пускане или %{upload_link}"],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":["Разглеждане на файловете"],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":["Избиране в клона"],"ChangeTypeActionLabel|Revert in branch":["Отмяна в клона"],"ChangeTypeAction|Cherry-pick":["Подбиране"],"ChangeType|commit":["подаване"],"ChangeType|merge request":["заявка за сливане"],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":["Подбиране на това подаване"],"Cherry-pick this merge-request":["Подбиране на тази заявка за сливане"],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":["Съобщение за подаването"],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":["Подадено от"],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":["Създаване на нов…"],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":["Етикет"],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":["Синтаксис на „Cron“"],"Custom":["Персонализиран"],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":["Задайте потребителски шаблон, използвайки синтаксиса на „Cron“"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":["Изпращане на кръпките по е-поща"],"DownloadCommit|Plain Diff":["Обикновен файл с разлики"],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":["Всеки ден (в 4 ч. сутринта)"],"Every month (on the 1st at 4:00am)":["Всеки месец (на 1-во число, в 4 ч. сутринта)"],"Every week (Sundays at 4:00am)":["Всяка седмица (в неделя, в 4 ч. сутринта)"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Разклонение","Разклонения"],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":["Научете повече в"],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":["Нов план"],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":["Филтър"],"OpenedNDaysAgo|Opened":["Отворен"],"Options":["Опции"],"Owner":["Собственик"],"Pipeline":["Схема"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":["Отмяна на това подаване"],"Revert this merge-request":["Отмяна на тази заявка за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":["Планиране на схемите"],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":["Създайте нова заявка за сливане с тези промени"],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["Този план за схема ще изпълнява схемите в бъдеще, периодично, за определени клонове или етикети. Тези планирани схеми ще наследят ограниченията на достъпа до проекта на свързания с тях потребител."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["На път сте да премахнете връзката на разклонението към оригиналния проект, „%{forked_from_project}“. НАИСТИНА ли искате това?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["родител","родители"],"pipeline schedules documentation":["документацията за планирането на схеми"],"with stage":["с етап","с етапи"]}}}; \ No newline at end of file diff --git a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml new file mode 100644 index 00000000000..4f2ba2e1de3 --- /dev/null +++ b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml @@ -0,0 +1,4 @@ +--- +title: Supplement Bulgarian translation of Project Page & Repository Page +merge_request: 12083 +author: Lyubomir Vasilev diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 43a5de65c43..370aca1f1d9 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -3,25 +3,245 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-04 19:24-0500\n" +"POT-Creation-Date: 2017-06-12 19:29-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-05 09:40-0400\n" +"PO-Revision-Date: 2017-06-13 04:23-0400\n" "Last-Translator: Lyubomir Vasilev \n" -"Language-Team: Bulgarian\n" +"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} подаде %{commit_timeago}" + +msgid "About auto deploy" +msgstr "Относно автоматичното внедряване" + +msgid "Active" +msgstr "Активно" + +msgid "Activity" +msgstr "Дейност" + +msgid "Add Changelog" +msgstr "Добавяне на списък с промени" + +msgid "Add Contribution guide" +msgstr "Добавяне на ръководство за сътрудничество" + +msgid "Add License" +msgstr "Добавяне на лиценз" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате " +"промени чрез SSH." + +msgid "Add new directory" +msgstr "Добавяне на нова папка" + +msgid "Archived project! Repository is read-only" +msgstr "Архивиран проект! Хранилището е само за четене" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Наистина ли искате да изтриете този план за схема?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "Прикачете файл чрез влачене и пускане или %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Клон" +msgstr[1] "Клонове" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"Клонът %{branch_name} беше създаден. За да настроите " +"автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте " +"промените си. %{link_to_autodeploy_doc}" + +msgid "Branches" +msgstr "Клонове" + +msgid "Browse files" +msgstr "Разглеждане на файловете" + msgid "ByAuthor|by" msgstr "от" +msgid "CI configuration" +msgstr "Конфигурация на непрекъсната интеграция" + +msgid "Cancel" +msgstr "Отказ" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "Избиране в клона" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "Отмяна в клона" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "Подбиране" + +msgid "ChangeType|commit" +msgstr "подаване" + +msgid "ChangeType|merge request" +msgstr "заявка за сливане" + +msgid "Changelog" +msgstr "Списък с промени" + +msgid "Charts" +msgstr "Графики" + +msgid "Cherry-pick this commit" +msgstr "Подбиране на това подаване" + +msgid "Cherry-pick this merge-request" +msgstr "Подбиране на тази заявка за сливане" + +msgid "CiStatusLabel|canceled" +msgstr "отказано" + +msgid "CiStatusLabel|created" +msgstr "създадено" + +msgid "CiStatusLabel|failed" +msgstr "неуспешно" + +msgid "CiStatusLabel|manual action" +msgstr "ръчно действие" + +msgid "CiStatusLabel|passed" +msgstr "успешно" + +msgid "CiStatusLabel|passed with warnings" +msgstr "успешно, с предупреждения" + +msgid "CiStatusLabel|pending" +msgstr "на изчакване" + +msgid "CiStatusLabel|skipped" +msgstr "пропуснато" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "чакане за ръчно действие" + +msgid "CiStatusText|blocked" +msgstr "блокирано" + +msgid "CiStatusText|canceled" +msgstr "отказано" + +msgid "CiStatusText|created" +msgstr "създадено" + +msgid "CiStatusText|failed" +msgstr "неуспешно" + +msgid "CiStatusText|manual" +msgstr "ръчно" + +msgid "CiStatusText|passed" +msgstr "успешно" + +msgid "CiStatusText|pending" +msgstr "на изчакване" + +msgid "CiStatusText|skipped" +msgstr "пропуснато" + +msgid "CiStatus|running" +msgstr "протича в момента" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Commit message" +msgstr "Съобщение за подаването" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Добавяне на „%{file_name}“" + +msgid "Commits" +msgstr "Подавания" + +msgid "Commits|History" +msgstr "История" + +msgid "Committed by" +msgstr "Подадено от" + +msgid "Compare" +msgstr "Сравнение" + +msgid "Contribution guide" +msgstr "Ръководство за сътрудничество" + +msgid "Contributors" +msgstr "Сътрудници" + +msgid "Copy URL to clipboard" +msgstr "Копиране на адреса в буфера за обмен" + +msgid "Copy commit SHA to clipboard" +msgstr "Копиране на идентификатора на подаването в буфера за обмен" + +msgid "Create New Directory" +msgstr "Създаване на нова папка" + +msgid "Create directory" +msgstr "Създаване на папка" + +msgid "Create empty bare repository" +msgstr "Създаване на празно хранилище" + +msgid "Create merge request" +msgstr "Създаване на заявка за сливане" + +msgid "Create new..." +msgstr "Създаване на нов…" + +msgid "CreateNewFork|Fork" +msgstr "Разклоняване" + +msgid "CreateTag|Tag" +msgstr "Етикет" + +msgid "Cron Timezone" +msgstr "Часова зона за „Cron“" + +msgid "Cron syntax" +msgstr "Синтаксис на „Cron“" + +msgid "Custom" +msgstr "Персонализиран" + +msgid "Custom notification events" +msgstr "Персонализирани събития за известяване" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Персонализираните нива на известяване са същите като нивата за участие. С " +"персонализираните нива на известяване ще можете да получавате и известия за " +"избрани събития. За да научите повече, прегледайте %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Анализ на циклите" + msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " "to production in your project." @@ -50,17 +270,97 @@ msgstr "Подготовка за издаване" msgid "CycleAnalyticsStage|Test" msgstr "Тестване" +msgid "Define a custom pattern with cron syntax" +msgstr "Задайте потребителски шаблон, използвайки синтаксиса на „Cron“" + +msgid "Delete" +msgstr "Изтриване" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "Внедряване" msgstr[1] "Внедрявания" +msgid "Description" +msgstr "Описание" + +msgid "Directory name" +msgstr "Име на папката" + +msgid "Don't show again" +msgstr "Да не се показва повече" + +msgid "Download" +msgstr "Сваляне" + +msgid "Download tar" +msgstr "Сваляне във формат „tar“" + +msgid "Download tar.bz2" +msgstr "Сваляне във формат „tar.bz2“" + +msgid "Download tar.gz" +msgstr "Сваляне във формат „tar.gz“" + +msgid "Download zip" +msgstr "Сваляне във формат „zip“" + +msgid "DownloadArtifacts|Download" +msgstr "Сваляне" + +msgid "DownloadCommit|Email Patches" +msgstr "Изпращане на кръпките по е-поща" + +msgid "DownloadCommit|Plain Diff" +msgstr "Обикновен файл с разлики" + +msgid "DownloadSource|Download" +msgstr "Сваляне" + +msgid "Edit" +msgstr "Редактиране" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Редактиране на плана %{id} за схема" + +msgid "Every day (at 4:00am)" +msgstr "Всеки ден (в 4 ч. сутринта)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Всеки месец (на 1-во число, в 4 ч. сутринта)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Всяка седмица (в неделя, в 4 ч. сутринта)" + +msgid "Failed to change the owner" +msgstr "Собственикът не може да бъде променен" + +msgid "Failed to remove the pipeline schedule" +msgstr "Планът за схема не може да бъде премахнат" + +msgid "Files" +msgstr "Файлове" + +msgid "Find by path" +msgstr "Търсене по път" + +msgid "Find file" +msgstr "Търсене на файл" + msgid "FirstPushedBy|First" msgstr "Първо" msgid "FirstPushedBy|pushed by" msgstr "изпращане на промени от" +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "Разклонение" +msgstr[1] "Разклонения" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "Разклонение на" + msgid "From issue creation until deploy to production" msgstr "От създаването на проблема до внедряването в крайната версия" @@ -68,50 +368,290 @@ msgid "From merge request merge until deploy to production" msgstr "" "От прилагането на заявката за сливане до внедряването в крайната версия" +msgid "Go to your fork" +msgstr "Към Вашето разклонение" + +msgid "GoToYourFork|Fork" +msgstr "Разклонение" + +msgid "Home" +msgstr "Начало" + +msgid "Housekeeping successfully started" +msgstr "Освежаването започна успешно" + +msgid "Import repository" +msgstr "Внасяне на хранилище" + +msgid "Interval Pattern" +msgstr "Шаблон за интервала" + msgid "Introducing Cycle Analytics" -msgstr "Представяме Ви анализът на циклите" +msgstr "Представяме Ви анализа на циклите" + +msgid "LFSStatus|Disabled" +msgstr "Изключено" + +msgid "LFSStatus|Enabled" +msgstr "Включено" msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последния %d ден" msgstr[1] "Последните %d дни" +msgid "Last Pipeline" +msgstr "Последна схема" + +msgid "Last Update" +msgstr "Последна промяна" + +msgid "Last commit" +msgstr "Последно подаване" + +msgid "Learn more in the" +msgstr "Научете повече в" + +msgid "Leave group" +msgstr "Напускане на групата" + +msgid "Leave project" +msgstr "Напускане на проекта" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" -msgstr[0] "Ограничено до показване на последното %d събитие" -msgstr[1] "Ограничено до показване на последните %d събития" +msgstr[0] "Ограничено до показване на най-много %d събитие" +msgstr[1] "Ограничено до показване на най-много %d събития" msgid "Median" msgstr "Медиана" +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "добавите SSH ключ" + msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Нов проблем" msgstr[1] "Нови проблема" +msgid "New Pipeline Schedule" +msgstr "Нов план за схема" + +msgid "New branch" +msgstr "Нов клон" + +msgid "New directory" +msgstr "Нова папка" + +msgid "New file" +msgstr "Нов файл" + +msgid "New issue" +msgstr "Нов проблем" + +msgid "New merge request" +msgstr "Нова заявка за сливане" + +msgid "New schedule" +msgstr "Нов план" + +msgid "New snippet" +msgstr "Нов отрязък" + +msgid "New tag" +msgstr "Нов етикет" + +msgid "No repository" +msgstr "Няма хранилище" + +msgid "No schedules" +msgstr "Няма планове" + msgid "Not available" msgstr "Не е налично" msgid "Not enough data" msgstr "Няма достатъчно данни" +msgid "Notification events" +msgstr "Събития за известяване" + +msgid "NotificationEvent|Close issue" +msgstr "Затваряне на проблем" + +msgid "NotificationEvent|Close merge request" +msgstr "Затваряне на заявка за сливане" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Неуспешно изпълнение на схема" + +msgid "NotificationEvent|Merge merge request" +msgstr "Прилагане на заявка за сливане" + +msgid "NotificationEvent|New issue" +msgstr "Нов проблем" + +msgid "NotificationEvent|New merge request" +msgstr "Нова заявка за сливане" + +msgid "NotificationEvent|New note" +msgstr "Нова бележка" + +msgid "NotificationEvent|Reassign issue" +msgstr "Преназначаване на проблем" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Преназначаване на заявка за сливане" + +msgid "NotificationEvent|Reopen issue" +msgstr "Повторно отваряне на проблем" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Успешно изпълнение на схема" + +msgid "NotificationLevel|Custom" +msgstr "Персонализирани" + +msgid "NotificationLevel|Disabled" +msgstr "Изключени" + +msgid "NotificationLevel|Global" +msgstr "Глобални" + +msgid "NotificationLevel|On mention" +msgstr "При споменаване" + +msgid "NotificationLevel|Participate" +msgstr "Участие" + +msgid "NotificationLevel|Watch" +msgstr "Наблюдение" + +msgid "OfSearchInADropdown|Filter" +msgstr "Филтър" + msgid "OpenedNDaysAgo|Opened" msgstr "Отворен" +msgid "Options" +msgstr "Опции" + +msgid "Owner" +msgstr "Собственик" + +msgid "Pipeline" +msgstr "Схема" + msgid "Pipeline Health" msgstr "Състояние" +msgid "Pipeline Schedule" +msgstr "План за схема" + +msgid "Pipeline Schedules" +msgstr "Планове за схема" + +msgid "PipelineSchedules|Activated" +msgstr "Включено" + +msgid "PipelineSchedules|Active" +msgstr "Активно" + +msgid "PipelineSchedules|All" +msgstr "Всички" + +msgid "PipelineSchedules|Inactive" +msgstr "Неактивно" + +msgid "PipelineSchedules|Next Run" +msgstr "Следващо изпълнение" + +msgid "PipelineSchedules|None" +msgstr "Нищо" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Въведете кратко описание за тази схема" + +msgid "PipelineSchedules|Take ownership" +msgstr "Поемане на собствеността" + +msgid "PipelineSchedules|Target" +msgstr "Цел" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване." + +msgid "Project '%{project_name}' was successfully created." +msgstr "Проектът „%{project_name}“ беше създаден успешно." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Проектът „%{project_name}“ беше обновен успешно." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Проектът „%{project_name}“ ще бъде изтрит." + +msgid "Project access must be granted explicitly to each user." +msgstr "" +"Достъпът до проекта трябва да бъде даван поотделно на всеки потребител." + +msgid "Project export could not be deleted." +msgstr "Изнесените данни на проекта не могат да бъдат изтрити." + +msgid "Project export has been deleted." +msgstr "Изнесените данни на проекта бяха изтрити." + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" +"Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова " +"от настройките на проекта." + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Изнасянето на проекта започна. Ще получите връзка към данните по е-поща." + +msgid "Project home" +msgstr "Начална страница на проекта" + +msgid "ProjectFeature|Disabled" +msgstr "Изключено" + +msgid "ProjectFeature|Everyone with access" +msgstr "Всеки с достъп" + +msgid "ProjectFeature|Only team members" +msgstr "Само членовете на екипа" + +msgid "ProjectFileTree|Name" +msgstr "Име" + +msgid "ProjectLastActivity|Never" +msgstr "Никога" + msgid "ProjectLifecycle|Stage" msgstr "Етап" +msgid "ProjectNetworkGraph|Graph" +msgstr "Графика" + msgid "Read more" msgstr "Прочетете повече" +msgid "Readme" +msgstr "ПрочетиМе" + +msgid "RefSwitcher|Branches" +msgstr "Клонове" + +msgid "RefSwitcher|Tags" +msgstr "Етикети" + msgid "Related Commits" msgstr "Свързани подавания" msgid "Related Deployed Jobs" -msgstr "Свързани задачи за внедряване" +msgstr "Свързани внедрени задачи" msgid "Related Issues" msgstr "Свързани проблеми" @@ -125,11 +665,87 @@ msgstr "Свързани заявки за сливане" msgid "Related Merged Requests" msgstr "Свързани приложени заявки за сливане" +msgid "Remind later" +msgstr "Напомняне по-късно" + +msgid "Remove project" +msgstr "Премахване на проекта" + +msgid "Request Access" +msgstr "Заявка за достъп" + +msgid "Revert this commit" +msgstr "Отмяна на това подаване" + +msgid "Revert this merge-request" +msgstr "Отмяна на тази заявка за сливане" + +msgid "Save pipeline schedule" +msgstr "Запазване на плана за схема" + +msgid "Schedule a new pipeline" +msgstr "Създаване на нов план за схема" + +msgid "Scheduling Pipelines" +msgstr "Планиране на схемите" + +msgid "Search branches and tags" +msgstr "Търсене в клоновете и етикетите" + +msgid "Select Archive Format" +msgstr "Изберете формата на архива" + +msgid "Select a timezone" +msgstr "Изберете часова зона" + +msgid "Select target branch" +msgstr "Изберете целеви клон" + +msgid "Set a password on your account to pull or push via %{protocol}" +msgstr "" +"Задайте парола на профила си, за да можете да изтегляте и изпращате промени " +"чрез %{protocol}" + +msgid "Set up CI" +msgstr "Настройка на НИ" + +msgid "Set up Koding" +msgstr "Настройка на „Koding“" + +msgid "Set up auto deploy" +msgstr "Настройка на авт. внедряване" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "зададете парола" + msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показване на %d събитие" msgstr[1] "Показване на %d събития" +msgid "Source code" +msgstr "Изходен код" + +msgid "StarProject|Star" +msgstr "Звезда" + +msgid "Start a new merge request with these changes" +msgstr "Създайте нова заявка за сливане с тези промени" + +msgid "Switch branch/tag" +msgstr "Преминаване към клон/етикет" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Етикет" +msgstr[1] "Етикети" + +msgid "Tags" +msgstr "Етикети" + +msgid "Target Branch" +msgstr "Целеви клон" + msgid "" "The coding stage shows the time from the first commit to creating the merge " "request. The data will automatically be added here once you create your " @@ -142,6 +758,9 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "Съвкупността от събития добавени към данните събрани за този етап." +msgid "The fork relationship has been removed." +msgstr "Връзката на разклонение беше премахната." + msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " @@ -155,6 +774,15 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "Етапът от цикъла на разработка" +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"Този план за схема ще изпълнява схемите в бъдеще, периодично, за определени " +"клонове или етикети. Тези планирани схеми ще наследят ограниченията на " +"достъпа до проекта на свързания с тях потребител." + msgid "" "The planning stage shows the time from the previous step to pushing your " "first commit. This time will be added automatically once you push your first " @@ -170,7 +798,18 @@ msgid "" "once you have completed the full idea to production cycle." msgstr "" "Етапът на издаване показва общото време, което е нужно от създаването на " -"проблем до внедряването на кода в крайната версия." +"проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени " +"автоматично след като завършите един пълен цикъл и превърнете първата си " +"идея в реалност." + +msgid "The project can be accessed by any logged in user." +msgstr "Всеки вписан потребител има достъп до проекта." + +msgid "The project can be accessed without any authentication." +msgstr "Всеки може да има достъп до проекта, без нужда от удостоверяване." + +msgid "The repository for this project does not exist." +msgstr "Хранилището за този проект не съществува." msgid "" "The review stage shows the time from creating the merge request to merging " @@ -197,8 +836,8 @@ msgid "" "first pipeline finishes running." msgstr "" "Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни " -"всички задачи за свързаната заявка за сливане. Данните ще бъдат добавени " -"автоматично след като приключи изпълнените на първата Ви такава задача." +"всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат " +"добавени автоматично след като приключи изпълнението на първата Ви схема." msgid "The time taken by each data entry gathered by that stage." msgstr "Времето, което отнема всеки запис от данни за съответния етап." @@ -212,6 +851,13 @@ msgstr "" "данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е " "(5+7)/2 = 6." +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" +"Това означава, че няма да можете да изпращате код, докато не създадете " +"празно хранилище или не внесете съществуващо такова." + msgid "Time before an issue gets scheduled" msgstr "Време преди един проблем да бъде планиран за работа" @@ -225,6 +871,129 @@ msgstr "" msgid "Time until first merge request" msgstr "Време преди първата заявка за сливане" +msgid "Timeago|%s days ago" +msgstr "преди %s дни" + +msgid "Timeago|%s days remaining" +msgstr "остават %s дни" + +msgid "Timeago|%s hours remaining" +msgstr "остават %s часа" + +msgid "Timeago|%s minutes ago" +msgstr "преди %s минути" + +msgid "Timeago|%s minutes remaining" +msgstr "остават %s минути" + +msgid "Timeago|%s months ago" +msgstr "преди %s месеца" + +msgid "Timeago|%s months remaining" +msgstr "остават %s месеца" + +msgid "Timeago|%s seconds remaining" +msgstr "остават %s секунди" + +msgid "Timeago|%s weeks ago" +msgstr "преди %s седмици" + +msgid "Timeago|%s weeks remaining" +msgstr "остават %s седмици" + +msgid "Timeago|%s years ago" +msgstr "преди %s години" + +msgid "Timeago|%s years remaining" +msgstr "остават %s години" + +msgid "Timeago|1 day remaining" +msgstr "остава 1 ден" + +msgid "Timeago|1 hour remaining" +msgstr "остава 1 час" + +msgid "Timeago|1 minute remaining" +msgstr "остава 1 минута" + +msgid "Timeago|1 month remaining" +msgstr "остава 1 месец" + +msgid "Timeago|1 week remaining" +msgstr "остава 1 седмица" + +msgid "Timeago|1 year remaining" +msgstr "остава 1 година" + +msgid "Timeago|Past due" +msgstr "Просрочено" + +msgid "Timeago|a day ago" +msgstr "преди един ден" + +msgid "Timeago|a month ago" +msgstr "преди един месец" + +msgid "Timeago|a week ago" +msgstr "преди една седмица" + +msgid "Timeago|a while" +msgstr "преди известно време" + +msgid "Timeago|a year ago" +msgstr "преди една година" + +msgid "Timeago|about %s hours ago" +msgstr "преди около %s часа" + +msgid "Timeago|about a minute ago" +msgstr "преди около една минута" + +msgid "Timeago|about an hour ago" +msgstr "преди около един час" + +msgid "Timeago|in %s days" +msgstr "след %s дни" + +msgid "Timeago|in %s hours" +msgstr "след %s часа" + +msgid "Timeago|in %s minutes" +msgstr "след %s минути" + +msgid "Timeago|in %s months" +msgstr "след %s месеца" + +msgid "Timeago|in %s seconds" +msgstr "след %s секунди" + +msgid "Timeago|in %s weeks" +msgstr "след %s седмици" + +msgid "Timeago|in %s years" +msgstr "след %s години" + +msgid "Timeago|in 1 day" +msgstr "след 1 ден" + +msgid "Timeago|in 1 hour" +msgstr "след 1 час" + +msgid "Timeago|in 1 minute" +msgstr "след 1 минута" + +msgid "Timeago|in 1 month" +msgstr "след 1 месец" + +msgid "Timeago|in 1 week" +msgstr "след 1 седмица" + +msgid "Timeago|in 1 year" +msgstr "след 1 година" + +msgid "Timeago|less than a minute ago" +msgstr "преди по-малко от минута" + msgid "Time|hr" msgid_plural "Time|hrs" msgstr[0] "час" @@ -244,20 +1013,121 @@ msgstr "Общо време" msgid "Total test time for all commits/merges" msgstr "Общо време за тестване на всички подавания/сливания" +msgid "Unstar" +msgstr "Без звезда" + +msgid "Upload New File" +msgstr "Качване на нов файл" + +msgid "Upload file" +msgstr "Качване на файл" + +msgid "Use your global notification setting" +msgstr "Използване на глобалната Ви настройка за известията" + +msgid "VisibilityLevel|Internal" +msgstr "Вътрешен" + +msgid "VisibilityLevel|Private" +msgstr "Частен" + +msgid "VisibilityLevel|Public" +msgstr "Публичен" + msgid "Want to see the data? Please ask an administrator for access." msgstr "Искате ли да видите данните? Помолете администратор за достъп." msgid "We don't have enough data to show this stage." msgstr "Няма достатъчно данни за този етап." -msgid "You have reached your project limit" +msgid "Withdraw Access Request" +msgstr "Оттегляне на заявката за достъп" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" msgstr "" +"На път сте да премахнете „%{project_name_with_namespace}“.\n" +"Ако го премахнете, той НЕ може да бъде възстановен!\n" +"НАИСТИНА ли искате това?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да премахнете връзката на разклонението към оригиналния проект, " +"„%{forked_from_project}“. НАИСТИНА ли искате това?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да прехвърлите „%{project_name_with_namespace}“ към друг " +"собственик. НАИСТИНА ли искате това?" + +msgid "You can only add files when you are on a branch" +msgstr "Можете да добавяте файлове само когато се намирате в клон" + +msgid "You must sign in to star a project" +msgstr "Трябва да се впишете, за да отбележите проект със звезда" msgid "You need permission." msgstr "Нуждаете се от разрешение." +msgid "You will not get any notifications via email" +msgstr "Няма да получавате никакви известия по е-поща" + +msgid "You will only receive notifications for the events you choose" +msgstr "Ще получавате известия само за събитията, за които желаете" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "Ще получавате известия само за нещата, в които участвате" + +msgid "You will receive notifications for any activity" +msgstr "Ще получавате известия за всяка дейност" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "Ще получавате известия само за коментари, в които Ви @споменават" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, " +"докато не %{set_password_link} за профила си" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" +"Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не " +"%{add_ssh_key_link} в профила си" + +msgid "Your name" +msgstr "Вашето име" + msgid "day" msgid_plural "days" msgstr[0] "ден" msgstr[1] "дни" +msgid "notification emails" +msgstr "известия по е-поща" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "родител" +msgstr[1] "родители" + +msgid "pipeline schedules documentation" +msgstr "документацията за планирането на схеми" + +msgid "with stage" +msgid_plural "with stages" +msgstr[0] "с етап" +msgstr[1] "с етапи" + -- cgit v1.2.1 From e7d12a70d283b766cbcd4f417c293f34df10de3b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 15 Jun 2017 12:50:45 +0200 Subject: Add in_review_folder to usage ping As its hard to reliably check how many review apps there are on the clients machine, we start by checking where the type is `review`. This means the folder is called that way. This will lead to a seq scan on the table. However, this is done once a week, so the benefit of adding an index seems not to apply here. --- app/models/environment.rb | 1 + changelogs/unreleased/zj-review-apps-usage-data.yml | 4 ++++ lib/gitlab/usage_data.rb | 1 + spec/lib/gitlab/usage_data_spec.rb | 1 + 4 files changed, 7 insertions(+) create mode 100644 changelogs/unreleased/zj-review-apps-usage-data.yml diff --git a/app/models/environment.rb b/app/models/environment.rb index 6211a5c1e63..c56e4bdb989 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -45,6 +45,7 @@ class Environment < ActiveRecord::Base to_sql order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC')) end + scope :in_review_folder, -> { where(environment_type: "review") } state_machine :state, initial: :available do event :start do diff --git a/changelogs/unreleased/zj-review-apps-usage-data.yml b/changelogs/unreleased/zj-review-apps-usage-data.yml new file mode 100644 index 00000000000..7d224d0fc32 --- /dev/null +++ b/changelogs/unreleased/zj-review-apps-usage-data.yml @@ -0,0 +1,4 @@ +--- +title: Add review apps to usage metrics +merge_request: 12185 +author: diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index bcba2e3e1b6..38dc82493cf 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -27,6 +27,7 @@ module Gitlab deploy_keys: DeployKey.count, deployments: Deployment.count, environments: Environment.count, + in_review_folder: Environment.in_review_folder.count, groups: Group.count, issues: Issue.count, keys: Key.count, diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index b47e1b56fa9..3c7c7562b46 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -37,6 +37,7 @@ describe Gitlab::UsageData do deploy_keys deployments environments + in_review_folder groups issues keys -- cgit v1.2.1 From b3dd9e736a725d47d12224c3fefc283ed3018fd9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 27 Jun 2017 12:13:05 +0200 Subject: Use latest chrome and chrome driver in GitLab QA --- qa/Dockerfile | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/qa/Dockerfile b/qa/Dockerfile index 97ae1961e34..f3a81a7e355 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,8 +1,6 @@ FROM ruby:2.3 LABEL maintainer "Grzegorz Bizon " - -ENV CHROME_VERSION 59.0.3071.109-1 -ENV CHROME_DRIVER_VERSION 2.30 +ENV DEBIAN_FRONTEND noninteractive ## # Update APT sources and install some dependencies @@ -15,22 +13,17 @@ RUN apt-get update && apt-get install -y wget git unzip xvfb # RUN curl -sS -L https://dl.google.com/linux/linux_signing_key.pub | apt-key add - RUN echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list -RUN apt-get update -q && DEBIAN_FRONTEND=noninteractive apt-get install -y google-chrome-stable=$CHROME_VERSION +RUN apt-get update -q && apt-get install -y google-chrome-stable && apt-get clean ## # Install chromedriver to make it work with Selenium # -RUN wget -q https://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip +RUN wget -q https://chromedriver.storage.googleapis.com/$(wget -q -O - https://chromedriver.storage.googleapis.com/LATEST_RELEASE)/chromedriver_linux64.zip RUN unzip chromedriver_linux64.zip -d /usr/local/bin -RUN apt-get clean - WORKDIR /home/qa - COPY ./Gemfile* ./ - RUN bundle install - COPY ./ ./ ENTRYPOINT ["bin/test"] -- cgit v1.2.1 From 397d3fd7d0cd352c94637a26b5ed25c025aa8bf9 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 26 Jun 2017 17:45:32 +0200 Subject: Only do one query for updating routes --- .../v1/rename_base.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index c7ce9749eba..c696c8baf7e 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -8,6 +8,7 @@ module Gitlab delegate :update_column_in_batches, :execute, :replace_sql, + :quote_string, :say, to: :migration @@ -44,10 +45,18 @@ module Gitlab def rename_routes(old_full_path, new_full_path) routes = Route.arel_table - main_route_ids = routes.project(routes[:id]).where(routes[:path].matches(old_full_path)) - child_route_ids = routes.project(routes[:id]).where(routes[:path].matches("#{old_full_path}/%")) - matching_ids = main_route_ids.union(child_route_ids) - ids = execute(matching_ids.to_sql).map { |entry| entry['id'] } + + quoted_old_full_path = quote_string(old_full_path) + quoted_old_wildcard_path = quote_string("#{old_full_path}/%") + + filter = if Database.mysql? + "lower(routes.path) = lower('#{quoted_old_full_path}') "\ + "OR routes.path LIKE '#{quoted_old_wildcard_path}'" + else + "routes.id IN "\ + "( SELECT routes.id FROM routes WHERE lower(routes.path) = lower('#{quoted_old_full_path}') "\ + "UNION SELECT routes.id FROM routes WHERE routes.path ILIKE '#{quoted_old_wildcard_path}' )" + end replace_statement = replace_sql(Route.arel_table[:path], old_full_path, @@ -56,7 +65,8 @@ module Gitlab update = Arel::UpdateManager.new(ActiveRecord::Base) .table(routes) .set([[routes[:path], replace_statement]]) - .where(routes[:id].in(ids)) + .where(Arel::Nodes::SqlLiteral.new(filter)) + execute(update.to_sql) end -- cgit v1.2.1 From 144e37c667c1681ce8c1c8292ee8f48b9eb455c5 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 27 Jun 2017 12:17:14 +0200 Subject: Remove Gitlab::Git::Repository#find_all --- app/models/network/graph.rb | 2 +- lib/gitlab/git/commit.rb | 77 ++++++++++++++++++++++++++++++++- lib/gitlab/git/repository.rb | 78 ---------------------------------- spec/lib/gitlab/git/commit_spec.rb | 27 ++++++++++++ spec/lib/gitlab/git/repository_spec.rb | 29 ------------- 5 files changed, 103 insertions(+), 110 deletions(-) diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 59737bb6085..2bc00a082df 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -113,7 +113,7 @@ module Network opts[:ref] = @commit.id if @filter_ref - @repo.find_commits(opts) + Gitlab::Git::Commit.find_all(@repo.raw_repository, opts) end def commits_sort_by_ref diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index b68378f5c0b..abd5fac8f78 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -104,9 +104,68 @@ module Gitlab [] end - # Delegate Repository#find_commits + # Returns commits collection + # + # Ex. + # Commit.find_all( + # repo, + # ref: 'master', + # max_count: 10, + # skip: 5, + # order: :date + # ) + # + # +options+ is a Hash of optional arguments to git + # :ref is the ref from which to begin (SHA1 or name) + # :contains is the commit contained by the refs from which to begin (SHA1 or name) + # :max_count is the maximum number of commits to fetch + # :skip is the number of commits to skip + # :order is the commits order and allowed value is :none (default), :date, + # :topo, or any combination of them (in an array). Commit ordering types + # are documented here: + # http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant) + # def find_all(repo, options = {}) - repo.find_commits(options) + actual_options = options.dup + + allowed_options = [:ref, :max_count, :skip, :contains, :order] + + actual_options.keep_if do |key| + allowed_options.include?(key) + end + + default_options = { skip: 0 } + actual_options = default_options.merge(actual_options) + + rugged = repo.rugged + walker = Rugged::Walker.new(rugged) + + if actual_options[:ref] + walker.push(rugged.rev_parse_oid(actual_options[:ref])) + elsif actual_options[:contains] + repo.branches_contains(actual_options[:contains]).each do |branch| + walker.push(branch.target_id) + end + else + rugged.references.each("refs/heads/*") do |ref| + walker.push(ref.target_id) + end + end + + walker.sorting(rugged_sort_type(actual_options[:order])) + + commits = [] + offset = actual_options[:skip] + limit = actual_options[:max_count] + walker.each(offset: offset, limit: limit) do |commit| + commits.push(decorate(commit)) + end + + walker.reset + + commits + rescue Rugged::OdbError + [] end def decorate(commit, ref = nil) @@ -131,6 +190,20 @@ module Gitlab diff.find_similar!(break_rewrites: break_rewrites) diff end + + # Returns the `Rugged` sorting type constant for one or more given + # sort types. Valid keys are `:none`, `:topo`, and `:date`, or an array + # containing more than one of them. `:date` uses a combination of date and + # topological sorting to closer mimic git's native ordering. + def rugged_sort_type(sort_type) + @rugged_sort_types ||= { + none: Rugged::SORT_NONE, + topo: Rugged::SORT_TOPO, + date: Rugged::SORT_DATE | Rugged::SORT_TOPO + } + + @rugged_sort_types.fetch(sort_type, Rugged::SORT_NONE) + end end def initialize(raw_commit, head = nil) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index c1f942f931a..0a0c6f76cd3 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -494,70 +494,6 @@ module Gitlab end end - # Returns commits collection - # - # Ex. - # repo.find_commits( - # ref: 'master', - # max_count: 10, - # skip: 5, - # order: :date - # ) - # - # +options+ is a Hash of optional arguments to git - # :ref is the ref from which to begin (SHA1 or name) - # :contains is the commit contained by the refs from which to begin (SHA1 or name) - # :max_count is the maximum number of commits to fetch - # :skip is the number of commits to skip - # :order is the commits order and allowed value is :none (default), :date, - # :topo, or any combination of them (in an array). Commit ordering types - # are documented here: - # http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant) - # - def find_commits(options = {}) - actual_options = options.dup - - allowed_options = [:ref, :max_count, :skip, :contains, :order] - - actual_options.keep_if do |key| - allowed_options.include?(key) - end - - default_options = { skip: 0 } - actual_options = default_options.merge(actual_options) - - walker = Rugged::Walker.new(rugged) - - if actual_options[:ref] - walker.push(rugged.rev_parse_oid(actual_options[:ref])) - elsif actual_options[:contains] - branches_contains(actual_options[:contains]).each do |branch| - walker.push(branch.target_id) - end - else - rugged.references.each("refs/heads/*") do |ref| - walker.push(ref.target_id) - end - end - - sort_type = rugged_sort_type(actual_options[:order]) - walker.sorting(sort_type) - - commits = [] - offset = actual_options[:skip] - limit = actual_options[:max_count] - walker.each(offset: offset, limit: limit) do |commit| - gitlab_commit = Gitlab::Git::Commit.decorate(commit) - commits.push(gitlab_commit) - end - - walker.reset - - commits - rescue Rugged::OdbError - [] - end - # Returns branch names collection that contains the special commit(SHA1 # or name) # @@ -1228,20 +1164,6 @@ module Gitlab rescue GRPC::BadStatus => e raise CommandError.new(e) end - - # Returns the `Rugged` sorting type constant for one or more given - # sort types. Valid keys are `:none`, `:topo`, and `:date`, or an array - # containing more than one of them. `:date` uses a combination of date and - # topological sorting to closer mimic git's native ordering. - def rugged_sort_type(sort_type) - @rugged_sort_types ||= { - none: Rugged::SORT_NONE, - topo: Rugged::SORT_TOPO, - date: Rugged::SORT_DATE | Rugged::SORT_TOPO - } - - @rugged_sort_types.fetch(sort_type, Rugged::SORT_NONE) - end end end end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 3e44c577643..9e44aefc2db 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -244,6 +244,33 @@ describe Gitlab::Git::Commit, seed_helper: true do end describe '.find_all' do + it 'should return a return a collection of commits' do + commits = described_class.find_all(repository) + + expect(commits).not_to be_empty + expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) + end + + context 'while applying a sort order based on the `order` option' do + it "allows ordering topologically (no parents shown before their children)" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO) + + described_class.find_all(repository, order: :topo) + end + + it "allows ordering by date" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO) + + described_class.find_all(repository, order: :date) + end + + it "applies no sorting by default" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE) + + described_class.find_all(repository) + end + end + context 'max_count' do subject do commits = Gitlab::Git::Commit.find_all( diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 703b0c2c202..4894b558e03 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1101,35 +1101,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe '#find_commits' do - it 'should return a return a collection of commits' do - commits = repository.find_commits - - expect(commits).not_to be_empty - expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) - end - - context 'while applying a sort order based on the `order` option' do - it "allows ordering topologically (no parents shown before their children)" do - expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO) - - repository.find_commits(order: :topo) - end - - it "allows ordering by date" do - expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO) - - repository.find_commits(order: :date) - end - - it "applies no sorting by default" do - expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE) - - repository.find_commits - end - end - end - describe '#branches with deleted branch' do before(:each) do ref = double() -- cgit v1.2.1 From 43c3a65062ed321427634d88f81755daf5611900 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 27 Jun 2017 12:30:15 +0200 Subject: Remove 'contains' option from Commit.find_all --- lib/gitlab/git/commit.rb | 7 +------ spec/lib/gitlab/git/commit_spec.rb | 20 -------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index abd5fac8f78..9c0606d780a 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -117,7 +117,6 @@ module Gitlab # # +options+ is a Hash of optional arguments to git # :ref is the ref from which to begin (SHA1 or name) - # :contains is the commit contained by the refs from which to begin (SHA1 or name) # :max_count is the maximum number of commits to fetch # :skip is the number of commits to skip # :order is the commits order and allowed value is :none (default), :date, @@ -128,7 +127,7 @@ module Gitlab def find_all(repo, options = {}) actual_options = options.dup - allowed_options = [:ref, :max_count, :skip, :contains, :order] + allowed_options = [:ref, :max_count, :skip, :order] actual_options.keep_if do |key| allowed_options.include?(key) @@ -142,10 +141,6 @@ module Gitlab if actual_options[:ref] walker.push(rugged.rev_parse_oid(actual_options[:ref])) - elsif actual_options[:contains] - repo.branches_contains(actual_options[:contains]).each do |branch| - walker.push(branch.target_id) - end else rugged.references.each("refs/heads/*") do |ref| walker.push(ref.target_id) diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 9e44aefc2db..f20a14155dc 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -308,26 +308,6 @@ describe Gitlab::Git::Commit, seed_helper: true do it { is_expected.to include(SeedRepo::FirstCommit::ID) } it { is_expected.not_to include(SeedRepo::LastCommit::ID) } end - - context 'contains feature + max_count' do - subject do - commits = Gitlab::Git::Commit.find_all( - repository, - contains: 'feature', - max_count: 7 - ) - - commits.map { |c| c.id } - end - - it 'has 7 elements' do - expect(subject.size).to eq(7) - end - - it { is_expected.not_to include(SeedRepo::Commit::PARENT_ID) } - it { is_expected.not_to include(SeedRepo::Commit::ID) } - it { is_expected.to include(SeedRepo::BigCommit::ID) } - end end end -- cgit v1.2.1 From 3f167e4894c111360c6d54ac7716d2bf059cb508 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 27 Jun 2017 10:46:01 +0000 Subject: =?UTF-8?q?Action=20Buttons=20on=20Prio=20Labels=20working=20again?= =?UTF-8?q?=20by=20setting=20pointer=20events=20to=20none=20on=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/javascripts/label_manager.js | 6 ++++++ app/assets/stylesheets/pages/labels.scss | 1 + app/views/shared/_label.html.haml | 2 +- app/views/shared/_label_row.html.haml | 4 ++-- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index 38b2eb9ff14..d8814802d9e 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -21,6 +21,7 @@ } bindEvents() { + this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick); return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); } @@ -36,6 +37,11 @@ _this.toggleEmptyState($label, $btn, action); } + onButtonActionClick(e) { + e.stopPropagation(); + $(e.currentTarget).tooltip('hide'); + } + toggleEmptyState($label, $btn, action) { this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index c10588ac58e..b158416b940 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -138,6 +138,7 @@ .fa { font-size: 18px; vertical-align: middle; + pointer-events: none; } &:hover { diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index b8a2c5cb87d..de0281e97c6 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -7,7 +7,7 @@ = render "shared/label_row", label: label .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown - %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } } + %button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } } Options = icon('caret-down') .dropdown-menu.dropdown-menu-align-right diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index d28f9421ecf..7b599dff0e3 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -4,9 +4,9 @@ = icon('bars') .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label), dom_id: dom_id(label), type: label.type } } - %button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' } + %button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' } = icon('star-o') - %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' } + %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', type: 'button', :'data-placement' => 'top' } = icon('star') %span.label-name = link_to_label(label, subject: @project, tooltip: false) -- cgit v1.2.1 From 4447006832d8955f371e2430988e0c95b20f155d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 19 Jun 2017 19:56:27 +0200 Subject: Split pipelines by origin on usage data When sending the usage data, it now includes all pipelines. This commit will split the pipelines in two; internal and external. This will lead to historical data being incorrectly marked this way. Fixes gitlab-org/gitlab-ce#33172 --- app/models/ci/pipeline.rb | 5 +++++ changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml | 4 ++++ lib/gitlab/usage_data.rb | 3 ++- spec/lib/gitlab/usage_data_spec.rb | 3 ++- spec/models/ci/pipeline_spec.rb | 6 ++++++ 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 1b3e5a25ac2..364858964b0 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -140,6 +140,7 @@ module Ci where(id: max_id) end end + scope :internal, -> { where(source: internal_sources) } def self.latest_status(ref = nil) latest(ref).status @@ -163,6 +164,10 @@ module Ci where.not(duration: nil).sum(:duration) end + def self.internal_sources + sources.reject { |source| source == "external" }.values + end + def stages_count statuses.select(:stage).distinct.count end diff --git a/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml new file mode 100644 index 00000000000..0ace7b99657 --- /dev/null +++ b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml @@ -0,0 +1,4 @@ +--- +title: Split pipelines as internal and external in the usage data +merge_request: 12277 +author: diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index bcba2e3e1b6..b23a2934874 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -20,7 +20,8 @@ module Gitlab counts: { boards: Board.count, ci_builds: ::Ci::Build.count, - ci_pipelines: ::Ci::Pipeline.count, + ci_internal_pipelines: ::Ci::Pipeline.internal.count, + ci_external_pipelines: ::Ci::Pipeline.external.count, ci_runners: ::Ci::Runner.count, ci_triggers: ::Ci::Trigger.count, ci_pipeline_schedules: ::Ci::PipelineSchedule.count, diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index b47e1b56fa9..9196d0ccc3f 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -30,7 +30,8 @@ describe Gitlab::UsageData do expect(count_data.keys).to match_array(%i( boards ci_builds - ci_pipelines + ci_internal_pipelines + ci_external_pipelines ci_runners ci_triggers ci_pipeline_schedules diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index dab8e8ca432..55d85a6e228 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -672,6 +672,12 @@ describe Ci::Pipeline, models: true do end end + describe '.internal_sources' do + subject { described_class.internal_sources } + + it { is_expected.to be_an(Array) } + end + describe '#status' do let(:build) do create(:ci_build, :created, pipeline: pipeline, name: 'test') -- cgit v1.2.1 From 7c53fcf11fff8a05442ae813c9da76a9ed710abd Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 27 Jun 2017 13:05:02 +0200 Subject: Adjust for new static-analysis failures --- .../rename_reserved_paths_migration/v1/rename_base.rb | 4 ++-- .../database/rename_reserved_paths_migration/v1_spec.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index c696c8baf7e..33f8939bc61 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -37,8 +37,8 @@ module Gitlab def perform_rename(routable, old_full_path, new_full_path) # skips callbacks & validations new_path = new_full_path.split('/').last - routable.class.where(id: routable). - update_all(path: new_path) + routable.class.where(id: routable) + .update_all(path: new_path) rename_routes(old_full_path, new_full_path) end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb index 9a48bc3a048..7695b95dc57 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb @@ -55,9 +55,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do describe '#revert_renames' do it 'renames namespaces' do rename_namespaces = double - expect(described_class::RenameNamespaces). - to receive(:new).with([], subject). - and_return(rename_namespaces) + expect(described_class::RenameNamespaces) + .to receive(:new).with([], subject) + .and_return(rename_namespaces) expect(rename_namespaces).to receive(:revert_renames) subject.revert_renames @@ -65,9 +65,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do it 'renames projects' do rename_projects = double - expect(described_class::RenameProjects). - to receive(:new).with([], subject). - and_return(rename_projects) + expect(described_class::RenameProjects) + .to receive(:new).with([], subject) + .and_return(rename_projects) expect(rename_projects).to receive(:revert_renames) subject.revert_renames -- cgit v1.2.1 From a2a276a81509b9093087dcb7b6eb1cbed7c41e79 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Jun 2017 03:48:56 -0500 Subject: rename getEmojiCategoryMap and remove unnecessary parameter --- app/assets/javascripts/awards_handler.js | 4 ++-- app/assets/javascripts/emoji/index.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 6f0384ebebe..f0ccbc4b998 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -133,7 +133,7 @@ export default class AwardsHandler { this.isCreatingEmojiMenu = true; // Render the first category - const categoryMap = Emoji.getEmojiByCategory(); + const categoryMap = Emoji.getEmojiCategoryMap(); const categoryNameKey = Object.keys(categoryMap)[0]; const emojisInCategory = categoryMap[categoryNameKey]; const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); @@ -173,7 +173,7 @@ export default class AwardsHandler { } this.isAddingRemainingEmojiMenuCategories = true; - const categoryMap = Emoji.getEmojiByCategory(); + const categoryMap = Emoji.getEmojiCategoryMap(); // Avoid the jank and render the remaining categories separately // This will take more time, but makes UI more responsive diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index bbecd1e41d1..cac35d6eed5 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -20,10 +20,10 @@ export function filterEmojiNamesByAlias(filter) { return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name))); } -let emojiByCategory; -export function getEmojiByCategory(category = null) { - if (!emojiByCategory) { - emojiByCategory = { +let emojiCategoryMap; +export function getEmojiCategoryMap() { + if (!emojiCategoryMap) { + emojiCategoryMap = { activity: [], people: [], nature: [], @@ -35,12 +35,12 @@ export function getEmojiByCategory(category = null) { }; Object.keys(emojiMap).forEach((name) => { const emoji = emojiMap[name]; - if (emojiByCategory[emoji.category]) { - emojiByCategory[emoji.category].push(name); + if (emojiCategoryMap[emoji.category]) { + emojiCategoryMap[emoji.category].push(name); } }); } - return category ? emojiByCategory[category] : emojiByCategory; + return emojiCategoryMap; } export function getEmojiInfo(query) { -- cgit v1.2.1 From 88e12ac94b13bc4ee57a60adca64af5cf7d14030 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 27 Jun 2017 04:24:08 -0500 Subject: revert removal of requestAnimationFrame and move to a separate MR/discussion --- app/assets/javascripts/awards_handler.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index f0ccbc4b998..c34d80f0601 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -6,6 +6,10 @@ import * as Emoji from './emoji'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; +const requestAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.setTimeout; const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence @@ -46,7 +50,9 @@ export default class AwardsHandler { this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => { const $menu = $('.emoji-menu'); if ($menu.length === 0) { - setTimeout(() => this.createEmojiMenu()); + requestAnimationFrame(() => { + this.createEmojiMenu(); + }); } }); this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => { @@ -189,7 +195,7 @@ export default class AwardsHandler { categoryLabelMap[categoryNameKey], emojisInCategory, ); - setTimeout(() => { + requestAnimationFrame(() => { emojiContentElement.insertAdjacentHTML('beforeend', categoryMarkup); resolve(); }); -- cgit v1.2.1 From cf131bf71323ee9812c503adedbcd347097efe48 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 27 Jun 2017 14:20:26 +0200 Subject: Make Gitlab::Ggit::Repository#submodules private --- app/models/repository.rb | 16 ------------ lib/gitlab/git/repository.rb | 47 +++++++++++++++++++--------------- spec/lib/gitlab/git/repository_spec.rb | 8 +++--- 3 files changed, 30 insertions(+), 41 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index c67475357d9..8c24e722a8b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -605,22 +605,6 @@ class Repository end end - # Returns url for submodule - # - # Ex. - # @repository.submodule_url_for('master', 'rack') - # # => git@localhost:rack.git - # - def submodule_url_for(ref, path) - if submodules(ref).any? - submodule = submodules(ref)[path] - - if submodule - submodule['url'] - end - end - end - def last_commit_for_path(sha, path) sha = last_commit_id_for_path(sha, path) commit(sha) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index c1f942f931a..5f9ec09d79a 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -613,32 +613,20 @@ module Gitlab rugged.rev_parse(oid_or_ref_name) end - # Return hash with submodules info for this repository + # Returns url for submodule # # Ex. - # { - # "current_path/rack" => { - # "name" => "original_path/rack", - # "id" => "c67be4624545b4263184c4a0e8f887efd0a66320", - # "url" => "git://github.com/chneukirchen/rack.git" - # }, - # "encoding" => { - # "id" => .... - # } - # } + # @repository.submodule_url_for('master', 'rack') + # # => git@localhost:rack.git # - def submodules(ref) - commit = rev_parse_target(ref) - return {} unless commit + def submodule_url_for(ref, path) + if submodules(ref).any? + submodule = submodules(ref)[path] - begin - content = blob_content(commit, ".gitmodules") - rescue InvalidBlobName - return {} + if submodule + submodule['url'] + end end - - parser = GitmodulesParser.new(content) - fill_submodule_ids(commit, parser.parse) end # Return total commits count accessible from passed ref @@ -976,6 +964,23 @@ module Gitlab private + # We are trying to deprecate this method because it does a lot of work + # but it seems to be used only to look up submodule URL's. + # https://gitlab.com/gitlab-org/gitaly/issues/329 + def submodules(ref) + commit = rev_parse_target(ref) + return {} unless commit + + begin + content = blob_content(commit, ".gitmodules") + rescue InvalidBlobName + return {} + end + + parser = GitmodulesParser.new(content) + fill_submodule_ids(commit, parser.parse) + end + def alternate_object_directories Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).compact end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 703b0c2c202..fafb2cc2350 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -348,7 +348,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } context 'where repo has submodules' do - let(:submodules) { repository.submodules('master') } + let(:submodules) { repository.send(:submodules, 'master') } let(:submodule) { submodules.first } it { expect(submodules).to be_kind_of Hash } @@ -383,12 +383,12 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'should not have an entry for an uncommited submodule dir' do - submodules = repository.submodules('fix-existing-submodule-dir') + submodules = repository.send(:submodules, 'fix-existing-submodule-dir') expect(submodules).not_to have_key('submodule-existing-dir') end it 'should handle tags correctly' do - submodules = repository.submodules('v1.2.1') + submodules = repository.send(:submodules, 'v1.2.1') expect(submodules.first).to eq([ "six", { @@ -414,7 +414,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end context 'where repo doesn\'t have submodules' do - let(:submodules) { repository.submodules('6d39438') } + let(:submodules) { repository.send(:submodules, '6d39438') } it 'should return an empty hash' do expect(submodules).to be_empty end -- cgit v1.2.1 From 84c4d2d25c537f2543c3aa47defcdb3e7350b216 Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Tue, 27 Jun 2017 11:20:32 -0300 Subject: adjust EE landing page to unify CE and EE docs == https://gitlab.com/gitlab-org/gitlab-ee/commit/30304bd2ab3c87d6304a3d4c4 bd71ab7ffd2794b from https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2265 --- doc/README.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/doc/README.md b/doc/README.md index ab8ea192a26..fa755852304 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,12 +1,24 @@ -# GitLab Community Edition +# GitLab Documentation -[GitLab](https://about.gitlab.com/) is a Git-based fully featured platform -for software development. +Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured +platform for software development! -**GitLab Community Edition (CE)** is an opensource product, self-hosted, free to use. -All [GitLab products](https://about.gitlab.com/products/) contain the features -available in GitLab CE. Premium features are available in -[GitLab Enterprise Edition (EE)](https://about.gitlab.com/gitlab-ee/). +We offer four different products for you and your company: + +- **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/), +self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com. +- **GitLab Enterprise Edition (EE)** is an [opencore product](https://gitlab.com/gitlab-org/gitlab-ee/), +self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)** and **GitLab Enterprise Edition Premium (EEP)**. +- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings). + +**GitLab EE** contains all features available in **GitLab CE**, +plus premium features available in each version: **Enterprise Edition Starter** +(**EES**) and **Enterprise Edition Premium** (**EEP**). Everything available in +**EES** is also available in **EEP**. + +**Note:** _We are unifying the documentation for CE and EE. To check if certain feature is +available in CE or EE, look for a note right below the page title containing the GitLab +version which introduced that feature._ ---- @@ -125,7 +137,7 @@ have access to GitLab administration tools and settings. - [Access restrictions](user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab - [Authentication/Authorization](topics/authentication/index.md#gitlab-administrators): Enforce 2FA, configure external authentication with LDAP, SAML, CAS and additional Omniauth providers. -### GitLab admins' superpowers +### Features - [Container Registry](administration/container_registry.md): Configure Docker Registry with GitLab. - [Custom Git hooks](administration/custom_hooks.md): Custom Git hooks (on the filesystem) for when webhooks aren't enough. -- cgit v1.2.1 From abfb59109d5b7ff874af9003980779d1a61a0064 Mon Sep 17 00:00:00 2001 From: winh Date: Tue, 27 Jun 2017 10:47:58 +0200 Subject: Add failing test for #34141 --- .../filtered_search/dropdown_user_spec.js | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index f7708301b6e..0132f4b7c93 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -66,4 +66,38 @@ describe('Dropdown User', () => { window.gon = {}; }); }); + + describe('hideCurrentUser', () => { + const fixtureTemplate = 'issues/issue_list.html.raw'; + preloadFixtures(fixtureTemplate); + + let dropdown; + let authorFilterDropdownElement; + + beforeEach(() => { + loadFixtures(fixtureTemplate); + authorFilterDropdownElement = document.querySelector('#js-dropdown-author'); + const dummyInput = document.createElement('div'); + dropdown = new gl.DropdownUser(null, authorFilterDropdownElement, dummyInput); + }); + + const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user'); + + it('hides the current user from dropdown', () => { + const currentUserElement = findCurrentUserElement(); + expect(currentUserElement).not.toBe(null); + + dropdown.hideCurrentUser(); + + expect(currentUserElement.classList).toContain('hidden'); + }); + + it('does nothing if no user is logged in', () => { + const currentUserElement = findCurrentUserElement(); + currentUserElement.parentNode.removeChild(currentUserElement); + expect(findCurrentUserElement()).toBe(null); + + dropdown.hideCurrentUser(); + }); + }); }); -- cgit v1.2.1 From a61eda78a7f7f7e52c4e2b9e3221cc1da37ccf9f Mon Sep 17 00:00:00 2001 From: winh Date: Tue, 27 Jun 2017 11:11:01 +0200 Subject: Add condition if there is a current user to DropdownUser --- app/assets/javascripts/filtered_search/dropdown_user.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js index 65c1b2050ac..1af791dba22 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_user.js @@ -32,8 +32,11 @@ class DropdownUser extends gl.FilteredSearchDropdown { } hideCurrentUser() { - const currenUserItem = this.dropdown.querySelector('.js-current-user'); - currenUserItem.classList.add('hidden'); + const currentUserItem = this.dropdown.querySelector('.js-current-user'); + + if (currentUserItem) { + currentUserItem.classList.add('hidden'); + } } itemClicked(e) { -- cgit v1.2.1 From 58454d9b4222f0be5211e6672f656f5c4883b547 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 27 Jun 2017 14:58:43 +0000 Subject: Update changelog item [ci skip] --- changelogs/unreleased/dm-commit-row-browse-button.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/dm-commit-row-browse-button.yml b/changelogs/unreleased/dm-commit-row-browse-button.yml index 4689dc3b0e2..4240a7de5de 100644 --- a/changelogs/unreleased/dm-commit-row-browse-button.yml +++ b/changelogs/unreleased/dm-commit-row-browse-button.yml @@ -1,4 +1,4 @@ --- -title: Adjust commit partial cache key to fix browse buttons +title: Fix inconsistent display of the "Browse files" button in the commit list merge_request: author: -- cgit v1.2.1 From e2ea3e5c84b32af405799b1f7158dfdc590db915 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 27 Jun 2017 15:41:56 +0000 Subject: Resolve "Unable to access edit comment from dropdown menu in certain screen sizes" --- app/assets/stylesheets/pages/notes.scss | 2 +- spec/features/snippets/notes_on_personal_snippets_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index f0dbe4249c5..53d5cf2f7bc 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -473,7 +473,7 @@ ul.notes { } .more-actions { - display: inline; + display: inline-block; .tooltip { white-space: nowrap; diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index d310e7501ec..c7e2e3d8a34 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -33,6 +33,7 @@ describe 'Comments on personal snippets', :js, feature: true do expect(page).to have_selector('.note-emoji-button') end + find('body').click # close dropdown open_more_actions_dropdown(snippet_notes[1]) page.within("#notes-list li#note_#{snippet_notes[1].id}") do -- cgit v1.2.1 From b0f2861c3dd662a82ecbf535d1c259ad81df378b Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 27 Jun 2017 17:41:58 +0200 Subject: Make the SimpleExecutor rescue exceptions in the executing Checks --- lib/system_check/simple_executor.rb | 2 ++ spec/lib/system_check/simple_executor_spec.rb | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb index dc2d4643a01..e5986612908 100644 --- a/lib/system_check/simple_executor.rb +++ b/lib/system_check/simple_executor.rb @@ -75,6 +75,8 @@ module SystemCheck check.show_error end + rescue StandardError => e + $stdout.puts "Exception: #{e.message}".color(:red) end private diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb index a5c6170cd7d..5de768d99ed 100644 --- a/spec/lib/system_check/simple_executor_spec.rb +++ b/spec/lib/system_check/simple_executor_spec.rb @@ -75,6 +75,15 @@ describe SystemCheck::SimpleExecutor, lib: true do end end + class BugousCheck < SystemCheck::BaseCheck + CustomError = Class.new(StandardError) + set_name 'my bugous check' + + def check? + raise CustomError, 'omg' + end + end + describe '#component' do it 'returns stored component name' do expect(subject.component).to eq('Test') @@ -138,14 +147,14 @@ describe SystemCheck::SimpleExecutor, lib: true do context 'when check pass' do it 'prints yes' do expect_any_instance_of(SimpleCheck).to receive(:check?).and_call_original - expect { subject.run_check(SimpleCheck) }.to output(/ \.\.\. yes/).to_stdout + expect { subject.run_check(SimpleCheck) }.to output(/ \.\.\. \e\[32myes/).to_stdout end end context 'when check fails' do it 'prints no' do expect_any_instance_of(OtherCheck).to receive(:check?).and_call_original - expect { subject.run_check(OtherCheck) }.to output(/ \.\.\. no/).to_stdout + expect { subject.run_check(OtherCheck) }.to output(/ \.\.\. \e\[31mno/).to_stdout end it 'displays error message from #show_error' do @@ -219,5 +228,11 @@ describe SystemCheck::SimpleExecutor, lib: true do end end end + + context 'when there is an exception' do + it 'rescues the exception' do + expect{ subject.run_check(BugousCheck) }.not_to raise_exception + end + end end end -- cgit v1.2.1 From 64b0a799f3a3d6099734f83803519624bc0be7c1 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 27 Jun 2017 16:06:38 +0000 Subject: Resolve "Submitting reply to existing diff discussion using Cmd/Ctrl+Enter submits twice and refreshes page" --- app/assets/javascripts/behaviors/quick_submit.js | 2 +- spec/javascripts/behaviors/quick_submit_spec.js | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 1f9e0448084..bc693616460 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -40,7 +40,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => { e.preventDefault(); const $form = $(e.target).closest('form'); - const $submitButton = $form.find('input[type=submit], button[type=submit]'); + const $submitButton = $form.find('input[type=submit], button[type=submit]').first(); if (!$submitButton.attr('disabled')) { $submitButton.trigger('click', [e]); diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index f56b99f8a16..6dc48f9a293 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -40,16 +40,29 @@ import '~/behaviors/quick_submit'; it('disables input of type submit', function() { const submitButton = $('.js-quick-submit input[type=submit]'); this.textarea.trigger(keydownEvent()); + expect(submitButton).toBeDisabled(); }); it('disables button of type submit', function() { - // button doesn't exist in fixture, add it manually - const submitButton = $(''); - submitButton.insertAfter(this.textarea); - + const submitButton = $('.js-quick-submit input[type=submit]'); this.textarea.trigger(keydownEvent()); + expect(submitButton).toBeDisabled(); }); + it('only clicks one submit', function() { + const existingSubmit = $('.js-quick-submit input[type=submit]'); + // Add an extra submit button + const newSubmit = $(''); + newSubmit.insertAfter(this.textarea); + + const oldClick = spyOnEvent(existingSubmit, 'click'); + const newClick = spyOnEvent(newSubmit, 'click'); + + this.textarea.trigger(keydownEvent()); + + expect(oldClick).not.toHaveBeenTriggered(); + expect(newClick).toHaveBeenTriggered(); + }); // We cannot stub `navigator.userAgent` for CI's `rake karma` task, so we'll // only run the tests that apply to the current platform if (navigator.userAgent.match(/Macintosh/)) { -- cgit v1.2.1 From 5545d4dc8761d393f39acfd8c22966f2fe394d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 27 Jun 2017 18:34:34 +0200 Subject: Move share examples to their correct location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../access_control_ce_shared_examples.rb | 91 ---------------------- .../protected_branches_access_control_ce.rb | 91 ++++++++++++++++++++++ 2 files changed, 91 insertions(+), 91 deletions(-) delete mode 100644 spec/support/protected_branches/access_control_ce_shared_examples.rb create mode 100644 spec/support/shared_examples/features/protected_branches_access_control_ce.rb diff --git a/spec/support/protected_branches/access_control_ce_shared_examples.rb b/spec/support/protected_branches/access_control_ce_shared_examples.rb deleted file mode 100644 index 287d6bb13c3..00000000000 --- a/spec/support/protected_branches/access_control_ce_shared_examples.rb +++ /dev/null @@ -1,91 +0,0 @@ -RSpec.shared_examples "protected branches > access control > CE" do - ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| - it "allows creating protected branches that #{access_type_name} can push to" do - visit namespace_project_protected_branches_path(project.namespace, project) - - set_protected_branch_name('master') - - within('.new_protected_branch') do - allowed_to_push_button = find(".js-allowed-to-push") - - unless allowed_to_push_button.text == access_type_name - allowed_to_push_button.trigger('click') - within(".dropdown.open .dropdown-menu") { click_on access_type_name } - end - end - - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id]) - end - - it "allows updating protected branches so that #{access_type_name} can push to them" do - visit namespace_project_protected_branches_path(project.namespace, project) - - set_protected_branch_name('master') - - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - - within(".protected-branches-list") do - find(".js-allowed-to-push").click - - within('.js-allowed-to-push-container') do - expect(first("li")).to have_content("Roles") - click_on access_type_name - end - end - - wait_for_requests - - expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id) - end - end - - ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| - it "allows creating protected branches that #{access_type_name} can merge to" do - visit namespace_project_protected_branches_path(project.namespace, project) - - set_protected_branch_name('master') - - within('.new_protected_branch') do - allowed_to_merge_button = find(".js-allowed-to-merge") - - unless allowed_to_merge_button.text == access_type_name - allowed_to_merge_button.click - within(".dropdown.open .dropdown-menu") { click_on access_type_name } - end - end - - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id]) - end - - it "allows updating protected branches so that #{access_type_name} can merge to them" do - visit namespace_project_protected_branches_path(project.namespace, project) - - set_protected_branch_name('master') - - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - - within(".protected-branches-list") do - find(".js-allowed-to-merge").click - - within('.js-allowed-to-merge-container') do - expect(first("li")).to have_content("Roles") - click_on access_type_name - end - end - - wait_for_requests - - expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id) - end - end -end diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb new file mode 100644 index 00000000000..b6341127a76 --- /dev/null +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -0,0 +1,91 @@ +shared_examples "protected branches > access control > CE" do + ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + it "allows creating protected branches that #{access_type_name} can push to" do + visit namespace_project_protected_branches_path(project.namespace, project) + + set_protected_branch_name('master') + + within('.new_protected_branch') do + allowed_to_push_button = find(".js-allowed-to-push") + + unless allowed_to_push_button.text == access_type_name + allowed_to_push_button.trigger('click') + within(".dropdown.open .dropdown-menu") { click_on access_type_name } + end + end + + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id]) + end + + it "allows updating protected branches so that #{access_type_name} can push to them" do + visit namespace_project_protected_branches_path(project.namespace, project) + + set_protected_branch_name('master') + + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + + within(".protected-branches-list") do + find(".js-allowed-to-push").click + + within('.js-allowed-to-push-container') do + expect(first("li")).to have_content("Roles") + click_on access_type_name + end + end + + wait_for_requests + + expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id) + end + end + + ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + it "allows creating protected branches that #{access_type_name} can merge to" do + visit namespace_project_protected_branches_path(project.namespace, project) + + set_protected_branch_name('master') + + within('.new_protected_branch') do + allowed_to_merge_button = find(".js-allowed-to-merge") + + unless allowed_to_merge_button.text == access_type_name + allowed_to_merge_button.click + within(".dropdown.open .dropdown-menu") { click_on access_type_name } + end + end + + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id]) + end + + it "allows updating protected branches so that #{access_type_name} can merge to them" do + visit namespace_project_protected_branches_path(project.namespace, project) + + set_protected_branch_name('master') + + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + + within(".protected-branches-list") do + find(".js-allowed-to-merge").click + + within('.js-allowed-to-merge-container') do + expect(first("li")).to have_content("Roles") + click_on access_type_name + end + end + + wait_for_requests + + expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id) + end + end +end -- cgit v1.2.1 From 2da314212f0c120d20d4eae724a524a9505de19c Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Jun 2017 16:52:13 +0000 Subject: Slightly refactor pipeline schedules form in preparation for additions --- .../components/interval_pattern_input.js | 148 --------------------- .../components/interval_pattern_input.vue | 144 ++++++++++++++++++++ .../pipeline_schedule_form_bundle.js | 35 ++++- .../interval_pattern_input_spec.js | 5 +- 4 files changed, 176 insertions(+), 156 deletions(-) delete mode 100644 app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js create mode 100644 app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js deleted file mode 100644 index 901adbe9fce..00000000000 --- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js +++ /dev/null @@ -1,148 +0,0 @@ -import Vue from 'vue'; -import Translate from '../../vue_shared/translate'; - -Vue.use(Translate); - -const inputNameAttribute = 'schedule[cron]'; - -export default { - props: { - initialCronInterval: { - type: String, - required: false, - default: '', - }, - }, - data() { - return { - inputNameAttribute, - cronInterval: this.initialCronInterval, - cronIntervalPresets: { - everyDay: '0 4 * * *', - everyWeek: '0 4 * * 0', - everyMonth: '0 4 1 * *', - }, - cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron', - customInputEnabled: false, - }; - }, - computed: { - intervalIsPreset() { - return _.contains(this.cronIntervalPresets, this.cronInterval); - }, - // The text input is editable when there's a custom interval, or when it's - // a preset interval and the user clicks the 'custom' radio button - isEditable() { - return !!(this.customInputEnabled || !this.intervalIsPreset); - }, - }, - methods: { - toggleCustomInput(shouldEnable) { - this.customInputEnabled = shouldEnable; - - if (shouldEnable) { - // We need to change the value so other radios don't remain selected - // because the model (cronInterval) hasn't changed. The server trims it. - this.cronInterval = `${this.cronInterval} `; - } - }, - }, - created() { - if (this.intervalIsPreset) { - this.enableCustomInput = false; - } - }, - watch: { - cronInterval() { - // updates field validation state when model changes, as - // glFieldError only updates on input. - Vue.nextTick(() => { - gl.pipelineScheduleFieldErrors.updateFormValidityState(); - }); - }, - }, - template: ` -
    -
    - - - - - - ({{ __('Cron syntax') }}) - -
    - -
    - - - -
    - -
    - - - -
    - -
    - - - -
    - -
    - -
    -
    - `, -}; diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue new file mode 100644 index 00000000000..ce46b3fa3fa --- /dev/null +++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.vue @@ -0,0 +1,144 @@ + + + diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js index c60e77decce..b424e7f205d 100644 --- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js +++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js @@ -1,20 +1,41 @@ import Vue from 'vue'; -import IntervalPatternInput from './components/interval_pattern_input'; +import Translate from '../vue_shared/translate'; +import intervalPatternInput from './components/interval_pattern_input.vue'; import TimezoneDropdown from './components/timezone_dropdown'; import TargetBranchDropdown from './components/target_branch_dropdown'; -document.addEventListener('DOMContentLoaded', () => { - const IntervalPatternInputComponent = Vue.extend(IntervalPatternInput); +Vue.use(Translate); + +function initIntervalPatternInput() { const intervalPatternMount = document.getElementById('interval-pattern-input'); const initialCronInterval = intervalPatternMount ? intervalPatternMount.dataset.initialInterval : ''; - new IntervalPatternInputComponent({ - propsData: { - initialCronInterval, + return new Vue({ + el: intervalPatternMount, + components: { + intervalPatternInput, }, - }).$mount(intervalPatternMount); + render(createElement) { + return createElement('interval-pattern-input', { + props: { + initialCronInterval, + }, + }); + }, + }); +} + +document.addEventListener('DOMContentLoaded', () => { + /* Most of the form is written in haml, but for fields with more complex behaviors, + * you should mount individual Vue components here. If at some point components need + * to share state, it may make sense to refactor the whole form to Vue */ + + initIntervalPatternInput(); + + // Initialize non-Vue JS components in the form const formElement = document.getElementById('new-pipeline-schedule-form'); + gl.timezoneDropdown = new TimezoneDropdown(); gl.targetBranchDropdown = new TargetBranchDropdown(); gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement); diff --git a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js index 56c57d94798..040d14efed2 100644 --- a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js +++ b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js @@ -1,5 +1,8 @@ import Vue from 'vue'; -import IntervalPatternInput from '~/pipeline_schedules/components/interval_pattern_input'; +import Translate from '~/vue_shared/translate'; +import IntervalPatternInput from '~/pipeline_schedules/components/interval_pattern_input.vue'; + +Vue.use(Translate); const IntervalPatternInputComponent = Vue.extend(IntervalPatternInput); const inputNameAttribute = 'schedule[cron]'; -- cgit v1.2.1 From 263c662ff5fd2a471da0c23f6828a0469d0e7e1f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 26 Jun 2017 15:35:25 +0000 Subject: Merge branch 'update-guides-for-9-3' into '9-3-stable' Update guide for 9.3 See merge request !12038 --- doc/install/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 84af6432889..992ff162efb 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -294,9 +294,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-2-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-3-stable gitlab -**Note:** You can change `9-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `9-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It -- cgit v1.2.1 From 236b921bd6d006b007b19437a76cb5d7f034b6b0 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 27 Jun 2017 12:38:07 -0500 Subject: Backport inline loading_icon updates from EE See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2246#note_33577729 --- .../javascripts/vue_shared/components/loading_icon.vue | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue index 41b1d0165b0..15581d5c2a0 100644 --- a/app/assets/javascripts/vue_shared/components/loading_icon.vue +++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue @@ -12,9 +12,18 @@ required: false, default: '1', }, + + inline: { + type: Boolean, + required: false, + default: false, + }, }, computed: { + rootElementType() { + return this.inline ? 'span' : 'div'; + }, cssClass() { return `fa-${this.size}x`; }, @@ -22,12 +31,14 @@ }; -- cgit v1.2.1 From 758e020019860c611237e23a8898fa59c9bca5eb Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 27 Jun 2017 20:18:23 +0200 Subject: Disable rainbow during SimpleExecutor specs to have consistence --- spec/lib/system_check/simple_executor_spec.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb index 5de768d99ed..795f11ee1f8 100644 --- a/spec/lib/system_check/simple_executor_spec.rb +++ b/spec/lib/system_check/simple_executor_spec.rb @@ -84,6 +84,15 @@ describe SystemCheck::SimpleExecutor, lib: true do end end + before do + @rainbow = Rainbow.enabled + Rainbow.enabled = false + end + + after do + Rainbow.enabled = @rainbow + end + describe '#component' do it 'returns stored component name' do expect(subject.component).to eq('Test') @@ -147,14 +156,14 @@ describe SystemCheck::SimpleExecutor, lib: true do context 'when check pass' do it 'prints yes' do expect_any_instance_of(SimpleCheck).to receive(:check?).and_call_original - expect { subject.run_check(SimpleCheck) }.to output(/ \.\.\. \e\[32myes/).to_stdout + expect { subject.run_check(SimpleCheck) }.to output(/ \.\.\. yes/).to_stdout end end context 'when check fails' do it 'prints no' do expect_any_instance_of(OtherCheck).to receive(:check?).and_call_original - expect { subject.run_check(OtherCheck) }.to output(/ \.\.\. \e\[31mno/).to_stdout + expect { subject.run_check(OtherCheck) }.to output(/ \.\.\. no/).to_stdout end it 'displays error message from #show_error' do -- cgit v1.2.1 From 9c2a51a6cda78b055f7e9308f7c045eac781ff4f Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Tue, 27 Jun 2017 20:21:58 +0200 Subject: Update mmap2 gem tha disables mmap_obj.gsub! as current implementation uses method that is no longer part of Ruby API --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7c9dd051211..07b7db722e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -461,7 +461,7 @@ GEM mimemagic (0.3.0) mini_portile2 (2.1.0) minitest (5.7.0) - mmap2 (2.2.6) + mmap2 (2.2.7) mousetrap-rails (1.4.6) multi_json (1.12.1) multi_xml (0.6.0) -- cgit v1.2.1 From 6ab12777bc9aa79f37859318bce07d6b5a67acd6 Mon Sep 17 00:00:00 2001 From: winh Date: Tue, 27 Jun 2017 20:37:30 +0200 Subject: Introduce addClassIfElementExists utility --- .../javascripts/filtered_search/dropdown_user.js | 7 ++--- .../filtered_search/filtered_search_manager.js | 7 ++--- app/assets/javascripts/lib/utils/dom_utils.js | 7 +++++ spec/javascripts/lib/utils/dom_utils_spec.js | 35 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/lib/utils/dom_utils.js create mode 100644 spec/javascripts/lib/utils/dom_utils_spec.js diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js index 1af791dba22..19fed771197 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_user.js @@ -2,6 +2,7 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter'; import './filtered_search_dropdown'; +import { addClassIfElementExists } from '../lib/utils/dom_utils'; class DropdownUser extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input, tokenKeys, filter) { @@ -32,11 +33,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { } hideCurrentUser() { - const currentUserItem = this.dropdown.querySelector('.js-current-user'); - - if (currentUserItem) { - currentUserItem.classList.add('hidden'); - } + addClassIfElementExists(this.dropdown.querySelector('.js-current-user'), 'hidden'); } itemClicked(e) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 1425769d2de..7872e9e68ad 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -3,6 +3,7 @@ import RecentSearchesRoot from './recent_searches_root'; import RecentSearchesStore from './stores/recent_searches_store'; import RecentSearchesService from './services/recent_searches_service'; import eventHub from './event_hub'; +import { addClassIfElementExists } from '../lib/utils/dom_utils'; class FilteredSearchManager { constructor(page) { @@ -227,11 +228,7 @@ class FilteredSearchManager { } addInputContainerFocus() { - const inputContainer = this.filteredSearchInput.closest('.filtered-search-box'); - - if (inputContainer) { - inputContainer.classList.add('focus'); - } + addClassIfElementExists(this.filteredSearchInput.closest('.filtered-search-box'), 'focus'); } removeInputContainerFocus(e) { diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js new file mode 100644 index 00000000000..de65ea15a60 --- /dev/null +++ b/app/assets/javascripts/lib/utils/dom_utils.js @@ -0,0 +1,7 @@ +/* eslint-disable import/prefer-default-export */ + +export const addClassIfElementExists = (element, className) => { + if (element) { + element.classList.add(className); + } +}; diff --git a/spec/javascripts/lib/utils/dom_utils_spec.js b/spec/javascripts/lib/utils/dom_utils_spec.js new file mode 100644 index 00000000000..867bf5912d1 --- /dev/null +++ b/spec/javascripts/lib/utils/dom_utils_spec.js @@ -0,0 +1,35 @@ +import { addClassIfElementExists } from '~/lib/utils/dom_utils'; + +describe('DOM Utils', () => { + describe('addClassIfElementExists', () => { + const className = 'biology'; + const fixture = ` +
    +
    +
    + `; + + let parentElement; + + beforeEach(() => { + setFixtures(fixture); + parentElement = document.querySelector('.parent'); + }); + + it('adds class if element exists', () => { + const childElement = parentElement.querySelector('.child'); + expect(childElement).not.toBe(null); + + addClassIfElementExists(childElement, className); + + expect(childElement.classList).toContain(className); + }); + + it('does not throw if element does not exist', () => { + const childElement = parentElement.querySelector('.other-child'); + expect(childElement).toBe(null); + + addClassIfElementExists(childElement, className); + }); + }); +}); -- cgit v1.2.1 From bea0d4e48a3eff42efc06558746631102916bb08 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 22 Jun 2017 11:30:09 -0500 Subject: Ignore JSON files generated from PO files --- .gitignore | 1 + app/assets/javascripts/locale/bg/app.js | 1 - app/assets/javascripts/locale/de/app.js | 1 - app/assets/javascripts/locale/en/app.js | 1 - app/assets/javascripts/locale/es/app.js | 1 - app/assets/javascripts/locale/fr/app.js | 1 - app/assets/javascripts/locale/pt_BR/app.js | 1 - app/assets/javascripts/locale/zh_CN/app.js | 1 - app/assets/javascripts/locale/zh_HK/app.js | 1 - app/assets/javascripts/locale/zh_TW/app.js | 1 - 10 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 app/assets/javascripts/locale/bg/app.js delete mode 100644 app/assets/javascripts/locale/de/app.js delete mode 100644 app/assets/javascripts/locale/en/app.js delete mode 100644 app/assets/javascripts/locale/es/app.js delete mode 100644 app/assets/javascripts/locale/fr/app.js delete mode 100644 app/assets/javascripts/locale/pt_BR/app.js delete mode 100644 app/assets/javascripts/locale/zh_CN/app.js delete mode 100644 app/assets/javascripts/locale/zh_HK/app.js delete mode 100644 app/assets/javascripts/locale/zh_TW/app.js diff --git a/.gitignore b/.gitignore index 89da29fd790..e529e33530a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ eslint-report.html /.yarn-cache /.byebug_history /Vagrantfile +/app/assets/javascripts/locale/**/app.js /backups/* /config/aws.yml /config/database.yml diff --git a/app/assets/javascripts/locale/bg/app.js b/app/assets/javascripts/locale/bg/app.js deleted file mode 100644 index 24888e33b2e..00000000000 --- a/app/assets/javascripts/locale/bg/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['bg'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-12 19:29-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-13 04:23-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Bulgarian (https://translate.zanata.org/project/view/GitLab)","Language":"bg","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"bg","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} подаде %{commit_timeago}"],"About auto deploy":["Относно автоматичното внедряване"],"Active":["Активно"],"Activity":["Дейност"],"Add Changelog":["Добавяне на списък с промени"],"Add Contribution guide":["Добавяне на ръководство за сътрудничество"],"Add License":["Добавяне на лиценз"],"Add an SSH key to your profile to pull or push via SSH.":["Добавете SSH ключ в профила си, за да можете да изтегляте или изпращате промени чрез SSH."],"Add new directory":["Добавяне на нова папка"],"Archived project! Repository is read-only":["Архивиран проект! Хранилището е само за четене"],"Are you sure you want to delete this pipeline schedule?":["Наистина ли искате да изтриете този план за схема?"],"Attach a file by drag & drop or %{upload_link}":["Прикачете файл чрез влачене и пускане или %{upload_link}"],"Branch":["Клон","Клонове"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"],"Branches":["Клонове"],"Browse files":["Разглеждане на файловете"],"ByAuthor|by":["от"],"CI configuration":["Конфигурация на непрекъсната интеграция"],"Cancel":["Отказ"],"ChangeTypeActionLabel|Pick into branch":["Избиране в клона"],"ChangeTypeActionLabel|Revert in branch":["Отмяна в клона"],"ChangeTypeAction|Cherry-pick":["Подбиране"],"ChangeType|commit":["подаване"],"ChangeType|merge request":["заявка за сливане"],"Changelog":["Списък с промени"],"Charts":["Графики"],"Cherry-pick this commit":["Подбиране на това подаване"],"Cherry-pick this merge-request":["Подбиране на тази заявка за сливане"],"CiStatusLabel|canceled":["отказано"],"CiStatusLabel|created":["създадено"],"CiStatusLabel|failed":["неуспешно"],"CiStatusLabel|manual action":["ръчно действие"],"CiStatusLabel|passed":["успешно"],"CiStatusLabel|passed with warnings":["успешно, с предупреждения"],"CiStatusLabel|pending":["на изчакване"],"CiStatusLabel|skipped":["пропуснато"],"CiStatusLabel|waiting for manual action":["чакане за ръчно действие"],"CiStatusText|blocked":["блокирано"],"CiStatusText|canceled":["отказано"],"CiStatusText|created":["създадено"],"CiStatusText|failed":["неуспешно"],"CiStatusText|manual":["ръчно"],"CiStatusText|passed":["успешно"],"CiStatusText|pending":["на изчакване"],"CiStatusText|skipped":["пропуснато"],"CiStatus|running":["протича в момента"],"Commit":["Подаване","Подавания"],"Commit message":["Съобщение за подаването"],"CommitMessage|Add %{file_name}":["Добавяне на „%{file_name}“"],"Commits":["Подавания"],"Commits|History":["История"],"Committed by":["Подадено от"],"Compare":["Сравнение"],"Contribution guide":["Ръководство за сътрудничество"],"Contributors":["Сътрудници"],"Copy URL to clipboard":["Копиране на адреса в буфера за обмен"],"Copy commit SHA to clipboard":["Копиране на идентификатора на подаването в буфера за обмен"],"Create New Directory":["Създаване на нова папка"],"Create directory":["Създаване на папка"],"Create empty bare repository":["Създаване на празно хранилище"],"Create merge request":["Създаване на заявка за сливане"],"Create new...":["Създаване на нов…"],"CreateNewFork|Fork":["Разклоняване"],"CreateTag|Tag":["Етикет"],"Cron Timezone":["Часова зона за „Cron“"],"Cron syntax":["Синтаксис на „Cron“"],"Custom":["Персонализиран"],"Custom notification events":["Персонализирани събития за известяване"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Персонализираните нива на известяване са същите като нивата за участие. С персонализираните нива на известяване ще можете да получавате и известия за избрани събития. За да научите повече, прегледайте %{notification_link}."],"Cycle Analytics":["Анализ на циклите"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Анализът на циклите дава общ поглед върху това колко време е нужно на една идея да се превърне в завършена функционалност в проекта."],"CycleAnalyticsStage|Code":["Програмиране"],"CycleAnalyticsStage|Issue":["Проблем"],"CycleAnalyticsStage|Plan":["Планиране"],"CycleAnalyticsStage|Production":["Издаване"],"CycleAnalyticsStage|Review":["Преглед и одобрение"],"CycleAnalyticsStage|Staging":["Подготовка за издаване"],"CycleAnalyticsStage|Test":["Тестване"],"Define a custom pattern with cron syntax":["Задайте потребителски шаблон, използвайки синтаксиса на „Cron“"],"Delete":["Изтриване"],"Deploy":["Внедряване","Внедрявания"],"Description":["Описание"],"Directory name":["Име на папката"],"Don't show again":["Да не се показва повече"],"Download":["Сваляне"],"Download tar":["Сваляне във формат „tar“"],"Download tar.bz2":["Сваляне във формат „tar.bz2“"],"Download tar.gz":["Сваляне във формат „tar.gz“"],"Download zip":["Сваляне във формат „zip“"],"DownloadArtifacts|Download":["Сваляне"],"DownloadCommit|Email Patches":["Изпращане на кръпките по е-поща"],"DownloadCommit|Plain Diff":["Обикновен файл с разлики"],"DownloadSource|Download":["Сваляне"],"Edit":["Редактиране"],"Edit Pipeline Schedule %{id}":["Редактиране на плана %{id} за схема"],"Every day (at 4:00am)":["Всеки ден (в 4 ч. сутринта)"],"Every month (on the 1st at 4:00am)":["Всеки месец (на 1-во число, в 4 ч. сутринта)"],"Every week (Sundays at 4:00am)":["Всяка седмица (в неделя, в 4 ч. сутринта)"],"Failed to change the owner":["Собственикът не може да бъде променен"],"Failed to remove the pipeline schedule":["Планът за схема не може да бъде премахнат"],"Files":["Файлове"],"Find by path":["Търсене по път"],"Find file":["Търсене на файл"],"FirstPushedBy|First":["Първо"],"FirstPushedBy|pushed by":["изпращане на промени от"],"Fork":["Разклонение","Разклонения"],"ForkedFromProjectPath|Forked from":["Разклонение на"],"From issue creation until deploy to production":["От създаването на проблема до внедряването в крайната версия"],"From merge request merge until deploy to production":["От прилагането на заявката за сливане до внедряването в крайната версия"],"Go to your fork":["Към Вашето разклонение"],"GoToYourFork|Fork":["Разклонение"],"Home":["Начало"],"Housekeeping successfully started":["Освежаването започна успешно"],"Import repository":["Внасяне на хранилище"],"Interval Pattern":["Шаблон за интервала"],"Introducing Cycle Analytics":["Представяме Ви анализа на циклите"],"LFSStatus|Disabled":["Изключено"],"LFSStatus|Enabled":["Включено"],"Last %d day":["Последния %d ден","Последните %d дни"],"Last Pipeline":["Последна схема"],"Last Update":["Последна промяна"],"Last commit":["Последно подаване"],"Learn more in the":["Научете повече в"],"Leave group":["Напускане на групата"],"Leave project":["Напускане на проекта"],"Limited to showing %d event at most":["Ограничено до показване на най-много %d събитие","Ограничено до показване на най-много %d събития"],"Median":["Медиана"],"MissingSSHKeyWarningLink|add an SSH key":["добавите SSH ключ"],"New Issue":["Нов проблем","Нови проблема"],"New Pipeline Schedule":["Нов план за схема"],"New branch":["Нов клон"],"New directory":["Нова папка"],"New file":["Нов файл"],"New issue":["Нов проблем"],"New merge request":["Нова заявка за сливане"],"New schedule":["Нов план"],"New snippet":["Нов отрязък"],"New tag":["Нов етикет"],"No repository":["Няма хранилище"],"No schedules":["Няма планове"],"Not available":["Не е налично"],"Not enough data":["Няма достатъчно данни"],"Notification events":["Събития за известяване"],"NotificationEvent|Close issue":["Затваряне на проблем"],"NotificationEvent|Close merge request":["Затваряне на заявка за сливане"],"NotificationEvent|Failed pipeline":["Неуспешно изпълнение на схема"],"NotificationEvent|Merge merge request":["Прилагане на заявка за сливане"],"NotificationEvent|New issue":["Нов проблем"],"NotificationEvent|New merge request":["Нова заявка за сливане"],"NotificationEvent|New note":["Нова бележка"],"NotificationEvent|Reassign issue":["Преназначаване на проблем"],"NotificationEvent|Reassign merge request":["Преназначаване на заявка за сливане"],"NotificationEvent|Reopen issue":["Повторно отваряне на проблем"],"NotificationEvent|Successful pipeline":["Успешно изпълнение на схема"],"NotificationLevel|Custom":["Персонализирани"],"NotificationLevel|Disabled":["Изключени"],"NotificationLevel|Global":["Глобални"],"NotificationLevel|On mention":["При споменаване"],"NotificationLevel|Participate":["Участие"],"NotificationLevel|Watch":["Наблюдение"],"OfSearchInADropdown|Filter":["Филтър"],"OpenedNDaysAgo|Opened":["Отворен"],"Options":["Опции"],"Owner":["Собственик"],"Pipeline":["Схема"],"Pipeline Health":["Състояние"],"Pipeline Schedule":["План за схема"],"Pipeline Schedules":["Планове за схема"],"PipelineSchedules|Activated":["Включено"],"PipelineSchedules|Active":["Активно"],"PipelineSchedules|All":["Всички"],"PipelineSchedules|Inactive":["Неактивно"],"PipelineSchedules|Next Run":["Следващо изпълнение"],"PipelineSchedules|None":["Нищо"],"PipelineSchedules|Provide a short description for this pipeline":["Въведете кратко описание за тази схема"],"PipelineSchedules|Take ownership":["Поемане на собствеността"],"PipelineSchedules|Target":["Цел"],"Project '%{project_name}' queued for deletion.":["Проектът „%{project_name}“ е добавен в опашката за изтриване."],"Project '%{project_name}' was successfully created.":["Проектът „%{project_name}“ беше създаден успешно."],"Project '%{project_name}' was successfully updated.":["Проектът „%{project_name}“ беше обновен успешно."],"Project '%{project_name}' will be deleted.":["Проектът „%{project_name}“ ще бъде изтрит."],"Project access must be granted explicitly to each user.":["Достъпът до проекта трябва да бъде даван поотделно на всеки потребител."],"Project export could not be deleted.":["Изнесените данни на проекта не могат да бъдат изтрити."],"Project export has been deleted.":["Изнесените данни на проекта бяха изтрити."],"Project export link has expired. Please generate a new export from your project settings.":["Връзката към изнесените данни на проекта изгуби давност. Моля, създайте нова от настройките на проекта."],"Project export started. A download link will be sent by email.":["Изнасянето на проекта започна. Ще получите връзка към данните по е-поща."],"Project home":["Начална страница на проекта"],"ProjectFeature|Disabled":["Изключено"],"ProjectFeature|Everyone with access":["Всеки с достъп"],"ProjectFeature|Only team members":["Само членовете на екипа"],"ProjectFileTree|Name":["Име"],"ProjectLastActivity|Never":["Никога"],"ProjectLifecycle|Stage":["Етап"],"ProjectNetworkGraph|Graph":["Графика"],"Read more":["Прочетете повече"],"Readme":["ПрочетиМе"],"RefSwitcher|Branches":["Клонове"],"RefSwitcher|Tags":["Етикети"],"Related Commits":["Свързани подавания"],"Related Deployed Jobs":["Свързани внедрени задачи"],"Related Issues":["Свързани проблеми"],"Related Jobs":["Свързани задачи"],"Related Merge Requests":["Свързани заявки за сливане"],"Related Merged Requests":["Свързани приложени заявки за сливане"],"Remind later":["Напомняне по-късно"],"Remove project":["Премахване на проекта"],"Request Access":["Заявка за достъп"],"Revert this commit":["Отмяна на това подаване"],"Revert this merge-request":["Отмяна на тази заявка за сливане"],"Save pipeline schedule":["Запазване на плана за схема"],"Schedule a new pipeline":["Създаване на нов план за схема"],"Scheduling Pipelines":["Планиране на схемите"],"Search branches and tags":["Търсене в клоновете и етикетите"],"Select Archive Format":["Изберете формата на архива"],"Select a timezone":["Изберете часова зона"],"Select target branch":["Изберете целеви клон"],"Set a password on your account to pull or push via %{protocol}":["Задайте парола на профила си, за да можете да изтегляте и изпращате промени чрез %{protocol}"],"Set up CI":["Настройка на НИ"],"Set up Koding":["Настройка на „Koding“"],"Set up auto deploy":["Настройка на авт. внедряване"],"SetPasswordToCloneLink|set a password":["зададете парола"],"Showing %d event":["Показване на %d събитие","Показване на %d събития"],"Source code":["Изходен код"],"StarProject|Star":["Звезда"],"Start a new merge request with these changes":["Създайте нова заявка за сливане с тези промени"],"Switch branch/tag":["Преминаване към клон/етикет"],"Tag":["Етикет","Етикети"],"Tags":["Етикети"],"Target Branch":["Целеви клон"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Етапът на програмиране показва времето от първото подаване до създаването на заявката за сливане. Данните ще бъдат добавени тук автоматично след като бъде създадена първата заявка за сливане."],"The collection of events added to the data gathered for that stage.":["Съвкупността от събития добавени към данните събрани за този етап."],"The fork relationship has been removed.":["Връзката на разклонение беше премахната."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Етапът на проблемите показва колко е времето от създаването на проблем до определянето на целеви етап на проекта за него, или до добавянето му в списък на дъската за проблеми. Започнете да добавяте проблеми, за да видите данните за този етап."],"The phase of the development lifecycle.":["Етапът от цикъла на разработка"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["Този план за схема ще изпълнява схемите в бъдеще, периодично, за определени клонове или етикети. Тези планирани схеми ще наследят ограниченията на достъпа до проекта на свързания с тях потребител."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Етапът на планиране показва колко е времето от преходната стъпка до изпращането на първото подаване. Това време ще бъде добавено автоматично след като изпратите първото си подаване."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Етапът на издаване показва общото време, което е нужно от създаването на проблем до внедряването на кода в крайната версия. Данните ще бъдат добавени автоматично след като завършите един пълен цикъл и превърнете първата си идея в реалност."],"The project can be accessed by any logged in user.":["Всеки вписан потребител има достъп до проекта."],"The project can be accessed without any authentication.":["Всеки може да има достъп до проекта, без нужда от удостоверяване."],"The repository for this project does not exist.":["Хранилището за този проект не съществува."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Етапът на тестване показва времето, което е нужно на „Gitlab CI“ да изпълни всяка схема от задачи за свързаната заявка за сливане. Данните ще бъдат добавени автоматично след като приключи изпълнението на първата Ви схема."],"The time taken by each data entry gathered by that stage.":["Времето, което отнема всеки запис от данни за съответния етап."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Стойността, която се намира в средата на последователността от наблюдавани данни. Например: медианата на 3, 5 и 9 е 5, а медианата на 3, 5, 7 и 8 е (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Това означава, че няма да можете да изпращате код, докато не създадете празно хранилище или не внесете съществуващо такова."],"Time before an issue gets scheduled":["Време преди един проблем да бъде планиран за работа"],"Time before an issue starts implementation":["Време преди работата по проблем да започне"],"Time between merge request creation and merge/close":["Време между създаване на заявка за сливане и прилагането/отхвърлянето ѝ"],"Time until first merge request":["Време преди първата заявка за сливане"],"Timeago|%s days ago":["преди %s дни"],"Timeago|%s days remaining":["остават %s дни"],"Timeago|%s hours remaining":["остават %s часа"],"Timeago|%s minutes ago":["преди %s минути"],"Timeago|%s minutes remaining":["остават %s минути"],"Timeago|%s months ago":["преди %s месеца"],"Timeago|%s months remaining":["остават %s месеца"],"Timeago|%s seconds remaining":["остават %s секунди"],"Timeago|%s weeks ago":["преди %s седмици"],"Timeago|%s weeks remaining":["остават %s седмици"],"Timeago|%s years ago":["преди %s години"],"Timeago|%s years remaining":["остават %s години"],"Timeago|1 day remaining":["остава 1 ден"],"Timeago|1 hour remaining":["остава 1 час"],"Timeago|1 minute remaining":["остава 1 минута"],"Timeago|1 month remaining":["остава 1 месец"],"Timeago|1 week remaining":["остава 1 седмица"],"Timeago|1 year remaining":["остава 1 година"],"Timeago|Past due":["Просрочено"],"Timeago|a day ago":["преди един ден"],"Timeago|a month ago":["преди един месец"],"Timeago|a week ago":["преди една седмица"],"Timeago|a while":["преди известно време"],"Timeago|a year ago":["преди една година"],"Timeago|about %s hours ago":["преди около %s часа"],"Timeago|about a minute ago":["преди около една минута"],"Timeago|about an hour ago":["преди около един час"],"Timeago|in %s days":["след %s дни"],"Timeago|in %s hours":["след %s часа"],"Timeago|in %s minutes":["след %s минути"],"Timeago|in %s months":["след %s месеца"],"Timeago|in %s seconds":["след %s секунди"],"Timeago|in %s weeks":["след %s седмици"],"Timeago|in %s years":["след %s години"],"Timeago|in 1 day":["след 1 ден"],"Timeago|in 1 hour":["след 1 час"],"Timeago|in 1 minute":["след 1 минута"],"Timeago|in 1 month":["след 1 месец"],"Timeago|in 1 week":["след 1 седмица"],"Timeago|in 1 year":["след 1 година"],"Timeago|less than a minute ago":["преди по-малко от минута"],"Time|hr":["час","часа"],"Time|min":["мин","мин"],"Time|s":["сек"],"Total Time":["Общо време"],"Total test time for all commits/merges":["Общо време за тестване на всички подавания/сливания"],"Unstar":["Без звезда"],"Upload New File":["Качване на нов файл"],"Upload file":["Качване на файл"],"Use your global notification setting":["Използване на глобалната Ви настройка за известията"],"VisibilityLevel|Internal":["Вътрешен"],"VisibilityLevel|Private":["Частен"],"VisibilityLevel|Public":["Публичен"],"Want to see the data? Please ask an administrator for access.":["Искате ли да видите данните? Помолете администратор за достъп."],"We don't have enough data to show this stage.":["Няма достатъчно данни за този етап."],"Withdraw Access Request":["Оттегляне на заявката за достъп"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["На път сте да премахнете „%{project_name_with_namespace}“.\\nАко го премахнете, той НЕ може да бъде възстановен!\\nНАИСТИНА ли искате това?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["На път сте да премахнете връзката на разклонението към оригиналния проект, „%{forked_from_project}“. НАИСТИНА ли искате това?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?"],"You can only add files when you are on a branch":["Можете да добавяте файлове само когато се намирате в клон"],"You must sign in to star a project":["Трябва да се впишете, за да отбележите проект със звезда"],"You need permission.":["Нуждаете се от разрешение."],"You will not get any notifications via email":["Няма да получавате никакви известия по е-поща"],"You will only receive notifications for the events you choose":["Ще получавате известия само за събитията, за които желаете"],"You will only receive notifications for threads you have participated in":["Ще получавате известия само за нещата, в които участвате"],"You will receive notifications for any activity":["Ще получавате известия за всяка дейност"],"You will receive notifications only for comments in which you were @mentioned":["Ще получавате известия само за коментари, в които Ви @споменават"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Няма да можете да изтегляте или изпращате код в проекта чрез %{protocol}, докато не %{set_password_link} за профила си"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Няма да можете да изтегляте или изпращате код в проекта чрез SSH, докато не %{add_ssh_key_link} в профила си"],"Your name":["Вашето име"],"day":["ден","дни"],"notification emails":["известия по е-поща"],"parent":["родител","родители"],"pipeline schedules documentation":["документацията за планирането на схеми"],"with stage":["с етап","с етапи"]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/de/app.js b/app/assets/javascripts/locale/de/app.js deleted file mode 100644 index e7d2b174405..00000000000 --- a/app/assets/javascripts/locale/de/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['de'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-09 13:44+0200","Language-Team":"German","Language":"de","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"","X-Generator":"Poedit 2.0.1","lang":"de","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["Von"],"Cancel":[""],"Commit":["Commit","Commits"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics liefern einen Überblick darüber, wie viel Zeit in Ihrem Projekt von einer Idee bis zum Produktivdeployment vergeht."],"CycleAnalyticsStage|Code":["Code"],"CycleAnalyticsStage|Issue":["Issue"],"CycleAnalyticsStage|Plan":["Planung"],"CycleAnalyticsStage|Production":["Produktiv"],"CycleAnalyticsStage|Review":["Review"],"CycleAnalyticsStage|Staging":["Staging"],"CycleAnalyticsStage|Test":["Test"],"Delete":[""],"Deploy":["Deployment","Deployments"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["Erster"],"FirstPushedBy|pushed by":["gepusht von"],"From issue creation until deploy to production":["Vom Anlegen des Issues bis zum Produktivdeployment"],"From merge request merge until deploy to production":["Vom Merge Request bis zum Produktivdeployment"],"Interval Pattern":[""],"Introducing Cycle Analytics":["Was sind Cycle Analytics?"],"Last %d day":["Letzter %d Tag","Letzten %d Tage"],"Last Pipeline":[""],"Limited to showing %d event at most":["Eingeschränkt auf maximal %d Ereignis","Eingeschränkt auf maximal %d Ereignisse"],"Median":["Median"],"New Issue":["Neues Issue","Neue Issues"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["Nicht verfügbar"],"Not enough data":["Nicht genügend Daten"],"OpenedNDaysAgo|Opened":["Erstellt"],"Owner":[""],"Pipeline Health":["Pipeline Kennzahlen"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["Phase"],"Read more":["Mehr"],"Related Commits":["Zugehörige Commits"],"Related Deployed Jobs":["Zugehörige Deploymentjobs"],"Related Issues":["Zugehörige Issues"],"Related Jobs":["Zugehörige Jobs"],"Related Merge Requests":["Zugehörige Merge Requests"],"Related Merged Requests":["Zugehörige abgeschlossene Merge Requests"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["Zeige %d Ereignis","Zeige %d Ereignisse"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Die Code-Phase stellt die Zeit vom ersten Commit bis zum Erstellen eines Merge Requests dar. Sobald Sie Ihren ersten Merge Request anlegen, werden dessen Daten automatisch ergänzt."],"The collection of events added to the data gathered for that stage.":["Ereignisse, die für diese Phase ausgewertet wurden."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Die Issue-Phase stellt die Zeit vom Anlegen eines Issues bis zum Zuweisen eines Meilensteins oder Hinzufügen zum Issue Board dar. Erstellen Sie einen Issue, damit dessen Daten hier erscheinen."],"The phase of the development lifecycle.":["Die Phase im Entwicklungsprozess."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Die Planungsphase stellt die Zeit von der vorherigen Phase bis zum Pushen des ersten Commits dar. Sobald Sie den ersten Commit pushen, werden dessen Daten hier erscheinen."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Die Produktiv-Phase stellt die Gesamtzeit vom Anlegen eines Issues bis zum Deployment auf dem Produktivsystem dar. Sobald Sie den vollständigen Entwicklungszyklus von einer Idee bis zum Produktivdeployment durchlaufen haben, erscheinen die zugehörigen Daten hier."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Die Review-Phase stellt die Zeit vom Anlegen eines Merge Requests bis zum Mergen dar. Sobald Sie Ihren ersten Merge Request abschließen, werden dessen Daten hier automatisch angezeigt."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Die Staging-Phase stellt die Zeit zwischen Mergen eines Merge Requests und dem Produktivdeployment dar. Sobald Sie das erste Produktivdeployment durchgeführt haben, werden dessen Daten hier automatisch angezeigt."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Die Test-Phase stellt die Zeit dar, die GitLab CI benötigt um die Pipelines von Merge Requests abzuarbeiten. Sobald die erste Pipeline abgeschlossen ist, werden deren Daten hier automatisch angezeigt."],"The time taken by each data entry gathered by that stage.":["Zeit die für das jeweilige Ereignis in der Phase ermittelt wurde."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Der mittlere aller erfassten Werte. Zum Beispiel ist für 3, 5, 9 der Median 5. Bei 3, 5, 7, 8 ist der Median (5+7)/2 = 6."],"Time before an issue gets scheduled":["Zeit bis ein Issue geplant wird"],"Time before an issue starts implementation":["Zeit bis die Implementierung für ein Issue beginnt"],"Time between merge request creation and merge/close":["Zeit zwischen Anlegen und Mergen/Schließen eines Merge Requests"],"Time until first merge request":["Zeit bis zum ersten Merge Request"],"Time|hr":["h","h"],"Time|min":["min","min"],"Time|s":["s"],"Total Time":["Gesamtzeit"],"Total test time for all commits/merges":["Gesamte Testlaufzeit für alle Commits/Merges"],"Want to see the data? Please ask an administrator for access.":["Um diese Daten einsehen zu können, wenden Sie sich bitte an Ihren Administrator."],"We don't have enough data to show this stage.":["Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen."],"You need permission.":["Sie benötigen Zugriffsrechte."],"day":["Tag","Tage"]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/en/app.js b/app/assets/javascripts/locale/en/app.js deleted file mode 100644 index d634af959e5..00000000000 --- a/app/assets/javascripts/locale/en/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['en'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-04-12 22:36-0500","Last-Translator":"FULL NAME ","Language-Team":"English","Language":"en","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","lang":"en","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"%{commit_author_link} committed %{commit_timeago}":[""],"About auto deploy":[""],"Active":[""],"Activity":[""],"Add Changelog":[""],"Add Contribution guide":[""],"Add License":[""],"Add an SSH key to your profile to pull or push via SSH.":[""],"Add new directory":[""],"Archived project! Repository is read-only":[""],"Are you sure you want to delete this pipeline schedule?":[""],"Attach a file by drag & drop or %{upload_link}":[""],"Branch":["",""],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":[""],"Branches":[""],"Browse files":[""],"ByAuthor|by":[""],"CI configuration":[""],"Cancel":[""],"ChangeTypeActionLabel|Pick into branch":[""],"ChangeTypeActionLabel|Revert in branch":[""],"ChangeTypeAction|Cherry-pick":[""],"Changelog":[""],"Charts":[""],"Cherry-pick this commit":[""],"Cherry-pick this merge request":[""],"CiStatusLabel|canceled":[""],"CiStatusLabel|created":[""],"CiStatusLabel|failed":[""],"CiStatusLabel|manual action":[""],"CiStatusLabel|passed":[""],"CiStatusLabel|passed with warnings":[""],"CiStatusLabel|pending":[""],"CiStatusLabel|skipped":[""],"CiStatusLabel|waiting for manual action":[""],"CiStatusText|blocked":[""],"CiStatusText|canceled":[""],"CiStatusText|created":[""],"CiStatusText|failed":[""],"CiStatusText|manual":[""],"CiStatusText|passed":[""],"CiStatusText|pending":[""],"CiStatusText|skipped":[""],"CiStatus|running":[""],"Commit":["",""],"Commit message":[""],"CommitBoxTitle|Commit":[""],"CommitMessage|Add %{file_name}":[""],"Commits":[""],"Commits|History":[""],"Committed by":[""],"Compare":[""],"Contribution guide":[""],"Contributors":[""],"Copy URL to clipboard":[""],"Copy commit SHA to clipboard":[""],"Create New Directory":[""],"Create directory":[""],"Create empty bare repository":[""],"Create merge request":[""],"Create new...":[""],"CreateNewFork|Fork":[""],"CreateTag|Tag":[""],"Cron Timezone":[""],"Cron syntax":[""],"Custom notification events":[""],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":[""],"Cycle Analytics":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":[""],"CycleAnalyticsStage|Code":[""],"CycleAnalyticsStage|Issue":[""],"CycleAnalyticsStage|Plan":[""],"CycleAnalyticsStage|Production":[""],"CycleAnalyticsStage|Review":[""],"CycleAnalyticsStage|Staging":[""],"CycleAnalyticsStage|Test":[""],"Define a custom pattern with cron syntax":[""],"Delete":[""],"Deploy":["",""],"Description":[""],"Directory name":[""],"Don't show again":[""],"Download":[""],"Download tar":[""],"Download tar.bz2":[""],"Download tar.gz":[""],"Download zip":[""],"DownloadArtifacts|Download":[""],"DownloadCommit|Email Patches":[""],"DownloadCommit|Plain Diff":[""],"DownloadSource|Download":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Every day (at 4:00am)":[""],"Every month (on the 1st at 4:00am)":[""],"Every week (Sundays at 4:00am)":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Files":[""],"Find by path":[""],"Find file":[""],"FirstPushedBy|First":[""],"FirstPushedBy|pushed by":[""],"Fork":["",""],"ForkedFromProjectPath|Forked from":[""],"From issue creation until deploy to production":[""],"From merge request merge until deploy to production":[""],"Go to your fork":[""],"GoToYourFork|Fork":[""],"Home":[""],"Housekeeping successfully started":[""],"Import repository":[""],"Interval Pattern":[""],"Introducing Cycle Analytics":[""],"LFSStatus|Disabled":[""],"LFSStatus|Enabled":[""],"Last %d day":["",""],"Last Pipeline":[""],"Last Update":[""],"Last commit":[""],"Learn more in the":[""],"Learn more in the|pipeline schedules documentation":[""],"Leave group":[""],"Leave project":[""],"Limited to showing %d event at most":["",""],"Median":[""],"MissingSSHKeyWarningLink|add an SSH key":[""],"New Issue":["",""],"New Pipeline Schedule":[""],"New branch":[""],"New directory":[""],"New file":[""],"New issue":[""],"New merge request":[""],"New schedule":[""],"New snippet":[""],"New tag":[""],"No repository":[""],"No schedules":[""],"Not available":[""],"Not enough data":[""],"Notification events":[""],"NotificationEvent|Close issue":[""],"NotificationEvent|Close merge request":[""],"NotificationEvent|Failed pipeline":[""],"NotificationEvent|Merge merge request":[""],"NotificationEvent|New issue":[""],"NotificationEvent|New merge request":[""],"NotificationEvent|New note":[""],"NotificationEvent|Reassign issue":[""],"NotificationEvent|Reassign merge request":[""],"NotificationEvent|Reopen issue":[""],"NotificationEvent|Successful pipeline":[""],"NotificationLevel|Custom":[""],"NotificationLevel|Disabled":[""],"NotificationLevel|Global":[""],"NotificationLevel|On mention":[""],"NotificationLevel|Participate":[""],"NotificationLevel|Watch":[""],"OfSearchInADropdown|Filter":[""],"OpenedNDaysAgo|Opened":[""],"Options":[""],"Owner":[""],"Pipeline":[""],"Pipeline Health":[""],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"PipelineSheduleIntervalPattern|Custom":[""],"Pipeline|with stage":[""],"Pipeline|with stages":[""],"Project '%{project_name}' queued for deletion.":[""],"Project '%{project_name}' was successfully created.":[""],"Project '%{project_name}' was successfully updated.":[""],"Project '%{project_name}' will be deleted.":[""],"Project access must be granted explicitly to each user.":[""],"Project export could not be deleted.":[""],"Project export has been deleted.":[""],"Project export link has expired. Please generate a new export from your project settings.":[""],"Project export started. A download link will be sent by email.":[""],"Project home":[""],"ProjectFeature|Disabled":[""],"ProjectFeature|Everyone with access":[""],"ProjectFeature|Only team members":[""],"ProjectFileTree|Name":[""],"ProjectLastActivity|Never":[""],"ProjectLifecycle|Stage":[""],"ProjectNetworkGraph|Graph":[""],"Read more":[""],"Readme":[""],"RefSwitcher|Branches":[""],"RefSwitcher|Tags":[""],"Related Commits":[""],"Related Deployed Jobs":[""],"Related Issues":[""],"Related Jobs":[""],"Related Merge Requests":[""],"Related Merged Requests":[""],"Remind later":[""],"Remove project":[""],"Request Access":[""],"Revert this commit":[""],"Revert this merge request":[""],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Scheduling Pipelines":[""],"Search branches and tags":[""],"Select Archive Format":[""],"Select a timezone":[""],"Select target branch":[""],"Set a password on your account to pull or push via %{protocol}":[""],"Set up CI":[""],"Set up Koding":[""],"Set up auto deploy":[""],"SetPasswordToCloneLink|set a password":[""],"Showing %d event":["",""],"Source code":[""],"StarProject|Star":[""],"Start a %{new_merge_request} with these changes":[""],"Start a new merge request with these changes":[""],"Switch branch/tag":[""],"Tag":["",""],"Tags":[""],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":[""],"The collection of events added to the data gathered for that stage.":[""],"The fork relationship has been removed.":[""],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":[""],"The phase of the development lifecycle.":[""],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":[""],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":[""],"The project can be accessed by any logged in user.":[""],"The project can be accessed without any authentication.":[""],"The repository for this project does not exist.":[""],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":[""],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":[""],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":[""],"The time taken by each data entry gathered by that stage.":[""],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":[""],"This means you can not push code until you create an empty repository or import existing one.":[""],"Time before an issue gets scheduled":[""],"Time before an issue starts implementation":[""],"Time between merge request creation and merge/close":[""],"Time until first merge request":[""],"Timeago|%s days ago":[""],"Timeago|%s days remaining":[""],"Timeago|%s hours remaining":[""],"Timeago|%s minutes ago":[""],"Timeago|%s minutes remaining":[""],"Timeago|%s months ago":[""],"Timeago|%s months remaining":[""],"Timeago|%s seconds remaining":[""],"Timeago|%s weeks ago":[""],"Timeago|%s weeks remaining":[""],"Timeago|%s years ago":[""],"Timeago|%s years remaining":[""],"Timeago|1 day remaining":[""],"Timeago|1 hour remaining":[""],"Timeago|1 minute remaining":[""],"Timeago|1 month remaining":[""],"Timeago|1 week remaining":[""],"Timeago|1 year remaining":[""],"Timeago|Past due":[""],"Timeago|a day ago":[""],"Timeago|a month ago":[""],"Timeago|a week ago":[""],"Timeago|a while":[""],"Timeago|a year ago":[""],"Timeago|about %s hours ago":[""],"Timeago|about a minute ago":[""],"Timeago|about an hour ago":[""],"Timeago|in %s days":[""],"Timeago|in %s hours":[""],"Timeago|in %s minutes":[""],"Timeago|in %s months":[""],"Timeago|in %s seconds":[""],"Timeago|in %s weeks":[""],"Timeago|in %s years":[""],"Timeago|in 1 day":[""],"Timeago|in 1 hour":[""],"Timeago|in 1 minute":[""],"Timeago|in 1 month":[""],"Timeago|in 1 week":[""],"Timeago|in 1 year":[""],"Timeago|less than a minute ago":[""],"Time|hr":["",""],"Time|min":["",""],"Time|s":[""],"Total Time":[""],"Total test time for all commits/merges":[""],"Unstar":[""],"Upload New File":[""],"Upload file":[""],"Use your global notification setting":[""],"VisibilityLevel|Internal":[""],"VisibilityLevel|Private":[""],"VisibilityLevel|Public":[""],"Want to see the data? Please ask an administrator for access.":[""],"We don't have enough data to show this stage.":[""],"Withdraw Access Request":[""],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":[""],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":[""],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":[""],"You can only add files when you are on a branch":[""],"You must sign in to star a project":[""],"You need permission.":[""],"You will not get any notifications via email":[""],"You will only receive notifications for the events you choose":[""],"You will only receive notifications for threads you have participated in":[""],"You will receive notifications for any activity":[""],"You will receive notifications only for comments in which you were @mentioned":[""],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":[""],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":[""],"Your name":[""],"day":["",""],"new merge request":[""],"notification emails":[""],"parent":["",""]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/es/app.js b/app/assets/javascripts/locale/es/app.js deleted file mode 100644 index 8d951928849..00000000000 --- a/app/assets/javascripts/locale/es/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['es'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-06-21 12:09-0500","Language-Team":"Spanish","Language":"es","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"Bob Van Landuyt ","X-Generator":"Poedit 2.0.2","lang":"es","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"%d additional commit has been omitted to prevent performance issues.":["%d cambio adicional ha sido omitido para evitar problemas de rendimiento.","%d cambios adicionales han sido omitidos para evitar problemas de rendimiento."],"%d commit":["%d cambio","%d cambios"],"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} cambió %{commit_timeago}"],"About auto deploy":["Acerca del auto despliegue"],"Active":["Activo"],"Activity":["Actividad"],"Add Changelog":["Agregar Changelog"],"Add Contribution guide":["Agregar guía de contribución"],"Add License":["Agregar Licencia"],"Add an SSH key to your profile to pull or push via SSH.":["Agregar una clave SSH a tu perfil para actualizar o enviar a través de SSH."],"Add new directory":["Agregar nuevo directorio"],"Archived project! Repository is read-only":["¡Proyecto archivado! El repositorio es de solo lectura"],"Are you sure you want to delete this pipeline schedule?":["¿Estás seguro que deseas eliminar esta programación del pipeline?"],"Attach a file by drag & drop or %{upload_link}":["Adjunte un archivo arrastrando & soltando o %{upload_link}"],"Branch":["Rama","Ramas"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["La rama %{branch_name} fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envía tus cambios. %{link_to_autodeploy_doc}"],"BranchSwitcherPlaceholder|Search branches":["Buscar ramas"],"BranchSwitcherTitle|Switch branch":["Cambiar rama"],"Branches":["Ramas"],"Browse Directory":["Examinar directorio"],"Browse File":["Examinar archivo"],"Browse Files":["Examinar archivos"],"Browse files":["Examinar archivos"],"ByAuthor|by":["por"],"CI configuration":["Configuración de CI"],"Cancel":["Cancelar"],"ChangeTypeActionLabel|Pick into branch":["Escoger en la rama"],"ChangeTypeActionLabel|Revert in branch":["Revertir en la rama"],"ChangeTypeAction|Cherry-pick":["Cherry-pick"],"ChangeTypeAction|Revert":["Revertir"],"Changelog":["Changelog"],"Charts":["Gráficos"],"Cherry-pick this commit":["Escoger este cambio"],"Cherry-pick this merge request":["Escoger esta solicitud de fusión"],"CiStatusLabel|canceled":["cancelado"],"CiStatusLabel|created":["creado"],"CiStatusLabel|failed":["fallido"],"CiStatusLabel|manual action":["acción manual"],"CiStatusLabel|passed":["pasó"],"CiStatusLabel|passed with warnings":["pasó con advertencias"],"CiStatusLabel|pending":["pendiente"],"CiStatusLabel|skipped":["omitido"],"CiStatusLabel|waiting for manual action":["esperando acción manual"],"CiStatusText|blocked":["bloqueado"],"CiStatusText|canceled":["cancelado"],"CiStatusText|created":["creado"],"CiStatusText|failed":["fallado"],"CiStatusText|manual":["manual"],"CiStatusText|passed":["pasó"],"CiStatusText|pending":["pendiente"],"CiStatusText|skipped":["omitido"],"CiStatus|running":["en ejecución"],"Commit":["Cambio","Cambios"],"Commit message":["Mensaje del cambio"],"CommitBoxTitle|Commit":["Cambio"],"CommitMessage|Add %{file_name}":["Agregar %{file_name}"],"Commits":["Cambios"],"Commits feed":["Feed de cambios"],"Commits|History":["Historial"],"Committed by":["Enviado por"],"Compare":["Comparar"],"Contribution guide":["Guía de contribución"],"Contributors":["Contribuidores"],"Copy URL to clipboard":["Copiar URL al portapapeles"],"Copy commit SHA to clipboard":["Copiar SHA del cambio al portapapeles"],"Create New Directory":["Crear Nuevo Directorio"],"Create directory":["Crear directorio"],"Create empty bare repository":["Crear repositorio vacío"],"Create merge request":["Crear solicitud de fusión"],"Create new...":["Crear nuevo..."],"CreateNewFork|Fork":["Bifurcar"],"CreateTag|Tag":["Etiqueta"],"Cron Timezone":["Zona horaria del Cron"],"Cron syntax":["Sintaxis de Cron"],"Custom notification events":["Eventos de notificaciones personalizadas"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["Los niveles de notificación personalizados son los mismos que los niveles participantes. Con los niveles de notificación personalizados, también recibirá notificaciones para eventos seleccionados. Para obtener más información, consulte %{notification_link}."],"Cycle Analytics":["Cycle Analytics"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto."],"CycleAnalyticsStage|Code":["Código"],"CycleAnalyticsStage|Issue":["Incidencia"],"CycleAnalyticsStage|Plan":["Planificación"],"CycleAnalyticsStage|Production":["Producción"],"CycleAnalyticsStage|Review":["Revisión"],"CycleAnalyticsStage|Staging":["Puesta en escena"],"CycleAnalyticsStage|Test":["Pruebas"],"Define a custom pattern with cron syntax":["Definir un patrón personalizado con la sintaxis de cron"],"Delete":["Eliminar"],"Deploy":["Despliegue","Despliegues"],"Description":["Descripción"],"Directory name":["Nombre del directorio"],"Don't show again":["No mostrar de nuevo"],"Download":["Descargar"],"Download tar":["Descargar tar"],"Download tar.bz2":["Descargar tar.bz2"],"Download tar.gz":["Descargar tar.gz"],"Download zip":["Descargar zip"],"DownloadArtifacts|Download":["Descargar"],"DownloadCommit|Email Patches":["Parches por correo electrónico"],"DownloadCommit|Plain Diff":["Diferencias en texto plano"],"DownloadSource|Download":["Descargar"],"Edit":["Editar"],"Edit Pipeline Schedule %{id}":["Editar Programación del Pipeline %{id}"],"Every day (at 4:00am)":["Todos los días (a las 4:00 am)"],"Every month (on the 1st at 4:00am)":["Todos los meses (el día 1 a las 4:00 am)"],"Every week (Sundays at 4:00am)":["Todas las semanas (domingos a las 4:00 am)"],"Failed to change the owner":["Error al cambiar el propietario"],"Failed to remove the pipeline schedule":["Error al eliminar la programación del pipeline"],"Files":["Archivos"],"Filter by commit message":["Filtrar por mensaje del cambio"],"Find by path":["Buscar por ruta"],"Find file":["Buscar archivo"],"FirstPushedBy|First":["Primer"],"FirstPushedBy|pushed by":["enviado por"],"Fork":["Bifurcación","Bifurcaciones"],"ForkedFromProjectPath|Forked from":["Bifurcado de"],"From issue creation until deploy to production":["Desde la creación de la incidencia hasta el despliegue a producción"],"From merge request merge until deploy to production":["Desde la integración de la solicitud de fusión hasta el despliegue a producción"],"Go to your fork":["Ir a tu bifurcación"],"GoToYourFork|Fork":["Bifurcación"],"Home":["Inicio"],"Housekeeping successfully started":["Servicio de limpieza iniciado con éxito"],"Import repository":["Importar repositorio"],"Interval Pattern":["Patrón de intervalo"],"Introducing Cycle Analytics":["Introducción a Cycle Analytics"],"LFSStatus|Disabled":["Deshabilitado"],"LFSStatus|Enabled":["Habilitado"],"Last %d day":["Último %d día","Últimos %d días"],"Last Pipeline":["Último Pipeline"],"Last Update":["Última actualización"],"Last commit":["Último cambio"],"Learn more in the":["Más información en la"],"Learn more in the|pipeline schedules documentation":["documentación sobre la programación de pipelines"],"Leave group":["Abandonar grupo"],"Leave project":["Abandonar proyecto"],"Limited to showing %d event at most":["Limitado a mostrar máximo %d evento","Limitado a mostrar máximo %d eventos"],"Median":["Mediana"],"MissingSSHKeyWarningLink|add an SSH key":["agregar una clave SSH"],"New Issue":["Nueva incidencia","Nuevas incidencias"],"New Pipeline Schedule":["Nueva Programación del Pipeline"],"New branch":["Nueva rama"],"New directory":["Nuevo directorio"],"New file":["Nuevo archivo"],"New issue":["Nueva incidencia"],"New merge request":["Nueva solicitud de fusión"],"New schedule":["Nueva programación"],"New snippet":["Nuevo fragmento de código"],"New tag":["Nueva etiqueta"],"No repository":["No hay repositorio"],"No schedules":["No hay programaciones"],"Not available":["No disponible"],"Not enough data":["No hay suficientes datos"],"Notification events":["Eventos de notificación"],"NotificationEvent|Close issue":["Cerrar incidencia"],"NotificationEvent|Close merge request":["Cerrar solicitud de fusión"],"NotificationEvent|Failed pipeline":["Pipeline fallido"],"NotificationEvent|Merge merge request":["Integrar solicitud de fusión"],"NotificationEvent|New issue":["Nueva incidencia"],"NotificationEvent|New merge request":["Nueva solicitud de fusión"],"NotificationEvent|New note":["Nueva nota"],"NotificationEvent|Reassign issue":["Reasignar incidencia"],"NotificationEvent|Reassign merge request":["Reasignar solicitud de fusión"],"NotificationEvent|Reopen issue":["Reabrir incidencia"],"NotificationEvent|Successful pipeline":["Pipeline exitoso"],"NotificationLevel|Custom":["Personalizado"],"NotificationLevel|Disabled":["Deshabilitado"],"NotificationLevel|Global":["Global"],"NotificationLevel|On mention":["Cuando me mencionan"],"NotificationLevel|Participate":["Participación"],"NotificationLevel|Watch":["Vigilancia"],"OfSearchInADropdown|Filter":["Filtrar"],"OpenedNDaysAgo|Opened":["Abierto"],"Options":["Opciones"],"Owner":["Propietario"],"Pipeline":["Pipeline"],"Pipeline Health":["Estado del Pipeline"],"Pipeline Schedule":["Programación del Pipeline"],"Pipeline Schedules":["Programaciones de los Pipelines"],"PipelineSchedules|Activated":["Activado"],"PipelineSchedules|Active":["Activos"],"PipelineSchedules|All":["Todos"],"PipelineSchedules|Inactive":["Inactivos"],"PipelineSchedules|Next Run":["Próxima Ejecución"],"PipelineSchedules|None":["Ninguno"],"PipelineSchedules|Provide a short description for this pipeline":["Proporcione una breve descripción para este pipeline"],"PipelineSchedules|Take ownership":["Tomar posesión"],"PipelineSchedules|Target":["Destino"],"PipelineSheduleIntervalPattern|Custom":["Personalizado"],"Pipeline|with stage":["con etapa"],"Pipeline|with stages":["con etapas"],"Project '%{project_name}' queued for deletion.":["Proyecto ‘%{project_name}’ en cola para eliminación."],"Project '%{project_name}' was successfully created.":["Proyecto ‘%{project_name}’ fue creado satisfactoriamente."],"Project '%{project_name}' was successfully updated.":["Proyecto ‘%{project_name}’ fue actualizado satisfactoriamente."],"Project '%{project_name}' will be deleted.":["Proyecto ‘%{project_name}’ será eliminado."],"Project access must be granted explicitly to each user.":["El acceso al proyecto debe concederse explícitamente a cada usuario."],"Project export could not be deleted.":["No se pudo eliminar la exportación del proyecto."],"Project export has been deleted.":["La exportación del proyecto ha sido eliminada."],"Project export link has expired. Please generate a new export from your project settings.":["El enlace de exportación del proyecto ha caducado. Por favor, genera una nueva exportación desde la configuración del proyecto."],"Project export started. A download link will be sent by email.":["Se inició la exportación del proyecto. Se enviará un enlace de descarga por correo electrónico."],"Project home":["Inicio del proyecto"],"ProjectFeature|Disabled":["Deshabilitada"],"ProjectFeature|Everyone with access":["Todos con acceso"],"ProjectFeature|Only team members":["Solo miembros del equipo"],"ProjectFileTree|Name":["Nombre"],"ProjectLastActivity|Never":["Nunca"],"ProjectLifecycle|Stage":["Etapa"],"ProjectNetworkGraph|Graph":["Historial gráfico"],"Read more":["Leer más"],"Readme":["Léeme"],"RefSwitcher|Branches":["Ramas"],"RefSwitcher|Tags":["Etiquetas"],"Related Commits":["Cambios Relacionados"],"Related Deployed Jobs":["Trabajos Desplegados Relacionados"],"Related Issues":["Incidencias Relacionadas"],"Related Jobs":["Trabajos Relacionados"],"Related Merge Requests":["Solicitudes de fusión Relacionadas"],"Related Merged Requests":["Solicitudes de fusión Relacionadas"],"Remind later":["Recordar después"],"Remove project":["Eliminar proyecto"],"Request Access":["Solicitar acceso"],"Revert this commit":["Revertir este cambio"],"Revert this merge request":["Revertir esta solicitud de fusión"],"Save pipeline schedule":["Guardar programación del pipeline"],"Schedule a new pipeline":["Programar un nuevo pipeline"],"Scheduling Pipelines":["Programación de Pipelines"],"Search branches and tags":["Buscar ramas y etiquetas"],"Select Archive Format":["Seleccionar formato de archivo"],"Select a timezone":["Selecciona una zona horaria"],"Select target branch":["Selecciona una rama de destino"],"Set a password on your account to pull or push via %{protocol}":["Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}"],"Set up CI":["Configurar CI"],"Set up Koding":["Configurar Koding"],"Set up auto deploy":["Configurar auto despliegue"],"SetPasswordToCloneLink|set a password":["establecer una contraseña"],"Showing %d event":["Mostrando %d evento","Mostrando %d eventos"],"Source code":["Código fuente"],"StarProject|Star":["Destacar"],"Start a %{new_merge_request} with these changes":["Iniciar una %{new_merge_request} con estos cambios"],"Switch branch/tag":["Cambiar rama/etiqueta"],"Tag":["Etiqueta","Etiquetas"],"Tags":["Etiquetas"],"Target Branch":["Rama de destino"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["La etapa de desarrollo muestra el tiempo desde el primer cambio hasta la creación de la solicitud de fusión. Los datos serán automáticamente incorporados aquí una vez creada tu primera solicitud de fusión."],"The collection of events added to the data gathered for that stage.":["La colección de eventos agregados a los datos recopilados para esa etapa."],"The fork relationship has been removed.":["La relación con la bifurcación se ha eliminado."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["La etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa."],"The phase of the development lifecycle.":["La etapa del ciclo de vida de desarrollo."],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["La programación de pipelines ejecuta pipelines en el futuro, repetidamente, para ramas o etiquetas específicas. Los pipelines programados heredarán acceso limitado al proyecto basado en su usuario asociado."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["La etapa de planificación muestra el tiempo desde el paso anterior hasta el envío de tu primera confirmación. Este tiempo se añadirá automáticamente una vez que usted envíe el primer cambio."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["La etapa de producción muestra el tiempo total que tarda entre la creación de una incidencia y el despliegue del código a producción. Los datos se añadirán automáticamente una vez haya finalizado por completo el ciclo de idea a producción."],"The project can be accessed by any logged in user.":["El proyecto puede ser accedido por cualquier usuario conectado."],"The project can be accessed without any authentication.":["El proyecto puede accederse sin ninguna autenticación."],"The repository for this project does not exist.":["El repositorio para este proyecto no existe."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["La etapa de revisión muestra el tiempo desde la creación de la solicitud de fusión hasta que los cambios se fusionaron. Los datos se añadirán automáticamente después de fusionar su primera solicitud de fusión."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["La etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["La etapa de pruebas muestra el tiempo que GitLab CI toma para ejecutar cada pipeline para la solicitud de fusión relacionada. Los datos se añadirán automáticamente luego de que el primer pipeline termine de ejecutarse."],"The time taken by each data entry gathered by that stage.":["El tiempo utilizado por cada entrada de datos obtenido por esa etapa."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["El valor en el punto medio de una serie de valores observados. Por ejemplo, entre 3, 5, 9, la mediana es 5. Entre 3, 5, 7, 8, la mediana es (5 + 7) / 2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Esto significa que no puede enviar código hasta que cree un repositorio vacío o importe uno existente."],"Time before an issue gets scheduled":["Tiempo antes de que una incidencia sea programada"],"Time before an issue starts implementation":["Tiempo antes de que empieze la implementación de una incidencia"],"Time between merge request creation and merge/close":["Tiempo entre la creación de la solicitud de fusión y la integración o cierre de ésta"],"Time until first merge request":["Tiempo hasta la primera solicitud de fusión"],"Timeago|%s days ago":["hace %s días"],"Timeago|%s days remaining":["%s días restantes"],"Timeago|%s hours remaining":["%s horas restantes"],"Timeago|%s minutes ago":["hace %s minutos"],"Timeago|%s minutes remaining":["%s minutos restantes"],"Timeago|%s months ago":["hace %s meses"],"Timeago|%s months remaining":["%s meses restantes"],"Timeago|%s seconds remaining":["%s segundos restantes"],"Timeago|%s weeks ago":["hace %s semanas"],"Timeago|%s weeks remaining":["%s semanas restantes"],"Timeago|%s years ago":["hace %s años"],"Timeago|%s years remaining":["%s años restantes"],"Timeago|1 day remaining":["1 día restante"],"Timeago|1 hour remaining":["1 hora restante"],"Timeago|1 minute remaining":["1 minuto restante"],"Timeago|1 month remaining":["1 mes restante"],"Timeago|1 week remaining":["1 semana restante"],"Timeago|1 year remaining":["1 año restante"],"Timeago|Past due":["Atrasado"],"Timeago|a day ago":["hace un día"],"Timeago|a month ago":["hace un mes"],"Timeago|a week ago":["hace una semana"],"Timeago|a while":["hace un momento"],"Timeago|a year ago":["hace un año"],"Timeago|about %s hours ago":["hace alrededor de %s horas"],"Timeago|about a minute ago":["hace alrededor de 1 minuto"],"Timeago|about an hour ago":["hace alrededor de 1 hora"],"Timeago|in %s days":["en %s días"],"Timeago|in %s hours":["en %s horas"],"Timeago|in %s minutes":["en %s minutos"],"Timeago|in %s months":["en %s meses"],"Timeago|in %s seconds":["en %s segundos"],"Timeago|in %s weeks":["en %s semanas"],"Timeago|in %s years":["en %s años"],"Timeago|in 1 day":["en 1 día"],"Timeago|in 1 hour":["en 1 hora"],"Timeago|in 1 minute":["en 1 minuto"],"Timeago|in 1 month":["en 1 mes"],"Timeago|in 1 week":["en 1 semana"],"Timeago|in 1 year":["en 1 año"],"Timeago|less than a minute ago":["hace menos de 1 minuto"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Tiempo Total"],"Total test time for all commits/merges":["Tiempo total de pruebas para todos los cambios o integraciones"],"Unstar":["No Destacar"],"Upload New File":["Subir nuevo archivo"],"Upload file":["Subir archivo"],"UploadLink|click to upload":["Hacer clic para subir"],"Use your global notification setting":["Utiliza tu configuración de notificación global"],"View open merge request":["Ver solicitud de fusión abierta"],"VisibilityLevel|Internal":["Interno"],"VisibilityLevel|Private":["Privado"],"VisibilityLevel|Public":["Público"],"Want to see the data? Please ask an administrator for access.":["¿Quieres ver los datos? Por favor pide acceso al administrador."],"We don't have enough data to show this stage.":["No hay suficientes datos para mostrar en esta etapa."],"Withdraw Access Request":["Retirar Solicitud de Acceso"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["Va a eliminar %{project_name_with_namespace}.\\n¡El proyecto eliminado NO puede ser restaurado!\\n¿Estás TOTALMENTE seguro?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?"],"You can only add files when you are on a branch":["Solo puedes agregar archivos cuando estás en una rama"],"You have reached your project limit":["Has alcanzado el límite de tu proyecto"],"You must sign in to star a project":["Debes iniciar sesión para destacar un proyecto"],"You need permission.":["Necesitas permisos."],"You will not get any notifications via email":["No recibirás ninguna notificación por correo electrónico"],"You will only receive notifications for the events you choose":["Solo recibirás notificaciones de los eventos que elijas"],"You will only receive notifications for threads you have participated in":["Solo recibirás notificaciones de los temas en los que has participado"],"You will receive notifications for any activity":["Recibirás notificaciones por cualquier actividad"],"You will receive notifications only for comments in which you were @mentioned":["Recibirás notificaciones solo para los comentarios en los que se te mencionó"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["No podrás actualizar o enviar código al proyecto a través de %{protocol} hasta que %{set_password_link} en tu cuenta"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["No podrás actualizar o enviar código al proyecto a través de SSH hasta que %{add_ssh_key_link} en su perfil"],"Your name":["Tu nombre"],"day":["día","días"],"new merge request":["nueva solicitud de fusión"],"notification emails":["correos electrónicos de notificación"],"parent":["padre","padres"]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/fr/app.js b/app/assets/javascripts/locale/fr/app.js deleted file mode 100644 index f9904ea61ea..00000000000 --- a/app/assets/javascripts/locale/fr/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['fr'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 20:38+0000","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-14 04:21-0400","Last-Translator":"Dremor ","Language-Team":"French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)","Language":"fr","Plural-Forms":"nplurals=2; plural=(n > 1);","X-Generator":"Zanata 3.9.6","lang":"fr","domain":"app","plural_forms":"nplurals=2; plural=(n > 1);"},"ByAuthor|by":["par"],"Commit":["Validation","Validations"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet."],"CycleAnalyticsStage|Code":["Code"],"CycleAnalyticsStage|Issue":["Incident"],"CycleAnalyticsStage|Plan":["Planification"],"CycleAnalyticsStage|Production":["Production"],"CycleAnalyticsStage|Review":["Examen"],"CycleAnalyticsStage|Staging":["Pré-production"],"CycleAnalyticsStage|Test":["Test"],"Deploy":["Déploiement","Déploiements"],"FirstPushedBy|First":["En premier"],"FirstPushedBy|pushed by":["poussé par"],"From issue creation until deploy to production":["Depuis la création de l'incident jusqu'au déploiement en production"],"From merge request merge until deploy to production":["Depuis la fusion de la demande de fusion jusqu'au déploiement en production"],"Introducing Cycle Analytics":["Introduction à l'analyseur de cycle"],"Last %d day":["Le dernier %d jour","Les derniers %d jours"],"Limited to showing %d event at most":["Limiter l'affichage au plus à %d évènement","Limiter l'affichage au plus à %d évènements"],"Median":["Médian"],"New Issue":["Nouvel incident","Nouveaux incidents"],"Not available":["Indisponible"],"Not enough data":["Données insuffisantes"],"OpenedNDaysAgo|Opened":["Ouvert"],"Pipeline Health":["Santé du Pipeline"],"ProjectLifecycle|Stage":["Étape"],"Read more":["Lire plus"],"Related Commits":["Validations liés"],"Related Deployed Jobs":["Tâches de déploiement liés"],"Related Issues":["Incidents liés"],"Related Jobs":["Tâches liées"],"Related Merge Requests":["Demandes de fusion liées"],"Related Merged Requests":["Demandes fusionnées liées"],"Showing %d event":["Affichage de %d évènement","Affichage de %d évènements"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."],"The collection of events added to the data gathered for that stage.":["L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape."],"The phase of the development lifecycle.":["Les étapes du cycle de développement."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera."],"The time taken by each data entry gathered by that stage.":["Le temps pris par chaque entrée récoltée durant cette étape."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["La valeur située au point médian d’une série de valeur observée. C.à.d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."],"Time before an issue gets scheduled":["Temps avant qu’un incident ne soit planifié"],"Time before an issue starts implementation":["Temps avant que résolution ne débute"],"Time between merge request creation and merge/close":["Temps entre la création d'une demande de fusion et sa fusion/clôture"],"Time until first merge request":["Temps jusqu’à la première demande de fusion"],"Time|hr":["hr","hrs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Temps total"],"Total test time for all commits/merges":["Temps total de test pour toutes les validations/fusions"],"Want to see the data? Please ask an administrator for access.":["Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès."],"We don't have enough data to show this stage.":["Nous n'avons pas suffisamment de données pour afficher cette étape."],"You need permission.":["Vous avez besoin d’une autorisation."],"day":["jour","jours"],"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} a validé %{commit_timeago}"],"About auto deploy":["A propos de l'auto-déploiement"],"Active":["Actif"],"Activity":["Activité"],"Add Changelog":["Ajouter un journal des modifications"],"Add Contribution guide":["Ajouter un guide de contribution"],"Add License":["Ajouter une licence"],"Add an SSH key to your profile to pull or push via SSH.":["Ajoutez une clef SSH à votre profil pour pouvoir récupérer et pousser par SSH."],"Add new directory":["Ajouter un nouveau dossier"],"Archived project! Repository is read-only":["Projet archivé ! Le dépôt est en lecture seule"],"Are you sure you want to delete this pipeline schedule?":["Êtes-vous sûr de vouloir supprimer ce pipeline programmé"],"Attach a file by drag & drop or %{upload_link}":["Attachez un fichier par glisser & déposer ou %{upload_link}"],"Branch":["Branche","Branches"],"#~ \"Branch %{branch_name} was created. To set up auto deploy, cho\"#~ \"ose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_do\"#~ \"c}\"":["#~ \"La branche %{branch_name} a été crée. Pour mettre en place le\"#~ \" déploiement automatisé, sélectionnez un modèle de fichier Yaml pour Gitlab CI\"#~ \", et validez les modifications. %{link_to_autodeploy_doc}\""],"Branches":["Branches"],"Browse files":["Parcourir les fichiers"],"CI configuration":["Configuration du CI"],"Cancel":["Annuler"],"ChangeTypeActionLabel|Pick into branch":["Sélectionner dans la branche"],"ChangeTypeActionLabel|Revert in branch":["Annuler dans la branche"],"ChangeTypeAction|Cherry-pick":["Sélectionner"],"ChangeType|commit":["validation"],"ChangeType|merge request":["demande de fusion"],"Changelog":["Journal des modifications"],"Charts":["Graphiques"],"Cherry-pick this commit":["Sélectionner cette validation"],"Cherry-pick this merge-request":["Sélectionner cette demande de fusion"],"CiStatusLabel|canceled":["annulé"],"CiStatusLabel|created":["créé"],"CiStatusLabel|failed":["échoué"],"CiStatusLabel|manual action":["action manuelle"],"CiStatusLabel|passed":["passé"],"CiStatusLabel|passed with warnings":["passé avec des avertissements"],"CiStatusLabel|pending":["en attente"],"CiStatusLabel|skipped":["ignoré"],"CiStatusLabel|waiting for manual action":["en attente d'action manuelle"],"CiStatusText|blocked":["bloqué"],"CiStatusText|canceled":["annulé "],"CiStatusText|created":["créé"],"CiStatusText|failed":["échoué"],"CiStatusText|manual":["manuel"],"CiStatusText|passed":["passé"],"CiStatusText|pending":["en attente"],"CiStatusText|skipped":["ignoré"],"CiStatus|running":["en cours"],"Commit message":["Message de validation"],"CommitMessage|Add %{file_name}":["Ajout de %{file_name}"],"Commits":["Validations"],"Commits|History":["Historique"],"Committed by":["Validé par"],"Compare":["Comparer"],"Contribution guide":["Guilde de contribution"],"Contributors":["Contributeurs"],"Copy URL to clipboard":["Copier l'URL dans le presse-papier"],"Copy commit SHA to clipboard":["Copier le SAH de la validation"],"Create New Directory":["Créer un nouveau dossier"],"Create directory":["Créer un dossier"],"Create empty bare repository":["Créer un dépôt vide"],"Create merge request":["Créer une demande de fusion"],"Create new...":["Créer nouveau..."],"CreateNewFork|Fork":["Fork"],"CreateTag|Tag":["Étiquette"],"Cron Timezone":["Fuseau horaire de Cron"],"Cron syntax":["Syntaxe CRON"],"Custom":["Personnalisé"],"Custom notification events":["Événements de notification personnalisés"],"#~ \"Custom notification levels are the same as participating levels. With custom n\"#~ \"otification levels you will also receive notifications for select events. To f\"#~ \"ind out more, check out %{notification_link}.\"":["#~ \"Le niveau de notification Personnalisé est similaire au niveau Participation. \"#~ \"Il permet cependant également de recevoir des notifications pour des événement\"#~ \"s sélectionnés. Pour plus d’information, vous pouvez consulter %{notification_\"#~ \"link}.\""],"Cycle Analytics":["Analyseur de cycle"],"Define a custom pattern with cron syntax":["Définir un schéma personnalisé avec une syntaxe CRON"],"Delete":["Supprimer"],"Description":["Description"],"Directory name":["Nom du dossier"],"Don't show again":["Ne plus montrer"],"Download":["Télécharger"],"Download tar":["Télécharger tar"],"Download tar.bz2":["Télécharger tar.bz2"],"Download tar.gz":["Télécharger tar.gz"],"Download zip":["Télécharger zip"],"DownloadArtifacts|Download":["Télécharger"],"DownloadCommit|Email Patches":["Patch email"],"DownloadCommit|Plain Diff":["Diff simple"],"DownloadSource|Download":["Télécharger"],"Edit":["Éditer"],"Edit Pipeline Schedule %{id}":["Éditer le pipeline programmé %{id}"],"Every day (at 4:00am)":["Chaque jour (à 4:00 du matin)"],"Every month (on the 1st at 4:00am)":["Chaque mois (le 1er à 4:00 du matin)"],"Every week (Sundays at 4:00am)":["Chaque semaine (Dimanche à 4:00 du matin)"],"Failed to change the owner":["Échec du changement de propriétaire"],"Failed to remove the pipeline schedule":["Échec de la suppression du pipeline programmé"],"Files":["Fichiers"],"Find by path":["Rechercher par chemin"],"Find file":["Rechercher un fichier"],"Fork":["Fork","Forks"],"ForkedFromProjectPath|Forked from":["Forké depuis"],"Go to your fork":["Aller à votre fork"],"GoToYourFork|Fork":["Fork"],"Home":["Accueil"],"Housekeeping successfully started":["Maintenance démarrée avec succès"],"Import repository":["Importer un dépôt"],"Interval Pattern":["Schéma d’intervalle"],"LFSStatus|Disabled":["Désactivé"],"LFSStatus|Enabled":["Activé"],"Last Pipeline":["Dernier pipeline"],"Last Update":["Dernière mise à jour"],"Last commit":["Dernière validation"],"Learn more in the":["En apprendre plus dans le"],"Leave group":["Quitter le groupe"],"Leave project":["Quitter le projet"],"MissingSSHKeyWarningLink|add an SSH key":["ajouter un clef SSH"],"New Pipeline Schedule":["Nouveau pipeline programmé"],"New branch":["Nouvelle branche"],"New directory":["Nouveau dossier"],"New file":["Nouveau Fichier"],"New issue":["Nouvel incident"],"New merge request":["Nouvelle demande de fusion"],"New schedule":["Nouveau programme"],"New snippet":["Nouvel extrait de code"],"New tag":["Nouvelle étiquette"],"No repository":["Pas de dépôt"],"No schedules":["Aucun programme"],"Notification events":["Événement de notifications"],"NotificationEvent|Close issue":["Clore l'incident"],"NotificationEvent|Close merge request":["Clore la demande de fusion"],"NotificationEvent|Failed pipeline":["Pipeline échoué"],"NotificationEvent|Merge merge request":["Fusionner le demande de fusion"],"NotificationEvent|New issue":["Nouvel incident"],"NotificationEvent|New merge request":["Nouvelle demande de fusion"],"NotificationEvent|New note":["Nouvelle note"],"NotificationEvent|Reassign issue":["Réassigner l'incident"],"NotificationEvent|Reassign merge request":["Réassigner la demande de fusion"],"NotificationEvent|Reopen issue":["Ré-ouvrir l'incident"],"NotificationEvent|Successful pipeline":["Pipeline réussi"],"NotificationLevel|Custom":["Personnalisé"],"NotificationLevel|Disabled":["Désactivé"],"NotificationLevel|Global":["Global"],"NotificationLevel|On mention":["En cas de mention"],"NotificationLevel|Participate":["Participation"],"NotificationLevel|Watch":["Surveillé"],"OfSearchInADropdown|Filter":["Filtre"],"Options":["Options"],"Owner":["Propriétaire"],"Pipeline":["Pipeline"],"Pipeline Schedule":["Programmation de pipeline"],"Pipeline Schedules":["Programmations de pipeline"],"PipelineSchedules|Activated":["Activé"],"PipelineSchedules|Active":["Active"],"PipelineSchedules|All":["Tous"],"PipelineSchedules|Inactive":["Inactive"],"PipelineSchedules|Next Run":["Prochaine exécution"],"PipelineSchedules|None":["Aucune"],"PipelineSchedules|Provide a short description for this pipeline":["Indiquez une courte description"],"PipelineSchedules|Take ownership":["S’approprier"],"PipelineSchedules|Target":["Cible"],"Project '%{project_name}' queued for deletion.":["Projet '%{project_name}' en attente de suppression."],"Project '%{project_name}' was successfully created.":["Projet '%{project_name}' créé avec succès."],"Project '%{project_name}' was successfully updated.":["Projet '%{project_name}' mis à jour avec succès."],"Project '%{project_name}' will be deleted.":["Projet '%{project_name}' sera supprimé."],"Project access must be granted explicitly to each user.":["L’accès au projet doit être explicitement accordé à chaque utilisateur."],"Project export could not be deleted.":["L'export du projet n'a pas pu être supprimé."],"Project export has been deleted.":["L'export du projet a été supprimé."],"#~ \"Project export link has expired. Please generate a new export from your projec\"#~ \"t settings.\"":["#~ \"Le lien de l’export du projet a expiré. Merci de générer un nouvel export depu\"#~ \"is les paramètres du projet.\""],"Project export started. A download link will be sent by email.":["#~ \"L'export du projet a débuté. Un lien de téléchargement sera envoyé par courrie\"#~ \"l.\""],"Project home":["Accueil du projet"],"ProjectFeature|Disabled":["Désactivé"],"ProjectFeature|Everyone with access":["Toute personne ayant accès"],"ProjectFeature|Only team members":["Seulement les membres de l'équipe"],"ProjectFileTree|Name":["Nom"],"ProjectLastActivity|Never":["Jamais"],"ProjectNetworkGraph|Graph":["Graphique "],"Readme":["LisezMoi"],"RefSwitcher|Branches":["Branches"],"RefSwitcher|Tags":["Étiquettes"],"Remind later":["Me le rappeler ultérieurement"],"Remove project":["Supprimer le projet"],"Request Access":["Demander l'accès"],"Revert this commit":["Annuler cette validation"],"Revert this merge-request":["Annuler cette demande de fusion"],"Save pipeline schedule":["Sauvegarder le pipeline programmé"],"Schedule a new pipeline":["Programmer un nouveau pipeline"],"Scheduling Pipelines":["Programmer des pipelines"],"Search branches and tags":["Rechercher dans les branches et les étiquettes"],"Select Archive Format":["Sélectionnez le format de l'archive"],"Select a timezone":["Sélectionnez un fuseau horaire"],"Select target branch":["Sélectionnez une branche cible"],"Set a password on your account to pull or push via %{protocol}":["#~ \"Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser par\"#~ \" %{protocol}\""],"Set up CI":["Mettre en place le CI"],"Set up Koding":["Mettre en place Koding"],"Set up auto deploy":["Mettre en place l’auto-déploiement "],"SetPasswordToCloneLink|set a password":["définir un mot de passe"],"Source code":["Code source"],"StarProject|Star":["S'abonner"],"Start a new merge request with these changes":["Créer une nouvelle demande de fusion avec ces changements"],"Switch branch/tag":["Changer de branche / d'étiquette"],"Tag":["Étiquette","Étiquettes"],"Tags":["Étiquettes"],"Target Branch":["Branche cible"],"The fork relationship has been removed.":["La relation de fork a été supprimée."],"#~ \"The pipelines schedule runs pipelines in the future, repeatedly, for specific \"#~ \"branches or tags. Those scheduled pipelines will inherit limited project acces\"#~ \"s based on their associated user.\"":["#~ \"Les pipelines programmés exécutent des pipelines dans le futur, de façon répét\"#~ \"ée, pour les branches et étiquettes spécifiées. Ces pipelines programmés hérit\"#~ \"ent d’un accès partiel au projet basé sur l’utilisateur que leurs est associé.\""],"The project can be accessed by any logged in user.":["Votre projet peut être accédé par n’importe quel utilisateur authentifié"],"The project can be accessed without any authentication.":["Votre projet peut être accédé sans aucune authentification."],"The repository for this project does not exist.":["Le dépôt pour ce projet n'existe pas."],"#~ \"This means you can not push code until you create an empty repository or impor\"#~ \"t existing one.\"":["#~ \"Cela signifie que vous ne pouvez pas pousser du code tant que vous ne créez pa\"#~ \"s un dépôt vide, ou importez une dépôt existant.\""],"Timeago|%s days ago":["Il y a %s jours"],"Timeago|%s days remaining":["Il reste %s jours"],"Timeago|%s hours remaining":["Il reste %s heures"],"Timeago|%s minutes ago":["Il y a %s minutes"],"Timeago|%s minutes remaining":["Il reste %s minutes"],"Timeago|%s months ago":["Il y a %s mois"],"Timeago|%s months remaining":["Il reste %s mois"],"Timeago|%s seconds remaining":["Il reste %s secondes"],"Timeago|%s weeks ago":["Il y a %s semaines"],"Timeago|%s weeks remaining":["Il reste %s semaines"],"Timeago|%s years ago":["Il y a %s ans"],"Timeago|%s years remaining":["Il reste %s ans"],"Timeago|1 day remaining":["Il reste un jour"],"Timeago|1 hour remaining":["Il reste une heure"],"Timeago|1 minute remaining":["Il reste une minute"],"Timeago|1 month remaining":["Il reste un mois"],"Timeago|1 week remaining":["Il reste une semaine"],"Timeago|1 year remaining":["Il reste un an"],"Timeago|Past due":["En retard"],"Timeago|a day ago":["Il y a un jour"],"Timeago|a month ago":["Il y a un mois"],"Timeago|a week ago":["Il y a une semaine"],"Timeago|a while":["Il y a un moment"],"Timeago|a year ago":["Il y a un an"],"Timeago|about %s hours ago":["Il y a environ %s heures"],"Timeago|about a minute ago":["Il y a environ une minute"],"Timeago|about an hour ago":["Il y a environ une heure"],"Timeago|in %s days":["Dans %s jours"],"Timeago|in %s hours":["Dans %s heures"],"Timeago|in %s minutes":["Dans %s minutes"],"Timeago|in %s months":["Dans %s mois"],"Timeago|in %s seconds":["Dans %s secondes"],"Timeago|in %s weeks":["Dans %s semaines"],"Timeago|in %s years":["Dans %s années"],"Timeago|in 1 day":["Dans 1 jour"],"Timeago|in 1 hour":["Dans 1 heure"],"Timeago|in 1 minute":["Dans 1 minute"],"Timeago|in 1 month":["Dans 1 mois"],"Timeago|in 1 week":["Dans 1 semaine"],"Timeago|in 1 year":["Dans 1 an"],"Timeago|less than a minute ago":["il y a moins d'une minute"],"Unstar":["Se désabonner"],"Upload New File":["Téléverser un nouveau fichier"],"Upload file":["Téléverser un fichier"],"Use your global notification setting":["Utiliser vos paramètres de notification globaux"],"VisibilityLevel|Internal":["Interne"],"VisibilityLevel|Private":["Privé"],"VisibilityLevel|Public":["Publique"],"Withdraw Access Request":["Retirer la demande d'accès"],"#~ \"You are going to remove %{project_name_with_namespace}.\\n\"#~ \"Removed project CANNOT be restored!\\n\"#~ \"Are you ABSOLUTELY sure?\"":["#~ \"Vous êtes sur le point de supprimer %{project_name_with_namespace}.\\n\"#~ \"Les projets supprimés NE PEUVENT PAS être restaurés !\\n\"#~ \"Êtes vous ABSOLUMENT sûr ? \""],"#~ \"You are going to remove the fork relationship to source project %{forked_from_\"#~ \"project}. Are you ABSOLUTELY sure?\"":["#~ \"Vous allez supprimer la relation de fork avec le projet source %{forked_from_p\"#~ \"roject}. Êtes-vous VRAIMENT sûr.\""],"#~ \"You are going to transfer %{project_name_with_namespace} to another owner. Are\"#~ \" you ABSOLUTELY sure?\"":["#~ \"Vous allez transférer %{project_name_with_namespace} à un nouveau propriétaire\"#~ \". Êtes vous VRAIMENT sûr ?\""],"You can only add files when you are on a branch":["Vous ne pouvez ajouter de fichier que dans une branche"],"You must sign in to star a project":["Vous devez vous identifier pour vous abonner à un projet"],"You will not get any notifications via email":["Vous ne recevrez aucune notification par courriel"],"You will only receive notifications for the events you choose":["#~ \"Vous ne recevrez de notification que pour les évènements que vous aurez choisi\"#~ \"s\""],"You will only receive notifications for threads you have participated in":["#~ \"Vous ne recevrez de notification que pour les sujets auxquels vous avez partic\"#~ \"ipé\""],"You will receive notifications for any activity":["Vous recevrez des notifications pour n’importe quelles activités"],"You will receive notifications only for comments in which you were @mentioned":["#~ \"Vous ne recevrez de notifications que pour les commentaires où vous êtes @ment\"#~ \"ionné\""],"#~ \"You won't be able to pull or push project code via %{protocol} until you %{set\"#~ \"_password_link} on your account\"":["#~ \"Vous ne pourrez pas récupérer ou pousser de code par %{protocol} tant que vo\"#~ \"us n'aurez pas %{set_password_link} pour votre compte\""],"#~ \"You won't be able to pull or push project code via SSH until you %{add_ssh_key\"#~ \"_link} to your profile\"":["#~ \"Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous n’aur\"#~ \"ez pas %{add_ssh_key_link} dans votre profil\""],"Your name":["Votre nom"],"notification emails":["courriels de notification"],"parent":["parent","parents"],"pipeline schedules documentation":["documentation des pipeline programmés"],"with stage":["avec l'étape","avec les étapes"]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/pt_BR/app.js b/app/assets/javascripts/locale/pt_BR/app.js deleted file mode 100644 index f2eed3da064..00000000000 --- a/app/assets/javascripts/locale/pt_BR/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['pt_BR'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-05-04 19:24-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-05 03:29-0400","Last-Translator":"Alexandre Alencar ","Language-Team":"Portuguese (Brazil)","Language":"pt-BR","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"pt_BR","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"ByAuthor|by":["por"],"Commit":["Commit","Commits"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["A Análise de Ciclo fornece uma visão geral de quanto tempo uma ideia demora para ir para produção em seu projeto."],"CycleAnalyticsStage|Code":["Código"],"CycleAnalyticsStage|Issue":["Tarefa"],"CycleAnalyticsStage|Plan":["Plano"],"CycleAnalyticsStage|Production":["Produção"],"CycleAnalyticsStage|Review":["Revisão"],"CycleAnalyticsStage|Staging":["Homologação"],"CycleAnalyticsStage|Test":["Teste"],"Deploy":["Implantação","Implantações"],"FirstPushedBy|First":["Primeiro"],"FirstPushedBy|pushed by":["publicado por"],"From issue creation until deploy to production":["Da criação de tarefas até a implantação para a produção"],"From merge request merge until deploy to production":["Da incorporação do merge request até a implantação em produção"],"Introducing Cycle Analytics":["Apresentando a Análise de Ciclo"],"Last %d day":["Último %d dia","Últimos %d dias"],"Limited to showing %d event at most":["Limitado a mostrar %d evento no máximo","Limitado a mostrar %d eventos no máximo"],"Median":["Mediana"],"New Issue":["Nova Tarefa","Novas Tarefas"],"Not available":["Não disponível"],"Not enough data":["Dados insuficientes"],"OpenedNDaysAgo|Opened":["Aberto"],"Pipeline Health":["Saúde da Pipeline"],"ProjectLifecycle|Stage":["Etapa"],"Read more":["Ler mais"],"Related Commits":["Commits Relacionados"],"Related Deployed Jobs":["Jobs Relacionados Incorporados"],"Related Issues":["Tarefas Relacionadas"],"Related Jobs":["Jobs Relacionados"],"Related Merge Requests":["Merge Requests Relacionados"],"Related Merged Requests":["Merge Requests Relacionados"],"Showing %d event":["Mostrando %d evento","Mostrando %d eventos"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["O estágio de codificação mostra o tempo desde o primeiro commit até a criação do merge request. \\nOs dados serão automaticamente adicionados aqui uma vez que você tenha criado seu primeiro merge request."],"The collection of events added to the data gathered for that stage.":["A coleção de eventos adicionados aos dados coletados para esse estágio."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["O estágio em questão mostra o tempo que leva desde a criação de uma tarefa até a sua assinatura para um milestone, ou a sua adição para a lista no seu Painel de Tarefas. Comece a criar tarefas para ver dados para esta etapa."],"The phase of the development lifecycle.":["A fase do ciclo de vida do desenvolvimento."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["A fase de planejamento mostra o tempo do passo anterior até empurrar o seu primeiro commit. Este tempo será adicionado automaticamente assim que você realizar seu primeiro commit."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["O estágio de produção mostra o tempo total que leva entre criar uma tarefa e implantar o código na produção. Os dados serão adicionados automaticamente até que você complete todo o ciclo de produção."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["A etapa de revisão mostra o tempo de criação de um merge request até que o merge seja feito. Os dados serão automaticamente adicionados depois que você fizer seu primeiro merge request."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["O estágio de estágio mostra o tempo entre a fusão do MR e o código de implantação para o ambiente de produção. Os dados serão automaticamente adicionados depois de implantar na produção pela primeira vez."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["A fase de teste mostra o tempo que o GitLab CI leva para executar cada pipeline para o merge request relacionado. Os dados serão automaticamente adicionados após a conclusão do primeiro pipeline."],"The time taken by each data entry gathered by that stage.":["O tempo necessário para cada entrada de dados reunida por essa etapa."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["O valor situado no ponto médio de uma série de valores observados. Ex., entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5 + 7) / 2 = 6."],"Time before an issue gets scheduled":["Tempo até que uma tarefa seja planejada"],"Time before an issue starts implementation":["Tempo até que uma tarefa comece a ser implementada"],"Time between merge request creation and merge/close":["Tempo entre a criação do merge request e o merge/fechamento"],"Time until first merge request":["Tempo até o primeiro merge request"],"Time|hr":["h","hs"],"Time|min":["min","mins"],"Time|s":["s"],"Total Time":["Tempo Total"],"Total test time for all commits/merges":["Tempo de teste total para todos os commits/merges"],"Want to see the data? Please ask an administrator for access.":["Precisa visualizar os dados? Solicite acesso ao administrador."],"We don't have enough data to show this stage.":["Não temos dados suficientes para mostrar esta fase."],"You need permission.":["Você precisa de permissão."],"day":["dia","dias"]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/zh_CN/app.js b/app/assets/javascripts/locale/zh_CN/app.js deleted file mode 100644 index 9c28e4e4627..00000000000 --- a/app/assets/javascripts/locale/zh_CN/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['zh_CN'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 21:59-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-19 09:57-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (China) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-CN","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=1; plural=0","lang":"zh_CN","domain":"app","plural_forms":"nplurals=1; plural=0"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交于 %{commit_timeago}"],"About auto deploy":["关于自动部署"],"Active":["启用"],"Activity":["活动"],"Add Changelog":["添加更新日志"],"Add Contribution guide":["添加贡献指南"],"Add License":["添加许可证"],"Add an SSH key to your profile to pull or push via SSH.":["新建一个用于推送或拉取的 SSH 秘钥到账号中。"],"Add new directory":["添加目录"],"Archived project! Repository is read-only":["项目已归档!存储库为只读状态"],"Are you sure you want to delete this pipeline schedule?":["确定要删除此流水线计划吗?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此处或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["已创建分支 %{branch_name} 。如需设置自动部署, 请选择合适的 GitLab CI Yaml 模板并提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["浏览文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["选择分支"],"ChangeTypeActionLabel|Revert in branch":["还原分支"],"ChangeTypeAction|Cherry-pick":["优选"],"ChangeTypeAction|Revert":["还原"],"Changelog":["更新日志"],"Charts":["统计图"],"Cherry-pick this commit":["优选此提交"],"Cherry-pick this merge request":["优选此合并请求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已创建"],"CiStatusLabel|failed":["已失败"],"CiStatusLabel|manual action":["手动操作"],"CiStatusLabel|passed":["已通过"],"CiStatusLabel|passed with warnings":["已通过但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳过"],"CiStatusLabel|waiting for manual action":["等待手动操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已创建"],"CiStatusText|failed":["已失败"],"CiStatusText|manual":["手动操作"],"CiStatusText|passed":["已通过"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳过"],"CiStatus|running":["运行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitBoxTitle|Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["历史"],"Committed by":["提交者:"],"Compare":["比较"],"Contribution guide":["贡献指南"],"Contributors":["贡献者"],"Copy URL to clipboard":["复制 URL 到剪贴板"],"Copy commit SHA to clipboard":["复制提交 SHA 的值到剪贴板"],"Create New Directory":["创建新目录"],"Create directory":["创建目录"],"Create empty bare repository":["创建空的存储库"],"Create merge request":["创建合并请求"],"Create new...":["创建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["标签"],"Cron Timezone":["Cron 时区"],"Cron syntax":["Cron 语法"],"Custom notification events":["自定义通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定义通知级别继承自参与级别。使用自定义通知级别,您会收到参与级别及选定事件的通知。想了解更多信息,请查看 %{notification_link}."],"Cycle Analytics":["周期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["周期分析概述了项目从想法到产品实现的各阶段所需的时间。"],"CycleAnalyticsStage|Code":["编码"],"CycleAnalyticsStage|Issue":["议题"],"CycleAnalyticsStage|Plan":["计划"],"CycleAnalyticsStage|Production":["生产"],"CycleAnalyticsStage|Review":["评审"],"CycleAnalyticsStage|Staging":["预发布"],"CycleAnalyticsStage|Test":["测试"],"Define a custom pattern with cron syntax":["使用 Cron 语法定义自定义模式"],"Delete":["删除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目录名称"],"Don't show again":["不再显示"],"Download":["下载"],"Download tar":["下载 tar"],"Download tar.bz2":["下载 tar.bz2"],"Download tar.gz":["下载 tar.gz"],"Download zip":["下载 zip"],"DownloadArtifacts|Download":["下载"],"DownloadCommit|Email Patches":["电子邮件补丁"],"DownloadCommit|Plain Diff":["差异文件"],"DownloadSource|Download":["下载"],"Edit":["编辑"],"Edit Pipeline Schedule %{id}":["编辑 %{id} 流水线计划"],"Every day (at 4:00am)":["每日执行(凌晨 4 点)"],"Every month (on the 1st at 4:00am)":["每月执行(每月 1 日凌晨 4 点)"],"Every week (Sundays at 4:00am)":["每周执行(周日凌晨 4 点)"],"Failed to change the owner":["无法变更所有者"],"Failed to remove the pipeline schedule":["无法删除流水线计划"],"Files":["文件"],"Find by path":["按路径查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["从创建议题到部署至生产环境"],"From merge request merge until deploy to production":["从合并请求被合并后到部署至生产环境"],"Go to your fork":["跳转到派生项目"],"GoToYourFork|Fork":["跳转到派生项目"],"Home":["首页"],"Housekeeping successfully started":["已开始维护"],"Import repository":["导入存储库"],"Interval Pattern":["循环周期"],"Introducing Cycle Analytics":["周期分析简介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["启用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水线"],"Last Update":["最后更新"],"Last commit":["最后提交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["流水线计划文档"],"Leave group":["退出群组"],"Leave project":["退出项目"],"Limited to showing %d event at most":["最多显示 %d 个事件"],"Median":["中位数"],"MissingSSHKeyWarningLink|add an SSH key":["新建 SSH 公钥"],"New Issue":["新建议题"],"New Pipeline Schedule":["创建流水线计划"],"New branch":["新建分支"],"New directory":["新建目录"],"New file":["新建文件"],"New issue":["新建议题"],"New merge request":["新建合并请求"],"New schedule":["新建计划"],"New snippet":["新建代码片段"],"New tag":["新建标签"],"No repository":["没有存储库"],"No schedules":["没有计划"],"Not available":["数据不足"],"Not enough data":["数据不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["关闭议题"],"NotificationEvent|Close merge request":["关闭合并请求"],"NotificationEvent|Failed pipeline":["流水线失败"],"NotificationEvent|Merge merge request":["合并请求被合并"],"NotificationEvent|New issue":["新建议题"],"NotificationEvent|New merge request":["新建合并请求"],"NotificationEvent|New note":["新建评论"],"NotificationEvent|Reassign issue":["重新指派议题"],"NotificationEvent|Reassign merge request":["重新指派合并请求"],"NotificationEvent|Reopen issue":["重启议题"],"NotificationEvent|Successful pipeline":["流水线成功完成"],"NotificationLevel|Custom":["自定义"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["参与"],"NotificationLevel|Watch":["关注"],"OfSearchInADropdown|Filter":["筛选"],"OpenedNDaysAgo|Opened":["开始于"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水线"],"Pipeline Health":["流水线健康指标"],"Pipeline Schedule":["流水线计划"],"Pipeline Schedules":["流水线计划"],"PipelineSchedules|Activated":["是否启用"],"PipelineSchedules|Active":["已启用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未启用"],"PipelineSchedules|Next Run":["下次运行时间"],"PipelineSchedules|None":["无"],"PipelineSchedules|Provide a short description for this pipeline":["为此流水线提供简短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目标"],"PipelineSheduleIntervalPattern|Custom":["自定义"],"Pipeline|with stage":["于阶段"],"Pipeline|with stages":["于阶段"],"Project '%{project_name}' queued for deletion.":["项目 '%{project_name}' 已进入删除队列。"],"Project '%{project_name}' was successfully created.":["项目 '%{project_name}' 已创建成功。"],"Project '%{project_name}' was successfully updated.":["项目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["项目 '%{project_name}' 将被删除。"],"Project access must be granted explicitly to each user.":["项目访问权限必须明确授权给每个用户。"],"Project export could not be deleted.":["无法删除项目导出。"],"Project export has been deleted.":["项目导出已被删除。"],"Project export link has expired. Please generate a new export from your project settings.":["项目导出链接已过期。请从项目设置中重新生成项目导出。"],"Project export started. A download link will be sent by email.":["项目导出已开始。下载链接将通过电子邮件发送。"],"Project home":["项目首页"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何对项目有访问权的人"],"ProjectFeature|Only team members":["只限团队成员"],"ProjectFileTree|Name":["名称"],"ProjectLastActivity|Never":["从未"],"ProjectLifecycle|Stage":["阶段"],"ProjectNetworkGraph|Graph":["分支图"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["标签"],"Related Commits":["相关的提交"],"Related Deployed Jobs":["相关的部署作业"],"Related Issues":["相关的议题"],"Related Jobs":["相关的作业"],"Related Merge Requests":["相关的合并请求"],"Related Merged Requests":["相关已合并的合并请求"],"Remind later":["稍后提醒"],"Remove project":["删除项目"],"Request Access":["申请权限"],"Revert this commit":["还原此提交"],"Revert this merge request":["还原此合并请求"],"Save pipeline schedule":["保存流水线计划"],"Schedule a new pipeline":["新建流水线计划"],"Scheduling Pipelines":["流水线计划"],"Search branches and tags":["搜索分支和标签"],"Select Archive Format":["选择下载格式"],"Select a timezone":["选择时区"],"Select target branch":["选择目标分支"],"Set a password on your account to pull or push via %{protocol}":["为账号创建一个用于推送或拉取的 %{protocol} 密码。"],"Set up CI":["设置 CI"],"Set up Koding":["设置 Koding"],"Set up auto deploy":["设置自动部署"],"SetPasswordToCloneLink|set a password":["设置密码"],"Showing %d event":["显示 %d 个事件"],"Source code":["源代码"],"StarProject|Star":["星标"],"Start a %{new_merge_request} with these changes":["由此更改 %{new_merge_request}"],"Switch branch/tag":["切换分支/标签"],"Tag":["标签"],"Tags":["标签"],"Target Branch":["目标分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["编码阶段概述了从第一次提交到创建合并请求的时间。创建第一个合并请求后,数据将自动添加到此处。"],"The collection of events added to the data gathered for that stage.":["与该阶段相关的事件集合。"],"The fork relationship has been removed.":["派生关系已被删除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["议题阶段概述了从创建议题到将议题添加到里程碑或议题看板所花费的时间。创建第一个议题后,数据将自动添加到此处.。"],"The phase of the development lifecycle.":["项目生命周期中的各个阶段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水线计划会周期性重复运行指定分支或标签的流水线。这些流水线将根据其关联用户继承有限的项目访问权限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["计划阶段概述了从议题添加到日程到推送首次提交的时间。当首次推送提交后,数据将自动添加到此处。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生产阶段概述了从创建一个议题到将代码部署到生产环境的总时间。当完成想法到部署生产的循环,数据将自动添加到此处。"],"The project can be accessed by any logged in user.":["该项目允许已登录的用户访问。"],"The project can be accessed without any authentication.":["该项目允许任何人访问。"],"The repository for this project does not exist.":["此项目的存储库不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["评审阶段概述了从创建合并请求到被合并的时间。当创建第一个合并请求后,数据将自动添加到此处。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["预发布阶段概述了从合并请求被合并到部署至生产环境的总时间。首次部署到生产环境后,数据将自动添加到此处。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["测试阶段概述了 GitLab CI 为相关合并请求运行每个流水线所需的时间。当第一个流水线运行完成后,数据将自动添加到此处。"],"The time taken by each data entry gathered by that stage.":["该阶段每条数据所花的时间"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位数是一个数列中最中间的值。例如在 3、5、9 之间,中位数是 5。在 3、5、7、8 之间,中位数是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在创建一个空的存储库或导入现有存储库之前,将无法推送代码。"],"Time before an issue gets scheduled":["议题被列入日程表的时间"],"Time before an issue starts implementation":["开始进行编码前的时间"],"Time between merge request creation and merge/close":["从创建合并请求到被合并或关闭的时间"],"Time until first merge request":["创建第一个合并请求之前的时间"],"Timeago|%s days ago":[" %s 天前"],"Timeago|%s days remaining":["剩余 %s 天"],"Timeago|%s hours remaining":["剩余 %s 小时"],"Timeago|%s minutes ago":[" %s 分钟前"],"Timeago|%s minutes remaining":["剩余 %s 分钟"],"Timeago|%s months ago":[" %s 个月前"],"Timeago|%s months remaining":["剩余 %s 月"],"Timeago|%s seconds remaining":["剩余 %s 秒"],"Timeago|%s weeks ago":[" %s 星期前"],"Timeago|%s weeks remaining":["剩余 %s 星期"],"Timeago|%s years ago":[" %s 年前"],"Timeago|%s years remaining":["剩余 %s 年"],"Timeago|1 day remaining":["剩余 1 天"],"Timeago|1 hour remaining":["剩余 1 小时"],"Timeago|1 minute remaining":["剩余 1 分钟"],"Timeago|1 month remaining":["剩余 1 个月"],"Timeago|1 week remaining":["剩余 1 星期"],"Timeago|1 year remaining":["剩余 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":[" 1 天前"],"Timeago|a month ago":[" 1 个月前"],"Timeago|a week ago":[" 1 星期前"],"Timeago|a while":["刚刚"],"Timeago|a year ago":[" 1 年前"],"Timeago|about %s hours ago":["约 %s 小时前"],"Timeago|about a minute ago":["约 1 分钟前"],"Timeago|about an hour ago":["约 1 小时前"],"Timeago|in %s days":[" %s 天后"],"Timeago|in %s hours":[" %s 小时后"],"Timeago|in %s minutes":[" %s 分钟后"],"Timeago|in %s months":[" %s 个月后"],"Timeago|in %s seconds":[" %s 秒后"],"Timeago|in %s weeks":[" %s 星期后"],"Timeago|in %s years":[" %s 年后"],"Timeago|in 1 day":[" 1 天后"],"Timeago|in 1 hour":[" 1 小时后"],"Timeago|in 1 minute":[" 1 分钟后"],"Timeago|in 1 month":[" 1 月后"],"Timeago|in 1 week":[" 1 星期后"],"Timeago|in 1 year":[" 1 年后"],"Timeago|less than a minute ago":["不到 1 分钟前"],"Time|hr":["小时"],"Time|min":["分钟"],"Time|s":["秒"],"Total Time":["总时间"],"Total test time for all commits/merges":["所有提交和合并的总测试时间"],"Unstar":["取消星标"],"Upload New File":["上传新文件"],"Upload file":["上传文件"],"Use your global notification setting":["使用全局通知设置"],"VisibilityLevel|Internal":["内部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公开"],"Want to see the data? Please ask an administrator for access.":["权限不足。如需查看相关数据,请向管理员申请权限。"],"We don't have enough data to show this stage.":["该阶段的数据不足,无法显示。"],"Withdraw Access Request":["取消权限申请"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即将要删除 %{project_name_with_namespace}。\\n已删除的项目无法恢复!\\n确定继续吗?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即将删除与源项目 %{forked_from_project} 的派生关系。确定继续吗?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即将 %{project_name_with_namespace} 转移给另一个所有者。确定继续吗?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You have reached your project limit":["您已达到项目数量限制"],"You must sign in to star a project":["必须登录才能对项目加星标"],"You need permission.":["需要相关的权限。"],"You will not get any notifications via email":["不会收到任何通知邮件"],"You will only receive notifications for the events you choose":["只接收选择的事件通知"],"You will only receive notifications for threads you have participated in":["只接收参与的主题的通知"],"You will receive notifications for any activity":["接收所有活动的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收评论中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在账号中 %{set_password_link} 之前将无法通过 %{protocol} 拉取或推送代码。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在账号中 %{add_ssh_key_link} 之前将无法通过 SSH 拉取或推送代码。"],"Your name":["您的名字"],"day":["天"],"new merge request":["新建合并请求"],"notification emails":["通知邮件"],"parent":["父级"]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/zh_HK/app.js b/app/assets/javascripts/locale/zh_HK/app.js deleted file mode 100644 index 21227e79efa..00000000000 --- a/app/assets/javascripts/locale/zh_HK/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['zh_HK'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 21:59-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-19 09:57-0400","Last-Translator":"Huang Tao ","Language-Team":"Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)","Language":"zh-HK","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=1; plural=0","lang":"zh_HK","domain":"app","plural_forms":"nplurals=1; plural=0"},"%{commit_author_link} committed %{commit_timeago}":["由 %{commit_author_link} 提交於 %{commit_timeago}"],"About auto deploy":["關於自動部署"],"Active":["啟用"],"Activity":["活動"],"Add Changelog":["添加更新日誌"],"Add Contribution guide":["添加貢獻指南"],"Add License":["添加許可證"],"Add an SSH key to your profile to pull or push via SSH.":["新增壹個用於推送或拉取的 SSH 秘鑰到賬號中。"],"Add new directory":["添加新目錄"],"Archived project! Repository is read-only":["歸檔項目!存儲庫為只讀"],"Are you sure you want to delete this pipeline schedule?":["確定要刪除此流水線計劃嗎?"],"Attach a file by drag & drop or %{upload_link}":["拖放文件到此處或者 %{upload_link}"],"Branch":["分支"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"],"Branches":["分支"],"Browse files":["瀏覽文件"],"ByAuthor|by":["作者:"],"CI configuration":["CI 配置"],"Cancel":["取消"],"ChangeTypeActionLabel|Pick into branch":["挑選到分支"],"ChangeTypeActionLabel|Revert in branch":["還原分支"],"ChangeTypeAction|Cherry-pick":["優選"],"ChangeTypeAction|Revert":["還原"],"Changelog":["更新日誌"],"Charts":["統計圖"],"Cherry-pick this commit":["優選此提交"],"Cherry-pick this merge request":["優選此合併請求"],"CiStatusLabel|canceled":["已取消"],"CiStatusLabel|created":["已創建"],"CiStatusLabel|failed":["已失敗"],"CiStatusLabel|manual action":["手動操作"],"CiStatusLabel|passed":["已通過"],"CiStatusLabel|passed with warnings":["已通過但有警告"],"CiStatusLabel|pending":["等待中"],"CiStatusLabel|skipped":["已跳過"],"CiStatusLabel|waiting for manual action":["等待手動操作"],"CiStatusText|blocked":["已阻塞"],"CiStatusText|canceled":["已取消"],"CiStatusText|created":["已創建"],"CiStatusText|failed":["已失敗"],"CiStatusText|manual":["待手動"],"CiStatusText|passed":["已通過"],"CiStatusText|pending":["等待中"],"CiStatusText|skipped":["已跳過"],"CiStatus|running":["運行中"],"Commit":["提交"],"Commit message":["提交信息"],"CommitBoxTitle|Commit":["提交"],"CommitMessage|Add %{file_name}":["添加 %{file_name}"],"Commits":["提交"],"Commits|History":["歷史"],"Committed by":["提交者:"],"Compare":["比較"],"Contribution guide":["貢獻指南"],"Contributors":["貢獻者"],"Copy URL to clipboard":["複製URL到剪貼板"],"Copy commit SHA to clipboard":["複製提交 SHA 到剪貼板"],"Create New Directory":["創建新目錄"],"Create directory":["創建目錄"],"Create empty bare repository":["創建空的存儲庫"],"Create merge request":["創建合併請求"],"Create new...":["創建..."],"CreateNewFork|Fork":["派生"],"CreateTag|Tag":["標籤"],"Cron Timezone":["Cron 時區"],"Cron syntax":["Cron 語法"],"Custom notification events":["自定義通知事件"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["自定義通知級別繼承自參與級別。使用自定義通知級別,您會收到參與級別及選定事件的通知。想了解更多信息,請查看 %{notification_link}."],"Cycle Analytics":["週期分析"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了項目從想法到產品實現的各階段所需的時間。"],"CycleAnalyticsStage|Code":["編碼"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["生產"],"CycleAnalyticsStage|Review":["評審"],"CycleAnalyticsStage|Staging":["預發布"],"CycleAnalyticsStage|Test":["測試"],"Define a custom pattern with cron syntax":["使用 Cron 語法定義自定義模式"],"Delete":["刪除"],"Deploy":["部署"],"Description":["描述"],"Directory name":["目錄名稱"],"Don't show again":["不再顯示"],"Download":["下載"],"Download tar":["下載 tar"],"Download tar.bz2":["下載 tar.bz2"],"Download tar.gz":["下載 tar.gz"],"Download zip":["下載 zip"],"DownloadArtifacts|Download":["下載"],"DownloadCommit|Email Patches":["電子郵件補丁"],"DownloadCommit|Plain Diff":["差異文件"],"DownloadSource|Download":["下載"],"Edit":["編輯"],"Edit Pipeline Schedule %{id}":["編輯 %{id} 流水線計劃"],"Every day (at 4:00am)":["每日執行(淩晨 4 點)"],"Every month (on the 1st at 4:00am)":["每月執行(每月 1 日淩晨 4 點)"],"Every week (Sundays at 4:00am)":["每週執行(周日淩晨 4 點)"],"Failed to change the owner":["無法變更所有者"],"Failed to remove the pipeline schedule":["無法刪除流水線計劃"],"Files":["文件"],"Find by path":["按路徑查找"],"Find file":["查找文件"],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"Fork":["派生"],"ForkedFromProjectPath|Forked from":["派生自"],"From issue creation until deploy to production":["從創建議題到部署到生產環境"],"From merge request merge until deploy to production":["從合併請求的合併到部署至生產環境"],"Go to your fork":["跳轉到派生項目"],"GoToYourFork|Fork":["跳轉到派生項目"],"Home":["首頁"],"Housekeeping successfully started":["已開始維護"],"Import repository":["導入存儲庫"],"Interval Pattern":["循環週期"],"Introducing Cycle Analytics":["週期分析簡介"],"LFSStatus|Disabled":["停用"],"LFSStatus|Enabled":["啟用"],"Last %d day":["最近 %d 天"],"Last Pipeline":["最新流水線"],"Last Update":["最後更新"],"Last commit":["最後提交"],"Learn more in the":["了解更多"],"Learn more in the|pipeline schedules documentation":["流水線計劃文檔"],"Leave group":["退出群組"],"Leave project":["退出項目"],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"MissingSSHKeyWarningLink|add an SSH key":["添加壹個 SSH 公鑰"],"New Issue":["新建議題"],"New Pipeline Schedule":["創建流水線計劃"],"New branch":["新增分支"],"New directory":["新增目錄"],"New file":["新增文件"],"New issue":["新議題"],"New merge request":["新增合併請求"],"New schedule":["新增计划"],"New snippet":["新代碼片段"],"New tag":["新增標籤"],"No repository":["沒有存儲庫"],"No schedules":["沒有計劃"],"Not available":["不可用"],"Not enough data":["數據不足"],"Notification events":["通知事件"],"NotificationEvent|Close issue":["關閉議題"],"NotificationEvent|Close merge request":["關閉合併請求"],"NotificationEvent|Failed pipeline":["流水線失敗"],"NotificationEvent|Merge merge request":["合併請求被合併"],"NotificationEvent|New issue":["新增議題"],"NotificationEvent|New merge request":["新合併請求"],"NotificationEvent|New note":["新增評論"],"NotificationEvent|Reassign issue":["重新指派議題"],"NotificationEvent|Reassign merge request":["重新指派合併請求"],"NotificationEvent|Reopen issue":["重啟議題"],"NotificationEvent|Successful pipeline":["流水線成功完成"],"NotificationLevel|Custom":["自定義"],"NotificationLevel|Disabled":["停用"],"NotificationLevel|Global":["全局"],"NotificationLevel|On mention":["提及"],"NotificationLevel|Participate":["參與"],"NotificationLevel|Watch":["關注"],"OfSearchInADropdown|Filter":["篩選"],"OpenedNDaysAgo|Opened":["開始於"],"Options":["操作"],"Owner":["所有者"],"Pipeline":["流水線"],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":["流水線計劃"],"Pipeline Schedules":["流水線計劃"],"PipelineSchedules|Activated":["是否啟用"],"PipelineSchedules|Active":["已啟用"],"PipelineSchedules|All":["所有"],"PipelineSchedules|Inactive":["未啟用"],"PipelineSchedules|Next Run":["下次運行時間"],"PipelineSchedules|None":["無"],"PipelineSchedules|Provide a short description for this pipeline":["為此流水線提供簡短描述"],"PipelineSchedules|Take ownership":["取得所有者"],"PipelineSchedules|Target":["目標"],"PipelineSheduleIntervalPattern|Custom":["自定義"],"Pipeline|with stage":["於階段"],"Pipeline|with stages":["於階段"],"Project '%{project_name}' queued for deletion.":["項目 '%{project_name}' 已進入刪除隊列。"],"Project '%{project_name}' was successfully created.":["項目 '%{project_name}' 已創建成功。"],"Project '%{project_name}' was successfully updated.":["項目 '%{project_name}' 已更新完成。"],"Project '%{project_name}' will be deleted.":["項目 '%{project_name}' 將被刪除。"],"Project access must be granted explicitly to each user.":["項目訪問權限必須明確授權給每個用戶。"],"Project export could not be deleted.":["無法刪除項目導出。"],"Project export has been deleted.":["項目導出已被刪除。"],"Project export link has expired. Please generate a new export from your project settings.":["項目導出鏈接已過期。請從項目設置中重新生成項目導出。"],"Project export started. A download link will be sent by email.":["項目導出已開始。下載鏈接將通過電子郵件發送。"],"Project home":["項目首頁"],"ProjectFeature|Disabled":["停用"],"ProjectFeature|Everyone with access":["任何人都可訪問"],"ProjectFeature|Only team members":["只限團隊成員"],"ProjectFileTree|Name":["名稱"],"ProjectLastActivity|Never":["從未"],"ProjectLifecycle|Stage":["階段"],"ProjectNetworkGraph|Graph":["分支圖"],"Read more":["了解更多"],"Readme":["自述文件"],"RefSwitcher|Branches":["分支"],"RefSwitcher|Tags":["標籤"],"Related Commits":["相關的提交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的合併請求"],"Remind later":["稍後提醒"],"Remove project":["刪除項目"],"Request Access":["申請權限"],"Revert this commit":["還原此提交"],"Revert this merge request":["還原此合併請求"],"Save pipeline schedule":["保存流水線計劃"],"Schedule a new pipeline":["新建流水線計劃"],"Scheduling Pipelines":["流水線計劃"],"Search branches and tags":["搜索分支和標籤"],"Select Archive Format":["選擇下載格式"],"Select a timezone":["選擇時區"],"Select target branch":["選擇目標分支"],"Set a password on your account to pull or push via %{protocol}":["為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。"],"Set up CI":["設置 CI"],"Set up Koding":["設置 Koding"],"Set up auto deploy":["設置自動部署"],"SetPasswordToCloneLink|set a password":["設置密碼"],"Showing %d event":["顯示 %d 個事件"],"Source code":["源代碼"],"StarProject|Star":["星標"],"Start a %{new_merge_request} with these changes":["由此更改 %{new_merge_request}"],"Switch branch/tag":["切換分支/標籤"],"Tag":["標籤"],"Tags":["標籤"],"Target Branch":["目標分支"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["編碼階段概述了從第壹次提交到創建合併請求的時間。創建第壹個合併請求後,數據將自動添加到此處。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The fork relationship has been removed.":["派生關係已被刪除。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段概述了從創建議題到將議題添加到裏程碑或議題看板所花費的時間。創建第壹個議題後,數據將自動添加到此處.。"],"The phase of the development lifecycle.":["項目生命週期中的各個階段。"],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["流水線計劃會週期性重複運行指定分支或標籤的流水線。這些流水線將根據其關聯用戶繼承有限的項目訪問權限。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段概述了從議題添加到日程到推送首次提交的時間。當首次推送提交後,數據將自動添加到此處。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["生產階段概述了從創建議題到將代碼部署到生產環境的時間。當完成完整的想法到部署生產,數據將自動添加到此處。"],"The project can be accessed by any logged in user.":["該項目允許已登錄的用戶訪問。"],"The project can be accessed without any authentication.":["該項目允許任何人訪問。"],"The repository for this project does not exist.":["此項目的存儲庫不存在。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段概述了 GitLab CI 為相關合併請求運行每個流水線所需的時間。當第壹個流水線運行完成後,數據將自動添加到此處。"],"The time taken by each data entry gathered by that stage.":["該階段每條數據所花的時間"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是壹個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"This means you can not push code until you create an empty repository or import existing one.":["在創建壹個空的存儲庫或導入現有存儲庫之前,您將無法推送代碼。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["開始進行編碼前的時間"],"Time between merge request creation and merge/close":["從創建合併請求到被合併或關閉的時間"],"Time until first merge request":["創建第壹個合併請求之前的時間"],"Timeago|%s days ago":[" %s 天前"],"Timeago|%s days remaining":["剩餘 %s 天"],"Timeago|%s hours remaining":["剩餘 %s 小時"],"Timeago|%s minutes ago":[" %s 分鐘前"],"Timeago|%s minutes remaining":["剩餘 %s 分鐘"],"Timeago|%s months ago":[" %s 個月前"],"Timeago|%s months remaining":["剩餘 %s 月"],"Timeago|%s seconds remaining":["剩餘 %s 秒"],"Timeago|%s weeks ago":[" %s 星期前"],"Timeago|%s weeks remaining":["剩餘 %s 星期"],"Timeago|%s years ago":[" %s 年前"],"Timeago|%s years remaining":["剩餘 %s 年"],"Timeago|1 day remaining":["剩餘 1 天"],"Timeago|1 hour remaining":["剩餘 1 小時"],"Timeago|1 minute remaining":["剩餘 1 分鐘"],"Timeago|1 month remaining":["剩餘 1 個月"],"Timeago|1 week remaining":["剩餘 1 星期"],"Timeago|1 year remaining":["剩餘 1 年"],"Timeago|Past due":["逾期"],"Timeago|a day ago":[" 1 天前"],"Timeago|a month ago":[" 1 個月前"],"Timeago|a week ago":[" 1 星期前"],"Timeago|a while":[" 剛剛"],"Timeago|a year ago":[" 1 年前"],"Timeago|about %s hours ago":["約 %s 小時前"],"Timeago|about a minute ago":["約 1 分鐘前"],"Timeago|about an hour ago":["約 1 小時前"],"Timeago|in %s days":[" %s 天後"],"Timeago|in %s hours":[" %s 小時後"],"Timeago|in %s minutes":[" %s 分鐘後"],"Timeago|in %s months":[" %s 個月後"],"Timeago|in %s seconds":[" %s 秒後"],"Timeago|in %s weeks":[" %s 星期後"],"Timeago|in %s years":[" %s 年後"],"Timeago|in 1 day":[" 1 天後"],"Timeago|in 1 hour":[" 1 小時後"],"Timeago|in 1 minute":[" 1 分鐘後"],"Timeago|in 1 month":[" 1 月後"],"Timeago|in 1 week":[" 1 星期後"],"Timeago|in 1 year":[" 1 年後"],"Timeago|less than a minute ago":["不到 1 分鐘前"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有提交和合併的總測試時間"],"Unstar":["取消星標"],"Upload New File":["上傳新文件"],"Upload file":["上傳文件"],"Use your global notification setting":["使用全局通知設置"],"VisibilityLevel|Internal":["內部"],"VisibilityLevel|Private":["私有"],"VisibilityLevel|Public":["公開"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關數據,請向管理員申請權限。"],"We don't have enough data to show this stage.":["該階段的數據不足,無法顯示。"],"Withdraw Access Request":["取消權限申请"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["即將要刪除 %{project_name_with_namespace}。\\n已刪除的項目無法恢複!\\n確定繼續嗎?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["即將刪除與源項目 %{forked_from_project} 的派生關系。確定繼續嗎?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?"],"You can only add files when you are on a branch":["只能在分支上添加文件"],"You have reached your project limit":["您已達到項目數量限制"],"You must sign in to star a project":["必須登錄才能對項目加星標"],"You need permission.":["需要相關的權限。"],"You will not get any notifications via email":["不會收到任何通知郵件"],"You will only receive notifications for the events you choose":["只接收您選擇的事件通知"],"You will only receive notifications for threads you have participated in":["只接收您參與的主題的通知"],"You will receive notifications for any activity":["接收所有活動的通知"],"You will receive notifications only for comments in which you were @mentioned":["只接收評論中提及(@)您的通知"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["在賬號上 %{set_password_link} 之前將無法通過 %{protocol} 拉取或推送代碼。"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["在賬號中 %{add_ssh_key_link} 之前將無法通過 SSH 拉取或推送代碼。"],"Your name":["您的名字"],"day":["天"],"new merge request":["新建合併請求"],"notification emails":["通知郵件"],"parent":["父級"]}}}; \ No newline at end of file diff --git a/app/assets/javascripts/locale/zh_TW/app.js b/app/assets/javascripts/locale/zh_TW/app.js deleted file mode 100644 index f0fe1e31f18..00000000000 --- a/app/assets/javascripts/locale/zh_TW/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['zh_TW'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-04 19:24-0500","Last-Translator":"HuangTao , 2017","Language-Team":"Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/75177/zh_TW/)","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Language":"zh_TW","Plural-Forms":"nplurals=1; plural=0;","lang":"zh_TW","domain":"app","plural_forms":"nplurals=1; plural=0;"},"Are you sure you want to delete this pipeline schedule?":[""],"ByAuthor|by":["作者:"],"Cancel":[""],"Commit":["送交"],"Cron Timezone":[""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["週期分析概述了你的專案從想法到產品實現,各階段所需的時間。"],"CycleAnalyticsStage|Code":["程式開發"],"CycleAnalyticsStage|Issue":["議題"],"CycleAnalyticsStage|Plan":["計劃"],"CycleAnalyticsStage|Production":["上線"],"CycleAnalyticsStage|Review":["複閱"],"CycleAnalyticsStage|Staging":["預備"],"CycleAnalyticsStage|Test":["測試"],"Delete":[""],"Deploy":["部署"],"Description":[""],"Edit":[""],"Edit Pipeline Schedule %{id}":[""],"Failed to change the owner":[""],"Failed to remove the pipeline schedule":[""],"Filter":[""],"FirstPushedBy|First":["首次推送"],"FirstPushedBy|pushed by":["推送者:"],"From issue creation until deploy to production":["從議題建立至線上部署"],"From merge request merge until deploy to production":["從請求被合併後至線上部署"],"Interval Pattern":[""],"Introducing Cycle Analytics":["週期分析簡介"],"Last %d day":["最後 %d 天"],"Last Pipeline":[""],"Limited to showing %d event at most":["最多顯示 %d 個事件"],"Median":["中位數"],"New Issue":["新議題"],"New Pipeline Schedule":[""],"No schedules":[""],"Not available":["無法使用"],"Not enough data":["資料不足"],"OpenedNDaysAgo|Opened":["開始於"],"Owner":[""],"Pipeline Health":["流水線健康指標"],"Pipeline Schedule":[""],"Pipeline Schedules":[""],"PipelineSchedules|Activated":[""],"PipelineSchedules|Active":[""],"PipelineSchedules|All":[""],"PipelineSchedules|Inactive":[""],"PipelineSchedules|Next Run":[""],"PipelineSchedules|None":[""],"PipelineSchedules|Provide a short description for this pipeline":[""],"PipelineSchedules|Take ownership":[""],"PipelineSchedules|Target":[""],"ProjectLifecycle|Stage":["專案生命週期"],"Read more":["了解更多"],"Related Commits":["相關的送交"],"Related Deployed Jobs":["相關的部署作業"],"Related Issues":["相關的議題"],"Related Jobs":["相關的作業"],"Related Merge Requests":["相關的合併請求"],"Related Merged Requests":["相關已合併的請求"],"Save pipeline schedule":[""],"Schedule a new pipeline":[""],"Select a timezone":[""],"Select target branch":[""],"Showing %d event":["顯示 %d 個事件"],"Target Branch":[""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["程式開發階段顯示從第一次送交到建立合併請求的時間。建立第一個合併請求後,資料將自動填入。"],"The collection of events added to the data gathered for that stage.":["與該階段相關的事件。"],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["議題階段顯示從議題建立到設置里程碑、或將該議題加至議題看板的時間。建立第一個議題後,資料將自動填入。"],"The phase of the development lifecycle.":["專案開發生命週期的各個階段。"],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["計劃階段顯示從議題添加到日程後至推送第一個送交的時間。當第一次推送送交後,資料將自動填入。"],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["上線階段顯示從建立一個議題到部署程式至線上的總時間。當完成從想法到產品實現的循環後,資料將自動填入。"],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["複閱階段顯示從合併請求建立後至被合併的時間。當建立第一個合併請求後,資料將自動填入。"],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["預備階段顯示從合併請求被合併後至部署上線的時間。當第一次部署上線後,資料將自動填入。"],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["測試階段顯示相關合併請求的流水線所花的時間。當第一個流水線運作完畢後,資料將自動填入。"],"The time taken by each data entry gathered by that stage.":["每筆該階段相關資料所花的時間。"],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"],"Time before an issue gets scheduled":["議題被列入日程表的時間"],"Time before an issue starts implementation":["議題等待開始實作的時間"],"Time between merge request creation and merge/close":["合併請求被合併或是關閉的時間"],"Time until first merge request":["第一個合併請求被建立前的時間"],"Time|hr":["小時"],"Time|min":["分鐘"],"Time|s":["秒"],"Total Time":["總時間"],"Total test time for all commits/merges":["所有送交和合併的總測試時間"],"Want to see the data? Please ask an administrator for access.":["權限不足。如需查看相關資料,請向管理員申請權限。"],"We don't have enough data to show this stage.":["因該階段的資料不足而無法顯示相關資訊"],"You need permission.":["您需要相關的權限。"],"day":["天"]}}}; \ No newline at end of file -- cgit v1.2.1 From 7373bc0bcace219074dee30b1ed544abf30a8634 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 27 Jun 2017 09:17:02 +0100 Subject: updated gitlab-ci.yml to compile locale --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76a95ad6e0a..08aef3dd8ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -193,6 +193,7 @@ setup-test-env: script: - node --version - yarn install --pure-lockfile --cache-folder .yarn-cache + - bundle exec rake gettext:po_to_json - bundle exec rake gitlab:assets:compile - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' artifacts: @@ -433,6 +434,7 @@ gitlab:assets:compile: NO_COMPRESSION: "true" script: - yarn install --pure-lockfile --production --cache-folder .yarn-cache + - bundle exec rake gettext:po_to_json - bundle exec rake gitlab:assets:compile artifacts: name: webpack-report @@ -450,6 +452,7 @@ karma: BABEL_ENV: "coverage" CHROME_LOG_FILE: "chrome_debug.log" script: + - bundle exec rake gettext:po_to_json - bundle exec rake karma coverage: '/^Statements *: (\d+\.\d+%)/' artifacts: -- cgit v1.2.1 From 6afff7c6073f58caf590dee5dadd84996a69f1ef Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 27 Jun 2017 14:08:40 +0200 Subject: Fix head pipeline stored in merge request for external pipelines --- .../fix-head-pipeline-for-external-pipelines.md | 4 ++++ lib/api/commit_statuses.rb | 3 +++ spec/requests/api/commit_statuses_spec.rb | 27 +++++++++++++++++----- 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md diff --git a/changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md b/changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md new file mode 100644 index 00000000000..bf667464e60 --- /dev/null +++ b/changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md @@ -0,0 +1,4 @@ +--- +title: Fix head pipeline stored in merge request for external pipelines +merge_request: +author: diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 10f2d5ef6a3..485b680cd5f 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -108,6 +108,9 @@ module API render_api_error!('invalid state', 400) end + MergeRequest.where(source_project: @project, source_branch: ref) + .update_all(head_pipeline_id: pipeline) if pipeline.latest? + present status, with: Entities::CommitStatus rescue StateMachines::InvalidTransition => e render_api_error!(e.message, 400) diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index b8ca73c321c..cdb60fc0d1a 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -164,25 +164,40 @@ describe API::CommitStatuses do context 'with all optional parameters' do context 'when creating a commit status' do - it 'creates commit status' do + subject do post api(post_url, developer), { state: 'success', context: 'coverage', - ref: 'develop', + ref: 'master', description: 'test', coverage: 80.0, target_url: 'http://gitlab.com/status' } + end + + it 'creates commit status' do + subject expect(response).to have_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') - expect(json_response['ref']).to eq('develop') + expect(json_response['ref']).to eq('master') expect(json_response['coverage']).to eq(80.0) expect(json_response['description']).to eq('test') expect(json_response['target_url']).to eq('http://gitlab.com/status') end + + context 'when merge request exists for given branch' do + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'develop') } + + it 'sets head pipeline' do + subject + + expect(response).to have_http_status(201) + expect(merge_request.reload.head_pipeline).not_to be_nil + end + end end context 'when updatig a commit status' do @@ -190,7 +205,7 @@ describe API::CommitStatuses do post api(post_url, developer), { state: 'running', context: 'coverage', - ref: 'develop', + ref: 'master', description: 'coverage test', coverage: 0.0, target_url: 'http://gitlab.com/status' @@ -199,7 +214,7 @@ describe API::CommitStatuses do post api(post_url, developer), { state: 'success', name: 'coverage', - ref: 'develop', + ref: 'master', description: 'new description', coverage: 90.0 } @@ -210,7 +225,7 @@ describe API::CommitStatuses do expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') - expect(json_response['ref']).to eq('develop') + expect(json_response['ref']).to eq('master') expect(json_response['coverage']).to eq(90.0) expect(json_response['description']).to eq('new description') expect(json_response['target_url']).to eq('http://gitlab.com/status') -- cgit v1.2.1 From 913f5834a39817125f9b8c26bcf8a0325a97c653 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 27 Jun 2017 19:24:49 +0000 Subject: Remove unused data from code climate artifact --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76a95ad6e0a..abb49506828 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -473,6 +473,7 @@ codeclimate: script: - docker pull codeclimate/codeclimate - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json + - sed -i.bak 's/\"body\"\:\".*\"//' codeclimate.json artifacts: paths: [codeclimate.json] -- cgit v1.2.1 From 80d6e5bbd4bb4b2a418e3e81b45b59c01ca00b0c Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 6 Apr 2017 14:06:24 -0700 Subject: add a new DeclarativePolicy framework --- lib/declarative_policy.rb | 58 ++++++ lib/declarative_policy/base.rb | 329 ++++++++++++++++++++++++++++++ lib/declarative_policy/cache.rb | 32 +++ lib/declarative_policy/condition.rb | 102 +++++++++ lib/declarative_policy/dsl.rb | 103 ++++++++++ lib/declarative_policy/preferred_scope.rb | 28 +++ lib/declarative_policy/rule.rb | 301 +++++++++++++++++++++++++++ lib/declarative_policy/runner.rb | 181 ++++++++++++++++ lib/declarative_policy/step.rb | 86 ++++++++ 9 files changed, 1220 insertions(+) create mode 100644 lib/declarative_policy.rb create mode 100644 lib/declarative_policy/base.rb create mode 100644 lib/declarative_policy/cache.rb create mode 100644 lib/declarative_policy/condition.rb create mode 100644 lib/declarative_policy/dsl.rb create mode 100644 lib/declarative_policy/preferred_scope.rb create mode 100644 lib/declarative_policy/rule.rb create mode 100644 lib/declarative_policy/runner.rb create mode 100644 lib/declarative_policy/step.rb diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb new file mode 100644 index 00000000000..d9959bc1aff --- /dev/null +++ b/lib/declarative_policy.rb @@ -0,0 +1,58 @@ +require_dependency 'declarative_policy/cache' +require_dependency 'declarative_policy/condition' +require_dependency 'declarative_policy/dsl' +require_dependency 'declarative_policy/preferred_scope' +require_dependency 'declarative_policy/rule' +require_dependency 'declarative_policy/runner' +require_dependency 'declarative_policy/step' + +require_dependency 'declarative_policy/base' + +module DeclarativePolicy + class << self + def policy_for(user, subject, opts = {}) + cache = opts[:cache] || {} + key = Cache.policy_key(user, subject) + + cache[key] ||= class_for(subject).new(user, subject, opts) + end + + def class_for(subject) + return GlobalPolicy if subject == :global + return NilPolicy if subject.nil? + + subject = find_delegate(subject) + + subject.class.ancestors.each do |klass| + next unless klass.name + + begin + policy_class = "#{klass.name}Policy".constantize + + # NOTE: the < operator here tests whether policy_class + # inherits from Base. We can't use #is_a? because that + # tests for *instances*, not *subclasses*. + return policy_class if policy_class < Base + rescue NameError + nil + end + end + + raise "no policy for #{subject.class.name}" + end + + private + + def find_delegate(subject) + seen = Set.new + + while subject.respond_to?(:declarative_policy_delegate) + raise ArgumentError, "circular delegations" if seen.include?(subject.object_id) + seen << subject.object_id + subject = subject.declarative_policy_delegate + end + + subject + end + end +end diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb new file mode 100644 index 00000000000..df94cafb6a1 --- /dev/null +++ b/lib/declarative_policy/base.rb @@ -0,0 +1,329 @@ +module DeclarativePolicy + class Base + # A map of ability => list of rules together with :enable + # or :prevent actions. Used to look up which rules apply to + # a given ability. See Base.ability_map + class AbilityMap + attr_reader :map + def initialize(map = {}) + @map = map + end + + # This merge behavior is different than regular hashes - if both + # share a key, the values at that key are concatenated, rather than + # overridden. + def merge(other) + conflict_proc = proc { |key, my_val, other_val| my_val + other_val } + AbilityMap.new(@map.merge(other.map, &conflict_proc)) + end + + def actions(key) + @map[key] ||= [] + end + + def enable(key, rule) + actions(key) << [:enable, rule] + end + + def prevent(key, rule) + actions(key) << [:prevent, rule] + end + end + + class << self + # The `own_ability_map` vs `ability_map` distinction is used so that + # the data structure is properly inherited - with subclasses recursively + # merging their parent class. + # + # This pattern is also used for conditions, global_actions, and delegations. + def ability_map + if self == Base + own_ability_map + else + superclass.ability_map.merge(own_ability_map) + end + end + + def own_ability_map + @own_ability_map ||= AbilityMap.new + end + + # an inheritable map of conditions, by name + def conditions + if self == Base + own_conditions + else + superclass.conditions.merge(own_conditions) + end + end + + def own_conditions + @own_conditions ||= {} + end + + # a list of global actions, generated by `prevent_all`. these aren't + # stored in `ability_map` because they aren't indexed by a particular + # ability. + def global_actions + if self == Base + own_global_actions + else + superclass.global_actions + own_global_actions + end + end + + def own_global_actions + @own_global_actions ||= [] + end + + # an inheritable map of delegations, indexed by name (which may be + # autogenerated) + def delegations + if self == Base + own_delegations + else + superclass.delegations.merge(own_delegations) + end + end + + def own_delegations + @own_delegations ||= {} + end + + # all the [rule, action] pairs that apply to a particular ability. + # we combine the specific ones looked up in ability_map with the global + # ones. + def configuration_for(ability) + ability_map.actions(ability) + global_actions + end + + ### declaration methods ### + + def delegate(name = nil, &delegation_block) + if name.nil? + @delegate_name_counter ||= 0 + @delegate_name_counter += 1 + name = :"anonymous_#{@delegate_name_counter}" + end + + name = name.to_sym + + if delegation_block.nil? + delegation_block = proc { @subject.__send__(name) } + end + + own_delegations[name] = delegation_block + end + + # Declares a rule, constructed using RuleDsl, and returns + # a PolicyDsl which is used for registering the rule with + # this class. PolicyDsl will call back into Base.enable_when, + # Base.prevent_when, and Base.prevent_all_when. + def rule(&b) + rule = RuleDsl.new(self).instance_eval(&b) + PolicyDsl.new(self, rule) + end + + # A hash in which to store calls to `desc` and `with_scope`, etc. + def last_options + @last_options ||= {}.with_indifferent_access + end + + # retrieve and zero out the previously set options (used in .condition) + def last_options! + last_options.tap { @last_options = nil } + end + + # Declare a description for the following condition. Currently unused, + # but opens the potential for explaining to users why they were or were + # not able to do something. + def desc(description) + last_options[:description] = description + end + + def with_options(opts = {}) + last_options.merge!(opts) + end + + def with_scope(scope) + with_options scope: scope + end + + def with_score(score) + with_options score: score + end + + # Declares a condition. It gets stored in `own_conditions`, and generates + # a query method based on the condition's name. + def condition(name, opts = {}, &value) + name = name.to_sym + + opts = last_options!.merge(opts) + opts[:context_key] ||= self.name + + condition = Condition.new(name, opts, &value) + + self.own_conditions[name] = condition + + define_method(:"#{name}?") { condition(name).pass? } + end + + # These next three methods are mainly called from PolicyDsl, + # and are responsible for "inverting" the relationship between + # an ability and a rule. We store in `ability_map` a map of + # abilities to rules that affect them, together with a + # symbol indicating :prevent or :enable. + def enable_when(abilities, rule) + abilities.each { |a| own_ability_map.enable(a, rule) } + end + + def prevent_when(abilities, rule) + abilities.each { |a| own_ability_map.prevent(a, rule) } + end + + # we store global prevents (from `prevent_all`) separately, + # so that they can be combined into every decision made. + def prevent_all_when(rule) + own_global_actions << [:prevent, rule] + end + end + + # A policy object contains a specific user and subject on which + # to compute abilities. For this reason it's sometimes called + # "context" within the framework. + # + # It also stores a reference to the cache, so it can be used + # to cache computations by e.g. ManifestCondition. + attr_reader :user, :subject, :cache + def initialize(user, subject, opts = {}) + @user = user + @subject = subject + @cache = opts[:cache] || {} + end + + # helper for checking abilities on this and other subjects + # for the current user. + def can?(ability, new_subject = :_self) + return allowed?(ability) if new_subject == :_self + + policy_for(new_subject).allowed?(ability) + end + + # This is the main entry point for permission checks. It constructs + # or looks up a Runner for the given ability and asks it if it passes. + def allowed?(*abilities) + abilities.all? { |a| runner(a).pass? } + end + + # The inverse of #allowed?, used mainly in specs. + def disallowed?(*abilities) + abilities.all? { |a| !runner(a).pass? } + end + + # computes the given ability and prints a helpful debugging output + # showing which + def debug(ability, *a) + runner(ability).debug(*a) + end + + desc "Unknown user" + condition(:anonymous, scope: :user, score: 0) { @user.nil? } + + desc "By default" + condition(:default, scope: :global, score: 0) { true } + + def repr + subject_repr = + if @subject.respond_to?(:id) + "#{@subject.class.name}/#{@subject.id}" + else + @subject.inspect + end + + user_repr = + if @user + @user.to_reference + else + "" + end + + "(#{user_repr} : #{subject_repr})" + end + + def inspect + "#<#{self.class.name} #{repr}>" + end + + # returns a Runner for the given ability, capable of computing whether + # the ability is allowed. Runners are cached on the policy (which itself + # is cached on @cache), and caches its result. This is how we perform caching + # at the ability level. + def runner(ability) + ability = ability.to_sym + @runners ||= {} + @runners[ability] ||= + begin + delegated_runners = delegated_policies.values.compact.map { |p| p.runner(ability) } + own_runner = Runner.new(own_steps(ability)) + delegated_runners.inject(own_runner, &:merge_runner) + end + end + + # Helpers for caching. Used by ManifestCondition in performing condition + # computation. + # + # NOTE we can't use ||= here because the value might be the + # boolean `false` + def cache(key, &b) + return @cache[key] if cached?(key) + @cache[key] = yield + end + + def cached?(key) + !@cache[key].nil? + end + + # returns a ManifestCondition capable of computing itself. The computation + # will use our own @cache. + def condition(name) + name = name.to_sym + @_conditions ||= {} + @_conditions[name] ||= + begin + raise "invalid condition #{name}" unless self.class.conditions.key?(name) + ManifestCondition.new(self.class.conditions[name], self) + end + end + + # used in specs - returns true if there is no possible way for any action + # to be allowed, determined only by the global :prevent_all rules. + def banned? + global_steps = self.class.global_actions.map { |(action, rule)| Step.new(self, rule, action) } + !Runner.new(global_steps).pass? + end + + # A list of other policies that we've delegated to (see `Base.delegate`) + def delegated_policies + @delegated_policies ||= self.class.delegations.transform_values do |block| + new_subject = instance_eval(&block) + + # never delegate to nil, as that would immediately prevent_all + next if new_subject.nil? + + policy_for(new_subject) + end + end + + def policy_for(other_subject) + DeclarativePolicy.policy_for(@user, other_subject, cache: @cache) + end + + protected + + # constructs steps that come from this policy and not from any delegations + def own_steps(ability) + rules = self.class.configuration_for(ability) + rules.map { |(action, rule)| Step.new(self, rule, action) } + end + end +end diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb new file mode 100644 index 00000000000..b8cc60074c7 --- /dev/null +++ b/lib/declarative_policy/cache.rb @@ -0,0 +1,32 @@ +module DeclarativePolicy + module Cache + class << self + def user_key(user) + return '' if user.nil? + id_for(user) + end + + def policy_key(user, subject) + u = user_key(user) + s = subject_key(subject) + "/dp/policy/#{u}/#{s}" + end + + def subject_key(subject) + return '' if subject.nil? + return subject.inspect if subject.is_a?(Symbol) + "#{subject.class.name}:#{id_for(subject)}" + end + + private + + def id_for(obj) + if obj.respond_to?(:id) && obj.id + obj.id.to_s + else + "##{obj.object_id}" + end + end + end + end +end diff --git a/lib/declarative_policy/condition.rb b/lib/declarative_policy/condition.rb new file mode 100644 index 00000000000..9d7cf6b9726 --- /dev/null +++ b/lib/declarative_policy/condition.rb @@ -0,0 +1,102 @@ +module DeclarativePolicy + # A Condition is the data structure that is created by the + # `condition` declaration on DeclarativePolicy::Base. It is + # more or less just a struct of the data passed to that + # declaration. It holds on to the block to be instance_eval'd + # on a context (instance of Base) later, via #compute. + class Condition + attr_reader :name, :description, :scope + attr_reader :manual_score + attr_reader :context_key + def initialize(name, opts = {}, &compute) + @name = name + @compute = compute + @scope = opts.fetch(:scope, :normal) + @description = opts.delete(:description) + @context_key = opts[:context_key] + @manual_score = opts.fetch(:score, nil) + end + + def compute(context) + !!context.instance_eval(&@compute) + end + + def key + "#{@context_key}/#{@name}" + end + end + + # In contrast to a Condition, a ManifestCondition contains + # a Condition and a context object, and is capable of calculating + # a result itself. This is the return value of Base#condition. + class ManifestCondition + def initialize(condition, context) + @condition = condition + @context = context + end + + # The main entry point - does this condition pass? We reach into + # the context's cache here so that we can share in the global + # cache (often RequestStore or similar). + def pass? + @context.cache(cache_key) { @condition.compute(@context) } + end + + # Whether we've already computed this condition. + def cached? + @context.cached?(cache_key) + end + + # This is used to score Rule::Condition. See Rule::Condition#score + # and Runner#steps_by_score for how scores are used. + # + # The number here is intended to represent, abstractly, how + # expensive it would be to calculate this condition. + # + # See #cache_key for info about @condition.scope. + def score + # If we've been cached, no computation is necessary. + return 0 if cached? + + # Use the override from condition(score: ...) if present + return @condition.manual_score if @condition.manual_score + + # Global scope rules are cheap due to max cache sharing + return 2 if @condition.scope == :global + + # "Normal" rules can't share caches with any other policies + return 16 if @condition.scope == :normal + + # otherwise, we're :user or :subject scope, so it's 4 if + # the caller has declared a preference + return 4 if @condition.scope == DeclarativePolicy.preferred_scope + + # and 8 for all other :user or :subject scope conditions. + 8 + end + + private + + # This method controls the caching for the condition. This is where + # the condition(scope: ...) option comes into play. Notice that + # depending on the scope, we may cache only by the user or only by + # the subject, resulting in sharing across different policy objects. + def cache_key + case @condition.scope + when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}" + when :user then "/dp/condition/#{@condition.key}/#{user_key}" + when :subject then "/dp/condition/#{@condition.key}/#{subject_key}" + when :global then "/dp/condition/#{@condition.key}" + else raise 'invalid scope' + end + end + + def user_key + Cache.user_key(@context.user) + end + + def subject_key + Cache.subject_key(@context.subject) + end + end +end diff --git a/lib/declarative_policy/dsl.rb b/lib/declarative_policy/dsl.rb new file mode 100644 index 00000000000..b26807a7622 --- /dev/null +++ b/lib/declarative_policy/dsl.rb @@ -0,0 +1,103 @@ +module DeclarativePolicy + # The DSL evaluation context inside rule { ... } blocks. + # Responsible for creating and combining Rule objects. + # + # See Base.rule + class RuleDsl + def initialize(context_class) + @context_class = context_class + end + + def can?(ability) + Rule::Ability.new(ability) + end + + def all?(*rules) + Rule::And.make(rules) + end + + def any?(*rules) + Rule::Or.make(rules) + end + + def none?(*rules) + ~Rule::Or.new(rules) + end + + def cond(condition) + Rule::Condition.new(condition) + end + + def delegate(delegate_name, condition) + Rule::DelegatedCondition.new(delegate_name, condition) + end + + def method_missing(m, *a, &b) + return super unless a.size == 0 && !block_given? + + if @context_class.delegations.key?(m) + DelegateDsl.new(self, m) + else + cond(m.to_sym) + end + end + end + + # Used when the name of a delegate is mentioned in + # the rule DSL. + class DelegateDsl + def initialize(rule_dsl, delegate_name) + @rule_dsl = rule_dsl + @delegate_name = delegate_name + end + + def method_missing(m, *a, &b) + return super unless a.size == 0 && !block_given? + + @rule_dsl.delegate(@delegate_name, m) + end + end + + # The return value of a rule { ... } declaration. + # Can call back to register rules with the containing + # Policy class (context_class here). See Base.rule + # + # Note that the #policy method just performs an #instance_eval, + # which is useful for multiple #enable or #prevent callse. + # + # Also provides a #method_missing proxy to the context + # class's class methods, so that helper methods can be + # defined and used in a #policy { ... } block. + class PolicyDsl + def initialize(context_class, rule) + @context_class = context_class + @rule = rule + end + + def policy(&b) + instance_eval(&b) + end + + def enable(*abilities) + @context_class.enable_when(abilities, @rule) + end + + def prevent(*abilities) + @context_class.prevent_when(abilities, @rule) + end + + def prevent_all + @context_class.prevent_all_when(@rule) + end + + def method_missing(m, *a, &b) + return super unless @context_class.respond_to?(m) + + @context_class.__send__(m, *a, &b) + end + + def respond_to_missing?(m) + @context_class.respond_to?(m) || super + end + end +end diff --git a/lib/declarative_policy/preferred_scope.rb b/lib/declarative_policy/preferred_scope.rb new file mode 100644 index 00000000000..b0754098149 --- /dev/null +++ b/lib/declarative_policy/preferred_scope.rb @@ -0,0 +1,28 @@ +module DeclarativePolicy + PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope" + + class << self + def with_preferred_scope(scope, &b) + Thread.current[PREFERRED_SCOPE_KEY], old_scope = scope, Thread.current[PREFERRED_SCOPE_KEY] + yield + ensure + Thread.current[PREFERRED_SCOPE_KEY] = old_scope + end + + def preferred_scope + Thread.current[PREFERRED_SCOPE_KEY] + end + + def user_scope(&b) + with_preferred_scope(:user, &b) + end + + def subject_scope(&b) + with_preferred_scope(:subject, &b) + end + + def preferred_scope=(scope) + Thread.current[PREFERRED_SCOPE_KEY] = scope + end + end +end diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb new file mode 100644 index 00000000000..bfcec241489 --- /dev/null +++ b/lib/declarative_policy/rule.rb @@ -0,0 +1,301 @@ +module DeclarativePolicy + module Rule + # A Rule is the object that results from the `rule` declaration, + # usually built using the DSL in `RuleDsl`. It is a basic logical + # combination of building blocks, and is capable of deciding, + # given a context (instance of DeclarativePolicy::Base) whether it + # passes or not. Note that this decision doesn't by itself know + # how that affects the actual ability decision - for that, a + # `Step` is used. + class Base + def self.make(*a) + new(*a).simplify + end + + # true or false whether this rule passes. + # `context` is a policy - an instance of + # DeclarativePolicy::Base. + def pass?(context) + raise 'abstract' + end + + # same as #pass? except refuses to do any I/O, + # returning nil if the result is not yet cached. + # used for accurately scoring And/Or + def cached_pass?(context) + raise 'abstract' + end + + # abstractly, how long would it take to compute + # this rule? lower-scored rules are tried first. + def score(context) + raise 'abstract' + end + + # unwrap double negatives and nested and/or + def simplify + self + end + + # convenience combination methods + def or(other) + Or.make([self, other]) + end + + def and(other) + And.make([self, other]) + end + + def negate + Not.make(self) + end + + alias_method :|, :or + alias_method :&, :and + alias_method :~@, :negate + + def inspect + "#" + end + end + + # A rule that checks a condition. This is the + # type of rule that results from a basic bareword + # in the rule dsl (see RuleDsl#method_missing). + class Condition < Base + def initialize(name) + @name = name + end + + # we delegate scoring to the condition. See + # ManifestCondition#score. + def score(context) + context.condition(@name).score + end + + # Let the ManifestCondition from the context + # decide whether we pass. + def pass?(context) + context.condition(@name).pass? + end + + # returns nil unless it's already cached + def cached_pass?(context) + condition = context.condition(@name) + return nil unless condition.cached? + condition.pass? + end + + def description(context) + context.class.conditions[@name].description + end + + def repr + @name.to_s + end + end + + # A rule constructed from DelegateDsl - using a condition from a + # delegated policy. + class DelegatedCondition < Base + # Internal use only - this is rescued each time it's raised. + MissingDelegate = Class.new(StandardError) + + def initialize(delegate_name, name) + @delegate_name = delegate_name + @name = name + end + + def delegated_context(context) + policy = context.delegated_policies[@delegate_name] + raise MissingDelegate if policy.nil? + policy + end + + def score(context) + delegated_context(context).condition(@name).score + rescue MissingDelegate + 0 + end + + def cached_pass?(context) + condition = delegated_context(context).condition(@name) + return nil unless condition.cached? + condition.pass? + rescue MissingDelegate + false + end + + def pass?(context) + delegated_context(context).condition(@name).pass? + rescue MissingDelegate + false + end + + def repr + "#{@delegate_name}.#{@name}" + end + end + + # A rule constructed from RuleDsl#can?. Computes a different ability + # on the same subject. + class Ability < Base + attr_reader :ability + def initialize(ability) + @ability = ability + end + + # We ask the ability's runner for a score + def score(context) + context.runner(@ability).score + end + + def pass?(context) + context.allowed?(@ability) + end + + def cached_pass?(context) + runner = context.runner(@ability) + return nil unless runner.cached? + runner.pass? + end + + def description(context) + "User can #{@ability.inspect}" + end + + def repr + "can?(#{@ability.inspect})" + end + end + + # Logical `and`, containing a list of rules. Only passes + # if all of them do. + class And < Base + attr_reader :rules + def initialize(rules) + @rules = rules + end + + def simplify + simplified_rules = @rules.flat_map do |rule| + simplified = rule.simplify + case simplified + when And then simplified.rules + else [simplified] + end + end + + And.new(simplified_rules) + end + + def score(context) + return 0 unless cached_pass?(context).nil? + + # note that cached rules will have score 0 anyways. + @rules.map { |r| r.score(context) }.inject(0, :+) + end + + def pass?(context) + # try to find a cached answer before + # checking in order + cached = cached_pass?(context) + return cached unless cached.nil? + + @rules.all? { |r| r.pass?(context) } + end + + def cached_pass?(context) + passes = @rules.map { |r| r.cached_pass?(context) } + return false if passes.any? { |p| p == false } + return true if passes.all? { |p| p == true } + + nil + end + + def repr + "all?(#{rules.map(&:repr).join(', ')})" + end + end + + # Logical `or`. Mirrors And. + class Or < Base + attr_reader :rules + def initialize(rules) + @rules = rules + end + + def pass?(context) + cached = cached_pass?(context) + return cached unless cached.nil? + + @rules.any? { |r| r.pass?(context) } + end + + def simplify + simplified_rules = @rules.flat_map do |rule| + simplified = rule.simplify + case simplified + when Or then simplified.rules + else [simplified] + end + end + + Or.new(simplified_rules) + end + + def cached_pass?(context) + passes = @rules.map { |r| r.cached_pass?(context) } + return true if passes.any? { |p| p == true } + return false if passes.all? { |p| p == false } + + nil + end + + def score(context) + return 0 unless cached_pass?(context).nil? + @rules.map { |r| r.score(context) }.inject(0, :+) + end + + def repr + "any?(#{@rules.map(&:repr).join(', ')})" + end + end + + class Not < Base + attr_reader :rule + def initialize(rule) + @rule = rule + end + + def simplify + case @rule + when And then Or.new(@rule.rules.map(&:negate)).simplify + when Or then And.new(@rule.rules.map(&:negate)).simplify + when Not then @rule.rule.simplify + else Not.new(@rule.simplify) + end + end + + def pass?(context) + !@rule.pass?(context) + end + + def cached_pass?(context) + case @rule.cached_pass?(context) + when nil then nil + when true then false + when false then true + end + end + + def score(context) + @rule.score(context) + end + + def repr + "~#{@rule.repr}" + end + end + end +end diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb new file mode 100644 index 00000000000..b5c615da4e3 --- /dev/null +++ b/lib/declarative_policy/runner.rb @@ -0,0 +1,181 @@ +module DeclarativePolicy + class Runner + class State + def initialize + @enabled = false + @prevented = false + end + + def enable! + @enabled = true + end + + def enabled? + @enabled + end + + def prevent! + @prevented = true + end + + def prevented? + @prevented + end + + def pass? + !prevented? && enabled? + end + end + + # a Runner contains a list of Steps to be run. + attr_reader :steps + def initialize(steps) + @steps = steps + end + + # We make sure only to run any given Runner once, + # and just continue to use the resulting @state + # that's left behind. + def cached? + !!@state + end + + # used by Rule::Ability. See #steps_by_score + def score + return 0 if cached? + steps.map(&:score).inject(0, :+) + end + + def merge_runner(other) + Runner.new(@steps + other.steps) + end + + # The main entry point, called for making an ability decision. + # See #run and DeclarativePolicy::Base#can? + def pass? + run unless cached? + + @state.pass? + end + + # see DeclarativePolicy::Base#debug + def debug(out = $stderr) + run(out) + end + + private + + def flatten_steps! + @steps = @steps.flat_map { |s| s.flattened(@steps) } + end + + # This method implements the semantic of "one enable and no prevents". + # It relies on #steps_by_score for the main loop, and updates @state + # with the result of the step. + def run(debug = nil) + @state = State.new + + steps_by_score do |step, score| + passed = nil + case step.action + when :enable then + # we only check :enable actions if they have a chance of + # changing the outcome - if no other rule has enabled or + # prevented. + unless @state.enabled? || @state.prevented? + passed = step.pass? + @state.enable! if passed + end + + debug << inspect_step(step, score, passed) if debug + when :prevent then + # we only check :prevent actions if the state hasn't already + # been prevented. + unless @state.prevented? + passed = step.pass? + if passed + @state.prevent! + return unless debug + end + end + + debug << inspect_step(step, score, passed) if debug + else raise "invalid action #{step.action.inspect}" + end + end + + @state + end + + # This is the core spot where all those `#score` methods matter. + # It is critcal for performance to run steps in the correct order, + # so that we don't compute expensive conditions (potentially n times + # if we're called on, say, a large list of users). + # + # In order to determine the cheapest step to run next, we rely on + # Step#score, which returns a numerical rating of how expensive + # it would be to calculate - the lower the better. It would be + # easy enough to statically sort by these scores, but we can do + # a little better - the scores are cache-aware (conditions that + # are already in the cache have score 0), which means that running + # a step can actually change the scores of other steps. + # + # So! The way we sort here involves re-scoring at every step. This + # is by necessity quadratic, but most of the time the number of steps + # will be low. But just in case, if the number of steps exceeds 50, + # we print a warning and fall back to a static sort. + # + # For each step, we yield the step object along with the computed score + # for debugging purposes. + def steps_by_score(&b) + flatten_steps! + + if @steps.size > 50 + warn "DeclarativePolicy: large number of steps (#{steps.size}), falling back to static sort" + + @steps.map { |s| [s.score, s] }.sort_by { |(score, _)| score }.each do |(score, step)| + yield step, score + end + + return + end + + steps = Set.new(@steps) + + loop do + return if steps.empty? + + # if the permission hasn't yet been enabled and we only have + # prevent steps left, we short-circuit the state here + @state.prevent! if !@state.enabled? && steps.all?(&:prevent?) + + lowest_score = Float::INFINITY + next_step = nil + + steps.each do |step| + score = step.score + if score < lowest_score + next_step = step + lowest_score = score + end + end + + steps.delete(next_step) + + yield next_step, lowest_score + end + end + + # Formatter for debugging output. + def inspect_step(step, original_score, passed) + symbol = + case passed + when true then '+' + when false then '-' + when nil then ' ' + end + + "#{symbol} [#{original_score.to_i}] #{step.repr}\n" + end + end +end diff --git a/lib/declarative_policy/step.rb b/lib/declarative_policy/step.rb new file mode 100644 index 00000000000..3469fe9f991 --- /dev/null +++ b/lib/declarative_policy/step.rb @@ -0,0 +1,86 @@ +module DeclarativePolicy + # This object represents one step in the runtime decision of whether + # an ability is allowed. It contains a Rule and a context (instance + # of DeclarativePolicy::Base), which contains the user, the subject, + # and the cache. It also contains an "action", which is the symbol + # :prevent or :enable. + class Step + attr_reader :context, :rule, :action + def initialize(context, rule, action) + @context = context + @rule = rule + @action = action + end + + # In the flattening process, duplicate steps may be generated in the + # same rule. This allows us to eliminate those (see Runner#steps_by_score + # and note its use of a Set) + def ==(other) + @context == other.context && @rule == other.rule && @action == other.action + end + + # In the runner, steps are sorted dynamically by score, so that + # we are sure to compute them in close to the optimal order. + # + # See also Rule#score, ManifestCondition#score, and Runner#steps_by_score. + def score + # we slightly prefer the preventative actions + # since they are more likely to short-circuit + case @action + when :prevent + @rule.score(@context) * (7.0 / 8) + when :enable + @rule.score(@context) + end + end + + def with_action(action) + Step.new(@context, @rule, action) + end + + def enable? + @action == :enable + end + + def prevent? + @action == :prevent + end + + # This rather complex method allows us to split rules into parts so that + # they can be sorted independently for better optimization + def flattened(roots) + case @rule + when Rule::Or + # A single `Or` step is the same as each of its elements as separate steps + @rule.rules.flat_map { |r| Step.new(@context, r, @action).flattened(roots) } + when Rule::Ability + # This looks like a weird micro-optimization but it buys us quite a lot + # in some cases. If we depend on an Ability (i.e. a `can?(...)` rule), + # and that ability *only* has :enable actions (modulo some actions that + # we already have taken care of), then its rules can be safely inlined. + steps = @context.runner(@rule.ability).steps.reject { |s| roots.include?(s) } + + if steps.all?(&:enable?) + # in the case that we are a :prevent step, each inlined step becomes + # an independent :prevent, even though it was an :enable in its initial + # context. + steps.map! { |s| s.with_action(:prevent) } if prevent? + + steps.flat_map { |s| s.flattened(roots) } + else + [self] + end + else + [self] + end + end + + def pass? + @rule.pass?(@context) + end + + def repr + "#{@action} when #{@rule.repr} (#{@context.repr})" + end + end +end -- cgit v1.2.1 From 963b374dc7ca077aeb97c6a3a79605e2646631d1 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 6 Apr 2017 14:09:58 -0700 Subject: update the specs to not require a set to be returned --- spec/models/ability_spec.rb | 11 ++- spec/policies/base_policy_spec.rb | 6 +- spec/policies/ci/build_policy_spec.rb | 28 +++--- spec/policies/ci/trigger_policy_spec.rb | 14 +-- spec/policies/deploy_key_policy_spec.rb | 12 +-- spec/policies/environment_policy_spec.rb | 12 +-- spec/policies/group_policy_spec.rb | 116 ++++++++++++------------ spec/policies/issue_policy_spec.rb | 122 +++++++++++++------------- spec/policies/personal_snippet_policy_spec.rb | 68 +++++++------- spec/policies/project_policy_spec.rb | 113 +++++++++++++----------- spec/policies/project_snippet_policy_spec.rb | 64 ++++++++------ spec/policies/user_policy_spec.rb | 12 +-- 12 files changed, 303 insertions(+), 275 deletions(-) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 090f9e70c50..dc7a0d80752 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Ability, lib: true do context 'using a nil subject' do - it 'is always empty' do - expect(Ability.allowed(nil, nil).to_set).to be_empty + it 'has no permissions' do + expect(Ability.policy_for(nil, nil)).to be_banned end end @@ -255,12 +255,15 @@ describe Ability, lib: true do describe '.project_disabled_features_rules' do let(:project) { create(:empty_project, :wiki_disabled) } - subject { described_class.allowed(project.owner, project) } + subject { described_class.policy_for(project.owner, project) } context 'wiki named abilities' do it 'disables wiki abilities if the project has no wiki' do expect(project).to receive(:has_external_wiki?).and_return(false) - expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki) + expect(subject).not_to be_allowed(:read_wiki) + expect(subject).not_to be_allowed(:create_wiki) + expect(subject).not_to be_allowed(:update_wiki) + expect(subject).not_to be_allowed(:admin_wiki) end end end diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb index 02acdcb36df..e1963091a72 100644 --- a/spec/policies/base_policy_spec.rb +++ b/spec/policies/base_policy_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' describe BasePolicy, models: true do describe '.class_for' do it 'detects policy class based on the subject ancestors' do - expect(described_class.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy) + expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy) end it 'detects policy class for a presented subject' do presentee = Ci::BuildPresenter.new(Ci::Build.new) - expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy) + expect(DeclarativePolicy.class_for(presentee)).to eq(Ci::BuildPolicy) end it 'uses GlobalPolicy when :global is given' do - expect(described_class.class_for(:global)).to eq(GlobalPolicy) + expect(DeclarativePolicy.class_for(:global)).to eq(GlobalPolicy) end end end diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index 48a139d4b83..ace95ac7067 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -5,8 +5,8 @@ describe Ci::BuildPolicy, :models do let(:build) { create(:ci_build, pipeline: pipeline) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } - let(:policies) do - described_class.abilities(user, build).to_set + let(:policy) do + described_class.new(user, build) end shared_context 'public pipelines disabled' do @@ -21,7 +21,7 @@ describe Ci::BuildPolicy, :models do context 'when public builds are enabled' do it 'does not include ability to read build' do - expect(policies).not_to include :read_build + expect(policy).not_to be_allowed :read_build end end @@ -29,7 +29,7 @@ describe Ci::BuildPolicy, :models do include_context 'public pipelines disabled' it 'does not include ability to read build' do - expect(policies).not_to include :read_build + expect(policy).not_to be_allowed :read_build end end end @@ -39,7 +39,7 @@ describe Ci::BuildPolicy, :models do context 'when public builds are enabled' do it 'includes ability to read build' do - expect(policies).to include :read_build + expect(policy).to be_allowed :read_build end end @@ -47,7 +47,7 @@ describe Ci::BuildPolicy, :models do include_context 'public pipelines disabled' it 'does not include ability to read build' do - expect(policies).not_to include :read_build + expect(policy).not_to be_allowed :read_build end end end @@ -62,7 +62,7 @@ describe Ci::BuildPolicy, :models do context 'when public builds are enabled' do it 'includes ability to read build' do - expect(policies).to include :read_build + expect(policy).to be_allowed :read_build end end @@ -70,7 +70,7 @@ describe Ci::BuildPolicy, :models do include_context 'public pipelines disabled' it 'does not include ability to read build' do - expect(policies).not_to include :read_build + expect(policy).not_to be_allowed :read_build end end end @@ -82,7 +82,7 @@ describe Ci::BuildPolicy, :models do context 'when public builds are enabled' do it 'includes ability to read build' do - expect(policies).to include :read_build + expect(policy).to be_allowed :read_build end end @@ -90,7 +90,7 @@ describe Ci::BuildPolicy, :models do include_context 'public pipelines disabled' it 'does not include ability to read build' do - expect(policies).to include :read_build + expect(policy).to be_allowed :read_build end end end @@ -115,7 +115,7 @@ describe Ci::BuildPolicy, :models do end it 'does not include ability to update build' do - expect(policies).not_to include :update_build + expect(policy).to be_disallowed :update_build end end @@ -125,7 +125,7 @@ describe Ci::BuildPolicy, :models do end it 'includes ability to update build' do - expect(policies).to include :update_build + expect(policy).to be_allowed :update_build end end end @@ -135,7 +135,7 @@ describe Ci::BuildPolicy, :models do let(:build) { create(:ci_build, :manual, pipeline: pipeline) } it 'includes ability to update build' do - expect(policies).to include :update_build + expect(policy).to be_allowed :update_build end end @@ -143,7 +143,7 @@ describe Ci::BuildPolicy, :models do let(:build) { create(:ci_build, pipeline: pipeline) } it 'includes ability to update build' do - expect(policies).to include :update_build + expect(policy).to be_allowed :update_build end end end diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb index 63ad5eb7322..ed4010e723b 100644 --- a/spec/policies/ci/trigger_policy_spec.rb +++ b/spec/policies/ci/trigger_policy_spec.rb @@ -6,36 +6,36 @@ describe Ci::TriggerPolicy, :models do let(:trigger) { create(:ci_trigger, project: project, owner: owner) } let(:policies) do - described_class.abilities(user, trigger).to_set + described_class.new(user, trigger) end shared_examples 'allows to admin and manage trigger' do it 'does include ability to admin trigger' do - expect(policies).to include :admin_trigger + expect(policies).to be_allowed :admin_trigger end it 'does include ability to manage trigger' do - expect(policies).to include :manage_trigger + expect(policies).to be_allowed :manage_trigger end end shared_examples 'allows to manage trigger' do it 'does not include ability to admin trigger' do - expect(policies).not_to include :admin_trigger + expect(policies).not_to be_allowed :admin_trigger end it 'does include ability to manage trigger' do - expect(policies).to include :manage_trigger + expect(policies).to be_allowed :manage_trigger end end shared_examples 'disallows to admin and manage trigger' do it 'does not include ability to admin trigger' do - expect(policies).not_to include :admin_trigger + expect(policies).not_to be_allowed :admin_trigger end it 'does not include ability to manage trigger' do - expect(policies).not_to include :manage_trigger + expect(policies).not_to be_allowed :manage_trigger end end diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb index 28e10f0bfe2..f15f4a11f02 100644 --- a/spec/policies/deploy_key_policy_spec.rb +++ b/spec/policies/deploy_key_policy_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe DeployKeyPolicy, models: true do - subject { described_class.abilities(current_user, deploy_key).to_set } + subject { described_class.new(current_user, deploy_key) } describe 'updating a deploy_key' do context 'when a regular user' do @@ -16,7 +16,7 @@ describe DeployKeyPolicy, models: true do project.deploy_keys << deploy_key end - it { is_expected.to include(:update_deploy_key) } + it { is_expected.to be_allowed(:update_deploy_key) } end context 'tries to update private deploy key attached to other project' do @@ -27,13 +27,13 @@ describe DeployKeyPolicy, models: true do other_project.deploy_keys << deploy_key end - it { is_expected.not_to include(:update_deploy_key) } + it { is_expected.to be_disallowed(:update_deploy_key) } end context 'tries to update public deploy key' do let(:deploy_key) { create(:another_deploy_key, public: true) } - it { is_expected.not_to include(:update_deploy_key) } + it { is_expected.to be_disallowed(:update_deploy_key) } end end @@ -43,13 +43,13 @@ describe DeployKeyPolicy, models: true do context ' tries to update private deploy key' do let(:deploy_key) { create(:deploy_key, public: false) } - it { is_expected.to include(:update_deploy_key) } + it { is_expected.to be_allowed(:update_deploy_key) } end context 'when an admin user tries to update public deploy key' do let(:deploy_key) { create(:another_deploy_key, public: true) } - it { is_expected.to include(:update_deploy_key) } + it { is_expected.to be_allowed(:update_deploy_key) } end end end diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb index 650432520bb..035e20c7452 100644 --- a/spec/policies/environment_policy_spec.rb +++ b/spec/policies/environment_policy_spec.rb @@ -8,8 +8,8 @@ describe EnvironmentPolicy do create(:environment, :with_review_app, project: project) end - let(:policies) do - described_class.abilities(user, environment).to_set + let(:policy) do + described_class.new(user, environment) end describe '#rules' do @@ -17,7 +17,7 @@ describe EnvironmentPolicy do let(:project) { create(:project, :private) } it 'does not include ability to stop environment' do - expect(policies).not_to include :stop_environment + expect(policy).to be_disallowed :stop_environment end end @@ -25,7 +25,7 @@ describe EnvironmentPolicy do let(:project) { create(:project, :public) } it 'does not include ability to stop environment' do - expect(policies).not_to include :stop_environment + expect(policy).to be_disallowed :stop_environment end end @@ -38,7 +38,7 @@ describe EnvironmentPolicy do context 'when team member has ability to stop environment' do it 'does includes ability to stop environment' do - expect(policies).to include :stop_environment + expect(policy).to be_allowed :stop_environment end end @@ -49,7 +49,7 @@ describe EnvironmentPolicy do end it 'does not include ability to stop environment' do - expect(policies).not_to include :stop_environment + expect(policy).to be_disallowed :stop_environment end end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index a8331ceb5ff..06db0ea56e3 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -36,16 +36,24 @@ describe GroupPolicy, models: true do group.add_owner(owner) end - subject { described_class.abilities(current_user, group).to_set } + subject { described_class.new(current_user, group) } + + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } + end context 'with no user' do let(:current_user) { nil } it do - is_expected.to include(:read_group) - is_expected.not_to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_disallowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -53,10 +61,10 @@ describe GroupPolicy, models: true do let(:current_user) { guest } it do - is_expected.to include(:read_group) - is_expected.not_to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_disallowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -64,10 +72,10 @@ describe GroupPolicy, models: true do let(:current_user) { reporter } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -75,10 +83,10 @@ describe GroupPolicy, models: true do let(:current_user) { developer } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -86,10 +94,10 @@ describe GroupPolicy, models: true do let(:current_user) { master } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_allowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -97,10 +105,10 @@ describe GroupPolicy, models: true do let(:current_user) { owner } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.to include(*master_permissions) - is_expected.to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_allowed(*master_permissions) + expect_allowed(*owner_permissions) end end @@ -108,10 +116,10 @@ describe GroupPolicy, models: true do let(:current_user) { admin } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.to include(*master_permissions) - is_expected.to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_allowed(*master_permissions) + expect_allowed(*owner_permissions) end end @@ -130,16 +138,16 @@ describe GroupPolicy, models: true do nested_group.add_owner(owner) end - subject { described_class.abilities(current_user, nested_group).to_set } + subject { described_class.new(current_user, nested_group) } context 'with no user' do let(:current_user) { nil } it do - is_expected.not_to include(:read_group) - is_expected.not_to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_disallowed(:read_group) + expect_disallowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -147,10 +155,10 @@ describe GroupPolicy, models: true do let(:current_user) { guest } it do - is_expected.to include(:read_group) - is_expected.not_to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_disallowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -158,10 +166,10 @@ describe GroupPolicy, models: true do let(:current_user) { reporter } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -169,10 +177,10 @@ describe GroupPolicy, models: true do let(:current_user) { developer } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -180,10 +188,10 @@ describe GroupPolicy, models: true do let(:current_user) { master } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_allowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -191,10 +199,10 @@ describe GroupPolicy, models: true do let(:current_user) { owner } it do - is_expected.to include(:read_group) - is_expected.to include(*reporter_permissions) - is_expected.to include(*master_permissions) - is_expected.to include(*owner_permissions) + expect_allowed(:read_group) + expect_allowed(*reporter_permissions) + expect_allowed(*master_permissions) + expect_allowed(*owner_permissions) end end end diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index 4a07c864428..c978cbd6185 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -9,7 +9,7 @@ describe IssuePolicy, models: true do let(:reporter_from_group_link) { create(:user) } def permissions(user, issue) - described_class.abilities(user, issue).to_set + described_class.new(user, issue) end context 'a private project' do @@ -30,42 +30,42 @@ describe IssuePolicy, models: true do end it 'does not allow non-members to read issues' do - expect(permissions(non_member, issue)).not_to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(non_member, issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end it 'allows guests to read issues' do - expect(permissions(guest, issue)).to include(:read_issue) - expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue) + expect(permissions(guest, issue)).to be_allowed(:read_issue) + expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue) - expect(permissions(guest, issue_no_assignee)).to include(:read_issue) - expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue) + expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue) + expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue) end it 'allows reporters to read, update, and admin issues' do - expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows reporters from group links to read, update, and admin issues' do - expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows issue authors to read and update their issues' do - expect(permissions(author, issue)).to include(:read_issue, :update_issue) - expect(permissions(author, issue)).not_to include(:admin_issue) + expect(permissions(author, issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(author, issue)).to be_disallowed(:admin_issue) - expect(permissions(author, issue_no_assignee)).to include(:read_issue) - expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue) + expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue) + expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue) end it 'allows issue assignees to read and update their issues' do - expect(permissions(assignee, issue)).to include(:read_issue, :update_issue) - expect(permissions(assignee, issue)).not_to include(:admin_issue) + expect(permissions(assignee, issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(assignee, issue)).to be_disallowed(:admin_issue) - expect(permissions(assignee, issue_no_assignee)).to include(:read_issue) - expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue) + expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue) + expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue) end context 'with confidential issues' do @@ -73,37 +73,37 @@ describe IssuePolicy, models: true do let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) } it 'does not allow non-members to read confidential issues' do - expect(permissions(non_member, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(non_member, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(non_member, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(non_member, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end it 'does not allow guests to read confidential issues' do - expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end it 'allows reporters to read, update, and admin confidential issues' do - expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows reporters from group links to read, update, and admin confidential issues' do - expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows issue authors to read and update their confidential issues' do - expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue) - expect(permissions(author, confidential_issue)).not_to include(:admin_issue) + expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue) - expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end it 'allows issue assignees to read and update their confidential issues' do - expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue) - expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue) + expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue) - expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end end end @@ -123,37 +123,37 @@ describe IssuePolicy, models: true do end it 'allows guests to read issues' do - expect(permissions(guest, issue)).to include(:read_issue) - expect(permissions(guest, issue)).not_to include(:update_issue, :admin_issue) + expect(permissions(guest, issue)).to be_allowed(:read_issue) + expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue) - expect(permissions(guest, issue_no_assignee)).to include(:read_issue) - expect(permissions(guest, issue_no_assignee)).not_to include(:update_issue, :admin_issue) + expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue) + expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue) end it 'allows reporters to read, update, and admin issues' do - expect(permissions(reporter, issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows reporters from group links to read, update, and admin issues' do - expect(permissions(reporter_from_group_link, issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter_from_group_link, issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows issue authors to read and update their issues' do - expect(permissions(author, issue)).to include(:read_issue, :update_issue) - expect(permissions(author, issue)).not_to include(:admin_issue) + expect(permissions(author, issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(author, issue)).to be_disallowed(:admin_issue) - expect(permissions(author, issue_no_assignee)).to include(:read_issue) - expect(permissions(author, issue_no_assignee)).not_to include(:update_issue, :admin_issue) + expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue) + expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue) end it 'allows issue assignees to read and update their issues' do - expect(permissions(assignee, issue)).to include(:read_issue, :update_issue) - expect(permissions(assignee, issue)).not_to include(:admin_issue) + expect(permissions(assignee, issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(assignee, issue)).to be_disallowed(:admin_issue) - expect(permissions(assignee, issue_no_assignee)).to include(:read_issue) - expect(permissions(assignee, issue_no_assignee)).not_to include(:update_issue, :admin_issue) + expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue) + expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue) end context 'with confidential issues' do @@ -161,32 +161,32 @@ describe IssuePolicy, models: true do let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) } it 'does not allow guests to read confidential issues' do - expect(permissions(guest, confidential_issue)).not_to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(guest, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end it 'allows reporters to read, update, and admin confidential issues' do - expect(permissions(reporter, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows reporter from group links to read, update, and admin confidential issues' do - expect(permissions(reporter_from_group_link, confidential_issue)).to include(:read_issue, :update_issue, :admin_issue) - expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :update_issue, :admin_issue) + expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :update_issue, :admin_issue) end it 'allows issue authors to read and update their confidential issues' do - expect(permissions(author, confidential_issue)).to include(:read_issue, :update_issue) - expect(permissions(author, confidential_issue)).not_to include(:admin_issue) + expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue) - expect(permissions(author, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end it 'allows issue assignees to read and update their confidential issues' do - expect(permissions(assignee, confidential_issue)).to include(:read_issue, :update_issue) - expect(permissions(assignee, confidential_issue)).not_to include(:admin_issue) + expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :update_issue) + expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue) - expect(permissions(assignee, confidential_issue_no_assignee)).not_to include(:read_issue, :update_issue, :admin_issue) + expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :update_issue, :admin_issue) end end end diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb index 58aa1145c9e..4d6350fc653 100644 --- a/spec/policies/personal_snippet_policy_spec.rb +++ b/spec/policies/personal_snippet_policy_spec.rb @@ -14,7 +14,7 @@ describe PersonalSnippetPolicy, models: true do end def permissions(user) - described_class.abilities(user, snippet).to_set + described_class.new(user, snippet) end context 'public snippet' do @@ -24,9 +24,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(nil) } it do - is_expected.to include(:read_personal_snippet) - is_expected.not_to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_allowed(:read_personal_snippet) + is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -34,9 +34,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(regular_user) } it do - is_expected.to include(:read_personal_snippet) - is_expected.to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_allowed(:read_personal_snippet) + is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -44,9 +44,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(snippet.author) } it do - is_expected.to include(:read_personal_snippet) - is_expected.to include(:comment_personal_snippet) - is_expected.to include(*author_permissions) + is_expected.to be_allowed(:read_personal_snippet) + is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*author_permissions) end end end @@ -58,9 +58,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(nil) } it do - is_expected.not_to include(:read_personal_snippet) - is_expected.not_to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_disallowed(:read_personal_snippet) + is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -68,9 +68,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(regular_user) } it do - is_expected.to include(:read_personal_snippet) - is_expected.to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_allowed(:read_personal_snippet) + is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -78,9 +78,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(external_user) } it do - is_expected.not_to include(:read_personal_snippet) - is_expected.not_to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_disallowed(:read_personal_snippet) + is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -88,9 +88,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(snippet.author) } it do - is_expected.to include(:read_personal_snippet) - is_expected.to include(:comment_personal_snippet) - is_expected.to include(*author_permissions) + is_expected.to be_allowed(:read_personal_snippet) + is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*author_permissions) end end end @@ -102,9 +102,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(nil) } it do - is_expected.not_to include(:read_personal_snippet) - is_expected.not_to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_disallowed(:read_personal_snippet) + is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -112,9 +112,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(regular_user) } it do - is_expected.not_to include(:read_personal_snippet) - is_expected.not_to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_disallowed(:read_personal_snippet) + is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -122,9 +122,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(external_user) } it do - is_expected.not_to include(:read_personal_snippet) - is_expected.not_to include(:comment_personal_snippet) - is_expected.not_to include(*author_permissions) + is_expected.to be_disallowed(:read_personal_snippet) + is_expected.to be_disallowed(:comment_personal_snippet) + is_expected.to be_disallowed(*author_permissions) end end @@ -132,9 +132,9 @@ describe PersonalSnippetPolicy, models: true do subject { permissions(snippet.author) } it do - is_expected.to include(:read_personal_snippet) - is_expected.to include(:comment_personal_snippet) - is_expected.to include(*author_permissions) + is_expected.to be_allowed(:read_personal_snippet) + is_expected.to be_allowed(:comment_personal_snippet) + is_expected.to be_allowed(*author_permissions) end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index d70e15f006b..87b2bbf5a85 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -73,37 +73,45 @@ describe ProjectPolicy, models: true do project.team << [reporter, :reporter] end + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } + end + it 'does not include the read_issue permission when the issue author is not a member of the private project' do project = create(:empty_project, :private) issue = create(:issue, project: project) user = issue.author - expect(project.team.member?(issue.author)).to eq(false) + expect(project.team.member?(issue.author)).to be false - expect(BasePolicy.class_for(project).abilities(user, project).can_set) - .not_to include(:read_issue) - - expect(Ability.allowed?(user, :read_issue, project)).to be_falsy + expect(Ability).not_to be_allowed(user, :read_issue, project) end - it 'does not include the wiki permissions when the feature is disabled' do - project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) - wiki_permissions = [:read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code] + context 'when the feature is disabled' do + subject { described_class.new(owner, project) } - permissions = described_class.abilities(owner, project).to_set + before do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + end - expect(permissions).not_to include(*wiki_permissions) + it 'does not include the wiki permissions' do + expect_disallowed :read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code + end end context 'abilities for non-public projects' do let(:project) { create(:empty_project, namespace: owner.namespace) } - subject { described_class.abilities(current_user, project).to_set } + subject { described_class.new(current_user, project) } context 'with no user' do let(:current_user) { nil } - it { is_expected.to be_empty } + it { is_expected.to be_banned } end context 'guests' do @@ -114,18 +122,18 @@ describe ProjectPolicy, models: true do end it do - is_expected.to include(*guest_permissions) - is_expected.not_to include(*reporter_public_build_permissions) - is_expected.not_to include(*team_member_reporter_permissions) - is_expected.not_to include(*developer_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(*guest_permissions) + expect_disallowed(*reporter_public_build_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end context 'public builds enabled' do it do - is_expected.to include(*guest_permissions) - is_expected.to include(:read_build, :read_pipeline) + expect_allowed(*guest_permissions) + expect_allowed(:read_build, :read_pipeline) end end @@ -135,8 +143,8 @@ describe ProjectPolicy, models: true do end it do - is_expected.to include(*guest_permissions) - is_expected.not_to include(:read_build, :read_pipeline) + expect_allowed(*guest_permissions) + expect_disallowed(:read_build, :read_pipeline) end end @@ -157,12 +165,13 @@ describe ProjectPolicy, models: true do let(:current_user) { reporter } it do - is_expected.to include(*guest_permissions) - is_expected.to include(*reporter_permissions) - is_expected.to include(*team_member_reporter_permissions) - is_expected.not_to include(*developer_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -170,12 +179,12 @@ describe ProjectPolicy, models: true do let(:current_user) { dev } it do - is_expected.to include(*guest_permissions) - is_expected.to include(*reporter_permissions) - is_expected.to include(*team_member_reporter_permissions) - is_expected.to include(*developer_permissions) - is_expected.not_to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_disallowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -183,12 +192,12 @@ describe ProjectPolicy, models: true do let(:current_user) { master } it do - is_expected.to include(*guest_permissions) - is_expected.to include(*reporter_permissions) - is_expected.to include(*team_member_reporter_permissions) - is_expected.to include(*developer_permissions) - is_expected.to include(*master_permissions) - is_expected.not_to include(*owner_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*master_permissions) + expect_disallowed(*owner_permissions) end end @@ -196,12 +205,12 @@ describe ProjectPolicy, models: true do let(:current_user) { owner } it do - is_expected.to include(*guest_permissions) - is_expected.to include(*reporter_permissions) - is_expected.to include(*team_member_reporter_permissions) - is_expected.to include(*developer_permissions) - is_expected.to include(*master_permissions) - is_expected.to include(*owner_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*master_permissions) + expect_allowed(*owner_permissions) end end @@ -209,12 +218,12 @@ describe ProjectPolicy, models: true do let(:current_user) { admin } it do - is_expected.to include(*guest_permissions) - is_expected.to include(*reporter_permissions) - is_expected.not_to include(*team_member_reporter_permissions) - is_expected.to include(*developer_permissions) - is_expected.to include(*master_permissions) - is_expected.to include(*owner_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*master_permissions) + expect_allowed(*owner_permissions) end end end diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index d2b2528c57a..34218998803 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -15,7 +15,15 @@ describe ProjectSnippetPolicy, models: true do def abilities(user, snippet_visibility) snippet = create(:project_snippet, snippet_visibility, project: project) - described_class.abilities(user, snippet).to_set + described_class.new(user, snippet) + end + + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } end context 'public snippet' do @@ -23,8 +31,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(nil, :public) } it do - is_expected.to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -32,8 +40,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(regular_user, :public) } it do - is_expected.to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -41,8 +49,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(external_user, :public) } it do - is_expected.to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end end @@ -52,8 +60,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(nil, :internal) } it do - is_expected.not_to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_disallowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -61,8 +69,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(regular_user, :internal) } it do - is_expected.to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -70,8 +78,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(external_user, :internal) } it do - is_expected.not_to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_disallowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -83,8 +91,8 @@ describe ProjectSnippetPolicy, models: true do end it do - is_expected.to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end end @@ -94,8 +102,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(nil, :private) } it do - is_expected.not_to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_disallowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -103,19 +111,19 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(regular_user, :private) } it do - is_expected.not_to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_disallowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end context 'snippet author' do let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) } - subject { described_class.abilities(regular_user, snippet).to_set } + subject { described_class(regular_user, snippet) } it do - is_expected.to include(:read_project_snippet) - is_expected.to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_allowed(*author_permissions) end end @@ -127,8 +135,8 @@ describe ProjectSnippetPolicy, models: true do end it do - is_expected.to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -140,8 +148,8 @@ describe ProjectSnippetPolicy, models: true do end it do - is_expected.to include(:read_project_snippet) - is_expected.not_to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_disallowed(*author_permissions) end end @@ -149,8 +157,8 @@ describe ProjectSnippetPolicy, models: true do subject { abilities(create(:admin), :private) } it do - is_expected.to include(:read_project_snippet) - is_expected.to include(*author_permissions) + expect_allowed(:read_project_snippet) + expect_allowed(*author_permissions) end end end diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb index d5761390d39..0251d5dcf1c 100644 --- a/spec/policies/user_policy_spec.rb +++ b/spec/policies/user_policy_spec.rb @@ -4,34 +4,34 @@ describe UserPolicy, models: true do let(:current_user) { create(:user) } let(:user) { create(:user) } - subject { described_class.abilities(current_user, user).to_set } + subject { UserPolicy.new(current_user, user) } describe "reading a user's information" do - it { is_expected.to include(:read_user) } + it { is_expected.to be_allowed(:read_user) } end describe "destroying a user" do context "when a regular user tries to destroy another regular user" do - it { is_expected.not_to include(:destroy_user) } + it { is_expected.not_to be_allowed(:destroy_user) } end context "when a regular user tries to destroy themselves" do let(:current_user) { user } - it { is_expected.to include(:destroy_user) } + it { is_expected.to be_allowed(:destroy_user) } end context "when an admin user tries to destroy a regular user" do let(:current_user) { create(:user, :admin) } - it { is_expected.to include(:destroy_user) } + it { is_expected.to be_allowed(:destroy_user) } end context "when an admin user tries to destroy a ghost user" do let(:current_user) { create(:user, :admin) } let(:user) { create(:user, :ghost) } - it { is_expected.not_to include(:destroy_user) } + it { is_expected.not_to be_allowed(:destroy_user) } end end end -- cgit v1.2.1 From e5aad75a2673b2e4465d311cbd27970d5c81d5f7 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 22 Jun 2017 11:08:40 -0700 Subject: implement Presenter::Base#declarative_policy_delegate --- lib/gitlab/view/presenter/base.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb index dbfe0941e4d..841fb681435 100644 --- a/lib/gitlab/view/presenter/base.rb +++ b/lib/gitlab/view/presenter/base.rb @@ -15,6 +15,11 @@ module Gitlab super(user, action, overriden_subject || subject) end + # delegate all #can? queries to the subject + def declarative_policy_delegate + subject + end + class_methods do def presenter? true -- cgit v1.2.1 From 37c401433b76170f0150d70865f1f4584db01fa8 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 6 Apr 2017 14:06:42 -0700 Subject: convert all the policies to DeclarativePolicy --- app/models/ability.rb | 30 +- app/models/project_feature.rb | 7 +- app/policies/base_policy.rb | 132 +----- app/policies/ci/build_policy.rb | 28 +- app/policies/ci/pipeline_policy.rb | 4 +- app/policies/ci/runner_policy.rb | 15 +- app/policies/ci/trigger_policy.rb | 21 +- app/policies/commit_status_policy.rb | 6 +- app/policies/deploy_key_policy.rb | 14 +- app/policies/deployment_policy.rb | 4 +- app/policies/environment_policy.rb | 16 +- app/policies/external_issue_policy.rb | 4 +- app/policies/global_policy.rb | 46 ++- app/policies/group_label_policy.rb | 4 +- app/policies/group_member_policy.rb | 29 +- app/policies/group_policy.rb | 96 +++-- app/policies/issuable_policy.rb | 19 +- app/policies/issue_policy.rb | 26 +- app/policies/namespace_policy.rb | 12 +- app/policies/nil_policy.rb | 3 + app/policies/note_policy.rb | 31 +- app/policies/personal_snippet_policy.rb | 41 +- app/policies/project_label_policy.rb | 4 +- app/policies/project_member_policy.rb | 26 +- app/policies/project_policy.rb | 574 +++++++++++++++------------ app/policies/project_snippet_policy.rb | 64 ++- app/policies/user_policy.rb | 25 +- lib/gitlab/allowable.rb | 4 +- spec/policies/project_policy_spec.rb | 4 +- spec/policies/project_snippet_policy_spec.rb | 2 +- 30 files changed, 629 insertions(+), 662 deletions(-) create mode 100644 app/policies/nil_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index f3692a5a067..3b99e65957d 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -56,24 +56,26 @@ class Ability end end - def allowed?(user, action, subject = :global) - allowed(user, subject).include?(action) - end + def allowed?(user, action, subject = :global, opts = {}) + if subject.is_a?(Hash) + opts, subject = subject, :global + end - def allowed(user, subject = :global) - return BasePolicy::RuleSet.none if subject.nil? - return uncached_allowed(user, subject) unless RequestStore.active? + policy = policy_for(user, subject) - user_key = user ? user.id : 'anonymous' - subject_key = subject == :global ? 'global' : "#{subject.class.name}/#{subject.id}" - key = "/ability/#{user_key}/#{subject_key}" - RequestStore[key] ||= uncached_allowed(user, subject).freeze + case opts[:scope] + when :user + DeclarativePolicy.user_scope { policy.can?(action) } + when :subject + DeclarativePolicy.subject_scope { policy.can?(action) } + else + policy.can?(action) + end end - private - - def uncached_allowed(user, subject) - BasePolicy.class_for(subject).abilities(user, subject) + def policy_for(user, subject = :global) + cache = RequestStore.active? ? RequestStore : {} + DeclarativePolicy.policy_for(user, subject, cache: cache) end end end diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 48edd0738ee..c8fabb16dc1 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -51,8 +51,11 @@ class ProjectFeature < ActiveRecord::Base default_value_for :repository_access_level, value: ENABLED, allows_nil: false def feature_available?(feature, user) - access_level = public_send(ProjectFeature.access_level_attribute(feature)) - get_permission(user, access_level) + get_permission(user, access_level(feature)) + end + + def access_level(feature) + public_send(ProjectFeature.access_level_attribute(feature)) end def builds_enabled? diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 623424c63e0..00067ce756e 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -1,127 +1,13 @@ -class BasePolicy - class RuleSet - attr_reader :can_set, :cannot_set - def initialize(can_set, cannot_set) - @can_set = can_set - @cannot_set = cannot_set - end +require 'declarative_policy' - delegate :size, to: :to_set +class BasePolicy < DeclarativePolicy::Base + desc "User is an instance admin" + with_options scope: :user, score: 0 + condition(:admin) { @user&.admin? } - def self.empty - new(Set.new, Set.new) - end + with_options scope: :user, score: 0 + condition(:external_user) { @user.nil? || @user.external? } - def self.none - empty.freeze - end - - def can?(ability) - @can_set.include?(ability) && !@cannot_set.include?(ability) - end - - def include?(ability) - can?(ability) - end - - def to_set - @can_set - @cannot_set - end - - def merge(other) - @can_set.merge(other.can_set) - @cannot_set.merge(other.cannot_set) - end - - def can!(*abilities) - @can_set.merge(abilities) - end - - def cannot!(*abilities) - @cannot_set.merge(abilities) - end - - def freeze - @can_set.freeze - @cannot_set.freeze - super - end - end - - def self.abilities(user, subject) - new(user, subject).abilities - end - - def self.class_for(subject) - return GlobalPolicy if subject == :global - raise ArgumentError, 'no policy for nil' if subject.nil? - - if subject.class.try(:presenter?) - subject = subject.subject - end - - subject.class.ancestors.each do |klass| - next unless klass.name - - begin - policy_class = "#{klass.name}Policy".constantize - - # NOTE: the < operator here tests whether policy_class - # inherits from BasePolicy - return policy_class if policy_class < BasePolicy - rescue NameError - nil - end - end - - raise "no policy for #{subject.class.name}" - end - - attr_reader :user, :subject - def initialize(user, subject) - @user = user - @subject = subject - end - - def abilities - return RuleSet.none if @user && @user.blocked? - return anonymous_abilities if @user.nil? - collect_rules { rules } - end - - def anonymous_abilities - collect_rules { anonymous_rules } - end - - def anonymous_rules - rules - end - - def rules - raise NotImplementedError - end - - def delegate!(new_subject) - @rule_set.merge(Ability.allowed(@user, new_subject)) - end - - def can?(rule) - @rule_set.can?(rule) - end - - def can!(*rules) - @rule_set.can!(*rules) - end - - def cannot!(*rules) - @rule_set.cannot!(*rules) - end - - private - - def collect_rules(&b) - @rule_set = RuleSet.empty - yield - @rule_set - end + with_options scope: :user, score: 0 + condition(:can_create_group) { @user&.can_create_group } end diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index 2d7405dc240..a886efc1360 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -1,29 +1,13 @@ module Ci class BuildPolicy < CommitStatusPolicy - alias_method :build, :subject - - def rules - super - - # If we can't read build we should also not have that - # ability when looking at this in context of commit_status - %w[read create update admin].each do |rule| - cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build" - end - - if can?(:update_build) && protected_action? - cannot! :update_build - end - end - - private - - def protected_action? - return false unless build.action? + condition(:protected_action) do + next false unless @subject.action? !::Gitlab::UserAccess - .new(user, project: build.project) - .can_merge_to_branch?(build.ref) + .new(@user, project: @subject.project) + .can_merge_to_branch?(@subject.ref) end + + rule { protected_action }.prevent :update_build end end diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb index 10aa2d3e72a..a2dde95dbc8 100644 --- a/app/policies/ci/pipeline_policy.rb +++ b/app/policies/ci/pipeline_policy.rb @@ -1,7 +1,5 @@ module Ci class PipelinePolicy < BasePolicy - def rules - delegate! @subject.project - end + delegate { @subject.project } end end diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb index 416d93ffe63..7dff8470e23 100644 --- a/app/policies/ci/runner_policy.rb +++ b/app/policies/ci/runner_policy.rb @@ -1,13 +1,16 @@ module Ci class RunnerPolicy < BasePolicy - def rules - return unless @user + with_options scope: :subject, score: 0 + condition(:shared) { @subject.is_shared? } - can! :assign_runner if @user.admin? + with_options scope: :subject, score: 0 + condition(:locked, scope: :subject) { @subject.locked? } - return if @subject.is_shared? || @subject.locked? + condition(:authorized_runner) { @user.ci_authorized_runners.include?(@subject) } - can! :assign_runner if @user.ci_authorized_runners.include?(@subject) - end + rule { anonymous }.prevent_all + rule { admin | authorized_runner }.enable :assign_runner + rule { ~admin & shared }.prevent :assign_runner + rule { ~admin & locked }.prevent :assign_runner end end diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb index c90c9ac0583..5592ac30812 100644 --- a/app/policies/ci/trigger_policy.rb +++ b/app/policies/ci/trigger_policy.rb @@ -1,13 +1,16 @@ module Ci class TriggerPolicy < BasePolicy - def rules - delegate! @subject.project - - if can?(:admin_build) - can! :admin_trigger if @subject.owner.blank? || - @subject.owner == @user - can! :manage_trigger - end - end + delegate { @subject.project } + + with_options scope: :subject, score: 0 + condition(:legacy) { @subject.legacy? } + + with_score 0 + condition(:is_owner) { @user && @subject.owner_id == @user.id } + + rule { ~can?(:admin_build) }.prevent :admin_trigger + rule { legacy | is_owner }.enable :admin_trigger + + rule { can?(:admin_build) }.enable :manage_trigger end end diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb index 593df738328..24b2a4cc7fd 100644 --- a/app/policies/commit_status_policy.rb +++ b/app/policies/commit_status_policy.rb @@ -1,5 +1,7 @@ class CommitStatusPolicy < BasePolicy - def rules - delegate! @subject.project + delegate { @subject.project } + + %w[read create update admin].each do |action| + rule { ~can?(:"#{action}_commit_status") }.prevent :"#{action}_build" end end diff --git a/app/policies/deploy_key_policy.rb b/app/policies/deploy_key_policy.rb index ebab213e6be..62a22a59be6 100644 --- a/app/policies/deploy_key_policy.rb +++ b/app/policies/deploy_key_policy.rb @@ -1,11 +1,11 @@ class DeployKeyPolicy < BasePolicy - def rules - return unless @user + with_options scope: :subject, score: 0 + condition(:private_deploy_key) { @subject.private? } - can! :update_deploy_key if @user.admin? + condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) } - if @subject.private? && @user.project_deploy_keys.exists?(id: @subject.id) - can! :update_deploy_key - end - end + rule { anonymous }.prevent_all + + rule { admin }.enable :update_deploy_key + rule { private_deploy_key & has_deploy_key }.enable :update_deploy_key end diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb index 163d070ff90..62b63b9f87b 100644 --- a/app/policies/deployment_policy.rb +++ b/app/policies/deployment_policy.rb @@ -1,5 +1,3 @@ class DeploymentPolicy < BasePolicy - def rules - delegate! @subject.project - end + delegate { @subject.project } end diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb index 2fa15e64562..375a5535359 100644 --- a/app/policies/environment_policy.rb +++ b/app/policies/environment_policy.rb @@ -1,17 +1,9 @@ class EnvironmentPolicy < BasePolicy - alias_method :environment, :subject + delegate { @subject.project } - def rules - delegate! environment.project - - if can?(:create_deployment) && environment.stop_action? - can! :stop_environment if can_play_stop_action? - end + condition(:stop_action_allowed) do + @subject.stop_action? && can?(:update_build, @subject.stop_action) end - private - - def can_play_stop_action? - Ability.allowed?(user, :update_build, environment.stop_action) - end + rule { can?(:create_deployment) & stop_action_allowed }.enable :stop_environment end diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb index d9e28bd107a..e031b38078c 100644 --- a/app/policies/external_issue_policy.rb +++ b/app/policies/external_issue_policy.rb @@ -1,5 +1,3 @@ class ExternalIssuePolicy < BasePolicy - def rules - delegate! @subject.project - end + delegate { @subject.project } end diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 2683aaad981..535faa922dd 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -1,16 +1,40 @@ class GlobalPolicy < BasePolicy - def rules - return unless @user + desc "User is blocked" + with_options scope: :user, score: 0 + condition(:blocked) { @user.blocked? } - can! :create_group if @user.can_create_group - can! :read_users_list + desc "User is an internal user" + with_options scope: :user, score: 0 + condition(:internal) { @user.internal? } - unless @user.blocked? || @user.internal? - can! :log_in unless @user.access_locked? - can! :access_api - can! :access_git - can! :receive_notifications - can! :use_quick_actions - end + desc "User's access has been locked" + with_options scope: :user, score: 0 + condition(:access_locked) { @user.access_locked? } + + rule { anonymous }.prevent_all + + rule { default }.policy do + enable :read_users_list + enable :log_in + enable :access_api + enable :access_git + enable :receive_notifications + enable :use_quick_actions + end + + rule { blocked | internal }.policy do + prevent :log_in + prevent :access_api + prevent :access_git + prevent :receive_notifications + prevent :use_quick_actions + end + + rule { can_create_group }.policy do + enable :create_group + end + + rule { access_locked }.policy do + prevent :log_in end end diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb index 7b34aa182eb..e3dd3296699 100644 --- a/app/policies/group_label_policy.rb +++ b/app/policies/group_label_policy.rb @@ -1,5 +1,3 @@ class GroupLabelPolicy < BasePolicy - def rules - delegate! @subject.group - end + delegate { @subject.group } end diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb index 5a3fe814b77..23dd0d7cd23 100644 --- a/app/policies/group_member_policy.rb +++ b/app/policies/group_member_policy.rb @@ -1,25 +1,22 @@ class GroupMemberPolicy < BasePolicy - def rules - return unless @user + delegate :group - target_user = @subject.user - group = @subject.group + with_scope :subject + condition(:last_owner) { @subject.group.last_owner?(@subject.user) } - return if group.last_owner?(target_user) + desc "Membership is users' own" + with_score 0 + condition(:is_target_user) { @user && @subject.user_id == @user.id } - can_manage = Ability.allowed?(@user, :admin_group_member, group) + rule { anonymous }.prevent_all + rule { last_owner }.prevent_all - if can_manage - can! :update_group_member - can! :destroy_group_member - elsif @user == target_user - can! :destroy_group_member - end - - additional_rules! + rule { can?(:admin_group_member) }.policy do + enable :update_group_member + enable :destroy_group_member end - def additional_rules! - # This is meant to be overriden in EE + rule { is_target_user }.policy do + enable :destroy_group_member end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index fb07298c6c2..dcb37416ca3 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -1,50 +1,58 @@ class GroupPolicy < BasePolicy - def rules - can! :read_group if @subject.public? - return unless @user - - globally_viewable = @subject.public? || (@subject.internal? && !@user.external?) - access_level = @subject.max_member_access_for_user(@user) - owner = access_level >= GroupMember::OWNER - master = access_level >= GroupMember::MASTER - reporter = access_level >= GroupMember::REPORTER - - can_read = false - can_read ||= globally_viewable - can_read ||= access_level >= GroupMember::GUEST - can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? - can! :read_group if can_read - - if reporter - can! :admin_label - end - - # Only group masters and group owners can create new projects - if master - can! :create_projects - can! :admin_milestones - end - - # Only group owner and administrators can admin group - if owner - can! :admin_group - can! :admin_namespace - can! :admin_group_member - can! :change_visibility_level - can! :create_subgroup if @user.can_create_group - end - - if globally_viewable && @subject.request_access_enabled && access_level == GroupMember::NO_ACCESS - can! :request_access - end - end + desc "Group is public" + with_options scope: :subject, score: 0 + condition(:public_group) { @subject.public? } + + with_score 0 + condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? } + + condition(:has_access) { access_level != GroupMember::NO_ACCESS } - def can_read_group? - return true if @subject.public? - return true if @user.admin? - return true if @subject.internal? && !@user.external? - return true if @subject.users.include?(@user) + condition(:guest) { access_level >= GroupMember::GUEST } + condition(:owner) { access_level >= GroupMember::OWNER } + condition(:master) { access_level >= GroupMember::MASTER } + condition(:reporter) { access_level >= GroupMember::REPORTER } + condition(:has_projects) do GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? end + + with_options scope: :subject, score: 0 + condition(:request_access_enabled) { @subject.request_access_enabled } + + rule { public_group } .enable :read_group + rule { logged_in_viewable }.enable :read_group + rule { guest } .enable :read_group + rule { admin } .enable :read_group + rule { has_projects } .enable :read_group + + rule { reporter }.enable :admin_label + + rule { master }.policy do + enable :create_projects + enable :admin_milestones + end + + rule { owner }.policy do + enable :admin_group + enable :admin_namespace + enable :admin_group_member + enable :change_visibility_level + end + + rule { owner & can_create_group }.enable :create_subgroup + + rule { public_group | logged_in_viewable }.enable :view_globally + + rule { default }.enable(:request_access) + + rule { ~request_access_enabled }.prevent :request_access + rule { ~can?(:view_globally) }.prevent :request_access + rule { has_access }.prevent :request_access + + def access_level + return GroupMember::NO_ACCESS if @user.nil? + + @access_level ||= @subject.max_member_access_for_user(@user) + end end diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb index 9501e499507..daf6fa9e18a 100644 --- a/app/policies/issuable_policy.rb +++ b/app/policies/issuable_policy.rb @@ -1,14 +1,15 @@ class IssuablePolicy < BasePolicy - def action_name - @subject.class.name.underscore - end + delegate { @subject.project } - def rules - if @user && @subject.assignee_or_author?(@user) - can! :"read_#{action_name}" - can! :"update_#{action_name}" - end + desc "User is the assignee or author" + condition(:assignee_or_author) do + @user && @subject.assignee_or_author?(@user) + end - delegate! @subject.project + rule { assignee_or_author }.policy do + enable :read_issue + enable :update_issue + enable :read_merge_request + enable :update_merge_request end end diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb index 88f3179c6ff..bd2d417b2a8 100644 --- a/app/policies/issue_policy.rb +++ b/app/policies/issue_policy.rb @@ -3,25 +3,17 @@ class IssuePolicy < IssuablePolicy # Make sure to sync this class checks with issue.rb to avoid security problems. # Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information. - def issue - @subject + desc "User can read confidential issues" + condition(:can_read_confidential) do + @user && IssueCollection.new([@subject]).visible_to(@user).any? end - def rules - super + desc "Issue is confidential" + condition(:confidential, scope: :subject) { @subject.confidential? } - if @subject.confidential? && !can_read_confidential? - cannot! :read_issue - cannot! :update_issue - cannot! :admin_issue - end - end - - private - - def can_read_confidential? - return false unless @user - - IssueCollection.new([@subject]).visible_to(@user).any? + rule { confidential & ~can_read_confidential }.policy do + prevent :read_issue + prevent :update_issue + prevent :admin_issue end end diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb index 29bb357e00a..85b67f0a237 100644 --- a/app/policies/namespace_policy.rb +++ b/app/policies/namespace_policy.rb @@ -1,10 +1,10 @@ class NamespacePolicy < BasePolicy - def rules - return unless @user + rule { anonymous }.prevent_all - if @subject.owner == @user || @user.admin? - can! :create_projects - can! :admin_namespace - end + condition(:owner) { @subject.owner == @user } + + rule { owner | admin }.policy do + enable :create_projects + enable :admin_namespace end end diff --git a/app/policies/nil_policy.rb b/app/policies/nil_policy.rb new file mode 100644 index 00000000000..13f46ba60f0 --- /dev/null +++ b/app/policies/nil_policy.rb @@ -0,0 +1,3 @@ +class NilPolicy < BasePolicy + rule { default }.prevent_all +end diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb index 5326061bd07..20cd51cfb99 100644 --- a/app/policies/note_policy.rb +++ b/app/policies/note_policy.rb @@ -1,19 +1,24 @@ class NotePolicy < BasePolicy - def rules - delegate! @subject.project + delegate { @subject.project } - return unless @user + condition(:is_author) { @user && @subject.author == @user } + condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? } + condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id } - if @subject.author == @user - can! :read_note - can! :update_note - can! :admin_note - can! :resolve_note - end + condition(:editable, scope: :subject) { @subject.editable? } - if @subject.for_merge_request? && - @subject.noteable.author == @user - can! :resolve_note - end + rule { ~editable | anonymous }.prevent :edit_note + rule { is_author | admin }.enable :edit_note + rule { can?(:master_access) }.enable :edit_note + + rule { is_author }.policy do + enable :read_note + enable :update_note + enable :admin_note + enable :resolve_note + end + + rule { for_merge_request & is_noteable_author }.policy do + enable :resolve_note end end diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb index e1e5336da8c..cac0530b9f7 100644 --- a/app/policies/personal_snippet_policy.rb +++ b/app/policies/personal_snippet_policy.rb @@ -1,27 +1,28 @@ class PersonalSnippetPolicy < BasePolicy - def rules - can! :read_personal_snippet if @subject.public? - return unless @user + condition(:public_snippet, scope: :subject) { @subject.public? } + condition(:is_author) { @user && @subject.author == @user } + condition(:internal_snippet, scope: :subject) { @subject.internal? } - if @subject.public? - can! :comment_personal_snippet - end + rule { public_snippet }.policy do + enable :read_personal_snippet + enable :comment_personal_snippet + end - if @subject.author == @user - can! :read_personal_snippet - can! :update_personal_snippet - can! :destroy_personal_snippet - can! :admin_personal_snippet - can! :comment_personal_snippet - end + rule { is_author }.policy do + enable :read_personal_snippet + enable :update_personal_snippet + enable :destroy_personal_snippet + enable :admin_personal_snippet + enable :comment_personal_snippet + end - unless @user.external? - can! :create_personal_snippet - end + rule { ~anonymous }.enable :create_personal_snippet + rule { external_user }.prevent :create_personal_snippet - if @subject.internal? && !@user.external? - can! :read_personal_snippet - can! :comment_personal_snippet - end + rule { internal_snippet & ~external_user }.policy do + enable :read_personal_snippet + enable :comment_personal_snippet end + + rule { anonymous }.prevent :comment_personal_snippet end diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb index b12b4c5166b..2d0f021118b 100644 --- a/app/policies/project_label_policy.rb +++ b/app/policies/project_label_policy.rb @@ -1,5 +1,3 @@ class ProjectLabelPolicy < BasePolicy - def rules - delegate! @subject.project - end + delegate { @subject.project } end diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb index 1c038dddd4b..9aedb620be9 100644 --- a/app/policies/project_member_policy.rb +++ b/app/policies/project_member_policy.rb @@ -1,22 +1,16 @@ class ProjectMemberPolicy < BasePolicy - def rules - # anonymous users have no abilities here - return unless @user + delegate { @subject.project } - target_user = @subject.user - project = @subject.project + condition(:target_is_owner, scope: :subject) { @subject.user == @subject.project.owner } + condition(:target_is_self) { @user && @subject.user == @user } - return if target_user == project.owner + rule { anonymous }.prevent_all + rule { target_is_owner }.prevent_all - can_manage = Ability.allowed?(@user, :admin_project_member, project) - - if can_manage - can! :update_project_member - can! :destroy_project_member - end - - if @user == target_user - can! :destroy_project_member - end + rule { can?(:admin_project_member) }.policy do + enable :update_project_member + enable :destroy_project_member end + + rule { target_is_self }.enable :destroy_project_member end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 47518dddb61..7cbca63fab4 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -1,297 +1,353 @@ class ProjectPolicy < BasePolicy - def rules - team_access!(user) + def self.create_read_update_admin(name) + [ + :"create_#{name}", + :"read_#{name}", + :"update_#{name}", + :"admin_#{name}" + ] + end - owner_access! if user.admin? || owner? - team_member_owner_access! if owner? + desc "User is a project owner" + condition :owner do + @user && project.owner == @user || (project.group && project.group.has_owner?(@user)) + end - if project.public? || (project.internal? && !user.external?) - guest_access! - public_access! - can! :request_access if access_requestable? - end + desc "Project has public builds enabled" + condition(:public_builds, scope: :subject) { project.public_builds? } + + # For guest access we use #is_team_member? so we can use + # project.members, which gets cached in subject scope. + # This is safe because team_access_level is guaranteed + # by ProjectAuthorization's validation to be at minimum + # GUEST + desc "User has guest access" + condition(:guest) { is_team_member? } - archived_access! if project.archived? + desc "User has reporter access" + condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER } - disabled_features! + desc "User has developer access" + condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER } + + desc "User has master access" + condition(:master) { team_access_level >= Gitlab::Access::MASTER } + + desc "Project is public" + condition(:public_project, scope: :subject) { project.public? } + + desc "Project is visible to internal users" + condition(:internal_access) do + project.internal? && !user.external? end - def project - @subject + desc "User is a member of the group" + condition(:group_member, scope: :subject) { project_group_member? } + + desc "Project is archived" + condition(:archived, scope: :subject) { project.archived? } + + condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? } + + desc "Container registry is disabled" + condition(:container_registry_disabled, scope: :subject) do + !project.container_registry_enabled end - def owner? - return @owner if defined?(@owner) - - @owner = project.owner == user || - (project.group && project.group.has_owner?(user)) - end - - def guest_access! - can! :read_project - can! :read_board - can! :read_list - can! :read_wiki - can! :read_issue - can! :read_label - can! :read_milestone - can! :read_project_snippet - can! :read_project_member - can! :read_note - can! :create_project - can! :create_issue - can! :create_note - can! :upload_file - can! :read_cycle_analytics - - if project.public_builds? - can! :read_pipeline - can! :read_pipeline_schedule - can! :read_build - end + desc "Project has an external wiki" + condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? } + + desc "Project has request access enabled" + condition(:request_access_enabled, scope: :subject) { project.request_access_enabled } + + features = %w[ + merge_requests + issues + repository + snippets + wiki + builds + ] + + features.each do |f| + # these are scored high because they are unlikely + desc "Project has #{f} disabled" + condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) } end - def reporter_access! - can! :download_code - can! :download_wiki_code - can! :fork_project - can! :create_project_snippet - can! :update_issue - can! :admin_issue - can! :admin_label - can! :admin_list - can! :read_commit_status - can! :read_build - can! :read_container_image - can! :read_pipeline - can! :read_pipeline_schedule - can! :read_environment - can! :read_deployment - can! :read_merge_request - end - - # Permissions given when an user is team member of a project - def team_member_reporter_access! - can! :build_download_code - can! :build_read_container_image - end - - def developer_access! - can! :admin_merge_request - can! :update_merge_request - can! :create_commit_status - can! :update_commit_status - can! :create_build - can! :update_build - can! :create_pipeline - can! :update_pipeline - can! :create_pipeline_schedule - can! :update_pipeline_schedule - can! :create_merge_request - can! :create_wiki - can! :push_code - can! :resolve_note - can! :create_container_image - can! :update_container_image - can! :create_environment - can! :create_deployment - end - - def master_access! - can! :delete_protected_branch - can! :update_project_snippet - can! :update_environment - can! :update_deployment - can! :admin_milestone - can! :admin_project_snippet - can! :admin_project_member - can! :admin_note - can! :admin_wiki - can! :admin_project - can! :admin_commit_status - can! :admin_build - can! :admin_container_image - can! :admin_pipeline - can! :admin_pipeline_schedule - can! :admin_environment - can! :admin_deployment - can! :admin_pages - can! :read_pages - can! :update_pages - end - - def public_access! - can! :download_code - can! :fork_project - can! :read_commit_status - can! :read_pipeline - can! :read_pipeline_schedule - can! :read_container_image - can! :build_download_code - can! :build_read_container_image - can! :read_merge_request - end - - def owner_access! - guest_access! - reporter_access! - developer_access! - master_access! - can! :change_namespace - can! :change_visibility_level - can! :rename_project - can! :remove_project - can! :archive_project - can! :remove_fork_project - can! :destroy_merge_request - can! :destroy_issue - can! :remove_pages - end - - def team_member_owner_access! - team_member_reporter_access! - end - - # Push abilities on the users team role - def team_access!(user) - access = project.team.max_member_access(user.id) - - return if access < Gitlab::Access::GUEST - guest_access! - - return if access < Gitlab::Access::REPORTER - reporter_access! - team_member_reporter_access! - - return if access < Gitlab::Access::DEVELOPER - developer_access! - - return if access < Gitlab::Access::MASTER - master_access! - end - - def archived_access! - cannot! :create_merge_request - cannot! :push_code - cannot! :delete_protected_branch - cannot! :update_merge_request - cannot! :admin_merge_request - end - - def disabled_features! - repository_enabled = project.feature_available?(:repository, user) - - block_issues_abilities - - unless project.feature_available?(:merge_requests, user) && repository_enabled - cannot!(*named_abilities(:merge_request)) - end + rule { guest }.enable :guest_access + rule { reporter }.enable :reporter_access + rule { developer }.enable :developer_access + rule { master }.enable :master_access + + rule { owner | admin }.policy do + enable :guest_access + enable :reporter_access + enable :developer_access + enable :master_access + + enable :change_namespace + enable :change_visibility_level + enable :rename_project + enable :remove_project + enable :archive_project + enable :remove_fork_project + enable :destroy_merge_request + enable :destroy_issue + enable :remove_pages + end - unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user) - cannot!(*named_abilities(:label)) - cannot!(*named_abilities(:milestone)) - end + rule { owner | reporter }.policy do + enable :build_download_code + enable :build_read_container_image + end - unless project.feature_available?(:snippets, user) - cannot!(*named_abilities(:project_snippet)) - end + rule { can?(:guest_access) }.policy do + enable :read_project + enable :read_board + enable :read_list + enable :read_wiki + enable :read_issue + enable :read_label + enable :read_milestone + enable :read_project_snippet + enable :read_project_member + enable :read_note + enable :create_project + enable :create_issue + enable :create_note + enable :upload_file + enable :read_cycle_analytics + enable :read_project_snippet + end - unless project.feature_available?(:wiki, user) || project.has_external_wiki? - cannot!(*named_abilities(:wiki)) - cannot!(:download_wiki_code) - end + rule { can?(:reporter_access) }.policy do + enable :download_code + enable :download_wiki_code + enable :fork_project + enable :create_project_snippet + enable :update_issue + enable :admin_issue + enable :admin_label + enable :admin_list + enable :read_commit_status + enable :read_build + enable :read_container_image + enable :read_pipeline + enable :read_pipeline_schedule + enable :read_environment + enable :read_deployment + enable :read_merge_request + end - unless project.feature_available?(:builds, user) && repository_enabled - cannot!(*named_abilities(:build)) - cannot!(*named_abilities(:pipeline) - [:read_pipeline]) - cannot!(*named_abilities(:pipeline_schedule)) - cannot!(*named_abilities(:environment)) - cannot!(*named_abilities(:deployment)) - end + rule { (~anonymous & public_project) | internal_access }.policy do + enable :public_user_access + end - unless repository_enabled - cannot! :push_code - cannot! :delete_protected_branch - cannot! :download_code - cannot! :fork_project - cannot! :read_commit_status - end + rule { can?(:public_user_access) }.policy do + enable :guest_access + enable :request_access + end - unless project.container_registry_enabled - cannot!(*named_abilities(:container_image)) - end + rule { owner | admin | guest | group_member }.prevent :request_access + rule { ~request_access_enabled }.prevent :request_access + + rule { can?(:developer_access) }.policy do + enable :admin_merge_request + enable :update_merge_request + enable :create_commit_status + enable :update_commit_status + enable :create_build + enable :update_build + enable :create_pipeline + enable :update_pipeline + enable :create_pipeline_schedule + enable :update_pipeline_schedule + enable :create_merge_request + enable :create_wiki + enable :push_code + enable :resolve_note + enable :create_container_image + enable :update_container_image + enable :create_environment + enable :create_deployment end - def anonymous_rules - return unless project.public? + rule { can?(:master_access) }.policy do + enable :delete_protected_branch + enable :update_project_snippet + enable :update_environment + enable :update_deployment + enable :admin_milestone + enable :admin_project_snippet + enable :admin_project_member + enable :admin_note + enable :admin_wiki + enable :admin_project + enable :admin_commit_status + enable :admin_build + enable :admin_container_image + enable :admin_pipeline + enable :admin_pipeline_schedule + enable :admin_environment + enable :admin_deployment + enable :admin_pages + enable :read_pages + enable :update_pages + end - base_readonly_access! + rule { can?(:public_user_access) }.policy do + enable :public_access - # Allow to read builds by anonymous user if guests are allowed - can! :read_build if project.public_builds? + enable :fork_project + enable :build_download_code + enable :build_read_container_image + end - disabled_features! + rule { archived }.policy do + prevent :create_merge_request + prevent :push_code + prevent :delete_protected_branch + prevent :update_merge_request + prevent :admin_merge_request end - def block_issues_abilities - unless project.feature_available?(:issues, user) - cannot! :read_issue if project.default_issues_tracker? - cannot! :create_issue - cannot! :update_issue - cannot! :admin_issue - end + rule { merge_requests_disabled | repository_disabled }.policy do + prevent(*create_read_update_admin(:merge_request)) end - def named_abilities(name) - [ - :"read_#{name}", - :"create_#{name}", - :"update_#{name}", - :"admin_#{name}" - ] + rule { issues_disabled & merge_requests_disabled }.policy do + prevent(*create_read_update_admin(:label)) + prevent(*create_read_update_admin(:milestone)) + end + + rule { snippets_disabled }.policy do + prevent(*create_read_update_admin(:project_snippet)) + end + + rule { wiki_disabled & ~has_external_wiki }.policy do + prevent(*create_read_update_admin(:wiki)) + prevent(:download_wiki_code) + end + + rule { builds_disabled | repository_disabled }.policy do + prevent(*create_read_update_admin(:build)) + prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline])) + prevent(*create_read_update_admin(:pipeline_schedule)) + prevent(*create_read_update_admin(:environment)) + prevent(*create_read_update_admin(:deployment)) + end + + rule { repository_disabled }.policy do + prevent :push_code + prevent :push_code_to_protected_branches + prevent :download_code + prevent :fork_project + prevent :read_commit_status + end + + rule { container_registry_disabled }.policy do + prevent(*create_read_update_admin(:container_image)) + end + + rule { anonymous & ~public_project }.prevent_all + rule { public_project }.enable(:public_access) + + rule { can?(:public_access) }.policy do + enable :read_project + enable :read_board + enable :read_list + enable :read_wiki + enable :read_label + enable :read_milestone + enable :read_project_snippet + enable :read_project_member + enable :read_merge_request + enable :read_note + enable :read_pipeline + enable :read_pipeline_schedule + enable :read_commit_status + enable :read_container_image + enable :download_code + enable :download_wiki_code + enable :read_cycle_analytics + + # NOTE: may be overridden by IssuePolicy + enable :read_issue + end + + rule { public_builds }.policy do + enable :read_build + end + + rule { public_builds & can?(:guest_access) }.policy do + enable :read_pipeline + enable :read_pipeline_schedule + end + + rule { issues_disabled }.policy do + prevent :create_issue + prevent :update_issue + prevent :admin_issue + end + + rule { issues_disabled & default_issues_tracker }.policy do + prevent :read_issue end private - def project_group_member?(user) + def is_team_member? + return false if @user.nil? + + greedy_load_subject = false + + # when scoping by subject, we want to be greedy + # and load *all* the members with one query. + greedy_load_subject ||= DeclarativePolicy.preferred_scope == :subject + + # in this case we're likely to have loaded #members already + # anyways, and #member? would fail with an error + greedy_load_subject ||= !@user.persisted? + + if greedy_load_subject + project.team.members.include?(user) + else + # otherwise we just make a specific query for + # this particular user. + team_access_level >= Gitlab::Access::GUEST + end + end + + def project_group_member? + return false if @user.nil? + project.group && ( - project.group.members_with_parents.exists?(user_id: user.id) || - project.group.requesters.exists?(user_id: user.id) + project.group.members_with_parents.exists?(user_id: @user.id) || + project.group.requesters.exists?(user_id: @user.id) ) end - def access_requestable? - project.request_access_enabled && - !owner? && - !user.admin? && - !project.team.member?(user) && - !project_group_member?(user) - end - - # A base set of abilities for read-only users, which - # is then augmented as necessary for anonymous and other - # read-only users. - def base_readonly_access! - can! :read_project - can! :read_board - can! :read_list - can! :read_wiki - can! :read_label - can! :read_milestone - can! :read_project_snippet - can! :read_project_member - can! :read_merge_request - can! :read_note - can! :read_pipeline - can! :read_pipeline_schedule - can! :read_commit_status - can! :read_container_image - can! :download_code - can! :download_wiki_code - can! :read_cycle_analytics + def team_access_level + return -1 if @user.nil? - # NOTE: may be overridden by IssuePolicy - can! :read_issue + # NOTE: max_member_access has its own cache + project.team.max_member_access(@user.id) + end + + def feature_available?(feature) + case project.project_feature.access_level(feature) + when ProjectFeature::DISABLED + false + when ProjectFeature::PRIVATE + guest? || admin? + else + true + end + end + + def project + @subject end end diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb index bc5c4f32f79..dd270643bbf 100644 --- a/app/policies/project_snippet_policy.rb +++ b/app/policies/project_snippet_policy.rb @@ -1,25 +1,45 @@ class ProjectSnippetPolicy < BasePolicy - def rules - # We have to check both project feature visibility and a snippet visibility and take the stricter one - # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573 - return unless @subject.project.feature_available?(:snippets, @user) - return unless Ability.allowed?(@user, :read_project, @subject.project) - - can! :read_project_snippet if @subject.public? - return unless @user - - if @user && (@subject.author == @user || @user.admin?) - can! :read_project_snippet - can! :update_project_snippet - can! :admin_project_snippet - end - - if @subject.internal? && !@user.external? - can! :read_project_snippet - end - - if @subject.project.team.member?(@user) - can! :read_project_snippet - end + delegate :project + + desc "Snippet is public" + condition(:public_snippet, scope: :subject) { @subject.public? } + condition(:private_snippet, scope: :subject) { @subject.private? } + condition(:public_project, scope: :subject) { @subject.project.public? } + + condition(:is_author) { @user && @subject.author == @user } + + condition(:internal, scope: :subject) { @subject.internal? } + + # We have to check both project feature visibility and a snippet visibility and take the stricter one + # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573 + rule { ~can?(:read_project) }.policy do + prevent :read_project_snippet + prevent :update_project_snippet + prevent :admin_project_snippet + end + + # we have to use this complicated prevent because the delegated project policy + # is overly greedy in allowing :read_project_snippet, since it doesn't have any + # information about the snippet. However, :read_project_snippet on the *project* + # is used to hide/show various snippet-related controls, so we can't just move + # all of the handling here. + rule do + all?(private_snippet | (internal & external_user), + ~project.guest, + ~admin, + ~is_author) + end.prevent :read_project_snippet + + rule { internal & ~is_author & ~admin }.policy do + prevent :update_project_snippet + prevent :admin_project_snippet + end + + rule { public_snippet }.enable :read_project_snippet + + rule { is_author | admin }.policy do + enable :read_project_snippet + enable :update_project_snippet + enable :admin_project_snippet end end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 229846e368c..0181ddf85e0 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -1,19 +1,20 @@ class UserPolicy < BasePolicy include Gitlab::CurrentSettings - def rules - can! :read_user if @user || !restricted_public_level? + desc "The application is restricted from public visibility" + condition(:restricted_public_level, scope: :global) do + current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + end - if @user - if @user.admin? || @subject == @user - can! :destroy_user - end + desc "The current user is the user in question" + condition(:user_is_self, score: 0) { @subject == @user } - cannot! :destroy_user if @subject.ghost? - end - end + desc "This is the ghost user" + condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? } - def restricted_public_level? - current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - end + rule { ~restricted_public_level }.enable :read_user + rule { ~anonymous }.enable :read_user + + rule { user_is_self | admin }.enable :destroy_user + rule { subject_ghost }.prevent :destroy_user end diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb index e4f7cad2b79..45c2b01dd8f 100644 --- a/lib/gitlab/allowable.rb +++ b/lib/gitlab/allowable.rb @@ -1,7 +1,7 @@ module Gitlab module Allowable - def can?(user, action, subject = :global) - Ability.allowed?(user, action, subject) + def can?(*args) + Ability.allowed?(*args) end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 87b2bbf5a85..ca435dd0218 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -155,8 +155,8 @@ describe ProjectPolicy, models: true do end it do - is_expected.not_to include(:read_build) - is_expected.to include(:read_pipeline) + expect_disallowed(:read_build) + expect_allowed(:read_pipeline) end end end diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index 34218998803..2799f03fb9b 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -119,7 +119,7 @@ describe ProjectSnippetPolicy, models: true do context 'snippet author' do let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) } - subject { described_class(regular_user, snippet) } + subject { described_class.new(regular_user, snippet) } it do expect_allowed(:read_project_snippet) -- cgit v1.2.1 From e895b49fce13342cd33f22735069484844dd5d77 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 6 Apr 2017 14:07:27 -0700 Subject: convert the special-case Ability methods to use policies --- app/models/ability.rb | 44 ++++++++++---------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 3b99e65957d..d2b8a8447b5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,35 +1,20 @@ +require 'declarative_policy' + class Ability class << self # Given a list of users and a project this method returns the users that can # read the given project. def users_that_can_read_project(users, project) - if project.public? - users - else - users.select do |user| - if user.admin? - true - elsif project.internal? && !user.external? - true - elsif project.owner == user - true - elsif project.team.members.include?(user) - true - else - false - end - end + DeclarativePolicy.subject_scope do + users.select { |u| allowed?(u, :read_project, project) } end end # Given a list of users and a snippet this method returns the users that can # read the given snippet. def users_that_can_read_personal_snippet(users, snippet) - case snippet.visibility_level - when Snippet::INTERNAL, Snippet::PUBLIC - users - when Snippet::PRIVATE - users.include?(snippet.author) ? [snippet.author] : [] + DeclarativePolicy.subject_scope do + users.select { |u| allowed?(u, :read_personal_snippet, snippet) } end end @@ -38,22 +23,13 @@ class Ability # issues - The issues to reduce down to those readable by the user. # user - The User for which to check the issues def issues_readable_by_user(issues, user = nil) - return issues if user && user.admin? - - issues.select { |issue| issue.visible_to_user?(user) } + DeclarativePolicy.user_scope do + issues.select { |issue| issue.visible_to_user?(user) } + end end - # TODO: make this private and use the actual abilities stuff for this def can_edit_note?(user, note) - return false if !note.editable? || !user.present? - return true if note.author == user || user.admin? - - if note.project - max_access_level = note.project.team.max_member_access(user.id) - max_access_level >= Gitlab::Access::MASTER - else - false - end + allowed?(user, :edit_note, note) end def allowed?(user, action, subject = :global, opts = {}) -- cgit v1.2.1 From 59e7c39f4ceb054d3803e3012107a3d0d6d2d2f4 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 11 Apr 2017 14:07:46 -0700 Subject: use subject scope in :id/users since we're loading all the members anyways --- lib/api/projects.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c5df45b7902..886e97a2638 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,3 +1,5 @@ +require 'declarative_policy' + module API # Projects API class Projects < Grape::API @@ -396,7 +398,7 @@ module API use :pagination end get ':id/users' do - users = user_project.team.users + users = DeclarativePolicy.subject_scope { user_project.team.users } users = users.search(params[:search]) if params[:search].present? present paginate(users), with: Entities::UserBasic -- cgit v1.2.1 From 72dc16dabd8996329b4272a03af47ef296a737f8 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 8 Jun 2017 17:22:19 -0700 Subject: add some extremely rough documentation --- doc/development/policies.md | 116 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 doc/development/policies.md diff --git a/doc/development/policies.md b/doc/development/policies.md new file mode 100644 index 00000000000..62141356f59 --- /dev/null +++ b/doc/development/policies.md @@ -0,0 +1,116 @@ +# `DeclarativePolicy` framework + +The DeclarativePolicy framework is designed to assist in performance of policy checks, and to enable ease of extension for EE. The DSL code in `app/policies` is what `Ability.allowed?` uses to check whether a particular action is allowed on a subject. + +The policy used is based on the subject's class name - so `Ability.allowed?(user, :some_ability, project)` will create a `ProjectPolicy` and check permissions on that. + +## Managing Permission Rules + +Permissions are broken into two parts: `conditions` and `rules`. Conditions are boolean expressions that can access the database and the environment, while rules are statically configured combinations of expressions and other rules that enable or prevent certain abilities. For an ability to be allowed, it must be enabled by at least one rule, and not prevented by any. + + +### Conditions + +Conditions are defined by the `condition` method, and are given a name and a block. The block will be executed in the context of the policy object - so it can access `@user` and `@subject`, as well as call any methods defined on the policy. Note that `@user` may be nil (in the anonymous case), but `@subject` is guaranteed to be a real instance of the subject class. + +``` ruby +class FooPolicy < BasePolicy + condition(:is_public) do + # @subject guaranteed to be an instance of Foo + @subject.public? + end + + # instance methods can be called from the condition as well + condition(:thing) { check_thing } + + def check_thing + # ... + end +end +``` + +When you define a condition, a predicate method is defined on the policy to check whether that condition passes - so in the above example, an instance of `FooPolicy` will also respond to `#is_public?` and `#thing?`. + +Conditions are cached according to their scope. Scope and ordering will be covered later. + +### Rules + +A `rule` is a logical combination of conditions and other rules, that are configured to enable or prevent certain abilities. It is important to note that the rule configuration is static - a rule's logic cannot touch the database or know about `@user` or `@subject`. This allows us to cache only at the condition level. Rules are specified through the `rule` method, which takes a block of DSL configuration, and returns an object that responds to `#enable` or `#prevent`: + +``` ruby +class FooPolicy < BasePolicy + # ... + + rule { is_public }.enable :read + rule { thing }.prevent :read + + # equivalently, + rule { is_public }.policy do + enable :read + end + + rule { ~thing }.policy do + prevent :read + end +end +``` + +Within the rule DSL, you can use: + +* A regular word mentions a condition by name - a rule that is in effect when that condition is truthy. +* `~` indicates negation +* `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)` +* `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability. + +## Scores, Order, Performance + +To see how the rules get evaluated into a judgment, it is useful in a console to use `policy.debug(:some_ability)`. This will print the rules in the order they are evaluated. + +When a policy is asked whether a particular ability is allowed (`policy.allowed?(:some_ability)`), it does not necessarily have to compute all the conditions on the policy. First, only the rules relevant to that particular ability are selected. Then, the execution model takes advantage of short-circuiting, and attempts to sort rules based on a heuristic of how expensive they will be to calculate. The sorting is dynamic and cache-aware, so that previously calculated conditions will be considered first, before computing other conditions. + +## Scope + +Sometimes, a condition will only use data from `@user` or only from `@subject`. In this case, we want to change the scope of the caching, so that we don't recalculate conditions unnecessarily. For example, given: + +``` ruby +class FooPolicy < BasePolicy + condition(:expensive_condition) { @subject.expensive_query? } + + rule { expensive_condition }.enable :some_ability +end +``` + +Naively, if we call `Ability.can?(user1, :some_ability, foo)` and `Ability.can?(user2, :some_ability, foo)`, we would have to calculate the condition twice - since they are for different users. But if we use the `scope: :subject` option: + +``` ruby + condition(:expensive_condition, scope: :subject) { @subject.expensive_query? } +``` + +then the result of the condition will be cached globally only based on the subject - so it will not be calculated repeatedly for different users. Similarly, `scope: :user` will cache only based on the user. + +**DANGER**: If you use a `:scope` option when the condition actually uses data from +both user and subject (including a simple anonymous check!) your result will be cached at too global of a scope and will result in cache bugs. + +Sometimes we are checking permissions for a lot of users for one subject, or a lot of subjects for one user. In this case, we want to set a *preferred scope* - i.e. tell the system that we prefer rules that can be cached on the repeated parameter. For example, in `Ability.users_that_can_read_project`: + +``` ruby +def users_that_can_read_project(users, project) + DeclarativePolicy.subject_scope do + users.select { |u| allowed?(u, :read_project, project) } + end +end +``` + +This will, for example, prefer checking `project.public?` to checking `user.admin?`. + +## Delegation + +Delegation is the inclusion of rules from another policy, on a different subject. For example, + +``` ruby +class FooPolicy < BasePolicy + delegate { @subject.project } +end +``` + +will include all rules from `ProjectPolicy`. The delegated conditions will be evaluated with the correct delegated subject, and will be sorted along with the regular rules in the policy. Note that only the relevant rules for a particular ability will actually be considered. -- cgit v1.2.1 -- cgit v1.2.1 From ef6b90a27f32c39b364053037285299d02c673f2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 27 Jun 2017 22:11:35 +0200 Subject: Fix changelog entry file extension [ci skip] --- changelogs/unreleased/fix-head-pipeline-for-commit-status.yml | 4 ++++ changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/fix-head-pipeline-for-commit-status.yml delete mode 100644 changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md diff --git a/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml b/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml new file mode 100644 index 00000000000..f12e7b53790 --- /dev/null +++ b/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml @@ -0,0 +1,4 @@ +--- +title: Fix head pipeline stored in merge request for external pipelines +merge_request: 12478 +author: diff --git a/changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md b/changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md deleted file mode 100644 index bf667464e60..00000000000 --- a/changelogs/unreleased/fix-head-pipeline-for-external-pipelines.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix head pipeline stored in merge request for external pipelines -merge_request: -author: -- cgit v1.2.1 From ec18956c387766c1293d59a1b00ac2eab6bfa5f8 Mon Sep 17 00:00:00 2001 From: Nitin Madhok Date: Tue, 27 Jun 2017 20:36:37 +0000 Subject: Fix steps numbering --- doc/update/9.2-to-9.3.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md index 0c32e4db53f..8fbcc892fd5 100644 --- a/doc/update/9.2-to-9.3.md +++ b/doc/update/9.2-to-9.3.md @@ -117,7 +117,7 @@ cd /home/git/gitlab sudo -u git -H git checkout 9-3-stable-ee ``` -### 5. Update gitlab-shell +### 7. Update gitlab-shell ```bash cd /home/git/gitlab-shell @@ -127,7 +127,7 @@ sudo -u git -H git checkout v$( Date: Tue, 27 Jun 2017 21:13:52 +0000 Subject: Improve sed regex for codeclimate ci job --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index abb49506828..3995f489b5a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -473,7 +473,7 @@ codeclimate: script: - docker pull codeclimate/codeclimate - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json - - sed -i.bak 's/\"body\"\:\".*\"//' codeclimate.json + - sed -i.bak 's/\({"body":"\)[^"]*\("}\)/\1\2/g' codeclimate.json artifacts: paths: [codeclimate.json] -- cgit v1.2.1 From 34f7c3bd1a189ff79205f75d8f8b45b1e6a43c15 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 27 Jun 2017 14:47:57 -0500 Subject: Fix diff of requirements.txt file by not matching newlines as part of package names --- changelogs/unreleased/dm-dependency-linker-newlines.yml | 5 +++++ lib/gitlab/dependency_linker/base_linker.rb | 2 +- spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/dm-dependency-linker-newlines.yml diff --git a/changelogs/unreleased/dm-dependency-linker-newlines.yml b/changelogs/unreleased/dm-dependency-linker-newlines.yml new file mode 100644 index 00000000000..5631095fcb7 --- /dev/null +++ b/changelogs/unreleased/dm-dependency-linker-newlines.yml @@ -0,0 +1,5 @@ +--- +title: Fix diff of requirements.txt file by not matching newlines as part of package + names +merge_request: +author: diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb index 7bbd154eb03..d2360583741 100644 --- a/lib/gitlab/dependency_linker/base_linker.rb +++ b/lib/gitlab/dependency_linker/base_linker.rb @@ -52,7 +52,7 @@ module Gitlab # # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"` def link_regex(regex, &url_proc) highlighted_lines.map!.with_index do |rich_line, i| - marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe) + marker = StringRegexMarker.new(plain_lines[i].chomp, rich_line.html_safe) marker.mark(regex, group: :name) do |text, left:, right:| url = yield(text) diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb index 7e32770f95d..64b233f3e68 100644 --- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb @@ -87,5 +87,9 @@ describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do it 'links URLs' do expect(subject).to include(link('http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl', 'http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl')) end + + it 'does not contain link with a newline as package name' do + expect(subject).not_to include(link("\n", "https://pypi.python.org/pypi/\n")) + end end end -- cgit v1.2.1 From e7470534e5d55e9635ae7d866eeefb13bd6811f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Mon, 19 Jun 2017 18:37:15 -0400 Subject: Use gl_repository exclusively as identifier on post-receive --- app/workers/post_receive.rb | 21 +++------------------ spec/workers/post_receive_spec.rb | 38 +++++++++++++------------------------- 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 89286595ca6..b8f8d3750d9 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -2,11 +2,11 @@ class PostReceive include Sidekiq::Worker include DedicatedSidekiqQueue - def perform(project_identifier, identifier, changes) - project, is_wiki = parse_project_identifier(project_identifier) + def perform(gl_repository, identifier, changes) + project, is_wiki = Gitlab::GlRepository.parse(gl_repository) if project.nil? - log("Triggered hook for non-existing project with identifier \"#{project_identifier}\"") + log("Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"") return false end @@ -59,21 +59,6 @@ class PostReceive # Nothing defined here yet. end - # To maintain backwards compatibility, we accept both gl_repository or - # repository paths as project identifiers. Our plan is to migrate to - # gl_repository only with the following plan: - # 9.2: Handle both possible values. Keep Gitlab-Shell sending only repo paths - # 9.3 (or patch release): Make GitLab Shell pass gl_repository if present - # 9.4 (or patch release): Make GitLab Shell always pass gl_repository - # 9.5 (or patch release): Handle only gl_repository as project identifier on this method - def parse_project_identifier(project_identifier) - if project_identifier.start_with?('/') - Gitlab::RepoPath.parse(project_identifier) - else - Gitlab::GlRepository.parse(project_identifier) - end - end - def log(message) Gitlab::GitLogger.error("POST-RECEIVE: #{message}") end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index cc9bc29c6cc..a8f4bb72acf 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -4,7 +4,7 @@ describe PostReceive do let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" } let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") } let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) } - let(:project_identifier) { "project-#{project.id}" } + let(:gl_repository) { "project-#{project.id}" } let(:key) { create(:key, user: project.owner) } let(:key_id) { key.shell_id } @@ -19,22 +19,14 @@ describe PostReceive do end context 'with a non-existing project' do - let(:project_identifier) { "project-123456789" } + let(:gl_repository) { "project-123456789" } let(:error_message) do - "Triggered hook for non-existing project with identifier \"#{project_identifier}\"" + "Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"" end it "returns false and logs an error" do expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}") - expect(described_class.new.perform(project_identifier, key_id, base64_changes)).to be(false) - end - end - - context "with an absolute path as the project identifier" do - it "searches the project by full path" do - expect(Project).to receive(:find_by_full_path).with(project.full_path, follow_redirects: true).and_call_original - - described_class.new.perform(pwd(project), key_id, base64_changes) + expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be(false) end end @@ -49,7 +41,7 @@ describe PostReceive do it "calls GitTagPushService" do expect_any_instance_of(GitPushService).to receive(:execute).and_return(true) expect_any_instance_of(GitTagPushService).not_to receive(:execute) - described_class.new.perform(project_identifier, key_id, base64_changes) + described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -59,7 +51,7 @@ describe PostReceive do it "calls GitTagPushService" do expect_any_instance_of(GitPushService).not_to receive(:execute) expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true) - described_class.new.perform(project_identifier, key_id, base64_changes) + described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -69,12 +61,12 @@ describe PostReceive do it "does not call any of the services" do expect_any_instance_of(GitPushService).not_to receive(:execute) expect_any_instance_of(GitTagPushService).not_to receive(:execute) - described_class.new.perform(project_identifier, key_id, base64_changes) + described_class.new.perform(gl_repository, key_id, base64_changes) end end context "gitlab-ci.yml" do - subject { described_class.new.perform(project_identifier, key_id, base64_changes) } + subject { described_class.new.perform(gl_repository, key_id, base64_changes) } context "creates a Ci::Pipeline for every change" do before do @@ -111,7 +103,7 @@ describe PostReceive do it 'calls SystemHooksService' do expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true) - described_class.new.perform(project_identifier, key_id, base64_changes) + described_class.new.perform(gl_repository, key_id, base64_changes) end end end @@ -119,7 +111,7 @@ describe PostReceive do context "webhook" do it "fetches the correct project" do expect(Project).to receive(:find_by).with(id: project.id.to_s) - described_class.new.perform(project_identifier, key_id, base64_changes) + described_class.new.perform(gl_repository, key_id, base64_changes) end it "does not run if the author is not in the project" do @@ -129,7 +121,7 @@ describe PostReceive do expect(project).not_to receive(:execute_hooks) - expect(described_class.new.perform(project_identifier, key_id, base64_changes)).to be_falsey + expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be_falsey end it "asks the project to trigger all hooks" do @@ -137,18 +129,14 @@ describe PostReceive do expect(project).to receive(:execute_hooks).twice expect(project).to receive(:execute_services).twice - described_class.new.perform(project_identifier, key_id, base64_changes) + described_class.new.perform(gl_repository, key_id, base64_changes) end it "enqueues a UpdateMergeRequestsWorker job" do allow(Project).to receive(:find_by).and_return(project) expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args) - described_class.new.perform(project_identifier, key_id, base64_changes) + described_class.new.perform(gl_repository, key_id, base64_changes) end end - - def pwd(project) - File.join(Gitlab.config.repositories.storages.default['path'], project.path_with_namespace) - end end -- cgit v1.2.1 From 604db40bbe8dfcef846360e459345313dfd4020b Mon Sep 17 00:00:00 2001 From: Sid Sijbrandij Date: Wed, 28 Jun 2017 00:47:54 +0000 Subject: Update architecture.md --- doc/development/architecture.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/development/architecture.md b/doc/development/architecture.md index acd5e3c2093..54029e00507 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -194,3 +194,7 @@ bundle exec rake gitlab:check RAILS_ENV=production ``` Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. While the sudo commands provided by gitlabhq work in Ubuntu they do not always work in RHEL. + +## GitLab.com + +We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/infrastructure/production-architecture/) but this is probably over the top unless you have millions of users. -- cgit v1.2.1 From a7f114b1368fd11dba036f67b2ef66c2bf39f02a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 27 Jun 2017 22:53:42 -0500 Subject: Update CHANGELOG.md for 9.3.2 [ci skip] --- CHANGELOG.md | 5 +++++ changelogs/unreleased/34282-fix-api-using-include_missing-false.yml | 4 ---- changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml | 5 ----- 3 files changed, 5 insertions(+), 9 deletions(-) delete mode 100644 changelogs/unreleased/34282-fix-api-using-include_missing-false.yml delete mode 100644 changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe49ad4579..f372cbf91e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.3.2 (2017-06-27) + +- API: Fix optional arugments for POST :id/variables. !12474 +- Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval of assets. + ## 9.3.1 (2017-06-26) - Fix reversed breadcrumb order for nested groups. !12322 diff --git a/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml b/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml deleted file mode 100644 index e7fff2c1a9f..00000000000 --- a/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Fix optional arugments for POST :id/variables' -merge_request: 12474 -author: diff --git a/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml b/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml deleted file mode 100644 index 9e3c3e19bea..00000000000 --- a/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval - of assets -merge_request: -author: -- cgit v1.2.1 From aee26e42b609a8539de46f6f4c7e77ab92457647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Wed, 28 Jun 2017 13:39:50 +0800 Subject: According to comments to optimize existing bulgarian translation --- locale/bg/gitlab.po | 56 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 370aca1f1d9..dd1430700f8 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -1,13 +1,14 @@ +# Huang Tao , 2017. #zanata # Lyubomir Vasilev , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-12 19:29-0500\n" +"POT-Creation-Date: 2017-06-15 21:59-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-13 04:23-0400\n" +"PO-Revision-Date: 2017-06-20 06:26-0400\n" "Last-Translator: Lyubomir Vasilev \n" "Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" @@ -90,11 +91,8 @@ msgstr "Отмяна в клона" msgid "ChangeTypeAction|Cherry-pick" msgstr "Подбиране" -msgid "ChangeType|commit" -msgstr "подаване" - -msgid "ChangeType|merge request" -msgstr "заявка за сливане" +msgid "ChangeTypeAction|Revert" +msgstr "Отмяна" msgid "Changelog" msgstr "Списък с промени" @@ -105,7 +103,7 @@ msgstr "Графики" msgid "Cherry-pick this commit" msgstr "Подбиране на това подаване" -msgid "Cherry-pick this merge-request" +msgid "Cherry-pick this merge request" msgstr "Подбиране на тази заявка за сливане" msgid "CiStatusLabel|canceled" @@ -170,6 +168,9 @@ msgstr[1] "Подавания" msgid "Commit message" msgstr "Съобщение за подаването" +msgid "CommitBoxTitle|Commit" +msgstr "Подаване" + msgid "CommitMessage|Add %{file_name}" msgstr "Добавяне на „%{file_name}“" @@ -224,9 +225,6 @@ msgstr "Часова зона за „Cron“" msgid "Cron syntax" msgstr "Синтаксис на „Cron“" -msgid "Custom" -msgstr "Персонализиран" - msgid "Custom notification events" msgstr "Персонализирани събития за известяване" @@ -412,6 +410,9 @@ msgstr "Последно подаване" msgid "Learn more in the" msgstr "Научете повече в" +msgid "Learn more in the|pipeline schedules documentation" +msgstr "документацията относно планирането на схеми" + msgid "Leave group" msgstr "Напускане на групата" @@ -578,6 +579,15 @@ msgstr "Поемане на собствеността" msgid "PipelineSchedules|Target" msgstr "Цел" +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "собствен" + +msgid "Pipeline|with stage" +msgstr "с етап" + +msgid "Pipeline|with stages" +msgstr "с етапи" + msgid "Project '%{project_name}' queued for deletion." msgstr "Проектът „%{project_name}“ е добавен в опашката за изтриване." @@ -677,7 +687,7 @@ msgstr "Заявка за достъп" msgid "Revert this commit" msgstr "Отмяна на това подаване" -msgid "Revert this merge-request" +msgid "Revert this merge request" msgstr "Отмяна на тази заявка за сливане" msgid "Save pipeline schedule" @@ -690,7 +700,7 @@ msgid "Scheduling Pipelines" msgstr "Планиране на схемите" msgid "Search branches and tags" -msgstr "Търсене в клоновете и етикетите" +msgstr "Търсете в клоновете и етикетите" msgid "Select Archive Format" msgstr "Изберете формата на архива" @@ -729,8 +739,8 @@ msgstr "Изходен код" msgid "StarProject|Star" msgstr "Звезда" -msgid "Start a new merge request with these changes" -msgstr "Създайте нова заявка за сливане с тези промени" +msgid "Start a %{new_merge_request} with these changes" +msgstr "Създайте %{new_merge_request} с тези промени" msgid "Switch branch/tag" msgstr "Преминаване към клон/етикет" @@ -779,7 +789,7 @@ msgid "" "specific branches or tags. Those scheduled pipelines will inherit limited " "project access based on their associated user." msgstr "" -"Този план за схема ще изпълнява схемите в бъдеще, периодично, за определени " +"Планът за схемата ще изпълнява схемите в бъдеще, периодично, за определени " "клонове или етикети. Тези планирани схеми ще наследят ограниченията на " "достъпа до проекта на свързания с тях потребител." @@ -1069,6 +1079,9 @@ msgstr "" msgid "You can only add files when you are on a branch" msgstr "Можете да добавяте файлове само когато се намирате в клон" +msgid "You have reached your project limit" +msgstr "Не можете да създавате повече проекти" + msgid "You must sign in to star a project" msgstr "Трябва да се впишете, за да отбележите проект със звезда" @@ -1115,6 +1128,9 @@ msgid_plural "days" msgstr[0] "ден" msgstr[1] "дни" +msgid "new merge request" +msgstr "нова заявка за сливане" + msgid "notification emails" msgstr "известия по е-поща" @@ -1123,11 +1139,3 @@ msgid_plural "parents" msgstr[0] "родител" msgstr[1] "родители" -msgid "pipeline schedules documentation" -msgstr "документацията за планирането на схеми" - -msgid "with stage" -msgid_plural "with stages" -msgstr[0] "с етап" -msgstr[1] "с етапи" - -- cgit v1.2.1 From df4e731a9f2946f4144188e9415030e3789f0fd8 Mon Sep 17 00:00:00 2001 From: kushalpandya Date: Wed, 28 Jun 2017 11:33:47 +0530 Subject: Remove unnecessary call to `_.escape` --- app/assets/javascripts/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index b21d7774920..34476f3303f 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1485,7 +1485,7 @@ export default class Notes { const cachedNoteBodyText = $noteBodyText.html(); // Show updated comment content temporarily - $noteBodyText.html(_.escape(formContent)); + $noteBodyText.html(formContent); $editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half'); $editingNote.find('.note-headline-meta a').html(''); -- cgit v1.2.1 From 41aebaa103a8c9ef6ae5edef5cc500ba6ca24bb9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 26 Jun 2017 22:03:34 +0900 Subject: Decuplin --- app/models/ci/variable.rb | 19 ++----------- app/models/concerns/has_variable.rb | 23 ++++++++++++++++ spec/models/ci/variable_spec.rb | 40 +-------------------------- spec/models/concerns/has_variable_spec.rb | 45 +++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 56 deletions(-) create mode 100644 app/models/concerns/has_variable.rb create mode 100644 spec/models/concerns/has_variable_spec.rb diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index f235260208f..96d6e120998 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,27 +1,12 @@ module Ci class Variable < ActiveRecord::Base extend Ci::Model + include HasVariable belongs_to :project - validates :key, - presence: true, - uniqueness: { scope: :project_id }, - length: { maximum: 255 }, - format: { with: /\A[a-zA-Z0-9_]+\z/, - message: "can contain only letters, digits and '_'." } + validates :key, uniqueness: { scope: :project_id } - scope :order_key_asc, -> { reorder(key: :asc) } scope :unprotected, -> { where(protected: false) } - - attr_encrypted :value, - mode: :per_attribute_iv_and_salt, - insecure_mode: true, - key: Gitlab::Application.secrets.db_key_base, - algorithm: 'aes-256-cbc' - - def to_runner_variable - { key: key, value: value, public: false } - end end end diff --git a/app/models/concerns/has_variable.rb b/app/models/concerns/has_variable.rb new file mode 100644 index 00000000000..9585b5583dc --- /dev/null +++ b/app/models/concerns/has_variable.rb @@ -0,0 +1,23 @@ +module HasVariable + extend ActiveSupport::Concern + + included do + validates :key, + presence: true, + length: { maximum: 255 }, + format: { with: /\A[a-zA-Z0-9_]+\z/, + message: "can contain only letters, digits and '_'." } + + scope :order_key_asc, -> { reorder(key: :asc) } + + attr_encrypted :value, + mode: :per_attribute_iv_and_salt, + insecure_mode: true, + key: Gitlab::Application.secrets.db_key_base, + algorithm: 'aes-256-cbc' + + def to_runner_variable + { key: key, value: value, public: false } + end + end +end diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 83494af24ba..ade279cd16f 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -3,14 +3,8 @@ require 'spec_helper' describe Ci::Variable, models: true do subject { build(:ci_variable) } - let(:secret_value) { 'secret' } - - it { is_expected.to validate_presence_of(:key) } + it { is_expected.to be_kind_of(HasVariable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) } - it { is_expected.to validate_length_of(:key).is_at_most(255) } - it { is_expected.to allow_value('foo').for(:key) } - it { is_expected.not_to allow_value('foo bar').for(:key) } - it { is_expected.not_to allow_value('foo/bar').for(:key) } describe '.unprotected' do subject { described_class.unprotected } @@ -33,36 +27,4 @@ describe Ci::Variable, models: true do end end end - - describe '#value' do - before do - subject.value = secret_value - end - - it 'stores the encrypted value' do - expect(subject.encrypted_value).not_to be_nil - end - - it 'stores an iv for value' do - expect(subject.encrypted_value_iv).not_to be_nil - end - - it 'stores a salt for value' do - expect(subject.encrypted_value_salt).not_to be_nil - end - - it 'fails to decrypt if iv is incorrect' do - subject.encrypted_value_iv = SecureRandom.hex - subject.instance_variable_set(:@value, nil) - expect { subject.value } - .to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt') - end - end - - describe '#to_runner_variable' do - it 'returns a hash for the runner' do - expect(subject.to_runner_variable) - .to eq(key: subject.key, value: subject.value, public: false) - end - end end diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb new file mode 100644 index 00000000000..3bb2d01e7c5 --- /dev/null +++ b/spec/models/concerns/has_variable_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe HasVariable do + subject { build(:ci_variable) } + + let(:secret_value) { 'secret' } + + it { is_expected.to validate_presence_of(:key) } + it { is_expected.to validate_length_of(:key).is_at_most(255) } + it { is_expected.to allow_value('foo').for(:key) } + it { is_expected.not_to allow_value('foo bar').for(:key) } + it { is_expected.not_to allow_value('foo/bar').for(:key) } + + describe '#value' do + before do + subject.value = secret_value + end + + it 'stores the encrypted value' do + expect(subject.encrypted_value).not_to be_nil + end + + it 'stores an iv for value' do + expect(subject.encrypted_value_iv).not_to be_nil + end + + it 'stores a salt for value' do + expect(subject.encrypted_value_salt).not_to be_nil + end + + it 'fails to decrypt if iv is incorrect' do + subject.encrypted_value_iv = SecureRandom.hex + subject.instance_variable_set(:@value, nil) + expect { subject.value } + .to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt') + end + end + + describe '#to_runner_variable' do + it 'returns a hash for the runner' do + expect(subject.to_runner_variable) + .to eq(key: subject.key, value: subject.value, public: false) + end + end +end -- cgit v1.2.1 From 083179e936dba669085cda7bb159c10be4d9312a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 27 Jun 2017 00:12:52 +0900 Subject: Add a test for checking validates :key does not override by concern --- spec/models/ci/variable_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index ade279cd16f..b7821afbdc9 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -6,6 +6,40 @@ describe Ci::Variable, models: true do it { is_expected.to be_kind_of(HasVariable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) } + describe 'validates :key' do + let(:project) { create(:project) } + + it 'be invalid if it exceeds maximum' do + expect do + create(:ci_variable, project: project, key: "A"*256) + end.to raise_error(ActiveRecord::RecordInvalid) + end + + it 'be invalid if violates constraints' do + expect do + create(:ci_variable, project: project, key: "*") + end.to raise_error(ActiveRecord::RecordInvalid) + end + + context 'when there is a variable' do + before do + create(:ci_variable, key: 'AAA', project: project) + end + + it 'be valid if it is unique' do + expect do + create(:ci_variable, project: project, key: 'CCC') + end.not_to raise_error + end + + it 'be invalid if it is duplicated' do + expect do + create(:ci_variable, project: project, key: 'AAA') + end.to raise_error(ActiveRecord::RecordInvalid) + end + end + end + describe '.unprotected' do subject { described_class.unprotected } -- cgit v1.2.1 From 2633a635311f60d15d9729dc770fdf2324a67927 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 27 Jun 2017 19:25:31 +0900 Subject: Remove PoC spec --- spec/models/ci/variable_spec.rb | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index b7821afbdc9..ade279cd16f 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -6,40 +6,6 @@ describe Ci::Variable, models: true do it { is_expected.to be_kind_of(HasVariable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) } - describe 'validates :key' do - let(:project) { create(:project) } - - it 'be invalid if it exceeds maximum' do - expect do - create(:ci_variable, project: project, key: "A"*256) - end.to raise_error(ActiveRecord::RecordInvalid) - end - - it 'be invalid if violates constraints' do - expect do - create(:ci_variable, project: project, key: "*") - end.to raise_error(ActiveRecord::RecordInvalid) - end - - context 'when there is a variable' do - before do - create(:ci_variable, key: 'AAA', project: project) - end - - it 'be valid if it is unique' do - expect do - create(:ci_variable, project: project, key: 'CCC') - end.not_to raise_error - end - - it 'be invalid if it is duplicated' do - expect do - create(:ci_variable, project: project, key: 'AAA') - end.to raise_error(ActiveRecord::RecordInvalid) - end - end - end - describe '.unprotected' do subject { described_class.unprotected } -- cgit v1.2.1 From de893b19c30acf83ce43dd42376783505d704763 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Jun 2017 15:25:36 +0900 Subject: Improve small things --- spec/models/ci/variable_spec.rb | 2 +- spec/models/concerns/has_variable_spec.rb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index ade279cd16f..329682a0771 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::Variable, models: true do subject { build(:ci_variable) } - it { is_expected.to be_kind_of(HasVariable) } + it { is_expected.to include_module(HasVariable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) } describe '.unprotected' do diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb index 3bb2d01e7c5..f4b24e6d1d9 100644 --- a/spec/models/concerns/has_variable_spec.rb +++ b/spec/models/concerns/has_variable_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' describe HasVariable do subject { build(:ci_variable) } - let(:secret_value) { 'secret' } - it { is_expected.to validate_presence_of(:key) } it { is_expected.to validate_length_of(:key).is_at_most(255) } it { is_expected.to allow_value('foo').for(:key) } @@ -13,7 +11,7 @@ describe HasVariable do describe '#value' do before do - subject.value = secret_value + subject.value = 'secret' end it 'stores the encrypted value' do -- cgit v1.2.1 From d2eb5bbd9cf194a67624044ee3cabc1280f33f4e Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 28 Jun 2017 09:30:18 +0200 Subject: Fix setting `last_credential_check` on LDAP-login --- lib/gitlab/ldap/access.rb | 2 +- spec/lib/gitlab/ldap/access_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index 8779577258b..fb68627dedf 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -16,7 +16,7 @@ module Gitlab def self.allowed?(user) self.open(user) do |access| if access.allowed? - Users::UpdateService.new(user, last_credential_check_a: Time.now).execute + Users::UpdateService.new(user, last_credential_check_at: Time.now).execute true else diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index 9dd997aa7dc..756fcb0fcaf 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -4,6 +4,16 @@ describe Gitlab::LDAP::Access, lib: true do let(:access) { Gitlab::LDAP::Access.new user } let(:user) { create(:omniauth_user) } + describe '.allowed?' do + it 'updates the users `last_credential_check_at' do + expect(access).to receive(:allowed?) { true } + expect(described_class).to receive(:open).and_yield(access) + + expect { described_class.allowed?(user) } + .to change { user.last_credential_check_at } + end + end + describe '#allowed?' do subject { access.allowed? } -- cgit v1.2.1 From 1c38d32840be3a6a1ad3d602786dac195a01d86a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 28 Jun 2017 08:30:57 +0000 Subject: Resolve "Navigation - Move contextual navigation to sidebar" --- app/assets/stylesheets/framework/variables.scss | 3 + app/assets/stylesheets/new_sidebar.scss | 121 +++++++++++ app/views/layouts/_head.html.haml | 1 + app/views/layouts/_page.html.haml | 20 +- app/views/layouts/admin.html.haml | 6 +- app/views/layouts/group.html.haml | 6 +- app/views/layouts/nav/_new_admin_sidebar.html.haml | 119 ++++++++++ app/views/layouts/nav/_new_group_sidebar.html.haml | 56 +++++ .../layouts/nav/_new_profile_sidebar.html.haml | 49 +++++ .../layouts/nav/_new_project_sidebar.html.haml | 242 +++++++++++++++++++++ app/views/layouts/profile.html.haml | 6 +- app/views/layouts/project.html.haml | 6 +- config/application.rb | 1 + 13 files changed, 624 insertions(+), 12 deletions(-) create mode 100644 app/assets/stylesheets/new_sidebar.scss create mode 100644 app/views/layouts/nav/_new_admin_sidebar.html.haml create mode 100644 app/views/layouts/nav/_new_group_sidebar.html.haml create mode 100644 app/views/layouts/nav/_new_profile_sidebar.html.haml create mode 100644 app/views/layouts/nav/_new_project_sidebar.html.haml diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 630f557602c..da4d91511e0 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -74,6 +74,8 @@ $red-700: #a62d19; $red-800: #8b2615; $red-900: #711e11; +$purple-600: #6e49cb; +$purple-650: #5c35ae; $purple-700: #4a2192; $purple-800: #2c0a5c; $purple-900: #380d75; @@ -103,6 +105,7 @@ $well-light-text-color: #5b6169; */ $gl-font-size: 14px; $gl-text-color: rgba(0, 0, 0, .85); +$gl-text-color-light: rgba(0, 0, 0, .7); $gl-text-color-secondary: rgba(0, 0, 0, .55); $gl-text-color-disabled: rgba(0, 0, 0, .35); $gl-text-color-inverted: rgba(255, 255, 255, 1.0); diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss new file mode 100644 index 00000000000..06c6025ed6b --- /dev/null +++ b/app/assets/stylesheets/new_sidebar.scss @@ -0,0 +1,121 @@ +@import "framework/variables"; +@import 'framework/tw_bootstrap_variables'; +@import "bootstrap/variables"; + +$new-sidebar-width: 220px; + +.page-with-new-sidebar { + + @media (min-width: $screen-sm-min) { + padding-left: $new-sidebar-width; + } + + .right-sidebar { + position: fixed; + height: 100%; + } +} + +.nav-sidebar { + position: fixed; + z-index: 400; + width: $new-sidebar-width; + top: 50px; + bottom: 0; + left: 0; + overflow: auto; + background-color: $gray-light; + border-right: 1px solid $border-color; + + ul { + padding: 0; + list-style: none; + } + + li { + a { + display: block; + padding: 12px 14px; + } + } + + a { + color: $gl-text-color; + text-decoration: none; + } +} + +.sidebar-sub-level-items { + display: none; + + > li { + a { + padding: 12px 24px; + color: $gl-text-color-light; + + &:hover { + color: $gl-text-color; + background-color: $border-color; + } + } + + &.active { + > a { + color: $purple-650; + font-weight: 600; + } + } + } +} + +.sidebar-top-level-items { + > li { + .badge { + float: right; + background-color: $border-color; + color: $gl-text-color; + } + + &.active { + > a { + background-color: $purple-600; + color: $white-light; + font-weight: 600; + } + + .badge { + background-color: $purple-700; + color: $white-light; + } + + .sidebar-sub-level-items { + background-color: $gray-normal; + border-left: 6px solid $purple-600; + display: block; + } + } + + &:not(.active) > a:hover { + background-color: $border-color; + + .badge { + transition: background-color 100ms linear; + background-color: $gray-normal; + } + } + } +} + + +// Make issue boards full-height now that sub-nav is gone + +.boards-list { + height: calc(100vh - 50px); + + @media (min-width: $screen-sm-min) { + height: 475px; // Needed for PhantomJS + // scss-lint:disable DuplicateProperty + height: calc(100vh - 120px); + // scss-lint:enable DuplicateProperty + } +} diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index f7a1d7e8844..eabc9a3b01c 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -32,6 +32,7 @@ - if show_new_nav? = stylesheet_link_tag "new_nav", media: "all" + = stylesheet_link_tag "new_sidebar", media: "all" = Gon::Base.render_data diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index b7df11681d3..62a76a1b00e 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,11 +1,15 @@ -.page-with-sidebar{ class: page_gutter_class } - - if defined?(nav) && nav - .layout-nav - .container-fluid - = render "layouts/nav/#{nav}" - - if content_for?(:sub_nav) - = yield :sub_nav - .content-wrapper{ class: layout_nav_class } +.page-with-sidebar{ class: "#{('page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar)} #{page_gutter_class}" } + - if show_new_nav? + - if defined?(nav) && nav + = render "layouts/nav/#{nav}" + - else + - if defined?(nav) && nav + .layout-nav + .container-fluid + = render "layouts/nav/#{nav}" + - if content_for?(:sub_nav) + = yield :sub_nav + .content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" } .alert-wrapper = render "layouts/broadcast" = render "layouts/flash" diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 87064cc9b3f..ae9eee215e0 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,5 +1,9 @@ - page_title "Admin Area" - header_title "Admin Area", admin_root_path -- nav "admin" +- if show_new_nav? + - nav "new_admin_sidebar" + - @new_sidebar = true +- else + - nav "admin" = render template: "layouts/application" diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index f06acc98ca1..35abfa0e80c 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,6 +1,10 @@ - page_title @group.name - page_description @group.description unless page_description - header_title group_title(@group) unless header_title -- nav "group" +- if show_new_nav? + - nav "new_group_sidebar" + - @new_sidebar = true +- else + - nav "group" = render template: "layouts/application" diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml new file mode 100644 index 00000000000..f995145917c --- /dev/null +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -0,0 +1,119 @@ +.nav-sidebar + %ul.sidebar-top-level-items + = nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do + = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do + %span + Overview + + %ul.sidebar-sub-level-items + = nav_link(controller: :dashboard, html_options: {class: 'home'}) do + = link_to admin_root_path, title: 'Overview' do + %span + Overview + = nav_link(controller: [:admin, :projects]) do + = link_to admin_projects_path, title: 'Projects' do + %span + Projects + = nav_link(controller: :users) do + = link_to admin_users_path, title: 'Users' do + %span + Users + = nav_link(controller: :groups) do + = link_to admin_groups_path, title: 'Groups' do + %span + Groups + = nav_link path: 'builds#index' do + = link_to admin_jobs_path, title: 'Jobs' do + %span + Jobs + = nav_link path: ['runners#index', 'runners#show'] do + = link_to admin_runners_path, title: 'Runners' do + %span + Runners + = nav_link path: 'cohorts#index' do + = link_to admin_cohorts_path, title: 'Cohorts' do + %span + Cohorts + + = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do + = link_to admin_conversational_development_index_path, title: 'Monitoring' do + %span + Monitoring + + %ul.sidebar-sub-level-items + = nav_link(controller: :conversational_development_index) do + = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do + %span + ConvDev Index + = nav_link(controller: :system_info) do + = link_to admin_system_info_path, title: 'System Info' do + %span + System Info + = nav_link(controller: :background_jobs) do + = link_to admin_background_jobs_path, title: 'Background Jobs' do + %span + Background Jobs + = nav_link(controller: :logs) do + = link_to admin_logs_path, title: 'Logs' do + %span + Logs + = nav_link(controller: :health_check) do + = link_to admin_health_check_path, title: 'Health Check' do + %span + Health Check + = nav_link(controller: :requests_profiles) do + = link_to admin_requests_profiles_path, title: 'Requests Profiles' do + %span + Requests Profiles + + = nav_link(controller: :broadcast_messages) do + = link_to admin_broadcast_messages_path, title: 'Messages' do + %span + Messages + = nav_link(controller: [:hooks, :hook_logs]) do + = link_to admin_hooks_path, title: 'Hooks' do + %span + System Hooks + + = nav_link(controller: :applications) do + = link_to admin_applications_path, title: 'Applications' do + %span + Applications + + = nav_link(controller: :abuse_reports) do + = link_to admin_abuse_reports_path, title: "Abuse Reports" do + %span + Abuse Reports + %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) + + - if akismet_enabled? + = nav_link(controller: :spam_logs) do + = link_to admin_spam_logs_path, title: "Spam Logs" do + %span + Spam Logs + + = nav_link(controller: :deploy_keys) do + = link_to admin_deploy_keys_path, title: 'Deploy Keys' do + %span + Deploy Keys + + = nav_link(controller: :services) do + = link_to admin_application_settings_services_path, title: 'Service Templates' do + %span + Service Templates + + = nav_link(controller: :labels) do + = link_to admin_labels_path, title: 'Labels' do + %span + Labels + + = nav_link(controller: :appearances) do + = link_to admin_appearances_path, title: 'Appearances' do + %span + Appearance + + %li.divider + = nav_link(controller: :application_settings) do + = link_to admin_application_settings_path, title: 'Settings' do + %span + Settings diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml new file mode 100644 index 00000000000..3b658e055b3 --- /dev/null +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -0,0 +1,56 @@ +.nav-sidebar + %ul.sidebar-top-level-items + = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do + = link_to group_path(@group), title: 'Home' do + %span + Group + + %ul.sidebar-sub-level-items + = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do + = link_to group_path(@group), title: 'Group Home' do + %span + Home + + = nav_link(path: 'groups#activity') do + = link_to activity_group_path(@group), title: 'Activity' do + %span + Activity + + = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do + = link_to issues_group_path(@group), title: 'Issues' do + %span + Issues + - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute + %span.badge.count= number_with_delimiter(issues.count) + + %ul.sidebar-sub-level-items + = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do + = link_to issues_group_path(@group), title: 'List' do + %span + List + + = nav_link(path: 'labels#index') do + = link_to group_labels_path(@group), title: 'Labels' do + %span + Labels + + = nav_link(path: 'milestones#index') do + = link_to group_milestones_path(@group), title: 'Milestones' do + %span + Milestones + + = nav_link(path: 'groups#merge_requests') do + = link_to merge_requests_group_path(@group), title: 'Merge Requests' do + %span + Merge Requests + - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute + %span.badge.count= number_with_delimiter(merge_requests.count) + = nav_link(path: 'group_members#index') do + = link_to group_group_members_path(@group), title: 'Members' do + %span + Members + - if current_user && can?(current_user, :admin_group, @group) + = nav_link(path: %w[groups#projects groups#edit]) do + = link_to edit_group_path(@group), title: 'Settings' do + %span + Settings diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml new file mode 100644 index 00000000000..37ffbbecca8 --- /dev/null +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -0,0 +1,49 @@ +.nav-sidebar + %ul.sidebar-top-level-items + = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do + = link_to profile_path, title: 'Profile Settings' do + %span + Profile + = nav_link(controller: [:accounts, :two_factor_auths]) do + = link_to profile_account_path, title: 'Account' do + %span + Account + - if current_application_settings.user_oauth_applications? + = nav_link(controller: 'oauth/applications') do + = link_to applications_profile_path, title: 'Applications' do + %span + Applications + = nav_link(controller: :chat_names) do + = link_to profile_chat_names_path, title: 'Chat' do + %span + Chat + = nav_link(controller: :personal_access_tokens) do + = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do + %span + Access Tokens + = nav_link(controller: :emails) do + = link_to profile_emails_path, title: 'Emails' do + %span + Emails + - unless current_user.ldap_user? + = nav_link(controller: :passwords) do + = link_to edit_profile_password_path, title: 'Password' do + %span + Password + = nav_link(controller: :notifications) do + = link_to profile_notifications_path, title: 'Notifications' do + %span + Notifications + + = nav_link(controller: :keys) do + = link_to profile_keys_path, title: 'SSH Keys' do + %span + SSH Keys + = nav_link(controller: :preferences) do + = link_to profile_preferences_path, title: 'Preferences' do + %span + Preferences + = nav_link(path: 'profiles#audit_log') do + = link_to audit_log_profile_path, title: 'Authentication log' do + %span + Authentication log diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml new file mode 100644 index 00000000000..f85781737aa --- /dev/null +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -0,0 +1,242 @@ +.nav-sidebar + - can_edit = can?(current_user, :admin_project, @project) + %ul.sidebar-top-level-items + = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do + = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do + %span + Project + + %ul.sidebar-sub-level-items + = nav_link(path: 'projects#show') do + = link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do + %span= _('Home') + + = nav_link(path: 'projects#activity') do + = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do + %span= _('Activity') + + - if can?(current_user, :read_cycle_analytics, @project) + = nav_link(path: 'cycle_analytics#show') do + = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do + %span= _('Cycle Analytics') + + - if project_nav_tab? :files + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do + = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do + %span + Repository + + %ul.sidebar-sub-level-items + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do + = link_to project_files_path(@project) do + #{ _('Files') } + + = nav_link(controller: [:commit, :commits]) do + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + #{ _('Commits') } + + = nav_link(html_options: {class: branches_tab_class}) do + = link_to namespace_project_branches_path(@project.namespace, @project) do + #{ _('Branches') } + + = nav_link(controller: [:tags, :releases]) do + = link_to namespace_project_tags_path(@project.namespace, @project) do + #{ _('Tags') } + + = nav_link(path: 'graphs#show') do + = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do + #{ _('Contributors') } + + = nav_link(controller: %w(network)) do + = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do + #{ s_('ProjectNetworkGraph|Graph') } + + = nav_link(controller: :compare) do + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do + #{ _('Compare') } + + = nav_link(path: 'graphs#charts') do + = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do + #{ _('Charts') } + + - if project_nav_tab? :container_registry + = nav_link(controller: %w[projects/registry/repositories]) do + = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do + %span + Registry + + - if project_nav_tab? :issues + = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do + = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do + %span + Issues + - if @project.default_issues_tracker? + %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) + + %ul.sidebar-sub-level-items + - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) + = nav_link(controller: :issues) do + = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do + %span + List + + = nav_link(controller: :boards) do + = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do + %span + Board + + - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) + = nav_link(controller: :merge_requests) do + = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do + %span + Merge Requests + + - if project_nav_tab? :labels + = nav_link(controller: :labels) do + = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + %span + Labels + + - if project_nav_tab? :milestones + = nav_link(controller: :milestones) do + = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + %span + Milestones + + - if project_nav_tab? :merge_requests + = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do + = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do + %span + Merge Requests + %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) + + - if project_nav_tab? :pipelines + = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do + %span + Pipelines + + %ul.sidebar-sub-level-items + - if project_nav_tab? :pipelines + = nav_link(path: ['pipelines#index', 'pipelines#show']) do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do + %span + Pipelines + + - if project_nav_tab? :builds + = nav_link(controller: [:jobs, :artifacts]) do + = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do + %span + Jobs + + - if project_nav_tab? :pipelines + = nav_link(controller: :pipeline_schedules) do + = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do + %span + Schedules + + - if project_nav_tab? :environments + = nav_link(controller: :environments) do + = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do + %span + Environments + + - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? + = nav_link(path: 'pipelines#charts') do + = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do + %span + Charts + + - if project_nav_tab? :wiki + = nav_link(controller: :wikis) do + = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do + %span + Wiki + + - if project_nav_tab? :snippets + = nav_link(controller: :snippets) do + = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do + %span + Snippets + + - if project_nav_tab? :settings + = nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do + = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do + %span + Settings + + %ul.sidebar-sub-level-items + - can_edit = can?(current_user, :admin_project, @project) + - if can_edit + = nav_link(controller: :projects) do + = link_to edit_project_path(@project), title: 'General' do + %span + General + = nav_link(controller: :members) do + = link_to project_settings_members_path(@project), title: 'Members' do + %span + Members + - if can_edit + = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do + = link_to project_settings_integrations_path(@project), title: 'Integrations' do + %span + Integrations + = nav_link(controller: :repository) do + = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do + %span + Repository + - if @project.feature_available?(:builds, current_user) + = nav_link(controller: :ci_cd) do + = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'Pipelines' do + %span + Pipelines + - if Gitlab.config.pages.enabled + = nav_link(controller: :pages) do + = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do + %span + Pages + + - else + = nav_link(path: %w[members#show]) do + = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do + %span + Settings + + -# Shortcut to Project > Activity + %li.hidden + = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do + %span + Activity + + -# Shortcut to Repository > Graph (formerly, Network) + - if project_nav_tab? :network + %li.hidden + = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do + Graph + + -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs") + - unless @project.empty_repo? + %li.hidden + = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do + Charts + + -# Shortcut to Issues > New Issue + %li.hidden + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do + Create a new issue + + -# Shortcut to Pipelines > Jobs + - if project_nav_tab? :builds + %li.hidden + = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do + Jobs + + -# Shortcut to commits page + - if project_nav_tab? :commits + %li.hidden + = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do + Commits + + -# Shortcut to issue boards + %li.hidden + = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 0ee8a57dbd4..c365839e605 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,6 +1,10 @@ - page_title "User Settings" - header_title "User Settings", profile_path unless header_title - sidebar "dashboard" -- nav "profile" +- if show_new_nav? + - nav "new_profile_sidebar" + - @new_sidebar = true +- else + - nav "profile" = render template: "layouts/application" diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 3f5b0c54e50..4458c3c2c23 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -1,7 +1,11 @@ - page_title @project.name_with_namespace - page_description @project.description unless page_description - header_title project_title(@project) unless header_title -- nav "project" +- if show_new_nav? + - nav "new_project_sidebar" + - @new_sidebar = true +- else + - nav "project" - content_for :project_javascripts do - project = @target_project || @project diff --git a/config/application.rb b/config/application.rb index 12242c3b0f5..3f39170a123 100644 --- a/config/application.rb +++ b/config/application.rb @@ -110,6 +110,7 @@ module Gitlab config.assets.precompile << "vendor/assets/fonts/*" config.assets.precompile << "test.css" config.assets.precompile << "new_nav.css" + config.assets.precompile << "new_sidebar.css" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' -- cgit v1.2.1 From 70dd2e6b3a42a9ab06b710483d4f9f8c7de2280c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 28 Jun 2017 09:43:05 +0100 Subject: Fix find file breadcrumb styling Closes #34427 --- app/assets/stylesheets/pages/projects.scss | 1 + app/assets/stylesheets/pages/tree.scss | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 562ecbc6986..ba530bf7f9b 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -377,6 +377,7 @@ a.deploy-project-label { } .breadcrumb.repo-breadcrumb { + flex: 1; padding: 0; background: transparent; border: none; diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index ce1a13c6afa..9b2ed0d68a1 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -70,7 +70,8 @@ } .file-finder { - width: 50%; + max-width: 500px; + width: 100%; .file-finder-input { width: 95%; -- cgit v1.2.1 From 9aa832285c10f627a67b5f49870d694b27fc37c2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 19 Jun 2017 15:56:22 +0100 Subject: Fixed issue boards closed list not listing all issues This was a regression caused by the backlog list being re-added Closes #33850 --- app/services/boards/issues/list_service.rb | 12 ++++++++++-- changelogs/unreleased/issue-boards-closed-list-all.yml | 4 ++++ spec/services/boards/issues/list_service_spec.rb | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/issue-boards-closed-list-all.yml diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 418fa9afd6e..a1d67cbc244 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -3,7 +3,7 @@ module Boards class ListService < BaseService def execute issues = IssuesFinder.new(current_user, filter_params).execute - issues = without_board_labels(issues) unless movable_list? + issues = without_board_labels(issues) unless movable_list? || closed_list? issues = with_list_label(issues) if movable_list? issues.order_by_position_and_priority end @@ -21,7 +21,15 @@ module Boards end def movable_list? - @movable_list ||= list.present? && list.movable? + return @movable_list if defined?(@movable_list) + + @movable_list = list.present? && list.movable? + end + + def closed_list? + return @closed_list if defined?(@closed_list) + + @closed_list = list.present? && list.closed? end def filter_params diff --git a/changelogs/unreleased/issue-boards-closed-list-all.yml b/changelogs/unreleased/issue-boards-closed-list-all.yml new file mode 100644 index 00000000000..7643864150d --- /dev/null +++ b/changelogs/unreleased/issue-boards-closed-list-all.yml @@ -0,0 +1,4 @@ +--- +title: Fixed issue boards closed list not showing all closed issues +merge_request: +author: diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index a1e220c2322..a66cc2cd6e9 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -67,7 +67,7 @@ describe Boards::Issues::ListService, services: true do issues = described_class.new(project, user, params).execute - expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1] + expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1] end it 'returns opened issues that have label list applied when listing issues from a label list' do -- cgit v1.2.1 From 8f13c1ebd9671058a878b21ce3f40d255e8591df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 27 Jun 2017 15:00:10 +0200 Subject: Use a slightly cleaner approach to stub ENV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/support/stub_env.rb | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb index 2999bcd9fb1..b8928867174 100644 --- a/spec/support/stub_env.rb +++ b/spec/support/stub_env.rb @@ -1,15 +1,33 @@ +# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb module StubENV - def stub_env(key, value) - allow(ENV).to receive(:[]).and_call_original unless @env_already_stubbed - @env_already_stubbed ||= true + def stub_env(key_or_hash, value = nil) + init_stub unless env_stubbed? + if key_or_hash.is_a? Hash + key_or_hash.each { |k, v| add_stubbed_value(k, v) } + else + add_stubbed_value key_or_hash, value + end + end + + private + + STUBBED_KEY = '__STUBBED__'.freeze + + def add_stubbed_value(key, value) allow(ENV).to receive(:[]).with(key).and_return(value) + allow(ENV).to receive(:fetch).with(key).and_return(value) + allow(ENV).to receive(:fetch).with(key, anything()) do |_, default_val| + value || default_val + end + end + + def env_stubbed? + ENV[STUBBED_KEY] end -end -# It's possible that the state of the class variables are not reset across -# test runs. -RSpec.configure do |config| - config.after(:each) do - @env_already_stubbed = nil + def init_stub + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:fetch).and_call_original + add_stubbed_value(STUBBED_KEY, true) end end -- cgit v1.2.1 From 489d8e11b39f7eae6b36d2bdcf3098548ed62e53 Mon Sep 17 00:00:00 2001 From: "Jacob Vosmaer (GitLab)" Date: Wed, 28 Jun 2017 09:30:02 +0000 Subject: Vendor the gitlab-git-test repo --- .gitlab-ci.yml | 6 ++++ spec/support/generate-seed-repo-rb | 2 +- spec/support/gitlab-git-test.git/HEAD | 1 + spec/support/gitlab-git-test.git/README.md | 16 +++++++++ spec/support/gitlab-git-test.git/config | 7 ++++ ...ck-691247af2a6acb0b63b73ac0cb90540e93614043.idx | Bin 0 -> 5496 bytes ...k-691247af2a6acb0b63b73ac0cb90540e93614043.pack | Bin 0 -> 381502 bytes spec/support/gitlab-git-test.git/packed-refs | 18 ++++++++++ .../gitlab-git-test.git/refs/heads/.gitkeep | 0 .../support/gitlab-git-test.git/refs/tags/.gitkeep | 0 spec/support/prepare-gitlab-git-test-for-commit | 17 +++++++++ spec/support/seed_helper.rb | 2 +- spec/support/unpack-gitlab-git-test | 38 +++++++++++++++++++++ 13 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 spec/support/gitlab-git-test.git/HEAD create mode 100644 spec/support/gitlab-git-test.git/README.md create mode 100644 spec/support/gitlab-git-test.git/config create mode 100644 spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx create mode 100644 spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack create mode 100644 spec/support/gitlab-git-test.git/packed-refs create mode 100644 spec/support/gitlab-git-test.git/refs/heads/.gitkeep create mode 100644 spec/support/gitlab-git-test.git/refs/tags/.gitkeep create mode 100755 spec/support/prepare-gitlab-git-test-for-commit create mode 100755 spec/support/unpack-gitlab-git-test diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 08aef3dd8ee..d97828431c5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -550,3 +550,9 @@ cache gems: only: - master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee + +gitlab_git_test: + variables: + SETUP_DB: "false" + script: + - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb index 7335f74c0e9..c89389b90ca 100755 --- a/spec/support/generate-seed-repo-rb +++ b/spec/support/generate-seed-repo-rb @@ -15,7 +15,7 @@ require 'erb' require 'tempfile' -SOURCE = 'https://gitlab.com/gitlab-org/gitlab-git-test.git'.freeze +SOURCE = File.expand_path('../gitlab-git-test.git', __FILE__).freeze SCRIPT_NAME = 'generate-seed-repo-rb'.freeze REPO_NAME = 'gitlab-git-test.git'.freeze diff --git a/spec/support/gitlab-git-test.git/HEAD b/spec/support/gitlab-git-test.git/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/spec/support/gitlab-git-test.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/spec/support/gitlab-git-test.git/README.md b/spec/support/gitlab-git-test.git/README.md new file mode 100644 index 00000000000..f072cd421be --- /dev/null +++ b/spec/support/gitlab-git-test.git/README.md @@ -0,0 +1,16 @@ +# Gitlab::Git test repository + +This repository is used by (some of) the tests in spec/lib/gitlab/git. + +Do not add new large files to this repository. Otherwise we needlessly +inflate the size of the gitlab-ce repository. + +## How to make changes to this repository + +- (if needed) clone `https://gitlab.com/gitlab-org/gitlab-ce.git` to your local machine +- clone `gitlab-ce/spec/support/gitlab-git-test.git` locally (i.e. clone from your hard drive, not from the internet) +- make changes in your local clone of gitlab-git-test +- run `git push` which will push to your local source `gitlab-ce/spec/support/gitlab-git-test.git` +- in gitlab-ce: run `spec/support/prepare-gitlab-git-test-for-commit` +- in gitlab-ce: `git add spec/support/seed_repo.rb spec/support/gitlab-git-test.git` +- commit your changes in gitlab-ce diff --git a/spec/support/gitlab-git-test.git/config b/spec/support/gitlab-git-test.git/config new file mode 100644 index 00000000000..03e2d1b1e0f --- /dev/null +++ b/spec/support/gitlab-git-test.git/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + precomposeunicode = true +[remote "origin"] + url = https://gitlab.com/gitlab-org/gitlab-git-test.git diff --git a/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx new file mode 100644 index 00000000000..2253da798c4 Binary files /dev/null and b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.idx differ diff --git a/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack new file mode 100644 index 00000000000..3a61107c5b1 Binary files /dev/null and b/spec/support/gitlab-git-test.git/objects/pack/pack-691247af2a6acb0b63b73ac0cb90540e93614043.pack differ diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs new file mode 100644 index 00000000000..ce5ab1f705b --- /dev/null +++ b/spec/support/gitlab-git-test.git/packed-refs @@ -0,0 +1,18 @@ +# pack-refs with: peeled fully-peeled +0b4bc9a49b562e85de7cc9e834518ea6828729b9 refs/heads/feature +12d65c8dd2b2676fa3ac47d955accc085a37a9c1 refs/heads/fix +6473c90867124755509e100d0d35ebdc85a0b6ae refs/heads/fix-blob-path +58fa1a3af4de73ea83fe25a1ef1db8e0c56f67e5 refs/heads/fix-existing-submodule-dir +40f4a7a617393735a95a0bb67b08385bc1e7c66d refs/heads/fix-mode +9abd6a8c113a2dd76df3fdb3d58a8cec6db75f8d refs/heads/gitattributes +46e1395e609395de004cacd4b142865ab0e52a29 refs/heads/gitattributes-updated +4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/master +5937ac0a7beb003549fc5fd26fc247adbce4a52e refs/heads/merge-test +f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0 +^6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 +8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b refs/tags/v1.1.0 +^5937ac0a7beb003549fc5fd26fc247adbce4a52e +10d64eed7760f2811ee2d64b44f1f7d3b364f17b refs/tags/v1.2.0 +^eb49186cfa5c4338011f5f590fac11bd66c5c631 +2ac1f24e253e08135507d0830508febaaccf02ee refs/tags/v1.2.1 +^fa1b1e6c004a68b7d8763b86455da9e6b23e36d6 diff --git a/spec/support/gitlab-git-test.git/refs/heads/.gitkeep b/spec/support/gitlab-git-test.git/refs/heads/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/spec/support/gitlab-git-test.git/refs/tags/.gitkeep b/spec/support/gitlab-git-test.git/refs/tags/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/spec/support/prepare-gitlab-git-test-for-commit b/spec/support/prepare-gitlab-git-test-for-commit new file mode 100755 index 00000000000..3047786a599 --- /dev/null +++ b/spec/support/prepare-gitlab-git-test-for-commit @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +abort unless [ + system('spec/support/generate-seed-repo-rb', out: 'spec/support/seed_repo.rb'), + system('spec/support/unpack-gitlab-git-test') +].all? + +exit if ARGV.first != '--check-for-changes' + +git_status = IO.popen(%w[git status --porcelain], &:read) +abort unless $?.success? + +puts git_status + +if git_status.lines.grep(%r{^.. spec/support/gitlab-git-test.git}).any? + abort "error: detected changes in gitlab-git-test.git" +end diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb index 47b5f556e66..8731847592b 100644 --- a/spec/support/seed_helper.rb +++ b/spec/support/seed_helper.rb @@ -9,7 +9,7 @@ TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'.freeze TEST_BROKEN_REPO_PATH = 'broken-repo.git'.freeze module SeedHelper - GITLAB_GIT_TEST_REPO_URL = ENV.fetch('GITLAB_GIT_TEST_REPO_URL', 'https://gitlab.com/gitlab-org/gitlab-git-test.git').freeze + GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __FILE__).freeze def ensure_seeds if File.exist?(SEED_STORAGE_PATH) diff --git a/spec/support/unpack-gitlab-git-test b/spec/support/unpack-gitlab-git-test new file mode 100755 index 00000000000..d5b4912457d --- /dev/null +++ b/spec/support/unpack-gitlab-git-test @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +require 'fileutils' + +REPO = 'spec/support/gitlab-git-test.git'.freeze +PACK_DIR = REPO + '/objects/pack' +GIT = %W[git --git-dir=#{REPO}].freeze +BASE_PACK = 'pack-691247af2a6acb0b63b73ac0cb90540e93614043'.freeze + +def main + unpack + # We want to store the refs in a packed-refs file because if we don't + # they can get mangled by filesystems. + abort unless system(*GIT, *%w[pack-refs --all]) + abort unless system(*GIT, 'fsck') +end + +# We don't want contributors to commit new pack files because those +# create unnecessary churn. +def unpack + pack_files = Dir[File.join(PACK_DIR, '*')].reject do |pack| + pack.start_with?(File.join(PACK_DIR, BASE_PACK)) + end + return if pack_files.empty? + + pack_files.each do |pack| + unless pack.end_with?('.pack') + FileUtils.rm(pack) + next + end + + File.open(pack, 'rb') do |open_pack| + File.unlink(pack) + abort unless system(*GIT, 'unpack-objects', in: open_pack) + end + end +end + +main -- cgit v1.2.1 From 9da107f00bd17a525064f415ec0dcfd92b4907a0 Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Mon, 12 Jun 2017 13:40:02 +0100 Subject: refactors Project#search namespace join --- app/models/project.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/project.rb b/app/models/project.rb index 1176bec8873..298ba512a3b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -377,6 +377,7 @@ class Project < ActiveRecord::Base .or(ptable[:description].matches(pattern)) ) + namespaces = unscoped.select(:id) .joins(:namespace) .where(ntable[:name].matches(pattern)) -- cgit v1.2.1 From 8e8eb97861a8a93625590274af8382e9333cbf91 Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Tue, 13 Jun 2017 16:32:50 +0100 Subject: Removes pending delete from filter scopes --- app/controllers/projects_controller.rb | 2 +- app/helpers/search_helper.rb | 4 ++-- app/models/project.rb | 5 ++--- app/views/shared/projects/_list.html.haml | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 5480814874b..450895cdf3a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -97,7 +97,7 @@ class ProjectsController < Projects::ApplicationController end if @project.pending_delete? - flash[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name } + flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name } end respond_to do |format| diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8f15904f068..9c46035057f 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -97,8 +97,8 @@ module SearchHelper # Autocomplete results for the current user's projects def projects_autocomplete(term, limit = 5) - current_user.authorized_projects.search_by_title(term) - .sorted_by_stars.non_archived.limit(limit).map do |p| + current_user.authorized_projects.search_by_title(term). + sorted_by_stars.non_archived.limit(limit).map do |p| { category: "Projects", id: p.id, diff --git a/app/models/project.rb b/app/models/project.rb index 298ba512a3b..2ba7f683d12 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -222,9 +222,8 @@ class Project < ActiveRecord::Base has_many :uploads, as: :model, dependent: :destroy # Scopes - default_scope { where(pending_delete: false) } - - scope :with_deleted, -> { unscope(where: :pending_delete) } + scope :with_deleted, -> { where(pending_delete: true) } + scope :without_deleted, -> { where(pending_delete: false) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index aaffc0927eb..7ed6c622558 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -13,7 +13,7 @@ - if projects.any? %ul.projects-list - projects.each_with_index do |project, i| - - css_class = (i >= projects_limit) ? 'hide' : nil + - css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil = render "shared/projects/project", project: project, skip_namespace: skip_namespace, avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, forks: forks, show_last_commit_as_description: show_last_commit_as_description -- cgit v1.2.1 From 34f57b462bc14ad194cf890a4585d403bdbde55c Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Thu, 15 Jun 2017 13:06:49 +0100 Subject: Fix current feature related specs --- app/finders/todos_finder.rb | 5 ++++- app/models/notification_setting.rb | 2 +- app/models/project.rb | 2 +- app/services/ci/register_job_service.rb | 8 ++++---- app/services/groups/destroy_service.rb | 2 +- app/services/users/destroy_service.rb | 2 +- app/workers/stuck_ci_jobs_worker.rb | 2 +- .../30708-stop-using-deleted-at-to-filter-namespaces.yml | 4 ++++ lib/api/helpers/runner.rb | 3 ++- lib/ci/api/helpers.rb | 3 ++- spec/models/project_spec.rb | 9 --------- 11 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index c358f23f541..5a4c5767be5 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -78,11 +78,14 @@ class TodosFinder end def project + return nil if @project&.pending_delete? return @project if defined?(@project) if project? @project = Project.find(params[:project_id]) + @project = nil if @project.pending_delete? + unless Ability.allowed?(current_user, :read_project, @project) @project = nil end @@ -95,7 +98,7 @@ class TodosFinder def projects(items) item_project_ids = items.reorder(nil).select(:project_id) - ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute + ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute.without_deleted end def type? diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index b0df7aeb323..81844b1e2ca 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -19,7 +19,7 @@ class NotificationSetting < ActiveRecord::Base # pending delete). # scope :for_projects, -> do - includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil }) + includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil, pending_delete: true }) end EMAIL_EVENTS = [ diff --git a/app/models/project.rb b/app/models/project.rb index 2ba7f683d12..40a8e7f07c6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1456,7 +1456,7 @@ class Project < ActiveRecord::Base def pending_delete_twin return false unless path - Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace) + Project.with_deleted.find_by_full_path(path_with_namespace) end ## diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index af84d4c7427..27bdb6b9a2f 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -54,9 +54,9 @@ module Ci def builds_for_shared_runner new_builds. # don't run projects which have not enabled shared runners and builds - joins(:project).where(projects: { shared_runners_enabled: true }) - .joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id') - .where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). + joins(:project).where(projects: { shared_runners_enabled: true, pending_delete: false }). + joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id'). + where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). # Implement fair scheduling # this returns builds that are ordered by number of running builds @@ -66,7 +66,7 @@ module Ci end def builds_for_specific_runner - new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC') + new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('created_at ASC') end def running_builds_for_shared_runners diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index 497fdb09cdc..d40d280140a 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -10,7 +10,7 @@ module Groups def execute group.prepare_for_destroy - group.projects.with_deleted.each do |project| + group.projects.each do |project| # Execute the destruction of the models immediately to ensure atomic cleanup. # Skip repository removal because we remove directory with namespace # that contain all these repositories diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 673afb8b5b9..9d7237c2fbb 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -35,7 +35,7 @@ module Users Groups::DestroyService.new(group, current_user).execute end - user.personal_projects.with_deleted.each do |project| + user.personal_projects.each do |project| # Skip repository removal because we remove directory with namespace # that contain all this repositories ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb index ae8c980c9e4..8b0cfcc8af8 100644 --- a/app/workers/stuck_ci_jobs_worker.rb +++ b/app/workers/stuck_ci_jobs_worker.rb @@ -45,7 +45,7 @@ class StuckCiJobsWorker def search(status, timeout) builds = Ci::Build.where(status: status).where('ci_builds.updated_at < ?', timeout.ago) - builds.joins(:project).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build| + builds.joins(:project).merge(Project.without_deleted).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build| yield(build) end end diff --git a/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml new file mode 100644 index 00000000000..83ce3fb4d0a --- /dev/null +++ b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml @@ -0,0 +1,4 @@ +--- +title: Removes deleted_at and pending_delete occurrences in Project related queries +merge_request: 12091 +author: diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 1369b021ea4..f8645e364ce 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -46,7 +46,8 @@ module API yield if block_given? - forbidden!('Project has been deleted!') unless job.project + project = job.project + forbidden!('Project has been deleted!') if project.nil? || project.pending_delete? forbidden!('Job has been erased!') if job.erased? end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 5109dc9670f..a40b6ab6c9f 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -28,7 +28,8 @@ module Ci yield if block_given? - forbidden!('Project has been deleted!') unless build.project + project = build.project + forbidden!('Project has been deleted!') if project.nil? || project.pending_delete? forbidden!('Build has been erased!') if build.erased? end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d7fcadb895e..1dbe901b004 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -284,15 +284,6 @@ describe Project, models: true do end end - describe 'default_scope' do - it 'excludes projects pending deletion from the results' do - project = create(:empty_project) - create(:empty_project, pending_delete: true) - - expect(Project.all).to eq [project] - end - end - describe 'project token' do it 'sets an random token if none provided' do project = FactoryGirl.create :empty_project, runners_token: '' -- cgit v1.2.1 From da07d851293c772c7a12e8b2356893aec2b7fb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Wed, 28 Jun 2017 18:39:19 +0800 Subject: delete Esperanto translations of JSON files generated from PO files --- app/assets/javascripts/locale/eo/app.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 app/assets/javascripts/locale/eo/app.js diff --git a/app/assets/javascripts/locale/eo/app.js b/app/assets/javascripts/locale/eo/app.js deleted file mode 100644 index 55f000e9b88..00000000000 --- a/app/assets/javascripts/locale/eo/app.js +++ /dev/null @@ -1 +0,0 @@ -var locales = locales || {}; locales['eo'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 21:59-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-20 06:24-0400","Last-Translator":"Lyubomir Vasilev ","Language-Team":"Esperanto (https://translate.zanata.org/project/view/GitLab)","Language":"eo","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"eo","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} enmetis %{commit_timeago}"],"About auto deploy":["Pri la aŭtomata disponigado"],"Active":["Aktiva"],"Activity":["Aktiveco"],"Add Changelog":["Aldoni liston de ŝanĝoj"],"Add Contribution guide":["Aldoni gvidliniojn por kontribuado"],"Add License":["Aldoni rajtigilon"],"Add an SSH key to your profile to pull or push via SSH.":["Aldonu SSH-ŝlosilon al via profilo por ebligi al vi eltiri kaj alpuŝi per SSH."],"Add new directory":["Aldoni novan dosierujon"],"Archived project! Repository is read-only":["Arkivita projekto! La deponejo permesas nur legadon"],"Are you sure you want to delete this pipeline schedule?":["Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?"],"Attach a file by drag & drop or %{upload_link}":["Alkroĉu dosieron per ŝovmetado aŭ %{upload_link}"],"Branch":["Branĉo","Branĉoj"],"Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":["La branĉo %{branch_name} estis kreita. Por agordi aŭtomatan disponigadon, bonvolu elekti Yaml-ŝablonon por GitLab CI kaj enmeti viajn ŝanĝojn. %{link_to_autodeploy_doc}"],"Branches":["Branĉoj"],"Browse files":["Elekti dosierojn"],"ByAuthor|by":["de"],"CI configuration":["Agordoj de seninterrompa integrado"],"Cancel":["Nuligi"],"ChangeTypeActionLabel|Pick into branch":["Elekti en branĉon"],"ChangeTypeActionLabel|Revert in branch":["Malfari en branĉo"],"ChangeTypeAction|Cherry-pick":["Precize elekti"],"ChangeTypeAction|Revert":["Malfari"],"Changelog":["Listo de ŝanĝoj"],"Charts":["Diagramoj"],"Cherry-pick this commit":["Precize elekti ĉi tiun kunmetadon"],"Cherry-pick this merge request":["Precize elekti ĉi tiun peton pri kunfando"],"CiStatusLabel|canceled":["nuligita"],"CiStatusLabel|created":["kreita"],"CiStatusLabel|failed":["malsukcesa"],"CiStatusLabel|manual action":["mana ago"],"CiStatusLabel|passed":["sukcesa"],"CiStatusLabel|passed with warnings":["sukcesa, kun avertoj"],"CiStatusLabel|pending":["okazonta"],"CiStatusLabel|skipped":["transsaltita"],"CiStatusLabel|waiting for manual action":["atendanta manan agon"],"CiStatusText|blocked":["blokita"],"CiStatusText|canceled":["nuligita"],"CiStatusText|created":["kreita"],"CiStatusText|failed":["malsukcesa"],"CiStatusText|manual":["mana"],"CiStatusText|passed":["sukcesa"],"CiStatusText|pending":["okazonta"],"CiStatusText|skipped":["transsaltita"],"CiStatus|running":["plenumiĝanta"],"Commit":["Enmetado","Enmetadoj"],"Commit message":["Mesaĝo pri la enmetado"],"CommitBoxTitle|Commit":["Enmeti"],"CommitMessage|Add %{file_name}":["Aldoni „%{file_name}“"],"Commits":["Enmetadoj"],"Commits|History":["Historio"],"Committed by":["Enmetita de"],"Compare":["Kompari"],"Contribution guide":["Gvidlinioj por kontribuado"],"Contributors":["Kontribuantoj"],"Copy URL to clipboard":["Kopii la adreson en la kopibufron"],"Copy commit SHA to clipboard":["Kopii la identigilon de la enmetado"],"Create New Directory":["Krei novan dosierujon"],"Create directory":["Krei dosierujon"],"Create empty bare repository":["Krei malplenan deponejon"],"Create merge request":["Krei peton pri kunfando"],"Create new...":["Krei novan…"],"CreateNewFork|Fork":["Disbranĉigi"],"CreateTag|Tag":["Etikedo"],"Cron Timezone":["Horzono por Cron"],"Cron syntax":["La sintakso de Cron"],"Custom notification events":["Propraj sciigaj eventoj"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["La propraj sciigaj niveloj estas la samaj kiel la niveloj de partoprenado. Uzante la proprajn sciigajn nivelojn, vi ricevos ankaŭ sciigojn por elektitaj de vi eventoj. Por lerni pli, bonvolu vidi %{notification_link}."],"Cycle Analytics":["Cikla analizo"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["La cikla analizo esploras kiom da tempo necesas por disvolvi ideon ĝis ĝi fariĝos realaĵo."],"CycleAnalyticsStage|Code":["Programado"],"CycleAnalyticsStage|Issue":["Problemo"],"CycleAnalyticsStage|Plan":["Plano"],"CycleAnalyticsStage|Production":["Eldonado"],"CycleAnalyticsStage|Review":["Kontrolo"],"CycleAnalyticsStage|Staging":["Preparo por eldono"],"CycleAnalyticsStage|Test":["Testado"],"Define a custom pattern with cron syntax":["Difini propran ŝablonon, uzante la sintakson de Cron"],"Delete":["Forigi"],"Deploy":["Disponigado","Disponigadoj"],"Description":["Priskribo"],"Directory name":["Nomo de dosierujo"],"Don't show again":["Ne montru denove"],"Download":["Elŝuti"],"Download tar":["Elŝuti en formato „tar“"],"Download tar.bz2":["Elŝuti en formato „tar.bz2“"],"Download tar.gz":["Elŝuti en formato „tar.gz“"],"Download zip":["Elŝuti en formato „zip“"],"DownloadArtifacts|Download":["Elŝuti"],"DownloadCommit|Email Patches":["Sendi flikaĵojn per retpoŝto"],"DownloadCommit|Plain Diff":["Normala dosiero kun diferencoj"],"DownloadSource|Download":["Elŝuti"],"Edit":["Redakti"],"Edit Pipeline Schedule %{id}":["Redakti ĉenstablan planon %{id}"],"Every day (at 4:00am)":["Ĉiutage (je 4:00)"],"Every month (on the 1st at 4:00am)":["Ĉiumonate (en la 1a de la monato, je 4:00)"],"Every week (Sundays at 4:00am)":["Ĉiusemajne (en dimanĉo, je 4:00)"],"Failed to change the owner":["Ne eblas ŝanĝi la posedanton"],"Failed to remove the pipeline schedule":["Ne eblas forigi la ĉenstablan planon"],"Files":["Dosieroj"],"Find by path":["Trovi per dosierindiko"],"Find file":["Trovi dosieron"],"FirstPushedBy|First":["Unue"],"FirstPushedBy|pushed by":["alpuŝita de"],"Fork":["Disbranĉigo","Disbranĉigoj"],"ForkedFromProjectPath|Forked from":["Disbranĉigita el"],"From issue creation until deploy to production":["De la kreado de la problemo ĝis la disponigado en la publika versio"],"From merge request merge until deploy to production":["De la kunfandado de la peto pri kunfando ĝis la disponigado en la publika versio"],"Go to your fork":["Al via disbranĉigo"],"GoToYourFork|Fork":["Disbranĉigo"],"Home":["Hejmo"],"Housekeeping successfully started":["La refreŝigo komenciĝis sukcese"],"Import repository":["Enporti deponejon"],"Interval Pattern":["Intervala ŝablono"],"Introducing Cycle Analytics":["Ni prezentas al vi la ciklan analizon"],"LFSStatus|Disabled":["Malŝaltita"],"LFSStatus|Enabled":["Ŝaltita"],"Last %d day":["La lasta %d tago","La lastaj %d tagoj"],"Last Pipeline":["Lasta ĉenstablo"],"Last Update":["Lasta ĝisdatigo"],"Last commit":["Lasta enmetado"],"Learn more in the":["Lernu pli en la"],"Learn more in the|pipeline schedules documentation":["dokumentado pri ĉenstablaj planoj"],"Leave group":["Forlasi la grupon"],"Leave project":["Forlasi la projekton"],"Limited to showing %d event at most":["Limigita al montrado de ne pli ol %d evento","Limigita al montrado de ne pli ol %d eventoj"],"Median":["Mediano"],"MissingSSHKeyWarningLink|add an SSH key":["aldonos SSH-ŝlosilon"],"New Issue":["Nova problemo","Novaj problemoj"],"New Pipeline Schedule":["Nova ĉenstabla plano"],"New branch":["Nova branĉo"],"New directory":["Nova dosierujo"],"New file":["Nova dosiero"],"New issue":["Nova problemo"],"New merge request":["Nova peto pri kunfando"],"New schedule":["Nova plano"],"New snippet":["Nova kodaĵo"],"New tag":["Nova etikedo"],"No repository":["Ne estas deponejo"],"No schedules":["Ne estas planoj"],"Not available":["Ne disponebla"],"Not enough data":["Ne estas sufiĉe da datenoj"],"Notification events":["Sciigaj eventoj"],"NotificationEvent|Close issue":["Fermi problemon"],"NotificationEvent|Close merge request":["Fermi peton pri kunfando"],"NotificationEvent|Failed pipeline":["Malsukcesa ĉenstablo"],"NotificationEvent|Merge merge request":["Apliki peton pri kunfando"],"NotificationEvent|New issue":["Nova problemo"],"NotificationEvent|New merge request":["Nova peto pri kunfando"],"NotificationEvent|New note":["Nova noto"],"NotificationEvent|Reassign issue":["Reatribui problemon"],"NotificationEvent|Reassign merge request":["Reatribui peton pri kunfando"],"NotificationEvent|Reopen issue":["Remalfermi problemon"],"NotificationEvent|Successful pipeline":["Sukcesa ĉenstablo"],"NotificationLevel|Custom":["Propraj"],"NotificationLevel|Disabled":["Malŝaltitaj"],"NotificationLevel|Global":["Ĝeneralaj"],"NotificationLevel|On mention":["Ĉe mencio"],"NotificationLevel|Participate":["Partoprenado"],"NotificationLevel|Watch":["Rigardado"],"OfSearchInADropdown|Filter":["Filtrilo"],"OpenedNDaysAgo|Opened":["Malfermita"],"Options":["Opcioj"],"Owner":["Posedanto"],"Pipeline":["Ĉenstablo"],"Pipeline Health":["Stato"],"Pipeline Schedule":["Ĉenstabla plano"],"Pipeline Schedules":["Ĉenstablaj planoj"],"PipelineSchedules|Activated":["Ŝaltita"],"PipelineSchedules|Active":["Ŝaltitaj"],"PipelineSchedules|All":["Ĉiuj"],"PipelineSchedules|Inactive":["Malŝaltitaj"],"PipelineSchedules|Next Run":["Sekvanta plenumo"],"PipelineSchedules|None":["Nenio"],"PipelineSchedules|Provide a short description for this pipeline":["Entajpu mallongan priskribon pri ĉi tiu ĉenstablo"],"PipelineSchedules|Take ownership":["Akiri posedon"],"PipelineSchedules|Target":["Celo"],"PipelineSheduleIntervalPattern|Custom":["Propra"],"Pipeline|with stage":["kun etapo"],"Pipeline|with stages":["kun etapoj"],"Project '%{project_name}' queued for deletion.":["La projekto „%{project_name}“ estis alvicigita por forigado."],"Project '%{project_name}' was successfully created.":["La projekto „%{project_name}“ estis sukcese kreita."],"Project '%{project_name}' was successfully updated.":["La projekto „%{project_name}“ estis sukcese ĝisdatigita."],"Project '%{project_name}' will be deleted.":["La projekto „%{project_name}“ estos forigita."],"Project access must be granted explicitly to each user.":["Ĉiu uzanto devas akiri propran atingon al la projekto."],"Project export could not be deleted.":["Ne eblas forigi la projektan elporton."],"Project export has been deleted.":["La projekta elporto estis forigita."],"Project export link has expired. Please generate a new export from your project settings.":["La ligilo por la projekta elporto eksvalidiĝis. Bonvolu krei novan elporton en la agordoj de la projekto."],"Project export started. A download link will be sent by email.":["La elporto de la projekto komenciĝis. Vi ricevos ligilon per retpoŝto por elŝuti la datenoj."],"Project home":["Hejmo de la projekto"],"ProjectFeature|Disabled":["Malŝaltita"],"ProjectFeature|Everyone with access":["Ĉiu, kiu havas atingon"],"ProjectFeature|Only team members":["Nur skipanoj"],"ProjectFileTree|Name":["Nomo"],"ProjectLastActivity|Never":["Neniam"],"ProjectLifecycle|Stage":["Etapo"],"ProjectNetworkGraph|Graph":["Grafeo"],"Read more":["Legu pli"],"Readme":["LeguMin"],"RefSwitcher|Branches":["Branĉoj"],"RefSwitcher|Tags":["Etikedoj"],"Related Commits":["Rilataj enmetadoj"],"Related Deployed Jobs":["Rilataj disponigitaj taskoj"],"Related Issues":["Rilataj problemoj"],"Related Jobs":["Rilataj taskoj"],"Related Merge Requests":["Rilataj petoj pri kunfando"],"Related Merged Requests":["Rilataj aplikitaj petoj pri kunfando"],"Remind later":["Rememorigu denove"],"Remove project":["Forigi la projekton"],"Request Access":["Peti atingeblon"],"Revert this commit":["Malfari ĉi tiun enmetadon"],"Revert this merge request":["Malfari ĉi tiun peton pri kunfando"],"Save pipeline schedule":["Konservi ĉenstablan planon"],"Schedule a new pipeline":["Plani novan ĉenstablon"],"Scheduling Pipelines":["Planado de la ĉenstabloj"],"Search branches and tags":["Serĉu branĉon aŭ etikedon"],"Select Archive Format":["Elektu formaton de arkivo"],"Select a timezone":["Elektu horzonon"],"Select target branch":["Elektu celan branĉon"],"Set a password on your account to pull or push via %{protocol}":["Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuŝi per %{protocol}"],"Set up CI":["Agordi SI"],"Set up Koding":["Agordi „Koding“"],"Set up auto deploy":["Agordi aŭtomatan disponigadon"],"SetPasswordToCloneLink|set a password":["kreos pasvorton"],"Showing %d event":["Estas montrata %d evento","Estas montrataj %d eventoj"],"Source code":["Kodo"],"StarProject|Star":["Steligi"],"Start a %{new_merge_request} with these changes":["Kreu %{new_merge_request} kun ĉi tiuj ŝanĝoj"],"Switch branch/tag":["Iri al branĉo/etikedo"],"Tag":["Etikedo","Etikedoj"],"Tags":["Etikedoj"],"Target Branch":["Cela branĉo"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["La etapo de programado montras la tempon de la unua enmetado ĝis la kreado de la peto pri kunfando. La datenoj aldoniĝos aŭtomate ĉi tie post kiam vi kreas la unuan peton pri kunfando."],"The collection of events added to the data gathered for that stage.":["La aro da eventoj, kiuj estas aldonitaj al la datenoj kolektitaj por la etapo."],"The fork relationship has been removed.":["La rilato de disbranĉigo estis forigita."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["La etapo de la problemo montras kiom la tempo pasas de la kreado de problemo ĝis la atribuado de la problemo al cela etapo de la projekto, aŭ al listo sur la problemtabulo. Komencu krei problemojn por vidi la datenojn por ĉi tiu etapo."],"The phase of the development lifecycle.":["La etapo de la disvolva ciklo."],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["La ĉenstabla plano plenumas ĉenstablojn en la estonteco, ripete, por difinitaj branĉoj aŭ etikedoj. Tiuj planitaj ĉenstabloj heredos la limigitan atingon al la projekto de la rilata uzanto."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["La etapo de la plano montras la tempon de la antaŭa ŝtupo ĝis la alpuŝado de via unua enmetado. Ĉi tiu tempo aldoniĝos aŭtomate post kiam vi alpuŝas la unuan enmetadon."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["La etapo de eldonado montras la tutan tempon de la kreado de problemo ĝis la disponigado en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi kompletigos plenan ciklon de ideo ĝis realaĵo."],"The project can be accessed by any logged in user.":["Ĉiu ensalutita uzanto havas atingon al la projekto"],"The project can be accessed without any authentication.":["Ĉiu povas havi atingon al la projekto, sen ensaluti"],"The repository for this project does not exist.":["La deponejo por ĉi tiu projekto ne ekzistas."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["La etapo de la kontrolo montras la tempon de la kreado de la peto pri kunfando ĝis ĝia aplikado. La datenoj aldoniĝos aŭtomate post kiam vi aplikos la unuan peton pri kunfando."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["La etapo de preparo por eldono montras la tempon inter la aplikado de la peto pri kunfando kaj la disponigado de la kodo en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi faros la unuan disponigadon en la publika versio."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["La etapo de testado montras kiom da tempo necesas al „GitLab CI“ por plenumi ĉiujn ĉenstablojn por la rilata peto pri kunfando. La datenoj aldoniĝos aŭtomate post kiam via unua ĉenstablo finiĝos."],"The time taken by each data entry gathered by that stage.":["La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["La valoro, kiu troviĝas en la mezo de aro da rigardataj valoroj. Ekzemple: inter 3, 5 kaj 9, la mediano estas 5. Inter 3, 5, 7 kaj 8, la mediano estas (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Ĉi tiu signifas, ke vi ne povos alpuŝi kodon, antaŭ ol vi kreos malplenan deponejon aŭ enportos jam ekzistantan."],"Time before an issue gets scheduled":["Tempo antaŭ problemo estas planita por ellabori"],"Time before an issue starts implementation":["Tempo antaŭ la komenco de laboro super problemo"],"Time between merge request creation and merge/close":["Tempo inter la kreado de poeto pri kunfando kaj ĝia aplikado/fermado"],"Time until first merge request":["Tempo ĝis la unua peto pri kunfando"],"Timeago|%s days ago":["antaŭ %s tagoj"],"Timeago|%s days remaining":["restas %s tagoj"],"Timeago|%s hours remaining":["restas %s horoj"],"Timeago|%s minutes ago":["antaŭ %s minutoj"],"Timeago|%s minutes remaining":["restas %s minutoj"],"Timeago|%s months ago":["antaŭ %s monatoj"],"Timeago|%s months remaining":["restas %s monatoj"],"Timeago|%s seconds remaining":["restas %s sekundoj"],"Timeago|%s weeks ago":["antaŭ %s semajnoj"],"Timeago|%s weeks remaining":["restas %s semajnoj"],"Timeago|%s years ago":["antaŭ %s jaroj"],"Timeago|%s years remaining":["restas %s jaroj"],"Timeago|1 day remaining":["restas 1 tago"],"Timeago|1 hour remaining":["restas 1 horo"],"Timeago|1 minute remaining":["restas 1 minuto"],"Timeago|1 month remaining":["restas 1 monato"],"Timeago|1 week remaining":["restas 1 semajno"],"Timeago|1 year remaining":["restas 1 jaro"],"Timeago|Past due":["Malfruiĝis"],"Timeago|a day ago":["antaŭ unu tago"],"Timeago|a month ago":["antaŭ unu monato"],"Timeago|a week ago":["antaŭ unu semajno"],"Timeago|a while":["antaŭ iom da tempo"],"Timeago|a year ago":["antaŭ unu jaro"],"Timeago|about %s hours ago":["antaŭ ĉirkaŭ %s horoj"],"Timeago|about a minute ago":["antaŭ ĉirkaŭ unu minuto"],"Timeago|about an hour ago":["antaŭ ĉirkaŭ unu horo"],"Timeago|in %s days":["post %s tagoj"],"Timeago|in %s hours":["post %s horoj"],"Timeago|in %s minutes":["post %s minutoj"],"Timeago|in %s months":["post %s monatoj"],"Timeago|in %s seconds":["post %s sekundoj"],"Timeago|in %s weeks":["post %s semajnoj"],"Timeago|in %s years":["post %s jaroj"],"Timeago|in 1 day":["post 1 tago"],"Timeago|in 1 hour":["post 1 horo"],"Timeago|in 1 minute":["post 1 minuto"],"Timeago|in 1 month":["post 1 monato"],"Timeago|in 1 week":["post 1 semajno"],"Timeago|in 1 year":["post 1 jaro"],"Timeago|less than a minute ago":["antaŭ malpli ol minuto"],"Time|hr":["h","h"],"Time|min":["min","min"],"Time|s":["s"],"Total Time":["Totala tempo"],"Total test time for all commits/merges":["Totala tempo por la testado de ĉiuj enmetadoj/kunfandoj"],"Unstar":["Malsteligi"],"Upload New File":["Alŝuti novan dosieron"],"Upload file":["Alŝuti dosieron"],"Use your global notification setting":["Uzi vian ĝeneralan agordon pri la sciigoj"],"VisibilityLevel|Internal":["Interna"],"VisibilityLevel|Private":["Privata"],"VisibilityLevel|Public":["Publika"],"Want to see the data? Please ask an administrator for access.":["Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto."],"We don't have enough data to show this stage.":["Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."],"Withdraw Access Request":["Nuligi la peton pri atingeblo"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["Vi forigos „%{project_name_with_namespace}“.\\nOni NE POVAS malfari la forigon de projekto!\\nĈu vi estas ABSOLUTE certa?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["Vi transigos „%{project_name_with_namespace}“ al alia posedanto. Ĉu vi estas ABSOLUTE certa?"],"You can only add files when you are on a branch":["Oni povas aldoni dosierojn nur kiam oni estas en branĉo"],"You have reached your project limit":["Vi ne povas krei pliajn projektojn"],"You must sign in to star a project":["Oni devas ensaluti por steligi projekton"],"You need permission.":["VI bezonas permeson."],"You will not get any notifications via email":["VI ne ricevos sciigojn per retpoŝto"],"You will only receive notifications for the events you choose":["Vi ricevos sciigojn nur por la eventoj elektitaj de vi"],"You will only receive notifications for threads you have participated in":["Vi ricevos sciigojn nur por la fadenoj, en kiuj vi partoprenis"],"You will receive notifications for any activity":["Vi ricevos sciigojn por ĉiu ago"],"You will receive notifications only for comments in which you were @mentioned":["Vi ricevos sciigojn nur por komentoj, en kiuj vi estas @menciita"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Vi ne povos eltiri aŭ alpuŝi kodon per %{protocol} antaŭ ol vi %{set_password_link} por via konto"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Vi ne povos eltiri aŭ alpuŝi kodon per SSH antaŭ ol vi %{add_ssh_key_link} al via profilo"],"Your name":["Via nomo"],"day":["tago","tagoj"],"new merge request":["novan peton pri kunfando"],"notification emails":["sciigoj per retpoŝto"],"parent":["patro","patroj"]}}}; \ No newline at end of file -- cgit v1.2.1 From 60bd2ae372a9cab09990a6d2d5522ad3aeed8a40 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 28 Jun 2017 11:44:32 +0100 Subject: Ensure NotificationRecipientService doesn't modify participants Even though it does modify the participants of the notification target in some cases, this should have been safe, as different workers are responsible for creating the notifications for each target. However, this is at best confusing, and we should ensure we don't do that. --- app/services/notification_recipient_service.rb | 17 ++++++----- ...on-recipient-service-modifying-participants.yml | 5 ++++ .../notification_recipient_service_spec.rb | 34 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml create mode 100644 spec/services/notification_recipient_service_spec.rb diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 8d1820bc504..9ac561e4bd2 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -11,7 +11,7 @@ class NotificationRecipientService def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true) custom_action = build_custom_key(action, target) - recipients = target.participants(current_user) + recipients = participants(target, current_user) recipients = add_project_watchers(recipients) recipients = add_custom_notifications(recipients, custom_action) recipients = reject_mention_users(recipients) @@ -86,12 +86,7 @@ class NotificationRecipientService mentioned_users = note.mentioned_users.select { |user| user.can?(ability, subject) } # Add all users participating in the thread (author, assignee, comment authors) - recipients = - if target.respond_to?(:participants) - target.participants(note.author) - else - mentioned_users - end + recipients = participants(target, note.author) || mentioned_users unless note.for_personal_snippet? # Merge project watchers @@ -123,6 +118,14 @@ class NotificationRecipientService protected + # Ensure that if we modify this array, we aren't modifying the memoised + # participants on the target. + def participants(target, user) + return unless target.respond_to?(:participants) + + target.participants(user).dup + end + # Get project/group users with CUSTOM notification level def add_custom_notifications(recipients, action) user_ids = [] diff --git a/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml new file mode 100644 index 00000000000..7e66ea4ca8b --- /dev/null +++ b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml @@ -0,0 +1,5 @@ +--- +title: Ensure participants for issues, merge requests, etc. are calculated correctly + when sending notifications +merge_request: +author: diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb new file mode 100644 index 00000000000..dfe1ee7c41e --- /dev/null +++ b/spec/services/notification_recipient_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe NotificationRecipientService, services: true do + set(:user) { create(:user) } + set(:project) { create(:empty_project, :public) } + set(:issue) { create(:issue, project: project) } + + set(:watcher) do + watcher = create(:user) + setting = watcher.notification_settings_for(project) + setting.level = :watch + setting.save + + watcher + end + + subject { described_class.new(project) } + + describe '#build_recipients' do + it 'does not modify the participants of the target' do + expect { subject.build_recipients(issue, user, action: :new_issue) } + .not_to change { issue.participants(user) } + end + end + + describe '#build_new_note_recipients' do + set(:note) { create(:note_on_issue, noteable: issue, project: project) } + + it 'does not modify the participants of the target' do + expect { subject.build_new_note_recipients(note) } + .not_to change { note.noteable.participants(note.author) } + end + end +end -- cgit v1.2.1 From d67b5c099e12e3f70ae821c32e5a5cedd86ed398 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 1 Jun 2017 23:30:12 +0900 Subject: Use authorize_update_pipeline_schedule in PipelineSchedulesController --- app/controllers/projects/pipeline_schedules_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb index ef4f083b98f..60db179277b 100644 --- a/app/controllers/projects/pipeline_schedules_controller.rb +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -1,6 +1,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController before_action :authorize_read_pipeline_schedule! - before_action :authorize_create_pipeline_schedule!, only: [:new, :create, :edit, :take_ownership, :update] + before_action :authorize_create_pipeline_schedule!, only: [:new, :create] + before_action :authorize_update_pipeline_schedule!, only: [:edit, :take_ownership, :update] before_action :authorize_admin_pipeline_schedule!, only: [:destroy] before_action :schedule, only: [:edit, :update, :destroy, :take_ownership] -- cgit v1.2.1 From 8026f47d67546d48139cb35a0dc16bbac47b1bb7 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 5 Jun 2017 22:19:23 +0900 Subject: Add changelog. Add AccessMatchersForController --- ...ke_ownership-in-pipelineschedulescontroller.yml | 4 + .../projects/pipeline_schedules_controller_spec.rb | 58 ++++++++++++++ .../matchers/access_matchers_for_controller.rb | 88 ++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml create mode 100644 spec/support/matchers/access_matchers_for_controller.rb diff --git a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml new file mode 100644 index 00000000000..3769940feb9 --- /dev/null +++ b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml @@ -0,0 +1,4 @@ +--- +title: Use authorize_update_pipeline_schedule in PipelineSchedulesController +merge_request: 11846 +author: dosuken123 diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index f8f95dd9bc8..abbf74235ef 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -84,4 +84,62 @@ describe Projects::PipelineSchedulesController do end end end + + describe 'security' do + include AccessMatchersForController + + describe 'GET edit' do + let(:action) do + Proc.new do |user| + get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + end + end + + specify { expect(action).to be_allowed_for(:admin) } + specify { expect(action).to be_allowed_for(:owner).of(project) } + specify { expect(action).to be_allowed_for(:master).of(project) } + specify { expect(action).to be_allowed_for(:developer).of(project) } + specify { expect(action).to be_denied_for(:reporter).of(project) } + specify { expect(action).to be_denied_for(:guest).of(project) } + specify { expect(action).to be_denied_for(:user) } + specify { expect(action).to be_denied_for(:external) } + specify { expect(action).to be_denied_for(:visitor) } + end + + describe 'GET take_ownership' do + let(:action) do + Proc.new do |user| + post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + end + end + + specify { expect(action).to be_allowed_for(:admin) } + specify { expect(action).to be_allowed_for(:owner).of(project) } + specify { expect(action).to be_allowed_for(:master).of(project) } + specify { expect(action).to be_allowed_for(:developer).of(project) } + specify { expect(action).to be_denied_for(:reporter).of(project) } + specify { expect(action).to be_denied_for(:guest).of(project) } + specify { expect(action).to be_denied_for(:user) } + specify { expect(action).to be_denied_for(:external) } + specify { expect(action).to be_denied_for(:visitor) } + end + + describe 'PUT update' do + let(:action) do + Proc.new do |user| + put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + end + end + + specify { expect(action).to be_allowed_for(:admin) } + specify { expect(action).to be_allowed_for(:owner).of(project) } + specify { expect(action).to be_allowed_for(:master).of(project) } + specify { expect(action).to be_allowed_for(:developer).of(project) } + specify { expect(action).to be_denied_for(:reporter).of(project) } + specify { expect(action).to be_denied_for(:guest).of(project) } + specify { expect(action).to be_denied_for(:user) } + specify { expect(action).to be_denied_for(:external) } + specify { expect(action).to be_denied_for(:visitor) } + end + end end diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb new file mode 100644 index 00000000000..4da10247801 --- /dev/null +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -0,0 +1,88 @@ +# AccessMatchersForController +# +# For testing authorize_xxx in controller. +module AccessMatchersForController + extend RSpec::Matchers::DSL + include Warden::Test::Helpers + + EXPECTED_STATUS_CODE_ALLOWED = [200, 302] + EXPECTED_STATUS_CODE_DENIED = [404] + + def emulate_user(role, membership = nil) + case role + when :admin + user = create(:admin) + sign_in(user) + when *Gitlab::Access.sym_options_with_owner.keys # owner, master, developer, reporter, guest + raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership + + if role == :owner && membership.owner + user = membership.owner + else + user = create(:user) + membership.public_send(:"add_#{role}", user) + end + + sign_in(user) + when :user + user = create(:user) + sign_in(user) + when :external + user = create(:user, external: true) + sign_in(user) + when :visitor + # no-op + else + raise ArgumentError, "cannot emulate user #{role}" + end + + user + end + + def description_for(role, type, expected, result) + "be #{type} for #{role}." \ + " Expected: #{expected.join(',')} Result: #{result}" + end + + matcher :be_allowed_for do |role| + match do |action| + user = emulate_user(role, @membership) + begin + action.call(user) + rescue Exception => e + # Ignore internal exceptions which will be caused in the controller + # In such cases, response.status will be 200. + end + + EXPECTED_STATUS_CODE_ALLOWED.include?(response.status) + end + + chain :of do |membership| + @membership = membership + end + + description { description_for(role, 'allowed', EXPECTED_STATUS_CODE_ALLOWED, response.status) } + supports_block_expectations + end + + matcher :be_denied_for do |role| + match do |action| + user = emulate_user(role, @membership) + begin + action.call(user) + rescue Exception => e + # Ignore internal exceptions which will be caused in the controller + # In such cases, response.status will be 200. + end + + EXPECTED_STATUS_CODE_DENIED.include?(response.status) + end + + chain :of do |membership| + @membership = membership + end + + description { description_for(role, 'denied', EXPECTED_STATUS_CODE_DENIED, response.status) } + supports_block_expectations + end +end -- cgit v1.2.1 From e9734c6df33dba7ab89d4c575ace3a508c5da64b Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Jun 2017 00:13:17 +0900 Subject: Resolve static analysis --- spec/support/matchers/access_matchers_for_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index 4da10247801..14500201a59 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -49,7 +49,7 @@ module AccessMatchersForController user = emulate_user(role, @membership) begin action.call(user) - rescue Exception => e + rescue # Ignore internal exceptions which will be caused in the controller # In such cases, response.status will be 200. end @@ -70,7 +70,7 @@ module AccessMatchersForController user = emulate_user(role, @membership) begin action.call(user) - rescue Exception => e + rescue # Ignore internal exceptions which will be caused in the controller # In such cases, response.status will be 200. end -- cgit v1.2.1 From 359d72ba3ec931205a58b4a992cc9b6e93fd0ca2 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Jun 2017 02:29:34 +0900 Subject: Fix static analysys --- .../projects/pipeline_schedules_controller_spec.rb | 6 +++--- .../matchers/access_matchers_for_controller.rb | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index abbf74235ef..575e2050354 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -90,7 +90,7 @@ describe Projects::PipelineSchedulesController do describe 'GET edit' do let(:action) do - Proc.new do |user| + proc do |user| get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id end end @@ -108,7 +108,7 @@ describe Projects::PipelineSchedulesController do describe 'GET take_ownership' do let(:action) do - Proc.new do |user| + proc do |user| post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id end end @@ -126,7 +126,7 @@ describe Projects::PipelineSchedulesController do describe 'PUT update' do let(:action) do - Proc.new do |user| + proc do |user| put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id end end diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index 14500201a59..12f4b57e43d 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -5,14 +5,22 @@ module AccessMatchersForController extend RSpec::Matchers::DSL include Warden::Test::Helpers - EXPECTED_STATUS_CODE_ALLOWED = [200, 302] - EXPECTED_STATUS_CODE_DENIED = [404] + EXPECTED_STATUS_CODE_ALLOWED = [200, 302].freeze + EXPECTED_STATUS_CODE_DENIED = [404].freeze def emulate_user(role, membership = nil) case role when :admin user = create(:admin) sign_in(user) + when :user + user = create(:user) + sign_in(user) + when :external + user = create(:user, external: true) + sign_in(user) + when :visitor # rubocop:disable Lint/EmptyWhen + # no-op when *Gitlab::Access.sym_options_with_owner.keys # owner, master, developer, reporter, guest raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership @@ -24,14 +32,6 @@ module AccessMatchersForController end sign_in(user) - when :user - user = create(:user) - sign_in(user) - when :external - user = create(:user, external: true) - sign_in(user) - when :visitor - # no-op else raise ArgumentError, "cannot emulate user #{role}" end -- cgit v1.2.1 From 043f1b8232a0d5a5f0b1427c3ed8038dcf029f53 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 26 Jun 2017 14:59:25 +0900 Subject: Remove author. Replace Result to Got. --- ...edule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml | 2 +- spec/support/matchers/access_matchers_for_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml index 3769940feb9..d3172c405c3 100644 --- a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml +++ b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml @@ -1,4 +1,4 @@ --- title: Use authorize_update_pipeline_schedule in PipelineSchedulesController merge_request: 11846 -author: dosuken123 +author: diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index 12f4b57e43d..844464233b8 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -41,7 +41,7 @@ module AccessMatchersForController def description_for(role, type, expected, result) "be #{type} for #{role}." \ - " Expected: #{expected.join(',')} Result: #{result}" + " Expected: #{expected.join(',')} Got: #{result}" end matcher :be_allowed_for do |role| -- cgit v1.2.1 From ac7ad422812cb3224afb6e1943f9ee22e2488912 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 26 Jun 2017 23:42:01 +0900 Subject: IMprove access_matchers --- .../projects/pipeline_schedules_controller_spec.rb | 3 +- .../matchers/access_matchers_for_controller.rb | 47 ++++++++++------------ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 575e2050354..4f37548a447 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -127,7 +127,8 @@ describe Projects::PipelineSchedulesController do describe 'PUT update' do let(:action) do proc do |user| - put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id, + schedule: { description: 'a' } end end diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index 844464233b8..59c6f528e74 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -5,8 +5,8 @@ module AccessMatchersForController extend RSpec::Matchers::DSL include Warden::Test::Helpers - EXPECTED_STATUS_CODE_ALLOWED = [200, 302].freeze - EXPECTED_STATUS_CODE_DENIED = [404].freeze + EXPECTED_STATUS_CODE_ALLOWED = [200, 201, 302].freeze + EXPECTED_STATUS_CODE_DENIED = [401, 404].freeze def emulate_user(role, membership = nil) case role @@ -19,18 +19,13 @@ module AccessMatchersForController when :external user = create(:user, external: true) sign_in(user) - when :visitor # rubocop:disable Lint/EmptyWhen - # no-op + when :visitor + user = nil + when User + user = role + sign_in(user) when *Gitlab::Access.sym_options_with_owner.keys # owner, master, developer, reporter, guest - raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership - - if role == :owner && membership.owner - user = membership.owner - else - user = create(:user) - membership.public_send(:"add_#{role}", user) - end - + user = cerate_user_by_membership(role, membership) sign_in(user) else raise ArgumentError, "cannot emulate user #{role}" @@ -39,6 +34,18 @@ module AccessMatchersForController user end + def cerate_user_by_membership(role, membership = nil) + raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership + + if role == :owner && membership.owner + user = membership.owner + else + user = create(:user) + membership.public_send(:"add_#{role}", user) + end + user + end + def description_for(role, type, expected, result) "be #{type} for #{role}." \ " Expected: #{expected.join(',')} Got: #{result}" @@ -47,12 +54,7 @@ module AccessMatchersForController matcher :be_allowed_for do |role| match do |action| user = emulate_user(role, @membership) - begin - action.call(user) - rescue - # Ignore internal exceptions which will be caused in the controller - # In such cases, response.status will be 200. - end + action.call(user) EXPECTED_STATUS_CODE_ALLOWED.include?(response.status) end @@ -68,12 +70,7 @@ module AccessMatchersForController matcher :be_denied_for do |role| match do |action| user = emulate_user(role, @membership) - begin - action.call(user) - rescue - # Ignore internal exceptions which will be caused in the controller - # In such cases, response.status will be 200. - end + action.call(user) EXPECTED_STATUS_CODE_DENIED.include?(response.status) end -- cgit v1.2.1 From c215b1042644775326dd99beef05be9274f61389 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 27 Jun 2017 00:48:13 +0900 Subject: Fix static analysis --- spec/controllers/projects/pipeline_schedules_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 4f37548a447..714fd76cee0 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -128,7 +128,7 @@ describe Projects::PipelineSchedulesController do let(:action) do proc do |user| put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id, - schedule: { description: 'a' } + schedule: { description: 'a' } end end -- cgit v1.2.1 From d1fae597431c8005067a11d0030a0d690231685a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 27 Jun 2017 00:55:20 +0900 Subject: Improve description_for --- spec/support/matchers/access_matchers_for_controller.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index 59c6f528e74..78a0cc0a45d 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -47,8 +47,7 @@ module AccessMatchersForController end def description_for(role, type, expected, result) - "be #{type} for #{role}." \ - " Expected: #{expected.join(',')} Got: #{result}" + "be #{type} for #{role}. Expected: #{expected.join(',')} Got: #{result}" end matcher :be_allowed_for do |role| -- cgit v1.2.1 From 07b0615e2b59cbd3f277e17d7d0e1bf7f11f9fee Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 27 Jun 2017 22:43:43 +0900 Subject: Fixed typo and hash alighment --- spec/controllers/projects/pipeline_schedules_controller_spec.rb | 4 +++- spec/support/matchers/access_matchers_for_controller.rb | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 714fd76cee0..40f339917ce 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -128,7 +128,9 @@ describe Projects::PipelineSchedulesController do let(:action) do proc do |user| put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id, - schedule: { description: 'a' } + schedule: { + description: 'a' + } end end diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index 78a0cc0a45d..d9980ff2f98 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -25,7 +25,7 @@ module AccessMatchersForController user = role sign_in(user) when *Gitlab::Access.sym_options_with_owner.keys # owner, master, developer, reporter, guest - user = cerate_user_by_membership(role, membership) + user = create_user_by_membership(role, membership) sign_in(user) else raise ArgumentError, "cannot emulate user #{role}" @@ -34,7 +34,7 @@ module AccessMatchersForController user end - def cerate_user_by_membership(role, membership = nil) + def create_user_by_membership(role, membership = nil) raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership if role == :owner && membership.owner -- cgit v1.2.1 From a513ae4ae58cf1f55872cc07c734866167eae45f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Jun 2017 00:31:26 +0900 Subject: use go instead of let proc --- .../projects/pipeline_schedules_controller_spec.rb | 84 ++++++++++------------ .../matchers/access_matchers_for_controller.rb | 8 +-- 2 files changed, 43 insertions(+), 49 deletions(-) diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 40f339917ce..775810a4c61 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -89,60 +89,54 @@ describe Projects::PipelineSchedulesController do include AccessMatchersForController describe 'GET edit' do - let(:action) do - proc do |user| - get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id - end + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_allowed_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + it { expect { go }.to be_denied_for(:visitor) } + + def go + get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id end - - specify { expect(action).to be_allowed_for(:admin) } - specify { expect(action).to be_allowed_for(:owner).of(project) } - specify { expect(action).to be_allowed_for(:master).of(project) } - specify { expect(action).to be_allowed_for(:developer).of(project) } - specify { expect(action).to be_denied_for(:reporter).of(project) } - specify { expect(action).to be_denied_for(:guest).of(project) } - specify { expect(action).to be_denied_for(:user) } - specify { expect(action).to be_denied_for(:external) } - specify { expect(action).to be_denied_for(:visitor) } end describe 'GET take_ownership' do - let(:action) do - proc do |user| - post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id - end + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_allowed_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + it { expect { go }.to be_denied_for(:visitor) } + + def go + post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id end - - specify { expect(action).to be_allowed_for(:admin) } - specify { expect(action).to be_allowed_for(:owner).of(project) } - specify { expect(action).to be_allowed_for(:master).of(project) } - specify { expect(action).to be_allowed_for(:developer).of(project) } - specify { expect(action).to be_denied_for(:reporter).of(project) } - specify { expect(action).to be_denied_for(:guest).of(project) } - specify { expect(action).to be_denied_for(:user) } - specify { expect(action).to be_denied_for(:external) } - specify { expect(action).to be_denied_for(:visitor) } end describe 'PUT update' do - let(:action) do - proc do |user| - put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id, - schedule: { - description: 'a' - } - end + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_allowed_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + it { expect { go }.to be_denied_for(:visitor) } + + def go + put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id, + schedule: { + description: 'a' + } end - - specify { expect(action).to be_allowed_for(:admin) } - specify { expect(action).to be_allowed_for(:owner).of(project) } - specify { expect(action).to be_allowed_for(:master).of(project) } - specify { expect(action).to be_allowed_for(:developer).of(project) } - specify { expect(action).to be_denied_for(:reporter).of(project) } - specify { expect(action).to be_denied_for(:guest).of(project) } - specify { expect(action).to be_denied_for(:user) } - specify { expect(action).to be_denied_for(:external) } - specify { expect(action).to be_denied_for(:visitor) } end end end diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index d9980ff2f98..c951c6b754d 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -52,8 +52,8 @@ module AccessMatchersForController matcher :be_allowed_for do |role| match do |action| - user = emulate_user(role, @membership) - action.call(user) + emulate_user(role, @membership) + action.call EXPECTED_STATUS_CODE_ALLOWED.include?(response.status) end @@ -68,8 +68,8 @@ module AccessMatchersForController matcher :be_denied_for do |role| match do |action| - user = emulate_user(role, @membership) - action.call(user) + emulate_user(role, @membership) + action.call EXPECTED_STATUS_CODE_DENIED.include?(response.status) end -- cgit v1.2.1 From af129038e089cfc91df08d14826fa9279252d7e9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Jun 2017 00:33:30 +0900 Subject: Fix sa --- spec/controllers/projects/pipeline_schedules_controller_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 775810a4c61..a8c44d5c313 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -133,9 +133,7 @@ describe Projects::PipelineSchedulesController do def go put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id, - schedule: { - description: 'a' - } + schedule: { description: 'a' } end end end -- cgit v1.2.1 From d9d5600711426d280cc1768820e809357293f14d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Jun 2017 22:04:49 +0900 Subject: Make membership required for create_user_by_membership --- spec/support/matchers/access_matchers_for_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb index c951c6b754d..fb43f51c70c 100644 --- a/spec/support/matchers/access_matchers_for_controller.rb +++ b/spec/support/matchers/access_matchers_for_controller.rb @@ -25,6 +25,8 @@ module AccessMatchersForController user = role sign_in(user) when *Gitlab::Access.sym_options_with_owner.keys # owner, master, developer, reporter, guest + raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership + user = create_user_by_membership(role, membership) sign_in(user) else @@ -34,9 +36,7 @@ module AccessMatchersForController user end - def create_user_by_membership(role, membership = nil) - raise ArgumentError, "cannot emulate #{role} without membership parent" unless membership - + def create_user_by_membership(role, membership) if role == :owner && membership.owner user = membership.owner else -- cgit v1.2.1 From 2a64607b987add8747c4b9601aa01fe40064d15d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Jun 2017 10:42:51 -0300 Subject: Add Project#ensure_repository --- app/models/project.rb | 4 ++++ spec/models/project_spec.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 1176bec8873..35d8f9a0154 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1094,6 +1094,10 @@ class Project < ActiveRecord::Base end end + def ensure_repository + create_repository unless repository_exists? + end + def repository_exists? !!repository.exists? end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d7fcadb895e..cc22b8a4edc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1327,6 +1327,37 @@ describe Project, models: true do end end + describe '#ensure_repository' do + let(:project) { create(:project, :repository) } + let(:shell) { Gitlab::Shell.new } + + before do + allow(project).to receive(:gitlab_shell).and_return(shell) + end + + it 'creates the repository if it not exist' do + allow(project).to receive(:repository_exists?) + .and_return(false) + + allow(shell).to receive(:add_repository) + .with(project.repository_storage_path, project.path_with_namespace) + .and_return(true) + + expect(project).to receive(:create_repository) + + project.ensure_repository + end + + it 'does not create the repository if it exists' do + allow(project).to receive(:repository_exists?) + .and_return(true) + + expect(project).not_to receive(:create_repository) + + project.ensure_repository + end + end + describe '#user_can_push_to_empty_repo?' do let(:project) { create(:empty_project) } let(:user) { create(:user) } -- cgit v1.2.1 From 26f3731021e8c5c6417fe874c53a5bf2065b8888 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Jun 2017 10:42:59 -0300 Subject: Add ProjectWiki#ensure_repository --- app/models/project_wiki.rb | 4 ++++ spec/models/project_wiki_spec.rb | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index f38fbda7839..f26ee57510c 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -149,6 +149,10 @@ class ProjectWiki wiki end + def ensure_repository + create_repo! unless repository_exists? + end + def hook_attrs { web_url: web_url, diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index bf74ac5ea25..1f314791479 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -278,6 +278,24 @@ describe ProjectWiki, models: true do end end + describe '#ensure_repository' do + it 'creates the repository if it not exist' do + allow(subject).to receive(:repository_exists?).and_return(false) + + expect(subject).to receive(:create_repo!) + + subject.ensure_repository + end + + it 'does not create the repository if it exists' do + allow(subject).to receive(:repository_exists?).and_return(true) + + expect(subject).not_to receive(:create_repo!) + + subject.ensure_repository + end + end + describe '#hook_attrs' do it 'returns a hash with values' do expect(subject.hook_attrs).to be_a Hash -- cgit v1.2.1 From d3bcf8ac2ae7e89d0ec6eddcd6374bc1e1c8b5fb Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 28 Jun 2017 14:20:29 +0200 Subject: Fix gitaly ref encoding bugs --- lib/gitlab/git.rb | 4 +++- lib/gitlab/git/repository.rb | 4 +--- lib/gitlab/gitaly_client/ref.rb | 23 ++++++++++++++++------- spec/lib/gitlab/git/repository_spec.rb | 24 ++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 936606152e9..4175746be39 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -7,8 +7,10 @@ module Gitlab CommandError = Class.new(StandardError) class << self + include Gitlab::EncodingHelper + def ref_name(ref) - ref.sub(/\Arefs\/(tags|heads)\//, '') + encode! ref.sub(/\Arefs\/(tags|heads)\//, '') end def branch_name(ref) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index dd296983491..23d0c8a9bdb 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -113,9 +113,7 @@ module Gitlab def local_branches(sort_by: nil) gitaly_migrate(:local_branches) do |is_enabled| if is_enabled - gitaly_ref_client.local_branches(sort_by: sort_by).map do |gitaly_branch| - Gitlab::Git::Branch.new(self, gitaly_branch.name, gitaly_branch) - end + gitaly_ref_client.local_branches(sort_by: sort_by) else branches(filter: :local, sort_by: sort_by) end diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb index 6d5f54dd959..f4786f28a3a 100644 --- a/lib/gitlab/gitaly_client/ref.rb +++ b/lib/gitlab/gitaly_client/ref.rb @@ -1,8 +1,11 @@ module Gitlab module GitalyClient class Ref + include Gitlab::EncodingHelper + # 'repository' is a Gitlab::Git::Repository def initialize(repository) + @repository = repository @gitaly_repo = repository.gitaly_repository @storage = repository.storage end @@ -16,13 +19,13 @@ module Gitlab def branch_names request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo) response = GitalyClient.call(@storage, :ref, :find_all_branch_names, request) - consume_refs_response(response, prefix: 'refs/heads/') + consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) } end def tag_names request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo) response = GitalyClient.call(@storage, :ref, :find_all_tag_names, request) - consume_refs_response(response, prefix: 'refs/tags/') + consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) } end def find_ref_name(commit_id, ref_prefix) @@ -51,10 +54,8 @@ module Gitlab private - def consume_refs_response(response, prefix:) - response.flat_map do |r| - r.names.map { |name| name.sub(/\A#{Regexp.escape(prefix)}/, '') } - end + def consume_refs_response(response) + response.flat_map { |message| message.names.map { |name| yield(name) } } end def sort_by_param(sort_by) @@ -64,7 +65,15 @@ module Gitlab end def consume_branches_response(response) - response.flat_map { |r| r.branches } + response.flat_map do |message| + message.branches.map do |gitaly_branch| + Gitlab::Git::Branch.new( + @repository, + encode!(gitaly_branch.name.dup), + gitaly_branch.commit_id + ) + end + end end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index ee25aeefa95..0cd458bf933 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -26,6 +26,10 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + it 'returns UTF-8' do + expect(repository.root_ref.encoding).to eq(Encoding.find('UTF-8')) + end + context 'with gitaly enabled' do before do stub_gitaly @@ -123,6 +127,11 @@ describe Gitlab::Git::Repository, seed_helper: true do it 'has SeedRepo::Repo::BRANCHES.size elements' do expect(subject.size).to eq(SeedRepo::Repo::BRANCHES.size) end + + it 'returns UTF-8' do + expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) + end + it { is_expected.to include("master") } it { is_expected.not_to include("branch-from-space") } @@ -158,10 +167,15 @@ describe Gitlab::Git::Repository, seed_helper: true do subject { repository.tag_names } it { is_expected.to be_kind_of Array } + it 'has SeedRepo::Repo::TAGS.size elements' do expect(subject.size).to eq(SeedRepo::Repo::TAGS.size) end + it 'returns UTF-8' do + expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) + end + describe '#last' do subject { super().last } it { is_expected.to eq("v1.2.1") } @@ -1276,6 +1290,16 @@ describe Gitlab::Git::Repository, seed_helper: true do Gitlab::GitalyClient.clear_stubs! end + it 'returns a Branch with UTF-8 fields' do + branches = @repo.local_branches.to_a + expect(branches.size).to be > 0 + utf_8 = Encoding.find('utf-8') + branches.each do |branch| + expect(branch.name.encoding).to eq(utf_8) + expect(branch.target.encoding).to eq(utf_8) unless branch.target.nil? + end + end + it 'gets the branches from GitalyClient' do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) .and_return([]) -- cgit v1.2.1 From ef722faa955a755b2a73efbacdc8a2ca17e399b2 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 27 Jun 2017 15:30:15 -0500 Subject: Add context header to sidebar --- app/assets/stylesheets/new_sidebar.scss | 26 +++++++++++++++++++++- app/views/layouts/nav/_new_admin_sidebar.html.haml | 4 ++++ app/views/layouts/nav/_new_group_sidebar.html.haml | 5 +++++ .../layouts/nav/_new_profile_sidebar.html.haml | 4 ++++ .../layouts/nav/_new_project_sidebar.html.haml | 5 +++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 06c6025ed6b..c8f0e1f4764 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -5,17 +5,41 @@ $new-sidebar-width: 220px; .page-with-new-sidebar { - @media (min-width: $screen-sm-min) { padding-left: $new-sidebar-width; } + // Override position: absolute .right-sidebar { position: fixed; height: 100%; } } +.context-header { + background-color: $gray-normal; + border-bottom: 1px solid $border-color; + display: flex; + align-items: center; + padding: 10px 14px; + + .avatar-container { + flex: 0 0 40px; + } +} + +.settings-avatar { + background-color: $white-light; + + .fa-wrench { + font-size: 20px; + width: 100%; + color: $gl-text-color-light; + text-align: center; + align-self: center; + } +} + .nav-sidebar { position: fixed; z-index: 400; diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index f995145917c..3f05ae67010 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -1,4 +1,8 @@ .nav-sidebar + .context-header + .avatar-container.s40.settings-avatar + = icon('wrench') + .project-title Admin Area %ul.sidebar-top-level-items = nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index 3b658e055b3..8eab32a6391 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -1,4 +1,9 @@ .nav-sidebar + .context-header + .avatar-container.s40.group-avatar + = image_tag group_icon(@group), class: "avatar s40 avatar-tile" + .group-title + = @group.name %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do = link_to group_path(@group), title: 'Home' do diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml index 37ffbbecca8..465bad1de21 100644 --- a/app/views/layouts/nav/_new_profile_sidebar.html.haml +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -1,4 +1,8 @@ .nav-sidebar + .context-header + .avatar-container.s40.settings-avatar + = icon('wrench') + .project-title Settings %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index f85781737aa..de0f4e3d2bc 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -1,5 +1,10 @@ .nav-sidebar - can_edit = can?(current_user, :admin_project, @project) + .context-header + .avatar-container.s40.project-avatar + = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') + .project-title + = @project.name %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do -- cgit v1.2.1 From 646feca6d72b75d92e1a4db6c7ebc6b0d5bc9be4 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 28 Jun 2017 09:52:11 -0500 Subject: Make header into links; CSS tweaks --- app/assets/stylesheets/new_sidebar.scss | 10 ++++++++-- app/views/layouts/nav/_new_admin_sidebar.html.haml | 2 +- app/views/layouts/nav/_new_group_sidebar.html.haml | 2 +- app/views/layouts/nav/_new_profile_sidebar.html.haml | 6 +++--- app/views/layouts/nav/_new_project_sidebar.html.haml | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index c8f0e1f4764..efcf456a8d1 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -19,6 +19,7 @@ $new-sidebar-width: 220px; .context-header { background-color: $gray-normal; border-bottom: 1px solid $border-color; + font-weight: 600; display: flex; align-items: center; padding: 10px 14px; @@ -26,15 +27,20 @@ $new-sidebar-width: 220px; .avatar-container { flex: 0 0 40px; } + + &:hover { + background-color: $border-color; + } } .settings-avatar { background-color: $white-light; - .fa-wrench { + .fa-wrench, + .fa-user { font-size: 20px; width: 100%; - color: $gl-text-color-light; + color: $gl-text-color-secondary; text-align: center; align-self: center; } diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index 3f05ae67010..c9415469912 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -1,5 +1,5 @@ .nav-sidebar - .context-header + = link_to admin_root_path, title: 'Overview', class: 'context-header' do .avatar-container.s40.settings-avatar = icon('wrench') .project-title Admin Area diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index 8eab32a6391..7720918be54 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -1,5 +1,5 @@ .nav-sidebar - .context-header + = link_to group_path(@group), title: 'Home', class: 'context-header' do .avatar-container.s40.group-avatar = image_tag group_icon(@group), class: "avatar s40 avatar-tile" .group-title diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml index 465bad1de21..033ea149cfb 100644 --- a/app/views/layouts/nav/_new_profile_sidebar.html.haml +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -1,8 +1,8 @@ .nav-sidebar - .context-header + = link_to profile_path, title: 'Profile Settings', class: 'context-header' do .avatar-container.s40.settings-avatar - = icon('wrench') - .project-title Settings + = icon('user') + .project-title User Settings %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index de0f4e3d2bc..eae9da5da14 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -1,6 +1,6 @@ .nav-sidebar - can_edit = can?(current_user, :admin_project, @project) - .context-header + = link_to project_path(@project), title: 'Project', class: 'context-header' do .avatar-container.s40.project-avatar = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') .project-title -- cgit v1.2.1 From 1fbb7f977777de7c8808429693359c7a98ffdfcc Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Mon, 19 Jun 2017 18:24:14 +0100 Subject: Removes redundant pending delete checks --- app/finders/todos_finder.rb | 3 +-- app/helpers/search_helper.rb | 4 ++-- app/models/namespace.rb | 2 ++ app/models/project.rb | 5 ++--- app/services/ci/register_job_service.rb | 6 +++--- spec/features/dashboard/todos/todos_spec.rb | 17 ----------------- 6 files changed, 10 insertions(+), 27 deletions(-) diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 5a4c5767be5..3fe37c75381 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -78,7 +78,6 @@ class TodosFinder end def project - return nil if @project&.pending_delete? return @project if defined?(@project) if project? @@ -98,7 +97,7 @@ class TodosFinder def projects(items) item_project_ids = items.reorder(nil).select(:project_id) - ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute.without_deleted + ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids).execute end def type? diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 9c46035057f..8f15904f068 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -97,8 +97,8 @@ module SearchHelper # Autocomplete results for the current user's projects def projects_autocomplete(term, limit = 5) - current_user.authorized_projects.search_by_title(term). - sorted_by_stars.non_archived.limit(limit).map do |p| + current_user.authorized_projects.search_by_title(term) + .sorted_by_stars.non_archived.limit(limit).map do |p| { category: "Projects", id: p.id, diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 583d4fb5244..efbed5a2ef5 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -47,6 +47,8 @@ class Namespace < ActiveRecord::Base before_destroy(prepend: true) { prepare_for_destroy } after_destroy :rm_dir + default_scope { with_deleted } + scope :for_user, -> { where('type IS NULL') } scope :with_statistics, -> do diff --git a/app/models/project.rb b/app/models/project.rb index 40a8e7f07c6..65f81727071 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -222,7 +222,7 @@ class Project < ActiveRecord::Base has_many :uploads, as: :model, dependent: :destroy # Scopes - scope :with_deleted, -> { where(pending_delete: true) } + scope :pending_delete, -> { where(pending_delete: true) } scope :without_deleted, -> { where(pending_delete: false) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } @@ -376,7 +376,6 @@ class Project < ActiveRecord::Base .or(ptable[:description].matches(pattern)) ) - namespaces = unscoped.select(:id) .joins(:namespace) .where(ntable[:name].matches(pattern)) @@ -1456,7 +1455,7 @@ class Project < ActiveRecord::Base def pending_delete_twin return false unless path - Project.with_deleted.find_by_full_path(path_with_namespace) + Project.pending_delete.find_by_full_path(path_with_namespace) end ## diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index 27bdb6b9a2f..b951e8d0c9f 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -54,9 +54,9 @@ module Ci def builds_for_shared_runner new_builds. # don't run projects which have not enabled shared runners and builds - joins(:project).where(projects: { shared_runners_enabled: true, pending_delete: false }). - joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id'). - where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). + joins(:project).where(projects: { shared_runners_enabled: true, pending_delete: false }) + .joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id') + .where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). # Implement fair scheduling # this returns builds that are ordered by number of running builds diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 24da5db305f..7fa4d198e00 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -317,23 +317,6 @@ feature 'Dashboard Todos' do end end - context 'User has a Todo in a project pending deletion' do - before do - deleted_project = create(:project, :public, pending_delete: true) - create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author) - create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author, state: :done) - sign_in(user) - visit dashboard_todos_path - end - - it 'shows "All done" message' do - within('.todos-count') { expect(page).to have_content '0' } - expect(page).to have_content 'To do 0' - expect(page).to have_content 'Done 0' - expect(page).to have_selector('.todos-all-done', count: 1) - end - end - context 'User has a Build Failed todo' do let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } -- cgit v1.2.1 From 49957cf55114d75dc2c1e62c71a98aad98866960 Mon Sep 17 00:00:00 2001 From: Horacio Bertorello Date: Tue, 27 Jun 2017 17:43:02 -0300 Subject: Fix errors caused by attempts to report already blocked or deleted users --- app/controllers/abuse_reports_controller.rb | 14 +++++++++++- .../hb-fix-abuse-report-on-stale-user-profile.yml | 4 ++++ spec/controllers/abuse_reports_controller_spec.rb | 25 ++++++++++++++++++++++ spec/features/abuse_report_spec.rb | 2 +- 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb index 2eac0cabf7a..ed13ead63f9 100644 --- a/app/controllers/abuse_reports_controller.rb +++ b/app/controllers/abuse_reports_controller.rb @@ -1,7 +1,9 @@ class AbuseReportsController < ApplicationController + before_action :set_user, only: [:new] + def new @abuse_report = AbuseReport.new - @abuse_report.user_id = params[:user_id] + @abuse_report.user_id = @user.id @ref_url = params.fetch(:ref_url, '') end @@ -27,4 +29,14 @@ class AbuseReportsController < ApplicationController user_id )) end + + def set_user + @user = User.find_by(id: params[:user_id]) + + if @user.nil? + redirect_to root_path, alert: "Cannot create the abuse report. The user has been deleted." + elsif @user.blocked? + redirect_to @user, alert: "Cannot create the abuse report. This user has been blocked." + end + end end diff --git a/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml new file mode 100644 index 00000000000..ec2f4f9c3d8 --- /dev/null +++ b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml @@ -0,0 +1,4 @@ +--- +title: Fix errors caused by attempts to report already blocked or deleted users +merge_request: 12502 +author: Horacio Bertorello diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb index 80a418feb3e..ada011e7595 100644 --- a/spec/controllers/abuse_reports_controller_spec.rb +++ b/spec/controllers/abuse_reports_controller_spec.rb @@ -13,6 +13,31 @@ describe AbuseReportsController do sign_in(reporter) end + describe 'GET new' do + context 'when the user has already been deleted' do + it 'redirects the reporter to root_path' do + user_id = user.id + user.destroy + + get :new, { user_id: user_id } + + expect(response).to redirect_to root_path + expect(flash[:alert]).to eq('Cannot create the abuse report. The user has been deleted.') + end + end + + context 'when the user has already been blocked' do + it 'redirects the reporter to the user\'s profile' do + user.block + + get :new, { user_id: user.id } + + expect(response).to redirect_to user + expect(flash[:alert]).to eq('Cannot create the abuse report. This user has been blocked.') + end + end + end + describe 'POST create' do context 'with valid attributes' do it 'saves the abuse report' do diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index 5e6cd64c5c1..b88e801c3d7 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -12,7 +12,7 @@ feature 'Abuse reports', feature: true do click_link 'Report abuse' - fill_in 'abuse_report_message', with: 'This user send spam' + fill_in 'abuse_report_message', with: 'This user sends spam' click_button 'Send report' expect(page).to have_content 'Thank you for your report' -- cgit v1.2.1 From da3e4f412846b754d31439da0d884181653bced0 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 27 Jun 2017 17:35:35 -0300 Subject: Add "members_count" and "parent_id" data on namespaces API --- app/models/group.rb | 6 ++++++ ...ers-count-and-parent-id-data-on-namespaces-api.yml | 4 ++++ doc/api/namespaces.md | 15 +++++++++++++-- lib/api/entities.rb | 6 +++++- spec/models/namespace_spec.rb | 19 +++++++++++++++++++ spec/requests/api/namespaces_spec.rb | 16 ++++++++++++++++ 6 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml diff --git a/app/models/group.rb b/app/models/group.rb index 0b93460d473..a6fdb30f84c 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -222,6 +222,12 @@ class Group < Namespace User.where(id: members_with_parents.select(:user_id)) end + def users_with_descendants + members_with_descendants = GroupMember.non_request.where(source_id: descendants.pluck(:id).push(id)) + + User.where(id: members_with_descendants.select(:user_id)) + end + def max_member_access_for_user(user) return GroupMember::OWNER if user.admin? diff --git a/changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml b/changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml new file mode 100644 index 00000000000..d3064a832ca --- /dev/null +++ b/changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml @@ -0,0 +1,4 @@ +--- +title: Add "members_count" and "parent_id" data on namespaces API +merge_request: +author: diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md index 4ad6071a0ed..d38a80176d9 100644 --- a/doc/api/namespaces.md +++ b/doc/api/namespaces.md @@ -29,18 +29,27 @@ Example response: { "id": 1, "path": "user1", - "kind": "user" + "kind": "user", + "full_path": "user1", + "parent_id": "null", + "members_count": "null" }, { "id": 2, "path": "group1", - "kind": "group" + "kind": "group", + "full_path": "group1", + "parent_id": "null", + "members_count": 2 + }, { "id": 3, "path": "bar", "kind": "group", "full_path": "foo/bar", + "parent_id": "9", + "members_count": 5 } ] ``` @@ -72,6 +81,8 @@ Example response: "path": "twitter", "kind": "group", "full_path": "twitter", + "parent_id": "null", + "members_count": 2 } ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index aa91451c9f4..2fe5280bc1c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -444,7 +444,11 @@ module API end class Namespace < Grape::Entity - expose :id, :name, :path, :kind, :full_path + expose :id, :name, :path, :kind, :full_path, :parent_id + + expose :members_count do |namespace, _| + namespace.users_with_descendants.count if namespace.kind == 'group' + end end class MemberAccess < Grape::Entity diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index e7c3acf19eb..d4f898f6d9f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -323,6 +323,25 @@ describe Namespace, models: true do end end + describe '#users_with_descendants', :nested_groups do + let(:user_a) { create(:user) } + let(:user_b) { create(:user) } + + let(:group) { create(:group) } + let(:nested_group) { create(:group, parent: group) } + let(:deep_nested_group) { create(:group, parent: nested_group) } + + it 'returns member users on every nest level without duplication' do + group.add_developer(user_a) + nested_group.add_developer(user_b) + deep_nested_group.add_developer(user_a) + + expect(group.users_with_descendants).to contain_exactly(user_a, user_b) + expect(nested_group.users_with_descendants).to contain_exactly(user_a, user_b) + expect(deep_nested_group.users_with_descendants).to contain_exactly(user_a) + end + end + describe '#user_ids_for_project_authorizations' do it 'returns the user IDs for which to refresh authorizations' do expect(namespace.user_ids_for_project_authorizations) diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 3bf16a3ae27..03b1f549ce0 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -15,6 +15,14 @@ describe API::Namespaces do end context "when authenticated as admin" do + it "returns correct attributes" do + get api("/namespaces", admin) + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response.first).to include('id', 'name', 'path', 'full_path', 'parent_id', 'members_count') + end + it "admin: returns an array of all namespaces" do get api("/namespaces", admin) @@ -37,6 +45,14 @@ describe API::Namespaces do end context "when authenticated as a regular user" do + it "returns correct attributes" do + get api("/namespaces", user) + + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response.first).to include('id', 'name', 'path', 'full_path', 'parent_id', 'members_count') + end + it "user: returns an array of namespaces" do get api("/namespaces", user) -- cgit v1.2.1 From 7db276897f290889ba7c1e66b38389b1c55f3f80 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 27 Jun 2017 19:53:00 -0300 Subject: Adjust projects spec on namespace fields --- spec/requests/api/projects_spec.rb | 4 +++- spec/requests/api/v3/projects_spec.rb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fd7ff0b9cff..b4964ed302a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -698,7 +698,9 @@ describe API::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path + 'full_path' => user.namespace.full_path, + 'parent_id' => nil, + 'members_count' => nil }) end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index cb74868324c..225c28d6f13 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -734,7 +734,9 @@ describe API::V3::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path + 'full_path' => user.namespace.full_path, + 'parent_id' => nil, + 'members_count' => nil }) end -- cgit v1.2.1 From 78379f66ddbb2a1784e55533159993d5460f18a4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 28 Jun 2017 14:01:39 -0500 Subject: Strip trailing whitespace in relative submodule URL --- app/helpers/submodule_helper.rb | 1 + .../unreleased/dm-relative-submodule-url-trailing-whitespace.yml | 4 ++++ spec/helpers/submodule_helper_spec.rb | 5 +++++ 3 files changed, 10 insertions(+) create mode 100644 changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 8e0a1e2ecdf..b24039fb349 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -73,6 +73,7 @@ module SubmoduleHelper end def relative_self_links(url, commit) + url.rstrip! # Map relative links to a namespace and project # For example: # ../bar.git -> same namespace, repo bar diff --git a/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml b/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml new file mode 100644 index 00000000000..616241dd941 --- /dev/null +++ b/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml @@ -0,0 +1,4 @@ +--- +title: Strip trailing whitespace in relative submodule URL +merge_request: +author: diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index cb727430117..9e561d0f191 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -170,6 +170,11 @@ describe SubmoduleHelper do expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end + it 'with trailing whitespace' do + result = relative_self_links('../test.git ', commit_id) + expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) + end + it 'two levels down' do result = relative_self_links('../../test.git', commit_id) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) -- cgit v1.2.1 From 6a9851eae9216a67ca1b1334d9245918b705d1da Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Jun 2017 14:46:48 -0300 Subject: Perform housekeeping only when an import of a fresh project is completed --- app/models/project.rb | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 35d8f9a0154..29eab671acf 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -352,7 +352,16 @@ class Project < ActiveRecord::Base after_transition started: :finished do |project, _| project.reset_cache_and_import_attrs - project.perform_housekeeping + + if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists? + project.run_after_commit do + begin + Projects::HousekeepingService.new(project).execute + rescue Projects::HousekeepingService::LeaseTaken => e + Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}") + end + end + end end end @@ -510,22 +519,6 @@ class Project < ActiveRecord::Base ProjectCacheWorker.perform_async(self.id) end - remove_import_data - end - - def perform_housekeeping - return unless repo_exists? - - run_after_commit do - begin - Projects::HousekeepingService.new(self).execute - rescue Projects::HousekeepingService::LeaseTaken => e - Rails.logger.info("Could not perform housekeeping for project #{self.path_with_namespace} (#{self.id}): #{e}") - end - end - end - - def remove_import_data import_data&.destroy end -- cgit v1.2.1 From 4b446bedcc9491b883708bc13a60e9daad34ecb5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Jun 2017 14:47:14 -0300 Subject: Add CHANGELOG --- changelogs/unreleased/fix-34417.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-34417.yml diff --git a/changelogs/unreleased/fix-34417.yml b/changelogs/unreleased/fix-34417.yml new file mode 100644 index 00000000000..5f012ad0c81 --- /dev/null +++ b/changelogs/unreleased/fix-34417.yml @@ -0,0 +1,4 @@ +--- +title: Perform housekeeping only when an import of a fresh project is completed +merge_request: +author: -- cgit v1.2.1 From 639639ef8a759c3956502a12df62c138022ee104 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Jun 2017 15:58:26 -0300 Subject: Add tests for project import state transition: [:started] => [:finished] --- spec/models/project_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index cc22b8a4edc..0d3494d9e58 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1509,6 +1509,40 @@ describe Project, models: true do end end + describe 'project import state transitions' do + context 'state transition: [:started] => [:finished]' do + let(:housekeeping_service) { spy } + + before do + allow(Projects::HousekeepingService).to receive(:new) { housekeeping_service } + end + + it 'performs housekeeping when an import of a fresh project is completed' do + project = create(:project_empty_repo, :import_started, import_type: :github) + + project.import_finish + + expect(housekeeping_service).to have_received(:execute) + end + + it 'does not perform housekeeping when project repository does not exist' do + project = create(:empty_project, :import_started, import_type: :github) + + project.import_finish + + expect(housekeeping_service).not_to have_received(:execute) + end + + it 'does not perform housekeeping when project does not have a valid import type' do + project = create(:empty_project, :import_started, import_type: nil) + + project.import_finish + + expect(housekeeping_service).not_to have_received(:execute) + end + end + end + describe '#latest_successful_builds_for' do def create_pipeline(status = 'success') create(:ci_pipeline, project: project, -- cgit v1.2.1 From 4d6ee98d861c98fe74340ae6263a866b6e36c9a1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 27 Jun 2017 16:19:16 -0500 Subject: Drop default ORDER scope when calling a find method on a Sortable model --- app/controllers/projects/issues_controller.rb | 2 +- app/models/concerns/sortable.rb | 23 ++++++++++++++++++++++ app/models/project.rb | 6 +++--- .../dm-drop-default-scope-on-sortable-finders.yml | 4 ++++ spec/models/concerns/sortable_spec.rb | 21 ++++++++++++++++++++ 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml create mode 100644 spec/models/concerns/sortable_spec.rb diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index dfc6baa34a4..5b2de93c168 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue return @issue if defined?(@issue) # The Sortable default scope causes performance issues when used with find_by - @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! + @noteable = @issue ||= @project.issues.find_by!(iid: params[:id]) return render_404 unless can?(current_user, :read_issue, @issue) diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index a155a064032..fdacfa5a194 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -5,6 +5,25 @@ module Sortable extend ActiveSupport::Concern + module DropDefaultScopeOnFinders + # Override these methods to drop the `ORDER BY id DESC` default scope. + # See http://dba.stackexchange.com/a/110919 for why we do this. + %i[find find_by find_by!].each do |meth| + define_method meth do |*args, &block| + return super(*args, &block) if block + + unordered_relation = unscope(:order) + + # We cannot simply call `meth` on `unscope(:order)`, since that is also + # an instance of the same relation class this module is included into, + # which means we'd get infinite recursion. + # We explicitly use the original implementation to prevent this. + original_impl = method(__method__).super_method.unbind + original_impl.bind(unordered_relation).call(*args) + end + end + end + included do # By default all models should be ordered # by created_at field starting from newest @@ -18,6 +37,10 @@ module Sortable scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_desc, -> { reorder(name: :desc) } + + # All queries (relations) on this model are instances of this `relation_klass`. + relation_klass = relation_delegate_class(ActiveRecord::Relation) + relation_klass.prepend DropDefaultScopeOnFinders end module ClassMethods diff --git a/app/models/project.rb b/app/models/project.rb index 1176bec8873..fcc47e57b91 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -823,7 +823,7 @@ class Project < ActiveRecord::Base end def ci_service - @ci_service ||= ci_services.reorder(nil).find_by(active: true) + @ci_service ||= ci_services.find_by(active: true) end def deployment_services @@ -831,7 +831,7 @@ class Project < ActiveRecord::Base end def deployment_service - @deployment_service ||= deployment_services.reorder(nil).find_by(active: true) + @deployment_service ||= deployment_services.find_by(active: true) end def monitoring_services @@ -839,7 +839,7 @@ class Project < ActiveRecord::Base end def monitoring_service - @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) + @monitoring_service ||= monitoring_services.find_by(active: true) end def jira_tracker? diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml new file mode 100644 index 00000000000..931e7a1ffc0 --- /dev/null +++ b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml @@ -0,0 +1,4 @@ +--- +title: Drop default ORDER scope when calling a find method on a Sortable model +merge_request: +author: diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb new file mode 100644 index 00000000000..d1e17c4f684 --- /dev/null +++ b/spec/models/concerns/sortable_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Sortable do + let(:relation) { Issue.all } + + describe '#where' do + it 'orders by id, descending' do + order_node = relation.where(iid: 1).order_values.first + expect(order_node).to be_a(Arel::Nodes::Descending) + expect(order_node.expr.name).to eq(:id) + end + end + + describe '#find_by' do + it 'does not order' do + expect(relation).to receive(:unscope).with(:order).and_call_original + + relation.find_by(iid: 1) + end + end +end -- cgit v1.2.1 From 5681bf63490a945df6a70c85bebd94f376181307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 28 Jun 2017 15:38:00 -0400 Subject: Fix a bug where an invalid sort param value was passed to Gitaly --- lib/gitlab/gitaly_client/ref.rb | 2 ++ spec/features/projects/branches_spec.rb | 40 ++++++++++++++++++++++++++++++- spec/lib/gitlab/gitaly_client/ref_spec.rb | 9 +++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb index f4786f28a3a..2d61992f595 100644 --- a/lib/gitlab/gitaly_client/ref.rb +++ b/lib/gitlab/gitaly_client/ref.rb @@ -59,6 +59,8 @@ module Gitlab end def sort_by_param(sort_by) + sort_by = 'name' if sort_by == 'name_asc' + enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym) raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value enum_value diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 8694366de35..0050864d305 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -21,10 +21,48 @@ describe 'Branches', feature: true do it 'shows all the branches' do visit namespace_project_branches_path(project.namespace, project) - repository.branches { |branch| expect(page).to have_content("#{branch.name}") } + repository.branches_sorted_by(:name).first(20).each do |branch| + expect(page).to have_content("#{branch.name}") + end expect(page).to have_content("Protected branches can be managed in project settings") end + it 'sorts the branches by name' do + visit namespace_project_branches_path(project.namespace, project) + + click_button "Name" # Open sorting dropdown + click_link "Name" + + sorted = repository.branches_sorted_by(:name).first(20).map do |branch| + Regexp.escape(branch.name) + end + expect(page).to have_content(/#{sorted.join(".*")}/) + end + + it 'sorts the branches by last updated' do + visit namespace_project_branches_path(project.namespace, project) + + click_button "Name" # Open sorting dropdown + click_link "Last updated" + + sorted = repository.branches_sorted_by(:updated_desc).first(20).map do |branch| + Regexp.escape(branch.name) + end + expect(page).to have_content(/#{sorted.join(".*")}/) + end + + it 'sorts the branches by oldest updated' do + visit namespace_project_branches_path(project.namespace, project) + + click_button "Name" # Open sorting dropdown + click_link "Oldest updated" + + sorted = repository.branches_sorted_by(:updated_asc).first(20).map do |branch| + Regexp.escape(branch.name) + end + expect(page).to have_content(/#{sorted.join(".*")}/) + end + it 'avoids a N+1 query in branches index' do control_count = ActiveRecord::QueryRecorder.new { visit namespace_project_branches_path(project.namespace, project) }.count diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb index 42dba2ff874..8ad39a02b93 100644 --- a/spec/lib/gitlab/gitaly_client/ref_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb @@ -69,6 +69,15 @@ describe Gitlab::GitalyClient::Ref do client.local_branches(sort_by: 'updated_desc') end + it 'translates known mismatches on sort param values' do + expect_any_instance_of(Gitaly::Ref::Stub) + .to receive(:find_local_branches) + .with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash)) + .and_return([]) + + client.local_branches(sort_by: 'name_asc') + end + it 'raises an argument error if an invalid sort_by parameter is passed' do expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError) end -- cgit v1.2.1 From bd4c2847f4a60b392902aa1866c1ccc87cfacbf6 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 28 Jun 2017 17:27:01 -0300 Subject: Rename members_count to members_count_with_descendants and expose only to group admins --- doc/api/namespaces.md | 12 ++++++------ lib/api/entities.rb | 8 ++++++-- lib/api/namespaces.rb | 2 +- spec/requests/api/namespaces_spec.rb | 29 ++++++++++++++++++++++++----- spec/requests/api/projects_spec.rb | 4 +--- spec/requests/api/v3/projects_spec.rb | 4 +--- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md index d38a80176d9..8b5afe29040 100644 --- a/doc/api/namespaces.md +++ b/doc/api/namespaces.md @@ -30,9 +30,7 @@ Example response: "id": 1, "path": "user1", "kind": "user", - "full_path": "user1", - "parent_id": "null", - "members_count": "null" + "full_path": "user1" }, { "id": 2, @@ -40,7 +38,7 @@ Example response: "kind": "group", "full_path": "group1", "parent_id": "null", - "members_count": 2 + "members_count_with_descendants": 2 }, { @@ -49,11 +47,13 @@ Example response: "kind": "group", "full_path": "foo/bar", "parent_id": "9", - "members_count": 5 + "members_count_with_descendants": 5 } ] ``` +**Note**: `members_count_with_descendants` are presented only for group masters/owners. + ## Search for namespace Get all namespaces that match a string in their name or path. @@ -82,7 +82,7 @@ Example response: "kind": "group", "full_path": "twitter", "parent_id": "null", - "members_count": 2 + "members_count_with_descendants": 2 } ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 2fe5280bc1c..cef5a0abe12 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -446,8 +446,12 @@ module API class Namespace < Grape::Entity expose :id, :name, :path, :kind, :full_path, :parent_id - expose :members_count do |namespace, _| - namespace.users_with_descendants.count if namespace.kind == 'group' + expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _| + namespace.users_with_descendants.count + end + + def expose_members_count_with_descendants?(namespace, opts) + namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace) end end diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 30761cb9b55..f1eaff6b0eb 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -17,7 +17,7 @@ module API namespaces = namespaces.search(params[:search]) if params[:search].present? - present paginate(namespaces), with: Entities::Namespace + present paginate(namespaces), with: Entities::Namespace, current_user: current_user end end end diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 03b1f549ce0..fdb7ebb6482 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -18,9 +18,15 @@ describe API::Namespaces do it "returns correct attributes" do get api("/namespaces", admin) + group_kind_json_response = json_response.find { |resource| resource['kind'] == 'group' } + user_kind_json_response = json_response.find { |resource| resource['kind'] == 'user' } + expect(response).to have_http_status(200) expect(response).to include_pagination_headers - expect(json_response.first).to include('id', 'name', 'path', 'full_path', 'parent_id', 'members_count') + expect(group_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', + 'parent_id', 'members_count_with_descendants') + + expect(user_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id') end it "admin: returns an array of all namespaces" do @@ -45,12 +51,25 @@ describe API::Namespaces do end context "when authenticated as a regular user" do - it "returns correct attributes" do + it "returns members_count_with_descendants if user can admin group" do + group1.add_owner(user) + get api("/namespaces", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response.first).to include('id', 'name', 'path', 'full_path', 'parent_id', 'members_count') + owned_group_response = json_response.find { |resource| resource['id'] == group1.id } + + expect(owned_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', + 'parent_id', 'members_count_with_descendants') + end + + it "does not returns members_count_with_descendants if user cannot admin group" do + group1.add_guest(user) + + get api("/namespaces", user) + + guest_group_response = json_response.find { |resource| resource['id'] == group1.id } + + expect(guest_group_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id') end it "user: returns an array of namespaces" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index b4964ed302a..fd7ff0b9cff 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -698,9 +698,7 @@ describe API::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path, - 'parent_id' => nil, - 'members_count' => nil + 'full_path' => user.namespace.full_path }) end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index 225c28d6f13..cb74868324c 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -734,9 +734,7 @@ describe API::V3::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path, - 'parent_id' => nil, - 'members_count' => nil + 'full_path' => user.namespace.full_path }) end -- cgit v1.2.1 From a0c044c114d803f0d89260804131afa2ed7d177b Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 28 Jun 2017 17:55:50 -0300 Subject: Improve tests text --- doc/api/namespaces.md | 1 - spec/requests/api/namespaces_spec.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md index 8b5afe29040..8133251dffe 100644 --- a/doc/api/namespaces.md +++ b/doc/api/namespaces.md @@ -39,7 +39,6 @@ Example response: "full_path": "group1", "parent_id": "null", "members_count_with_descendants": 2 - }, { "id": 3, diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index fdb7ebb6482..26cf653ca8e 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -51,7 +51,7 @@ describe API::Namespaces do end context "when authenticated as a regular user" do - it "returns members_count_with_descendants if user can admin group" do + it "returns correct attributes when user can admin group" do group1.add_owner(user) get api("/namespaces", user) @@ -62,7 +62,7 @@ describe API::Namespaces do 'parent_id', 'members_count_with_descendants') end - it "does not returns members_count_with_descendants if user cannot admin group" do + it "returns correct attributes when user cannot admin group" do group1.add_guest(user) get api("/namespaces", user) -- cgit v1.2.1 From 339313270a55cf1b4304aa6f017532d1544096a4 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 28 Jun 2017 22:47:52 +0000 Subject: Update README.md to reflect where "Secret variables" are located --- doc/ci/ssh_keys/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index befaa06e918..cf25a8b618f 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -34,9 +34,9 @@ instructions to [generate an SSH key](../../ssh/README.md). Do not add a passphrase to the SSH key, or the `before_script` will prompt for it. Then, create a new **Secret Variable** in your project settings on GitLab -following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` -and in the **Value** field paste the content of your _private_ key that you -created earlier. +following **Settings > Pipelines** and look for the "Secret Variables" section. +As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the +content of your _private_ key that you created earlier. It is also good practice to check the server's own public key to make sure you are not being targeted by a man-in-the-middle attack. To do this, add another -- cgit v1.2.1 From b9ec3f23dd749e74533900d16568b55201a37e6e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 28 Jun 2017 17:54:16 -0500 Subject: Make changelog more descriptive [skip ci] --- changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml index 931e7a1ffc0..b359a25053a 100644 --- a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml +++ b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml @@ -1,4 +1,4 @@ --- -title: Drop default ORDER scope when calling a find method on a Sortable model +title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause merge_request: author: -- cgit v1.2.1 From 7cb6466b74c2cb2ee5642acce383235afb2874ff Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 28 Jun 2017 20:01:43 -0300 Subject: Add parent_id back to the tests --- spec/requests/api/projects_spec.rb | 3 ++- spec/requests/api/v3/projects_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fd7ff0b9cff..14dec3d45b1 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -698,7 +698,8 @@ describe API::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path + 'full_path' => user.namespace.full_path, + 'parent_id' => nil }) end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index cb74868324c..af44ffa2331 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -734,7 +734,8 @@ describe API::V3::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path + 'full_path' => user.namespace.full_path, + 'parent_id' => nil }) end -- cgit v1.2.1 From 70b05a83772abc59b3c914c84bc4d2c07749884d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 13 Jun 2017 17:12:31 -0500 Subject: Split up MergeRequestsController --- app/assets/javascripts/dispatcher.js | 14 +- app/assets/javascripts/merge_request_tabs.js | 13 +- app/controllers/concerns/creates_commit.rb | 2 +- .../merge_requests/application_controller.rb | 48 ++ .../merge_requests/conflicts_controller.rb | 66 +++ .../merge_requests/creations_controller.rb | 128 +++++ .../projects/merge_requests/diffs_controller.rb | 66 +++ .../projects/merge_requests_controller.rb | 457 +++------------- app/helpers/application_helper.rb | 5 +- app/helpers/blob_helper.rb | 2 +- app/helpers/compare_helper.rb | 2 +- app/helpers/merge_requests_helper.rb | 4 +- app/helpers/nav_helper.rb | 6 +- app/services/merge_requests/get_urls_service.rb | 2 +- app/services/projects/unlink_fork_service.rb | 2 +- app/views/layouts/header/_new_dropdown.haml | 2 +- app/views/layouts/nav/_project.html.haml | 4 +- app/views/projects/buttons/_dropdown.html.haml | 2 +- app/views/projects/commits/_commits.html.haml | 2 +- app/views/projects/diffs/_diffs.html.haml | 2 +- app/views/projects/diffs/_warning.html.haml | 13 +- .../projects/merge_requests/_commits.html.haml | 8 + .../merge_requests/_how_to_merge.html.haml | 63 +++ .../projects/merge_requests/_mr_box.html.haml | 13 + .../projects/merge_requests/_mr_title.html.haml | 35 ++ .../projects/merge_requests/_new_compare.html.haml | 75 --- .../projects/merge_requests/_new_diffs.html.haml | 1 - .../projects/merge_requests/_new_submit.html.haml | 58 -- .../projects/merge_requests/_pipelines.html.haml | 4 + app/views/projects/merge_requests/_show.html.haml | 97 ---- .../projects/merge_requests/branch_from.html.haml | 2 - .../projects/merge_requests/branch_to.html.haml | 2 - .../projects/merge_requests/conflicts.html.haml | 4 +- .../merge_requests/conflicts/show.html.haml | 38 ++ .../merge_requests/creations/_diffs.html.haml | 1 + .../creations/_new_compare.html.haml | 75 +++ .../merge_requests/creations/_new_submit.html.haml | 57 ++ .../merge_requests/creations/branch_from.html.haml | 2 + .../merge_requests/creations/branch_to.html.haml | 2 + .../merge_requests/creations/new.html.haml | 6 + .../creations/update_branches.html.haml | 3 + app/views/projects/merge_requests/diffs.html.haml | 1 - .../projects/merge_requests/diffs/_diffs.html.haml | 5 + .../merge_requests/diffs/_versions.html.haml | 97 ++++ app/views/projects/merge_requests/index.html.haml | 4 +- .../projects/merge_requests/invalid.html.haml | 4 +- app/views/projects/merge_requests/new.html.haml | 6 - app/views/projects/merge_requests/show.html.haml | 98 +++- .../merge_requests/show/_commits.html.haml | 8 - .../projects/merge_requests/show/_diffs.html.haml | 5 - .../merge_requests/show/_how_to_merge.html.haml | 63 --- .../projects/merge_requests/show/_mr_box.html.haml | 13 - .../merge_requests/show/_mr_title.html.haml | 35 -- .../merge_requests/show/_pipelines.html.haml | 4 - .../merge_requests/show/_versions.html.haml | 97 ---- .../merge_requests/update_branches.html.haml | 3 - app/workers/expire_pipeline_cache_worker.rb | 2 +- config/routes/project.rb | 56 +- features/steps/dashboard/dashboard.rb | 2 +- features/steps/project/source/browse_files.rb | 4 +- spec/controllers/projects/blob_controller_spec.rb | 2 +- .../merge_requests/conflicts_controller_spec.rb | 307 +++++++++++ .../merge_requests/creations_controller_spec.rb | 120 ++++ .../merge_requests/diffs_controller_spec.rb | 160 ++++++ .../projects/merge_requests_controller_spec.rb | 608 +-------------------- spec/features/merge_requests/create_new_mr_spec.rb | 16 +- spec/features/merge_requests/form_spec.rb | 4 +- .../user_uses_slash_commands_spec.rb | 2 +- spec/features/merge_requests/widget_spec.rb | 2 +- spec/features/merge_requests/wip_message_spec.rb | 4 +- .../features/projects/merge_request_button_spec.rb | 4 +- spec/features/projects/merge_requests/list_spec.rb | 2 +- spec/features/projects/user_create_dir_spec.rb | 2 +- .../security/project/internal_access_spec.rb | 2 +- .../security/project/public_access_spec.rb | 2 +- spec/javascripts/fixtures/merge_requests.rb | 19 +- spec/javascripts/fixtures/merge_requests_diffs.rb | 57 ++ spec/javascripts/merge_request_notes_spec.js | 2 +- spec/javascripts/merge_request_tabs_spec.js | 16 +- spec/routing/project_routing_spec.rb | 73 ++- .../issuable_slash_commands_shared_examples.rb | 7 +- .../merge_requests/_commits.html.haml_spec.rb | 2 +- .../merge_requests/_new_submit.html.haml_spec.rb | 31 -- .../creations/_new_submit.html.haml_spec.rb | 31 ++ 84 files changed, 1732 insertions(+), 1638 deletions(-) create mode 100644 app/controllers/projects/merge_requests/application_controller.rb create mode 100644 app/controllers/projects/merge_requests/conflicts_controller.rb create mode 100644 app/controllers/projects/merge_requests/creations_controller.rb create mode 100644 app/controllers/projects/merge_requests/diffs_controller.rb create mode 100644 app/views/projects/merge_requests/_commits.html.haml create mode 100644 app/views/projects/merge_requests/_how_to_merge.html.haml create mode 100644 app/views/projects/merge_requests/_mr_box.html.haml create mode 100644 app/views/projects/merge_requests/_mr_title.html.haml delete mode 100644 app/views/projects/merge_requests/_new_compare.html.haml delete mode 100644 app/views/projects/merge_requests/_new_diffs.html.haml delete mode 100644 app/views/projects/merge_requests/_new_submit.html.haml create mode 100644 app/views/projects/merge_requests/_pipelines.html.haml delete mode 100644 app/views/projects/merge_requests/_show.html.haml delete mode 100644 app/views/projects/merge_requests/branch_from.html.haml delete mode 100644 app/views/projects/merge_requests/branch_to.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/show.html.haml create mode 100644 app/views/projects/merge_requests/creations/_diffs.html.haml create mode 100644 app/views/projects/merge_requests/creations/_new_compare.html.haml create mode 100644 app/views/projects/merge_requests/creations/_new_submit.html.haml create mode 100644 app/views/projects/merge_requests/creations/branch_from.html.haml create mode 100644 app/views/projects/merge_requests/creations/branch_to.html.haml create mode 100644 app/views/projects/merge_requests/creations/new.html.haml create mode 100644 app/views/projects/merge_requests/creations/update_branches.html.haml delete mode 100644 app/views/projects/merge_requests/diffs.html.haml create mode 100644 app/views/projects/merge_requests/diffs/_diffs.html.haml create mode 100644 app/views/projects/merge_requests/diffs/_versions.html.haml delete mode 100644 app/views/projects/merge_requests/new.html.haml delete mode 100644 app/views/projects/merge_requests/show/_commits.html.haml delete mode 100644 app/views/projects/merge_requests/show/_diffs.html.haml delete mode 100644 app/views/projects/merge_requests/show/_how_to_merge.html.haml delete mode 100644 app/views/projects/merge_requests/show/_mr_box.html.haml delete mode 100644 app/views/projects/merge_requests/show/_mr_title.html.haml delete mode 100644 app/views/projects/merge_requests/show/_pipelines.html.haml delete mode 100644 app/views/projects/merge_requests/show/_versions.html.haml delete mode 100644 app/views/projects/merge_requests/update_branches.html.haml create mode 100644 spec/controllers/projects/merge_requests/conflicts_controller_spec.rb create mode 100644 spec/controllers/projects/merge_requests/creations_controller_spec.rb create mode 100644 spec/controllers/projects/merge_requests/diffs_controller_spec.rb create mode 100644 spec/javascripts/fixtures/merge_requests_diffs.rb delete mode 100644 spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb create mode 100644 spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 31a86090242..4247540de22 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -209,8 +209,8 @@ import initExperimentalFlags from './experimental_flags'; new MilestoneSelect(); new gl.IssuableTemplateSelectors(); break; - case 'projects:merge_requests:new': - case 'projects:merge_requests:new_diffs': + case 'projects:merge_requests:creations:new': + case 'projects:merge_requests:creations:diffs': case 'projects:merge_requests:edit': new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); @@ -247,10 +247,6 @@ import initExperimentalFlags from './experimental_flags'; shortcut_handler = new ShortcutsIssuable(true); new ZenMode(); break; - case "projects:merge_requests:diffs": - new gl.Diff(); - new ZenMode(); - break; case 'dashboard:activity': new gl.Activities(); break; @@ -319,7 +315,7 @@ import initExperimentalFlags from './experimental_flags'; new gl.Members(); new UsersSelect(); break; - case 'projects:members:show': + case 'projects:settings:members:show': new gl.MemberExpirationDate('.js-access-expiration-date-groups'); new GroupsSelect(); new gl.MemberExpirationDate(); @@ -386,7 +382,7 @@ import initExperimentalFlags from './experimental_flags'; case 'search:show': new Search(); break; - case 'projects:repository:show': + case 'projects:settings:repository:show': // Initialize Protected Branch Settings new gl.ProtectedBranchCreate(); new gl.ProtectedBranchEditList(); @@ -396,7 +392,7 @@ import initExperimentalFlags from './experimental_flags'; // Initialize expandable settings panels initSettingsPanels(); break; - case 'projects:ci_cd:show': + case 'projects:settings:ci_cd:show': new gl.ProjectVariables(); break; case 'ci:lints:create': diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 786b6014dc6..3cf3233cc65 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -168,9 +168,8 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; // Activate a tab based on the current action activateTab(action) { - const activate = action === 'show' ? 'notes' : action; // important note: the .tab('show') method triggers 'shown.bs.tab' event itself - $(`.merge-request-tabs a[data-action='${activate}']`).tab('show'); + $(`.merge-request-tabs a[data-action='${action}']`).tab('show'); } // Replaces the current Merge Request-specific action in the URL with a new one @@ -185,7 +184,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; // location.pathname # => "/namespace/project/merge_requests/1/diffs" // // location.pathname # => "/namespace/project/merge_requests/1/diffs" - // setCurrentAction('notes') + // setCurrentAction('show') // location.pathname # => "/namespace/project/merge_requests/1" // // location.pathname # => "/namespace/project/merge_requests/1/diffs" @@ -194,13 +193,13 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; // // Returns the new URL String setCurrentAction(action) { - this.currentAction = action === 'show' ? 'notes' : action; + this.currentAction = action; - // Remove a trailing '/commits' '/diffs' '/pipelines' '/new' '/new/diffs' - let newState = location.pathname.replace(/\/(commits|diffs|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); + // Remove a trailing '/commits' '/diffs' '/pipelines' + let newState = location.pathname.replace(/\/(commits|diffs|pipelines)(\.html)?\/?$/, ''); // Append the new action if we're on a tab other than 'notes' - if (this.currentAction !== 'notes') { + if (this.currentAction !== 'show' && this.currentAction !== 'new') { newState += `/${this.currentAction}`; } diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 1a9904bbe57..f87db4d9e84 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -78,7 +78,7 @@ module CreatesCommit end def new_merge_request_path - new_namespace_project_merge_request_path( + namespace_project_new_merge_request_path( @project_to_commit_into.namespace, @project_to_commit_into, merge_request: { diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb new file mode 100644 index 00000000000..5de0f828010 --- /dev/null +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -0,0 +1,48 @@ +class Projects::MergeRequests::ApplicationController < Projects::ApplicationController + before_action :check_merge_requests_available! + before_action :merge_request + before_action :authorize_read_merge_request! + before_action :ensure_ref_fetched + + private + + def merge_request + @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) + end + + # Make sure merge requests created before 8.0 + # have head file in refs/merge-requests/ + def ensure_ref_fetched + @merge_request.ensure_ref_fetched + end + + def merge_request_params + params.require(:merge_request) + .permit(merge_request_params_attributes) + end + + def merge_request_params_attributes + [ + :assignee_id, + :description, + :force_remove_source_branch, + :lock_version, + :milestone_id, + :source_branch, + :source_project_id, + :state_event, + :target_branch, + :target_project_id, + :task_num, + :title, + + label_ids: [] + ] + end + + def set_pipeline_variables + @pipelines = @merge_request.all_pipelines + @pipeline = @merge_request.head_pipeline + @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 + end +end diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb new file mode 100644 index 00000000000..a71f23e790d --- /dev/null +++ b/app/controllers/projects/merge_requests/conflicts_controller.rb @@ -0,0 +1,66 @@ +class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::ApplicationController + include IssuableActions + + before_action :authorize_can_resolve_conflicts! + + def show + respond_to do |format| + format.html do + labels + end + + format.json do + if @conflicts_list.can_be_resolved_in_ui? + render json: @conflicts_list + elsif @merge_request.can_be_merged? + render json: { + message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.', + type: 'error' + } + else + render json: { + message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.', + type: 'error' + } + end + end + end + end + + def conflict_for_path + return render_404 unless @conflicts_list.can_be_resolved_in_ui? + + file = @conflicts_list.file_for_path(params[:old_path], params[:new_path]) + + return render_404 unless file + + render json: file, full_content: true + end + + def resolve_conflicts + return render_404 unless @conflicts_list.can_be_resolved_in_ui? + + if @merge_request.can_be_merged? + render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' } + return + end + + begin + ::MergeRequests::Conflicts::ResolveService + .new(merge_request) + .execute(current_user, params) + + flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' + + render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) } + rescue Gitlab::Conflict::ResolutionError => e + render status: :bad_request, json: { message: e.message } + end + end + + def authorize_can_resolve_conflicts! + @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request) + + return render_404 unless @conflicts_list.can_be_resolved_by?(current_user) + end +end diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb new file mode 100644 index 00000000000..da058da795e --- /dev/null +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -0,0 +1,128 @@ +class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController + include DiffForPath + include DiffHelper + + skip_before_action :merge_request + skip_before_action :ensure_ref_fetched + before_action :authorize_create_merge_request! + before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path] + before_action :build_merge_request, except: [:create] + + def new + define_new_vars + end + + def create + @target_branches ||= [] + @merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute + + if @merge_request.valid? + redirect_to(merge_request_path(@merge_request)) + else + @source_project = @merge_request.source_project + @target_project = @merge_request.target_project + + define_new_vars + render action: "new" + end + end + + def pipelines + @pipelines = @merge_request.all_pipelines + + Gitlab::PollingInterval.set_header(response, interval: 10_000) + + render json: { + pipelines: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines) + } + end + + def diffs + @diffs = if @merge_request.can_be_created + @merge_request.diffs(diff_options) + else + [] + end + @diff_notes_disabled = true + + @environment = @merge_request.environments_for(current_user).last + + render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) } + end + + def diff_for_path + @diffs = @merge_request.diffs(diff_options) + @diff_notes_disabled = true + + render_diff_for_path(@diffs) + end + + def branch_from + # This is always source + @source_project = @merge_request.nil? ? @project : @merge_request.source_project + + if params[:ref].present? + @ref = params[:ref] + @commit = @repository.commit("refs/heads/#{@ref}") + end + + render layout: false + end + + def branch_to + @target_project = selected_target_project + + if params[:ref].present? + @ref = params[:ref] + @commit = @target_project.commit("refs/heads/#{@ref}") + end + + render layout: false + end + + def update_branches + @target_project = selected_target_project + @target_branches = @target_project.repository.branch_names + + render layout: false + end + + private + + def build_merge_request + params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) + @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute + end + + def define_new_vars + @noteable = @merge_request + + @target_branches = if @merge_request.target_project + @merge_request.target_project.repository.branch_names + else + [] + end + + @target_project = @merge_request.target_project + @source_project = @merge_request.source_project + @commits = @merge_request.compare_commits.reverse + @commit = @merge_request.diff_head_commit + + @note_counts = Note.where(commit_id: @commits.map(&:id)) + .group(:commit_id).count + + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + + set_pipeline_variables + end + + def selected_target_project + if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil? + @project + else + @project.forked_project_link.forked_from_project + end + end +end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb new file mode 100644 index 00000000000..330b7df4541 --- /dev/null +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -0,0 +1,66 @@ +class Projects::MergeRequests::DiffsController < Projects::MergeRequests::ApplicationController + include DiffForPath + include DiffHelper + include RendersNotes + + before_action :apply_diff_view_cookie! + before_action :define_diff_vars + before_action :define_diff_comment_vars + + def show + @environment = @merge_request.environments_for(current_user).last + + render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") } + end + + def diff_for_path + render_diff_for_path(@diffs) + end + + private + + def define_diff_vars + @merge_request_diff = + if params[:diff_id] + @merge_request.merge_request_diffs.viewable.find(params[:diff_id]) + else + @merge_request.merge_request_diff + end + + @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff + @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } + + if params[:start_sha].present? + @start_sha = params[:start_sha] + @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } + + unless @start_version + @start_sha = @merge_request_diff.head_commit_sha + @start_version = @merge_request_diff + end + end + + @compare = + if @start_sha + @merge_request_diff.compare_with(@start_sha) + else + @merge_request_diff + end + + @diffs = @compare.diffs(diff_options) + end + + def define_diff_comment_vars + @new_diff_note_attrs = { + noteable_type: 'MergeRequest', + noteable_id: @merge_request.id + } + + @diff_notes_disabled = false + + @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs? + + @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs) + @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes)) + end +end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 879ff6d393e..04f8e95aa09 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -1,38 +1,17 @@ -class Projects::MergeRequestsController < Projects::ApplicationController +class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationController include ToggleSubscriptionAction - include DiffForPath - include DiffHelper include IssuableActions include RendersNotes include ToggleAwardEmoji include IssuableCollections - before_action :check_merge_requests_available! - before_action :merge_request, only: [ - :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, - :pipeline_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues, :commit_change_content - ] - before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines] - before_action :define_show_vars, only: [:diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] - before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines] - before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] - before_action :check_if_can_be_merged, only: :show - before_action :apply_diff_view_cookie!, only: [:new_diffs] - before_action :build_merge_request, only: [:new, :new_diffs] - - # Allow read any merge_request - before_action :authorize_read_merge_request! - - # Allow write(create) merge_request - before_action :authorize_create_merge_request!, only: [:new, :create] - - # Allow modify merge_request + skip_before_action :merge_request, only: [:index, :bulk_update] + skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update] + before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authenticate_user!, only: [:assign_related_issues] - before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts] - def index @collection_type = "MergeRequest" @merge_requests = merge_requests_collection @@ -72,10 +51,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def show + validates_merge_request + ensure_ref_fetched + close_merge_request_without_source_project + check_if_can_be_merged + respond_to do |format| format.html do - define_discussion_vars - define_show_vars + # Build a note object for comment form + @note = @project.notes.new(noteable: @merge_request) + + @discussions = @merge_request.discussions + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) + + @noteable = @merge_request + @commits_count = @merge_request.commits_count + + if @merge_request.locked_long_ago? + @merge_request.unlock_mr + @merge_request.close + end + + labels + + set_pipeline_variables end format.json do @@ -98,198 +97,40 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - def diffs - apply_diff_view_cookie! - - respond_to do |format| - format.html { define_discussion_vars } - format.json do - define_diff_vars - define_diff_comment_vars - - @environment = @merge_request.environments_for(current_user).last - - render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } - end - end - end - - # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new - # and uses that (unsaved) MR. - # - def diff_for_path - if params[:id] - merge_request - define_diff_vars - define_diff_comment_vars - else - build_merge_request - @compare = @merge_request - @diffs = @compare.diffs(diff_options) - @diff_notes_disabled = true - end - - render_diff_for_path(@diffs) - end - def commits - respond_to do |format| - format.html do - define_discussion_vars - - render 'show' - end - format.json do - # Get commits from repository - # or from cache if already merged - @commits = @merge_request.commits - @note_counts = Note.where(commit_id: @commits.map(&:id)) - .group(:commit_id).count - - render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } - end - end - end - - def conflicts - respond_to do |format| - format.html { define_discussion_vars } - - format.json do - if @conflicts_list.can_be_resolved_in_ui? - render json: @conflicts_list - elsif @merge_request.can_be_merged? - render json: { - message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.', - type: 'error' - } - else - render json: { - message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.', - type: 'error' - } - end - end - end - end - - def conflict_for_path - return render_404 unless @conflicts_list.can_be_resolved_in_ui? - - file = @conflicts_list.file_for_path(params[:old_path], params[:new_path]) - - return render_404 unless file - - render json: file, full_content: true - end - - def resolve_conflicts - return render_404 unless @conflicts_list.can_be_resolved_in_ui? - - if @merge_request.can_be_merged? - render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' } - return - end - - begin - MergeRequests::Conflicts::ResolveService - .new(merge_request) - .execute(current_user, params) - - flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' + # Get commits from repository + # or from cache if already merged + @commits = @merge_request.commits + @note_counts = Note.where(commit_id: @commits.map(&:id)) + .group(:commit_id).count - render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) } - rescue Gitlab::Conflict::ResolutionError => e - render status: :bad_request, json: { message: e.message } - end + render json: { html: view_to_html_string('projects/merge_requests/_commits') } end def pipelines @pipelines = @merge_request.all_pipelines - respond_to do |format| - format.html do - define_discussion_vars - - render 'show' - end - - format.json do - Gitlab::PollingInterval.set_header(response, interval: 10_000) + Gitlab::PollingInterval.set_header(response, interval: 10_000) - render json: PipelineSerializer - .new(project: @project, current_user: @current_user) - .represent(@pipelines) - end - end - end - - def new - respond_to do |format| - format.html { define_new_vars } - format.json do - define_pipelines_vars - - Gitlab::PollingInterval.set_header(response, interval: 10_000) - - render json: { - pipelines: PipelineSerializer - .new(project: @project, current_user: @current_user) - .represent(@pipelines) - } - end - end - end - - def new_diffs - respond_to do |format| - format.html do - define_new_vars - @show_changes_tab = true - render "new" - end - format.json do - @diffs = if @merge_request.can_be_created - @merge_request.diffs(diff_options) - else - [] - end - @diff_notes_disabled = true - - @environment = @merge_request.environments_for(current_user).last - - render json: { html: view_to_html_string('projects/merge_requests/_new_diffs', diffs: @diffs, environment: @environment) } - end - end - end - - def create - @target_branches ||= [] - @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute - - if @merge_request.valid? - redirect_to(merge_request_path(@merge_request)) - else - @source_project = @merge_request.source_project - @target_project = @merge_request.target_project - render action: "new" - end + render json: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent(@pipelines) end def edit - @source_project = @merge_request.source_project - @target_project = @merge_request.target_project - @target_branches = @merge_request.target_project.repository.branch_names + define_edit_vars end def update - @merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request) + @merge_request = ::MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request) respond_to do |format| format.html do if @merge_request.valid? redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request]) else + define_edit_vars + render :edit end end @@ -299,11 +140,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end rescue ActiveRecord::StaleObjectError + define_edit_vars if request.format.html? + render_conflict_response end def remove_wip - @merge_request = MergeRequests::UpdateService + @merge_request = ::MergeRequests::UpdateService .new(project, current_user, wip_event: 'unwip') .execute(@merge_request) @@ -319,7 +162,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController return access_denied! end - MergeRequests::MergeWhenPipelineSucceedsService + ::MergeRequests::MergeWhenPipelineSucceedsService .new(@project, current_user) .cancel(@merge_request) @@ -338,53 +181,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - def branch_from - # This is always source - @source_project = @merge_request.nil? ? @project : @merge_request.source_project - - if params[:ref].present? - @ref = params[:ref] - @commit = @repository.commit("refs/heads/#{@ref}") - end - - render layout: false - end - - def branch_to - @target_project = selected_target_project - - if params[:ref].present? - @ref = params[:ref] - @commit = @target_project.commit("refs/heads/#{@ref}") - end - - render layout: false - end - - def update_branches - @target_project = selected_target_project - @target_branches = @target_project.repository.branch_names - - render layout: false - end - def assign_related_issues - result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute + result = ::MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute - respond_to do |format| - format.html do - case result[:count] - when 0 - flash[:error] = "Failed to assign you issues related to the merge request" - when 1 - flash[:notice] = "1 issue has been assigned to you" - else - flash[:notice] = "#{result[:count]} issues have been assigned to you" - end - - redirect_to(merge_request_path(@merge_request)) - end + case result[:count] + when 0 + flash[:error] = "Failed to assign you issues related to the merge request" + when 1 + flash[:notice] = "1 issue has been assigned to you" + else + flash[:notice] = "#{result[:count]} issues have been assigned to you" end + + redirect_to(merge_request_path(@merge_request)) end def pipeline_status @@ -432,17 +241,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController protected - def selected_target_project - if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil? - @project - else - @project.forked_project_link.forked_from_project - end - end - - def merge_request - @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) - end alias_method :subscribable_resource, :merge_request alias_method :issuable, :merge_request alias_method :awardable, :merge_request @@ -455,12 +253,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController return render_404 unless can?(current_user, :admin_merge_request, @merge_request) end - def authorize_can_resolve_conflicts! - @conflicts_list = MergeRequests::Conflicts::ListService.new(@merge_request) - - return render_404 unless @conflicts_list.can_be_resolved_by?(current_user) - end - def validates_merge_request # Show git not found page # if there is no saved commits between source & target branch @@ -470,133 +262,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - def define_show_vars - @noteable = @merge_request - @commits_count = @merge_request.commits_count - - if @merge_request.locked_long_ago? - @merge_request.unlock_mr - @merge_request.close - end - - labels - define_pipelines_vars - end - - # Discussion tab data is rendered on html responses of actions - # :show, :diff, :commits, :builds. but not when request the data through AJAX - def define_discussion_vars - # Build a note object for comment form - @note = @project.notes.new(noteable: @merge_request) - - @discussions = @merge_request.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) - end - - def define_diff_vars - @merge_request_diff = - if params[:diff_id] - @merge_request.merge_request_diffs.viewable.find(params[:diff_id]) - else - @merge_request.merge_request_diff - end - - @merge_request_diffs = @merge_request.merge_request_diffs.viewable.select_without_diff - @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } - - if params[:start_sha].present? - @start_sha = params[:start_sha] - @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } - - unless @start_version - @start_sha = @merge_request_diff.head_commit_sha - @start_version = @merge_request_diff - end - end - - @compare = - if @start_sha - @merge_request_diff.compare_with(@start_sha) - else - @merge_request_diff - end - - @diffs = @compare.diffs(diff_options) - end - - def define_diff_comment_vars - @new_diff_note_attrs = { - noteable_type: 'MergeRequest', - noteable_id: @merge_request.id - } - - @diff_notes_disabled = false - - @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs? - - @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs) - @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes)) - end - - def define_pipelines_vars - @pipelines = @merge_request.all_pipelines - @pipeline = @merge_request.head_pipeline - @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 - end - - def define_new_vars - @noteable = @merge_request - - @target_branches = if @merge_request.target_project - @merge_request.target_project.repository.branch_names - else - [] - end - - @target_project = merge_request.target_project - @source_project = merge_request.source_project - @commits = @merge_request.compare_commits.reverse - @commit = @merge_request.diff_head_commit - - @note_counts = Note.where(commit_id: @commits.map(&:id)) - .group(:commit_id).count - - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute - - @show_changes_tab = params[:show_changes].present? - - define_pipelines_vars - end - def invalid_mr # Render special view for MR with removed target branch render 'invalid' end - def merge_request_params - params.require(:merge_request) - .permit(merge_request_params_attributes) - end - - def merge_request_params_attributes - [ - :assignee_id, - :description, - :force_remove_source_branch, - :lock_version, - :milestone_id, - :source_branch, - :source_project_id, - :state_event, - :target_branch, - :target_project_id, - :task_num, - :title, - - label_ids: [] - ] - end - def merge_params params.permit(merge_params_attributes) end @@ -605,22 +275,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController [:should_remove_source_branch, :commit_message] end - # Make sure merge requests created before 8.0 - # have head file in refs/merge-requests/ - def ensure_ref_fetched - @merge_request.ensure_ref_fetched - end - def merge_when_pipeline_succeeds_active? params[:merge_when_pipeline_succeeds].present? && @merge_request.head_pipeline && @merge_request.head_pipeline.active? end - def build_merge_request - params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) - @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute - end - def close_merge_request_without_source_project if !@merge_request.source_project && @merge_request.open? @merge_request.close @@ -648,7 +307,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController return :failed unless @merge_request.head_pipeline if @merge_request.head_pipeline.active? - MergeRequests::MergeWhenPipelineSucceedsService + ::MergeRequests::MergeWhenPipelineSucceedsService .new(@project, current_user, merge_params) .execute(@merge_request) @@ -672,4 +331,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController def serializer MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) end + + def define_edit_vars + @source_project = @merge_request.source_project + @target_project = @merge_request.target_project + @target_branches = @merge_request.target_project.repository.branch_names + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index dc7ff78f3df..7be8e3b96cf 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -131,10 +131,7 @@ module ApplicationHelper end def body_data_page - path = controller.controller_path.split('/') - namespace = path.first if path.second - - [namespace, controller.controller_name, controller.action_name].compact.join(':') + [*controller.controller_path.split('/'), controller.action_name].compact.join(':') end # shortcut for gitlab config diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 3efa7c36057..ee36617ba9a 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -284,7 +284,7 @@ module BlobHelper merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project)) if merge_project - options << link_to("create a merge request", new_namespace_project_merge_request_path(project.namespace, project)) + options << link_to("create a merge request", namespace_project_new_merge_request_path(project.namespace, project)) end options diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index 2aa0449c46e..424ded2b69d 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -9,7 +9,7 @@ module CompareHelper end def create_mr_path(from = params[:from], to = params[:to], project = @project) - new_namespace_project_merge_request_path( + namespace_project_new_merge_request_path( project.namespace, project, merge_request: { diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 39d30631646..54d6f86fa11 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,7 +1,7 @@ module MergeRequestsHelper def new_mr_path_from_push_event(event) target_project = event.project.default_merge_request_target - new_namespace_project_merge_request_path( + namespace_project_new_merge_request_path( event.project.namespace, event.project, new_mr_from_push_event(event, target_project) @@ -48,7 +48,7 @@ module MergeRequestsHelper end def mr_change_branches_path(merge_request) - new_namespace_project_merge_request_path( + namespace_project_new_merge_request_path( @project.namespace, @project, merge_request: { source_project_id: merge_request.source_project_id, diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 833d3c36b28..e589ed4e56d 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -1,11 +1,7 @@ module NavHelper def page_gutter_class if current_path?('merge_requests#show') || - current_path?('merge_requests#diffs') || - current_path?('merge_requests#commits') || - current_path?('merge_requests#builds') || - current_path?('merge_requests#conflicts') || - current_path?('merge_requests#pipelines') || + current_path?('projects/merge_requests/conflicts#show') || current_path?('issues#show') || current_path?('milestones#show') if cookies[:collapsed_gutter] == 'true' diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb index f00a33969a8..5dd40e07c0d 100644 --- a/app/services/merge_requests/get_urls_service.rb +++ b/app/services/merge_requests/get_urls_service.rb @@ -49,7 +49,7 @@ module MergeRequests def url_for_new_merge_request(branch_name) merge_request_params = { source_branch: branch_name } - url = Gitlab::Routing.url_helpers.new_namespace_project_merge_request_url(project.namespace, project, merge_request: merge_request_params) + url = Gitlab::Routing.url_helpers.namespace_project_new_merge_request_url(project.namespace, project, merge_request: merge_request_params) { branch_name: branch_name, url: url, new_merge_request: true } end diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index 315c3e16292..f385e426827 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -10,7 +10,7 @@ module Projects merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project) merge_requests.each do |mr| - MergeRequests::CloseService.new(@project, @current_user).execute(mr) + ::MergeRequests::CloseService.new(@project, @current_user).execute(mr) end @project.forked_project_link.destroy diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 9ff1164f2ee..4d41579168c 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -33,7 +33,7 @@ = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project) - if merge_project %li - = link_to 'New merge request', new_namespace_project_merge_request_path(merge_project.namespace, merge_project) + = link_to 'New merge request', namespace_project_new_merge_request_path(merge_project.namespace, merge_project) - if create_project_snippet %li.header-new-project-snippet = link_to 'New snippet', new_namespace_project_snippet_path(@project.namespace, @project) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 29658da7792..68024d782a6 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -31,7 +31,9 @@ %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) - if project_nav_tab? :merge_requests - = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do + - controllers = [:merge_requests, 'projects/merge_requests/conflicts'] + - controllers.push(:merge_requests, :labels, :milestones) unless @project.default_issues_tracker? + = nav_link(controller: controllers) do = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do %span Merge Requests diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 960b57a8008..aa1a533b5cb 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -16,7 +16,7 @@ - if merge_project %li - = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do + = link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project) do = icon('tasks fw') #{ _('New merge request') } diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index 93fd0789c11..cf8dffc8957 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -8,7 +8,7 @@ %li.commits-row{ data: { day: day } } %ul.content-list.commit-list - = render commits, project: project, ref: ref + = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref } - if hidden > 0 %li.alert.alert-warning diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index d538c4c86c8..f9385459a66 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -10,7 +10,7 @@ - if show_whitespace_toggle - if current_controller?(:commit) = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs') - - elsif current_controller?(:merge_requests) + - elsif current_controller?('projects/merge_requests/diffs') = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs') - elsif current_controller?(:compare) = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs') diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 295a1b62535..402c18c447e 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -2,13 +2,12 @@ %h4 Too many changes to show. .pull-right - - if current_controller?(:commit) or current_controller?(:merge_requests) - - if current_controller?(:commit) - = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm" - = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm" - - elsif @merge_request && @merge_request.persisted? - = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm" - = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm" + - if current_controller?(:commit) + = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm" + = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm" + - elsif current_controller?('projects/merge_requests/diffs') && @merge_request&.persisted? + = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm" + = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm" %p To preserve performance only %strong #{diff_files.size} of #{diff_files.real_size} diff --git a/app/views/projects/merge_requests/_commits.html.haml b/app/views/projects/merge_requests/_commits.html.haml new file mode 100644 index 00000000000..11793919ff7 --- /dev/null +++ b/app/views/projects/merge_requests/_commits.html.haml @@ -0,0 +1,8 @@ +- if @commits.empty? + .commits-empty + %h4 + There are no commits yet. + = custom_icon ('illustration_no_commits') +- else + %ol#commits-list.list-unstyled + = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch diff --git a/app/views/projects/merge_requests/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml new file mode 100644 index 00000000000..766cb272bec --- /dev/null +++ b/app/views/projects/merge_requests/_how_to_merge.html.haml @@ -0,0 +1,63 @@ +#modal_merge_info.modal + .modal-dialog + .modal-content + .modal-header + %a.close{ href: "#", "data-dismiss" => "modal" } × + %h3 Check out, review, and merge locally + .modal-body + %p + %strong Step 1. + Fetch and check out the branch for this merge request + = clipboard_button(target: "pre#merge-info-1", title: "Copy commands to clipboard") + %pre.dark#merge-info-1 + - if @merge_request.for_fork? + :preserve + git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch} + git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD + - else + :preserve + git fetch origin + git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch} + %p + %strong Step 2. + Review the changes locally + + %p + %strong Step 3. + Merge the branch and fix any conflicts that come up + = clipboard_button(target: "pre#merge-info-3", title: "Copy commands to clipboard") + %pre.dark#merge-info-3 + - if @merge_request.for_fork? + :preserve + git checkout #{h @merge_request.target_branch} + git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} + - else + :preserve + git checkout #{h @merge_request.target_branch} + git merge --no-ff #{h @merge_request.source_branch} + %p + %strong Step 4. + Push the result of the merge to GitLab + = clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard") + %pre.dark#merge-info-4 + :preserve + git push origin #{h @merge_request.target_branch} + - unless @merge_request.can_be_merged_by?(current_user) + %p + Note that pushing to GitLab requires write access to this repository. + %p + %strong Tip: + = succeed '.' do + You can also checkout merge requests locally by + = link_to 'following these guidelines', help_page_path('user/project/merge_requests/index.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer' + +:javascript + $(function(){ + var modal = $('#modal_merge_info').modal({modal: true, show:false}); + $('.how_to_merge_link').bind("click", function(){ + modal.show(); + }); + $('.modal-header .close').bind("click", function(){ + modal.hide(); + }) + }) diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml new file mode 100644 index 00000000000..8a390cf8700 --- /dev/null +++ b/app/views/projects/merge_requests/_mr_box.html.haml @@ -0,0 +1,13 @@ +.detail-page-description.content-block + %h2.title + = markdown_field(@merge_request, :title) + + %div + - if @merge_request.description.present? + .description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' } + .wiki + = markdown_field(@merge_request, :description) + %textarea.hidden.js-task-list-field + = @merge_request.description + + = edited_time_ago_with_tooltip(@merge_request, placement: 'bottom') diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml new file mode 100644 index 00000000000..d9428b8562e --- /dev/null +++ b/app/views/projects/merge_requests/_mr_title.html.haml @@ -0,0 +1,35 @@ +- if @merge_request.closed_without_fork? + .alert.alert-danger + %p The source project of this merge request has been removed. + +.clearfix.detail-page-header + .issuable-header + .issuable-status-box.status-box{ class: status_box_class(@merge_request) } + = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg") + %span.hidden-xs + = @merge_request.state_human_name + + %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } + = icon('angle-double-left') + + .issuable-meta + = issuable_meta(@merge_request, @project, "Merge request") + + - if can?(current_user, :update_merge_request, @merge_request) + .issuable-actions + .clearfix.issue-btn-group.dropdown + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } + Options + = icon('caret-down') + .dropdown-menu.dropdown-menu-align-right.hidden-lg + %ul + %li{ class: merge_request_button_visibility(@merge_request, true) } + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' + %li{ class: merge_request_button_visibility(@merge_request, false) } + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' + %li + = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit' + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request' + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request' + = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do + Edit diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml deleted file mode 100644 index 0f37abb579c..00000000000 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ /dev/null @@ -1,75 +0,0 @@ -%h3.page-title - New Merge Request - -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| - .hide.alert.alert-danger.mr-compare-errors - .merge-request-branches.row - .col-md-6 - .panel.panel-default.panel-new-merge-request - .panel-heading - Source branch - .panel-body.clearfix - .merge-request-select.dropdown - = f.hidden_field :source_project_id - = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } - .dropdown-menu.dropdown-menu-selectable.dropdown-source-project - = dropdown_title("Select source project") - = dropdown_filter("Search projects") - = dropdown_content do - = render 'projects/merge_requests/dropdowns/project', - projects: [@merge_request.source_project], - selected: f.object.source_project_id - .merge-request-select.dropdown - = f.hidden_field :source_branch - = dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch git-revision-dropdown-toggle" } - .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch.git-revision-dropdown - = dropdown_title("Select source branch") - = dropdown_filter("Search branches") - = dropdown_content do - = render 'projects/merge_requests/dropdowns/branch', - branches: @merge_request.source_branches, - selected: f.object.source_branch - .panel-footer - .text-center= icon('spinner spin', class: 'js-source-loading') - %ul.list-unstyled.mr_source_commit - - .col-md-6 - .panel.panel-default.panel-new-merge-request - .panel-heading - Target branch - .panel-body.clearfix - - projects = target_projects(@project) - .merge-request-select.dropdown - = f.hidden_field :target_project_id - = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } - .dropdown-menu.dropdown-menu-selectable.dropdown-target-project - = dropdown_title("Select target project") - = dropdown_filter("Search projects") - = dropdown_content do - = render 'projects/merge_requests/dropdowns/project', - projects: projects, - selected: f.object.target_project_id - .merge-request-select.dropdown - = f.hidden_field :target_branch - = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch git-revision-dropdown-toggle" } - .dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown.git-revision-dropdown - = dropdown_title("Select target branch") - = dropdown_filter("Search branches") - = dropdown_content do - = render 'projects/merge_requests/dropdowns/branch', - branches: @merge_request.target_branches, - selected: f.object.target_branch - .panel-footer - .text-center= icon('spinner spin', class: "js-target-loading") - %ul.list-unstyled.mr_target_commit - - - if @merge_request.errors.any? - = form_errors(@merge_request) - = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" - -:javascript - new Compare({ - targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", - sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", - targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}" - }); diff --git a/app/views/projects/merge_requests/_new_diffs.html.haml b/app/views/projects/merge_requests/_new_diffs.html.haml deleted file mode 100644 index 627fc4e9671..00000000000 --- a/app/views/projects/merge_requests/_new_diffs.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml deleted file mode 100644 index e3ecbee5490..00000000000 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ /dev/null @@ -1,58 +0,0 @@ -%h3.page-title - New Merge Request -%p.slead - - source_title, target_title = format_mr_branch_names(@merge_request) - From - %strong.ref-name= source_title - %span into - %strong.ref-name= target_title - - %span.pull-right - = link_to 'Change branches', mr_change_branches_path(@merge_request) -%hr -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f| - = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits - = f.hidden_field :source_project_id - = f.hidden_field :source_branch - = f.hidden_field :target_project_id - = f.hidden_field :target_branch - -.mr-compare.merge-request - - if @commits.empty? - .commits-empty - %h4 - There are no commits yet. - = custom_icon ('illustration_no_commits') - - else - %ul.merge-request-tabs.nav-links.no-top.no-bottom - %li.commits-tab.active - = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do - Commits - %span.badge= @commits.size - - if @pipelines.any? - %li.builds-tab - = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do - Pipelines - %span.badge= @pipelines.size - %li.diffs-tab - = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do - Changes - %span.badge= @merge_request.diff_size - - .tab-content - #commits.commits.tab-pane.active - = render "projects/merge_requests/show/commits" - #diffs.diffs.tab-pane - -# This tab is always loaded via AJAX - - if @pipelines.any? - #pipelines.pipelines.tab-pane - = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true - - .mr-loading-status - = spinner - -:javascript - var merge_request = new MergeRequest({ - action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", - setUrl: false, - }); diff --git a/app/views/projects/merge_requests/_pipelines.html.haml b/app/views/projects/merge_requests/_pipelines.html.haml new file mode 100644 index 00000000000..2f1dbe87619 --- /dev/null +++ b/app/views/projects/merge_requests/_pipelines.html.haml @@ -0,0 +1,4 @@ +- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json) +- disable_initialization = local_assigns.fetch(:disable_initialization, false) + += render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml deleted file mode 100644 index 75120409bb3..00000000000 --- a/app/views/projects/merge_requests/_show.html.haml +++ /dev/null @@ -1,97 +0,0 @@ -- @content_class = "limit-container-width" unless fluid_layout -- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" -- page_description @merge_request.description -- page_card_attributes @merge_request.card_attributes -- content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('common_vue') - = page_specific_javascript_bundle_tag('diff_notes') - -.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) } - = render "projects/merge_requests/show/mr_title" - - .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } } - = render "projects/merge_requests/show/mr_box" - - - if @merge_request.source_branch_exists? - = render "projects/merge_requests/show/how_to_merge" - - :javascript - window.gl.mrWidgetData = #{serialize_issuable(@merge_request)} - - #js-vue-mr-widget.mr-widget - - - content_for :page_specific_javascripts do - = webpack_bundle_tag 'common_vue' - = webpack_bundle_tag 'vue_merge_request_widget' - - .content-block.content-block-small.emoji-list-container - = render 'award_emoji/awards_block', awardable: @merge_request, inline: true - - .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } - .merge-request-tabs-container - .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller - .fade-left= icon('angle-left') - .fade-right= icon('angle-right') - .nav-links.scrolling-tabs - %ul.merge-request-tabs - %li.notes-tab - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do - Discussion - %span.badge= @merge_request.related_notes.user.count - - if @merge_request.source_project - %li.commits-tab - = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do - Commits - %span.badge= @commits_count - - if @pipelines.any? - %li.pipelines-tab - = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do - Pipelines - %span.badge= @pipelines.size - %li.diffs-tab - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do - Changes - %span.badge= @merge_request.diff_size - #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true } - %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" } - %div - .line-resolve-all{ "v-show" => "discussionCount > 0", - ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } - %span.line-resolve-btn.is-disabled{ type: "button", - ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } - = render "shared/icons/icon_status_success.svg" - %span.line-resolve-text - {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved - = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request - = render "discussions/jump_to_next" - - .tab-content#diff-notes-app - #notes.notes.tab-pane.voting_notes - .row - %section.col-md-12 - .issuable-discussion - = render "projects/merge_requests/discussion" - - #commits.commits.tab-pane - -# This tab is always loaded via AJAX - #pipelines.pipelines.tab-pane - - if @pipelines.any? - = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) - #diffs.diffs.tab-pane - -# This tab is always loaded via AJAX - - .mr-loading-status - = spinner - -= render 'shared/issuable/sidebar', issuable: @merge_request -- if @merge_request.can_be_reverted?(current_user) - = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title -- if @merge_request.can_be_cherry_picked? - = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title - -:javascript - $(function () { - window.mergeRequest = new MergeRequest({ - action: "#{controller.action_name}" - }); - }); diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/branch_from.html.haml deleted file mode 100644 index 3837c4b388d..00000000000 --- a/app/views/projects/merge_requests/branch_from.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -- if @commit - = commit_to_html(@commit, @ref, @source_project) diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/branch_to.html.haml deleted file mode 100644 index d69b71790a0..00000000000 --- a/app/views/projects/merge_requests/branch_to.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -- if @commit - = commit_to_html(@commit, @ref, @target_project) diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 51d59280be8..f016b9c13b3 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -3,10 +3,10 @@ = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('merge_conflicts') = page_specific_javascript_tag('lib/ace.js') -= render "projects/merge_requests/show/mr_title" += render "projects/merge_requests/mr_title" .merge-request-details.issuable-details - = render "projects/merge_requests/show/mr_box" + = render "projects/merge_requests/mr_box" = render 'shared/issuable/sidebar', issuable: @merge_request diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml new file mode 100644 index 00000000000..f016b9c13b3 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/show.html.haml @@ -0,0 +1,38 @@ +- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('common_vue') + = page_specific_javascript_bundle_tag('merge_conflicts') + = page_specific_javascript_tag('lib/ace.js') += render "projects/merge_requests/mr_title" + +.merge-request-details.issuable-details + = render "projects/merge_requests/mr_box" + += render 'shared/issuable/sidebar', issuable: @merge_request + +#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json), + resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } } + .loading{ "v-if" => "isLoading" } + %i.fa.fa-spinner.fa-spin + + .nothing-here-block{ "v-if" => "hasError" } + {{conflictsData.errorMessage}} + + = render partial: "projects/merge_requests/conflicts/commit_stats" + + .files-wrapper{ "v-if" => "!isLoading && !hasError" } + .files + .diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" } + .js-file-title.file-title + %i.fa.fa-fw{ ":class" => "file.iconClass" } + %strong {{file.filePath}} + = render partial: 'projects/merge_requests/conflicts/file_actions' + .diff-content.diff-wrap-lines + .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" + .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + %parallel-conflict-lines{ ":file" => "file" } + %div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" } + = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" + + = render partial: "projects/merge_requests/conflicts/submit_form" diff --git a/app/views/projects/merge_requests/creations/_diffs.html.haml b/app/views/projects/merge_requests/creations/_diffs.html.haml new file mode 100644 index 00000000000..627fc4e9671 --- /dev/null +++ b/app/views/projects/merge_requests/creations/_diffs.html.haml @@ -0,0 +1 @@ += render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml new file mode 100644 index 00000000000..7cda326afef --- /dev/null +++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml @@ -0,0 +1,75 @@ +%h3.page-title + New Merge Request + += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: namespace_project_new_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| + .hide.alert.alert-danger.mr-compare-errors + .merge-request-branches.row + .col-md-6 + .panel.panel-default.panel-new-merge-request + .panel-heading + Source branch + .panel-body.clearfix + .merge-request-select.dropdown + = f.hidden_field :source_project_id + = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } + .dropdown-menu.dropdown-menu-selectable.dropdown-source-project + = dropdown_title("Select source project") + = dropdown_filter("Search projects") + = dropdown_content do + = render 'projects/merge_requests/dropdowns/project', + projects: [@merge_request.source_project], + selected: f.object.source_project_id + .merge-request-select.dropdown + = f.hidden_field :source_branch + = dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch git-revision-dropdown-toggle" } + .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch.git-revision-dropdown + = dropdown_title("Select source branch") + = dropdown_filter("Search branches") + = dropdown_content do + = render 'projects/merge_requests/dropdowns/branch', + branches: @merge_request.source_branches, + selected: f.object.source_branch + .panel-footer + .text-center= icon('spinner spin', class: 'js-source-loading') + %ul.list-unstyled.mr_source_commit + + .col-md-6 + .panel.panel-default.panel-new-merge-request + .panel-heading + Target branch + .panel-body.clearfix + - projects = target_projects(@project) + .merge-request-select.dropdown + = f.hidden_field :target_project_id + = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" } + .dropdown-menu.dropdown-menu-selectable.dropdown-target-project + = dropdown_title("Select target project") + = dropdown_filter("Search projects") + = dropdown_content do + = render 'projects/merge_requests/dropdowns/project', + projects: projects, + selected: f.object.target_project_id + .merge-request-select.dropdown + = f.hidden_field :target_branch + = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch git-revision-dropdown-toggle" } + .dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown.git-revision-dropdown + = dropdown_title("Select target branch") + = dropdown_filter("Search branches") + = dropdown_content do + = render 'projects/merge_requests/dropdowns/branch', + branches: @merge_request.target_branches, + selected: f.object.target_branch + .panel-footer + .text-center= icon('spinner spin', class: "js-target-loading") + %ul.list-unstyled.mr_target_commit + + - if @merge_request.errors.any? + = form_errors(@merge_request) + = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" + +:javascript + new Compare({ + targetProjectUrl: "#{namespace_project_new_merge_request_update_branches_path(@source_project.namespace, @source_project)}", + sourceBranchUrl: "#{namespace_project_new_merge_request_branch_from_path(@source_project.namespace, @source_project)}", + targetBranchUrl: "#{namespace_project_new_merge_request_branch_to_path(@source_project.namespace, @source_project)}" + }); diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml new file mode 100644 index 00000000000..c72dd1d8e29 --- /dev/null +++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml @@ -0,0 +1,57 @@ +%h3.page-title + New Merge Request +%p.slead + - source_title, target_title = format_mr_branch_names(@merge_request) + From + %strong.ref-name= source_title + %span into + %strong.ref-name= target_title + + %span.pull-right + = link_to 'Change branches', mr_change_branches_path(@merge_request) +%hr += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f| + = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits + = f.hidden_field :source_project_id + = f.hidden_field :source_branch + = f.hidden_field :target_project_id + = f.hidden_field :target_branch + +.mr-compare.merge-request + - if @commits.empty? + .commits-empty + %h4 + There are no commits yet. + = custom_icon ('illustration_no_commits') + - else + %ul.merge-request-tabs.nav-links.no-top.no-bottom + %li.commits-tab.active + = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do + Commits + %span.badge= @commits.size + - if @pipelines.any? + %li.builds-tab + = link_to url_for(params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do + Pipelines + %span.badge= @pipelines.size + %li.diffs-tab + = link_to url_for(params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do + Changes + %span.badge= @merge_request.diff_size + + .tab-content + #commits.commits.tab-pane.active + = render "projects/merge_requests/commits" + #diffs.diffs.tab-pane + -# This tab is always loaded via AJAX + - if @pipelines.any? + #pipelines.pipelines.tab-pane + = render 'projects/merge_requests/pipelines', endpoint: url_for(params.merge(action: 'pipelines', format: :json)), disable_initialization: true + + .mr-loading-status + = spinner + +:javascript + var merge_request = new MergeRequest({ + action: "#{j params[:tab].presence || 'new'}", + }); diff --git a/app/views/projects/merge_requests/creations/branch_from.html.haml b/app/views/projects/merge_requests/creations/branch_from.html.haml new file mode 100644 index 00000000000..3837c4b388d --- /dev/null +++ b/app/views/projects/merge_requests/creations/branch_from.html.haml @@ -0,0 +1,2 @@ +- if @commit + = commit_to_html(@commit, @ref, @source_project) diff --git a/app/views/projects/merge_requests/creations/branch_to.html.haml b/app/views/projects/merge_requests/creations/branch_to.html.haml new file mode 100644 index 00000000000..d69b71790a0 --- /dev/null +++ b/app/views/projects/merge_requests/creations/branch_to.html.haml @@ -0,0 +1,2 @@ +- if @commit + = commit_to_html(@commit, @ref, @target_project) diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml new file mode 100644 index 00000000000..2e798ce780a --- /dev/null +++ b/app/views/projects/merge_requests/creations/new.html.haml @@ -0,0 +1,6 @@ +- page_title "New Merge Request" + +- if @merge_request.can_be_created && !params[:change_branches] + = render 'new_submit' +- else + = render 'new_compare' diff --git a/app/views/projects/merge_requests/creations/update_branches.html.haml b/app/views/projects/merge_requests/creations/update_branches.html.haml new file mode 100644 index 00000000000..64482973a89 --- /dev/null +++ b/app/views/projects/merge_requests/creations/update_branches.html.haml @@ -0,0 +1,3 @@ += render 'projects/merge_requests/dropdowns/branch', +branches: @target_branches, +selected: nil diff --git a/app/views/projects/merge_requests/diffs.html.haml b/app/views/projects/merge_requests/diffs.html.haml deleted file mode 100644 index 2a5b8b1441e..00000000000 --- a/app/views/projects/merge_requests/diffs.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render "show" diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml new file mode 100644 index 00000000000..fb31e2fef00 --- /dev/null +++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml @@ -0,0 +1,5 @@ +- if @merge_request_diff.collected? || @merge_request_diff.overflow? + = render 'projects/merge_requests/diffs/versions' + = render "projects/diffs/diffs", diffs: @diffs, environment: @environment +- elsif @merge_request_diff.empty? + .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} diff --git a/app/views/projects/merge_requests/diffs/_versions.html.haml b/app/views/projects/merge_requests/diffs/_versions.html.haml new file mode 100644 index 00000000000..0999b95c9c9 --- /dev/null +++ b/app/views/projects/merge_requests/diffs/_versions.html.haml @@ -0,0 +1,97 @@ +- if @merge_request_diffs.size > 1 + .mr-version-controls + .mr-version-menus-container.content-block + Changes between + %span.dropdown.inline.mr-version-dropdown + %a.dropdown-toggle.btn.btn-default{ data: {toggle: :dropdown} } + %span + - if @merge_request_diff.latest? + latest version + - else + version #{version_index(@merge_request_diff)} + = icon('caret-down') + .dropdown-menu.dropdown-select.dropdown-menu-selectable + .dropdown-title + %span Version: + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } + = icon('times', class: 'dropdown-menu-close-icon') + .dropdown-content + %ul + - @merge_request_diffs.each do |merge_request_diff| + %li + = link_to merge_request_version_path(@project, @merge_request, merge_request_diff, @start_sha), class: ('is-active' if merge_request_diff == @merge_request_diff) do + %div + %strong + - if merge_request_diff.latest? + latest version + - else + version #{version_index(merge_request_diff)} + %div + %small.commit-sha= short_sha(merge_request_diff.head_commit_sha) + %div + %small + #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)}, + = time_ago_with_tooltip(merge_request_diff.created_at) + + - if @merge_request_diff.base_commit_sha + and + %span.dropdown.inline.mr-version-compare-dropdown + %a.btn.btn-default.dropdown-toggle{ data: {toggle: :dropdown} } + - if @start_version + version #{version_index(@start_version)} + - else + %span.ref-name= @merge_request.target_branch + = icon('caret-down') + .dropdown-menu.dropdown-select.dropdown-menu-selectable + .dropdown-title + %span Compared with: + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } + = icon('times', class: 'dropdown-menu-close-icon') + .dropdown-content + %ul + - @comparable_diffs.each do |merge_request_diff| + %li + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do + %div + %strong + - if merge_request_diff.latest? + latest version + - else + version #{version_index(merge_request_diff)} + %div + %small.commit-sha= short_sha(merge_request_diff.head_commit_sha) + %div + %small + = time_ago_with_tooltip(merge_request_diff.created_at) + %li + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_version) do + %div + %strong + %span.ref-name= @merge_request.target_branch + (base) + %div + %strong.commit-sha= short_sha(@merge_request_diff.base_commit_sha) + + - if different_base?(@start_version, @merge_request_diff) + .content-block + = icon('info-circle') + Selected versions have different base commits. + Changes will include + = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do + new commits + from + = succeed '.' do + %code= @merge_request.target_branch + + - if @start_version || !@merge_request_diff.latest? + .comments-disabled-notif.content-block + = icon('info-circle') + Not all comments are displayed because you're + - if @start_version + comparing two versions + - else + viewing an old version + of the diff. + + .pull-right + = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm' diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 6d75a9f34a3..86996e488a1 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -22,7 +22,7 @@ = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle" - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - if merge_project - = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do + = link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do New merge request = render 'shared/issuable/search_bar', type: :merge_requests @@ -33,4 +33,4 @@ .merge-requests-holder = render 'merge_requests' - else - = render 'shared/empty_states/merge_requests', button_path: new_namespace_project_merge_request_path(@project.namespace, @project) + = render 'shared/empty_states/merge_requests', button_path: namespace_project_new_merge_request_path(@project.namespace, @project) diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml index a00d3128ffe..6df19d6438b 100644 --- a/app/views/projects/merge_requests/invalid.html.haml +++ b/app/views/projects/merge_requests/invalid.html.haml @@ -1,8 +1,8 @@ - page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" .merge-request - = render "projects/merge_requests/show/mr_title" - = render "projects/merge_requests/show/mr_box" + = render "projects/merge_requests/mr_title" + = render "projects/merge_requests/mr_box" .alert.alert-danger %p diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml deleted file mode 100644 index 2e798ce780a..00000000000 --- a/app/views/projects/merge_requests/new.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -- page_title "New Merge Request" - -- if @merge_request.can_be_created && !params[:change_branches] - = render 'new_submit' -- else - = render 'new_compare' diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 2a5b8b1441e..dbbf1bde088 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -1 +1,97 @@ -= render "show" +- @content_class = "limit-container-width" unless fluid_layout +- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" +- page_description @merge_request.description +- page_card_attributes @merge_request.card_attributes +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('common_vue') + = page_specific_javascript_bundle_tag('diff_notes') + +.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) } + = render "projects/merge_requests/mr_title" + + .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } } + = render "projects/merge_requests/mr_box" + + - if @merge_request.source_branch_exists? + = render "projects/merge_requests/how_to_merge" + + :javascript + window.gl.mrWidgetData = #{serialize_issuable(@merge_request)} + + #js-vue-mr-widget.mr-widget + + - content_for :page_specific_javascripts do + = webpack_bundle_tag 'common_vue' + = webpack_bundle_tag 'vue_merge_request_widget' + + .content-block.content-block-small.emoji-list-container + = render 'award_emoji/awards_block', awardable: @merge_request, inline: true + + .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } + .merge-request-tabs-container + .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller + .fade-left= icon('angle-left') + .fade-right= icon('angle-right') + .nav-links.scrolling-tabs + %ul.merge-request-tabs + %li.notes-tab + = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do + Discussion + %span.badge= @merge_request.related_notes.user.count + - if @merge_request.source_project + %li.commits-tab + = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do + Commits + %span.badge= @commits_count + - if @pipelines.any? + %li.pipelines-tab + = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do + Pipelines + %span.badge= @pipelines.size + %li.diffs-tab + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do + Changes + %span.badge= @merge_request.diff_size + #resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true } + %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" } + %div + .line-resolve-all{ "v-show" => "discussionCount > 0", + ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } + %span.line-resolve-btn.is-disabled{ type: "button", + ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } + = render "shared/icons/icon_status_success.svg" + %span.line-resolve-text + {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved + = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request + = render "discussions/jump_to_next" + + .tab-content#diff-notes-app + #notes.notes.tab-pane.voting_notes + .row + %section.col-md-12 + .issuable-discussion + = render "projects/merge_requests/discussion" + + #commits.commits.tab-pane + -# This tab is always loaded via AJAX + #pipelines.pipelines.tab-pane + - if @pipelines.any? + = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + #diffs.diffs.tab-pane + -# This tab is always loaded via AJAX + + .mr-loading-status + = spinner + += render 'shared/issuable/sidebar', issuable: @merge_request +- if @merge_request.can_be_reverted?(current_user) + = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title +- if @merge_request.can_be_cherry_picked? + = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title + +:javascript + $(function () { + window.mergeRequest = new MergeRequest({ + action: "#{j params[:tab].presence || 'show'}", + }); + }); diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml deleted file mode 100644 index 11793919ff7..00000000000 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -- if @commits.empty? - .commits-empty - %h4 - There are no commits yet. - = custom_icon ('illustration_no_commits') -- else - %ol#commits-list.list-unstyled - = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml deleted file mode 100644 index 7f0913ea516..00000000000 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -- if @merge_request_diff.collected? || @merge_request_diff.overflow? - = render 'projects/merge_requests/show/versions' - = render "projects/diffs/diffs", diffs: @diffs, environment: @environment -- elsif @merge_request_diff.empty? - .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml deleted file mode 100644 index 766cb272bec..00000000000 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ /dev/null @@ -1,63 +0,0 @@ -#modal_merge_info.modal - .modal-dialog - .modal-content - .modal-header - %a.close{ href: "#", "data-dismiss" => "modal" } × - %h3 Check out, review, and merge locally - .modal-body - %p - %strong Step 1. - Fetch and check out the branch for this merge request - = clipboard_button(target: "pre#merge-info-1", title: "Copy commands to clipboard") - %pre.dark#merge-info-1 - - if @merge_request.for_fork? - :preserve - git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch} - git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD - - else - :preserve - git fetch origin - git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch} - %p - %strong Step 2. - Review the changes locally - - %p - %strong Step 3. - Merge the branch and fix any conflicts that come up - = clipboard_button(target: "pre#merge-info-3", title: "Copy commands to clipboard") - %pre.dark#merge-info-3 - - if @merge_request.for_fork? - :preserve - git checkout #{h @merge_request.target_branch} - git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} - - else - :preserve - git checkout #{h @merge_request.target_branch} - git merge --no-ff #{h @merge_request.source_branch} - %p - %strong Step 4. - Push the result of the merge to GitLab - = clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard") - %pre.dark#merge-info-4 - :preserve - git push origin #{h @merge_request.target_branch} - - unless @merge_request.can_be_merged_by?(current_user) - %p - Note that pushing to GitLab requires write access to this repository. - %p - %strong Tip: - = succeed '.' do - You can also checkout merge requests locally by - = link_to 'following these guidelines', help_page_path('user/project/merge_requests/index.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer' - -:javascript - $(function(){ - var modal = $('#modal_merge_info').modal({modal: true, show:false}); - $('.how_to_merge_link').bind("click", function(){ - modal.show(); - }); - $('.modal-header .close').bind("click", function(){ - modal.hide(); - }) - }) diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml deleted file mode 100644 index 8a390cf8700..00000000000 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -.detail-page-description.content-block - %h2.title - = markdown_field(@merge_request, :title) - - %div - - if @merge_request.description.present? - .description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' } - .wiki - = markdown_field(@merge_request, :description) - %textarea.hidden.js-task-list-field - = @merge_request.description - - = edited_time_ago_with_tooltip(@merge_request, placement: 'bottom') diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml deleted file mode 100644 index d9428b8562e..00000000000 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ /dev/null @@ -1,35 +0,0 @@ -- if @merge_request.closed_without_fork? - .alert.alert-danger - %p The source project of this merge request has been removed. - -.clearfix.detail-page-header - .issuable-header - .issuable-status-box.status-box{ class: status_box_class(@merge_request) } - = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg") - %span.hidden-xs - = @merge_request.state_human_name - - %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } - = icon('angle-double-left') - - .issuable-meta - = issuable_meta(@merge_request, @project, "Merge request") - - - if can?(current_user, :update_merge_request, @merge_request) - .issuable-actions - .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - Options - = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right.hidden-lg - %ul - %li{ class: merge_request_button_visibility(@merge_request, true) } - = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' - %li{ class: merge_request_button_visibility(@merge_request, false) } - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' - %li - = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit' - = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request' - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request' - = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do - Edit diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml deleted file mode 100644 index 2f1dbe87619..00000000000 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json) -- disable_initialization = local_assigns.fetch(:disable_initialization, false) - -= render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml deleted file mode 100644 index 0999b95c9c9..00000000000 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ /dev/null @@ -1,97 +0,0 @@ -- if @merge_request_diffs.size > 1 - .mr-version-controls - .mr-version-menus-container.content-block - Changes between - %span.dropdown.inline.mr-version-dropdown - %a.dropdown-toggle.btn.btn-default{ data: {toggle: :dropdown} } - %span - - if @merge_request_diff.latest? - latest version - - else - version #{version_index(@merge_request_diff)} - = icon('caret-down') - .dropdown-menu.dropdown-select.dropdown-menu-selectable - .dropdown-title - %span Version: - %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } - = icon('times', class: 'dropdown-menu-close-icon') - .dropdown-content - %ul - - @merge_request_diffs.each do |merge_request_diff| - %li - = link_to merge_request_version_path(@project, @merge_request, merge_request_diff, @start_sha), class: ('is-active' if merge_request_diff == @merge_request_diff) do - %div - %strong - - if merge_request_diff.latest? - latest version - - else - version #{version_index(merge_request_diff)} - %div - %small.commit-sha= short_sha(merge_request_diff.head_commit_sha) - %div - %small - #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)}, - = time_ago_with_tooltip(merge_request_diff.created_at) - - - if @merge_request_diff.base_commit_sha - and - %span.dropdown.inline.mr-version-compare-dropdown - %a.btn.btn-default.dropdown-toggle{ data: {toggle: :dropdown} } - - if @start_version - version #{version_index(@start_version)} - - else - %span.ref-name= @merge_request.target_branch - = icon('caret-down') - .dropdown-menu.dropdown-select.dropdown-menu-selectable - .dropdown-title - %span Compared with: - %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } - = icon('times', class: 'dropdown-menu-close-icon') - .dropdown-content - %ul - - @comparable_diffs.each do |merge_request_diff| - %li - = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do - %div - %strong - - if merge_request_diff.latest? - latest version - - else - version #{version_index(merge_request_diff)} - %div - %small.commit-sha= short_sha(merge_request_diff.head_commit_sha) - %div - %small - = time_ago_with_tooltip(merge_request_diff.created_at) - %li - = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_version) do - %div - %strong - %span.ref-name= @merge_request.target_branch - (base) - %div - %strong.commit-sha= short_sha(@merge_request_diff.base_commit_sha) - - - if different_base?(@start_version, @merge_request_diff) - .content-block - = icon('info-circle') - Selected versions have different base commits. - Changes will include - = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do - new commits - from - = succeed '.' do - %code= @merge_request.target_branch - - - if @start_version || !@merge_request_diff.latest? - .comments-disabled-notif.content-block - = icon('info-circle') - Not all comments are displayed because you're - - if @start_version - comparing two versions - - else - viewing an old version - of the diff. - - .pull-right - = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm' diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml deleted file mode 100644 index 64482973a89..00000000000 --- a/app/views/projects/merge_requests/update_branches.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -= render 'projects/merge_requests/dropdowns/branch', -branches: @target_branches, -selected: nil diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb index d760f5b140f..92e622285de 100644 --- a/app/workers/expire_pipeline_cache_worker.rb +++ b/app/workers/expire_pipeline_cache_worker.rb @@ -46,7 +46,7 @@ class ExpirePipelineCacheWorker end def new_merge_request_pipelines_path(project) - Gitlab::Routing.url_helpers.new_namespace_project_merge_request_path( + Gitlab::Routing.url_helpers.namespace_project_new_merge_request_path( project.namespace, project, format: :json) diff --git a/config/routes/project.rb b/config/routes/project.rb index 19e18c733b1..0d0a8dff25e 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -87,13 +87,8 @@ constraints(ProjectUrlConstrainer.new) do resources :forks, only: [:index, :new, :create] resource :import, only: [:new, :create, :show] - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do + resources :merge_requests, concerns: :awardable, except: [:new, :create], constraints: { id: /\d+/ } do member do - get :commits - get :diffs - get :conflicts - get :conflict_for_path - get :pipelines get :commit_change_content post :merge post :cancel_merge_when_pipeline_succeeds @@ -101,18 +96,32 @@ constraints(ProjectUrlConstrainer.new) do get :ci_environments_status post :toggle_subscription post :remove_wip - get :diff_for_path - post :resolve_conflicts post :assign_related_issues + + scope constraints: { format: nil }, action: :show do + get :commits, defaults: { tab: 'commits' } + get :pipelines, defaults: { tab: 'pipelines' } + get :diffs, defaults: { tab: 'diffs' } + end + + scope constraints: { format: 'json' }, as: :json do + get :commits + get :pipelines + get :diffs, to: 'merge_requests/diffs#show' + end + + get :diff_for_path, controller: 'merge_requests/diffs' + + scope controller: 'merge_requests/conflicts' do + get :conflicts, action: :show + get :conflict_for_path + post :resolve_conflicts + end end collection do - get :branch_from - get :branch_to - get :update_branches get :diff_for_path post :bulk_update - get :new_diffs, path: 'new/diffs' end resources :discussions, only: [], constraints: { id: /\h{40}/ } do @@ -123,6 +132,29 @@ constraints(ProjectUrlConstrainer.new) do end end + controller 'merge_requests/creations', path: 'merge_requests' do + post '', action: :create, as: nil + + scope path: 'new', as: :new_merge_request do + get '', action: :new + + scope constraints: { format: nil }, action: :new do + get :diffs, defaults: { tab: 'diffs' } + get :pipelines, defaults: { tab: 'pipelines' } + end + + scope constraints: { format: 'json' }, as: :json do + get :diffs + get :pipelines + end + + get :diff_for_path + get :update_branches + get :branch_from + get :branch_to + end + end + resources :variables, only: [:index, :show, :update, :create, :destroy] resources :triggers, only: [:index, :create, :edit, :update, :destroy] do member do diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index 71c69a4fdea..3c06b188f3e 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -27,7 +27,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps step 'I see prefilled new Merge Request page' do expect(page).to have_selector('.merge-request-form') - expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project) + expect(current_path).to eq namespace_project_new_merge_request_path(@project.namespace, @project) expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s expect(find("input#merge_request_source_branch").value).to eq "fix" expect(find("input#merge_request_target_branch").value).to eq "master" diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 9ed4f8ea1f9..02434319a08 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -266,12 +266,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I am redirected to the new merge request page' do - expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project)) + expect(current_path).to eq(namespace_project_new_merge_request_path(@project.namespace, @project)) end step "I am redirected to the fork's new merge request page" do fork = @user.fork_of(@project) - expect(current_path).to eq(new_namespace_project_merge_request_path(fork.namespace, fork)) + expect(current_path).to eq(namespace_project_new_merge_request_path(fork.namespace, fork)) end step 'I am redirected to the root directory' do diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index c20cf6a4291..561bc219bb4 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -235,7 +235,7 @@ describe Projects::BlobController do put :update, default_params expect(response).to redirect_to( - new_namespace_project_merge_request_path( + namespace_project_new_merge_request_path( forked_project.namespace, forked_project, merge_request: { diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb new file mode 100644 index 00000000000..9278ac8edd8 --- /dev/null +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -0,0 +1,307 @@ +require 'spec_helper' + +describe Projects::MergeRequests::ConflictsController do + let(:project) { create(:project) } + let(:user) { project.owner } + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } + let(:merge_request_with_conflicts) do + create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| + mr.mark_as_unmergeable + end + end + + before do + sign_in(user) + end + + describe 'GET show' do + context 'when the conflicts cannot be resolved in the UI' do + before do + allow_any_instance_of(Gitlab::Conflict::Parser) + .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) + + get :show, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + format: 'json' + end + + it 'returns a 200 status code' do + expect(response).to have_http_status(:ok) + end + + it 'returns JSON with a message' do + expect(json_response.keys).to contain_exactly('message', 'type') + end + end + + context 'with valid conflicts' do + before do + get :show, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + format: 'json' + end + + it 'matches the schema' do + expect(response).to match_response_schema('conflicts') + end + + it 'includes meta info about the MR' do + expect(json_response['commit_message']).to include('Merge branch') + expect(json_response['commit_sha']).to match(/\h{40}/) + expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch) + expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch) + end + + it 'includes each file that has conflicts' do + filenames = json_response['files'].map { |file| file['new_path'] } + + expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb') + end + + it 'splits files into sections with lines' do + json_response['files'].each do |file| + file['sections'].each do |section| + expect(section).to include('conflict', 'lines') + + section['lines'].each do |line| + if section['conflict'] + expect(line['type']).to be_in(%w(old new)) + expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer)) + else + if line['type'].nil? + expect(line['old_line']).not_to eq(nil) + expect(line['new_line']).not_to eq(nil) + else + expect(line['type']).to eq('match') + expect(line['old_line']).to eq(nil) + expect(line['new_line']).to eq(nil) + end + end + end + end + end + end + + it 'has unique section IDs across files' do + section_ids = json_response['files'].flat_map do |file| + file['sections'].map { |section| section['id'] }.compact + end + + expect(section_ids.uniq).to eq(section_ids) + end + end + end + + describe 'GET conflict_for_path' do + def conflict_for_path(path) + get :conflict_for_path, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + old_path: path, + new_path: path, + format: 'json' + end + + context 'when the conflicts cannot be resolved in the UI' do + before do + allow_any_instance_of(Gitlab::Conflict::Parser) + .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) + + conflict_for_path('files/ruby/regex.rb') + end + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'when the file does not exist cannot be resolved in the UI' do + before do + conflict_for_path('files/ruby/regexp.rb') + end + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'with an existing file' do + let(:path) { 'files/ruby/regex.rb' } + + before do + conflict_for_path(path) + end + + it 'returns a 200 status code' do + expect(response).to have_http_status(:ok) + end + + it 'returns the file in JSON format' do + content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) + .file_for_path(path, path) + .content + + expect(json_response).to include('old_path' => path, + 'new_path' => path, + 'blob_icon' => 'file-text-o', + 'blob_path' => a_string_ending_with(path), + 'blob_ace_mode' => 'ruby', + 'content' => content) + end + end + end + + context 'POST resolve_conflicts' do + let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } + + def resolve_conflicts(files) + post :resolve_conflicts, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid, + format: 'json', + files: files, + commit_message: 'Commit message' + end + + context 'with valid params' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'creates a new commit on the branch' do + expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha) + expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message') + end + + it 'returns an OK response' do + expect(response).to have_http_status(:ok) + end + end + + context 'when sections are missing' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the name of the first missing section' do + expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when files are missing' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the name of the missing file' do + expect(json_response['message']).to include('files/ruby/popen.rb') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when a file has identical content to the conflict' do + before do + content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) + .file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb') + .content + + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'content' => content + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the path of the problem file' do + expect(json_response['message']).to include('files/ruby/popen.rb') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + end +end diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb new file mode 100644 index 00000000000..f9d8f0f5fcf --- /dev/null +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +describe Projects::MergeRequests::CreationsController do + let(:project) { create(:project) } + let(:user) { project.owner } + let(:fork_project) { create(:forked_project_with_submodules) } + + before do + fork_project.team << [user, :master] + + sign_in(user) + end + + describe 'GET new' do + context 'merge request that removes a submodule' do + render_views + + it 'renders new merge request widget template' do + get :new, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' + } + + expect(response).to be_success + end + end + end + + describe 'GET pipelines' do + before do + create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id, + ref: 'remove-submodule', + project: fork_project) + end + + it 'renders JSON including serialized pipelines' do + get :pipelines, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' + }, + format: :json + + expect(response).to be_ok + expect(json_response).to have_key 'pipelines' + expect(json_response['pipelines']).not_to be_empty + end + end + + describe 'GET diff_for_path' do + def diff_for_path(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project, + format: 'json' + } + + get :diff_for_path, params.merge(extra_params) + end + + let(:existing_path) { 'files/ruby/feature.rb' } + + context 'when both branches are in the same project' do + it 'disables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) + + expect(assigns(:diff_notes_disabled)).to be_truthy + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) + end + + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) + end + end + + context 'when the source branch is in a different project to the target' do + let(:other_project) { create(:project) } + + before do + other_project.team << [user, :master] + end + + context 'when the path exists in the diff' do + it 'disables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + + expect(assigns(:diff_notes_disabled)).to be_truthy + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) + end + + diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + end + end + + context 'when the path does not exist in the diff' do + before do + diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + end +end diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb new file mode 100644 index 00000000000..53fe2bdb189 --- /dev/null +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -0,0 +1,160 @@ +require 'spec_helper' + +describe Projects::MergeRequests::DiffsController do + let(:project) { create(:project) } + let(:user) { project.owner } + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } + + before do + sign_in(user) + end + + describe 'GET show' do + def go(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: 'json' + } + + get :show, params.merge(extra_params) + end + + context 'with default params' do + context 'for the same project' do + before do + go + end + + it 'renders the diffs template to a string' do + expect(response).to render_template('projects/merge_requests/diffs/_diffs') + expect(json_response).to have_key('html') + end + end + + context 'with forked projects with submodules' do + render_views + + let(:project) { create(:project) } + let(:fork_project) { create(:forked_project_with_submodules) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } + + before do + fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) + fork_project.save + merge_request.reload + go + end + + it 'renders' do + expect(response).to be_success + expect(response.body).to have_content('Subproject commit') + end + end + end + + context 'with ignore_whitespace_change' do + before do + go(w: 1) + end + + it 'renders the diffs template to a string' do + expect(response).to render_template('projects/merge_requests/diffs/_diffs') + expect(json_response).to have_key('html') + end + end + + context 'with view' do + before do + go(view: 'parallel') + end + + it 'saves the preferred diff view in a cookie' do + expect(response.cookies['diff_view']).to eq('parallel') + end + end + end + + describe 'GET diff_for_path' do + def diff_for_path(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: 'json' + } + + get :diff_for_path, params.merge(extra_params) + end + + let(:existing_path) { 'files/ruby/popen.rb' } + + context 'when the merge request exists' do + context 'when the user can view the merge request' do + context 'when the path exists in the diff' do + it 'enables diff notes' do + diff_for_path(old_path: existing_path, new_path: existing_path) + + expect(assigns(:diff_notes_disabled)).to be_falsey + expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest', + noteable_id: merge_request.id) + end + + it 'only renders the diffs for the path given' do + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) + end + + diff_for_path(old_path: existing_path, new_path: existing_path) + end + end + + context 'when the path does not exist in the diff' do + before do + diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the user cannot view the merge request' do + before do + project.team.truncate + diff_for_path(old_path: existing_path, new_path: existing_path) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'when the merge request does not exist' do + before do + diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + + context 'when the merge request belongs to a different project' do + let(:other_project) { create(:empty_project) } + + before do + other_project.team << [user, :master] + diff_for_path(old_path: existing_path, new_path: existing_path, project_id: other_project) + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6817c2652fd..6f9ce60cf75 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -14,53 +14,6 @@ describe Projects::MergeRequestsController do sign_in(user) end - describe 'GET new' do - context 'merge request that removes a submodule' do - render_views - - let(:fork_project) { create(:forked_project_with_submodules) } - - before do - fork_project.team << [user, :master] - end - - context 'when rendering HTML response' do - it 'renders new merge request widget template' do - submit_new_merge_request - - expect(response).to be_success - end - end - - context 'when rendering JSON response' do - before do - create(:ci_pipeline, sha: fork_project.commit('remove-submodule').id, - ref: 'remove-submodule', - project: fork_project) - end - - it 'renders JSON including serialized pipelines' do - submit_new_merge_request(format: :json) - - expect(response).to be_ok - expect(json_response).to have_key 'pipelines' - expect(json_response['pipelines']).not_to be_empty - end - end - end - - def submit_new_merge_request(format: :html) - get :new, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project, - merge_request: { - source_branch: 'remove-submodule', - target_branch: 'master' - }, - format: format - end - end - describe 'GET commit_change_content' do it 'renders commit_change_content template' do get :commit_change_content, @@ -497,234 +450,6 @@ describe Projects::MergeRequestsController do end end - describe 'GET diffs' do - def go(extra_params = {}) - params = { - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid - } - - get :diffs, params.merge(extra_params) - end - - it_behaves_like "loads labels", :diffs - - context 'with default params' do - context 'as html' do - before do - go(format: 'html') - end - - it 'renders the diff template' do - expect(response).to render_template('diffs') - end - end - - context 'as json' do - before do - go(format: 'json') - end - - it 'renders the diffs template to a string' do - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(json_response).to have_key('html') - end - end - - context 'with forked projects with submodules' do - render_views - - let(:project) { create(:project) } - let(:fork_project) { create(:forked_project_with_submodules) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } - - before do - fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) - fork_project.save - merge_request.reload - go(format: 'json') - end - - it 'renders' do - expect(response).to be_success - expect(response.body).to have_content('Subproject commit') - end - end - end - - context 'with ignore_whitespace_change' do - context 'as html' do - before do - go(format: 'html', w: 1) - end - - it 'renders the diff template' do - expect(response).to render_template('diffs') - end - end - - context 'as json' do - before do - go(format: 'json', w: 1) - end - - it 'renders the diffs template to a string' do - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(json_response).to have_key('html') - end - end - end - - context 'with view' do - before do - go(view: 'parallel') - end - - it 'saves the preferred diff view in a cookie' do - expect(response.cookies['diff_view']).to eq('parallel') - end - end - end - - describe 'GET diff_for_path' do - def diff_for_path(extra_params = {}) - params = { - namespace_id: project.namespace.to_param, - project_id: project - } - - get :diff_for_path, params.merge(extra_params) - end - - context 'when an ID param is passed' do - let(:existing_path) { 'files/ruby/popen.rb' } - - context 'when the merge request exists' do - context 'when the user can view the merge request' do - context 'when the path exists in the diff' do - it 'enables diff notes' do - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) - - expect(assigns(:diff_notes_disabled)).to be_falsey - expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest', - noteable_id: merge_request.id) - end - - it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| - expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs) - end - - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) - end - end - - context 'when the path does not exist in the diff' do - before do - diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'when the user cannot view the merge request' do - before do - project.team.truncate - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'when the merge request does not exist' do - before do - diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - - context 'when the merge request belongs to a different project' do - let(:other_project) { create(:empty_project) } - - before do - other_project.team << [user, :master] - diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'when source and target params are passed' do - let(:existing_path) { 'files/ruby/feature.rb' } - - context 'when both branches are in the same project' do - it 'disables diff notes' do - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) - - expect(assigns(:diff_notes_disabled)).to be_truthy - end - - it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| - expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs) - end - - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) - end - end - - context 'when the source branch is in a different project to the target' do - let(:other_project) { create(:project) } - - before do - other_project.team << [user, :master] - end - - context 'when the path exists in the diff' do - it 'disables diff notes' do - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) - - expect(assigns(:diff_notes_disabled)).to be_truthy - end - - it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| - expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs) - end - - diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) - end - end - - context 'when the path does not exist in the diff' do - before do - diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) - end - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - end - end - describe 'GET commits' do def go(format: 'html') get :commits, @@ -734,23 +459,11 @@ describe Projects::MergeRequestsController do format: format end - it_behaves_like "loads labels", :commits + it 'renders the commits template to a string' do + go format: 'json' - context 'as html' do - it 'renders the show template' do - go - - expect(response).to render_template('show') - end - end - - context 'as json' do - it 'renders the commits template to a string' do - go format: 'json' - - expect(response).to render_template('projects/merge_requests/show/_commits') - expect(json_response).to have_key('html') - end + expect(response).to render_template('projects/merge_requests/_commits') + expect(json_response).to have_key('html') end end @@ -759,106 +472,16 @@ describe Projects::MergeRequestsController do create(:ci_pipeline, project: merge_request.source_project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) - end - - context 'when using HTML format' do - it_behaves_like "loads labels", :pipelines - end - context 'when using JSON format' do - before do - get :pipelines, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, - format: :json - end - - it 'responds with serialized pipelines' do - expect(json_response).not_to be_empty - end - end - end - - describe 'GET conflicts' do - context 'when the conflicts cannot be resolved in the UI' do - before do - allow_any_instance_of(Gitlab::Conflict::Parser) - .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) - - get :conflicts, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - format: 'json' - end - - it 'returns a 200 status code' do - expect(response).to have_http_status(:ok) - end - - it 'returns JSON with a message' do - expect(json_response.keys).to contain_exactly('message', 'type') - end + get :pipelines, + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: :json end - context 'with valid conflicts' do - before do - get :conflicts, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - format: 'json' - end - - it 'matches the schema' do - expect(response).to match_response_schema('conflicts') - end - - it 'includes meta info about the MR' do - expect(json_response['commit_message']).to include('Merge branch') - expect(json_response['commit_sha']).to match(/\h{40}/) - expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch) - expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch) - end - - it 'includes each file that has conflicts' do - filenames = json_response['files'].map { |file| file['new_path'] } - - expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb') - end - - it 'splits files into sections with lines' do - json_response['files'].each do |file| - file['sections'].each do |section| - expect(section).to include('conflict', 'lines') - - section['lines'].each do |line| - if section['conflict'] - expect(line['type']).to be_in(%w(old new)) - expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer)) - else - if line['type'].nil? - expect(line['old_line']).not_to eq(nil) - expect(line['new_line']).not_to eq(nil) - else - expect(line['type']).to eq('match') - expect(line['old_line']).to eq(nil) - expect(line['new_line']).to eq(nil) - end - end - end - end - end - end - - it 'has unique section IDs across files' do - section_ids = json_response['files'].flat_map do |file| - file['sections'].map { |section| section['id'] }.compact - end - - expect(section_ids.uniq).to eq(section_ids) - end + it 'responds with serialized pipelines' do + expect(json_response).not_to be_empty end end @@ -913,215 +536,6 @@ describe Projects::MergeRequestsController do end end - describe 'GET conflict_for_path' do - def conflict_for_path(path) - get :conflict_for_path, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - old_path: path, - new_path: path, - format: 'json' - end - - context 'when the conflicts cannot be resolved in the UI' do - before do - allow_any_instance_of(Gitlab::Conflict::Parser) - .to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) - - conflict_for_path('files/ruby/regex.rb') - end - - it 'returns a 404 status code' do - expect(response).to have_http_status(:not_found) - end - end - - context 'when the file does not exist cannot be resolved in the UI' do - before do - conflict_for_path('files/ruby/regexp.rb') - end - - it 'returns a 404 status code' do - expect(response).to have_http_status(:not_found) - end - end - - context 'with an existing file' do - let(:path) { 'files/ruby/regex.rb' } - - before do - conflict_for_path(path) - end - - it 'returns a 200 status code' do - expect(response).to have_http_status(:ok) - end - - it 'returns the file in JSON format' do - content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) - .file_for_path(path, path) - .content - - expect(json_response).to include('old_path' => path, - 'new_path' => path, - 'blob_icon' => 'file-text-o', - 'blob_path' => a_string_ending_with(path), - 'blob_ace_mode' => 'ruby', - 'content' => content) - end - end - end - - context 'POST resolve_conflicts' do - let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } - - def resolve_conflicts(files) - post :resolve_conflicts, - namespace_id: merge_request_with_conflicts.project.namespace.to_param, - project_id: merge_request_with_conflicts.project, - id: merge_request_with_conflicts.iid, - format: 'json', - files: files, - commit_message: 'Commit message' - end - - context 'with valid params' do - before do - resolved_files = [ - { - 'new_path' => 'files/ruby/popen.rb', - 'old_path' => 'files/ruby/popen.rb', - 'sections' => { - '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' - } - }, { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'creates a new commit on the branch' do - expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha) - expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message') - end - - it 'returns an OK response' do - expect(response).to have_http_status(:ok) - end - end - - context 'when sections are missing' do - before do - resolved_files = [ - { - 'new_path' => 'files/ruby/popen.rb', - 'old_path' => 'files/ruby/popen.rb', - 'sections' => { - '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' - } - }, { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) - end - - it 'has a message with the name of the first missing section' do - expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - end - end - - context 'when files are missing' do - before do - resolved_files = [ - { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) - end - - it 'has a message with the name of the missing file' do - expect(json_response['message']).to include('files/ruby/popen.rb') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - end - end - - context 'when a file has identical content to the conflict' do - before do - content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) - .file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb') - .content - - resolved_files = [ - { - 'new_path' => 'files/ruby/popen.rb', - 'old_path' => 'files/ruby/popen.rb', - 'content' => content - }, { - 'new_path' => 'files/ruby/regex.rb', - 'old_path' => 'files/ruby/regex.rb', - 'sections' => { - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - } - } - ] - - resolve_conflicts(resolved_files) - end - - it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) - end - - it 'has a message with the path of the problem file' do - expect(json_response['message']).to include('files/ruby/popen.rb') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - end - end - end - describe 'POST assign_related_issues' do let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 8f7adbccaaa..6a08e50bf5e 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -65,7 +65,7 @@ feature 'Create New Merge Request', feature: true, js: true do it 'does not leak the private project name & namespace' do private_project = create(:project, :private) - visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) + visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) expect(page).not_to have_content private_project.path_with_namespace expect(page).to have_content project.path_with_namespace @@ -76,7 +76,7 @@ feature 'Create New Merge Request', feature: true, js: true do it 'does not leak the private project name & namespace' do private_project = create(:project, :private) - visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { source_project_id: private_project.id }) + visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { source_project_id: private_project.id }) expect(page).not_to have_content private_project.path_with_namespace expect(page).to have_content project.path_with_namespace @@ -84,13 +84,13 @@ feature 'Create New Merge Request', feature: true, js: true do end it 'populates source branch button' do - visit new_namespace_project_merge_request_path(project.namespace, project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' }) + visit namespace_project_new_merge_request_path(project.namespace, project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' }) expect(find('.js-source-branch')).to have_content('fix') end it 'allows to change the diff view' do - visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' }) + visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' }) click_link 'Changes' @@ -106,7 +106,7 @@ feature 'Create New Merge Request', feature: true, js: true do end it 'does not allow non-existing branches' do - visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' }) + visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' }) expect(page).to have_content('The form contains the following errors') expect(page).to have_content('Source branch "non-exist-source" does not exist') @@ -115,7 +115,7 @@ feature 'Create New Merge Request', feature: true, js: true do context 'when a branch contains commits that both delete and add the same image' do it 'renders the diff successfully' do - visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' }) + visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' }) click_link "Changes" @@ -125,7 +125,7 @@ feature 'Create New Merge Request', feature: true, js: true do # Isolates a regression (see #24627) it 'does not show error messages on initial form' do - visit new_namespace_project_merge_request_path(project.namespace, project) + visit namespace_project_new_merge_request_path(project.namespace, project) expect(page).not_to have_selector('#error_explanation') expect(page).not_to have_content('The form contains the following error') end @@ -138,7 +138,7 @@ feature 'Create New Merge Request', feature: true, js: true do end it 'shows pipelines for a new merge request' do - visit new_namespace_project_merge_request_path( + visit namespace_project_new_merge_request_path( project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' }) diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 1996c2fa09a..d03d498ce21 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -23,7 +23,7 @@ describe 'New/edit merge request', feature: true, js: true do context 'new merge request' do before do - visit new_namespace_project_merge_request_path( + visit namespace_project_new_merge_request_path( project.namespace, project, merge_request: { @@ -182,7 +182,7 @@ describe 'New/edit merge request', feature: true, js: true do context 'new merge request' do before do - visit new_namespace_project_merge_request_path( + visit namespace_project_new_merge_request_path( fork_project.namespace, fork_project, merge_request: { diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 71aa71e380e..a1f123f15ec 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -131,7 +131,7 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do end it 'changes target_branch in new merge_request' do - visit new_namespace_project_merge_request_path(another_project.namespace, another_project, new_url_opts) + visit namespace_project_new_merge_request_path(another_project.namespace, another_project, new_url_opts) fill_in "merge_request_title", with: 'My brand new feature' fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:" diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 3ac1f603de6..d8e9b949204 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -12,7 +12,7 @@ describe 'Merge request', :feature, :js do context 'new merge request' do before do - visit new_namespace_project_merge_request_path( + visit namespace_project_new_merge_request_path( project.namespace, project, merge_request: { diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb index 72d001bf408..0e304ba50af 100644 --- a/spec/features/merge_requests/wip_message_spec.rb +++ b/spec/features/merge_requests/wip_message_spec.rb @@ -11,7 +11,7 @@ feature 'Work In Progress help message', feature: true do context 'with WIP commits' do it 'shows a specific WIP hint' do - visit new_namespace_project_merge_request_path( + visit namespace_project_new_merge_request_path( project.namespace, project, merge_request: { @@ -32,7 +32,7 @@ feature 'Work In Progress help message', feature: true do context 'without WIP commits' do it 'shows the regular WIP message' do - visit new_namespace_project_merge_request_path( + visit namespace_project_new_merge_request_path( project.namespace, project, merge_request: { diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 6de8855016d..58054bbbbed 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -23,7 +23,7 @@ feature 'Merge Request button', feature: true do end it 'shows Create merge request button' do - href = new_namespace_project_merge_request_path(project.namespace, + href = namespace_project_new_merge_request_path(project.namespace, project, merge_request: { source_branch: 'feature', target_branch: 'master' }) @@ -67,7 +67,7 @@ feature 'Merge Request button', feature: true do let(:user) { forked_project.owner } it 'shows Create merge request button' do - href = new_namespace_project_merge_request_path(forked_project.namespace, + href = namespace_project_new_merge_request_path(forked_project.namespace, forked_project, merge_request: { source_branch: 'feature', target_branch: 'master' }) diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb index f2a2fd0311f..7ce3156215a 100644 --- a/spec/features/projects/merge_requests/list_spec.rb +++ b/spec/features/projects/merge_requests/list_spec.rb @@ -27,7 +27,7 @@ feature 'Merge Requests List' do it 'empty state should have a create merge request button' do visit namespace_project_merge_requests_path(project.namespace, project) - expect(page).to have_link 'New merge request', href: new_namespace_project_merge_request_path(project.namespace, project) + expect(page).to have_link 'New merge request', href: namespace_project_new_merge_request_path(project.namespace, project) end context 'if there are merge requests' do diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb index f375e1215db..5d0acad3832 100644 --- a/spec/features/projects/user_create_dir_spec.rb +++ b/spec/features/projects/user_create_dir_spec.rb @@ -51,7 +51,7 @@ feature 'New directory creation', feature: true, js: true do expect(page).to have_content 'New Merge Request' expect(page).to have_content "From #{new_branch_name} into master" expect(page).to have_content 'Add new directory' - expect(current_path).to eq(new_namespace_project_merge_request_path(project.namespace, project)) + expect(current_path).to eq(namespace_project_new_merge_request_path(project.namespace, project)) end end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index f33406a40a7..5e26b8bbed6 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -239,7 +239,7 @@ describe "Internal Project Access", feature: true do end describe "GET /:project_path/merge_requests/new" do - subject { new_namespace_project_merge_request_path(project.namespace, project) } + subject { namespace_project_new_merge_request_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 16a1331b2f3..59655b0c31a 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -452,7 +452,7 @@ describe "Public Project Access", feature: true do end describe "GET /:project_path/merge_requests/new" do - subject { new_namespace_project_merge_request_path(project.namespace, project) } + subject { namespace_project_new_merge_request_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index daaddd8f390..7e2f364ffa4 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -55,27 +55,14 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merge_request) end - it 'merge_requests/inline_changes_tab_with_comments.json' do |example| - create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) - create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) - render_merge_request(example.description, merge_request, action: :diffs, format: :json) - end - - it 'merge_requests/parallel_changes_tab_with_comments.json' do |example| - create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) - create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) - render_merge_request(example.description, merge_request, action: :diffs, format: :json, view: 'parallel') - end - private - def render_merge_request(fixture_file_name, merge_request, action: :show, format: :html, view: 'inline') - get action, + def render_merge_request(fixture_file_name, merge_request) + get :show, namespace_id: project.namespace.to_param, project_id: project, id: merge_request.to_param, - format: format, - view: view + format: :html expect(response).to be_success store_frontend_fixture(response, fixture_file_name) diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb new file mode 100644 index 00000000000..ac5b06ace6d --- /dev/null +++ b/spec/javascripts/fixtures/merge_requests_diffs.rb @@ -0,0 +1,57 @@ + +require 'spec_helper' + +describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') } + let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') } + let(:path) { "files/ruby/popen.rb" } + let(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + old_line: nil, + new_line: 14, + diff_refs: merge_request.diff_refs + ) + end + + render_views + + before(:all) do + clean_frontend_fixtures('merge_request_diffs/') + end + + before(:each) do + sign_in(admin) + end + + it 'merge_request_diffs/inline_changes_tab_with_comments.json' do |example| + create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) + create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) + render_merge_request(example.description, merge_request) + end + + it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do |example| + create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) + create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) + render_merge_request(example.description, merge_request, view: 'parallel') + end + + private + + def render_merge_request(fixture_file_name, merge_request, view: 'inline') + get :show, + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.to_param, + format: :json, + view: view + + expect(response).to be_success + store_frontend_fixture(response, fixture_file_name) + end +end diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js index 9e9eb17d439..395dc560671 100644 --- a/spec/javascripts/merge_request_notes_spec.js +++ b/spec/javascripts/merge_request_notes_spec.js @@ -15,7 +15,7 @@ describe('Merge request notes', () => { gl.utils = gl.utils || {}; const discussionTabFixture = 'merge_requests/diff_comment.html.raw'; - const changesTabJsonFixture = 'merge_requests/inline_changes_tab_with_comments.json'; + const changesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json'; preloadFixtures(discussionTabFixture, changesTabJsonFixture); describe('Discussion tab with diff comments', () => { diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index bb6b5d852d3..49ef21f75de 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -23,8 +23,8 @@ import 'vendor/jquery.scrollTo'; $.extend(stubLocation, defaults, stubs || {}); }; - const inlineChangesTabJsonFixture = 'merge_requests/inline_changes_tab_with_comments.json'; - const parallelChangesTabJsonFixture = 'merge_requests/parallel_changes_tab_with_comments.json'; + const inlineChangesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json'; + const parallelChangesTabJsonFixture = 'merge_request_diffs/parallel_changes_tab_with_comments.json'; preloadFixtures( 'merge_requests/merge_request_with_task_list.html.raw', 'merge_requests/diff_comment.html.raw', @@ -52,14 +52,10 @@ import 'vendor/jquery.scrollTo'; loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); this.subject = this.class.activateTab; }); - it('shows the first tab when action is show', function () { + it('shows the notes tab when action is show', function () { this.subject('show'); expect($('#notes')).toHaveClass('active'); }); - it('shows the notes tab when action is notes', function () { - this.subject('notes'); - expect($('#notes')).toHaveClass('active'); - }); it('shows the commits tab when action is commits', function () { this.subject('commits'); expect($('#commits')).toHaveClass('active'); @@ -161,7 +157,7 @@ import 'vendor/jquery.scrollTo'; setLocation({ pathname: '/foo/bar/merge_requests/1/commits' }); - expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); + expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); }); @@ -170,7 +166,7 @@ import 'vendor/jquery.scrollTo'; pathname: '/foo/bar/merge_requests/1/diffs' }); - expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); + expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); @@ -178,7 +174,7 @@ import 'vendor/jquery.scrollTo'; setLocation({ pathname: '/foo/bar/merge_requests/1/diffs.html' }); - expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); + expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 95d40138fea..2f1c3c95e59 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -246,28 +246,13 @@ describe 'project routing' do end end - # diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs - # commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits - # merge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/merge(.:format) projects/merge_requests#merge - # ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status - # toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription - # branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from - # branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to - # update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches - # namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index - # POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create - # new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new - # edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit - # namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show - # PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update - # PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update describe Projects::MergeRequestsController, 'routing' do - it 'to #diffs' do - expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + it 'to #commits' do + expect(get('/gitlab/gitlabhq/merge_requests/1/commits.json')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json') end - it 'to #commits' do - expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + it 'to #pipelines' do + expect(get('/gitlab/gitlabhq/merge_requests/1/pipelines.json')).to route_to('projects/merge_requests#pipelines', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json') end it 'to #merge' do @@ -277,25 +262,59 @@ describe 'project routing' do ) end + it 'to #show' do + expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff') + expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch') + expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'diffs') + expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'commits') + expect(get('/gitlab/gitlabhq/merge_requests/1/pipelines')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'pipelines') + end + + it_behaves_like 'RESTful project resources' do + let(:controller) { 'merge_requests' } + let(:actions) { [:index, :edit, :show, :update] } + end + end + + describe Projects::MergeRequests::CreationsController, 'routing' do + it 'to #new' do + expect(get('/gitlab/gitlabhq/merge_requests/new')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/merge_requests/new/diffs')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq', tab: 'diffs') + expect(get('/gitlab/gitlabhq/merge_requests/new/pipelines')).to route_to('projects/merge_requests/creations#new', namespace_id: 'gitlab', project_id: 'gitlabhq', tab: 'pipelines') + end + + it 'to #create' do + expect(post('/gitlab/gitlabhq/merge_requests')).to route_to('projects/merge_requests/creations#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + it 'to #branch_from' do - expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/merge_requests/new/branch_from')).to route_to('projects/merge_requests/creations#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq') end it 'to #branch_to' do - expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(get('/gitlab/gitlabhq/merge_requests/new/branch_to')).to route_to('projects/merge_requests/creations#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it 'to #show' do - expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff') - expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch') + it 'to #pipelines' do + expect(get('/gitlab/gitlabhq/merge_requests/new/pipelines.json')).to route_to('projects/merge_requests/creations#pipelines', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json') end - it_behaves_like 'RESTful project resources' do - let(:controller) { 'merge_requests' } - let(:actions) { [:index, :create, :new, :edit, :show, :update] } + it 'to #diffs' do + expect(get('/gitlab/gitlabhq/merge_requests/new/diffs.json')).to route_to('projects/merge_requests/creations#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json') + end + end + + describe Projects::MergeRequests::DiffsController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/merge_requests/1/diffs.json')).to route_to('projects/merge_requests/diffs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'json') end end + describe Projects::MergeRequests::ConflictsController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/merge_requests/1/conflicts')).to route_to('projects/merge_requests/conflicts#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + end # raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw # project_snippets GET /:project_id/snippets(.:format) snippets#index # POST /:project_id/snippets(.:format) snippets#create diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 50869099bb7..98b014df6cd 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -28,7 +28,12 @@ shared_examples 'issuable record that supports quick actions in its description describe "new #{issuable_type}", js: true do context 'with commands in the description' do it "creates the #{issuable_type} and interpret commands accordingly" do - visit public_send("new_namespace_project_#{issuable_type}_path", project.namespace, project, new_url_opts) + case issuable_type + when :merge_request + visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts) + when :issue + visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts) + end fill_in "#{issuable_type}_title", with: 'bug 345' fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\"" click_button "Submit #{issuable_type}".humanize diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb index 4052dbf8df3..3e17fe2104b 100644 --- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'projects/merge_requests/show/_commits.html.haml' do +describe 'projects/merge_requests/_commits.html.haml' do include Devise::Test::ControllerHelpers let(:user) { create(:user) } diff --git a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb deleted file mode 100644 index 4f698a34ab5..00000000000 --- a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -describe 'projects/merge_requests/_new_submit.html.haml', :view do - let(:merge_request) { create(:merge_request) } - let!(:pipeline) { create(:ci_empty_pipeline) } - - before do - controller.prepend_view_path('app/views/projects') - - assign(:merge_request, merge_request) - assign(:commits, merge_request.commits) - assign(:project, merge_request.target_project) - - allow(view).to receive(:can?).and_return(true) - allow(view).to receive(:url_for).and_return('#') - allow(view).to receive(:current_user).and_return(merge_request.author) - end - - context 'when there are pipelines for merge request but no pipeline for last commit' do - before do - assign(:pipelines, Ci::Pipeline.all) - assign(:pipeline, nil) - end - - it 'shows <> tab and hides <> tab' do - render - expect(rendered).to have_text('Pipelines 1') - expect(rendered).not_to have_text('Builds') - end - end -end diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb new file mode 100644 index 00000000000..1e9bdf9108f --- /dev/null +++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe 'projects/merge_requests/creations/_new_submit.html.haml', :view do + let(:merge_request) { create(:merge_request) } + let!(:pipeline) { create(:ci_empty_pipeline) } + + before do + controller.prepend_view_path('app/views/projects') + + assign(:merge_request, merge_request) + assign(:commits, merge_request.commits) + assign(:project, merge_request.target_project) + + allow(view).to receive(:can?).and_return(true) + allow(view).to receive(:url_for).and_return('#') + allow(view).to receive(:current_user).and_return(merge_request.author) + end + + context 'when there are pipelines for merge request but no pipeline for last commit' do + before do + assign(:pipelines, Ci::Pipeline.all) + assign(:pipeline, nil) + end + + it 'shows <> tab and hides <> tab' do + render + expect(rendered).to have_text('Pipelines 1') + expect(rendered).not_to have_text('Builds') + end + end +end -- cgit v1.2.1 From e8ebad15e3d6f5ddbe3596a6616aafec95725261 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 28 Jun 2017 20:46:30 -0300 Subject: Improve changelog --- ...group-members-counting-and-plan-related-data-on-namespaces-api.yml | 4 ++++ .../add-members-count-and-parent-id-data-on-namespaces-api.yml | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml delete mode 100644 changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml diff --git a/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml new file mode 100644 index 00000000000..f2591042e98 --- /dev/null +++ b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml @@ -0,0 +1,4 @@ +--- +title: Add group members counting and plan related data on namespaces API +merge_request: +author: diff --git a/changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml b/changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml deleted file mode 100644 index d3064a832ca..00000000000 --- a/changelogs/unreleased/add-members-count-and-parent-id-data-on-namespaces-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add "members_count" and "parent_id" data on namespaces API -merge_request: -author: -- cgit v1.2.1 From 3a4fc1246c6ba9fbdb521da56aeb8e341ef5a429 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 28 Jun 2017 19:06:42 -0500 Subject: Limit OpenGraph image size to 64x64 --- app/views/layouts/_head.html.haml | 2 ++ changelogs/unreleased/dm-page-image-size.yml | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 changelogs/unreleased/dm-page-image-size.yml diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index eabc9a3b01c..cc710f4ec7d 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -11,6 +11,8 @@ %meta{ property: 'og:title', content: page_title } %meta{ property: 'og:description', content: page_description } %meta{ property: 'og:image', content: page_image } + %meta{ property: 'og:image:width', content: '64' } + %meta{ property: 'og:image:height', content: '64' } %meta{ property: 'og:url', content: request.base_url + request.fullpath } -# Twitter Card - https://dev.twitter.com/cards/types/summary diff --git a/changelogs/unreleased/dm-page-image-size.yml b/changelogs/unreleased/dm-page-image-size.yml new file mode 100644 index 00000000000..b18c00470fc --- /dev/null +++ b/changelogs/unreleased/dm-page-image-size.yml @@ -0,0 +1,4 @@ +--- +title: Limit OpenGraph image size to 64x64 +merge_request: +author: -- cgit v1.2.1 From 199425cee601733ef4f33ec5b76f5afe948cba61 Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Wed, 28 Jun 2017 16:05:02 +0100 Subject: Inserts exact matches of username, email and name to the top of the user search list --- app/models/user.rb | 11 ++++- .../unreleased/26125-match-username-on-search.yml | 5 ++ spec/models/user_spec.rb | 57 ++++++++++++---------- 3 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 changelogs/unreleased/26125-match-username-on-search.yml diff --git a/app/models/user.rb b/app/models/user.rb index 6dd1b1415d6..35e0d021c47 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -299,11 +299,20 @@ class User < ActiveRecord::Base table = arel_table pattern = "%#{query}%" + order = <<~SQL + CASE + WHEN users.name = %{query} THEN 0 + WHEN users.username = %{query} THEN 1 + WHEN users.email = %{query} THEN 2 + ELSE 3 + END + SQL + where( table[:name].matches(pattern) .or(table[:email].matches(pattern)) .or(table[:username].matches(pattern)) - ) + ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc) end # searches user by given pattern diff --git a/changelogs/unreleased/26125-match-username-on-search.yml b/changelogs/unreleased/26125-match-username-on-search.yml new file mode 100644 index 00000000000..74e918bec16 --- /dev/null +++ b/changelogs/unreleased/26125-match-username-on-search.yml @@ -0,0 +1,5 @@ +--- +title: Inserts exact matches of name, username and email to the top of the search + list +merge_request: 12525 +author: diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8e895ec6634..448555d2190 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -754,42 +754,49 @@ describe User, models: true do end describe '.search' do - let(:user) { create(:user) } + let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') } + let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') } - it 'returns users with a matching name' do - expect(described_class.search(user.name)).to eq([user]) - end + describe 'name matching' do + it 'returns users with a matching name with exact match first' do + expect(described_class.search(user.name)).to eq([user, user2]) + end - it 'returns users with a partially matching name' do - expect(described_class.search(user.name[0..2])).to eq([user]) - end + it 'returns users with a partially matching name' do + expect(described_class.search(user.name[0..2])).to eq([user2, user]) + end - it 'returns users with a matching name regardless of the casing' do - expect(described_class.search(user.name.upcase)).to eq([user]) + it 'returns users with a matching name regardless of the casing' do + expect(described_class.search(user2.name.upcase)).to eq([user2]) + end end - it 'returns users with a matching Email' do - expect(described_class.search(user.email)).to eq([user]) - end + describe 'email matching' do + it 'returns users with a matching Email' do + expect(described_class.search(user.email)).to eq([user, user2]) + end - it 'returns users with a partially matching Email' do - expect(described_class.search(user.email[0..2])).to eq([user]) - end + it 'returns users with a partially matching Email' do + expect(described_class.search(user.email[0..2])).to eq([user2, user]) + end - it 'returns users with a matching Email regardless of the casing' do - expect(described_class.search(user.email.upcase)).to eq([user]) + it 'returns users with a matching Email regardless of the casing' do + expect(described_class.search(user2.email.upcase)).to eq([user2]) + end end - it 'returns users with a matching username' do - expect(described_class.search(user.username)).to eq([user]) - end + describe 'username matching' do + it 'returns users with a matching username' do + expect(described_class.search(user.username)).to eq([user, user2]) + end - it 'returns users with a partially matching username' do - expect(described_class.search(user.username[0..2])).to eq([user]) - end + it 'returns users with a partially matching username' do + expect(described_class.search(user.username[0..2])).to eq([user2, user]) + end - it 'returns users with a matching username regardless of the casing' do - expect(described_class.search(user.username.upcase)).to eq([user]) + it 'returns users with a matching username regardless of the casing' do + expect(described_class.search(user2.username.upcase)).to eq([user2]) + end end end -- cgit v1.2.1 From e488a4095f6c0b8901a796c17841e6eb03b06bbb Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Thu, 29 Jun 2017 10:34:05 +0200 Subject: Bring back branches badge to main project page --- app/views/projects/show.html.haml | 2 +- changelogs/unreleased/highest-return-on-diff-investment.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/highest-return-on-diff-investment.yml diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 7447197ed89..e1e70a53709 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -21,7 +21,7 @@ %li = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do #{n_('Commit', 'Commits', @project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)}) - %l + %li = link_to namespace_project_branches_path(@project.namespace, @project) do #{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) %li diff --git a/changelogs/unreleased/highest-return-on-diff-investment.yml b/changelogs/unreleased/highest-return-on-diff-investment.yml new file mode 100644 index 00000000000..c8be1e0ff8f --- /dev/null +++ b/changelogs/unreleased/highest-return-on-diff-investment.yml @@ -0,0 +1,4 @@ +--- +title: Bring back branches badge to main project page +merge_request: 12548 +author: -- cgit v1.2.1 From 6da858dcb18ef50230b63a306375403c516ae074 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 29 Jun 2017 11:08:54 +0000 Subject: Job details won't scroll horizontally to show long lines --- app/assets/javascripts/build.js | 7 ++++++- app/assets/stylesheets/pages/builds.scss | 5 ++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index c28f6e151a0..9974e135022 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -196,6 +196,7 @@ window.Build = (function () { }) .done((log) => { gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`); + if (log.state) { this.state = log.state; } @@ -220,7 +221,11 @@ window.Build = (function () { } if (!log.complete) { - this.toggleScrollAnimation(true); + if (!this.hasBeenScrolled) { + this.toggleScrollAnimation(true); + } else { + this.toggleScrollAnimation(false); + } Build.timeout = setTimeout(() => { //eslint-disable-next-line diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index cdb0b1bc59e..9cff99b839c 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -147,10 +147,9 @@ top: 35px; left: 10px; bottom: 0; - overflow-y: scroll; - overflow-x: hidden; padding: 10px 20px 20px 5px; - white-space: pre; + white-space: pre-wrap; + overflow: auto; } .environment-information { -- cgit v1.2.1 From 5994fc38a84e2988667274fe55b60c5d44255b5b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 29 Jun 2017 07:05:07 -0500 Subject: Minor edits --- app/assets/stylesheets/new_sidebar.scss | 3 +-- app/views/layouts/nav/_new_admin_sidebar.html.haml | 2 +- app/views/layouts/nav/_new_group_sidebar.html.haml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index efcf456a8d1..be4cc02b3ea 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -36,8 +36,7 @@ $new-sidebar-width: 220px; .settings-avatar { background-color: $white-light; - .fa-wrench, - .fa-user { + i { font-size: 20px; width: 100%; color: $gl-text-color-secondary; diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index c9415469912..40c1ca7b53e 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -1,5 +1,5 @@ .nav-sidebar - = link_to admin_root_path, title: 'Overview', class: 'context-header' do + = link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do .avatar-container.s40.settings-avatar = icon('wrench') .project-title Admin Area diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index 7720918be54..b7ac04cc3e5 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -1,5 +1,5 @@ .nav-sidebar - = link_to group_path(@group), title: 'Home', class: 'context-header' do + = link_to group_path(@group), title: 'Group', class: 'context-header' do .avatar-container.s40.group-avatar = image_tag group_icon(@group), class: "avatar s40 avatar-tile" .group-title -- cgit v1.2.1