From dfb8ac4853ce2a16ecaeca8dcd2a37fa0b3ecf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 19 Aug 2015 09:08:26 +0200 Subject: [PATCH] Add basic checkout instructions This commit adds very basic checkout instructions similar to `openproject-checkout` to repositories. Issues: * Uses table style for checkout component, as exact styling is unclear * Adds compressed table style with less padding * Checkout settings are best organized in a Hash with keys per SCM * vendor. Using default settings, a lot of new setting values would be introduced * Checks for Hash in SettingsController * Currently can't use SettingsHelper, as it requires single setting values --- app/assets/javascripts/settings.js.erb | 16 +- app/assets/stylesheets/content/_forms.lsg | 19 +++ app/assets/stylesheets/content/_forms.sass | 6 + app/controllers/repositories_controller.rb | 5 + app/controllers/settings_controller.rb | 2 + app/models/repository.rb | 4 + .../scm/checkout_instructions_service.rb | 142 ++++++++++++++++++ .../_checkout_instructions.html.erb | 29 ++++ app/views/repositories/empty.html.erb | 1 + app/views/repositories/show.html.erb | 1 + app/views/settings/_repositories.html.erb | 4 +- .../settings/_repositories_checkout.html.erb | 36 +++++ config/locales/en.yml | 16 +- config/settings.yml | 13 ++ lib/open_project/scm/adapters/base.rb | 2 + .../scm/adapters/checkout_instructions.rb | 79 ++++++++++ lib/open_project/scm/adapters/git.rb | 4 + lib/open_project/scm/adapters/subversion.rb | 8 + lib/open_project/scm/manageable_repository.rb | 2 - 19 files changed, 383 insertions(+), 6 deletions(-) create mode 100644 app/services/scm/checkout_instructions_service.rb create mode 100644 app/views/repositories/_checkout_instructions.html.erb create mode 100644 app/views/settings/_repositories_checkout.html.erb create mode 100644 lib/open_project/scm/adapters/checkout_instructions.rb diff --git a/app/assets/javascripts/settings.js.erb b/app/assets/javascripts/settings.js.erb index 3ce327fae6e..49394ba32e9 100644 --- a/app/assets/javascripts/settings.js.erb +++ b/app/assets/javascripts/settings.js.erb @@ -36,7 +36,7 @@ See doc/COPYRIGHT.rdoc for more details. /** Sync SCM vendor select when enabled SCMs are changed */ - $('[name="settings[enabled_scm][]"]').change(function () { + $('[name="settings[enabled_scm][]"]').change(function() { var wasDisabled = !this.checked, vendor = this.value, select = $('#settings_repositories_automatic_managed_vendor'), @@ -51,7 +51,19 @@ See doc/COPYRIGHT.rdoc for more details. if (wasDisabled && option.prop('selected')) { select.val(''); } - }); + + /** Toggle repository checkout fieldsets required when option is disabled */ + $('.settings-repositories--checkout-toggle').change(function() { + var wasChecked = this.checked, + fieldset = $(this).closest('fieldset'); + + fieldset + .find('input,select,textarea') + .filter(':not([type=checkbox])') + .filter(':not([type=hidden])') + .removeAttr('required') // Rails 4.0 still seems to use attribute + .prop('required', wasChecked); + }) }); }(jQuery)); diff --git a/app/assets/stylesheets/content/_forms.lsg b/app/assets/stylesheets/content/_forms.lsg index 26236c83744..86c7022e6b9 100644 --- a/app/assets/stylesheets/content/_forms.lsg +++ b/app/assets/stylesheets/content/_forms.lsg @@ -38,6 +38,25 @@ ``` +## Forms: Bordered compressed style (less padding) + +``` +
+
+ +
+
+ +
+
+
+ +
+ + +
+``` + ## Forms: Standard layout ``` diff --git a/app/assets/stylesheets/content/_forms.sass b/app/assets/stylesheets/content/_forms.sass index 04d4e0ef25e..49e2df66f66 100644 --- a/app/assets/stylesheets/content/_forms.sass +++ b/app/assets/stylesheets/content/_forms.sass @@ -58,6 +58,9 @@ $form--field-types: (text-field, text-area, select, check-box, radio-button, ran background-color: $content-form-bg-color border: $content-form-border + &.-compressed + padding: 10px 20px 0 20px + .form--separator border: 0 border-bottom: 1px solid $content-form-separator-color @@ -328,6 +331,9 @@ fieldset.form--fieldset @extend %form--field-after-container font-style: italic + &.-no-italic + font-style: normal + .form--field-extra-actions @extend %form--field-after-container @include grid-visible-overflow diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 9105aaef755..17d0889a780 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -308,6 +308,11 @@ class RepositoriesController < ApplicationController @repository = @project.repository (render_404; return false) unless @repository + # Prepare checkout instructions + # available on all pages (even empty!) + @instructions = ::Scm::CheckoutInstructionsService.new(@repository) + + # Asserts repository availability, or renders an appropriate error @repository.scm.check_availability! @path = params[:path] || '' diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 20ac62df270..2405e90eafb 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -48,6 +48,8 @@ class SettingsController < ApplicationController if value.is_a?(Array) # remove blank values in array settings value.delete_if(&:blank?) + elsif value.is_a?(Hash) + value.delete_if { |_, v| v.blank? } else value = value.strip end diff --git a/app/models/repository.rb b/app/models/repository.rb index fb4e671ca4d..4bcd18b904f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -129,6 +129,10 @@ class Repository < ActiveRecord::Base false end + def supports_checkout_info? + true + end + def entry(path = nil, identifier = nil) scm.entry(path, identifier) end diff --git a/app/services/scm/checkout_instructions_service.rb b/app/services/scm/checkout_instructions_service.rb new file mode 100644 index 00000000000..94b7615fee5 --- /dev/null +++ b/app/services/scm/checkout_instructions_service.rb @@ -0,0 +1,142 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +## +# Implements a repository service for building checkout instructions if supported +class Scm::CheckoutInstructionsService + attr_reader :repository, :user + + def initialize(repository, user = User.current) + @repository = repository + @user = user + end + + ## + # Retrieve the checkout URL using the repository vendor information + # It may additionally set a path parameter, if the repository supports subtree checkout + def checkout_url(path = nil) + repository.scm.checkout_url(repository, checkout_base_url, path) + end + + ## + # Returns the checkout command from SCM adapter + # (e.g., `git clone`) + def checkout_command + repository.scm.checkout_command + end + + ## + # Returns the checkout base URL as defined in settings. + def checkout_base_url + checkout_settings['base_url'] + end + + ## + # Returns the instructions defined in the settings. + def instructions + checkout_settings['text'] + end + + ## + # Returns true when the checkout URL may target a subtree of the repository. + def subtree_checkout? + repository.scm.subtree_checkout? + end + + ## + # Returns whether the repository supports showing checkout information + # and has been configured for it. + def available? + checkout_enabled? && + repository.supports_checkout_info? && + checkout_base_url.present? + end + + def checkout_enabled? + checkout_settings['enabled'].to_i > 0 + end + + ## + # Determines whether permissions for the given repository + # are available. + def permission? + repository.managed? + end + + ## + # Returns one of the following permission symbols for the given user + # + # - :readwrite: When user is allowed to read and commit (:commit_access) + # - :read: When user is allowed to checkout the repository, but not commit (:browse_repository) + # - :none: Otherwise + # + # Note that this information is only applicable when the repository is managed, + # because otherwise OpenProject does not control the repository permissions. + # Use +permission?+ to check whether this is the case. + # + def permission + project = repository.project + if user.allowed_to?(:commit_access, project) + :readwrite + elsif user.allowed_to?(:browse_repository, project) + :read + else + :none + end + end + + ## + # Returns whether the given user may checkout the repository + # + # Note that this information is only applicable when the repository is managed, + # because otherwise OpenProject does not control the repository permissions. + # Use +permission?+ to check whether this is the case. + def may_checkout? + [:readwrite, :read].include?(permission) + end + + ## + # Returns whether the given user may commit to the repository + # + # Note that this information is only applicable when the repository is managed, + # because otherwise OpenProject does not control the repository permissions. + # Use +permission?+ to check whether this is the case. + def may_commit? + permission == :readwrite + end + + private + + def checkout_settings + @settings ||= begin + hash = Setting.repository_checkout_data[repository.vendor.to_s] + hash.presence || {} + end + end +end diff --git a/app/views/repositories/_checkout_instructions.html.erb b/app/views/repositories/_checkout_instructions.html.erb new file mode 100644 index 00000000000..26d0ccae779 --- /dev/null +++ b/app/views/repositories/_checkout_instructions.html.erb @@ -0,0 +1,29 @@ +<% if instructions.present? && instructions.available? %> +
+
+ <%= l('repositories.checkout.instructions') %> +
+ +
+
+ <%= instructions.checkout_command %> +
+
+ +
+
+
+ <%= simple_format instructions.instructions %> + <% if instructions.permission? %> +

+ <%= l('repositories.checkout.access_permission') %> + <%= l("repositories.checkout.access.#{instructions.permission}") %> + <% end %> +

+
+
+
+<% end %> diff --git a/app/views/repositories/empty.html.erb b/app/views/repositories/empty.html.erb index 71eb1c26c5f..9712c827c3c 100644 --- a/app/views/repositories/empty.html.erb +++ b/app/views/repositories/empty.html.erb @@ -33,6 +33,7 @@ See doc/COPYRIGHT.rdoc for more details. message: l('repositories.errors.empty_repository')) %>

