├── ssh_keys └── .placeholder ├── app ├── views │ ├── gitolite_public_keys │ │ ├── index.html.slim │ │ ├── _view.html.slim │ │ ├── _form.html.slim │ │ └── _ssh_keys.html.slim │ ├── go_redirector │ │ └── index.html.slim │ ├── settings │ │ ├── authors.html.slim │ │ ├── install_gitolite_hooks.js.erb │ │ ├── redmine_git_hosting │ │ │ ├── _sidekiq_interface.html.slim │ │ │ ├── _gitolite_rescue.html.slim │ │ │ ├── _install_hooks_result.html.slim │ │ │ ├── _gitolite_recycle_bin.html.slim │ │ │ ├── _gitolite_config_storage.html.slim │ │ │ └── _gitolite_config_cache.html.slim │ │ └── _redmine_git_hosting.html.slim │ ├── repositories │ │ ├── _git_hosting_navigation.html.slim │ │ ├── statistics │ │ │ └── _global.html.slim │ │ ├── _download_revision.html.slim │ │ ├── _readme.html.slim │ │ ├── _edit_top.html.slim │ │ ├── _show_top.html.slim │ │ ├── _git_hosting_sidebar.html.slim │ │ └── edit.html.slim │ ├── repository_mirrors │ │ ├── edit.js.erb │ │ ├── new.js.erb │ │ ├── push.js.erb │ │ ├── _push_modal.html.slim │ │ ├── show.api.rsb │ │ ├── _new_modal.html.slim │ │ ├── _edit_modal.html.slim │ │ ├── index.api.rsb │ │ └── _form.html.slim │ ├── common │ │ ├── _git_hosting_html_head.html.slim │ │ ├── _git_hosting_js_headers.html.slim │ │ └── _git_urls.html.slim │ ├── hooks │ │ └── _show_repositories_sidebar.html.slim │ ├── repository_git_config_keys │ │ ├── edit.js.erb │ │ ├── new.js.erb │ │ ├── show.api.rsb │ │ ├── index.api.rsb │ │ ├── _form.html.slim │ │ ├── _new_modal.html.slim │ │ ├── _edit_modal.html.slim │ │ ├── _config_keys.html.slim │ │ └── index.html.slim │ ├── repository_git_extras │ │ ├── sort_urls.js.erb │ │ ├── update.js.erb │ │ ├── _sort_urls_modal.html.slim │ │ └── move.html.slim │ ├── repository_post_receive_urls │ │ ├── edit.js.erb │ │ ├── new.js.erb │ │ ├── show.api.rsb │ │ ├── _new_modal.html.slim │ │ ├── _edit_modal.html.slim │ │ ├── index.api.rsb │ │ └── _form.html.slim │ ├── repository_protected_branches │ │ ├── new.js.erb │ │ ├── edit.js.erb │ │ ├── show.api.rsb │ │ ├── index.api.rsb │ │ ├── _new_modal.html.slim │ │ ├── _edit_modal.html.slim │ │ └── _form.html.slim │ ├── repository_deployment_credentials │ │ ├── edit.js.erb │ │ ├── new.js.erb │ │ ├── _edit_modal.html.slim │ │ └── _new_modal.html.slim │ ├── dashboards │ │ └── blocks │ │ │ └── _git_urls.html.slim │ └── users │ │ └── index.api.rsb ├── models │ ├── repository_git_config_key │ │ ├── option.rb │ │ └── git_config.rb │ ├── github_issue.rb │ ├── github_comment.rb │ ├── concerns │ │ ├── gitolitable.rb │ │ └── gitolitable │ │ │ └── notifications.rb │ ├── git_cache.rb │ ├── protected_branches_member.rb │ ├── repository_git_config_key.rb │ └── repository_protected_branche.rb ├── reports │ ├── report_base.rb │ ├── report_helper.rb │ ├── report_query.rb │ └── repository_global_stats.rb ├── helpers │ ├── repository_git_config_keys_helper.rb │ ├── repository_post_receive_urls_helper.rb │ ├── gitolite_public_keys_helper.rb │ ├── repository_deployment_credentials_helper.rb │ ├── git_hosting_users_helper.rb │ ├── git_hosting_helper.rb │ └── repository_mirrors_helper.rb ├── services │ ├── redmine_hooks │ │ ├── http_helper.rb │ │ ├── fetch_changesets.rb │ │ └── base.rb │ └── permissions_builder │ │ ├── protected_branches.rb │ │ └── base.rb ├── controllers │ ├── go_redirector_controller.rb │ ├── archived_repositories_controller.rb │ ├── concerns │ │ └── xitolite_repository_finder.rb │ ├── download_git_revision_controller.rb │ └── repository_git_extras_controller.rb ├── use_cases │ ├── repository_mirrors │ │ ├── base.rb │ │ └── push.rb │ ├── projects │ │ ├── base.rb │ │ ├── execute_hooks.rb │ │ ├── update.rb │ │ └── create_repository.rb │ └── repositories │ │ └── base.rb ├── overrides │ └── repositories │ │ ├── navigation.rb │ │ └── show.rb ├── forms │ ├── base_form.rb │ ├── plugin_settings_validation │ │ ├── cache_config.rb │ │ ├── storage_config.rb │ │ ├── hooks_config.rb │ │ ├── ssh_config.rb │ │ └── mailing_list_config.rb │ └── move_repository_form.rb └── workers │ └── githosting_shell_worker.rb ├── lib ├── hrack │ ├── version.rb │ └── bundle.rb └── redmine_git_hosting │ ├── commands.rb │ ├── auth.rb │ ├── config.rb │ ├── git_access_status.rb │ ├── plugin_author.rb │ ├── gitolite_wrappers │ ├── global │ │ ├── purge_recycle_bin.rb │ │ ├── delete_from_recycle_bin.rb │ │ └── common.rb │ ├── users │ │ ├── resync_ssh_keys.rb │ │ ├── regenerate_ssh_keys.rb │ │ ├── delete_ssh_key.rb │ │ └── add_ssh_key.rb │ ├── repositories │ │ ├── move_repository.rb │ │ ├── delete_repository.rb │ │ └── update_repository.rb │ └── projects │ │ ├── move_repositories.rb │ │ ├── move_repositories_tree.rb │ │ └── common.rb │ ├── plugins.rb │ ├── utils │ ├── crypto.rb │ ├── git.rb │ └── ssh.rb │ ├── patches │ ├── issue_patch.rb │ ├── journal_patch.rb │ ├── repository_patch.rb │ ├── repositories_helper_patch.rb │ ├── sys_controller_patch.rb │ ├── member_patch.rb │ ├── setting_patch.rb │ ├── dashboard_content_project_patch.rb │ ├── grack_server_patch.rb │ ├── group_patch.rb │ ├── roles_controller_patch.rb │ └── watchers_controller_patch.rb │ ├── file_logger.rb │ ├── gitolite_handlers │ ├── ssh_keys │ │ ├── delete_ssh_key.rb │ │ ├── add_ssh_key.rb │ │ └── base.rb │ └── repositories │ │ ├── delete_repository.rb │ │ ├── update_repository.rb │ │ └── add_repository.rb │ ├── markdown_renderer.rb │ ├── config │ ├── gitolite_cache.rb │ ├── gitolite_storage.rb │ ├── gitolite_notifications.rb │ ├── mirroring.rb │ └── redmine_config.rb │ ├── gitolite_params │ └── base_param.rb │ ├── commands │ └── base.rb │ ├── plugins │ ├── sweepers │ │ ├── base_sweeper.rb │ │ └── repository_deletor.rb │ ├── extenders │ │ ├── base_extender.rb │ │ ├── config_key_deletor.rb │ │ ├── branch_updater.rb │ │ └── git_annex_creator.rb │ └── gitolite_plugin.rb │ ├── recycle_bin │ ├── item.rb │ ├── item_base.rb │ └── deletable_item.rb │ ├── recycle_bin.rb │ ├── console_logger.rb │ ├── error.rb │ ├── journal_logger.rb │ ├── gitolite_hooks.rb │ └── redcarpet_filter.rb ├── package.json ├── config └── sidekiq.yml ├── spec ├── factories │ ├── group.rb │ ├── repository_svn.rb │ ├── project.rb │ ├── repository_gitolite.rb │ ├── repository_post_receive_url.rb │ ├── repository_protected_branche.rb │ ├── repository_deployment_credential.rb │ ├── repository_mirror.rb │ ├── repository_git_extra.rb │ ├── user.rb │ ├── protected_branches_member.rb │ ├── gitolite_public_key.rb │ ├── repository_git_config_key.rb │ └── role.rb ├── models │ ├── group_spec.rb │ ├── repository_git_config_key_spec.rb │ ├── setting_spec.rb │ ├── user_spec.rb │ ├── repository_git_config_key │ │ ├── option_spec.rb │ │ └── git_config_spec.rb │ └── project_spec.rb ├── spec_helper.rb ├── lib │ └── redmine_git_hosting │ │ ├── config_spec.rb │ │ └── utils │ │ └── git_spec.rb ├── controllers │ └── users_controller_spec.rb └── root_spec_helper.rb ├── contrib ├── github │ ├── database-mysql.yml │ └── database-postgres.yml ├── scripts │ ├── puma.rb │ └── redmine └── hooks │ └── post-receive │ ├── lib │ └── git_hosting_hook_logger.rb │ ├── redmine_gitolite.rb │ └── mail_notifications.py ├── db └── migrate │ ├── 20111119170948_add_indexes_to_gitolite_public_key.rb │ ├── 20150703050000_add_type_field_to_git_config_keys.rb │ ├── 20150604051500_add_unique_index_to_fingerprint.rb │ ├── 20120522000000_add_post_receive_url_modes.rb │ ├── 20130909195828_rename_table_git_repository_extras.rb │ ├── 20150410031920_add_urls_order_to_repository_git_extra.rb │ ├── 20140618224954_add_fingerprint_to_gitolite_public_keys.rb │ ├── 20141228193500_add_git_annex_to_git_extras.rb │ ├── 20140523013000_add_split_payloads_to_post_receive.rb │ ├── 20150125234500_add_public_repo_to_repository_git_extra.rb │ ├── 20140621004300_add_protected_branch_to_repository_git_extra.rb │ ├── 20140516224900_add_trigger_to_post_receive.rb │ ├── 20140327015700_create_github_issues.rb │ ├── 20140327015701_create_github_comments.rb │ ├── 20130910195931_add_columns_to_repository_git_notification.rb │ ├── 20091119162428_create_git_caches.rb │ ├── 20140306002300_create_repository_git_config_keys.rb │ ├── 20140624150200_remove_gitolite_public_keys_active_column.rb │ ├── 20110807000000_create_repository_mirrors.rb │ ├── 20130909195727_create_repository_git_notifications.rb │ ├── 20120521000000_create_repository_post_receive_urls.rb │ ├── 20140305053200_remove_notify_cia.rb │ ├── 20091119162427_create_gitolite_public_keys.rb │ ├── 20130910195930_add_columns_to_repository_git_extra.rb │ ├── 20120710204007_add_repository_mirror_fields.rb │ ├── 20130909195929_rename_table_deployment_credentials.rb │ ├── 20150705180400_rename_git_config_keys_index.rb │ ├── 20140621004200_create_repository_protected_branches.rb │ ├── 20110726000000_extend_changesets_notified_cia.rb │ ├── 20150823030000_create_protected_branches_members.rb │ ├── 20140305083200_add_default_branch_to_repository_git_extra.rb │ ├── 20140417004100_enforce_models_constraints.rb │ ├── 20151127024000_shrink_git_notifications.rb │ ├── 20150823030100_migrate_protected_branches_users.rb │ ├── 20120708070841_add_settings_to_plugin_4.rb │ ├── 20110817000000_move_notified_cia_to_git_cia_notifications.rb │ ├── 20110813000000_create_git_repository_extras.rb │ ├── 20120724211806_add_settings_to_plugin_5.rb │ └── 20111123214911_add_settings_to_plugin.rb ├── .gitignore ├── .github └── workflows │ ├── stylelint.yml │ ├── brakeman.yml │ └── rubocop.yml ├── assets ├── images │ ├── button.svg │ ├── button_selected.svg │ └── button_focus.svg ├── stylesheets │ └── plugin.css └── javascripts │ ├── plugin.js │ └── git_urls.js ├── LICENSE ├── .slim-lint.yml └── custom_hooks.rb.example /ssh_keys/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/gitolite_public_keys/index.html.slim: -------------------------------------------------------------------------------- 1 | = render partial: 'gitolite_public_keys/view' 2 | -------------------------------------------------------------------------------- /lib/hrack/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Hrack 4 | module Version 5 | end 6 | VERSION = '1.0.0' 7 | end 8 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/commands.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Commands 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {}, 3 | "devDependencies": { 4 | "eslint": "^7.0.0", 5 | "stylelint": "^14.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :pidfile: tmp/pids/sidekiq_redmine_git_hosting.pid 3 | :concurrency: 1 4 | :timeout: 15 5 | :queues: 6 | - redmine_git_hosting 7 | -------------------------------------------------------------------------------- /app/views/go_redirector/index.html.slim: -------------------------------------------------------------------------------- 1 | - content_for :header_tags do 2 | meta name="go-import" content="#{@repository.go_url} git #{@repository.go_access_url}" 3 | -------------------------------------------------------------------------------- /app/views/settings/authors.html.slim: -------------------------------------------------------------------------------- 1 | div style='height: 50%;' 2 | ul.authors-list 3 | - @authors.each do |author| 4 | li = mail_to author.email, author.name 5 | -------------------------------------------------------------------------------- /app/views/repositories/_git_hosting_navigation.html.slim: -------------------------------------------------------------------------------- 1 | - if User.current.allowed_to? :add_repository_xitolite_watchers, @project 2 | = watcher_link @repository, User.current 3 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/edit.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_mirrors/edit_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/new.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_mirrors/new_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/push.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_mirrors/push_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /spec/factories/group.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :group do 5 | sequence(:lastname) { |n| "GroupTest#{n}" } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/common/_git_hosting_html_head.html.slim: -------------------------------------------------------------------------------- 1 | = stylesheet_link_tag 'plugin', plugin: 'redmine_git_hosting' 2 | = javascript_include_tag 'plugin', plugin: 'redmine_git_hosting' 3 | -------------------------------------------------------------------------------- /app/views/hooks/_show_repositories_sidebar.html.slim: -------------------------------------------------------------------------------- 1 | - if @repositories.size > 1 2 | - content_for :sidebar do 3 | = call_hook :view_repositories_show_sidebar, repository: @repository 4 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/edit.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_git_config_keys/edit_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/new.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_git_config_keys/new_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/show.api.rsb: -------------------------------------------------------------------------------- 1 | api.git_config_key do 2 | api.id @git_config_key.id 3 | api.key @git_config_key.key 4 | api.value @git_config_key.value 5 | end 6 | -------------------------------------------------------------------------------- /app/views/repository_git_extras/sort_urls.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_git_extras/sort_urls_modal') %>'); 2 | showModal('ajax-modal', '400px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_post_receive_urls/edit.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_post_receive_urls/edit_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_post_receive_urls/new.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_post_receive_urls/new_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_protected_branches/new.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_protected_branches/new_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_protected_branches/edit.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_protected_branches/edit_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_deployment_credentials/edit.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_deployment_credentials/edit_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/repository_deployment_credentials/new.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html('<%= escape_javascript(render partial: 'repository_deployment_credentials/new_modal') %>'); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /app/views/settings/install_gitolite_hooks.js.erb: -------------------------------------------------------------------------------- 1 | $('#ajax-modal').html("<%= escape_javascript(render partial: 'settings/redmine_git_hosting/install_hooks_result') %>"); 2 | showModal('ajax-modal', '600px'); 3 | -------------------------------------------------------------------------------- /spec/factories/repository_svn.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_svn, class: 'Repository::Subversion' do 5 | is_default { false } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /contrib/github/database-mysql.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql2 3 | database: redmine 4 | port: <%= ENV["MYSQL_PORT"] %> 5 | host: 127.0.0.1 6 | username: root 7 | password: BestPasswordEver 8 | encoding: utf8mb4 9 | -------------------------------------------------------------------------------- /app/views/common/_git_hosting_js_headers.html.slim: -------------------------------------------------------------------------------- 1 | - content_for :header_tags do 2 | = stylesheet_link_tag 'application', plugin: 'redmine_git_hosting' 3 | = javascript_include_tag 'application', plugin: 'redmine_git_hosting' 4 | -------------------------------------------------------------------------------- /spec/factories/project.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :project do 5 | sequence(:identifier) { |n| "project#{n}" } 6 | sequence(:name) { |n| "Project#{n}" } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/repository_gitolite.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_gitolite, class: 'Repository::Xitolite' do 5 | is_default { false } 6 | association :project 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/repositories/statistics/_global.html.slim: -------------------------------------------------------------------------------- 1 | - report = RepositoryGlobalStats.new(repository).build 2 | 3 | ul.thumbnails 4 | - report.each do |key, value| 5 | li.span4 6 | .thumbnail 7 | .title = key 8 | .value = value 9 | -------------------------------------------------------------------------------- /db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddIndexesToGitolitePublicKey < ActiveRecord::Migration[4.2] 4 | def change 5 | add_index :gitolite_public_keys, :identifier 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20150703050000_add_type_field_to_git_config_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddTypeFieldToGitConfigKeys < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_git_config_keys, :type, :string 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | Gemfile.lock 3 | settings.yml 4 | /tmp 5 | /rdoc 6 | /coverage 7 | /junit 8 | custom_hooks.rb 9 | /ssh_keys/redmine_gitolite_admin_id_rsa* 10 | .DS_Store 11 | .vscode/ 12 | node_modules/ 13 | yarn.lock 14 | coverage/ 15 | .enable_dev 16 | -------------------------------------------------------------------------------- /db/migrate/20150604051500_add_unique_index_to_fingerprint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddUniqueIndexToFingerprint < ActiveRecord::Migration[4.2] 4 | def change 5 | add_index :gitolite_public_keys, :fingerprint, unique: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20120522000000_add_post_receive_url_modes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddPostReceiveUrlModes < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_post_receive_urls, :mode, :string, default: 'github' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20130909195828_rename_table_git_repository_extras.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RenameTableGitRepositoryExtras < ActiveRecord::Migration[4.2] 4 | def change 5 | rename_table :git_repository_extras, :repository_git_extras 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20150410031920_add_urls_order_to_repository_git_extra.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddUrlsOrderToRepositoryGitExtra < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_git_extras, :urls_order, :text 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/_push_modal.html.slim: -------------------------------------------------------------------------------- 1 | p style='white-space: nowrap;' 2 | = render_push_state @mirror, @push_failed 3 | 4 | strong 5 | = l :label_mirror_push_output 6 | ' : 7 | 8 | pre class="mirror-push-#{'error' if @push_failed}" 9 | = @shellout 10 | -------------------------------------------------------------------------------- /app/models/repository_git_config_key/option.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepositoryGitConfigKey::Option < RepositoryGitConfigKey 4 | validates :key, presence: true, 5 | uniqueness: { case_sensitive: false, scope: %i[type repository_id] } 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20140618224954_add_fingerprint_to_gitolite_public_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddFingerprintToGitolitePublicKeys < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :gitolite_public_keys, :fingerprint, :string, after: 'key' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20141228193500_add_git_annex_to_git_extras.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddGitAnnexToGitExtras < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_git_extras, :git_annex, :boolean, default: false, after: :git_notify 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20140523013000_add_split_payloads_to_post_receive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddSplitPayloadsToPostReceive < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_post_receive_urls, :split_payloads, :boolean, default: false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/reports/report_base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ReportBase 4 | include Redmine::I18n 5 | include ReportHelper 6 | include ReportQuery 7 | 8 | attr_reader :repository 9 | 10 | def initialize(repository) 11 | @repository = repository 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/factories/repository_post_receive_url.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_post_receive_url do 5 | sequence(:url) { |n| "http://example.com/toto#{n}.php" } 6 | association :repository, factory: :repository_gitolite 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/repository_protected_branche.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_protected_branche do 5 | path { 'master' } 6 | permissions { 'RW+' } 7 | association :repository, factory: :repository_gitolite 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/github_issue.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GithubIssue < ActiveRecord::Base 4 | ## Relations 5 | belongs_to :issue 6 | 7 | ## Validations 8 | validates :github_id, presence: true 9 | validates :issue_id, presence: true, uniqueness: { scope: :github_id } 10 | end 11 | -------------------------------------------------------------------------------- /contrib/scripts/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | stdout_redirect '/home/redmine/redmine/log/puma.stderr.log', '/home/redmine/redmine/log/puma.stdout.log' 4 | 5 | on_worker_boot do 6 | ActiveSupport.on_load :active_record do 7 | ActiveRecord::Base.establish_connection 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/github_comment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GithubComment < ActiveRecord::Base 4 | ## Relations 5 | belongs_to :journal 6 | 7 | ## Validations 8 | validates :github_id, presence: true 9 | validates :journal_id, presence: true, uniqueness: { scope: :github_id } 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150125234500_add_public_repo_to_repository_git_extra.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddPublicRepoToRepositoryGitExtra < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_git_extras, :public_repo, :boolean, default: false, after: :protected_branch 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/repository_protected_branches/show.api.rsb: -------------------------------------------------------------------------------- 1 | api.protected_branch do 2 | api.id @protected_branch.id 3 | api.path @protected_branch.path 4 | api.permissions @protected_branch.permissions 5 | api.user_list @protected_branch.user_list 6 | api.position @protected_branch.position 7 | end 8 | -------------------------------------------------------------------------------- /app/views/settings/redmine_git_hosting/_sidekiq_interface.html.slim: -------------------------------------------------------------------------------- 1 | / Gitolite Sidekiq Config 2 | p 3 | = additionals_settings_checkbox :gitolite_use_sidekiq, 4 | value: RedmineGitHosting::Config.get_setting(:gitolite_use_sidekiq, true), 5 | value_is_bool: true 6 | -------------------------------------------------------------------------------- /db/migrate/20140621004300_add_protected_branch_to_repository_git_extra.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddProtectedBranchToRepositoryGitExtra < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_git_extras, :protected_branch, :boolean, default: false, after: :default_branch 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/repository_deployment_credential.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_deployment_credential do 5 | perm { 'RW+' } 6 | association :repository, factory: :repository_gitolite 7 | association :user 8 | association :gitolite_public_key 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/factories/repository_mirror.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_mirror do 5 | sequence(:url) { |n| "ssh://git@example.com:22/john_doe/john_doe/john_doe_#{n}.git" } 6 | push_mode { 0 } 7 | association :repository, factory: :repository_gitolite 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140516224900_add_trigger_to_post_receive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddTriggerToPostReceive < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_post_receive_urls, :use_triggers, :boolean, default: false 6 | add_column :repository_post_receive_urls, :triggers, :text 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20140327015700_create_github_issues.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGithubIssues < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :github_issues do |t| 6 | t.column :github_id, :integer, null: false 7 | t.column :issue_id, :integer, null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140327015701_create_github_comments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGithubComments < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :github_comments do |t| 6 | t.column :github_id, :integer, null: false 7 | t.column :journal_id, :integer, null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/factories/repository_git_extra.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_git_extra do 5 | git_http { 0 } 6 | default_branch { 'master' } 7 | association :repository, factory: :repository_gitolite 8 | key { RedmineGitHosting::Utils::Crypto.generate_secret 64 } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/helpers/repository_git_config_keys_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RepositoryGitConfigKeysHelper 4 | def git_config_key_options 5 | [ 6 | [l(:label_git_key_type_config), 'RepositoryGitConfigKey::GitConfig'], 7 | [l(:label_git_key_type_option), 'RepositoryGitConfigKey::Option'] 8 | ] 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20130910195931_add_columns_to_repository_git_notification.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddColumnsToRepositoryGitNotification < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_git_notifications, :prefix, :string 6 | add_column :repository_git_notifications, :sender_address, :string 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/hrack/bundle.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rack/builder' 4 | require 'rack/parser' 5 | 6 | module Hrack 7 | module Bundle 8 | module_function 9 | 10 | def new(config) 11 | Rack::Builder.new do 12 | use Rack::Parser 13 | run Hrack::Server.new(config) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/services/redmine_hooks/http_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineHooks 4 | module HttpHelper 5 | def http_post(url, **opts) 6 | RedmineGitHosting::Utils::Http.http_post url, **opts 7 | end 8 | 9 | def http_get(url, **opts) 10 | RedmineGitHosting::Utils::Http.http_get url, **opts 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/repository_git_extras/update.js.erb: -------------------------------------------------------------------------------- 1 | $('#xitolite-options').html("<%= escape_javascript(render 'repositories/xitolite_options', repository: @repository) %>"); 2 | $('#xitolite-messages').append("<%= escape_javascript render_flash_messages %>"); 3 | $('#xitolite-messages').children().each(function(index, element){ $(element).delay(3000).slideUp(200, function(){$(this) }) }); 4 | -------------------------------------------------------------------------------- /db/migrate/20091119162428_create_git_caches.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGitCaches < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :git_caches do |t| 6 | t.column :command, :text 7 | t.column :command_output, :binary 8 | t.column :proj_identifier, :string 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/index.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :repository_git_config_keys, api_meta(total_count: @repository_git_config_keys.count) do 2 | @repository_git_config_keys.each do |git_config_key| 3 | api.git_config_key do 4 | api.id git_config_key.id 5 | api.key git_config_key.key 6 | api.value git_config_key.value 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140306002300_create_repository_git_config_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateRepositoryGitConfigKeys < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :repository_git_config_keys do |t| 6 | t.column :repository_id, :integer 7 | t.column :key, :string 8 | t.column :value, :string 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/auth.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | class Auth 5 | def find(login, password) 6 | user = User.find_by login: login 7 | # Return if user not found 8 | return if user.nil? 9 | 10 | # Return user if password matches 11 | user if user.check_password? password 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140624150200_remove_gitolite_public_keys_active_column.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RemoveGitolitePublicKeysActiveColumn < ActiveRecord::Migration[4.2] 4 | def up 5 | remove_column :gitolite_public_keys, :active 6 | end 7 | 8 | def down 9 | add_column :gitolite_public_keys, :active, :boolean, default: true, after: :fingerprint 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/repositories/_download_revision.html.slim: -------------------------------------------------------------------------------- 1 | - if repository.downloadable? 2 | = font_awesome_icon 'fas_download', post_text: l(:label_download_format) 3 | ' 4 | = select_tag :download, options_for_select(available_download_format(repository, @rev)), 5 | prompt: l(:label_download_select_format), 6 | onchange: "if (this.value != '') { window.location = this.value; }" 7 | -------------------------------------------------------------------------------- /db/migrate/20110807000000_create_repository_mirrors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateRepositoryMirrors < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :repository_mirrors do |t| 6 | t.references :project, type: :integer 7 | t.column :active, :integer, default: 1 8 | t.column :url, :string 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20130909195727_create_repository_git_notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateRepositoryGitNotifications < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :repository_git_notifications do |t| 6 | t.column :repository_id, :integer 7 | t.column :include_list, :text 8 | t.column :exclude_list, :text 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/models/group_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('../spec_helper', __dir__) 4 | 5 | describe Group do 6 | subject { group } 7 | 8 | let(:group) { build :group } 9 | 10 | it { is_expected.to have_many(:protected_branches_members).dependent(:destroy) } 11 | it { is_expected.to have_many(:protected_branches).through(:protected_branches_members) } 12 | end 13 | -------------------------------------------------------------------------------- /.github/workflows/stylelint.yml: -------------------------------------------------------------------------------- 1 | name: Run StyleLint 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: '16' 17 | - run: yarn install 18 | - run: node_modules/.bin/stylelint "assets/stylesheets/*.css" 19 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Config 5 | GITHUB_ISSUE = 'https://github.com/jbox-web/redmine_git_hosting/issues' 6 | GITHUB_WIKI = 'http://redmine-git-hosting.io/configuration/variables/' 7 | 8 | GITOLITE_DEFAULT_CONFIG_FILE = 'gitolite.conf' 9 | GITOLITE_IDENTIFIER_DEFAULT_PREFIX = 'redmine_' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/repository_git_config_key/git_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepositoryGitConfigKey::GitConfig < RepositoryGitConfigKey 4 | VALID_CONFIG_KEY_REGEX = /\A[a-zA-Z0-9]+\.[a-zA-Z0-9.]+\z/ 5 | 6 | validates :key, presence: true, 7 | uniqueness: { case_sensitive: false, scope: %i[type repository_id] }, 8 | format: { with: VALID_CONFIG_KEY_REGEX } 9 | end 10 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/show.api.rsb: -------------------------------------------------------------------------------- 1 | api.mirror do 2 | api.id @mirror.id 3 | api.url @mirror.url 4 | api.active @mirror.active 5 | api.push_mode @mirror.push_mode_to_s 6 | api.include_all_branches @mirror.include_all_branches 7 | api.include_all_tags @mirror.include_all_tags 8 | api.explicit_refspec @mirror.explicit_refspec 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20120521000000_create_repository_post_receive_urls.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateRepositoryPostReceiveUrls < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :repository_post_receive_urls do |t| 6 | t.references :project, type: :integer 7 | t.column :active, :integer, default: 1 8 | t.column :url, :string 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140305053200_remove_notify_cia.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RemoveNotifyCia < ActiveRecord::Migration[4.2] 4 | def up 5 | drop_table :git_cia_notifications 6 | remove_column :repository_git_extras, :notify_cia 7 | end 8 | 9 | def down 10 | create_table :git_cia_notifications do |t| 11 | t.column :repository_id, :integer 12 | t.column :scmid, :string 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/go_redirector_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GoRedirectorController < ApplicationController 4 | include XitoliteRepositoryFinder 5 | 6 | # prevents login action to be filtered by check_if_login_required application scope filter 7 | skip_before_action :check_if_login_required, :verify_authenticity_token 8 | 9 | before_action :find_xitolite_repository_by_path 10 | 11 | def index; end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/repository_post_receive_urls/show.api.rsb: -------------------------------------------------------------------------------- 1 | api.post_receive_url do 2 | api.id @post_receive_url.id 3 | api.url @post_receive_url.url 4 | api.mode @post_receive_url.mode.to_s 5 | api.active @post_receive_url.active 6 | api.use_triggers @post_receive_url.use_triggers 7 | api.triggers @post_receive_url.triggers 8 | api.split_payloads @post_receive_url.split_payloads 9 | end 10 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/git_access_status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | class GitAccessStatus 5 | attr_accessor :status, :message 6 | alias allowed? status 7 | 8 | def initialize(status, message = '') 9 | @status = status 10 | @message = message 11 | end 12 | 13 | def to_json(*_args) 14 | { status: @status, message: @message }.to_json 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugin_author.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | class PluginAuthor 5 | attr_reader :author 6 | 7 | def initialize(author) 8 | @author = author 9 | end 10 | 11 | def name 12 | RedmineGitHosting::Utils::Git.author_name author 13 | end 14 | 15 | def email 16 | RedmineGitHosting::Utils::Git.author_email(author).downcase 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20091119162427_create_gitolite_public_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGitolitePublicKeys < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :gitolite_public_keys do |t| 6 | t.column :title, :string 7 | t.column :identifier, :string 8 | t.column :key, :text 9 | t.column :active, :integer, default: 1 10 | t.references :user 11 | t.timestamps 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20130910195930_add_columns_to_repository_git_extra.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddColumnsToRepositoryGitExtra < ActiveRecord::Migration[4.2] 4 | def up 5 | return if RepositoryGitExtra.column_names.include? 'git_notify' 6 | 7 | add_column :repository_git_extras, :git_notify, :integer, default: 0, after: :git_http 8 | end 9 | 10 | def down 11 | remove_column :repository_git_extras, :git_notify 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/global/purge_recycle_bin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Global 6 | class PurgeRecycleBin < GitoliteWrappers::Base 7 | def call 8 | RedmineGitHosting::RecycleBin.delete_expired_content 9 | RedmineGitHosting.logger.info 'purge_recycle_bin : done !' 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/repositories/_readme.html.slim: -------------------------------------------------------------------------------- 1 | #readme_container.box 2 | h2.readme_trigger style='cursor: pointer;' 3 | | README 4 | 5 | hr 6 | 7 | #readme_at_repo_dir.wiki 8 | - if html.html_safe.respond_to? :force_encoding 9 | = html.html_safe.force_encoding 'UTF-8' 10 | - else 11 | = html.html_safe 12 | 13 | javascript: 14 | $(document).ready(function() { $("#readme_trigger").click(function(){ $("#readme_at_repo_dir").toggle(500); }); }); 15 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Plugins 5 | extend self 6 | 7 | def execute(step, repository, **opts) 8 | Plugins::GitolitePlugin.all_plugins.each do |plugin| 9 | plugin.new(repository, **opts).send(step) if plugin.method_defined? step 10 | end 11 | rescue StandardError => e 12 | RedmineGitHosting.logger.error e.message 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/utils/crypto.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'securerandom' 4 | 5 | module RedmineGitHosting 6 | module Utils 7 | module Crypto 8 | extend self 9 | 10 | def generate_secret(length) 11 | length = length.to_i 12 | secret = SecureRandom.base64 length * 2 13 | secret = secret.gsub %r{[=_\-+/]}, '' 14 | secret.chars.sample(length).join 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/factories/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :user do 5 | sequence(:login) { |n| "user#{n}" } 6 | sequence(:firstname) { |n| "User#{n}" } 7 | sequence(:lastname) { |n| "Test#{n}" } 8 | sequence(:mail) { |n| "user#{n}@awesome.com" } 9 | language { 'fr' } 10 | hashed_password { '66eb4812e268747f89ec309178e2ea50410653fb' } 11 | salt { '5abd4e59ac0d483daf2f68d3b6544ff3' } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/global/delete_from_recycle_bin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Global 6 | class DeleteFromRecycleBin < GitoliteWrappers::Base 7 | def call 8 | RedmineGitHosting::RecycleBin.delete_content object_id 9 | RedmineGitHosting.logger.info 'delete_from_recycle_bin : done !' 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/use_cases/repository_mirrors/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RepositoryMirrors 4 | class Base 5 | attr_reader :mirror, :repository 6 | 7 | def initialize(mirror) 8 | @mirror = mirror 9 | @repository = mirror.repository 10 | end 11 | 12 | class << self 13 | def call(mirror) 14 | new(mirror).call 15 | end 16 | end 17 | 18 | def call 19 | raise NotImplementedError 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/concerns/gitolitable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolitable 4 | extend ActiveSupport::Concern 5 | include Gitolitable::Authorizations 6 | include Gitolitable::Cache 7 | include Gitolitable::Config 8 | include Gitolitable::Features 9 | include Gitolitable::Notifications 10 | include Gitolitable::Paths 11 | include Gitolitable::Permissions 12 | include Gitolitable::Urls 13 | include Gitolitable::Users 14 | include Gitolitable::Validations 15 | end 16 | -------------------------------------------------------------------------------- /app/views/repositories/_edit_top.html.slim: -------------------------------------------------------------------------------- 1 | - if @repository.is_a? Repository::Xitolite 2 | = render partial: 'common/git_hosting_js_headers' 3 | 4 | .git_hosting.box 5 | h3 6 | = l :label_repository_access_url 7 | - unless @repository.git_annex_enabled? 8 | ' 9 | = link_to l(:label_sort_urls), 10 | sort_urls_repository_git_extras_path(@repository), 11 | remote: true 12 | 13 | = render 'common/git_urls', repository: @repository 14 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/_form.html.slim: -------------------------------------------------------------------------------- 1 | .flash-messages = error_messages_for 'git_config_key' 2 | 3 | .box 4 | p = f.select :type, 5 | options_for_select(git_config_key_options, @git_config_key.type), 6 | { required: @git_config_key.new_record?, prompt: 'Select a key type' }, 7 | { disabled: !@git_config_key.new_record? } 8 | p = f.text_field :key, required: true, size: 65, label: :label_key 9 | p = f.text_field :value, required: true, size: 65 10 | -------------------------------------------------------------------------------- /spec/models/repository_git_config_key_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../spec_helper" 4 | 5 | describe RepositoryGitConfigKey do 6 | let(:git_config_key) { build :repository_git_config_key_base } 7 | 8 | subject { git_config_key } 9 | 10 | ## Relations 11 | it { should belong_to(:repository) } 12 | 13 | ## Validations 14 | it { should validate_presence_of(:repository_id) } 15 | it { should validate_presence_of(:value) } 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20120710204007_add_repository_mirror_fields.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddRepositoryMirrorFields < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :repository_mirrors, :push_mode, :integer, default: 0 6 | add_column :repository_mirrors, :include_all_branches, :boolean, default: false 7 | add_column :repository_mirrors, :include_all_tags, :boolean, default: false 8 | add_column :repository_mirrors, :explicit_refspec, :string, default: '' 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /contrib/github/database-postgres.yml: -------------------------------------------------------------------------------- 1 | production: 2 | adapter: postgresql 3 | host: localhost 4 | database: redmine 5 | username: postgres 6 | password: postgres 7 | encoding: utf8 8 | 9 | development: 10 | adapter: postgresql 11 | host: localhost 12 | database: redmine 13 | username: postgres 14 | password: postgres 15 | encoding: utf8 16 | 17 | test: 18 | adapter: postgresql 19 | host: localhost 20 | database: redmine 21 | username: postgres 22 | password: postgres 23 | encoding: utf8 24 | -------------------------------------------------------------------------------- /app/views/repository_git_extras/_sort_urls_modal.html.slim: -------------------------------------------------------------------------------- 1 | ul#sortable-url.list-unstyled data-remote="true" data-update-url="#{sort_urls_repository_git_extras_path @repository}" 2 | - @repository.available_urls_sorted.each_key do |url_type| 3 | li.url_type.draggable id="repository_git_extra_#{url_type}" 4 | = icon_for_url_type url_type 5 | ' 6 | span 7 | = label_for_url_type url_type 8 | 9 | javascript: 10 | $(document).ready(function() { setSortableElement('#sortable-url', '#sortable-url'); }); 11 | -------------------------------------------------------------------------------- /db/migrate/20130909195929_rename_table_deployment_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RenameTableDeploymentCredentials < ActiveRecord::Migration[4.2] 4 | def up 5 | remove_index :deployment_credentials, :gitolite_public_key_id 6 | rename_table :deployment_credentials, :repository_deployment_credentials 7 | end 8 | 9 | def down 10 | rename_table :repository_deployment_credentials, :deployment_credentials 11 | add_index :deployment_credentials, :gitolite_public_key_id 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/issue_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Patches 5 | module IssuePatch 6 | def self.prepended(base) 7 | base.class_eval do 8 | has_one :github_issue, foreign_key: 'issue_id', class_name: 'GithubIssue', dependent: :destroy 9 | end 10 | end 11 | end 12 | end 13 | end 14 | 15 | Issue.prepend RedmineGitHosting::Patches::IssuePatch unless Issue.included_modules.include? RedmineGitHosting::Patches::IssuePatch 16 | -------------------------------------------------------------------------------- /app/helpers/repository_post_receive_urls_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RepositoryPostReceiveUrlsHelper 4 | # Post-receive Mode 5 | def post_receive_mode(prurl) 6 | label = [] 7 | if prurl.github_mode? 8 | label << l(:label_github_post) 9 | label << "(#{l :label_split_payloads})" if prurl.split_payloads? 10 | elsif prurl.mode == :post 11 | label << l(:label_empty_post) 12 | else 13 | label << l(:label_empty_get) 14 | end 15 | label.join ' ' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/_new_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_mirror_add 2 | 3 | = labelled_form_for :repository_mirror, @mirror, 4 | url: repository_mirrors_path(@repository), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :post, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_add) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /app/helpers/gitolite_public_keys_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GitolitePublicKeysHelper 4 | def keylabel(key) 5 | key.user == User.current ? key.title&.to_s : "#{key.user.login}@#{key.title}" 6 | end 7 | 8 | def can_create_deployment_keys_for_some_project(theuser = User.current) 9 | return true if theuser.admin? 10 | 11 | theuser.projects_by_role.each_key do |role| 12 | return true if role.allowed_to? :create_repository_deployment_credentials 13 | end 14 | false 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/repository_protected_branches/index.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :repository_protected_branches, api_meta(total_count: @repository_protected_branches.count) do 2 | @repository_protected_branches.each do |protected_branch| 3 | api.protected_branch do 4 | api.id protected_branch.id 5 | api.path protected_branch.path 6 | api.permissions protected_branch.permissions 7 | api.user_list protected_branch.user_list 8 | api.position protected_branch.position 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/models/setting_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../spec_helper" 4 | 5 | describe Setting do 6 | before do 7 | RedmineGitHosting::Config.reload_from_file! 8 | @settings = Setting.plugin_redmine_git_hosting 9 | @default_settings = Redmine::Plugin.find('redmine_git_hosting').settings[:default] 10 | end 11 | 12 | subject { @settings } 13 | 14 | it { should be_an_instance_of(Hash) } 15 | 16 | # it { expect(@settings).to eq @default_settings } 17 | end 18 | -------------------------------------------------------------------------------- /app/overrides/repositories/navigation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Deface::Override.new virtual_path: 'repositories/_navigation', 4 | name: 'show-repositories-hook-navigation', 5 | insert_before: 'erb[loud]:contains("label_statistics")', 6 | original: '88f120e99075ba3246901c6e970ca671d7166855', 7 | text: '<%= call_hook(:view_repositories_navigation, repository: @repository) %>' 8 | 9 | module Repositories 10 | module Navigation 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/_edit_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_mirror_edit 2 | 3 | = labelled_form_for :repository_mirror, @mirror, 4 | url: repository_mirror_path(@repository, @mirror), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :put, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_save) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /app/use_cases/projects/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Projects 4 | class Base 5 | include RedmineGitHosting::GitoliteAccessor::Methods 6 | 7 | attr_reader :project, :options 8 | 9 | def initialize(project, opts = nil) 10 | @project = project 11 | @options = opts 12 | end 13 | 14 | class << self 15 | def call(project, opts = nil) 16 | new(project, opts).call 17 | end 18 | end 19 | 20 | def call 21 | raise NotImplementedError 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/factories/protected_branches_member.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :protected_branch_user_member, class: 'ProtectedBranchesMember' do 5 | association :protected_branch, factory: :repository_protected_branche 6 | association :principal, factory: :user 7 | end 8 | 9 | factory :protected_branch_group_member, class: 'ProtectedBranchesMember' do 10 | association :protected_branch, factory: :repository_protected_branche 11 | association :principal, factory: :group 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20150705180400_rename_git_config_keys_index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RenameGitConfigKeysIndex < ActiveRecord::Migration[4.2] 4 | def up 5 | remove_index :repository_git_config_keys, %i[key repository_id] 6 | add_index :repository_git_config_keys, %i[key type repository_id], unique: true, name: :unique_key_name 7 | end 8 | 9 | def down 10 | remove_index :repository_git_config_keys, name: :unique_key_name 11 | add_index :repository_git_config_keys, %i[key repository_id], unique: true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20140621004200_create_repository_protected_branches.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateRepositoryProtectedBranches < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :repository_protected_branches do |t| 6 | t.column :repository_id, :integer 7 | t.column :path, :string 8 | t.column :permissions, :string 9 | t.column :user_list, :text 10 | t.column :position, :integer 11 | end 12 | 13 | add_index :repository_protected_branches, :repository_id 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/file_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'logger' 4 | 5 | module RedmineGitHosting 6 | class FileLogger < ::Logger 7 | LOG_LEVELS = %w[debug info warn error].freeze 8 | 9 | def self.init_logs!(appname, logfile, loglevel) 10 | logger = new logfile 11 | logger.progname = appname 12 | logger.level = loglevel 13 | logger.formatter = proc do |severity, time, _progname, msg| 14 | "#{time} [#{severity}] #{msg}\n" 15 | end 16 | logger 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/_new_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_git_config_keys 2 | 3 | = labelled_form_for :repository_git_config_key, @git_config_key, 4 | url: repository_git_config_keys_path(@repository), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :post, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_add) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /app/views/settings/redmine_git_hosting/_gitolite_rescue.html.slim: -------------------------------------------------------------------------------- 1 | p 2 | = label_tag 'settings[rescue][resync_all_projects]', l(:label_resync_all_projects) 3 | = check_box_tag 'settings[rescue][resync_all_projects]', 'true', false 4 | 5 | p 6 | = label_tag 'settings[rescue][resync_all_ssh_keys]', l(:label_resync_all_ssh_keys) 7 | = check_box_tag 'settings[rescue][resync_all_ssh_keys]', 'true', false 8 | 9 | p 10 | = label_tag 'settings[rescue][flush_gitolite_cache]', l(:label_flush_cache) 11 | = check_box_tag 'settings[rescue][flush_gitolite_cache]', 'true', false 12 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/users/resync_ssh_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Users 6 | class ResyncSshKeys < GitoliteWrappers::Base 7 | def call 8 | admin.transaction do 9 | GitolitePublicKey.all.each do |ssh_key| 10 | create_gitolite_key ssh_key 11 | gitolite_admin_repo_commit "Add SSH key : #{ssh_key.identifier}" 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/repository_post_receive_urls/_new_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_post_receive_url_add 2 | 3 | = labelled_form_for :repository_post_receive_url, @post_receive_url, 4 | url: repository_post_receive_urls_path(@repository), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :post, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_add) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/_edit_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_git_config_keys 2 | 3 | = labelled_form_for :repository_git_config_key, @git_config_key, 4 | url: repository_git_config_key_path(@repository, @git_config_key), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :put, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_save) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /app/views/repository_protected_branches/_new_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_protected_branch_add 2 | 3 | = labelled_form_for :repository_protected_branche, @protected_branch, 4 | url: repository_protected_branches_path(@repository), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :post, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_add) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /assets/images/button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/journal_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'journal' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module JournalPatch 8 | def self.prepended(base) 9 | base.class_eval do 10 | has_one :github_comment, foreign_key: 'journal_id', class_name: 'GithubComment', dependent: :destroy 11 | end 12 | end 13 | end 14 | end 15 | end 16 | 17 | Journal.prepend RedmineGitHosting::Patches::JournalPatch unless Journal.included_modules.include? RedmineGitHosting::Patches::JournalPatch 18 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/repository_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'repository' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module RepositoryPatch 8 | # This is the (possibly non-unique) basename for the Gitolite repository 9 | def redmine_name 10 | identifier.presence || project.identifier 11 | end 12 | end 13 | end 14 | end 15 | 16 | unless Repository.included_modules.include? RedmineGitHosting::Patches::RepositoryPatch 17 | Repository.prepend RedmineGitHosting::Patches::RepositoryPatch 18 | end 19 | -------------------------------------------------------------------------------- /assets/images/button_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/stylesheets/plugin.css: -------------------------------------------------------------------------------- 1 | /* 2 | PLUGIN ICON 3 | */ 4 | 5 | /* stylelint-disable font-family-no-missing-generic-family-keyword */ 6 | 7 | #admin-menu a.redmine-git-hosting::before { 8 | font-family: Font Awesome\ 5 Brands; 9 | font-size: 1.4em; 10 | content: "\f841"; /* fab fa-git-alt */ 11 | padding-right: 4px; 12 | } 13 | 14 | #admin-menu a.redmine-git-hosting { 15 | padding-left: 0; 16 | } 17 | 18 | .authors-list { 19 | columns: 2; 20 | -webkit-columns: 2; 21 | -moz-columns: 2; 22 | } 23 | 24 | .icon-git { padding-right: 10px; } 25 | .icon-git-disabled { opacity: 0.5; } 26 | -------------------------------------------------------------------------------- /app/controllers/archived_repositories_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ArchivedRepositoriesController < RepositoriesController 4 | skip_before_action :authorize 5 | skip_before_action :find_project_repository, only: :index 6 | 7 | before_action :can_view_archived_projects 8 | 9 | def index 10 | @archived_projects = Project.where(status: Project::STATUS_ARCHIVED) 11 | .includes(:repositories) 12 | end 13 | 14 | private 15 | 16 | def can_view_archived_projects 17 | render_403 unless User.current.admin? 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/index.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :repository_mirrors, api_meta(total_count: @repository_mirrors.count) do 2 | @repository_mirrors.each do |mirror| 3 | api.mirror do 4 | api.id mirror.id 5 | api.url mirror.url 6 | api.active mirror.active 7 | api.push_mode mirror.push_mode_to_s 8 | api.include_all_branches mirror.include_all_branches 9 | api.include_all_tags mirror.include_all_tags 10 | api.explicit_refspec mirror.explicit_refspec 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /assets/images/button_focus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /db/migrate/20110726000000_extend_changesets_notified_cia.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ExtendChangesetsNotifiedCia < ActiveRecord::Migration[4.2] 4 | def up 5 | add_column :changesets, :notified_cia, :integer, default: 0 6 | end 7 | 8 | def down 9 | # Deal with fact that one of next migrations doesn't restore :notified_cia 10 | remove_column :changesets, :notified_cia if column_exists? :changesets, :notified_cia 11 | end 12 | 13 | def column_exists?(table_name, column_name) 14 | columns(table_name).any? { |c| c.name == column_name.to_s } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/repository_post_receive_urls/_edit_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_post_receive_url_edit 2 | 3 | = labelled_form_for :repository_post_receive_url, @post_receive_url, 4 | url: repository_post_receive_url_path(@repository, @post_receive_url), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :put, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_save) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /app/views/repository_protected_branches/_edit_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_protected_branch_edit 2 | 3 | = labelled_form_for :repository_protected_branche, @protected_branch, 4 | url: repository_protected_branch_path(@repository, @protected_branch), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :put, class: 'tabular', remote: true } do |f| 7 | 8 | = render partial: 'form', locals: { f: f } 9 | .buttons 10 | = submit_tag l(:button_save) 11 | ' 12 | = link_to_function l(:button_cancel), 'hideModal(this);' 13 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('../spec_helper', __dir__) 4 | 5 | describe User do 6 | let(:user) { build :user } 7 | 8 | it { is_expected.to have_many(:protected_branches_members).dependent(:destroy) } 9 | it { is_expected.to have_many(:protected_branches).through(:protected_branches_members) } 10 | 11 | describe '#gitolite_identifier' do 12 | it 'return the gitolite_identifier' do 13 | user = build :user, login: 'adam.30', id: 12 14 | expect(user.gitolite_identifier).to eq 'redmine_adam_30_12' 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/users/regenerate_ssh_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Users 6 | class RegenerateSshKeys < GitoliteWrappers::Base 7 | def call 8 | GitolitePublicKey.all.each do |ssh_key| 9 | gitolite_accessor.destroy_ssh_key ssh_key, bypass_sidekiq: true 10 | ssh_key.reset_identifiers skip_auto_increment: true 11 | gitolite_accessor.create_ssh_key ssh_key, bypass_sidekiq: true 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/settings/redmine_git_hosting/_install_hooks_result.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_install_hook_results 2 | 3 | table.list.git-results 4 | tr 5 | td = l :label_gitolite_hooks_installed 6 | td = render_gitolite_params_status @gitolite_checks[:hook_files] 7 | tr 8 | td = l :label_gitolite_hooks_params_installed 9 | td = render_gitolite_params_status @gitolite_checks[:global_params] 10 | tr 11 | td = l :label_gitolite_mailer_params_installed 12 | td = render_gitolite_params_status @gitolite_checks[:mailer_params] 13 | 14 | .buttons 15 | = link_to_function l(:button_cancel), 'hideModal(this);' 16 | -------------------------------------------------------------------------------- /db/migrate/20150823030000_create_protected_branches_members.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateProtectedBranchesMembers < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :protected_branches_members do |t| 6 | t.column :protected_branch_id, :integer 7 | t.column :principal_id, :integer 8 | t.column :inherited_by, :integer 9 | end 10 | 11 | add_index :protected_branches_members, 12 | %i[protected_branch_id principal_id inherited_by], 13 | unique: true, 14 | name: 'unique_protected_branch_member' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_handlers/ssh_keys/delete_ssh_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteHandlers 5 | module SshKeys 6 | class DeleteSshKey < Base 7 | def call 8 | repo_key = find_gitolite_key key[:owner], key[:location] 9 | 10 | # Remove it if found 11 | if repo_key 12 | admin.rm_key repo_key 13 | else 14 | logger.info "#{context} : SSH key '#{key[:owner]}@#{key[:location]}' does not exits in Gitolite, exit !" 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/forms/base_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module BaseForm 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | include ActiveModel::Validations 8 | include ActiveModel::Validations::Callbacks 9 | include ActiveModel::Conversion 10 | extend ActiveModel::Naming 11 | end 12 | 13 | def persisted? 14 | false 15 | end 16 | 17 | def submit(attributes = {}) 18 | attributes.each do |name, value| 19 | send "#{name}=", value 20 | end 21 | if valid? 22 | valid_form_submitted if respond_to? :valid_form_submitted 23 | true 24 | else 25 | false 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /db/migrate/20140305083200_add_default_branch_to_repository_git_extra.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddDefaultBranchToRepositoryGitExtra < ActiveRecord::Migration[4.2] 4 | def up 5 | add_column :repository_git_extras, :default_branch, :string, after: :git_notify 6 | 7 | RepositoryGitExtra.reset_column_information 8 | RepositoryGitExtra.all.each do |extra| 9 | extra.update_attribute :default_branch, 'master' 10 | end 11 | 12 | change_column :repository_git_extras, :default_branch, :string, null: false 13 | end 14 | 15 | def down 16 | remove_column :repository_git_extras, :default_branch 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/users/delete_ssh_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Users 6 | class DeleteSshKey < GitoliteWrappers::Base 7 | def call 8 | logger.info "Deleting SSH key '#{ssh_key[:title]}'" 9 | admin.transaction do 10 | delete_gitolite_key ssh_key 11 | gitolite_admin_repo_commit "Delete SSH key : #{ssh_key[:title]}" 12 | end 13 | end 14 | 15 | def ssh_key 16 | @ssh_key ||= object_id.symbolize_keys 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/repository_post_receive_urls/index.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :repository_post_receive_urls, api_meta(total_count: @repository_post_receive_urls.count) do 2 | @repository_post_receive_urls.each do |post_receive_url| 3 | api.post_receive_url do 4 | api.id post_receive_url.id 5 | api.url post_receive_url.url 6 | api.mode post_receive_url.mode.to_s 7 | api.active post_receive_url.active 8 | api.use_triggers post_receive_url.use_triggers 9 | api.triggers post_receive_url.triggers 10 | api.split_payloads post_receive_url.split_payloads 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/git_cache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GitCache < ActiveRecord::Base 4 | include Redmine::SafeAttributes 5 | 6 | CACHE_ADAPTERS = [%w[Database database], 7 | %w[Memcached memcached], 8 | %w[Redis redis]].freeze 9 | 10 | ## Attributes 11 | safe_attributes 'repo_identifier', 'command', 'command_output' 12 | 13 | ## Validations 14 | validates :repo_identifier, presence: true 15 | validates :command, presence: true 16 | validates :command_output, presence: true 17 | 18 | class << self 19 | def adapters 20 | CACHE_ADAPTERS.map(&:last) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/users/add_ssh_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Users 6 | class AddSshKey < GitoliteWrappers::Base 7 | def call 8 | logger.info "Adding SSH key '#{ssh_key.identifier}'" 9 | admin.transaction do 10 | create_gitolite_key ssh_key 11 | gitolite_admin_repo_commit "Add SSH key : #{ssh_key.identifier}" 12 | end 13 | end 14 | 15 | def ssh_key 16 | @ssh_key ||= GitolitePublicKey.find_by id: object_id 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /contrib/hooks/post-receive/lib/git_hosting_hook_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GitHosting 4 | class HookLogger 5 | attr_reader :loglevel 6 | 7 | def initialize(loglevel: 'info') 8 | @loglevel = loglevel 9 | end 10 | 11 | def debug(message) 12 | write message if loglevel == 'debug' 13 | end 14 | 15 | def info(message) 16 | write message 17 | end 18 | 19 | def error(message) 20 | write message 21 | end 22 | 23 | private 24 | 25 | def write(message) 26 | $stdout.sync = true 27 | $stdout.puts "\e[1G#{message}" 28 | $stdout.flush 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/views/repositories/_show_top.html.slim: -------------------------------------------------------------------------------- 1 | - if @repository.is_a? Repository::Xitolite 2 | - content_for :header_tags do 3 | = stylesheet_link_tag 'application', plugin: 'redmine_git_hosting' 4 | = stylesheet_link_tag 'markdown', plugin: 'redmine_git_hosting' 5 | 6 | .git_hosting_urls.box 7 | .container 8 | .row 9 | .col-md-8 style='vertical-align: middle;' 10 | = render 'common/git_urls', repository: @repository 11 | .col-md-4 style='text-align: right; vertical-align: middle;' 12 | - if RedmineGitHosting::Config.download_revision_enabled? 13 | = render 'repositories/download_revision', repository: @repository 14 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/markdown_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'html/pipeline' 4 | require 'task_list/filter' 5 | require 'task_list/railtie' 6 | 7 | module RedmineGitHosting 8 | module MarkdownRenderer 9 | extend self 10 | 11 | def to_html(markdown) 12 | pipeline.call(markdown)[:output].to_s 13 | end 14 | 15 | private 16 | 17 | def pipeline 18 | HTML::Pipeline.new filters 19 | end 20 | 21 | def filters 22 | [RedmineGitHosting::RedcarpetFilter, 23 | TaskList::Filter, 24 | HTML::Pipeline::AutolinkFilter, 25 | HTML::Pipeline::TableOfContentsFilter] 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/repositories_helper_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'repositories_helper' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module RepositoriesHelperPatch 8 | def xitolite_field_tags(form, repository) 9 | encoding_field(form, repository) + 10 | create_readme_field(form, repository) + 11 | enable_git_annex_field(form, repository) 12 | end 13 | end 14 | end 15 | end 16 | 17 | unless RepositoriesHelper.included_modules.include? RedmineGitHosting::Patches::RepositoriesHelperPatch 18 | RepositoriesHelper.prepend RedmineGitHosting::Patches::RepositoriesHelperPatch 19 | end 20 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../../../spec/spec_helper" 4 | 5 | HOME_BASE_DIR = RUBY_PLATFORM.include?('darwin') ? '/Users' : '/home' 6 | 7 | Shoulda::Matchers.configure do |config| 8 | config.integrate do |with| 9 | with.test_framework :rspec 10 | with.library :rails 11 | end 12 | end 13 | 14 | ## Configure RSpec 15 | RSpec.configure do |config| 16 | # Include our helpers from support directory 17 | config.include GlobalHelpers 18 | 19 | config.before :suite do 20 | RedmineGitHosting::Config.reload_from_file! 21 | Setting.enabled_scm = %w[Git Subversion Xitolite] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/workers/githosting_shell_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GithostingShellWorker 4 | include Sidekiq::Worker 5 | 6 | sidekiq_options queue: :redmine_git_hosting, retry: false 7 | 8 | def self.maybe_do(command, object, options) 9 | args = [command.to_s, object, options] 10 | Sidekiq::Queue.new(:redmine_git_hosting).each do |job| 11 | return nil if job.args == args 12 | end 13 | 14 | perform_async command, object, options 15 | end 16 | 17 | def perform(command, object, options) 18 | logger.info "#{command} | #{object} | #{options}" 19 | RedmineGitHosting::GitoliteWrapper.resync_gitolite command, object, **options 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /contrib/hooks/post-receive/redmine_gitolite.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # frozen_string_literal: true 4 | 5 | # This file was placed here by Redmine Git Hosting. It makes sure that your pushed commits 6 | # will be processed properly. 7 | 8 | refs = ARGF.read 9 | repo_path = Dir.pwd 10 | 11 | require_relative 'lib/git_hosting/http_helper' 12 | require_relative 'lib/git_hosting/hook_logger' 13 | require_relative 'lib/git_hosting/config' 14 | require_relative 'lib/git_hosting/post_receive' 15 | require_relative 'lib/git_hosting/custom_hook' 16 | 17 | if GitHosting::PostReceive.new(repo_path, refs).exec && GitHosting::CustomHook.new(repo_path, refs).exec 18 | exit 0 19 | else 20 | exit 1 21 | end 22 | -------------------------------------------------------------------------------- /app/views/settings/_redmine_git_hosting.html.slim: -------------------------------------------------------------------------------- 1 | = render partial: 'common/git_hosting_js_headers' 2 | - content_for :header_tags do 3 | = additionals_library_load :select2 4 | 5 | span 6 | = l :label_need_help 7 | ' : 8 | = link_to_external l(:label_redmine_git_hosting_wiki), 9 | RedmineGitHosting::Config::GITHUB_WIKI 10 | 11 | br 12 | 13 | span 14 | = l :label_open_issue 15 | ' : 16 | = link_to_external l(:label_redmine_git_hosting_issue), 17 | RedmineGitHosting::Config::GITHUB_ISSUE 18 | 19 | br 20 | br 21 | 22 | = render_tabs gitolite_plugin_settings_tabs 23 | 24 | javascript: 25 | $(document).ready(function() { setSettingsActiveTab(); }); 26 | -------------------------------------------------------------------------------- /app/use_cases/repositories/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Repositories 4 | class Base 5 | include RedmineGitHosting::GitoliteAccessor::Methods 6 | 7 | attr_reader :repository, :options, :project 8 | 9 | def initialize(repository, opts = nil) 10 | @repository = repository 11 | @options = opts 12 | @project = repository.project 13 | end 14 | 15 | class << self 16 | def call(repository, opts = nil) 17 | new(repository, opts).call 18 | end 19 | end 20 | 21 | def call 22 | raise NotImplementedError 23 | end 24 | 25 | private 26 | 27 | def logger 28 | RedmineGitHosting.logger 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/config/gitolite_cache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Config 5 | module GitoliteCache 6 | extend self 7 | 8 | def gitolite_cache_max_time 9 | get_setting(:gitolite_cache_max_time).to_i 10 | end 11 | 12 | def gitolite_cache_max_elements 13 | get_setting(:gitolite_cache_max_elements).to_i 14 | end 15 | 16 | def gitolite_cache_max_size 17 | get_setting(:gitolite_cache_max_size).to_i * 1024 * 1024 18 | end 19 | 20 | def gitolite_cache_adapter 21 | get_setting :gitolite_cache_adapter 22 | end 23 | end 24 | 25 | extend Config::GitoliteCache 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_params/base_param.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteParams 5 | module BaseParam 6 | private 7 | 8 | # Return a hash with global config parameters. 9 | # 10 | def get_git_config_params(namespace) 11 | RedmineGitHosting::Commands.sudo_get_git_global_params namespace 12 | end 13 | 14 | def set_git_config_param(namespace, key, value) 15 | RedmineGitHosting::Commands.sudo_set_git_global_param namespace, key, value 16 | end 17 | 18 | def unset_git_config_param(key) 19 | RedmineGitHosting::Commands.sudo_unset_git_global_param key 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/factories/gitolite_public_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :gitolite_public_key do 5 | sequence(:title) { |n| "test-key#{n}" } 6 | key do 7 | 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDpqFJzsx3wTi3t3X/eOizU6rdtNQoqg5uSjL89F+Ojjm2/sah3ouzx+3E461FDYaoJL58Qs9eRhL+ev0BY7khYXp' \ 8 | 'h8nIVDzNEjhLqjevX+YhpaW9Ll7V807CwAyvMNm08aup/NrrlI/jO+At348/ivJrfO7ClcPhq4+Id9RZfvbrKaitGOURD7q6Bd7xjUjELUN8wmYxu5zvx/2n/5woVd' \ 9 | 'BUMXamTPxOY5y6DxTNJ+EYzrCr+bNb7459rWUvBHUQGI2fXDGmFpGiv6ShKRhRtwob1JHI8QC9OtxonrIUesa2dW6RFneUaM7tfRfffC704Uo7yuSswb7YK+p1A9QI' \ 10 | 't5 nicolas@tchoum' 11 | end 12 | key_type { 0 } 13 | association :user 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/use_cases/projects/execute_hooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Projects 4 | class ExecuteHooks 5 | attr_reader :project, :hook_type, :payloads 6 | 7 | def initialize(project, hook_type, payloads = nil) 8 | @project = project 9 | @hook_type = hook_type 10 | @payloads = payloads 11 | end 12 | 13 | class << self 14 | def call(project, hook_type, payloads = nil) 15 | new(project, hook_type, payloads).call 16 | end 17 | end 18 | 19 | def call 20 | send "execute_#{hook_type}_hook" 21 | end 22 | 23 | private 24 | 25 | def execute_github_hook 26 | RedmineHooks::GithubIssuesSync.call project, payloads 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/sys_controller_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'sys_controller' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module SysControllerPatch 8 | include RedmineGitHosting::GitoliteAccessor::Methods 9 | 10 | def fetch_changesets 11 | # Flush GitCache 12 | gitolite_accessor.flush_git_cache 13 | 14 | super 15 | 16 | # Purge RecycleBin 17 | gitolite_accessor.purge_recycle_bin 18 | end 19 | end 20 | end 21 | end 22 | 23 | unless SysController.included_modules.include? RedmineGitHosting::Patches::SysControllerPatch 24 | SysController.prepend RedmineGitHosting::Patches::SysControllerPatch 25 | end 26 | -------------------------------------------------------------------------------- /app/models/protected_branches_member.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ProtectedBranchesMember < ActiveRecord::Base 4 | include Redmine::SafeAttributes 5 | 6 | ## Attributes 7 | safe_attributes 'principal_id', 'inherited_by' 8 | 9 | ## Relations 10 | belongs_to :protected_branch, class_name: 'RepositoryProtectedBranche' 11 | belongs_to :principal 12 | 13 | ## Callbacks 14 | after_destroy :remove_dependent_objects 15 | 16 | private 17 | 18 | def remove_dependent_objects 19 | return unless principal.instance_of? Group 20 | 21 | principal.users.each do |user| 22 | member = self.class.find_by principal_id: user.id, inherited_by: principal.id 23 | member&.destroy! 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/services/permissions_builder/protected_branches.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PermissionsBuilder 4 | class ProtectedBranches < Base 5 | attr_reader :permissions 6 | 7 | def initialize(*args) 8 | super 9 | @permissions = [] 10 | end 11 | 12 | def build 13 | build_protected_branch_permissions 14 | permissions 15 | end 16 | 17 | def build_protected_branch_permissions 18 | repository.protected_branches.each do |branch| 19 | perms = {} 20 | perms[branch.permissions] = {} 21 | perms[branch.permissions][branch.path] = branch.allowed_users unless branch.allowed_users.empty? 22 | permissions.push perms 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/commands/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Commands 5 | module Base 6 | extend self 7 | 8 | # Wrapper to Open3.capture. 9 | # 10 | def capture(args = [], **opts) 11 | cmd = args.shift 12 | RedmineGitHosting::Utils::Exec.capture cmd, args, **opts 13 | end 14 | 15 | # Wrapper to Open3.capture. 16 | # 17 | def execute(args = [], **opts) 18 | cmd = args.shift 19 | RedmineGitHosting::Utils::Exec.execute cmd, args, **opts 20 | end 21 | 22 | private 23 | 24 | def logger 25 | RedmineGitHosting.logger 26 | end 27 | end 28 | 29 | extend Commands::Base 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/config/gitolite_storage.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Config 5 | module GitoliteStorage 6 | extend self 7 | 8 | def gitolite_global_storage_dir 9 | get_setting :gitolite_global_storage_dir 10 | end 11 | 12 | def gitolite_redmine_storage_dir 13 | get_setting :gitolite_redmine_storage_dir 14 | end 15 | 16 | def gitolite_recycle_bin_dir 17 | get_setting :gitolite_recycle_bin_dir 18 | end 19 | 20 | def recycle_bin_dir 21 | File.join gitolite_home_dir, gitolite_recycle_bin_dir 22 | rescue StandardError 23 | nil 24 | end 25 | end 26 | 27 | extend Config::GitoliteStorage 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/config/gitolite_notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Config 5 | module GitoliteNotifications 6 | extend self 7 | 8 | def gitolite_notify_global_prefix 9 | get_setting :gitolite_notify_global_prefix 10 | end 11 | 12 | def gitolite_notify_global_sender_address 13 | get_setting :gitolite_notify_global_sender_address 14 | end 15 | 16 | def gitolite_notify_global_include 17 | get_setting :gitolite_notify_global_include 18 | end 19 | 20 | def gitolite_notify_global_exclude 21 | get_setting :gitolite_notify_global_exclude 22 | end 23 | end 24 | 25 | extend Config::GitoliteNotifications 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_handlers/repositories/delete_repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteHandlers 5 | module Repositories 6 | class DeleteRepository < Base 7 | def call 8 | if configuration_exists? 9 | log_ok_and_continue 'delete it ...' 10 | 11 | # Delete Gitolite repository 12 | delete_repository_config 13 | else 14 | log_repo_not_exist 'exit !' 15 | end 16 | end 17 | 18 | def gitolite_repo_name 19 | repository[:repo_name] 20 | end 21 | 22 | def gitolite_repo_path 23 | repository[:repo_path] 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/permissions_builder/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PermissionsBuilder 4 | class Base 5 | attr_reader :repository, :gitolite_users, :old_permissions 6 | 7 | def initialize(repository, gitolite_users, old_permissions = {}) 8 | @repository = repository 9 | @gitolite_users = gitolite_users 10 | @old_permissions = old_permissions 11 | end 12 | 13 | class << self 14 | def build(repository, gitolite_users, old_permissions = {}) 15 | new(repository, gitolite_users, old_permissions).build 16 | end 17 | end 18 | 19 | def build 20 | raise NotImplementedError 21 | end 22 | 23 | private 24 | 25 | def no_users?(type) 26 | gitolite_users[type].blank? 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/views/repository_deployment_credentials/_edit_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_deployment_credential_edit 2 | 3 | = labelled_form_for :repository_deployment_credential, @credential, 4 | url: repository_deployment_credential_path(@repository, @credential), 5 | authenticity_token: form_authenticity_token, 6 | html: { method: :put, class: 'tabular', remote: true } do |f| 7 | 8 | .flash-messages = error_messages_for 'credential' 9 | 10 | .box 11 | p = f.select :perm, options_for_select(RepositoryDeploymentCredential::VALID_PERMS, @credential.perm), required: true 12 | p = f.check_box :active 13 | 14 | .buttons 15 | = submit_tag l :button_save 16 | ' 17 | = link_to_function l(:button_cancel), 'hideModal(this);' 18 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_handlers/repositories/update_repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteHandlers 5 | module Repositories 6 | class UpdateRepository < Base 7 | def call 8 | if configuration_exists? 9 | log_ok_and_continue 'update it ...' 10 | 11 | # Update Gitolite repository 12 | update_repository_config 13 | else 14 | log_repo_not_exist 'exit !' 15 | end 16 | end 17 | 18 | def gitolite_repo_name 19 | repository.gitolite_repository_name 20 | end 21 | 22 | def gitolite_repo_path 23 | repository.gitolite_repository_path 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/repositories/_git_hosting_sidebar.html.slim: -------------------------------------------------------------------------------- 1 | ul.repository.git 2 | - @repositories.sort.each do |repo| 3 | li class="#{'repository git' if repo.is_a? Repository::Xitolite}" 4 | = link_to_repository repo, @repository 5 | - if User.current.allowed_to? :manage_repository, @project 6 | ' 7 | = link_to "(#{l :label_settings})", edit_repository_path(repo) 8 | 9 | - if @repository.try(:watchers) && \ 10 | (User.current.allowed_to?(:add_repository_xitolite_watchers, @project) || \ 11 | (@repository.watchers.present? && User.current.allowed_to?(:view_repository_xitolite_watchers, @project))) 12 | 13 | #watchers 14 | = render 'watchers/watchers', watched: @repository 15 | 16 | javascript: 17 | $(document).ready(function() { $('#sidebar p').remove(); }); 18 | -------------------------------------------------------------------------------- /db/migrate/20140417004100_enforce_models_constraints.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EnforceModelsConstraints < ActiveRecord::Migration[4.2] 4 | def up 5 | change_column :git_caches, :command_output, :binary, limit: 16_777_216 6 | remove_column :repository_mirrors, :created_at 7 | remove_column :repository_mirrors, :updated_at 8 | remove_column :repository_post_receive_urls, :created_at 9 | remove_column :repository_post_receive_urls, :updated_at 10 | end 11 | 12 | def down 13 | add_column :repository_mirrors, :created_at, :datetime 14 | add_column :repository_mirrors, :updated_at, :datetime 15 | add_column :repository_post_receive_urls, :created_at, :datetime 16 | add_column :repository_post_receive_urls, :updated_at, :datetime 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/use_cases/projects/update.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Projects 4 | class Update < Base 5 | def call 6 | # Adjust daemon status 7 | disable_git_daemon_if_not_public 8 | resync 9 | end 10 | 11 | private 12 | 13 | def disable_git_daemon_if_not_public 14 | # Go through all gitolite repos and disable Git daemon if necessary 15 | project.gitolite_repos.each do |repository| 16 | repository.extra[:git_daemon] = false if repository.git_daemon_enabled? && !project.is_public 17 | # Save GitExtra in all cases to trigger urls order consistency checks 18 | repository.extra.save 19 | end 20 | end 21 | 22 | def resync 23 | gitolite_accessor.update_projects [project.id], **options 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/member_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'member' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module MemberPatch 8 | include RedmineGitHosting::GitoliteAccessor::Methods 9 | 10 | def self.prepended(base) 11 | base.class_eval do 12 | after_commit :update_project 13 | end 14 | end 15 | 16 | private 17 | 18 | def update_project 19 | gitolite_accessor.update_projects [project.id], 20 | message: "Membership changes on project '#{project}', update!" 21 | end 22 | end 23 | end 24 | end 25 | 26 | Member.prepend RedmineGitHosting::Patches::MemberPatch unless Member.included_modules.include? RedmineGitHosting::Patches::MemberPatch 27 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins/sweepers/base_sweeper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting::Plugins::Sweepers 4 | class BaseSweeper < RedmineGitHosting::Plugins::GitolitePlugin 5 | attr_reader :repository_data, :gitolite_repo_name, :gitolite_repo_path, :delete_repository, :git_cache_id 6 | 7 | def initialize(repository_data, _options = {}) 8 | @repository_data = repository_data 9 | @gitolite_repo_name = repository_data[:repo_name] 10 | @gitolite_repo_path = repository_data[:repo_path] 11 | @delete_repository = repository_data[:delete_repository] 12 | @git_cache_id = repository_data[:git_cache_id] 13 | end 14 | 15 | private 16 | 17 | def delete_repository? 18 | RedminePluginKit.true? delete_repository 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /.github/workflows/brakeman.yml: -------------------------------------------------------------------------------- 1 | name: Run Brakeman 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install package dependencies 16 | run: > 17 | sudo apt-get install --yes --quiet 18 | pandoc 19 | 20 | - name: Setup Gemfile 21 | run: | 22 | touch .enable_dev 23 | sed -i "3isource 'https://rubygems.org'" Gemfile 24 | 25 | - name: Setup Ruby 26 | uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: 3.1 29 | bundler-cache: true 30 | 31 | - name: Setup gems 32 | run: | 33 | bundle install --jobs 4 --retry 3 34 | 35 | - name: Run Brakeman 36 | run: | 37 | bundle exec brakeman -5 38 | -------------------------------------------------------------------------------- /app/services/redmine_hooks/fetch_changesets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineHooks 4 | class FetchChangesets < Base 5 | def call 6 | repository.empty_cache! 7 | execute_hook do |out| 8 | out << fetch_changesets 9 | end 10 | end 11 | 12 | def repository 13 | object 14 | end 15 | 16 | def start_message 17 | "Fetching changesets for '#{repository.redmine_name}' repository" 18 | end 19 | 20 | private 21 | 22 | def fetch_changesets 23 | repository.fetch_changesets 24 | log_hook_succeeded 25 | success_message 26 | rescue ::Redmine::Scm::Adapters::CommandFailed => e 27 | log_hook_failed 28 | logger.error "Error during fetching changesets : #{e.message}" 29 | failure_message 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/views/repository_git_extras/move.html.slim: -------------------------------------------------------------------------------- 1 | h2 = l :label_move_repository, repo_name: @repository.gitolite_repository_name 2 | 3 | .box 4 | = labelled_form_for :repository_mover, @move_repository_form, 5 | url: move_repository_git_extras_path(@repository), 6 | authenticity_token: form_authenticity_token, 7 | html: { method: :post, class: 'tabular', data: { confirm: l(:text_are_you_sure) } } do |f| 8 | 9 | .flash-messages = error_messages_for 'move_repository_form' 10 | p 11 | = f.select :project_id, 12 | render_options_for_move_repo_select_box(@project), 13 | required: true 14 | p 15 | = f.submit l(:button_move) 16 | ' 17 | = link_to l(:button_cancel), settings_project_path(@project, tab: 'repositories') 18 | -------------------------------------------------------------------------------- /app/use_cases/projects/create_repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Projects 4 | class CreateRepository < Base 5 | def call 6 | create_project_repository 7 | end 8 | 9 | private 10 | 11 | def create_project_repository 12 | # Create new repository 13 | repository = Repository.factory 'Xitolite' 14 | repository.is_default = true 15 | repository.extra_info = {} 16 | repository.extra_info['extra_report_last_commit'] = '1' 17 | 18 | # Save it to database 19 | project.repositories << repository 20 | 21 | # Create it in Gitolite 22 | Repositories::Create.call repository, creation_options 23 | end 24 | 25 | def creation_options 26 | { create_readme_file: RedmineGitHosting::Config.init_repositories_on_create? } 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/setting_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'setting' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module SettingPatch 8 | def self.prepended(base) 9 | class << base 10 | prepend ClassMethods 11 | end 12 | end 13 | 14 | module ClassMethods 15 | def check_cache 16 | settings_updated_on = Setting.maximum :updated_on 17 | if settings_updated_on && @cached_cleared_on <= settings_updated_on 18 | clear_cache 19 | RedmineGitHosting::Config.check_cache 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | 27 | Setting.prepend RedmineGitHosting::Patches::SettingPatch unless Setting.included_modules.include? RedmineGitHosting::Patches::SettingPatch 28 | -------------------------------------------------------------------------------- /db/migrate/20151127024000_shrink_git_notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ShrinkGitNotifications < ActiveRecord::Migration[4.2] 4 | def up 5 | add_column :repository_git_extras, :notification_sender, :string 6 | add_column :repository_git_extras, :notification_prefix, :string 7 | drop_table :repository_git_notifications 8 | end 9 | 10 | def down 11 | remove_column :repository_git_extras, :notification_sender 12 | remove_column :repository_git_extras, :notification_prefix 13 | 14 | create_table :repository_git_notifications do |t| 15 | t.integer :repository_id 16 | t.text :include_list 17 | t.text :exclude_list 18 | t.string :prefix 19 | t.string :sender_address 20 | end 21 | 22 | add_index :repository_git_notifications, :repository_id, unique: true 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/config/mirroring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Config 5 | module Mirroring 6 | extend self 7 | 8 | def mirroring_public_key 9 | @mirroring_public_key ||= MirrorKeysInstaller.mirroring_public_key gitolite_ssh_public_key 10 | end 11 | 12 | def mirroring_keys_installed? 13 | @mirroring_keys_installed ||= MirrorKeysInstaller.new(gitolite_home_dir, 14 | gitolite_ssh_public_key, 15 | gitolite_ssh_private_key).installed? 16 | end 17 | 18 | def gitolite_mirroring_script 19 | File.join gitolite_home_dir, '.ssh', 'run_gitolite_admin_ssh' 20 | end 21 | end 22 | 23 | extend Config::Mirroring 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/models/repository_git_config_key/option_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../../spec_helper" 4 | 5 | describe RepositoryGitConfigKey::Option do 6 | before :each do 7 | @git_config_key = build :repository_git_option_key 8 | end 9 | 10 | subject { @git_config_key } 11 | 12 | ## Validations 13 | it { should be_valid } 14 | it { should validate_presence_of(:key) } 15 | it { should validate_uniqueness_of(:key).case_insensitive.scoped_to(:type, :repository_id) } 16 | it { should allow_value('hookfoo', 'hookfoo.foo', 'hookfoo.foo.bar').for(:key) } 17 | 18 | context 'when key is updated' do 19 | before do 20 | @git_config_key.save 21 | @git_config_key.key = 'hookbar.foo' 22 | @git_config_key.save 23 | end 24 | 25 | it { should be_valid } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/overrides/repositories/show.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Deface::Override.new virtual_path: 'repositories/show', 4 | name: 'show-repositories-hook-bottom', 5 | insert_before: 'erb[silent]:contains("other_formats_links")', 6 | original: 'f302d110cd10675a0a952f5f3e1ecfe57ebd38be', 7 | text: '<%= call_hook(:view_repositories_show_bottom, repository: @repository) %>' 8 | 9 | Deface::Override.new virtual_path: 'repositories/show', 10 | name: 'show-repositories-hook-sidebar', 11 | insert_before: 'erb[silent]:contains("html_title")', 12 | original: '2a0a09659d76066b896016c72527d479c69463ec', 13 | partial: 'hooks/show_repositories_sidebar' 14 | 15 | module Repositories 16 | module Show 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_handlers/ssh_keys/add_ssh_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteHandlers 5 | module SshKeys 6 | class AddSshKey < Base 7 | def call 8 | repo_key = find_gitolite_key key.owner, key.location 9 | 10 | # Add it if not found 11 | if repo_key.nil? 12 | admin.add_key build_gitolite_key(key) 13 | else 14 | logger.info "#{context} : SSH key '#{key.owner}@#{key.location}' already exists in Gitolite, update it ..." 15 | repo_key.type = key.type 16 | repo_key.blob = key.blob 17 | repo_key.email = key.email 18 | repo_key.owner = key.owner 19 | repo_key.location = key.location 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/forms/plugin_settings_validation/cache_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PluginSettingsValidation 4 | module CacheConfig 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | # Gitolite Cache Config 9 | add_accessor :gitolite_cache_max_time, 10 | :gitolite_cache_max_size, 11 | :gitolite_cache_max_elements, 12 | :gitolite_cache_adapter 13 | 14 | validates :gitolite_cache_max_time, presence: true, numericality: { only_integer: true } 15 | validates :gitolite_cache_max_size, presence: true, numericality: { only_integer: true } 16 | validates :gitolite_cache_max_elements, presence: true, numericality: { only_integer: true } 17 | validates :gitolite_cache_adapter, presence: true, inclusion: { in: GitCache.adapters } 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/recycle_bin/item.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module RecycleBin 5 | class Item 6 | attr_reader :path 7 | 8 | def initialize(path) 9 | @path = path 10 | end 11 | 12 | def size 13 | RedmineGitHosting::Commands.sudo_get_dir_size path 14 | end 15 | 16 | def destroy! 17 | logger.info "Deleting '#{path}' from Recycle Bin" 18 | begin 19 | RedmineGitHosting::Commands.sudo_rmdir path, force: true 20 | logger.info 'Done !' 21 | rescue RedmineGitHosting::Error::GitoliteCommandException 22 | logger.error "Errors while deleting '#{path}' from Recycle Bin !" 23 | end 24 | end 25 | 26 | private 27 | 28 | def logger 29 | RedmineGitHosting.logger 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/helpers/repository_deployment_credentials_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RepositoryDeploymentCredentialsHelper 4 | def build_list_of_keys(user_keys, other_keys, disabled_keys) 5 | option_array = [[l(:label_deployment_credential_select_deploy_key), -1]] 6 | option_array += user_keys.map { |key| [keylabel(key), key.id] } 7 | 8 | if other_keys.present? 9 | option_array2 = other_keys.map { |key| [keylabel(key), key.id] } 10 | maxlen = (option_array + option_array2).map { |x| x.first.length }.max 11 | 12 | extra = [maxlen - l(:select_other_keys).length - 2, 6].max / 2 13 | option_array += [[('-' * extra) + ' ' + l(:select_other_keys) + ' ' + ('-' * extra), -2]] 14 | option_array += option_array2 15 | end 16 | 17 | options_for_select(option_array, selected: -1, disabled: [-2] + disabled_keys.map(&:id)) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/common/_git_urls.html.slim: -------------------------------------------------------------------------------- 1 | - content_for :header_tags do 2 | = additionals_library_load :clipboardjs 3 | 4 | = stylesheet_link_tag 'git_urls', plugin: 'redmine_git_hosting' 5 | = javascript_include_tag 'git_urls', plugin: 'redmine_git_hosting' 6 | 7 | javascript: 8 | $(function() { 9 | setFirstGitUrl('.git_url_list'); setGitUrls('.git_url'); 10 | $('.clipboard-button').tooltip(); 11 | }) 12 | 13 | - repositories ||= Array.wrap repository 14 | - if repositories.map(&:available_urls_sorted).any? 15 | - repositories.sort_by { |r| r.is_default ? 0 : 1 }.each do |repository| 16 | - next if repository.available_urls_sorted.empty? 17 | - present repository do |p| 18 | .repository-urls 19 | = p.link_to_repository if repositories.count > 1 20 | = p.git_urls_box 21 | 22 | - else 23 | #git_url_box = l :label_repository_access_not_configured 24 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins/extenders/base_extender.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting::Plugins::Extenders 4 | class BaseExtender < RedmineGitHosting::Plugins::GitolitePlugin 5 | attr_reader :repository, :recovered, :gitolite_repo_name, :gitolite_repo_path, :git_default_branch, :options 6 | 7 | def initialize(repository, **options) 8 | @repository = repository 9 | @recovered = options.delete(:recovered) { false } 10 | @gitolite_repo_name = repository.gitolite_repository_name 11 | @gitolite_repo_path = repository.gitolite_repository_path 12 | @git_default_branch = repository.git_default_branch 13 | @options = options 14 | end 15 | 16 | private 17 | 18 | def recovered? 19 | recovered 20 | end 21 | 22 | def installable? 23 | false 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/recycle_bin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module RecycleBin 5 | extend self 6 | 7 | delegate :content, to: :recycle_bin 8 | 9 | def delete_expired_content(expiration_time = default_expiration_time) 10 | recycle_bin.delete_expired_content expiration_time 11 | end 12 | 13 | def delete_content(content_list = []) 14 | recycle_bin.delete_content content_list 15 | end 16 | 17 | delegate :move_object_to_recycle, to: :recycle_bin 18 | 19 | delegate :restore_object_from_recycle, to: :recycle_bin 20 | 21 | private 22 | 23 | def default_expiration_time 24 | RedmineGitHosting::Config.gitolite_recycle_bin_expiration_time 25 | end 26 | 27 | def recycle_bin 28 | @recycle_bin ||= RecycleBin::Manager.new RedmineGitHosting::Config.recycle_bin_dir 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/repositories/move_repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Repositories 6 | class MoveRepository < GitoliteWrappers::Base 7 | def call 8 | if repository.nil? 9 | log_object_dont_exist 10 | else 11 | move_repository 12 | end 13 | end 14 | 15 | def repository 16 | @repository ||= Repository.find_by id: object_id 17 | end 18 | 19 | def move_repository 20 | admin.transaction do 21 | move_gitolite_repository repository 22 | gitolite_admin_repo_commit repository.gitolite_repository_name 23 | end 24 | 25 | # Fetch changeset 26 | repository.fetch_changesets 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/helpers/git_hosting_users_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GitHostingUsersHelper 4 | def user_settings_tabs 5 | tabs = super 6 | tabs << { name: 'keys', partial: 'gitolite_public_keys/view', label: :label_public_keys } 7 | end 8 | 9 | # Hacked render_api_custom_values to add plugin values to user api. 10 | # @NOTE: there is no solution for index.api, because @user is missing 11 | # @TODO 12 | def render_api_custom_values(custom_values, api) 13 | rc = super 14 | 15 | if @user.present? 16 | api.array :ssh_keys do 17 | @user.gitolite_public_keys.each do |key| 18 | api.ssh_key do 19 | api.id key.id 20 | api.key_type key.key_type_as_string 21 | api.title key.title 22 | api.key key.key 23 | end 24 | end 25 | end 26 | end 27 | 28 | rc 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/console_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module ConsoleLogger 5 | extend self 6 | 7 | def title(message) 8 | info "\n * #{message}:" 9 | yield if block_given? 10 | info " Done!\n\n" 11 | end 12 | 13 | def debug(message) 14 | to_console message 15 | logger.debug message.strip 16 | end 17 | 18 | def info(message) 19 | to_console message 20 | logger.info message.strip 21 | end 22 | 23 | def warn 24 | to_console message 25 | logger.warn message.strip 26 | end 27 | 28 | def error(message) 29 | to_console message 30 | logger.error message.strip 31 | end 32 | 33 | private 34 | 35 | def to_console(message) 36 | puts message 37 | end 38 | 39 | def logger 40 | RedmineGitHosting.logger 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Error 5 | # Used to register errors when pulling and pushing the conf file 6 | class GitoliteException < StandardError; end 7 | 8 | class GitoliteWrapperException < GitoliteException; end 9 | 10 | class InvalidSshKey < GitoliteException; end 11 | 12 | class InvalidRefspec < GitoliteException 13 | class BadFormat < InvalidRefspec; end 14 | 15 | class NullComponent < InvalidRefspec; end 16 | end 17 | 18 | # Used to register errors when pulling and pushing the conf file 19 | class GitoliteCommandException < GitoliteException 20 | attr_reader :command, :output 21 | 22 | def initialize(command, output) 23 | super() 24 | @command = command 25 | @output = output 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/factories/repository_git_config_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :repository_git_config_key_base, class: 'RepositoryGitConfigKey' do 5 | sequence(:key) { |n| "hookfoo.foo#{n}" } 6 | value { 'bar' } 7 | association :repository, factory: :repository_gitolite 8 | end 9 | 10 | factory :repository_git_config_key, class: 'RepositoryGitConfigKey::GitConfig' do 11 | sequence(:key) { |n| "hookfoo.foo#{n}" } 12 | value { 'bar' } 13 | type { 'RepositoryGitConfigKey::GitConfig' } 14 | association :repository, factory: :repository_gitolite 15 | end 16 | 17 | factory :repository_git_option_key, class: 'RepositoryGitConfigKey::Option' do 18 | sequence(:key) { |n| "hookfoo.foo#{n}" } 19 | value { 'bar' } 20 | type { 'RepositoryGitConfigKey::Option' } 21 | association :repository, factory: :repository_gitolite 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/models/repository_git_config_key/git_config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../../spec_helper" 4 | 5 | describe RepositoryGitConfigKey::GitConfig do 6 | before :each do 7 | @git_config_key = build :repository_git_config_key 8 | end 9 | 10 | subject { @git_config_key } 11 | 12 | ## Validations 13 | it { should be_valid } 14 | it { should validate_presence_of(:key) } 15 | it { should validate_uniqueness_of(:key).case_insensitive.scoped_to(:type, :repository_id) } 16 | it { should allow_value('hookfoo.foo', 'hookfoo.foo.bar').for(:key) } 17 | it { should_not allow_value('hookfoo').for(:key) } 18 | 19 | context 'when key is updated' do 20 | before do 21 | @git_config_key.save 22 | @git_config_key.key = 'hookbar.foo' 23 | @git_config_key.save 24 | end 25 | 26 | it { should be_valid } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/repositories/edit.html.slim: -------------------------------------------------------------------------------- 1 | .contextual 2 | = render_repository_quick_jump @repository 3 | 4 | h2 5 | = link_to l(:label_settings), settings_project_path(@project, tab: 'repositories') 6 | ' 7 | = Additionals::LIST_SEPARATOR 8 | = l :label_repository 9 | ' 10 | = Additionals::LIST_SEPARATOR 11 | = @repository.redmine_name 12 | 13 | .splitcontent 14 | .splitcontentleft 15 | = call_hook :view_repository_edit_top, repository: @repository, project: @project 16 | 17 | = labelled_form_for :repository, @repository, url: repository_path(@repository), html: { method: :put, id: 'repository-form' } do |f| 18 | = render partial: 'form', locals: { f: f } 19 | 20 | .splitcontentright 21 | #xitolite-options style='vertical-align: top;' 22 | = render 'xitolite_options', repository: @repository 23 | 24 | = call_hook :view_repository_edit_bottom, repository: @repository, project: @project 25 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/projects/move_repositories.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Projects 6 | class MoveRepositories < GitoliteWrappers::Base 7 | include Common 8 | 9 | def call 10 | return if git_projects.empty? 11 | 12 | admin.transaction do 13 | @delete_parent_path = [] 14 | @delete_parent_path += handle_repositories_move git_projects 15 | # Remove empty directories 16 | clean_path @delete_parent_path 17 | end 18 | end 19 | 20 | def git_projects 21 | @git_projects ||= projects.uniq.select { |p| p.gitolite_repos.any? } 22 | end 23 | 24 | def projects 25 | @projects ||= Project.find_by(id: object_id).self_and_descendants 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/models/concerns/gitolitable/notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolitable 4 | module Notifications 5 | extend ActiveSupport::Concern 6 | 7 | def mailing_list 8 | default_list + global_include_list - global_exclude_list 9 | end 10 | 11 | def default_list 12 | watcher_users.map(&:email_address).map(&:address) 13 | end 14 | 15 | def global_include_list 16 | RedmineGitHosting::Config.gitolite_notify_global_include 17 | end 18 | 19 | def global_exclude_list 20 | RedmineGitHosting::Config.gitolite_notify_global_exclude 21 | end 22 | 23 | def sender_address 24 | extra.notification_sender.presence || RedmineGitHosting::Config.gitolite_notify_global_sender_address 25 | end 26 | 27 | def email_prefix 28 | extra.notification_prefix.presence || RedmineGitHosting::Config.gitolite_notify_global_prefix 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: Run RuboCop 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install package dependencies 16 | run: > 17 | sudo apt-get install --yes --quiet 18 | pandoc 19 | 20 | - name: Setup Gemfile 21 | run: | 22 | touch .enable_dev 23 | sed -i "3isource 'https://rubygems.org'" Gemfile 24 | 25 | - name: Setup Ruby 26 | uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: 3.1 29 | bundler-cache: true 30 | 31 | - name: Setup gems 32 | run: | 33 | bundle install --jobs 4 --retry 3 34 | 35 | - name: Run RuboCop 36 | run: | 37 | bundle exec rubocop -S 38 | 39 | - name: Run Slim-Lint 40 | run: | 41 | bundle exec slim-lint app/views 42 | if: always() 43 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/journal_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | # @see https://github.com/theforeman/journald-logger 5 | if defined? ::Journald::Logger 6 | class JournalLogger < ::Journald::Logger 7 | def self.init_logs!(progname, loglevel) 8 | logger = new progname, type: progname 9 | logger.level = loglevel 10 | 11 | logger 12 | end 13 | 14 | def debug(msg) 15 | super msg2str(msg) 16 | end 17 | 18 | def info(msg) 19 | super msg2str(msg) 20 | end 21 | 22 | def warn(msg) 23 | super msg2str(msg) 24 | end 25 | 26 | def error(msg) 27 | super msg2str(msg) 28 | end 29 | 30 | def msg2str(msg) 31 | case msg 32 | when ::String 33 | msg 34 | else 35 | msg.inspect 36 | end 37 | end 38 | end 39 | else 40 | module JournalLogger 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/helpers/git_hosting_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GitHostingHelper 4 | def present(object, klass = nil, *args) 5 | klass ||= "#{object.class.base_class}Presenter".constantize 6 | presenter = klass.new(object, self, *args) 7 | yield presenter if block_given? 8 | presenter 9 | end 10 | 11 | def checked_image_with_exclamation(checked:) 12 | checked ? image_tag('toggle_check.png') : image_tag('exclamation.png') 13 | end 14 | 15 | def render_shell_text(text) 16 | Redmine::SyntaxHighlighting.highlight_by_language text, 'shell' 17 | end 18 | 19 | def gitolite_project_settings_tabs 20 | tabs = [] 21 | 22 | tabs << { name: 'db', 23 | action: :show, 24 | partial: 'projects/settings/db', 25 | label: :label_db } 26 | 27 | tabs << { name: 'db2', 28 | action: :show, 29 | partial: 'projects/settings/db', 30 | label: :label_db } 31 | tabs 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/views/dashboards/blocks/_git_urls.html.slim: -------------------------------------------------------------------------------- 1 | - if @project.module_enabled?(:repository) && \ 2 | @project.repository.is_a?(Repository::Xitolite) && \ 3 | User.current.allowed_to?(:view_changesets, @project) 4 | .git_hosting 5 | h3 6 | = @project.repositories.count > 1 ? l(:label_repository_plural) : l(:label_repository) 7 | = render partial: 'common/git_urls', locals: { repositories: @project.gitolite_repos } 8 | - if @project.repositories.count > 1 9 | .clear-both 10 | p 11 | = link_to l(:label_see_other_repositories), { controller: '/repositories', 12 | action: 'show', 13 | id: @project, 14 | repository_id: @project.repository.identifier_param, 15 | rev: nil, 16 | path: nil } 17 | -------------------------------------------------------------------------------- /app/views/users/index.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :users, api_meta(total_count: @user_count, offset: @offset, limit: @limit) do 2 | @users.each do |user| 3 | api.user do 4 | api.id user.id 5 | api.login user.login 6 | api.admin user.admin? 7 | api.firstname user.firstname 8 | api.lastname user.lastname 9 | api.mail user.mail 10 | api.created_on user.created_on 11 | api.updated_on user.updated_on 12 | api.last_login_on user.last_login_on 13 | api.passwd_changed_on user.passwd_changed_on 14 | 15 | api.array :ssh_keys do 16 | user.gitolite_public_keys.each do |key| 17 | api.ssh_key do 18 | api.id key.id 19 | api.key_type key.key_type_as_string 20 | api.title key.title 21 | api.key key.key 22 | end 23 | end 24 | end 25 | 26 | render_api_custom_values user.visible_custom_field_values, api 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/dashboard_content_project_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Patches 5 | module DashboardContentProjectPatch 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | prepend InstanceOverwriteMethods 10 | end 11 | 12 | module InstanceOverwriteMethods 13 | def block_definitions 14 | blocks = super 15 | 16 | blocks['giturls'] = { label: l(:label_repository_url_plural), 17 | permission: :manage_repository, 18 | no_settings: true, 19 | partial: 'dashboards/blocks/git_urls' } 20 | 21 | blocks 22 | end 23 | end 24 | end 25 | end 26 | end 27 | 28 | if DashboardContentProject.included_modules.exclude? RedmineGitHosting::Patches::DashboardContentProjectPatch 29 | DashboardContentProject.include RedmineGitHosting::Patches::DashboardContentProjectPatch 30 | end 31 | -------------------------------------------------------------------------------- /app/views/repository_mirrors/_form.html.slim: -------------------------------------------------------------------------------- 1 | .flash-messages = error_messages_for 'mirror' 2 | 3 | .box 4 | p = f.text_field :url, required: true, size: 65 5 | em 6 | p 7 | = l :label_mirror_url_accepted_format 8 | ' : 9 | br 10 | | ssh://git@redmine.example.org/project1/project2/project3/project4.git 11 | br 12 | | ssh://git@redmine.example.org:22/project1/project2/project3/project4.git 13 | 14 | p = f.check_box :active 15 | p = f.select :push_mode, 16 | options_for_select(mirrors_options, @mirror.push_mode), 17 | { label: :label_mirror_push_mode }, 18 | onchange: 'push_mode_change(this); return false;' 19 | 20 | #ref_spec_options style="#{'display: none;' if @mirror.mirror_mode?}" 21 | p = f.check_box :include_all_branches, label: :label_mirror_include_all_branches 22 | p = f.check_box :include_all_tags, label: :label_mirror_include_all_tags 23 | p = f.text_field :explicit_refspec, label: :label_mirror_explicit_refspec, size: 65 24 | -------------------------------------------------------------------------------- /db/migrate/20150823030100_migrate_protected_branches_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepositoryProtectedBrancheWrapped < RepositoryProtectedBranche 4 | serialize :user_list, Array 5 | end 6 | 7 | class MigrateProtectedBranchesUsers < ActiveRecord::Migration[4.2] 8 | def up 9 | RepositoryProtectedBrancheWrapped.all.each do |protected_branch| 10 | users = protected_branch.user_list.map { |u| User.find_by login: u }.compact.uniq 11 | manager = RepositoryProtectedBranches::MemberManager.new protected_branch 12 | manager.add_users(users.map(&:id)) 13 | end 14 | remove_column :repository_protected_branches, :user_list 15 | end 16 | 17 | def down 18 | add_column :repository_protected_branches, :user_list, :text, after: :permissions 19 | RepositoryProtectedBrancheWrapped.all.each do |protected_branch| 20 | users = protected_branch.users.map(&:login).compact.uniq 21 | protected_branch.user_list = users 22 | protected_branch.save! 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/repositories/delete_repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Repositories 6 | class DeleteRepository < GitoliteWrappers::Base 7 | def call 8 | if repository.present? 9 | delete_repository 10 | else 11 | log_object_dont_exist 12 | end 13 | end 14 | 15 | def repository 16 | @repository ||= object_id.symbolize_keys 17 | end 18 | 19 | def delete_repository 20 | admin.transaction do 21 | delete_gitolite_repository repository 22 | gitolite_admin_repo_commit repository[:repo_name] 23 | end 24 | 25 | # Call Gitolite plugins 26 | logger.info 'Execute Gitolite Plugins' 27 | 28 | # Move repository to RecycleBin 29 | RedmineGitHosting::Plugins.execute :post_delete, repository 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/views/settings/redmine_git_hosting/_gitolite_recycle_bin.html.slim: -------------------------------------------------------------------------------- 1 | h3 = l :label_empty_recycle_bin 2 | 3 | - if !RedmineGitHosting::RecycleBin || !RedmineGitHosting::RecycleBin.content.empty? 4 | table.list 5 | tr 6 | th = l :label_repository 7 | th = l :label_recycle_bin_content_size 8 | th 9 | = l :label_delete_from_recyle_bin 10 | | ( 11 | = link_to l(:label_select_all), 'javascript:void(0);', id: 'select_all_delete' 12 | | ) 13 | 14 | - RedmineGitHosting::RecycleBin.content.each do |trash_object| 15 | tr 16 | td = trash_object.path 17 | td = trash_object.size 18 | td = check_box_tag 'settings[rescue][delete_trash_repo][]', trash_object.path, false, class: 'empty_trash' 19 | 20 | #delete_warning.alert.alert-error style='display: none;' 21 | = l :label_delete_warning 22 | 23 | - else 24 | p.nodata style='padding: 5px 0 0;' 25 | = l :label_no_data 26 | 27 | javascript: 28 | $(document).ready(function() { setRecycleBinWarnings(); }); 29 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins/sweepers/repository_deletor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting::Plugins::Sweepers 4 | class RepositoryDeletor < BaseSweeper 5 | def post_delete 6 | # Delete hook param if needed 7 | move_repository_to_recycle if delete_repository? 8 | remove_git_cache 9 | end 10 | 11 | private 12 | 13 | def move_repository_to_recycle 14 | if repository_data.is_a? Hash 15 | RedmineGitHosting::RecycleBin.move_object_to_recycle repository_data[:repo_name], repository_data[:repo_path] 16 | elsif repository_data.is_a? Array 17 | repository_data.each do |object_data| 18 | RedmineGitHosting::RecycleBin.move_object_to_recycle object_data[:repo_name], object_data[:repo_path] 19 | end 20 | end 21 | end 22 | 23 | def remove_git_cache 24 | logger.info "Clean cache for repository '#{gitolite_repo_name}'" 25 | RedmineGitHosting::Cache.clear_cache_for_repository git_cache_id 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins/extenders/config_key_deletor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting::Plugins::Extenders 4 | class ConfigKeyDeletor < BaseExtender 5 | attr_reader :delete_git_config_key 6 | 7 | def initialize(repository, **options) 8 | super(repository, **options) 9 | @delete_git_config_key = options.delete(:delete_git_config_key) { '' } 10 | end 11 | 12 | def post_update 13 | # Delete hook param if needed 14 | delete_hook_param if delete_git_config_key.present? 15 | end 16 | 17 | private 18 | 19 | def delete_hook_param 20 | sudo_git 'config', '--local', '--unset', delete_git_config_key 21 | rescue RedmineGitHosting::Error::GitoliteCommandException 22 | logger.error "Error while deleting Git config key '#{delete_git_config_key}' for repository '#{gitolite_repo_name}'" 23 | else 24 | logger.info "Git config key '#{delete_git_config_key}' successfully deleted for repository '#{gitolite_repo_name}'" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /contrib/hooks/post-receive/mail_notifications.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | 6 | import git_multimail 7 | 8 | 9 | # It is possible to modify the output templates here; e.g.: 10 | 11 | git_multimail.FOOTER_TEMPLATE = """\ 12 | 13 | -- \n\ 14 | This email was generated by the wonderful git-multimail tool from JBox Web. 15 | """ 16 | 17 | 18 | # Specify which "git config" section contains the configuration for 19 | # git-multimail: 20 | config = git_multimail.Config('multimailhook') 21 | 22 | # check if hook is enabled 23 | enabled = config.get_bool('enabled') 24 | 25 | if enabled: 26 | # Select the type of environment: 27 | environment = git_multimail.GitoliteEnvironment(config=config) 28 | 29 | # Choose the method of sending emails based on the git config: 30 | mailer = git_multimail.choose_mailer(config, environment) 31 | 32 | # Read changes from stdin and send notification emails: 33 | git_multimail.run_as_post_receive_hook(environment, mailer) 34 | else: 35 | print(" multimailhook is disabled") 36 | -------------------------------------------------------------------------------- /db/migrate/20120708070841_add_settings_to_plugin_4.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddSettingsToPlugin4 < ActiveRecord::Migration[4.2] 4 | def up 5 | # Add some new settings to settings page, if they don't exist 6 | valuehash = Setting.plugin_redmine_git_hosting.clone 7 | valuehash['gitForceHooksUpdate'] ||= 'true' 8 | 9 | if Setting.plugin_redmine_git_hosting != valuehash 10 | say 'Added redmine_git_hosting settings: gitForceHooksUpdate' 11 | Setting.plugin_redmine_git_hosting = valuehash 12 | end 13 | rescue StandardError => e 14 | say e.message 15 | end 16 | 17 | def down 18 | # Remove above settings from plugin page 19 | valuehash = Setting.plugin_redmine_git_hosting.clone 20 | valuehash.delete 'gitForceHooksUpdate' 21 | 22 | if Setting.plugin_redmine_git_hosting != valuehash 23 | say 'Removed redmine_git_hosting settings: gitForceHooksUpdate' 24 | Setting.plugin_redmine_git_hosting = valuehash 25 | end 26 | rescue StandardError => e 27 | say e.message 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /db/migrate/20110817000000_move_notified_cia_to_git_cia_notifications.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MoveNotifiedCiaToGitCiaNotifications < ActiveRecord::Migration[4.2] 4 | def up 5 | drop_table :git_cia_notifications if table_exists? :git_cia_notifications 6 | 7 | create_table :git_cia_notifications do |t| 8 | t.column :repository_id, :integer 9 | t.column :scmid, :string 10 | end 11 | 12 | # Speed up searches 13 | add_index :git_cia_notifications, :scmid 14 | 15 | # Make sure uniqueness of the two columns, :scmid, :repository_id 16 | add_index :git_cia_notifications, %i[scmid repository_id], unique: true 17 | 18 | remove_column :changesets, :notified_cia if column_exists? :changesets, :notified_cia 19 | end 20 | 21 | def down 22 | drop_table :git_cia_notifications 23 | end 24 | 25 | def table_exists?(name) 26 | ActiveRecord::Base.connection.tables.include? name 27 | end 28 | 29 | def column_exists?(table_name, column_name) 30 | columns(table_name).any? { |c| c.name == column_name.to_s } 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/grack_server_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'grack/server' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module GrackServerPatch 8 | # Override original *get_git* method to set the right path for the repository. 9 | # Also pass the *@env['REMOTE_USER']* variable to the Git constructor so we 10 | # can pass it to Gitolite hooks later. 11 | def get_git(path) 12 | path = gitolite_path path 13 | Grack::Git.new @config[:git_path], path, @env['REMOTE_USER'] 14 | end 15 | 16 | private 17 | 18 | def gitolite_path(path) 19 | File.join(RedmineGitHosting::Config.gitolite_home_dir, 20 | RedmineGitHosting::Config.gitolite_global_storage_dir, 21 | RedmineGitHosting::Config.gitolite_redmine_storage_dir, path) 22 | end 23 | end 24 | end 25 | end 26 | 27 | unless Grack::Server.included_modules.include? RedmineGitHosting::Patches::GrackServerPatch 28 | Grack::Server.prepend RedmineGitHosting::Patches::GrackServerPatch 29 | end 30 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/recycle_bin/item_base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module RecycleBin 5 | module ItemBase 6 | TRASH_DIR_SEP = '__' 7 | 8 | attr_reader :object_name, :recycle_bin_dir 9 | 10 | def initialize(recycle_bin_dir, object_name) 11 | @recycle_bin_dir = recycle_bin_dir 12 | @object_name = object_name 13 | end 14 | 15 | def trash_name 16 | object_name.gsub %r{/}, TRASH_DIR_SEP 17 | end 18 | 19 | private 20 | 21 | def logger 22 | RedmineGitHosting.logger 23 | end 24 | 25 | def directory_exists?(dir) 26 | RedmineGitHosting::Commands.sudo_dir_exists? dir 27 | end 28 | 29 | def find_trashed_object(regex) 30 | RedmineGitHosting::Commands.sudo_capture('find', recycle_bin_dir, '-type', 'd', '-regex', regex, '-prune', '-print') 31 | .chomp 32 | .split("\n") 33 | .sort.reverse 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /assets/javascripts/plugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | REDMINE PLUGIN LIST VIEW OVERRIDE 3 | */ 4 | function openAuthorModalBox(element) { 5 | $('#ajax-modal').dialog({ 6 | resizable: false, 7 | autoOpen: false, 8 | height: 'auto', 9 | width: 'auto', 10 | modal: true, 11 | hide: { 12 | effect: "fade", 13 | duration: 500 14 | }, 15 | buttons: { Ok: function(){ $(this).dialog('close'); } } 16 | }); 17 | 18 | var title = $(element).html(); 19 | $.get($(element).attr('href'), function(data){ 20 | $('#ajax-modal').html(data); 21 | $('#ajax-modal').dialog('option', 'title', title); 22 | $('#ajax-modal').dialog('open'); 23 | }); 24 | } 25 | 26 | function enhanceAuthorsUrlForPlugin(plugin_name) { 27 | var link = $('#plugin-' + plugin_name + ' > td.author > a'); 28 | if (link.length) { 29 | link.addClass('modal-box'); 30 | $(document).on('click', 'a.modal-box', function(e){ 31 | e.preventDefault(); 32 | openAuthorModalBox(this); 33 | }); 34 | } 35 | } 36 | 37 | $(document).ready(function() { 38 | enhanceAuthorsUrlForPlugin('redmine_git_hosting'); 39 | }); 40 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_handlers/ssh_keys/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteHandlers 5 | module SshKeys 6 | class Base 7 | attr_reader :admin, :key, :context 8 | 9 | def initialize(admin, key, context) 10 | @admin = admin 11 | @key = key 12 | @context = context 13 | end 14 | 15 | class << self 16 | def call(admin, key, context) 17 | new(admin, key, context).call 18 | end 19 | end 20 | 21 | def call 22 | raise NotImplementedError 23 | end 24 | 25 | private 26 | 27 | def logger 28 | RedmineGitHosting.logger 29 | end 30 | 31 | def find_gitolite_key(owner, location) 32 | admin.ssh_keys[owner].find { |k| k.location == location && k.owner == owner } 33 | end 34 | 35 | def build_gitolite_key(key) 36 | ::Gitolite::SSHKey.new key.type, key.blob, key.email, key.owner, key.location 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_hooks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteHooks 5 | class << self 6 | def register_hooks(&block) 7 | @gitolite_hooks ||= [] 8 | class_eval(&block) 9 | end 10 | 11 | def registered_hooks 12 | @gitolite_hooks 13 | end 14 | 15 | def source_dir(source_dir) 16 | @source_dir = source_dir 17 | end 18 | 19 | def hooks_installed? 20 | installed = {} 21 | registered_hooks.each do |hook| 22 | installed[hook.name] = hook.installed? 23 | rescue StandardError 24 | installed[hook.name] = false 25 | end 26 | installed 27 | end 28 | 29 | def install_hooks! 30 | installed = {} 31 | registered_hooks.each do |hook| 32 | installed[hook.name] = hook.install! 33 | end 34 | installed 35 | end 36 | 37 | def gitolite_hook(&block) 38 | @gitolite_hooks << RedmineGitHosting::GitoliteHook.new(@source_dir, &block) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/group_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'group' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module GroupPatch 8 | def self.prepended(base) 9 | base.class_eval do 10 | # Relations 11 | has_many :protected_branches_members, dependent: :destroy, foreign_key: :principal_id 12 | has_many :protected_branches, through: :protected_branches_members 13 | end 14 | end 15 | 16 | def user_added(user) 17 | super 18 | protected_branches.each do |pb| 19 | RepositoryProtectedBranches::MemberManager.new(pb).add_user_from_group(user, id) 20 | end 21 | end 22 | 23 | def user_removed(user) 24 | super 25 | protected_branches.each do |pb| 26 | RepositoryProtectedBranches::MemberManager.new(pb).remove_user_from_group(user, id) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | 33 | Group.prepend RedmineGitHosting::Patches::GroupPatch unless Group.included_modules.include? RedmineGitHosting::Patches::GroupPatch 34 | -------------------------------------------------------------------------------- /contrib/scripts/redmine: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: redmine 4 | # Required-Start: $local_fs $remote_fs $network $mysql $named 5 | # Required-Stop: $local_fs $remote_fs $network $mysql $named 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Redmine projects manager 9 | # Description: This file should be used to start and stop Redmine. 10 | ### END INIT INFO 11 | 12 | [ -f /etc/default/rcS ] && . /etc/default/rcS 13 | . /lib/lsb/init-functions 14 | 15 | REDMINE_USER="redmine" 16 | 17 | WEBSERVER="server_puma" 18 | WORKER1="sidekiq_git_hosting" 19 | 20 | case "$1" in 21 | start) 22 | su - $REDMINE_USER -c "${WEBSERVER}.sh start" 23 | su - $REDMINE_USER -c "${WORKER1}.sh start" 24 | ;; 25 | stop) 26 | su - $REDMINE_USER -c "${WEBSERVER}.sh stop" 27 | su - $REDMINE_USER -c "${WORKER1}.sh stop" 28 | ;; 29 | restart) 30 | su - $REDMINE_USER -c "${WEBSERVER}.sh restart" 31 | su - $REDMINE_USER -c "${WORKER1}.sh restart" 32 | ;; 33 | *) 34 | echo "Usage : /etc/init.d/redmine {start|stop|restart}" 35 | ;; 36 | esac 37 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/projects/move_repositories_tree.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Projects 6 | class MoveRepositoriesTree < GitoliteWrappers::Base 7 | include Common 8 | 9 | # Move repositories tree in a single transaction 10 | # 11 | def call 12 | admin.transaction do 13 | @delete_parent_path = [] 14 | projects.each do |project| 15 | # Only take projects that have Git repos. 16 | git_projects = project.self_and_descendants.uniq.select { |p| p.gitolite_repos.any? } 17 | next if git_projects.empty? 18 | 19 | @delete_parent_path += handle_repositories_move git_projects 20 | end 21 | # Remove empty directories 22 | clean_path @delete_parent_path 23 | end 24 | end 25 | 26 | def projects 27 | @projects ||= Project.includes(:repositories).all.select { |x| x.parent_id.nil? } 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/forms/plugin_settings_validation/storage_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PluginSettingsValidation 4 | module StorageConfig 5 | extend ActiveSupport::Concern 6 | 7 | PATHS_TO_VALIDATE = %i[gitolite_global_storage_dir gitolite_redmine_storage_dir gitolite_recycle_bin_dir].freeze 8 | 9 | included do 10 | # Gitolite Storage Config 11 | add_accessor :gitolite_global_storage_dir, 12 | :gitolite_redmine_storage_dir, 13 | :gitolite_recycle_bin_dir 14 | 15 | before_validation do 16 | self.gitolite_global_storage_dir = strip_value gitolite_global_storage_dir 17 | self.gitolite_redmine_storage_dir = strip_value gitolite_redmine_storage_dir 18 | self.gitolite_recycle_bin_dir = strip_value gitolite_recycle_bin_dir 19 | end 20 | 21 | validates_presence_of :gitolite_global_storage_dir, :gitolite_recycle_bin_dir 22 | 23 | validates_each PATHS_TO_VALIDATE do |record, attr, value| 24 | record.errors.add attr, 'must be relative' if value.starts_with? '/' 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/repositories/update_repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Repositories 6 | class UpdateRepository < GitoliteWrappers::Base 7 | def call 8 | if repository.nil? 9 | log_object_dont_exist 10 | else 11 | update_repository 12 | end 13 | end 14 | 15 | def repository 16 | @repository ||= Repository.find_by id: object_id 17 | end 18 | 19 | def update_repository 20 | admin.transaction do 21 | update_gitolite_repository repository 22 | gitolite_admin_repo_commit repository.gitolite_repository_name 23 | end 24 | 25 | # Call Gitolite plugins 26 | logger.info 'Execute Gitolite Plugins' 27 | 28 | # Delete Git Config Keys 29 | RedmineGitHosting::Plugins.execute :post_update, repository, **options 30 | 31 | # Fetch changeset 32 | repository.fetch_changesets 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/forms/plugin_settings_validation/hooks_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PluginSettingsValidation 4 | module HooksConfig 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | # Gitolite Hooks Config 9 | add_accessor :gitolite_overwrite_existing_hooks, 10 | :gitolite_hooks_are_asynchronous, 11 | :gitolite_hooks_debug, 12 | :gitolite_hooks_url 13 | 14 | before_validation do 15 | self.gitolite_hooks_url = strip_value gitolite_hooks_url 16 | end 17 | 18 | validates :gitolite_overwrite_existing_hooks, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS } 19 | validates :gitolite_hooks_are_asynchronous, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS } 20 | validates :gitolite_hooks_debug, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS } 21 | validates :gitolite_hooks_url, presence: true, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]) } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/_config_keys.html.slim: -------------------------------------------------------------------------------- 1 | - if git_config_keys.any? 2 | table.list 3 | thead 4 | tr 5 | th = l :label_key 6 | th = l :field_value 7 | th 8 | tbody 9 | - git_config_keys.each do |git_config_key| 10 | tr 11 | td 12 | span.label.label-info = git_config_key.key 13 | td 14 | span.label.label-success = git_config_key.value 15 | td.buttons 16 | - if User.current.git_allowed_to? :edit_repository_git_config_keys, @repository 17 | = link_to l(:button_edit), 18 | edit_repository_git_config_key_path(@repository, git_config_key), 19 | class: 'icon icon-edit' 20 | = link_to l(:button_delete), 21 | repository_git_config_key_path(@repository, git_config_key), 22 | remote: true, 23 | method: :delete, 24 | data: { confirm: l(:text_are_you_sure) }, 25 | class: 'icon icon-del' 26 | 27 | - else 28 | p.nodata = l :label_no_data 29 | -------------------------------------------------------------------------------- /app/forms/move_repository_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MoveRepositoryForm 4 | include BaseForm 5 | 6 | attr_reader :repository 7 | attr_accessor :project_id 8 | 9 | validates_presence_of :project_id 10 | validate :repository_is_movable 11 | validate :target_project 12 | validate :repository_uniqueness 13 | 14 | def initialize(repository) 15 | @repository = repository 16 | end 17 | 18 | def project 19 | @project ||= Project.find_by id: project_id 20 | end 21 | 22 | def valid_form_submitted 23 | repository.update_attribute :project_id, project.id 24 | RedmineGitHosting::GitoliteAccessor.move_repository repository 25 | end 26 | 27 | private 28 | 29 | def repository_is_movable 30 | errors.add :base, :identifier_empty unless repository.movable? 31 | end 32 | 33 | def target_project 34 | errors.add :base, :wrong_target_project if repository.project == project 35 | end 36 | 37 | def repository_uniqueness 38 | new_repo = project.repositories.find_by identifier: repository.identifier 39 | errors.add :base, :identifier_taken unless new_repo.nil? 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/services/redmine_hooks/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineHooks 4 | class Base 5 | attr_reader :object, :payloads 6 | 7 | def initialize(object, payloads = {}) 8 | @object = object 9 | @payloads = payloads 10 | end 11 | 12 | class << self 13 | def call(object, payloads = {}) 14 | new(object, payloads).call 15 | end 16 | end 17 | 18 | def call 19 | raise NotImplementedError 20 | end 21 | 22 | def start_message 23 | raise NotImplementedError 24 | end 25 | 26 | private 27 | 28 | def logger 29 | RedmineGitHosting.logger 30 | end 31 | 32 | def success_message 33 | " [success]\n" 34 | end 35 | 36 | def failure_message 37 | " [failure]\n" 38 | end 39 | 40 | def log_hook_succeeded 41 | logger.info 'Succeeded!' 42 | end 43 | 44 | def log_hook_failed 45 | logger.error 'Failed!' 46 | end 47 | 48 | def execute_hook 49 | y = +'' 50 | logger.info start_message 51 | y << " - #{start_message} ... " 52 | yield y 53 | y 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /db/migrate/20110813000000_create_git_repository_extras.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGitRepositoryExtras < ActiveRecord::Migration[4.2] 4 | def up 5 | drop_table :git_repository_extras if table_exists? :git_repository_extras 6 | 7 | create_table :git_repository_extras do |t| 8 | t.column :repository_id, :integer 9 | t.column :git_daemon, :integer, default: 1 10 | t.column :git_http, :integer, default: 1 11 | t.column :notify_cia, :integer, default: 0 12 | t.column :key, :string 13 | end 14 | 15 | drop_table :git_hook_keys if table_exists? 'git_hook_keys' 16 | remove_column :repositories, :git_daemon if column_exists? :repositories, :git_daemon 17 | remove_column :repositories, :git_http if column_exists? :repositories, :git_http 18 | end 19 | 20 | def down 21 | drop_table :git_repository_extras 22 | end 23 | 24 | def table_exists?(name) 25 | ActiveRecord::Base.connection.tables.include? name 26 | end 27 | 28 | def column_exists?(table_name, column_name) 29 | columns(table_name).any? { |c| c.name == column_name.to_s } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/recycle_bin/deletable_item.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module RecycleBin 5 | class DeletableItem 6 | include RecycleBin::ItemBase 7 | 8 | def move!(source_path) 9 | if directory_exists? source_path 10 | logger.info "Moving '#{object_name}' to Recycle Bin..." 11 | logger.debug "'#{source_path}' => '#{target_path}'" 12 | do_move source_path 13 | else 14 | logger.warn "Source directory does not exist '#{source_path}', exiting!" 15 | false 16 | end 17 | end 18 | 19 | def target_path 20 | @target_path ||= File.join recycle_bin_dir, "#{Time.now.to_i}#{TRASH_DIR_SEP}#{trash_name}.git" 21 | end 22 | 23 | private 24 | 25 | def do_move(source_path) 26 | RedmineGitHosting::Commands.sudo_move source_path, target_path 27 | logger.info 'Done !' 28 | true 29 | rescue RedmineGitHosting::Error::GitoliteCommandException 30 | logger.error "Attempt to move '#{source_path}' to Recycle Bin failed!" 31 | false 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/roles_controller_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'roles_controller' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module RolesControllerPatch 8 | include RedmineGitHosting::GitoliteAccessor::Methods 9 | 10 | def create 11 | super 12 | call_gitolite 'created' 13 | end 14 | 15 | def update 16 | super 17 | call_gitolite 'modified' 18 | end 19 | 20 | def destroy 21 | super 22 | call_gitolite 'deleted' 23 | end 24 | 25 | def permissions 26 | super 27 | call_gitolite 'modified' if request.post? 28 | end 29 | 30 | private 31 | 32 | def call_gitolite(message) 33 | options = { message: "Role has been #{message}, resync all projects (active or closed)..." } 34 | gitolite_accessor.update_projects 'active_or_closed', **options 35 | end 36 | end 37 | end 38 | end 39 | 40 | unless RolesController.included_modules.include? RedmineGitHosting::Patches::RolesControllerPatch 41 | RolesController.prepend RedmineGitHosting::Patches::RolesControllerPatch 42 | end 43 | -------------------------------------------------------------------------------- /db/migrate/20120724211806_add_settings_to_plugin_5.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddSettingsToPlugin5 < ActiveRecord::Migration[4.2] 4 | def up 5 | # Add some new settings to settings page, if they don't exist 6 | valuehash = Setting.plugin_redmine_git_hosting.clone 7 | valuehash['gitConfigFile'] ||= 'gitolite.conf' 8 | valuehash['gitConfigHasAdminKey'] ||= 'true' 9 | 10 | if Setting.plugin_redmine_git_hosting != valuehash 11 | say 'Added redmine_git_hosting settings: gitConfigFile, gitConfigHasAdminKey' 12 | Setting.plugin_redmine_git_hosting = valuehash 13 | end 14 | rescue StandardError => e 15 | say e.message 16 | end 17 | 18 | def down 19 | # Remove above settings from plugin page 20 | valuehash = Setting.plugin_redmine_git_hosting.clone 21 | valuehash.delete 'gitConfigFile' 22 | valuehash.delete 'gitConfigHasAdminKey' 23 | 24 | if Setting.plugin_redmine_git_hosting != valuehash 25 | say 'Removed redmine_git_hosting settings: gitConfigFile, gitConfigHasAdminKey' 26 | Setting.plugin_redmine_git_hosting = valuehash 27 | end 28 | rescue StandardError => e 29 | say e.message 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/views/repository_deployment_credentials/_new_modal.html.slim: -------------------------------------------------------------------------------- 1 | h3.title = l :label_deployment_credential_add 2 | 3 | = labelled_form_for :repository_deployment_credential, @credential, 4 | url: repository_deployment_credentials_path(@repository), 5 | html: { method: :post, class: 'tabular', remote: true } do |f| 6 | 7 | .flash-messages = error_messages_for 'credential' 8 | 9 | .box 10 | - if @user_keys.present? || @other_keys.present? 11 | p = f.select :gitolite_public_key_id, 12 | build_list_of_keys(@user_keys, @other_keys, @disabled_keys), 13 | required: true, 14 | label: :label_deployment_credential_select_deploy_key 15 | p = f.select :perm, 16 | options_for_select(RepositoryDeploymentCredential::VALID_PERMS, RepositoryDeploymentCredential::DEFAULT_PERM), 17 | required: true, 18 | label: :label_permissions 19 | - else 20 | = link_to l(:label_deployment_credential_create_key_first), public_keys_path 21 | 22 | .buttons 23 | = submit_tag l(:button_add) 24 | ' 25 | = link_to_function l(:button_cancel), 'hideModal(this);' 26 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins/extenders/branch_updater.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting::Plugins::Extenders 4 | class BranchUpdater < BaseExtender 5 | attr_reader :update_default_branch 6 | 7 | def initialize(repository, **options) 8 | super(repository, **options) 9 | @update_default_branch = options.delete(:update_default_branch) { false } 10 | end 11 | 12 | def post_update 13 | # Update default branch if needed 14 | do_update_default_branch if update_default_branch? 15 | end 16 | 17 | private 18 | 19 | def update_default_branch? 20 | RedminePluginKit.true? update_default_branch 21 | end 22 | 23 | def do_update_default_branch 24 | sudo_git 'symbolic-ref', 'HEAD', new_default_branch 25 | rescue RedmineGitHosting::GitHosting::GitHostingException 26 | logger.error "Error while updating default branch for repository '#{gitolite_repo_name}'" 27 | else 28 | logger.info "Default branch successfully updated for repository '#{gitolite_repo_name}'" 29 | repository.empty_cache! 30 | end 31 | 32 | def new_default_branch 33 | "refs/heads/#{git_default_branch}" 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/patches/watchers_controller_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency 'watchers_controller' 4 | 5 | module RedmineGitHosting 6 | module Patches 7 | module WatchersControllerPatch 8 | include RedmineGitHosting::GitoliteAccessor::Methods 9 | 10 | def create 11 | super 12 | update_repository @watched 13 | end 14 | 15 | def destroy 16 | super 17 | update_repository @watched 18 | end 19 | 20 | def watch 21 | super 22 | update_repository @watchables.first 23 | end 24 | 25 | def unwatch 26 | super 27 | update_repository @watchables.first 28 | end 29 | 30 | private 31 | 32 | def update_repository(repo) 33 | return unless repo.is_a? Repository::Xitolite 34 | 35 | options = { message: "Watcher changes on repository '#{repo}', update!" } 36 | gitolite_accessor.update_repository repo, **options 37 | end 38 | end 39 | end 40 | end 41 | 42 | unless WatchersController.included_modules.include? RedmineGitHosting::Patches::WatchersControllerPatch 43 | WatchersController.prepend RedmineGitHosting::Patches::WatchersControllerPatch 44 | end 45 | -------------------------------------------------------------------------------- /app/reports/report_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ReportHelper 4 | def date_to 5 | User.current.today 6 | end 7 | 8 | def week_day_hash 9 | { day_name(1) => 0, 10 | day_name(2) => 0, 11 | day_name(3) => 0, 12 | day_name(4) => 0, 13 | day_name(5) => 0, 14 | day_name(6) => 0, 15 | day_name(0) => 0 } 16 | end 17 | 18 | def hours 19 | (0..23).step(1).map { |h| "#{h}h" } 20 | end 21 | 22 | def months 23 | (1..12).map { |m| l('date.month_names')[m].capitalize } 24 | end 25 | 26 | def get_hour_from_date(date) 27 | return unless date 28 | 29 | time = date.to_time 30 | zone = User.current.time_zone 31 | local = if zone 32 | time.in_time_zone zone 33 | else 34 | (time.utc? ? time.localtime : time) 35 | end 36 | local.hour 37 | end 38 | 39 | def total_by_month_for(method) 40 | total = [0] * 12 41 | send(method).each { |c| total[(date_to.month - c.first.to_date.month) % 12] += c.last } 42 | total 43 | end 44 | 45 | def total_by_hour_for(method) 46 | total = [0] * 24 47 | send(method).each { |c| total[get_hour_from_date(c)] += 1 } 48 | total 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /app/views/gitolite_public_keys/_view.html.slim: -------------------------------------------------------------------------------- 1 | h3 = l :label_my_public_keys 2 | 3 | fieldset.public_key_view 4 | legend = l :label_current_user_keys 5 | = render partial: 'gitolite_public_keys/ssh_keys', 6 | locals: { ssh_keys: @gitolite_user_keys } 7 | 8 | br 9 | 10 | fieldset.public_key_view 11 | legend = l :label_current_deploy_keys 12 | = render partial: 'gitolite_public_keys/ssh_keys', 13 | locals: { ssh_keys: @gitolite_deploy_keys, deploy_keys: true } 14 | 15 | br 16 | 17 | fieldset.public_key_view 18 | legend = l :label_public_key_new 19 | = render partial: 'gitolite_public_keys/form', locals: { user: @user } 20 | 21 | - content_for :header_tags do 22 | = stylesheet_link_tag 'application', plugin: 'redmine_git_hosting' 23 | 24 | javascript: 25 | function key_type_change(element) { 26 | var idx = element.selectedIndex; 27 | if (idx == 0) { 28 | $('#key_type_options').hide(); 29 | $('#gitolite_public_key_delete_when_unused').prop("checked", true); 30 | } else { 31 | $('#key_type_options').show(); 32 | } 33 | } 34 | 35 | $(document).ready(function() { 36 | $('#gitolite_public_key_key_type').on('change', function() { 37 | key_type_change(this) 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /app/views/repository_git_config_keys/index.html.slim: -------------------------------------------------------------------------------- 1 | div 2 | - if User.current.git_allowed_to? :create_repository_git_config_keys, @repository 3 | .contextual 4 | = link_to l(:label_git_config_key_add), 5 | new_repository_git_config_key_path(@repository, type: 'git_config'), 6 | remote: true, 7 | class: 'icon icon-add' 8 | 9 | h3 10 | = l :label_git_config_keys 11 | ' 12 | = link_to_external "(#{l :label_gitolite_documentation})", 13 | 'https://gitolite.com/gitolite/git-config.html' 14 | 15 | = render 'config_keys', git_config_keys: @repository_git_config_keys 16 | 17 | - if User.current.git_allowed_to? :create_repository_git_config_keys, @repository 18 | .contextual 19 | = link_to l(:label_git_option_key_add), 20 | new_repository_git_config_key_path(@repository, type: 'git_option'), 21 | remote: true, 22 | class: 'icon icon-add' 23 | 24 | h3 25 | = l :label_git_option_keys 26 | ' 27 | = link_to_external "(#{l :label_gitolite_documentation})", 28 | 'https://gitolite.com/gitolite/options.html' 29 | 30 | = render 'config_keys', git_config_keys: @repository_git_option_keys 31 | -------------------------------------------------------------------------------- /spec/lib/redmine_git_hosting/config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../../spec_helper" 4 | 5 | describe RedmineGitHosting::Config do 6 | GITOLITE_VERSION_2 = [ 7 | 'hello redmine_gitolite_admin_id_rsa, this is gitolite v2.3.1-0-g912a8bd-dt running on git 1.7.2.5', 8 | 'hello gitolite_admin_id_rsa, this is gitolite gitolite-2.3.1 running on git 1.8.1.5', 9 | 'hello gitolite_admin_id_rsa, this is gitolite 2.3.1-1.el6 running on git 1.7.1', 10 | 'hello gitolite_admin_id_rsa, this is gitolite 2.2-1 (Debian) running on git 1.7.9.5' 11 | ].freeze 12 | 13 | GITOLITE_VERSION_3 = [ 14 | 'hello redmine_gitolite_admin_id_rsa, this is git@dev running gitolite3 v3.3-11-ga1aba93 on git 1.7.2.5' 15 | ].freeze 16 | 17 | GITOLITE_VERSION_2.each do |gitolite_version| 18 | it 'should recognize Gitolite2' do 19 | version = RedmineGitHosting::Config.find_version gitolite_version 20 | expect(version).to eq 2 21 | end 22 | end 23 | 24 | GITOLITE_VERSION_3.each do |gitolite_version| 25 | it 'should recognize Gitolite3' do 26 | version = RedmineGitHosting::Config.find_version gitolite_version 27 | expect(version).to eq 3 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/concerns/xitolite_repository_finder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module XitoliteRepositoryFinder 4 | extend ActiveSupport::Concern 5 | 6 | def find_xitolite_repository 7 | @repository = Repository::Xitolite.find find_repository_param 8 | rescue ActiveRecord::RecordNotFound 9 | render_404 10 | else 11 | @project = @repository.project 12 | render_404 if @project.nil? 13 | end 14 | 15 | def find_xitolite_repository_by_path 16 | repo_path = "#{params[:repo_path]}.git" 17 | repository = Repository::Xitolite.find_by_path repo_path, loose: true 18 | if repository.nil? 19 | RedmineGitHosting.logger.error "GoRedirector : repository not found at path : '#{repo_path}', " \ 20 | 'exiting!' 21 | render_404 22 | elsif !repository.go_access_available? 23 | RedmineGitHosting.logger.error "GoRedirector : GoAccess is disabled for this repository '#{repository.gitolite_repository_name}', " \ 24 | 'exiting!' 25 | render_403 26 | else 27 | RedmineGitHosting.logger.info "GoRedirector : access granted for repository '#{repository.gitolite_repository_name}'" 28 | @repository = repository 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/views/repository_post_receive_urls/_form.html.slim: -------------------------------------------------------------------------------- 1 | .flash-messages = error_messages_for 'post_receive_url' 2 | 3 | .box 4 | p = f.url_field :url, 5 | size: 65, 6 | placeholder: 'https://example.com', 7 | required: true 8 | 9 | p = f.check_box :active 10 | 11 | p = f.select :mode, 12 | [['Github-style POST', :github], 13 | ['Empty POST request', :post], 14 | ['Empty GET request', :get]], 15 | {}, 16 | onchange: 'post_mode_change(this); return false;' 17 | 18 | #payload_options style="#{@post_receive_url.github_mode? ? '' : 'display: none;'}" 19 | p = f.check_box :split_payloads 20 | 21 | - if @repository.branches.any? 22 | p = f.check_box :use_triggers, onchange: 'trigger_mode_change(this); return false;' 23 | 24 | #triggers_options style="#{@post_receive_url.use_triggers? ? '' : 'display: none;'}" 25 | = hidden_field_tag 'repository_post_receive_url[triggers][]', '' 26 | - @repository.branches.each do |branch| 27 | p 28 | label 29 | = check_box_tag 'repository_post_receive_url[triggers][]', branch.to_s, @post_receive_url.triggers.include?(branch.to_s) 30 | = branch.to_s 31 | -------------------------------------------------------------------------------- /app/use_cases/repository_mirrors/push.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RepositoryMirrors 4 | class Push < Base 5 | def call 6 | push! 7 | end 8 | 9 | def push! 10 | begin 11 | push_message = repository.mirror_push(*command) 12 | push_failed = false 13 | rescue RedmineGitHosting::Error::GitoliteCommandException => e 14 | push_message = e.output 15 | push_failed = true 16 | end 17 | 18 | [push_failed, push_message] 19 | end 20 | 21 | def command 22 | [mirror.url, branch, push_args] 23 | end 24 | 25 | private 26 | 27 | def push_args 28 | mirror.mirror_mode? ? ['--mirror'] : mirror_args 29 | end 30 | 31 | def mirror_args 32 | push_args = [] 33 | push_args << '--force' if mirror.force_mode? 34 | push_args << '--all' if mirror.include_all_branches? 35 | push_args << '--tags' if mirror.include_all_tags? 36 | push_args 37 | end 38 | 39 | def branch 40 | dequote(mirror.explicit_refspec).to_s if mirror.explicit_refspec.present? 41 | end 42 | 43 | # Put backquote in front of crucial characters 44 | def dequote(in_string) 45 | in_string.gsub(/[$,"\\\n]/) { |x| '\\' + x } 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/utils/git.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Utils 5 | module Git 6 | extend self 7 | 8 | REF_COMPONENT_PART = '[\\.\\-\\w_\\*]+' 9 | REF_COMPONENT_REGEX = %r{\A(refs/)?((#{REF_COMPONENT_PART})/)?(#{REF_COMPONENT_PART}(/#{REF_COMPONENT_PART})*)\z}.freeze 10 | 11 | # Parse a reference component. Two possibilities: 12 | # 13 | # 1) refs/type/name 14 | # 2) name 15 | # 16 | def parse_refspec(spec) 17 | parsed_refspec = spec.match REF_COMPONENT_REGEX 18 | return if parsed_refspec.nil? 19 | 20 | if parsed_refspec[1] 21 | # Should be first class. If no type component, return fail 22 | { type: parsed_refspec[3], name: parsed_refspec[4] } if parsed_refspec[3] 23 | elsif parsed_refspec[3] 24 | { type: nil, name: "#{parsed_refspec[3]}/#{parsed_refspec[4]}" } 25 | else 26 | { type: nil, name: parsed_refspec[4] } 27 | end 28 | end 29 | 30 | def author_name(committer) 31 | committer.gsub(/\A([^<]+)\s+.*\z/, '\1') 32 | end 33 | 34 | def author_email(committer) 35 | committer.gsub(/\A.*<([^>]+)>.*\z/, '\1') 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/factories/role.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :role do 5 | name { 'Manager' } 6 | builtin { 0 } 7 | issues_visibility { 'all' } 8 | position { 1 } 9 | permissions do 10 | %i[add_project edit_project close_project select_project_modules manage_members 11 | manage_versions manage_categories view_issues add_issues edit_issues manage_issue_relations 12 | manage_subtasks add_issue_notes move_issues delete_issues view_issue_watchers add_issue_watchers 13 | set_issues_private set_notes_private view_private_notes delete_issue_watchers 14 | manage_public_queries save_queries view_gantt view_calendar log_time view_time_entries 15 | edit_time_entries delete_time_entries manage_news comment_news view_documents 16 | add_documents edit_documents delete_documents view_wiki_pages export_wiki_pages 17 | view_wiki_edits edit_wiki_pages delete_wiki_pages_attachments protect_wiki_pages 18 | delete_wiki_pages rename_wiki_pages add_messages edit_messages delete_messages 19 | manage_boards view_files manage_files browse_repository manage_repository view_changesets 20 | manage_related_issues manage_project_activities create_gitolite_ssh_key commit_access] 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/views/settings/redmine_git_hosting/_gitolite_config_storage.html.slim: -------------------------------------------------------------------------------- 1 | / Gitolite Storage Config 2 | - gitolite_global_storage_dir = RedmineGitHosting::Config.get_setting :gitolite_global_storage_dir 3 | - gitolite_redmine_storage_dir = RedmineGitHosting::Config.get_setting :gitolite_redmine_storage_dir 4 | - gitolite_recycle_bin_dir = RedmineGitHosting::Config.get_setting :gitolite_recycle_bin_dir 5 | 6 | h3 = l :label_gitolite_storage_config 7 | 8 | p 9 | label = l :label_gitolite_global_storage_dir 10 | = text_field_tag 'settings[gitolite_global_storage_dir]', 11 | gitolite_global_storage_dir, 12 | size: 60, 13 | required: true 14 | br 15 | em.info 16 | = l :label_gitolite_global_storage_dir_desc 17 | 18 | p 19 | label = l :label_gitolite_recycle_bin_dir 20 | = text_field_tag 'settings[gitolite_recycle_bin_dir]', 21 | gitolite_recycle_bin_dir, 22 | size: 60, 23 | required: true 24 | br 25 | em.info 26 | = l :label_gitolite_recycle_bin_dir_desc 27 | 28 | p 29 | label = l :label_gitolite_redmine_storage_dir 30 | = text_field_tag 'settings[gitolite_redmine_storage_dir]', gitolite_redmine_storage_dir, size: 60 31 | br 32 | em.info 33 | = l :label_gitolite_redmine_storage_dir_desc 34 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_handlers/repositories/add_repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteHandlers 5 | module Repositories 6 | class AddRepository < Base 7 | def call 8 | if !configuration_exists? 9 | # Create repository in Gitolite 10 | log_repo_not_exist 'create it ...' 11 | create_repository_config 12 | 13 | elsif configuration_exists? && force 14 | # Recreate repository in Gitolite 15 | log_repo_already_exist 'force mode !' 16 | recreate_repository_config 17 | 18 | else 19 | log_repo_already_exist 'exit !' 20 | end 21 | end 22 | 23 | def gitolite_repo_name 24 | repository.gitolite_repository_name 25 | end 26 | 27 | def gitolite_repo_path 28 | repository.gitolite_repository_path 29 | end 30 | 31 | attr_reader :force 32 | 33 | def initialize(gitolite_config, repository, context, **options) 34 | super(gitolite_config, repository, context, **options) 35 | @force = @options.delete(:force) { false } 36 | @old_perms = @options.delete(:old_perms) { {} } 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/models/repository_git_config_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepositoryGitConfigKey < ActiveRecord::Base 4 | include Redmine::SafeAttributes 5 | 6 | ## Attributes 7 | safe_attributes 'type', 'key', 'value' 8 | 9 | ## Relations 10 | belongs_to :repository 11 | 12 | ## Validations 13 | validates :repository_id, presence: true 14 | validates :type, presence: true, inclusion: { in: ['RepositoryGitConfigKey::GitConfig', 'RepositoryGitConfigKey::Option'] } 15 | validates :value, presence: true 16 | 17 | ## Callbacks 18 | after_save :check_if_key_changed 19 | 20 | ## Virtual attribute 21 | attr_accessor :key_has_changed 22 | attr_accessor :old_key 23 | 24 | # Syntaxic sugar 25 | def key_has_changed? 26 | key_has_changed 27 | end 28 | 29 | private 30 | 31 | # This is Rails method : saved_changes 32 | # However, the value is cleared before passing the object to the controller. 33 | # We need to save it in virtual attribute to trigger Gitolite resync if changed. 34 | # 35 | def check_if_key_changed 36 | if saved_changes&.key? :key 37 | self.key_has_changed = true 38 | self.old_key = saved_changes[:key][1] 39 | else 40 | self.key_has_changed = false 41 | self.old_key = '' 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/global/common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Global 6 | module Common 7 | def redmine_gitolite_key 8 | 'redmine_gitolite_admin_id_rsa' 9 | end 10 | 11 | def all_repository 12 | '@all' 13 | end 14 | 15 | def all_repository_config 16 | gitolite_config.repos[all_repository] 17 | end 18 | 19 | def rw_access_config 20 | repo_conf = ::Gitolite::Config::Repo.new all_repository 21 | repo_conf.permissions = rw_access_perms 22 | repo_conf 23 | end 24 | 25 | def rw_access_perms 26 | permissions = {} 27 | permissions['RW+'] = {} 28 | permissions['RW+'][''] = [redmine_gitolite_key] 29 | [permissions] 30 | end 31 | 32 | def repo_conf 33 | all_repository_config 34 | end 35 | 36 | def perms 37 | repo_conf.permissions.select { |p| p.key? 'RW+' } 38 | end 39 | 40 | # RedmineGitHosting key can act on any refspec ('') so it should be in that 'subgroup' 41 | # 42 | def users 43 | perms[0]['RW+'][''] 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins/extenders/git_annex_creator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting::Plugins::Extenders 4 | class GitAnnexCreator < BaseExtender 5 | attr_reader :enable_git_annex 6 | 7 | def initialize(repository, **options) 8 | super(repository, **options) 9 | @enable_git_annex = options.delete(:enable_git_annex) { false } 10 | end 11 | 12 | def post_create 13 | return unless installable? 14 | 15 | if git_annex_installed? 16 | logger.warn "GitAnnex already exists in path '#{gitolite_repo_path}'" 17 | else 18 | install_git_annex 19 | end 20 | end 21 | 22 | private 23 | 24 | def installable? 25 | enable_git_annex? && !recovered? 26 | end 27 | 28 | def enable_git_annex? 29 | RedminePluginKit.true? enable_git_annex 30 | end 31 | 32 | def git_annex_installed? 33 | directory_exists? File.join(gitolite_repo_path, 'annex') 34 | end 35 | 36 | def install_git_annex 37 | sudo_git 'annex', 'init' 38 | rescue RedmineGitHosting::Error::GitoliteCommandException 39 | logger.error "Error while enabling GitAnnex for repository '#{gitolite_repo_name}'" 40 | else 41 | logger.info "GitAnnex successfully enabled for repository '#{gitolite_repo_name}'" 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/redcarpet_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'html/pipeline/filter' 4 | require 'html/pipeline/text_filter' 5 | require 'redcarpet' 6 | require 'rouge' 7 | require 'rouge/plugins/redcarpet' 8 | 9 | module RedmineGitHosting 10 | class HTMLwithRouge < Redcarpet::Render::HTML 11 | include Rouge::Plugins::Redcarpet 12 | end 13 | 14 | class RedcarpetFilter < HTML::Pipeline::TextFilter 15 | def initialize(text, context = nil, result = nil) 16 | super text, context, result 17 | @text = @text.delete "\r" 18 | end 19 | 20 | # Convert Markdown to HTML using the best available implementation 21 | # and convert into a DocumentFragment. 22 | # 23 | def call 24 | html = self.class.renderer.render @text 25 | html.rstrip! 26 | html 27 | end 28 | 29 | def self.renderer 30 | @renderer ||= Redcarpet::Markdown.new HTMLwithRouge, markdown_options 31 | end 32 | 33 | def self.markdown_options 34 | @markdown_options ||= { 35 | fenced_code_blocks: true, 36 | lax_spacing: true, 37 | strikethrough: true, 38 | autolink: true, 39 | tables: true, 40 | underline: true, 41 | highlight: true 42 | }.freeze 43 | end 44 | 45 | private_class_method :markdown_options 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/gitolite_wrappers/projects/common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module GitoliteWrappers 5 | module Projects 6 | module Common 7 | def handle_repositories_move(projects) 8 | repo_list = [] 9 | delete_parent_path = [] 10 | projects.reverse_each do |project| 11 | project.gitolite_repos.reverse_each do |repository| 12 | repo_list << repository.gitolite_repository_name 13 | delete_parent_path << move_gitolite_repository(repository) 14 | end 15 | gitolite_admin_repo_commit "#{context} : #{project.identifier} | #{repo_list}" 16 | end 17 | delete_parent_path 18 | end 19 | 20 | def clean_path(path_list) 21 | path_list.compact.uniq.sort.reverse_each do |path| 22 | rmdir path 23 | end 24 | end 25 | 26 | def rmdir(path) 27 | logger.info "#{context} : cleaning repository path : '#{path}'" 28 | begin 29 | RedmineGitHosting::Commands.sudo_rmdir path 30 | rescue RedmineGitHosting::Error::GitoliteCommandException 31 | logger.error "#{context} : error while cleaning repository path '#{path}'" 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/views/gitolite_public_keys/_form.html.slim: -------------------------------------------------------------------------------- 1 | #validation_messages 2 | = error_messages_for 'gitolite_public_key' 3 | 4 | = labelled_form_for :gitolite_public_key, GitolitePublicKey.new, 5 | url: { controller: 'gitolite_public_keys', action: 'create', user_id: params[:id], tab: params[:id] && 'keys' }, 6 | html: { method: :post } do |f| 7 | p 8 | = f.text_field :title, label: :label_identifier_can_be_arbitrary, required: true, style: 'width: 97%;' 9 | 10 | - if can_create_deployment_keys_for_some_project @user 11 | p 12 | = f.select :key_type, 13 | options_for_select([[l(:label_user_key), 0], [l(:label_deploy_key), 1]]), 14 | { required: true, label: :label_key_type }, 15 | { class: 'select_key_type' } 16 | #key_type_options style="display: none;" 17 | p 18 | = f.check_box :delete_when_unused, required: true, label: :label_deployment_credential_delete_when_unused 19 | p 20 | = f.text_area :key, label: :label_public_key, required: true, 21 | style: 'width: 97%; height: 200px; overflow: auto;', 22 | cols: nil, 23 | rows: nil 24 | em 25 | = l :label_cut_and_paste 26 | br 27 | br 28 | = submit_tag l(:button_create), name: 'create_button' 29 | ' 30 | = cancel_button_tag my_account_path 31 | -------------------------------------------------------------------------------- /app/reports/report_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ReportQuery 4 | private 5 | 6 | def all_changesets 7 | @all_changesets ||= Changeset.where repository_id: repository.id 8 | end 9 | 10 | def all_changes 11 | @all_changes ||= Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", repository.id) 12 | end 13 | 14 | def all_commits_by_day 15 | @all_commits_by_day ||= all_changesets.group :commit_date 16 | end 17 | 18 | def all_changes_by_day 19 | @all_changes_by_day ||= all_changes.group :commit_date 20 | end 21 | 22 | def redmine_committers 23 | @redmine_committers ||= all_changesets.where.not(user_id: nil).distinct.count(:user_id) 24 | end 25 | 26 | def external_committers 27 | @external_committers ||= all_changesets.where(user_id: nil).distinct.count(:committer) 28 | end 29 | 30 | def commits_by_day 31 | @commits_by_day ||= all_commits_by_day.count 32 | end 33 | 34 | def changes_by_day 35 | @changes_by_day ||= all_changes_by_day.count 36 | end 37 | 38 | def commits_by_hour 39 | @commits_by_hour ||= all_changesets.map(&:committed_on) 40 | end 41 | 42 | def commits_by_author 43 | @commits_by_author ||= all_changesets.group(:committer).count 44 | end 45 | 46 | def changes_by_author 47 | @changes_by_author ||= all_changes.group(:committer).count 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/models/project_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../spec_helper" 4 | 5 | describe Project do 6 | before :all do 7 | @project = create :project 8 | @git_repo_1 = create_git_repository project: @project, is_default: true 9 | @git_repo_2 = create_git_repository project: @project, identifier: 'git-repo-test' 10 | @svn_repo_1 = create_svn_repository project: @project, identifier: 'svn-repo-test', url: 'http://svn-repo-test' 11 | end 12 | 13 | subject { @project } 14 | 15 | ## Test relations 16 | it { should respond_to(:gitolite_repos) } 17 | it { should respond_to(:repo_blank_ident) } 18 | 19 | it 'should have 1 repository with blank ident' do 20 | expect(@project.repo_blank_ident).to eq @git_repo_1 21 | end 22 | 23 | it 'should have 2 Git repositories' do 24 | expect(@project.gitolite_repos).to include @git_repo_1, @git_repo_2 25 | end 26 | 27 | it 'should have 3 repositories' do 28 | expect(@project.repositories).to include @git_repo_1, @git_repo_2, @svn_repo_1 29 | end 30 | 31 | it 'should not match existing repository identifier' do 32 | expect(build(:project, identifier: 'git-repo-test')).to be_invalid 33 | end 34 | 35 | it 'should not match Gitolite Admin repository identifier' do 36 | expect(build(:project, identifier: 'gitolite-admin')).to be_invalid 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/utils/ssh.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Utils 5 | module Ssh 6 | extend self 7 | 8 | def ssh_fingerprint(key) 9 | file = Tempfile.new 'keytest' 10 | file.write key 11 | file.close 12 | 13 | begin 14 | output = Utils::Exec.capture 'ssh-keygen', ['-l', '-f', file.path] 15 | rescue RedmineGitHosting::Error::GitoliteCommandException 16 | raise RedmineGitHosting::Error::InvalidSshKey, "Invalid Ssh Key : #{key}" 17 | else 18 | output.split[1] 19 | ensure 20 | file.unlink 21 | end 22 | end 23 | 24 | def sanitize_ssh_key(key) 25 | # First -- let the first control char or space stand (to divide key type from key) 26 | # Really, this is catching a special case in which there is a \n between type and key. 27 | # Most common case turns first space back into space.... 28 | key = key.sub(/[ \r\n\t]/, ' ') 29 | 30 | # Next, if comment divided from key by control char, let that one stand as well 31 | # We can only tell this if there is an "=" in the key. So, won't help 1/3 times. 32 | key = key.sub(/=[ \r\n\t]/, '= ') 33 | 34 | # Delete any remaining control characters.... 35 | key.gsub(/[\a\r\n\t]/, '').strip 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/controllers/download_git_revision_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DownloadGitRevisionController < ApplicationController 4 | include XitoliteRepositoryFinder 5 | 6 | before_action :find_xitolite_repository 7 | before_action :can_download_git_revision 8 | before_action :set_download 9 | before_action :validate_download 10 | 11 | helper :git_hosting 12 | 13 | def index 14 | send_data @download.content, filename: @download.filename, type: @download.content_type 15 | rescue StandardError => e 16 | flash.now[:error] = l :git_archive_timeout, timeout: e.output 17 | render_404 18 | end 19 | 20 | private 21 | 22 | def find_repository_param 23 | params[:id] 24 | end 25 | 26 | def can_download_git_revision 27 | render_403 unless User.current.allowed_to_download? @repository 28 | end 29 | 30 | def set_download 31 | @download = Repositories::DownloadRevision.new @repository, download_revision, download_format 32 | end 33 | 34 | def download_revision 35 | @download_revision ||= (params[:rev] || 'master') 36 | end 37 | 38 | def download_format 39 | @download_format ||= (params[:download_format] || 'tar') 40 | end 41 | 42 | def validate_download 43 | return if @download.valid_commit? 44 | 45 | flash.now[:error] = l :error_download_revision_no_such_commit, commit: download_revision 46 | render_404 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /.slim-lint.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | LineLength: 3 | max: 140 4 | RuboCop: 5 | ignored_cops: 6 | - Layout/ArgumentAlignment 7 | - Layout/ArrayAlignment 8 | - Layout/BlockEndNewline 9 | - Layout/EmptyLineAfterGuardClause 10 | - Layout/HashAlignment 11 | - Layout/IndentationConsistency 12 | - Layout/IndentationWidth 13 | - Layout/IndentFirstArgument 14 | - Layout/IndentFirstArrayElement 15 | - Layout/IndentFirstHashElement 16 | - Layout/MultilineArrayBraceLayout 17 | - Layout/MultilineAssignmentLayout 18 | - Layout/MultilineBlockLayout 19 | - Layout/MultilineHashBraceLayout 20 | - Layout/MultilineMethodCallBraceLayout 21 | - Layout/MultilineMethodCallIndentation 22 | - Layout/MultilineMethodDefinitionBraceLayout 23 | - Layout/MultilineOperationIndentation 24 | - Layout/TrailingBlankLines 25 | - Layout/TrailingEmptyLines 26 | - Layout/TrailingWhitespace 27 | - Lint/BlockAlignment 28 | - Lint/EndAlignment 29 | - Lint/Void 30 | - Metrics/BlockLength 31 | - Metrics/BlockNesting 32 | - Metrics/LineLength 33 | - Naming/FileName 34 | - Rails/OutputSafety 35 | - Style/ConditionalAssignment 36 | - Style/FrozenStringLiteralComment 37 | - Style/IdenticalConditionalBranches 38 | - Style/IfUnlessModifier 39 | - Style/Next 40 | - Style/WhileUntilModifier 41 | -------------------------------------------------------------------------------- /assets/javascripts/git_urls.js: -------------------------------------------------------------------------------- 1 | // GIT URLS 2 | function updateUrl(element) { 3 | var url = $(element).data('url'); 4 | var target = $(element).data('target'); 5 | var committer = $(element).data('committer'); 6 | $('#git_url_text_' + target).val(url); 7 | $('#git_url_permissions_' + target).html(committer); 8 | $(element).parent().find('li').removeClass('selected'); 9 | $(element).addClass('selected'); 10 | } 11 | 12 | function setGitUrls(elements) { 13 | $(elements).each(function(index, element){ 14 | $(element).on('click', function(){ 15 | updateUrl($(this)); 16 | }); 17 | }); 18 | } 19 | 20 | function setFirstGitUrl(elements) { 21 | $(elements).each(function(index, element){ 22 | var first_url = $(element).children().first(); 23 | updateUrl(first_url); 24 | }); 25 | } 26 | 27 | // GIT INSTRUCTIONS 28 | function updateInstructionUrl(element) { 29 | var url = $(element).data('url'); 30 | var committer = $(element).data('committer'); 31 | $('.git_url_access').html(url); 32 | if (committer == 'RW') { 33 | $('#repository_setup').show(); 34 | } else { 35 | $('#repository_setup').hide(); 36 | } 37 | } 38 | 39 | function setGitUrlsInstructions(elements) { 40 | $(elements).each(function(index, element){ 41 | if (index == 0){ 42 | updateInstructionUrl(element); 43 | }; 44 | $(element).on('click', function(){ 45 | updateInstructionUrl($(this)); 46 | }); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /custom_hooks.rb.example: -------------------------------------------------------------------------------- 1 | # You can declare here you own hooks to install globally in Gitolite. 2 | # You must set the source directory of the files with the *source_dir* method and 3 | # declare your hooks with *gitolite_hook* method. 4 | # 5 | # *RedmineGitHosting::GitoliteHooks.register_hooks* can be called multiple times 6 | # with a different *source_dir*. 7 | # 8 | # *name* : the hook name (just a name to identify the hook) 9 | # *source* : the source path concatenated with *source_dir* 10 | # *destination* : the destination path on Gitolite side. 11 | # 12 | # The *destination* must be relative. 13 | # The final destination will depend on your Gitolite version : 14 | # 15 | # Gitolite v2 : /.gitolite/hooks/common 16 | # Gitolite v3 : /local/hooks/common/ 17 | # 18 | # RedmineGitHosting::GitoliteHooks.register_hooks do 19 | # source_dir '/tmp/global-hooks' 20 | # 21 | # gitolite_hook do 22 | # name 'global/check-branch' 23 | # source 'pre-receive/check_branch.rb' 24 | # destination 'pre-receive.d/check_branch.rb' 25 | # executable true 26 | # end 27 | # end 28 | # 29 | # RedmineGitHosting::GitoliteHooks.register_hooks do 30 | # source_dir '/tmp/custom-hooks' 31 | # 32 | # gitolite_hook do 33 | # name 'custom/notify-users' 34 | # source 'post-receive/notify_users.rb' 35 | # destination 'post-receive.d/notify_users.rb' 36 | # executable true 37 | # end 38 | # end 39 | -------------------------------------------------------------------------------- /app/views/repository_protected_branches/_form.html.slim: -------------------------------------------------------------------------------- 1 | .flash-messages = error_messages_for 'protected_branch' 2 | 3 | .box 4 | p = f.text_field :path, required: true, size: 65, label: l(:label_branch_path) 5 | p = f.select :permissions, 6 | options_for_select(RepositoryProtectedBranche::VALID_PERMS, @protected_branch.permissions), 7 | required: true, 8 | label: :label_permissions 9 | 10 | = hidden_field_tag 'user_ids[]', '' 11 | = hidden_field_tag 'group_ids[]', '' 12 | 13 | .container 14 | .row 15 | .col-md-6 16 | h4 17 | = font_awesome_icon 'fas_user', post_text: l(:label_user_plural), class: 'fa-lg' 18 | 19 | - @project.users_available.each do |user| 20 | p style='padding: 0px;' 21 | label style='margin-left: 0; width: auto; font-weight: lighter;' 22 | = check_box_tag 'user_ids[]', user.id, @protected_branch.users.include?(user) 23 | = user 24 | br 25 | 26 | .col-md-6 27 | h4 28 | = font_awesome_icon 'fas_users', post_text: l(:label_group_plural), class: 'fa-lg' 29 | 30 | - @project.groups_available.each do |group| 31 | p style= 'padding: 0px;' 32 | label style='margin-left: 0; width: auto; font-weight: lighter;' 33 | = check_box_tag 'group_ids[]', group.id, @protected_branch.groups.include?(group) 34 | = group 35 | br 36 | -------------------------------------------------------------------------------- /app/forms/plugin_settings_validation/ssh_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PluginSettingsValidation 4 | module SshConfig 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | # Gitolite SSH Config 9 | add_accessor :gitolite_user, 10 | :gitolite_server_host, 11 | :gitolite_server_port, 12 | :gitolite_ssh_private_key, 13 | :gitolite_ssh_public_key 14 | 15 | before_validation do 16 | self.gitolite_user = strip_value gitolite_user 17 | self.gitolite_server_host = strip_value gitolite_server_host 18 | self.gitolite_server_port = strip_value gitolite_server_port 19 | 20 | self.gitolite_ssh_private_key = strip_value gitolite_ssh_private_key 21 | self.gitolite_ssh_public_key = strip_value gitolite_ssh_public_key 22 | end 23 | 24 | validates :gitolite_user, :gitolite_server_host, :gitolite_ssh_private_key, :gitolite_ssh_public_key, 25 | presence: true 26 | validates :gitolite_server_port, 27 | presence: true, 28 | numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 65_536 } 29 | 30 | validates_each :gitolite_ssh_private_key, :gitolite_ssh_public_key do |record, attr, value| 31 | record.errors.add attr, 'must exists on filesystem' unless File.exist? value 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/settings/redmine_git_hosting/_gitolite_config_cache.html.slim: -------------------------------------------------------------------------------- 1 | / Gitolite Cache Config 2 | ruby: 3 | gitolite_cache_max_time = RedmineGitHosting::Config.get_setting :gitolite_cache_max_time 4 | gitolite_cache_max_size = RedmineGitHosting::Config.get_setting :gitolite_cache_max_size 5 | gitolite_cache_max_elements = RedmineGitHosting::Config.get_setting :gitolite_cache_max_elements 6 | gitolite_cache_adapter = RedmineGitHosting::Config.get_setting :gitolite_cache_adapter 7 | 8 | h3 = l :label_gitolite_cache_config 9 | 10 | p 11 | label = l :label_gitolite_cache_max_time 12 | = select_tag 'settings[gitolite_cache_max_time]', options_for_select(git_cache_options, gitolite_cache_max_time) 13 | br 14 | 15 | p 16 | label = l :label_gitolite_cache_max_size 17 | = number_field_tag 'settings[gitolite_cache_max_size]', 18 | gitolite_cache_max_size, 19 | size: 20, 20 | min: -1 21 | em< 22 | ' MB 23 | br 24 | 25 | p 26 | label = l :label_gitolite_cache_max_elements 27 | = number_field_tag 'settings[gitolite_cache_max_elements]', 28 | gitolite_cache_max_elements, 29 | size: 20, 30 | min: 1 31 | br 32 | 33 | p 34 | label = l :label_gitolite_cache_adapter 35 | = select_tag 'settings[gitolite_cache_adapter]', options_for_select(GitCache::CACHE_ADAPTERS, gitolite_cache_adapter) 36 | br 37 | em = l :label_gitolite_cache_adapter_desc 38 | -------------------------------------------------------------------------------- /spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../spec_helper" 4 | 5 | require 'sshkey' 6 | 7 | describe UsersController do 8 | describe 'GET #edit' do 9 | context 'with git hosting patch' do 10 | let(:user) { create_admin_user } 11 | let :user_key do 12 | create_ssh_key(user_id: user.id, 13 | title: 'user_key', 14 | key: SSHKey.generate(comment: 'faker_user_key@john_doe').ssh_public_key, 15 | key_type: 0) 16 | end 17 | let :deploy_key do 18 | create_ssh_key(user_id: user.id, 19 | title: 'deploy_key', 20 | key: SSHKey.generate(comment: 'faker_deploy_key@john_doe').ssh_public_key, 21 | key_type: 1) 22 | end 23 | 24 | it 'populates an array of gitolite_user_keys' do 25 | set_session_user user 26 | get :edit, 27 | params: { id: user.id } 28 | 29 | assert_response :success 30 | expect(@controller.view_assigns['gitolite_user_keys']).to eq [user_key] 31 | end 32 | 33 | # it 'populates an array of gitolite_deploy_keys' do 34 | # set_session_user(user) 35 | # get :edit, 36 | # params: { id: user.id } 37 | # expect(@controller.view_assigns['gitolite_deploy_keys']).to eq [deploy_key] 38 | # end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib/redmine_git_hosting/utils/git_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path "#{File.dirname __FILE__}/../../../spec_helper" 4 | 5 | describe RedmineGitHosting::Utils::Git do 6 | include RedmineGitHosting::Utils::Git 7 | 8 | describe '.parse_refspec' do 9 | context 'it should accept different refspec format' do 10 | it 'should accept ' do 11 | expect(parse_refspec('dev')).to eq({ type: nil, name: 'dev' }) 12 | end 13 | 14 | it 'should accept a branch path' do 15 | expect(parse_refspec('refs/branches/dev')).to eq({ type: 'branches', name: 'dev' }) 16 | end 17 | 18 | it 'should accept the wildcard param (*)' do 19 | expect(parse_refspec('refs/branches/dev/*')).to eq({ type: 'branches', name: 'dev/*' }) 20 | expect(parse_refspec('refs/tags/*')).to eq({ type: 'tags', name: '*' }) 21 | end 22 | 23 | it 'should parse different refspec path' do 24 | expect(parse_refspec('refs/remotes/origin/experiment/*')).to eq({ type: 'remotes', name: 'origin/experiment/*' }) 25 | expect(parse_refspec('refs/remotes/origin/experiment/master')).to eq({ type: 'remotes', name: 'origin/experiment/master' }) 26 | expect(parse_refspec('refs/remotes/origin/experiment')).to eq({ type: 'remotes', name: 'origin/experiment' }) 27 | expect(parse_refspec('refs/heads/experiment')).to eq({ type: 'heads', name: 'experiment' }) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/config/redmine_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting 4 | module Config 5 | module RedmineConfig 6 | extend self 7 | 8 | def gitolite_use_sidekiq? 9 | get_setting :gitolite_use_sidekiq, true 10 | end 11 | 12 | def sidekiq_available? 13 | @sidekiq_available ||= 14 | begin 15 | require 'sidekiq' 16 | require 'sidekiq/api' 17 | rescue LoadError 18 | false 19 | else 20 | true 21 | end 22 | end 23 | 24 | def hierarchical_organisation? 25 | get_setting :hierarchical_organisation, true 26 | end 27 | 28 | def unique_repo_identifier? 29 | get_setting :unique_repo_identifier, true 30 | end 31 | 32 | def all_projects_use_git? 33 | get_setting :all_projects_use_git, true 34 | end 35 | 36 | def init_repositories_on_create? 37 | get_setting :init_repositories_on_create, true 38 | end 39 | 40 | def download_revision_enabled? 41 | get_setting :download_revision_enabled, true 42 | end 43 | 44 | def delete_git_repositories? 45 | get_setting :delete_git_repositories, true 46 | end 47 | 48 | def gitolite_recycle_bin_expiration_time 49 | (get_setting(:gitolite_recycle_bin_expiration_time).to_f * 60).to_i 50 | end 51 | end 52 | 53 | extend Config::RedmineConfig 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /db/migrate/20111123214911_add_settings_to_plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddSettingsToPlugin < ActiveRecord::Migration[4.2] 4 | def up 5 | # Add some new settings to settings page, if they don't exist 6 | valuehash = Setting.plugin_redmine_git_hosting.clone 7 | valuehash['gitRecycleBasePath'] ||= 'recycle_bin/' 8 | valuehash['gitRecycleExpireTime'] ||= '24.0' 9 | valuehash['gitLockWaitTime'] ||= '10' 10 | valuehash['httpServer'] ||= RedmineGitHosting::Config.my_root_url ssl: false 11 | 12 | if Setting.plugin_redmine_git_hosting != valuehash 13 | Setting.plugin_redmine_git_hosting = valuehash 14 | say 'Added redmine_git_hosting settings: gitRecycleBasePath, getRecycleExpireTime, getLockWaitTime, httpServer' 15 | end 16 | rescue StandardError => e 17 | say e.message 18 | end 19 | 20 | def down 21 | # Remove above settings from plugin page 22 | valuehash = Setting.plugin_redmine_git_hosting.clone 23 | valuehash.delete 'gitRecycleBasePath' 24 | valuehash.delete 'gitRecycleExpireTime' 25 | valuehash.delete 'gitLockWaitTime' 26 | valuehash.delete 'gitLockWaitTime' 27 | 28 | if Setting.plugin_redmine_git_hosting != valuehash 29 | Setting.plugin_redmine_git_hosting = valuehash 30 | say 'Removed redmine_git_hosting settings: gitRecycleBasePath, getRecycleExpireTime, getLockWaitTime, httpServer' 31 | end 32 | Setting.plugin_redmine_git_hosting = valuehash 33 | rescue StandardError => e 34 | say e.message 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/repository_git_extras_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepositoryGitExtrasController < RedmineGitHostingController 4 | include RedmineGitHosting::GitoliteAccessor::Methods 5 | 6 | skip_before_action :set_current_tab 7 | 8 | helper :extend_repositories 9 | 10 | def update 11 | set_git_extra 12 | @git_extra.safe_attributes = params[:repository_git_extra] 13 | 14 | if @git_extra.save 15 | flash.now[:notice] = l :notice_gitolite_extra_updated 16 | gitolite_accessor.update_repository @repository, update_default_branch: @git_extra.default_branch_has_changed? 17 | else 18 | flash.now[:error] = l :notice_gitolite_extra_update_failed 19 | end 20 | end 21 | 22 | def sort_urls 23 | set_git_extra 24 | return unless request.post? 25 | 26 | if @git_extra.update urls_order: params[:repository_git_extra] 27 | flash.now[:notice] = l :notice_gitolite_extra_updated 28 | else 29 | flash.now[:error] = l :notice_gitolite_extra_update_failed 30 | end 31 | end 32 | 33 | def move 34 | @move_repository_form = MoveRepositoryForm.new @repository 35 | return unless request.post? 36 | 37 | @move_repository_form = MoveRepositoryForm.new @repository 38 | return unless @move_repository_form.submit params[:repository_mover] 39 | 40 | redirect_to settings_project_path(@repository.project, tab: 'repositories') 41 | end 42 | 43 | private 44 | 45 | def set_git_extra 46 | @git_extra = @repository.extra 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/helpers/repository_mirrors_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RepositoryMirrorsHelper 4 | # Mirror Mode 5 | def mirror_mode(mirror) 6 | [l(:label_mirror_full_mirror), 7 | l(:label_mirror_forced_update), 8 | l(:label_mirror_fast_forward)][mirror.push_mode] 9 | end 10 | 11 | # Refspec for mirrors 12 | def refspec(mirror, max_refspec = 0) 13 | if mirror.mirror_mode? 14 | l :all_references 15 | else 16 | result = [] 17 | result << tag.li(l(:all_branches)) if mirror.include_all_branches 18 | result << tag.li(l(:all_tags)) if mirror.include_all_tags 19 | result << tag.li(mirror.explicit_refspec) if max_refspec.zero? || ((1..max_refspec).include? mirror.explicit_refspec.length) 20 | result << tag.li(l(:explicit)) if max_refspec.positive? && (mirror.explicit_refspec.length > max_refspec) 21 | 22 | tag.ul safe_join(result), class: 'list-unstyled' if result.any? 23 | end 24 | end 25 | 26 | def mirrors_options 27 | [ 28 | [l(:label_mirror_full_mirror), 0], 29 | [l(:label_mirror_forced_update), 1], 30 | [l(:label_mirror_fast_forward), 2] 31 | ] 32 | end 33 | 34 | def render_push_state(mirror, error) 35 | if error 36 | status = l :label_mirror_push_fail 37 | status_css = 'error' 38 | else 39 | status = l :label_mirror_push_sucess 40 | status_css = 'success' 41 | end 42 | 43 | t :label_mirror_push_info_html, mirror_url: mirror.url, status: status, status_css: status_css 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /app/reports/repository_global_stats.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepositoryGlobalStats < ReportBase 4 | def build 5 | data = {} 6 | data[l(:label_total_commits)] = total_commits 7 | data[l(:label_total_contributors)] = committers 8 | data[l(:label_first_commit_date)] = format_date first_commit.commit_date 9 | data[l(:label_latest_commit_date)] = format_date last_commit.commit_date 10 | data[l(:label_active_for)] = "#{active_for} #{l :days, active_for}" 11 | data[l(:label_average_commit_per_day)] = average_commit_per_day 12 | data[l(:label_average_contributor_commits)] = average_contributor_commits 13 | data 14 | end 15 | 16 | private 17 | 18 | def total_commits 19 | @total_commits ||= all_changesets.count 20 | end 21 | 22 | def committers 23 | @committers ||= redmine_committers + external_committers 24 | end 25 | 26 | def first_commit 27 | @first_commit ||= all_changesets.order(commit_date: :asc).first 28 | end 29 | 30 | def last_commit 31 | @last_commit ||= all_changesets.order(commit_date: :asc).last 32 | end 33 | 34 | def active_for 35 | @active_for ||= (last_commit.commit_date - first_commit.commit_date).to_i 36 | end 37 | 38 | def average_commit_per_day 39 | @average_commit_per_day ||= total_commits.fdiv(active_for).round(2) 40 | end 41 | 42 | def average_contributor_commits 43 | @average_contributor_commits ||= total_commits.fdiv(committers).round(2) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /app/views/gitolite_public_keys/_ssh_keys.html.slim: -------------------------------------------------------------------------------- 1 | table.list 2 | - if ssh_keys.empty? 3 | tr 4 | td 5 | label = l :label_no_public_keys 6 | - else 7 | tbody 8 | - ssh_keys.each do |key| 9 | tr class="#{'highlight' if @gitolite_public_key == key}" 10 | td.name 11 | - used_in = [] 12 | - if defined?(deploy_keys) && deploy_keys 13 | - key.repository_deployment_credentials.each do |deployment_credential| 14 | - used_in << deployment_credential.repository.name 15 | - if used_in.count.positive? 16 | = tag.acronym h(key), title: used_in.sort.join(', ') 17 | - else 18 | = h key 19 | td.description 20 | i.fas.fa-check style="color: green; margin-left: 5px; margin-right: 5px;" 21 | = key.fingerprint 22 | - if params[:id] 23 | td style="text-align: left;" 24 | = key.gitolite_path 25 | - if defined?(deploy_keys) && deploy_keys 26 | td.tick 27 | - if key.delete_when_unused 28 | i.fas.fa-minus-circle title="#{l :label_deployment_credential_delete_when_unused}" 29 | td.buttons style="width: 10%;" 30 | = link_to l(:button_delete), public_key_path(key, user_id: params[:id]), 31 | method: 'delete', 32 | class: 'icon icon-del', 33 | data: { confirm: l(:text_gitolite_key_destroy_confirmation, title: keylabel(key)) } 34 | -------------------------------------------------------------------------------- /app/forms/plugin_settings_validation/mailing_list_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PluginSettingsValidation 4 | module MailingListConfig 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | # Git Mailing List Config 9 | add_accessor :gitolite_notify_by_default, 10 | :gitolite_notify_global_prefix, 11 | :gitolite_notify_global_sender_address, 12 | :gitolite_notify_global_include, 13 | :gitolite_notify_global_exclude 14 | 15 | before_validation do 16 | self.gitolite_notify_global_include = filter_email_list gitolite_notify_global_include 17 | self.gitolite_notify_global_exclude = filter_email_list gitolite_notify_global_exclude 18 | end 19 | 20 | validates :gitolite_notify_by_default, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS } 21 | validates :gitolite_notify_global_sender_address, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } 22 | validate :git_notifications_intersection 23 | end 24 | 25 | private 26 | 27 | # Validate intersection of global_include/global_exclude 28 | # 29 | def git_notifications_intersection 30 | intersection = gitolite_notify_global_include & gitolite_notify_global_exclude 31 | return unless intersection.count.positive? 32 | 33 | errors.add :base, 'duplicated entries detected in gitolite_notify_global_include and gitolite_notify_global_exclude' 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/root_spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if ENV['COVERAGE'] 4 | require 'simplecov' 5 | 6 | ## Start Simplecov 7 | SimpleCov.start 'rails' do 8 | add_group 'Redmine Git Hosting', 'plugins/redmine_git_hosting' 9 | end 10 | end 11 | 12 | unless defined? 'HOME_BASE_DIR' 13 | HOME_BASE_DIR = RUBY_PLATFORM.include?('darwin') ? '/Users' : '/home' 14 | end 15 | 16 | ## Load Redmine App 17 | ENV['RAILS_ENV'] = 'test' 18 | require File.expand_path "#{File.dirname __FILE__}/../config/environment" 19 | require 'rspec/rails' 20 | 21 | ## Load FactoryBots factories 22 | Dir[Rails.root.join('plugins/*/spec/factories/**/*.rb')].sort.each { |f| require f } 23 | 24 | Dir[Rails.root.join('plugins/*/spec/support/**/*.rb')].sort.each { |f| require f } 25 | 26 | ## Configure RSpec 27 | RSpec.configure do |config| 28 | config.include FactoryBot::Syntax::Methods 29 | 30 | config.infer_spec_type_from_file_location! 31 | 32 | config.color = true 33 | config.fail_fast = false 34 | 35 | config.expect_with :rspec do |c| 36 | c.syntax = :expect 37 | end 38 | 39 | config.before :suite do 40 | DatabaseCleaner.clean_with :truncation 41 | end 42 | 43 | config.before :each do 44 | DatabaseCleaner.strategy = :transaction 45 | end 46 | 47 | config.before :each do 48 | DatabaseCleaner.start 49 | end 50 | 51 | config.after :each do 52 | DatabaseCleaner.clean 53 | end 54 | end 55 | 56 | # Disable Test::Unit automatic runner 57 | Test::Unit::AutoRunner.need_auto_run = false if defined?(Test::Unit::AutoRunner) 58 | -------------------------------------------------------------------------------- /lib/redmine_git_hosting/plugins/gitolite_plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RedmineGitHosting::Plugins 4 | class GitolitePlugin 5 | class << self 6 | def plugins 7 | @plugins ||= [] 8 | end 9 | 10 | def all_plugins 11 | sweepers + extenders 12 | end 13 | 14 | def sweepers 15 | plugins.find { |p| p.name.demodulize == 'BaseSweeper' }.subclasses 16 | end 17 | 18 | def extenders 19 | plugins.find { |p| p.name.demodulize == 'BaseExtender' }.subclasses 20 | end 21 | 22 | def inherited(klass) 23 | @plugins ||= [] 24 | @plugins << klass 25 | end 26 | end 27 | 28 | private 29 | 30 | def logger 31 | RedmineGitHosting.logger 32 | end 33 | 34 | def repository_empty? 35 | RedmineGitHosting::Commands.sudo_repository_empty? gitolite_repo_path 36 | end 37 | 38 | def directory_exists?(dir) 39 | RedmineGitHosting::Commands.sudo_dir_exists? dir 40 | end 41 | 42 | def sudo_git(*params) 43 | cmd = RedmineGitHosting::Commands.sudo_git_args_for_repo(gitolite_repo_path, git_args).concat(params) 44 | RedmineGitHosting::Commands.capture cmd, **git_opts 45 | end 46 | 47 | # You may override this method to prepend args like environment variables 48 | # to the git command. 49 | # 50 | def git_args 51 | [] 52 | end 53 | 54 | # You may override this method to pass opts to Open3.capture. 55 | # 56 | def git_opts 57 | {} 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /app/models/repository_protected_branche.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RepositoryProtectedBranche < ActiveRecord::Base 4 | include Redmine::SafeAttributes 5 | VALID_PERMS = ['RW+', 'RW', 'R', '-'].freeze 6 | DEFAULT_PERM = 'RW+' 7 | 8 | acts_as_positioned 9 | 10 | ## Attributes 11 | safe_attributes 'path', 'permissions', 'position' 12 | 13 | ## Relations 14 | belongs_to :repository 15 | has_many :protected_branches_members, foreign_key: :protected_branch_id, dependent: :destroy 16 | has_many :members, through: :protected_branches_members, source: :principal 17 | 18 | ## Validations 19 | validates :repository_id, presence: true 20 | validates :path, presence: true, uniqueness: { case_sensitive: true, scope: %i[permissions repository_id] } 21 | validates :permissions, presence: true, inclusion: { in: VALID_PERMS } 22 | 23 | ## Scopes 24 | default_scope { order position: :asc } 25 | scope :sorted, -> { order :path } 26 | 27 | class << self 28 | def clone_from(parent) 29 | parent = find_by id: parent unless parent.is_a? RepositoryProtectedBranche 30 | copy = new 31 | copy.attributes = parent.attributes 32 | copy.repository = parent.repository 33 | copy 34 | end 35 | end 36 | 37 | # Accessors 38 | # 39 | def users 40 | members.select { |m| m.instance_of? User }.uniq 41 | end 42 | 43 | def groups 44 | members.select { |m| m.instance_of? Group }.uniq 45 | end 46 | 47 | def allowed_users 48 | users.map(&:gitolite_identifier).sort 49 | end 50 | end 51 | --------------------------------------------------------------------------------