+<%= render partial: 'checkout_instructions', locals: { repository: @repository, instructions: @instructions } %> <%= call_hook(:view_repositories_show_contextual, { repository: @repository, project: @project }) %> <%= call_hook(:repositories_below_empty_warning, diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index f2ac9ba776c..70c77346cac 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -27,6 +27,7 @@ See doc/COPYRIGHT.rdoc for more details. ++#%> +<%= render partial: 'checkout_instructions', locals: { repository: @repository, instructions: @instructions } %> <%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> diff --git a/app/views/settings/_repositories.html.erb b/app/views/settings/_repositories.html.erb index fb99733757a..0d80a96f304 100644 --- a/app/views/settings/_repositories.html.erb +++ b/app/views/settings/_repositories.html.erb @@ -87,7 +87,9 @@ See doc/COPYRIGHT.rdoc for more details.
<%= setting_text_field :repository_log_display_limit, :size => 6 %>
<%= setting_check_box :repository_authentication_caching_enabled %>
-
<%= l(:text_work_packages_ref_in_commit_messages) %> + <%= render partial: 'repositories_checkout' %> +
+ <%= l(:text_work_packages_ref_in_commit_messages) %>
<%= setting_text_field :commit_ref_keywords, :size => 30 %>
<%= l(:text_comma_separated) %>
diff --git a/app/views/settings/_repositories_checkout.html.erb b/app/views/settings/_repositories_checkout.html.erb new file mode 100644 index 00000000000..6e76457d2aa --- /dev/null +++ b/app/views/settings/_repositories_checkout.html.erb @@ -0,0 +1,36 @@ +
+ <%= l('repositories.checkout.instructions') %> + <% OpenProject::Scm::Manager.registered.each do |vendor, klass| %> + <% vendor = vendor.to_s %> + <% setting_base = "settings[repository_checkout_data][#{vendor}]" %> + <% enabled = Setting.repository_checkout_data[vendor]['enabled'].to_i > 0 %> + + <% end %> +
diff --git a/config/locales/en.yml b/config/locales/en.yml index e2ed3f8781e..4866d83dcbd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1286,7 +1286,18 @@ en: repositories: autofetch_information: "Check this if you want repositories to be updated automatically when accessing the repository module page.\nThis encompasses the retrieval of commits from the repository and refreshing the required disk storage." - checkout_instructions: "Checkout instructions" + checkout: + access: + readwrite: 'Read + Write' + read: 'Read-only' + none: 'No checkout access, you may only view the repository through this application.' + access_permission: 'Your permissions on this repository:' + url: "Checkout URL" + base_url_text: "The base URL to use for generating checkout URLs (e.g., https://myserver.example.org/repos/).\nNote: The base URL is only used for rewriting checkout URLs in managed repositories. Other repositories are not altered" + enable_instructions_text: "Displays checkout instructions defined below on all repository-related pages." + instructions: "Checkout instructions" + text_instructions: "This text is displayed alongside the checkout URL for guidance on how to check out the repository." + not_available: "Checkout instructions are not available for this repository" create_managed_delay: "Please note: The repository is managed, it is created asynchronously on the disk and will be available shortly." create_successful: "The repository has been registered." delete_sucessful: "The repository has been deleted." @@ -1413,6 +1424,9 @@ en: setting_repositories_encodings: "Repositories encodings" setting_repository_authentication_caching_enabled: "Enable caching for authentication request of version control software" setting_repository_storage_cache_minutes: "Repository disk size cache" + setting_repository_checkout_display: "Show checkout instructions" + setting_repository_checkout_base_url: "Checkout base URL" + setting_repository_checkout_text: "Checkout instruction text" setting_repository_log_display_limit: "Maximum number of revisions displayed on file log" setting_rest_api_enabled: "Enable REST web service" setting_self_registration: "Self-registration" diff --git a/config/settings.yml b/config/settings.yml index f6a811c8e0b..2dc2fdca5f7 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -280,6 +280,19 @@ journal_aggregation_time_minutes: repository_storage_cache_minutes: default: 720 format: int +repository_checkout_data: + serialized: true + default: + git: + enabled: 0 + text: |- + The data contained in this repository can be downloaded to your computer with Git using the checkout information below. + Please consult the documentation of Git if you need more information on the checkout procedure and available clients. + subversion: + enabled: 0 + text: |- + The data contained in this repository can be downloaded to your computer with Subversion using the checkout information below. + Please consult the documentation of Subversion if you need more information on the checkout procedure and available clients. api_max_page_size: format: int default: 500 diff --git a/lib/open_project/scm/adapters/base.rb b/lib/open_project/scm/adapters/base.rb index c4a6d484a91..fc6251e5c67 100644 --- a/lib/open_project/scm/adapters/base.rb +++ b/lib/open_project/scm/adapters/base.rb @@ -33,6 +33,8 @@ module OpenProject module Scm module Adapters class Base + include CheckoutInstructions + attr_accessor :url, :root_url def self.vendor diff --git a/lib/open_project/scm/adapters/checkout_instructions.rb b/lib/open_project/scm/adapters/checkout_instructions.rb new file mode 100644 index 00000000000..f3f37d28569 --- /dev/null +++ b/lib/open_project/scm/adapters/checkout_instructions.rb @@ -0,0 +1,79 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +module OpenProject + module Scm + module Adapters + module CheckoutInstructions + ## + # Returns the checkout URL for the given repository + # based on this adapter's knowledge + def checkout_url(repository, base_url, path) + checkout_url = if local? + ::URI.join(with_trailing_slash(base_url), + repository.repository_identifier) + else + url + end + + if subtree_checkout? && path.present? + ::URI.join(with_trailing_slash(checkout_url), path) + else + checkout_url + end + end + + ## + # Returns whether the SCM vendor supports subtree checkout + def subtree_checkout? + false + end + + ## + # Returns the checkout command for this vendor + def checkout_command + raise NotImplementedError + end + + private + + ## + # Ensure URL has a trailing slash. + # Needed for base URL, because URI.join will otherwise + # assume a relative resource. + def with_trailing_slash(url) + url = url.to_s + + url << '/' unless url.end_with?('/') + url + end + end + end + end +end diff --git a/lib/open_project/scm/adapters/git.rb b/lib/open_project/scm/adapters/git.rb index a5d41677bf8..44ec9b770de 100644 --- a/lib/open_project/scm/adapters/git.rb +++ b/lib/open_project/scm/adapters/git.rb @@ -43,6 +43,10 @@ module OpenProject @path_encoding = path_encoding.presence || 'UTF-8' end + def checkout_command + 'git clone' + end + def client_command @client_command ||= self.class.config[:client_command] || 'git' end diff --git a/lib/open_project/scm/adapters/subversion.rb b/lib/open_project/scm/adapters/subversion.rb index 8255e0e9e72..fefe1e0579f 100644 --- a/lib/open_project/scm/adapters/subversion.rb +++ b/lib/open_project/scm/adapters/subversion.rb @@ -80,6 +80,14 @@ module OpenProject @password = password end + def checkout_command + 'svn checkout' + end + + def subtree_checkout? + true + end + ## # Checks the status of this repository and throws unless it can be accessed # correctly by the adapter. diff --git a/lib/open_project/scm/manageable_repository.rb b/lib/open_project/scm/manageable_repository.rb index 9e0725efc52..f098ca28988 100644 --- a/lib/open_project/scm/manageable_repository.rb +++ b/lib/open_project/scm/manageable_repository.rb @@ -100,8 +100,6 @@ module OpenProject "file://#{managed_repository_path}" end - protected - ## # Repository relative path from scm managed root. # Will be overridden by including models to, e.g.,